From a4d3a66265b3d2de530b02a182868ee99bf4be17 Mon Sep 17 00:00:00 2001 From: king Date: Wed, 14 Oct 2026 10:23:02 +0800 Subject: [PATCH 1/6] docs(spec): paracross validator governance + cross-chain circuit breaker (phase 1) Design for replacing the parachain's permissionless, key-based validator group changes with a main-chain enforced state machine: - validator set changes are proposed by the parachain governance and only take effect via an explicit Activate after a risk-tiered delay: above the locked-asset threshold they require autonomy approval plus a 1 day lock (cancellable in the window), below it a 7 day lock without approval. The commit verification read path is unchanged, so main chain and parachain always agree on the active set. - governance can cut off cross-chain fund flow for a given parachain in both directions as an emergency stop. - phase 2 roadmap (challenge window + mavl witness fraud proofs, sampling and slashing, zk validity proofs) and the residual risk of still permissionless bootstrap are documented. Sections 1-6 were reviewed and confirmed section by section before this spec was written. --- ...4-paracross-validator-governance-design.md | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md diff --git a/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md b/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md new file mode 100644 index 000000000..1977446c1 --- /dev/null +++ b/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md @@ -0,0 +1,319 @@ +# 平行链验证人来源治理化 + 主链跨链急停(Phase 1 设计) + +- 日期:2026-10-14 +- 状态:设计已逐节确认(第 1–6 节),待编写实施计划 +- 相关文档:`docs/security/paracross-nodegroup-title-takeover-analysis.md`("标题前缀接管"漏洞分析与修复,PR #1326) +- 相关代码:`plugin/dapp/paracross`(执行器)、`plugin/consensus/para`(平行链共识)、`plugin/dapp/rollup`、autonomy `plugin/dapp/autonomy` + +## 1. 背景与动机 + +paracross 的跨链权限模型把「节点组(super node group)」当作唯一权威:主链只校验 commit 是否由该组 2/3 以上 BLS 聚合签名、高度与哈希是否连续,**从不判断平行链的状态转移本身是否合法**。 + +这带来两个结构性问题: + +1. **状态只由平行链自证**:主链无法独立判断"这条平行链新产生的状态是否由其交易正确推导而来",只能相信签名集合。 +2. **验证人身份等价于"持有私钥 + 自建节点组"**:`ForkParaFreeRegister` 之后,首次建组是自由注册 + 自审批;已有集合的成员变更由组内 2/3 投票决定。因此控制(或买通)一组私钥,或抢先注册一个无人认领的 title,就能左右跨链结果。 + +2026-10-13 的"标题前缀接管"事件(`docs/security/`)正是该模型被利用的实例:攻击者自建节点组后,用伪造 title 的 commit 触发真实平行链的提现,盗走约 161.8 万 BTY。 + +本设计不追求一次性彻底去信任化。Phase 1 的目标是把"更换验证人"从**内部投票即生效**改成**平行链提案 + 主链分档治理/时间锁 + 显式激活**,并给治理一个**跨链急停**开关;主链对状态的独立验证(挑战期/欺诈证明/罚没/zk)作为 Phase 2 在同一文档中给出路线与接口。 + +## 2. 威胁模型与目标 + +**威胁模型** + +- 攻击者可以取得或买通某条平行链当前验证人集合的私钥(含 BLS 私钥); +- 攻击者可以自建/抢先注册无人认领的平行链 title 节点组; +- 攻击者可能提交伪造的平行链状态与 commit(占用主链算力与状态空间); +- 治理(autonomy board)被部分或完全控制属于需要评估的风险,但不是"可以随意盗币"的前提。 + +**Phase 1 目标(本期交付)** + +1. 验证人集合的**任何更换**都有最短反应窗口(1 天或 7 天),窗口内治理与提案人都能撤销; +2. 高价值平行链(锁定资产 > 阈值)的更换**必须**经治理批准; +3. 治理可以**切断**指定平行链的跨链资金来往(双向),作为止血开关; +4. 以上机制不改变 commit 校验、BLS 阈值与提现的最终性语义。 + +**Phase 1 不承诺(明确不解决)** + +- 不承诺"多数验证人联合作恶也无法盗取资产"——这需要 Phase 2 的主链独立验证; +- 不改变"首次建组自由注册"的现状(见第 13 节残留缺口); +- 不引入质押与罚没(Phase 2 罚没的前置项)。 + +## 3. 范围 + +| 项 | Phase 1 | Phase 2(仅路线,本期不实现) | +|---|---|---| +| 验证人集合变更 | 治理闸门 + 时间锁 + 显式激活 | 与罚没挂钩 | +| 跨链急停 | 治理可控的双向开关 | 与挑战期/自动风控联动 | +| 主链状态验证 | 不做 | 挑战期 + 见证式欺诈证明;抽样审计;zk 有效性证明 | +| commit/BLS/提现最终性 | 不变 | 提现引入挑战期(最终性延后) | + +**非目标**:把平行链共识替换为主链共识;主链全量重执行平行链;在 Phase 1 引入 zk 电路。 + +## 4. 代码现状核对(设计依据) + +| 事实 | 位置 | +|---|---| +| 节点组地址集合存在 `mavl-paracross-nodes-title-` | `executor/kv.go:calcParaNodeGroupAddrsKey` | +| 每个节点的 BLS 公钥与状态存在 `mavl-paracross-nodes-titleAddr-<title>-<addr>` | `executor/superaccount.go:updateNodeAddrStatus`、`action.go:getAddrBlsPubKey` | +| 首次建组:`NodeGroupConfig` op=1/2(Apply/Approve),`ForkParaFreeRegister` 后自审批 | `executor/superaccount.go:nodeGroupApply/nodeGroupApprove/checkApproveOpNew` | +| 成员变更:`NodeConfig`(NewApply/Vote/Quit/Cancel/Modify),2/3 内部投票达成后改写集合 | `executor/superaccount.go:nodeJoin/nodeVote/nodeQuit/updateNodeGroup` | +| 监督节点组同构,也是必须共同达成 2/3 的一类验证人 | `executor/supervisionaccount.go` | +| 平行链共识模块**本地查询**自己的节点组状态(主链与平行链各存一份) | `plugin/consensus/para/paracommitmsg.go:getNodeGroupAddrs`(注释:"node group 会在主链和平行链都同时配置,只本地查询就可以") | +| 主链 commit 校验读主链那份集合 | `executor/action.go:getNodesGroup` | +| 治理判定复用 autonomy 与超级管理员 | `executor/superaccount.go:checkApproveOpOld/checkApproveOpNew`(`IsAutonomyApprovedItem`) | +| 链配置参数参与共识判定的先例 | `executor/superaccount.go:nodeGroupCoinsFrozen`(`nodeGroupFrozenCoins`) | +| 首次建组仅按"完全同名 key"判重 | `executor/superaccount.go:checkNodeGroupExist` | + +**结论**:节点组不是本地配置,而是**主链与平行链各一份、必须保持一致**的链上状态。任何验证人变更都必须在两侧确定性同时生效,否则主链与平行链对"谁有权签名"的判断分叉,共识停摆。 + +## 5. 总体架构 + +### 5.1 组件 + +1. **`ValidatorSetChange` 状态机**(paracross 执行器内,主链与平行链各执行一份): + 提案 →(可选治理批准)→ 时间锁 → 激活;激活时改写节点组地址集合与每节点状态(含 BLS 公钥),并递增集合版本号。 +2. **治理闸门**:复用 autonomy(`IsAutonomyApprovedItem`)与超级管理员;治理判定**只在主链做**,平行链按主链执行结果跟随(与现有 `NodeGroupApprove` 完全相同的模式)。 +3. **跨链急停(circuit breaker)**:title 维度的双向跨链开关,治理可切断/恢复。 +4. **分叉与升级**:主链新分叉高度;平行链按既有 `mainFork*` 约定配置同一主链高度。 + +### 5.2 核心不变式 + +> **任一时刻,"当前有效验证人集合"只由已被 `Activate` 的版本决定,绝不按高度动态推导;主链与平行链读到的是同一个集合。** + +这是选择"延迟到点后由显式激活交易改写"(而非"按高度动态解析")的根本原因:commit 校验的读路径(`getNodesGroup`)完全不变,共识风险最小。 + +### 5.3 数据流 + +``` +平行链治理(现有节点组内部 2/3 投票,或平行链链上治理合约) + └─> 主链交易 op=Propose(execer = user.p.<title>.paracross, 新集合 + BLS 公钥) + ├─ 快照该链锁定资产 → 定档 + ├─ 高价值档(>阈值): 必须 op=Approve(autonomy 项) → 生效高度 = 批准高度 + 1 天 + ├─ 低价值档(≤阈值): 无需批准 → 生效高度 = 提案高度 + 7 天 + └─ 窗口内: op=Cancel(治理或提案人) 可撤销 + └─> 到点后 op=Activate(任何人可发) + └─ 改写 mavl-paracross-nodes-title-<title> 与每节点状态, 版本号 +1 +``` + +## 6. 数据结构与接口 + +### 6.1 复用现有消息,不新增 protobuf/action + +`ParaNodeGroupConfig` 已包含 `Title / Op / Id / Addrs / BlsPubKeys / AutonomyItemID`,足够表达新集合与治理项。因此**在同一个 action 上新增 op**,不新增 `ParacrossAction` oneof 字段、不重新生成 `.pb.go`、不改 `GetTypeMap` 与 `allow()`: + +```go +// plugin/dapp/paracross/types/paracross.go 的 ParacrossNodeGroup* 常量后追加 +ParacrossValidatorSetPropose = 5 +ParacrossValidatorSetApprove = 6 +ParacrossValidatorSetCancel = 7 +ParacrossValidatorSetActivate = 8 +ParacrossCrossChainDisable = 9 +ParacrossCrossChainEnable = 10 +``` + +字段映射:`Title`=平行链 title;`Addrs`=新集合地址(逗号分隔);`BlsPubKeys`=与 `Addrs` 一一对应的 BLS 公钥;`Id`=提案 id;`AutonomyItemID`=治理批准项 id。 + +### 6.2 新增 KV(沿用 `mavl-paracross-` 前缀) + +| Key | 值 | 写入者 | +|---|---|---| +| `mavl-paracross-valset-change-<title>` | 当前待生效提案(第 6.3 节结构) | Propose/Approve/Cancel | +| `mavl-paracross-valset-version-<title>` | 当前生效版本号 `uint64` | Activate | +| `mavl-paracross-valset-history-<title>-<version>` | 该版本的集合与 `ActiveFromMainHeight` | Activate(供 Phase 2 追责) | +| `mavl-paracross-crosschain-switch-<title>` | 急停状态结构 | Disable/Enable | +| `mavl-paracross-nodes-title-<title>` | **现有 key,仅 Activate 改写** | Activate | +| `mavl-paracross-nodes-titleAddr-<title>-<addr>` | **现有 key**,每节点状态与 BLS 公钥 | Activate(复用 `nodeGroupCreate` 的写入逻辑) | + +### 6.3 状态对象 + +```go +type ParaValidatorSetChange struct { + Id string // 提案 id(Propose 交易 hash) + Title string + NewAddrs string // "addr1,addr2,..." + NewBlsPubKeys string // "pub1,pub2,..." + ProposeMainHeight int64 + LockedAssets int64 // Propose 时快照 + Tier int32 // 0=低价值(≤阈值) 1=高价值(>阈值) + Approved bool + ApprovalMainHeight int64 + AutonomyItemID string + EffectiveMainHeight int64 // 见 6.4 + Status int32 // 0=Pending 1=Approved 2=Cancelled 3=Activated + Version uint64 // 激活后版本号 +} + +type ParaCrossChainSwitch struct { + Disabled bool + Reason string + SetMainHeight int64 + AutonomyItemID string +} +``` + +### 6.4 参数与默认值(从链配置读取,口径同 `nodeGroupFrozenCoins`) + +| 参数 | 默认值 | 说明 | +|---|---|---| +| `validatorSetHighValueThreshold` | `100000` × coinPrecision | 高/低价值分档阈值(默认 10 万 BTY) | +| `validatorSetDelayBlocksHigh` | `8640` | 治理批准后的锁定期(按 10s/块 ≈ 1 天) | +| `validatorSetDelayBlocksLow` | `60480` | 未获治理批准的低价值档锁定期(≈ 7 天) | + +延迟一律按**主链区块高度**计算,不使用墙钟时间(共识确定性): + +- 已批准(高或低价值档):`EffectiveMainHeight = ApprovalMainHeight + validatorSetDelayBlocksHigh` +- 未批准且低价值档:`EffectiveMainHeight = ProposeMainHeight + validatorSetDelayBlocksLow` +- 高价值档未获批准:**不可激活**(`Activate` 失败) + +## 7. 状态机 + +### 7.1 状态与转移 + +``` + Propose Approve(治理) + (无提案) ──────────────> PENDING ──────────────────────────> APPROVED + │ ▲ │ + │ └────────── Cancel ────────────────┤ + │ (治理或提案人, Activate 前任意时刻) + ▼ ▼ + CANCELLED Activate + (mainHeight ≥ Effective 且 (低价值 或 已批准)) + │ + ▼ + ACTIVATED + (改写集合 + 每节点状态, 版本号 +1) +``` + +### 7.2 规则与幂等 + +1. **Propose 前置**:`title` 必须已有生效集合(首次建组仍走既有 op=1/2 流程,见第 8 节);同一 title 只允许一个 `PENDING/APPROVED` 提案,重复 Propose 直接失败;`Addrs` 与 `BlsPubKeys` 数量必须相等且非空;地址去重后排序,保证两侧写入字节一致;新集合与当前生效集合完全相同则 Propose 失败(空变更)。 + 提案携带的是**绝对新集合**(不是增量)。由于同一时刻只有一个待生效提案、且生效集合只能由 `Activate` 改写,提案期间当前集合不会变化,`Activate` 写入的集合与提案内容必然一致。 +2. **Approve 前置**:提案存在且为 `PENDING`;`IsAutonomyApprovedItem(AutonomyItemID, Id)` 通过(仅主链判定);记录 `Approved=true` 与 `ApprovalMainHeight`。 +3. **Cancel**:治理(autonomy 或超级管理员)可在任意时刻撤销未激活提案;提案人(`Id` 对应的原始提案地址)同样可撤销。Cancel 后状态置 `CANCELLED`,不影响当前生效集合。 +4. **Activate**:任何人可发;必须同时满足 —— 提案状态为 `PENDING/APPROVED`、`mainHeight ≥ EffectiveMainHeight`、`tier==0 || Approved`、目标版本号 > 当前版本号。执行内容:写入集合与每节点状态(复用 `nodeGroupCreate` 的写入路径)、`valset-version+1`、`valset-history` 记录、提案状态置 `ACTIVATED`。 +5. **幂等**:同版本重复 Activate 失败;`Activated/Cancelled` 状态的提案不可再 Approve/Activate。 +6. 所有状态变更都产生独立的 receipt log(含 title、提案 id、新版本号、生效高度),便于索引与监控。 + +## 8. 变更入口接管 + +| 现有入口 | 现状作用 | Phase 1 之后 | +|---|---|---| +| `NodeGroupConfig` op=1/2(Apply/Approve) | **首次建立集合**(自由注册 + 自审批) | **保持不变**(本期决策:不收紧开链;缺口见第 13 节) | +| `NodeGroupConfig` op=3/4(Quit/Modify) | 撤销未批准申请 / 修改冻结参数 | 不涉及成员集合,保持不变 | +| `NodeConfig` op=NewApply/Vote/Quit/Cancel/Modify | **已有集合的成员变更**(内部 2/3 投票达成后直接改写集合) | **被状态机接管**:投票达成后不再改写集合,而是生成/更新 `PENDING` 提案(新集合 = 当前集合 ± 成员) | +| `SupervisionNodeConfig` | 监督节点组(第二组验证人,同样需要 2/3) | 同样接管 | + +即:**内部 2/3 投票达成 ≠ 生效**。平行链侧投票仍由平行链自己完成(保留"平行链自身治理发起"的自主性),主链侧新增的是治理闸门与时间锁。 + +## 9. 治理闸门 + +- 治理主体:autonomy 自治 board(`types.AutonomyCfgKey` + `IsAutonomyApprovedItem`),超级管理员(`isSuperManager`)作为兜底; +- 治理批准项(autonomy item)的发起方式不在本设计范围内改变:沿用现有流程——由平行链治理在主链 autonomy 发起提案、board 成员投票通过后 `IsAutonomyApprovedItem(itemID, proposalId)` 返回成功。本设计的 `Approve` 交易只是**校验并记录**该批准结果,不改变 autonomy 自身的投票机制; +- 判定只在主链执行(`if !cfg.IsPara()`),平行链跟随主链执行结果,与现有 `nodeGroupApprove` 的模式一致; +- 高价值档必须批准;低价值档可选择批准以把等待期从 7 天缩短到 1 天; +- 批准后 1 天窗口内治理可撤销;`Activate` 之前任意时刻治理与提案人均可撤销。 + +## 10. 跨链急停 + +- 状态:`mavl-paracross-crosschain-switch-<title>`,per 平行链; +- 操作:op=9 `CrossChainDisable` / op=10 `CrossChainEnable`,均需治理批准(autonomy 项,超级管理员兜底),链上记录原因与高度; +- 生效范围(**双向**,已确认): + 1. 新提交:`CrossAssetTransfer` / `AssetWithdraw` / 存款类跨链动作(execer = `user.p.<title>.paracross`)直接失败; + 2. 已挂起:commit 触发 `execCrossTxs` 时,被禁用链的挂起提现(ExecPack)不执行、不改状态; + 3. 恢复:`Enable` 后挂起提现可正常执行,不额外延迟; + 4. 不影响:历史已验证状态、验证人变更流程(两者独立,急停期间仍可走变更流程)。 +- 安全性:急停只能"停"不能"取",滥用最多造成跨链暂停(拒绝服务),不会造成资金损失;开关变化全程有事件与原因字段。 + +## 11. 共识安全 + +- **两侧一致**:`Activate`、`Disable/Enable` 都是普通 paracross 交易,主链与平行链各自执行同一笔交易,写入 KV 完全确定;不使用墙钟时间,不用随区块变化的本地状态。 +- **不改读路径**:commit 校验仍只读 `mavl-paracross-nodes-title-<title>`,因此验证人切换不会引入"同一高度不同节点算出不同集合"的风险。 +- **分叉**:主链注册 `ForkParaValidatorSet`;平行链在 `[fork.sub.paracross]` 配置同一主链高度(与既有 `ForkParaAssetTransferRbk`/`ForkParaSupervision` 等分叉一致)。使用 `cfg.IsDappFork(mainHeight, pt.ParaX, fork)` 判定,不使用 `IsParaForkHeight`(后者对未映射 key 在平行链上会 panic)。 +- **升级要求**:分叉高度前必须完成主链与**全部平行链节点**的升级,否则未升级节点在分叉高度后与本设计判断不一致。 +- **回滚**:Activate/Disable 只写 receipt KV,框架自动回滚,无需额外逻辑。 +- **参数一致性**:第 6.4 节参数必须全网一致(进链配置并纳入升级检查清单)。 + +## 12. Phase 2 演进路线(主链也能验证平行链状态) + +1. **挑战期 + 见证式欺诈证明(推荐第一步)** + - 跨链转出进入 `PendingWithdrawal` 队列,等待 `challengeWindow`(建议与 Phase 1 的 1 天/7 天口径统一); + - 任何 watcher 可提交 `FraudProof`,引用 `{paraHeight, PreStateHash, StateHash, 被质疑交易}`,用 chain33 现成的 mavl 证明(`Tree.ConstructProof` / `Proof.Verify(key,value,root)`,见 `system/store/mavl/db/proof.go`)给出被触碰 key 的状态见证; + - 主链只需执行被质疑交易涉及的账户/合约子集即可判定,无需全量重执行、无需 zk; + - 挑战成立:撤销该提现、回滚相关状态;有质押则罚没。 +2. **抽样审计 + 罚没**:随机抽取平行链高度,要求验证人在时限内提供见证,超时/错误即罚没。**前置项:Phase 1 未引入质押,罚没需要先补质押机制。** +3. **有效性证明(zk)**:平行链随块提交状态转移证明,主链验证;无需挑战期与诚实方,但需 zkVM/电路与验证器,列为长期目标。 +4. **数据可用性**:目前平行链交易已通过 tx group 上主链(`FilterParaTxsByTitle` 的数据来源),暂不缺;若未来改为只传哈希,需要 DA 采样或强制上链。 + +**分层防御**:急停(止血)→ 变更延迟(反应窗口)→ 挑战期(纠错)→ zk(免信任)。Phase 1 交付前两层。 + +## 13. 风险登记与残留缺口 + +| 风险 | 说明 | 缓解 | +|---|---|---| +| 首次建组仍自由注册(本期决策 C) | 无生效集合的平行链仍可能被他人抢先建立集合,此后才受 1/7 天保护 | 治理急停可止血;监控"已有平行链 title 出现新节点组/自审批";后续可选:对"无有效集合"的链要求一次性治理批准开链 | +| 治理被控 | board 被控可批准恶意变更、可滥用急停 | 变更仍有最短窗口且可撤销;急停只能停不能取;board 门槛 + 超级管理员兜底 + 全程事件审计 | +| 现有集合私钥被控 | 本设计不改变签名验证,被控私钥仍能按旧规则出块/commit | 变更被强制延迟 1/7 天,治理可在窗口内撤销并急停;彻底解决依赖 Phase 2 的"可证明作恶 + 罚没" | +| 参数/配置不一致 | 阈值与延迟区块数若各节点不同 → 两侧判断分叉 | 参数进链配置;纳入升级检查清单;后续用治理参数/mver 固化 | +| 未升级节点 | 分叉后两侧集合判断不一致 | 强制升级窗口 + 分叉高度留足;启动校验 | +| `Activate` 无人发送 | 低价值档不会自动生效 | 任何人可发(提案人/节点/浏览器/脚本);CLI 与监控提示;**不**改为 commit 时顺带激活(会动读路径,违背方案取舍) | + +## 14. 迁移步骤 + +1. 发布新版本(主链 + 全部平行链节点),`ForkParaValidatorSet` 设在部署后若干区块; +2. 平行链按 `mainFork*` 约定配置同一主链高度; +3. 分叉后已有节点组自动视为 version 1,无需迁移;此后成员变更走状态机; +4. 配套交付:查询接口(提案状态、生效高度、当前版本、急停状态)、CLI 命令、监控指标与告警; +5. 运维文档:急停与恢复的操作流程、变更提案的生命周期说明。 + +## 15. 测试矩阵 + +**单元测试** + +- 状态机全部合法/非法转移:重复 Propose、缺治理批准的高价值 Activate、批准后 Cancel、Cancel 后 Activate、已完成提案再操作; +- 分档边界:锁定资产 = 阈值、阈值 + 1; +- 延迟边界:`EffectiveMainHeight - 1`(失败)、`EffectiveMainHeight`(成功); +- 幂等:同版本重复 Activate 失败; +- 急停:新提交被拒(双向)、挂起提现不执行、恢复后可执行、无治理批准不可切换。 + +**集成测试(testnode,主链 + 平行链)** + +- Activate 后两侧 `GetNodeGroupAddrs` 与 BLS 公钥完全一致; +- 变更期间旧集合仍可正常 commit,激活后旧集合签名失效; +- 区块回滚后状态正确恢复; +- 未升级节点在分叉高度后的行为(应被升级检查拦住,而不是静默分叉)。 + +**回归测试** + +- 现有 `nodegroup` / `node` / `supervision` / `asset transfer` / `rollup` 测试全绿; +- 标题/execer 精确绑定校验(PR #1326)在新流程下仍生效。 + +**混沌/边界** + +- 延迟窗口内连续提案(第二个 Propose 必须失败); +- 撤销后重新提案; +- 急停与验证人变更并发; +- 治理批准与 Cancel 在同一窗口内竞争。 + +## 16. 决策记录 + +| # | 决策 | 选择 | +|---|---|---| +| 1 | 本期产出 | 设计方案/可行性文档,暂不写共识代码 | +| 2 | 第一阶段范围 | 只改验证人来源(方向二);主链验证状态留 Phase 2 | +| 3 | 验证人变更权限 | 平行链自身治理发起,主链按锁定资产分档处理 | +| 4 | 分档规则 | 锁定资产 > 10 万:必须治理批准,批准后锁 1 天且窗口内可取消;≤ 10 万且无批准:锁 7 天 | +| 5 | 实现方案 | 验证人集合状态机 + 延迟到点后显式激活(commit 读路径不变) | +| 6 | 首次建集合 | 保持现有自由注册(状态机只管"已有集合的更换") | +| 7 | 急停 | 治理可控、**双向**切断跨链(含入金),恢复后挂起提现可执行 | + +## 17. 假设与默认口径(如需变更请在实施前提出) + +1. **治理主体** = autonomy 自治 board(`IsAutonomyApprovedItem`),超级管理员兜底; +2. **锁定资产口径** = `dapp.ExecAddress("user.p.<name>.paracross")` 的 coin 余额(token 暂不计入),在 `Propose` 时快照定档; +3. **同一时刻仅一个待生效提案**,新提案必须等前一个 Activate/Cancel 之后; +4. **提案 id** = `Propose` 交易的 hash;`Cancel` 权限 = 治理或提案发起地址; +5. **延迟与阈值参数** 走链配置(第 6.4 节默认值),非治理可调参数;后续可迁移为治理参数; +6. **监督节点组** 与超级节点组同样纳入本状态机; +7. **版本号** 从 1 开始,已有节点组视为 version 1。 From cf8964bd1df57551d06662fa8efa740e94cd2233 Mon Sep 17 00:00:00 2001 From: king <king@kingdeMacBook-Pro-2.local> Date: Wed, 14 Oct 2026 10:28:29 +0800 Subject: [PATCH 2/6] docs(plan): paracross validator set governance implementation plan 12 TDD tasks: constants/params and fork registration, state encoding and kv helpers, Propose/Approve/Cancel/Activate state machine, entry point rewiring (NodeGroupConfig/NodeConfig/SupervisionNodeConfig), query, the governance circuit breaker plus its enforcement on submit and on pending cross tx execution, fork config rollout, main/para consistency and rollback integration tests, and CLI/ops docs. Self-review fixed: error and log constants now declared in task 1, FromAddr added to the state struct up front, exact wire format spelled out, and the six review-focus failure modes each pinned to a task test. --- ...26-10-14-paracross-validator-governance.md | 384 ++++++++++++++++++ 1 file changed, 384 insertions(+) create mode 100644 docs/superpowers/plans/2026-10-14-paracross-validator-governance.md diff --git a/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md b/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md new file mode 100644 index 000000000..344074050 --- /dev/null +++ b/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md @@ -0,0 +1,384 @@ +# paracross 验证人治理化 + 跨链急停(Phase 1)实施计划 + +> **For agentic workers:** REQUIRED SUB-SKILL: Use subagent-driven-development (recommended) or executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 把平行链验证人集合的更换从"内部 2/3 投票即生效"改为"平行链提案 + 主链分档治理/时间锁 + 显式激活",并新增治理可控的双向跨链急停。 + +**Architecture:** 复用现有 `ParaNodeGroupConfig` action(不新增 protobuf)新增 op;新增一个 title+组别 维度的状态机(Propose/Approve/Cancel/Activate),延迟按主链区块高度计算,`Activate` 到点后由任意人发送并改写现有节点组 KV;commit 校验读路径保持不变,主链与平行链对同一交易写入相同状态。急停是独立的 KV 开关,在跨链提交与执行两个入口生效。 + +**Tech Stack:** Go 1.22、chain33 1.72.x、gogo/protobuf(仅读取,不生成)、testify、go test。 + +**Spec:** `docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md` + +## Global Constraints + +- 所有新逻辑由 `pt.ForkParaValidatorSet` 开启;未达分叉高度时行为与现状逐字节一致。 +- 分叉判定用 `cfg.IsDappFork(mainHeight, pt.ParaX, pt.ForkParaValidatorSet)`,**禁用** `pt.IsParaForkHeight`(未映射 key 在平行链上会 panic)。平行链在 `[fork.sub.paracross]` 配置与主链相同的主链高度。 +- title 必须为规范形式 `user.p.<name>.` 且与交易 execer 精确对应:复用 PR #1326 的 `pt.IsValidParaTitle` 与 `a.checkParaTitleExecMatch`(该 PR 可能尚未合入 master;若不在基线中,先 cherry-pick 或复制这两个函数)。 +- **不新增 protobuf 生成代码**:本环境无 `protoc`;状态用确定性自定义编码(大端、定长、版本字节)。 +- 延迟一律按**主链区块高度**,禁止墙钟时间;参数从链配置读取(`types.ConfSub(cfg, pt.ParaX).GInt(...)`),默认 `validatorSetHighValueThreshold = 100000 * cfg.GetCoinPrecision()`、`validatorSetDelayBlocksHigh = 8640`、`validatorSetDelayBlocksLow = 60480`。 +- "锁定资产"口径:`a.coinsAccount.LoadExecAccount(address.ExecAddress(title+pt.ParaX), address.ExecAddress(pt.ParaX)).Balance`(主链视角,与 `executor/asset.go` 的入金账户一致)。 +- 主链判定治理、平行链跟随(`if !cfg.IsPara()`),与现有 `nodeGroupApprove` 模式一致。 +- 所有含 `[fork.sub.paracross]` 的配置文件都必须补 `ForkParaValidatorSet`,否则 chain33 `initForkConfig` 直接 panic。 +- 每个任务结束提交一次,提交信息用 `feat(paracross): ...` / `test(paracross): ...` / `docs(paracross): ...`。 + +## Review Focus + +1. **高价值档未获治理批准就 Activate** —— 必须失败;放行等于绕过治理闸门。 +2. **`EffectiveMainHeight` 边界(−1 / 恰好到点)** —— 差一个区块就切换集合会让主链与平行链对签名人判断不一致。 +3. **同一 title 同时存在两个待生效提案** —— 必须拒绝第二个,否则 Activate 写入的集合与提案内容不符。 +4. **急停期间的挂起提现(ExecPack)** —— 必须不执行也不改状态,且**不能**让 commit 报错停链。 +5. **未升级节点 / 配置文件缺 fork 键** —— 节点启动 panic 或分叉后静默分叉。 +6. **重复 Activate** —— 第二次必须失败,不能重复写集合或回退版本号。 + +## File Structure + +| 文件 | 职责 | +|---|---| +| `plugin/dapp/paracross/types/paracross.go`(改) | 新 op 常量、提案状态常量、分档常量 | +| `plugin/dapp/paracross/types/type.go`(改) | `ForkParaValidatorSet` 常量与注册 | +| `plugin/dapp/paracross/executor/kv.go`(改) | 新增 KV key 构造函数 | +| `plugin/dapp/paracross/executor/valsetparam.go`(新) | 阈值/延迟参数读取 | +| `plugin/dapp/paracross/executor/valsetstore.go`(新) | 提案状态编解码、读写、版本号、历史 | +| `plugin/dapp/paracross/executor/valset.go`(新) | 状态机:Propose/Approve/Cancel/Activate、提案产出与集合写入 | +| `plugin/dapp/paracross/executor/circuitbreaker.go`(新) | 急停开关读写与检查 | +| `plugin/dapp/paracross/executor/superaccount.go`(改) | `NodeGroupConfig`/`NodeConfig` 接线 | +| `plugin/dapp/paracross/executor/supervisionaccount.go`(改) | 监督节点组接线 | +| `plugin/dapp/paracross/executor/asset.go`、`action.go`(改) | 急停在提交与挂起执行两个入口 | +| `plugin/dapp/paracross/executor/query.go`(改) | 查询接口 | +| `plugin/dapp/paracross/commands/paracross.go`(改) | CLI | +| `plugin/dapp/paracross/executor/{valsetstore,valset,circuitbreaker,valsetparam}_test.go`(新) | 单元测试 | +| `chain33.fork.toml` 等 11 个含 `[fork.sub.paracross]` 的配置(改) | 分叉高度 | + +--- + +### Task 1: 常量、参数与分叉注册 + +**Files:** +- Modify: `plugin/dapp/paracross/types/paracross.go`(`ParacrossNodeGroup*` 常量块之后) +- Modify: `plugin/dapp/paracross/types/type.go` +- Create: `plugin/dapp/paracross/executor/valsetparam.go` +- Test: `plugin/dapp/paracross/executor/valsetparam_test.go` + +**Interfaces:** +- Consumes: 无 +- Produces: + - `pt.ForkParaValidatorSet = "ForkParaValidatorSet"`(`InitFork` 中 `RegisterDappFork(ParaX, ForkParaValidatorSet, 0)`) + - `pt.ParacrossValidatorSetPropose=5`、`ParacrossValidatorSetApprove=6`、`ParacrossValidatorSetCancel=7`、`ParacrossValidatorSetActivate=8` + - `pt.ParacrossCrossChainDisable=9`、`ParacrossCrossChainEnable=10` + - `pt.ParacrossSupValidatorSetPropose=11`、`...Approve=12`、`...Cancel=13`、`...Activate=14` + - `pt.ParaValidatorSetPending=0`、`ParaValidatorSetApproved=1`、`ParaValidatorSetCancelled=2`、`ParaValidatorSetActivated=3` + - `pt.ParaValidatorSetTierLow=0`、`ParaValidatorSetTierHigh=1` + - `pt.TyLogParaValidatorSetConfig = 684`(新日志类型,并加入 `ParacrossType.GetLogMap`) + - 错误常量:`pt.ErrValidatorSetNotEffective`、`pt.ErrValidatorSetPendingExists`、`pt.ErrValidatorSetNoActive`、`pt.ErrCrossChainDisabled`(加入 `types/errors.go`) + - `executor.validatorSetParams(cfg *types.Chain33Config) (threshold, delayHigh, delayLow int64)` + +- [ ] **Step 1: 写失败测试** `plugin/dapp/paracross/executor/valsetparam_test.go` + +```go +func TestValidatorSetParamsDefault(t *testing.T) { + threshold, delayHigh, delayLow := validatorSetParams(chain33TestMainCfg) + assert.Equal(t, int64(100000)*chain33TestMainCfg.GetCoinPrecision(), threshold) + assert.Equal(t, int64(8640), delayHigh) + assert.Equal(t, int64(60480), delayLow) +} + +func TestValidatorSetParamsFromConf(t *testing.T) { + cfg := types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"test\"", 1)) + cfg.S("config.exec.sub.paracross.validatorSetHighValueThreshold", int64(500)) + cfg.S("config.exec.sub.paracross.validatorSetDelayBlocksHigh", int64(10)) + cfg.S("config.exec.sub.paracross.validatorSetDelayBlocksLow", int64(20)) + threshold, delayHigh, delayLow := validatorSetParams(cfg) + assert.Equal(t, int64(500), threshold) + assert.Equal(t, int64(10), delayHigh) + assert.Equal(t, int64(20), delayLow) +} +``` + +- [ ] **Step 2: 运行确认失败** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetParams -v` → 编译错误 `undefined: validatorSetParams`。 +- [ ] **Step 3: 加常量与实现** — 按上面 Interfaces 加常量;`valsetparam.go` 用 `types.ConfSub(cfg, pt.ParaX).GInt(key)` 读取,key 缺省或 ≤0 时回落到默认值(阈值默认 `100000 * cfg.GetCoinPrecision()`)。 +- [ ] **Step 4: 运行确认通过** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetParams -v` → PASS。 +- [ ] **Step 5: 提交** — `git add -A && git commit -m "feat(paracross): add validator set governance fork, ops and params"` + +--- + +### Task 2: 提案状态编解码与 KV helper + +**Files:** +- Modify: `plugin/dapp/paracross/executor/kv.go` +- Create: `plugin/dapp/paracross/executor/valsetstore.go` +- Test: `plugin/dapp/paracross/executor/valsetstore_test.go` + +**Interfaces:** +- Consumes: Task 1 的常量 +- Produces: + - `type paraValidatorSetChange struct { Id, Title, FromAddr string; NewAddrs, NewBlsPubKeys []string; ProposeMainHeight, LockedAssets int64; Tier int32; Approved bool; ApprovalMainHeight int64; AutonomyItemID string; EffectiveMainHeight int64; Status int32; Version uint64 }` + - `const validatorGroupSuper = "super"`、`const validatorGroupSupervision = "sup"` + - `encodeValidatorSetChange(c *paraValidatorSetChange) []byte` / `decodeValidatorSetChange(b []byte) (*paraValidatorSetChange, error)` + - `calcValidatorSetChangeKey(kind, title string) []byte` → `mavl-paracross-valset-<kind>-change-<title>` + - `calcValidatorSetVersionKey(kind, title string) []byte` → `mavl-paracross-valset-<kind>-version-<title>` + - `calcValidatorSetHistoryKey(kind, title string, version uint64) []byte` → `mavl-paracross-valset-<kind>-history-<title>-<version>` + - `(a *action) getValidatorSetChange(kind, title string) (*paraValidatorSetChange, error)`(不存在返回 `pt.ErrKeyNotExist`) + - `(a *action) saveValidatorSetChange(kind string, c *paraValidatorSetChange) *types.KeyValue` + - `(a *action) validatorSetVersion(kind, title string) uint64`(不存在返回 0) + - 编码格式 v1(全部大端):`u8 version=1`,随后依次为长度前缀字符串 `Id`、`FromAddr`、`AutonomyItemID`,`u32 count` + 逐项长度前缀 `NewAddrs`,`u32 count` + 逐项长度前缀 `NewBlsPubKeys`,`i64` × 5(`ProposeMainHeight`、`LockedAssets`、`ApprovalMainHeight`、`EffectiveMainHeight`、版本号),`u8 tier`、`u8 status`、`u8 approved`。`Title` 不进编码(由 key 推导,读取时回填)。 + +- [ ] **Step 1: 写失败测试** — `TestValidatorSetChangeEncodeDecodeRoundTrip`(构造含 2 个 addr/blspub 的完整结构,编码→解码,`assert.Equal` 全字段)、`TestValidatorSetChangeDecodeRejectsBadVersion`(首字节改 9 → 返回 error)、`TestValidatorSetChangeDecodeRejectsTruncated`(截断一半 → error)、`TestValidatorSetChangeKeyLayout`(断言三个 key 的字符串前缀为 `mavl-paracross-valset-super-`)。 +- [ ] **Step 2: 运行确认失败** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetChange -v` → FAIL(undefined)。 +- [ ] **Step 3: 实现编解码与 key/KV helper** — 编码用 `bytes.Buffer` + `encoding/binary`;解码逐字段校验剩余长度,长度上限设 64KB 防异常输入。 +- [ ] **Step 4: 运行确认通过** — 同上命令 → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): add validator set change state encoding and kv helpers"` + +--- + +### Task 3: Propose + +**Files:** +- Create: `plugin/dapp/paracross/executor/valset.go` +- Test: `plugin/dapp/paracross/executor/valset_test.go` + +**Interfaces:** +- Consumes: Task 1/2;`a.checkParaTitleExecMatch(title string) error`、`a.getNodesGroup(title) (map[string]struct{}, []string, error)`(现状)、`getSupervisionNodeGroupAddrs`(现状) +- Produces: + - `(a *action) validatorSetChangePropose(kind string, config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` + - `(a *action) proposeValidatorSetChange(kind, title, id string, addrs, blsPubKeys []string) (*types.Receipt, error)`(Task 6 复用) + - `(a *action) lockedAssets(title string) (int64, error)` + - `(a *action) activeValidatorSet(kind, title string) (map[string]struct{}, []string, error)` + +- [ ] **Step 1: 写失败测试** — `valset_test.go` 用 `chain33TestMainCfg` + memdb + `apimock`(参考 `superaccount_test.go` 的 `NodeManageTestSuite` 装配),先 `setNodeGroupAddrs(Title, Account14K,...)` 造出生效集合: + - `TestValidatorSetProposeOK`:addrs 乱序 + 重复 → 断言写入的提案 `NewAddrs` 已去重排序、`Status==ParaValidatorSetPending`、`Tier==ParaValidatorSetTierLow`(锁定资产 0)、`EffectiveMainHeight==ProposeMainHeight+60480`。 + - `TestValidatorSetProposeRejectsSecondPending`:连续两次 Propose → 第二次 `errors.Cause(err)==pt.ErrValidatorSetPendingExists` 且第一次的提案未变。 + - `TestValidatorSetProposeRejectsNonCanonicalTitle`:`Title="user.p.test"`(无结尾点)→ `errors.Cause(err)==pt.ErrInvalidTitle`。 + - `TestValidatorSetProposeRejectsEmptyChange`:新集合与当前集合相同 → error。 + - `TestValidatorSetProposeRejectsWhenNoActiveSet`:该 title 无生效集合 → `errors.Cause(err)==pt.ErrValidatorSetNoActive`。 + - `TestValidatorSetProposeHighTier`:`setExecAccountBalance(Title+pt.ParaX, 100000*precision+1)` → `Tier==ParaValidatorSetTierHigh` 且 `EffectiveMainHeight==0`(未批准前为 0)。 +- [ ] **Step 2: 运行确认失败** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetPropose -v` → FAIL。 +- [ ] **Step 3: 实现** — 前置校验顺序:fork → `checkParaTitleExecMatch` → 已有生效集合(`activeValidatorSet` 非空)→ 无 pending/approved 提案 → addrs/blspubs 数量相等非空 → 去重排序(addr 与 bls 配对后按 addr 排序)→ 与当前集合不同;随后 `lockedAssets` 定档、写提案 KV、发 log `pt.TyLogParaValidatorSetConfig`(log 结构沿用现有 `pt.ReceiptParaNodeGroupConfig`)。 +- [ ] **Step 4: 运行确认通过** — 同上 → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): add validator set change proposal"` + +--- + +### Task 4: Approve / Cancel + +**Files:** +- Modify: `plugin/dapp/paracross/executor/valset.go` +- Test: `plugin/dapp/paracross/executor/valset_test.go` + +**Interfaces:** +- Consumes: Task 3 +- Produces: + - `(a *action) validatorSetChangeApprove(kind string, config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` + - `(a *action) validatorSetChangeCancel(kind string, config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` + - `(a *action) checkValidatorSetGovernance(config *pt.ParaNodeGroupConfig) error`(主链才执行;`IsAutonomyApprovedItem(AutonomyItemID, Id)` 或 `isSuperManager`,否则 `types.ErrNotAllow`) + +- [ ] **Step 1: 写失败测试** + - `TestValidatorSetApproveBySuperManager`:`chain33TestMainCfg.S("config.exec.sub.manage.superManager", []interface{}{Account12Q})`,用 `PrivKey12Q` 签 Approve → `Approved==true`、`ApprovalMainHeight` 正确、`EffectiveMainHeight==ApprovalMainHeight+8640`。 + - `TestValidatorSetApproveRejectsNonManager`:用 `PrivKey14K` → `errors.Cause(err)==types.ErrNotAllow`。 + - `TestValidatorSetApproveRejectsUnknownProposal` → error。 + - `TestValidatorSetCancelByProposer` / `TestValidatorSetCancelByGovernance`:`Status==ParaValidatorSetCancelled`。 + - `TestValidatorSetCancelRejectsAfterActivate` → error。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — 平行链(`cfg.IsPara()`)跳过治理查询直接记录;`ApprovalMainHeight = a.exec.GetMainHeight()`;Cancel 授权:治理通过 或 `a.fromaddr == c.FromAddr`(`FromAddr` 已在 Task 2 的结构与编码中定义)。 +- [ ] **Step 4: 运行确认通过** → PASS(含 Task 2/3 全部用例)。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): add validator set approval and cancel"` + +--- + +### Task 5: Activate + +**Files:** +- Modify: `plugin/dapp/paracross/executor/valset.go` +- Test: `plugin/dapp/paracross/executor/valset_test.go` + +**Interfaces:** +- Consumes: Task 2/3/4;`nodeGroupCreate`(super 写集合+每节点 BLS 状态的现有实现)、`supervisionNodeGroupCreate`/`updateSupervisionNodeAddrStatus` +- Produces: + - `(a *action) validatorSetChangeActivate(kind string, config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` + - `(a *action) writeActiveValidatorSet(kind string, c *paraValidatorSetChange) (*types.Receipt, error)` + +- [ ] **Step 1: 写失败测试** + - `TestValidatorSetActivateBeforeEffectiveFails`:把 `a.exec` 高度设为 `EffectiveMainHeight-1` → `errors.Cause(err)==pt.ErrValidatorSetNotEffective`(对应 Review Focus 2 的下边界)。 + - `TestValidatorSetActivateAtEffectiveOK`:高度 = `EffectiveMainHeight` → 集合 KV 变为新集合、`validatorSetVersion==1`、`Status==ParaValidatorSetActivated`、每节点 BLS 公钥已写入(对应 Review Focus 2 的上边界)。 + - `TestValidatorSetActivateHighTierWithoutApprovalFails`(写死在断言里,对应 Review Focus 1)。 + - `TestValidatorSetActivateTwiceFails`(Review Focus 6)。 + - `TestValidatorSetActivateRejectsVersionRegression`:手工把版本号设为 2 后再 Activate 版本 1 的提案 → error。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — 校验顺序:fork → title/execer → 提案存在且 `Status∈{Pending,Approved}` → `mainHeight ≥ EffectiveMainHeight` → `Tier==Low || Approved` → `c.Version > validatorSetVersion()`;通过后 `writeActiveValidatorSet`(super 复用 `nodeGroupCreate`,supervision 复用监督组写入)+ 版本+1 + 写 history + 状态置 Activated。 +- [ ] **Step 4: 运行确认通过** → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): activate validator set changes after timelock"` + +--- + +### Task 6: 入口接线(NodeGroupConfig / NodeConfig / SupervisionNodeConfig) + +**Files:** +- Modify: `plugin/dapp/paracross/executor/superaccount.go`(`NodeGroupConfig`、`NodeConfig` 投票完成处) +- Modify: `plugin/dapp/paracross/executor/supervisionaccount.go` +- Test: `plugin/dapp/paracross/executor/valset_test.go` + +**Interfaces:** +- Consumes: Task 3 `proposeValidatorSetChange` +- Produces: + - `NodeGroupConfig` 新增 op 分发:5/6/7/8 → super 状态机;9/10 → 急停;11/12/13/14 → supervision 状态机 + - `NodeConfig` 成员变更在 2/3 达成后,**分叉后**改为调用 `proposeValidatorSetChange`(新集合 = 当前集合 ± 成员,proposal id = 当前 tx hash),不再直接 `updateNodeGroup` + - `SupervisionNodeConfig` 同理(kind=`validatorGroupSupervision`) + +- [ ] **Step 1: 写失败测试** + - `TestNodeGroupConfigDispatchesValidatorSetOps`:op=5 走状态机(断言写入提案),op=1 仍走旧流程。 + - `TestNodeVoteCompletionCreatesProposal`:用现有 `nodeVote` 测试辅助把票数推到 2/3 → 断言 `mavl-paracross-nodes-title-<title>` **未变**、而 `valset-super-change-<title>` 已写入新集合(Review Focus 3 的前置条件)。 + - `TestBootstrapStillWritesSetDirectly`:op=1/2 首次建组仍直接写集合(回归,确保没被接管)。 + - `TestSupervisionChangeCreatesProposal`:监督组变更同样只产生提案。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — 在 `NodeGroupConfig` 的 op switch 末尾加分支;把 `nodeJoin`/`nodeQuit`/`nodeVote` 中调用 `updateNodeGroup` 的位置改为"若 fork 生效 → 计算新集合后 `proposeValidatorSetChange`,否则保持原逻辑";`SupervisionNodeConfig` 同构。 +- [ ] **Step 4: 运行确认通过** → PASS(并跑 `go test ./plugin/dapp/paracross/executor/` 全量)。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): route member changes through validator set state machine"` + +--- + +### Task 7: 查询接口 + +**Files:** +- Modify: `plugin/dapp/paracross/executor/query.go`、`plugin/dapp/paracross/types/type.go`(如需注册查询名) +- Test: `plugin/dapp/paracross/executor/valset_test.go` + +**Interfaces:** +- Consumes: Task 2/5 +- Produces: `Query_GetValidatorSetStatus(in *pt.ReqParacrossNodeInfo) (types.Message, error)` 返回 `*types.ReplyConfig`,`Value` 为该 title 的 JSON:`{"activeVersion":1,"activeAddrs":[...],"pending":{"id":"...","status":0,"tier":0,"effectiveMainHeight":123,"approved":false}}`(无 pending 时 `pending` 为 null)。 + +- [ ] **Step 1: 写失败测试** `TestQueryValidatorSetStatus`:无提案、有提案、已激活三种状态各断言 JSON 字段。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — `encoding/json` 只用于查询返回值,不用于链上状态。 +- [ ] **Step 4: 运行确认通过** → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): query validator set status"` + +--- + +### Task 8: 急停开关 + +**Files:** +- Create: `plugin/dapp/paracross/executor/circuitbreaker.go` +- Modify: `plugin/dapp/paracross/executor/query.go`(可选:开关状态并入 Task 7 的 JSON) +- Test: `plugin/dapp/paracross/executor/circuitbreaker_test.go` + +**Interfaces:** +- Consumes: Task 1/4 的治理校验 +- Produces: + - `(a *action) crossChainDisable(config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` + - `(a *action) crossChainEnable(config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` + - `(a *action) isCrossChainDisabled(title string) bool` + - `calcCrossChainSwitchKey(title string) []byte` → `mavl-paracross-crosschain-switch-<title>`(值 = `types.Encode(&types.Int64{Data: setMainHeight})`,**key 存在即禁用**) + - `calcCrossChainSwitchInfoKey(title string) []byte` → `mavl-paracross-crosschain-switchinfo-<title>`(值 = `types.Encode(&types.ReqMultiStrings{Datas: []string{reason, autonomyItemID}})`) + - `Enable` 用 `Set(key, nil)` 删除两个 key。 + +- [ ] **Step 1: 写失败测试** + - `TestCrossChainDisableRequiresGovernance`:非治理地址 → `types.ErrNotAllow`。 + - `TestCrossChainDisableSetsSwitch`:治理地址 → `isCrossChainDisabled==true`、info 含 reason。 + - `TestCrossChainEnableClearsSwitch`:`Enable` 后 `isCrossChainDisabled==false`。 + - `TestCrossChainDisableTwiceFails` / `TestCrossChainEnableWhenEnabledFails`(幂等边界)。 + - `TestCrossChainDisableRejectsNonCanonicalTitle`。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — 复用 Task 4 的 `checkValidatorSetGovernance` 逻辑(抽成 `checkValidatorSetGovernance(config *pt.ParaNodeGroupConfig) error` 供两者共用)。 +- [ ] **Step 4: 运行确认通过** → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): governance controlled cross chain circuit breaker"` + +--- + +### Task 9: 急停生效点(提交 + 挂起执行) + +**Files:** +- Modify: `plugin/dapp/paracross/executor/asset.go`(提交入口)、`plugin/dapp/paracross/executor/action.go`(`execCrossTxs`) +- Test: `plugin/dapp/paracross/executor/circuitbreaker_test.go` + +**Interfaces:** +- Consumes: Task 8 +- Produces: + - `(a *action) checkCrossChainEnabled(title string) error`(禁用时返回 `pt.ErrCrossChainDisabled`,Task 1 定义) + - 提交入口:`AssetTransfer`、`AssetWithdraw`、`CrossAssetTransfer` 三个 action 在 fork 生效时先调 `checkCrossChainEnabled(title)`;title 用 chain33 的 `types.GetParaExecTitleName(string(a.tx.Execer))` 推导;若解析失败(非 `user.p.<name>.paracross` execer)则不做拦截并保持现状(这三个 action 在主链本来就只能以该形态提交) + - 执行入口:`execCrossTxs` 对禁用 title **跳过**(返回空 receipt,不报错) + +- [ ] **Step 1: 写失败测试** + - `TestDisabledChainRejectsCrossAssetTransferSubmit`:设置 switch 后提交 `CrossAssetTransfer` → 失败且无 KV 变化。 + - `TestDisabledChainRejectsAssetTransferAndWithdrawSubmit`(双向:入金与出金都拒)。 + - `TestDisabledChainSkipsPendingCrossTxExecution`:构造 `crossTxs` 调 `a.execCrossTxs(title, ...)` → 返回空 receipt、无错误、账户余额不变(Review Focus 4)。 + - `TestDisabledChainCommitDoesNotFail`:走 `procCrossTxs` 路径断言 commit 仍成功。 + - `TestEnabledChainUnaffected`(回归)。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — 提交入口三个 action 加检查;`execCrossTxs` 循环前判断 `isCrossChainDisabled(title)`,为真直接返回 `&types.Receipt{}, nil`。 +- [ ] **Step 4: 运行确认通过** → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): enforce cross chain circuit breaker on submit and execution"` + +--- + +### Task 10: 配置文件补分叉键 + +**Files:** +- Modify: `chain33.fork.toml`、`chain33.para.toml`、`chain33.para.toml.readme`、`plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml`、`plugin/dapp/ticket/executor/testdata/chain33.cfg.toml`、`plugin/dapp/ticket/rpc/rpc_test.go`、`build/ci/{cross2eth,bridgevmxgo,rollup,rgbx}/chain33.para.toml`、`build/ci/evm2/chain33.proxyminer.toml` + +**Interfaces:** +- Consumes: Task 1 的 `ForkParaValidatorSet` +- Produces: 每个含 `[fork.sub.paracross]` 的文件都有 `ForkParaValidatorSet=<height>`(主链模板 `chain33.fork.toml` 用一个大于当前主链高度的占位值;平行链/测试配置 `0`) + +- [ ] **Step 1: 用一次性命令确认当前缺键**(不落库) + +```bash +for f in $(grep -rl "fork.sub.paracross" --include=*.toml --include=*.go --include=*.readme .); do + grep -q "ForkParaValidatorSet" "$f" || echo "MISSING $f" +done +``` +Expected: 列出全部文件为 MISSING。 + +- [ ] **Step 2: 修改配置** — 在每个 `[fork.sub.paracross]` 段的 `Enable=` 行后插入该键(沿用 PR #1326 的写法与取值风格)。 +- [ ] **Step 3: 验证脚本通过** — 同上命令,无输出。 +- [ ] **Step 4: 验证配置可被解析** — `go test ./plugin/dapp/ticket/executor/ ./plugin/dapp/ticket/rpc/ -count=1`(这两个测试会加载含 `[fork.sub.paracross]` 的配置;缺键会 panic)。 +- [ ] **Step 5: 提交** — `git commit -m "chore(paracross): ship ForkParaValidatorSet in every paracross fork config"` + +--- + +### Task 11: 集成与一致性测试 + +**Files:** +- Create: `plugin/dapp/paracross/executor/valset_integration_test.go` + +**Interfaces:** +- Consumes: Task 1–9 全部 +- Produces: 端到端用例,证明主链与平行链执行同一批交易后状态一致 + +- [ ] **Step 1: 写失败测试** + - `TestValidatorSetChangeMainParaConsistency`:同一批交易(Propose→Approve→Activate)分别在 `chain33TestMainCfg`(主链)与 `chain33TestCfg`(平行链,`Title="user.p.test."`)构造的 executor 上按相同顺序执行,断言两侧 `mavl-paracross-nodes-title-<title>` 内容与每节点 BLS 公钥**逐字节相同**。 + - `TestValidatorSetRollbackRestoresActiveSet`:Activate 产生 receipt 后丢弃 receipt KV(模拟回滚),断言版本号与集合仍是旧值。 + - `TestCircuitBreakerEndToEnd`:建组 → 禁用 → 提交被拒 → 挂起提现不执行 → 恢复 → 提现可执行。 +- [ ] **Step 2: 运行确认失败** → FAIL(用例先写,实现已就绪时应只剩断言问题;若直接通过,说明用例无效,必须重写为能捕获上述断言的形态)。 +- [ ] **Step 3: 修正实现中的不一致**(若测试暴露主链/平行链差异)。 +- [ ] **Step 4: 运行确认通过** — `go test ./plugin/dapp/paracross/... -count=1` 全绿。 +- [ ] **Step 5: 提交** — `git commit -m "test(paracross): validator set governance main/para consistency and breaker e2e"` + +--- + +### Task 12: CLI 与运维文档 + +**Files:** +- Modify: `plugin/dapp/paracross/commands/paracross.go` +- Modify: `plugin/dapp/paracross/executor/superaccount.md`(或新增 `docs/` 运维章节) + +**Interfaces:** +- Consumes: Task 7/8 的查询与 op +- Produces: + - `paracross valset propose|approve|cancel|activate`(参数:paraName、addrs、blspubs、id、autonomyItemID) + - `paracross crosschain disable|enable --paraName --reason` + - `paracross valset status --paraName` + - 运维文档:变更生命周期、急停/恢复 SOP、监控项(`valset pending` 年龄、`tier`、紧停开关状态) + +- [ ] **Step 1: 写失败测试** — `plugin/dapp/paracross/commands/paracross_test.go`:`TestValidatorSetCmdBuildsPayload` 断言 `CreateTx` 的 `Execer=="user.p.test.paracross"`、`Payload` 解码后 `Op==pt.ParacrossValidatorSetPropose`。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现命令与文档**(复用现有 `nodeGroupApply` 的 flag/`rpctypes.CreateTxIn` 写法)。 +- [ ] **Step 4: 运行确认通过** — `go test ./plugin/dapp/paracross/commands/ -count=1` → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): cli and ops doc for validator set governance"` + +--- + +## 自检记录 + +- **Spec 覆盖**:第 5 节→Task 3-6;第 6 节→Task 1-2;第 7 节→Task 3-5;第 8 节→Task 6;第 9 节→Task 4;第 10 节→Task 8-9;第 11 节→Task 10-11;第 12 节→不在本期(仅路线);第 13 节→Task 6(bootstrap 不变)+ Task 11;第 14 节→Task 10-12;第 15 节→各任务测试 + Task 11。 +- **类型一致性**:`paraValidatorSetChange` 字段、`validatorGroupSuper/Supervision`、`validatorSetParams`、`checkValidatorSetGovernance`、`checkCrossChainEnabled` 在 Task 1–12 中命名一致。 +- **Review Focus**:6 项分别落在 Task 4(治理缺失)、Task 5(生效边界/幂等)、Task 3(重复提案)、Task 9(挂起提现与 commit 不停链)、Task 10(配置缺键)、Task 5(重复 Activate)的测试步骤中。 From e54847c1fd6e7db9d19589be3dcfc95fe2338149 Mon Sep 17 00:00:00 2001 From: king <king@kingdeMacBook-Pro-2.local> Date: Wed, 14 Oct 2026 11:30:00 +0800 Subject: [PATCH 3/6] docs(cip): introduce Chain33 Improvement Proposal process and CIP-001 Add docs/cip/ with the CIP process (types, status lifecycle, numbering, header metadata, submission flow), a template, and the first proposal: CIP-001 (Standards Track, Draft): validator set changes on a parachain must go through a main chain enforced state machine - parachain proposal, risk tiered governance approval (above the locked-asset threshold: 1 day, cancellable) or a 7 day lock, then an explicit Activate. Commit verification reads are unchanged so main chain and parachain cannot diverge on the active set. A governance controlled circuit breaker can cut cross chain fund flow in both directions. Phase 2 (challenge window with mavl witness fraud proofs, sampling, slashing, zk) is scoped as a follow-up CIP. --- docs/cip/README.md | 95 ++++++++ .../cip-001-paracross-validator-governance.md | 214 ++++++++++++++++++ docs/cip/cip-template.md | 61 +++++ 3 files changed, 370 insertions(+) create mode 100644 docs/cip/README.md create mode 100644 docs/cip/cip-001-paracross-validator-governance.md create mode 100644 docs/cip/cip-template.md diff --git a/docs/cip/README.md b/docs/cip/README.md new file mode 100644 index 000000000..da9886dba --- /dev/null +++ b/docs/cip/README.md @@ -0,0 +1,95 @@ +# CIP — Chain33 Improvement Proposal + +CIP 是 Chain33 生态的改进提案机制,参照 Bitcoin 的 [BIP](https://github.com/bitcoin/bips) 与 Ethereum 的 [EIP](https://github.com/ethereum/EIPs)。 +一份 CIP 是一份**设计文档**,用来描述:共识/跨链规则变更、执行器与接口标准、流程与治理、或信息性说明。代码实现与 CIP 分离:CIP 先评审定稿,再由实施计划落地并随分叉激活。 + +## 目录与命名 + +``` +docs/cip/ +├── README.md # 本文档:流程与规范 +├── cip-template.md # 新建 CIP 使用的模板 +└── cip-001-<slug>.md # 具体提案,按序号排列 +``` + +- 文件名:`cip-<三位序号>-<slug>.md`(序号从 `001` 开始,按合并顺序分配)。 +- 文档内引用写作 `CIP-001`。 +- 流程文档(`README.md`、`cip-template.md`)不占用序号。 +- `slug` 用小写英文与连字符,简短描述主题。 + +## 类型(Type) + +| 类型 | 用途 | +|---|---| +| `Standards Track` | 共识、跨链、执行器、RPC/接口、配置与分叉规则等需要实现者遵循的变更 | +| `Process` | 流程、治理、发布与协作方式的变更 | +| `Informational` | 设计说明、最佳实践、背景资料,不要求实现 | +| `Meta` | 关于 CIP 流程本身的变更 | + +`Standards Track` 提案应当注明 `category`(如 `Consensus`、`Cross-chain`、`Interface`、`Executor`)。 + +## 状态(Status) + +``` +Draft ──> Review ──> Last Call ──> Accepted ──> Final ──> Active + │ │ │ │ + └──────────┴───────────┴────────────┴──> Withdrawn / Rejected / Deferred / Superseded +``` + +| 状态 | 含义 | 进入条件 | +|---|---|---| +| `Draft` | 草案,内容可自由修改 | PR 提交即进入 | +| `Review` | 请求社区与维护者评审 | 作者认为内容已完整 | +| `Last Call` | 最后征集意见 | 评审无重大异议,维护者设定截止时间(默认 14 天) | +| `Accepted` | 已接受,进入实施 | Last Call 期满且无阻塞性异议 | +| `Final` | 实现已完成并随分叉高度发布 | 代码合入 + 分叉高度确定 | +| `Active` | 已在链上分叉激活 | 主链达到分叉高度并稳定运行 | +| `Withdrawn` | 作者主动撤回 | 作者决定 | +| `Rejected` | 维护者否决 | 存在阻塞性技术或治理问题 | +| `Deferred` | 暂缓,等待前置条件 | 依赖项未就绪 | +| `Superseded` | 被新 CIP 取代 | 头部填写 `superseded-by: CIP-XXX` | + +状态变更通过修改 CIP 头部的 `status` 字段并提交 PR 完成,评审记录保留在 PR 中。 + +## 头部元数据 + +每份 CIP 必须以 YAML 风格头部开始: + +```yaml +--- +cip: 001 +title: 一句话标题 +author: 姓名或组织 <邮箱,可选> +status: Draft +type: Standards Track +category: Consensus / Cross-chain +created: YYYY-MM-DD +requires: CIP-XXX 或 PR #NNN(可选,可多个) +replaces: CIP-XXX(可选) +superseded-by: CIP-XXX(可选) +discussions-to: 讨论链接(PR/issue) +--- +``` + +## 必需章节 + +`摘要`、`动机`、`规范`、`理由`、`向后兼容`、`安全考虑`、`测试与参考实现`、`实施路线`、`版权`。 +规范章节使用 RFC 2119 关键词:**必须(MUST)**、**不得(MUST NOT)**、**应当(SHOULD)**、**可以(MAY)**。 + +## 提交流程 + +1. 复制 `cip-template.md`,按 `cip-<序号>-<slug>.md` 命名(新提案先向维护者申请序号,或按 PR 顺序由维护者分配)。 +2. 提交 PR 到本仓库,PR 标题格式 `CIP-XXX: <title>`,初始 `status: Draft`。 +3. 评审期间在 PR 中讨论并更新文档;状态推进由作者发起、维护者确认。 +4. `Accepted` 后,实施计划写入 `docs/superpowers/plans/` 或对应的设计文档,代码实现另行 PR。 +5. 分叉激活后把状态更新为 `Active`。 + +## 与代码/分叉的关系 + +- CIP 描述**规则与理由**;具体的参数取值、KV 布局、错误码属于规范的一部分,必须写明。 +- 任何共识相关的 CIP 都必须说明:分叉开关名称、默认高度、平行链侧配置映射、以及未升级节点的后果。 +- CIP 不替代审计与测试:实施必须包含单元/集成测试与升级检查清单。 + +## 版权 + +CIP 文本按 [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/) 放弃版权(与 EIP 一致);参考实现代码遵循本仓库的 BSD 3-Clause License。维护者可以调整此约定。 diff --git a/docs/cip/cip-001-paracross-validator-governance.md b/docs/cip/cip-001-paracross-validator-governance.md new file mode 100644 index 000000000..691f3eb27 --- /dev/null +++ b/docs/cip/cip-001-paracross-validator-governance.md @@ -0,0 +1,214 @@ +--- +cip: 001 +title: 平行链验证人来源治理化与主链跨链急停 +author: 33cn/plugin maintainers +status: Draft +type: Standards Track +category: Consensus / Cross-chain +created: 2026-10-14 +requires: PR #1326(paracross title 与 execer 精确绑定) +discussions-to: https://github.com/33cn/plugin/issues +--- + +## 摘要 + +本提案把 paracross 平行链**验证人集合的更换**从"节点组内部 2/3 投票达成即生效"改为"**平行链提案 → 主链分档治理/时间锁 → 显式激活**",并新增**主链治理可控的双向跨链急停**。 + +分档规则:平行链锁定资产高于阈值(默认 10 万 BTY)时,更换**必须**经主链治理批准,批准后仍有 1 天锁定期且治理可在窗口内撤销;低于或等于阈值且未获治理批准时,更换需经历 7 天锁定。生效一律由显式 `Activate` 交易在到点后完成,commit 校验的读路径保持不变。 + +本提案**不**声称解决"多数验证人联合作恶"。主链对平行链状态的独立验证(挑战期、欺诈证明、罚没、zk)列为 Phase 2 路线,见"实施路线"。 + +## 动机 + +paracross 的跨链权限模型把「节点组(super node group)」当作唯一权威。主链只校验 commit 是否由该组 2/3 以上 BLS 聚合签名、高度与哈希是否连续,**不判断平行链状态转移本身是否合法**。由此产生两个结构性问题: + +1. **状态只由平行链自证**:主链无法独立验证"新状态是否由其交易正确推导"。 +2. **验证人身份等价于"持有私钥 + 自建节点组"**:`ForkParaFreeRegister` 之后首次建组为自由注册 + 自审批;已有集合的成员变更由组内 2/3 投票决定。控制或买通一组私钥,即可左右跨链结果。 + +2026-10-13 的"标题前缀接管"事件即为实例:攻击者自建节点组,用伪造 title 的 commit 触发真实平行链的挂起提现,盗走约 161.8 万 BTY(分析见 `docs/security/paracross-nodegroup-title-takeover-analysis.md`)。该事件的入口缺陷已由 PR #1326 修复;本提案处理其**权限模型**层面的根因:验证人集合可以被少数私钥快速更换,且主链没有任何反应窗口与止血手段。 + +## 规范 + +### 术语与现状 + +- **节点组**:某一平行链 title 的授权验证人集合,存储于 `mavl-paracross-nodes-title-<title>`;每个节点的 BLS 公钥与状态存储于 `mavl-paracross-nodes-titleAddr-<title>-<addr>`。 +- **监督节点组**:与节点组同构的第二组验证人,存储于 `mavl-paracross-supervision-nodes-title-<title>`,同样需要 2/3 才能达成共识。 +- 节点组不是节点本地配置:**主链与平行链各存一份**,平行链共识模块本地查询自己那份,主链验证 commit 时查询主链那份。任何变更必须在两侧确定性同时生效。 +- 首次建组(`NodeGroupConfig` op=1/2)维持现状,不在本提案范围内(见"安全考虑·残留风险")。 + +### 分叉与参数 + +- 新分叉开关:`paracross.ForkParaValidatorSet`。所有新增逻辑**必须**由该分叉开启;未达分叉高度时行为与现状逐字节一致。 +- 分叉判定**必须**使用 `cfg.IsDappFork(<主链高度>, "paracross", ForkParaValidatorSet)`。**不得**使用 `IsParaForkHeight`/`GetDappForkHeight`:该函数对未映射的 key 在平行链上会 panic。 +- 平行链**必须**在 `[fork.sub.paracross]` 配置与主链相同的主链高度;所有包含 `[fork.sub.paracross]` 的配置文件**必须**补上该键,否则 chain33 `initForkConfig` 会使节点启动失败。 +- 参数(从链配置 `config.exec.sub.paracross.*` 读取,缺省回落默认值): + +| 参数 | 默认值 | 含义 | +|---|---|---| +| `validatorSetHighValueThreshold` | `100000 × coinPrecision` | 高/低价值分档阈值 | +| `validatorSetDelayBlocksHigh` | `8640` | 治理批准后的锁定期(主链区块数,约 1 天) | +| `validatorSetDelayBlocksLow` | `60480` | 未获治理批准的低价值档锁定期(约 7 天) | + +延迟**必须**按主链区块高度计算,**不得**使用墙钟时间。 + +### 数据结构 + +- 消息复用:本提案**不得**新增 protobuf 生成代码。沿用现有 `ParaNodeGroupConfig`(字段 `Title`/`Op`/`Id`/`Addrs`/`BlsPubKeys`/`AutonomyItemID`),新增以下 op: + +| op | 名称 | 作用 | +|---|---|---| +| 5 | `ParacrossValidatorSetPropose` | 提交节点组变更提案 | +| 6 | `ParacrossValidatorSetApprove` | 治理批准(校验并记录) | +| 7 | `ParacrossValidatorSetCancel` | 撤销提案 | +| 8 | `ParacrossValidatorSetActivate` | 到点后激活(任何人可发) | +| 9 | `ParacrossCrossChainDisable` | 急停:切断该链跨链资金来往 | +| 10 | `ParacrossCrossChainEnable` | 解除急停 | +| 11–14 | `ParacrossSupValidatorSet{Propose,Approve,Cancel,Activate}` | 监督节点组的同一状态机 | + +- 提案状态常量:`ParaValidatorSetPending=0`、`Approved=1`、`Cancelled=2`、`Activated=3`;分档常量:`ParaValidatorSetTierLow=0`、`TierHigh=1`。 +- 新增错误:`ErrValidatorSetNotEffective`、`ErrValidatorSetPendingExists`、`ErrValidatorSetNoActive`、`ErrCrossChainDisabled`。 +- 状态对象(组别维度,`kind ∈ {super, sup}`): + +```go +type paraValidatorSetChange struct { + Id, Title, FromAddr string + NewAddrs, NewBlsPubKeys []string + ProposeMainHeight, LockedAssets int64 + Tier int32 + Approved bool + ApprovalMainHeight int64 + AutonomyItemID string + EffectiveMainHeight int64 + Status int32 + Version uint64 +} +``` + +- KV 布局: + +| Key | 值 | 写入者 | +|---|---|---| +| `mavl-paracross-valset-<kind>-change-<title>` | 提案状态(确定性自定义编码,见下) | Propose/Approve/Cancel/Activate | +| `mavl-paracross-valset-<kind>-version-<title>` | 当前生效版本号 | Activate | +| `mavl-paracross-valset-<kind>-history-<title>-<version>` | 该版本的集合与生效高度(供 Phase 2 追责) | Activate | +| `mavl-paracross-nodes-title-<title>` | 现有 key,**仅 Activate 改写** | Activate(节点组) | +| `mavl-paracross-supervision-nodes-title-<title>` | 现有 key,**仅 Activate 改写** | Activate(监督组) | +| `mavl-paracross-crosschain-switch-<title>` | 急停高度;**key 存在即禁用** | Disable/Enable(Enable 时删除) | +| `mavl-paracross-crosschain-switchinfo-<title>` | `{reason, autonomyItemID}` | Disable | + +- 提案状态的编码**必须**确定性:版本字节 `=1`,其后依次为大端长度前缀字符串 `Id`、`FromAddr`、`AutonomyItemID`,`u32` 计数 + 逐项长度前缀的 `NewAddrs` 与 `NewBlsPubKeys`,`i64 × 5`(`ProposeMainHeight`/`LockedAssets`/`ApprovalMainHeight`/`EffectiveMainHeight`/版本号),`u8 tier`、`u8 status`、`u8 approved`;`Title` 不进编码,由 key 推导。解码**必须**校验版本字节与长度上限(64KB),异常输入返回错误而非 panic。 +- 新增日志类型 `TyLogParaValidatorSetConfig`,用于索引提案与激活事件。 + +### 规则 + +**锁定资产口径**:某 title 的锁定资产 = `LoadExecAccount(ExecAddress(title + "paracross"), ExecAddress("paracross")).Balance`(主链视角,与 `executor/asset.go` 的入金账户一致),在 `Propose` 时快照定档。 + +**Propose** 必须满足: + +1. 分叉已生效; +2. `Title` 为规范形式 `user.p.<name>.` 且与交易 execer 精确对应(PR #1326 的 `IsValidParaTitle` + `checkParaTitleExecMatch`); +3. 该 title 已有生效集合,否则返回 `ErrValidatorSetNoActive`(首次建组走既有 op=1/2); +4. 同一 title **不得**同时存在 `Pending`/`Approved` 提案,否则返回 `ErrValidatorSetPendingExists`; +5. `Addrs` 与 `BlsPubKeys` 数量相等且非空;地址去重并按地址排序(BLS 公钥随地址同步重排),保证主链与平行链写入字节一致; +6. 新集合与当前生效集合不得完全相同。 + +提案 `Id` = 提案交易哈希,`FromAddr` = 交易发起地址。`Tier` 按锁定资产是否**严格大于**阈值判定;未获治理批准前 `EffectiveMainHeight = 0`。 + +**Approve**:提案必须为 `Pending`;治理判定**仅在主链执行**(`if !cfg.IsPara()`),平行链跟随主链结果。治理通过 = autonomy `IsAutonomyApprovedItem(AutonomyItemID, Id)` 成功,或 `isSuperManager(fromaddr)`;否则返回 `ErrNotAllow`。成功后记录 `Approved=true`、`ApprovalMainHeight = 当前主链高度`、`EffectiveMainHeight = ApprovalMainHeight + validatorSetDelayBlocksHigh`。 + +**Cancel**:`Activate` 之前任意时刻,治理(autonomy 或超级管理员)或提案发起地址(`FromAddr`)可以撤销;状态置 `Cancelled`,不影响当前生效集合。 + +**Activate**(任何人可发)必须同时满足: + +1. 提案状态为 `Pending` 或 `Approved`; +2. 当前主链高度 ≥ `EffectiveMainHeight`;未获批准的 `TierHigh` 提案 `EffectiveMainHeight` 恒为 0,但**不得**因此被激活——`Tier == TierHigh && !Approved` 时**必须**拒绝并返回 `ErrValidatorSetNotEffective`; +3. 提案版本号 > 当前生效版本号。 + +执行内容:写入该组别的集合 KV 与每节点状态(含 BLS 公钥,复用现有 `nodeGroupCreate` / 监督组写入路径)、版本号加一、写 history、状态置 `Activated`。**重复 Activate 必须失败**(幂等)。 + +### 入口接管 + +| 现有入口 | 现状 | 分叉后 | +|---|---|---| +| `NodeGroupConfig` op=1/2 | 首次建立集合(自由注册 + 自审批) | **保持不变** | +| `NodeGroupConfig` op=3/4 | 撤销未批准申请 / 修改冻结参数 | 不变(不涉及成员集合) | +| `NodeConfig` op=NewApply/Vote/Quit/Cancel/Modify | 成员变更,内部 2/3 投票达成后直接改写集合 | **改为产出 `Pending` 提案**(新集合 = 当前集合 ± 成员,`Id` = 该交易哈希) | +| `SupervisionNodeConfig` | 监督节点组变更 | 同上(`kind=sup`) | + +即:**内部 2/3 投票达成不等于生效**。平行链侧的提案与投票仍由平行链自己完成,主链新增的是治理闸门与时间锁。 + +### 跨链急停 + +- 粒度:per 平行链 title。触发与解除**必须**经治理批准(autonomy 项或超级管理员),并链上记录原因与设置高度。 +- 生效范围(**双向**): + 1. 新提交:该链的 `AssetTransfer`、`AssetWithdraw`、`CrossAssetTransfer`(execer 为 `user.p.<title>.paracross`)**必须**失败并返回 `ErrCrossChainDisabled`; + 2. 已挂起:`execCrossTxs` 执行挂起提现(ExecPack)时,被禁用 title 的交易**必须**跳过——不执行、不改状态,且**不得**让 commit 交易失败(避免停链); + 3. 解除后挂起提现可正常执行,无需重新提交; + 4. 不影响已验证历史与验证人变更流程(两者独立)。 +- 被禁用的 parallel chain 状态**不得**影响其它 title。 + +### 查询 + +新增 `GetValidatorSetStatus`(入参 `ReqParacrossNodeInfo`,返回 `ReplyConfig`,值为 JSON):包含 `activeVersion`、`activeAddrs`、`pending`(`id`/`status`/`tier`/`approved`/`effectiveMainHeight`)与急停开关状态。JSON 仅用于查询返回,**不得**用于链上状态编码。 + +## 理由 + +- **显式 `Activate` 而非按高度动态解析**:动态解析会改动 commit 校验的读路径(`getNodesGroup`),主链、平行链、历史查询与回滚都必须采用同一高度口径;任一未升级节点会在同一高度算出不同集合。显式激活把切换收敛为一个确定的状态写入,读路径不变,共识风险最小。 +- **复用 `ParaNodeGroupConfig`**:避免新增 protobuf 生成代码与 action/oneof 变更;`Addrs`/`BlsPubKeys`/`Id`/`AutonomyItemID` 已足够表达提案与治理项。 +- **延迟用主链高度而非时间**:区块高度在所有节点上确定,墙钟时间不是共识输入。 +- **高价值链 1 天 + 治理批准、低价值链 7 天免批准**:高价值链的额外风险由治理承担,因此给更短的窗口但必须有批准;低价值链用更长的窗口替代治理介入,兼顾开链效率。 +- **首次建组不收紧**:主链不存在平行链注册表,无法在合约内判定 title 真伪;收紧会改变既定共识语义与开链流程。该缺口作为已接受风险记录,并由急停与监控缓解。 + +## 向后兼容 + +- 未达分叉高度:行为与现状完全一致,无需迁移。 +- 分叉高度后:**必须**先升级主链与**全部**平行链节点。未升级的平行链节点会与主链对"有效集合"的判断不一致,导致共识停摆或分叉——与既有 paracross 分叉的升级要求相同。 +- 已存在的节点组视为 version 1,无需迁移;其后续成员变更走本状态机。 +- 现有查询与 CLI 保持可用;新增查询与命令为增量。 + +## 安全考虑 + +**本提案缓解** + +1. 私钥被控后可进行的"集合更换"被强制延迟 1/7 天,并在高价值链上必须经治理批准; +2. 治理可在窗口内撤销变更,并可用急停立即切断跨链资金来往; +3. 变更全过程链上可审计(提案、批准、激活、急停均有事件与原因字段)。 + +**本提案明确不缓解** + +- 现有集合多数联合作恶仍可按旧规则签名并提交伪造 commit 盗取资产(无欺诈证明与罚没)——需 Phase 2; +- 治理被控的风险:被控治理可以批准恶意变更、滥用急停(只能"停"不能"取",但可造成跨链拒绝服务)。 + +**残留风险** + +- 首次建组仍为自由注册:无生效集合的平行链仍可能被他人抢先建立集合,此后才受时间锁保护。缓解:急停止血;监控"已有平行链 title 出现新节点组/自审批"。 +- 参数(阈值、延迟区块数)若各节点配置不一致,会造成两侧判断不一致。缓解:纳入升级检查清单,后续可迁移为治理参数/mver 固化。 +- `Activate` 需要有人发送(低价值链不会自动生效)。缓解:任何人可发;CLI 与监控提示。 + +## 测试与参考实现 + +参考实现:`docs/superpowers/plans/2026-10-14-paracross-validator-governance.md`(12 个 TDD 任务),设计工作稿:`docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md`。 + +必须覆盖的用例: + +- 状态机全部合法/非法转移:重复 Propose、缺治理批准的高价值 Activate、批准后 Cancel、已激活提案再操作、重复 Activate、版本回退; +- 分档边界:锁定资产 = 阈值、阈值 + 1; +- 延迟边界:`EffectiveMainHeight - 1` 失败、`EffectiveMainHeight` 成功; +- 主链/平行链一致性:同一批交易在两侧执行后集合 KV 与 BLS 公钥逐字节相同; +- 回滚:丢弃 Activate 的 receipt KV 后版本号与集合回到旧值; +- 急停:双向提交被拒、挂起提现不执行且 commit 不失败、解除后恢复; +- 回归:现有 nodegroup/node/supervision/asset/rollup 测试全绿; +- 配置:所有含 `[fork.sub.paracross]` 的配置文件补键后可正常解析(缺键会 panic)。 + +## 实施路线 + +- **Phase 1(本提案范围)**:验证人集合变更状态机 + 治理闸门 + 跨链急停。交付后形成"急停(止血)→ 变更延迟(反应窗口)"两层防御。 +- **Phase 2(后续 CIP)**:主链可验证平行链状态。 + 1. 跨链转出进入挑战期,watcher 提交**见证式欺诈证明**(引用 `{paraHeight, PreStateHash, StateHash, 被质疑交易}`,用 chain33 现有 mavl 证明验证被触碰 key),挑战成立则撤销提现; + 2. 随机抽样审计 + 罚没(前置项:先引入质押,本提案未引入); + 3. zk 有效性证明(长期); + 4. 数据可用性补齐(当前平行链交易已通过 tx group 上主链,暂不缺)。 + +## 版权 + +CIP 文本按 [CC0-1.0](https://creativecommons.org/publicdomain/zero/1.0/) 放弃版权;参考实现代码遵循本仓库 BSD 3-Clause License。 diff --git a/docs/cip/cip-template.md b/docs/cip/cip-template.md new file mode 100644 index 000000000..aae7aea77 --- /dev/null +++ b/docs/cip/cip-template.md @@ -0,0 +1,61 @@ +--- +cip: XXX +title: <一句话标题> +author: <姓名或组织> <邮箱,可选> +status: Draft +type: Standards Track | Process | Informational | Meta +category: <Consensus / Cross-chain / Interface / Executor,Informational 可省略> +created: YYYY-MM-DD +requires: <CIP-XXX 或 PR #NNN,可选,可多个> +replaces: <CIP-XXX,可选> +superseded-by: <CIP-XXX,可选> +discussions-to: <讨论链接> +--- + +## 摘要 + +用 3–5 句话说明:改什么、为什么改、影响谁。 + +## 动机 + +现状是什么、存在什么问题、造成过什么实际后果(如有链上事件请给出高度与交易哈希)。 + +## 规范 + +本节是提案的规范部分,使用 必须/不得/应当/可以(RFC 2119)。 + +### 数据结构 + +新增/修改的链上状态、KV key、消息字段、错误码,逐一给出精确名称与含义。 + +### 规则 + +状态机的状态、转移条件、边界条件、幂等性要求。 + +### 分叉与配置 + +分叉开关名称、默认高度、平行链侧配置映射、参数默认值、未升级节点的后果。 + +## 理由 + +解释关键设计取舍:为什么选择该方案而不是被否掉的替代方案。 + +## 向后兼容 + +对现有链、现有平行链、现有查询/CLI、现有状态的影响;迁移与升级要求。 + +## 安全考虑 + +威胁模型、本提案缓解什么、明确不缓解什么、残留风险与运维缓解措施。 + +## 测试与参考实现 + +单元/集成/回归测试要覆盖的用例清单;参考实现所在的分支或 PR。 + +## 实施路线 + +阶段划分(本期实现 / 后续阶段)、每阶段的交付物与依赖项。 + +## 版权 + +CIP 文本按 CC0-1.0 放弃版权;参考实现代码遵循本仓库 LICENSE。 From f7f0f1d7265e794462a9f521aefc683c88ca1800 Mon Sep 17 00:00:00 2001 From: king <king@kingdeMacBook-Pro-2.local> Date: Wed, 14 Oct 2026 11:38:30 +0800 Subject: [PATCH 4/6] docs(cip): make the tier threshold a governance voted on-chain parameter The 100k BTY tier threshold must not be fixed in code or node config, so CIP-001, the design spec and the implementation plan now define it as an on-chain paracross parameter decided by main chain governance: - two-step flow reusing the existing autonomy PropItem pattern (ParamPropose=15 writes {id=tx hash, value}, board votes on that hash, ParamApply=16 verifies IsAutonomyApprovedItem and writes the value); the proposal tx hash binds the value, so an approved item id alone cannot be used to inject an arbitrary threshold. - the value takes effect immediately but only for proposals created afterwards: a proposal snapshots its tier at Propose time, so changing the threshold cannot retrospectively reclassify an in-flight change. - chain config now only carries the two delay parameters; the code default (100000 x precision) applies only while the state key is absent. Plan gains Task 8 (param state machine) and a seventh review focus item for threshold injection; tasks 8-12 renumbered to 9-13. --- .../cip-001-paracross-validator-governance.md | 38 ++++++++-- ...26-10-14-paracross-validator-governance.md | 73 ++++++++++++++----- ...4-paracross-validator-governance-design.md | 40 ++++++++-- 3 files changed, 117 insertions(+), 34 deletions(-) diff --git a/docs/cip/cip-001-paracross-validator-governance.md b/docs/cip/cip-001-paracross-validator-governance.md index 691f3eb27..3c3aacd22 100644 --- a/docs/cip/cip-001-paracross-validator-governance.md +++ b/docs/cip/cip-001-paracross-validator-governance.md @@ -14,7 +14,7 @@ discussions-to: https://github.com/33cn/plugin/issues 本提案把 paracross 平行链**验证人集合的更换**从"节点组内部 2/3 投票达成即生效"改为"**平行链提案 → 主链分档治理/时间锁 → 显式激活**",并新增**主链治理可控的双向跨链急停**。 -分档规则:平行链锁定资产高于阈值(默认 10 万 BTY)时,更换**必须**经主链治理批准,批准后仍有 1 天锁定期且治理可在窗口内撤销;低于或等于阈值且未获治理批准时,更换需经历 7 天锁定。生效一律由显式 `Activate` 交易在到点后完成,commit 校验的读路径保持不变。 +分档规则:平行链锁定资产高于阈值(初始 10 万 BTY)时,更换**必须**经主链治理批准,批准后仍有 1 天锁定期且治理可在窗口内撤销;低于或等于阈值且未获治理批准时,更换需经历 7 天锁定。**阈值本身由主链治理投票决定**(链上参数,见"阈值治理化"),不由代码常量或节点配置固定。生效一律由显式 `Activate` 交易在到点后完成,commit 校验的读路径保持不变。 本提案**不**声称解决"多数验证人联合作恶"。主链对平行链状态的独立验证(挑战期、欺诈证明、罚没、zk)列为 Phase 2 路线,见"实施路线"。 @@ -41,15 +41,15 @@ paracross 的跨链权限模型把「节点组(super node group)」当作唯 - 新分叉开关:`paracross.ForkParaValidatorSet`。所有新增逻辑**必须**由该分叉开启;未达分叉高度时行为与现状逐字节一致。 - 分叉判定**必须**使用 `cfg.IsDappFork(<主链高度>, "paracross", ForkParaValidatorSet)`。**不得**使用 `IsParaForkHeight`/`GetDappForkHeight`:该函数对未映射的 key 在平行链上会 panic。 - 平行链**必须**在 `[fork.sub.paracross]` 配置与主链相同的主链高度;所有包含 `[fork.sub.paracross]` 的配置文件**必须**补上该键,否则 chain33 `initForkConfig` 会使节点启动失败。 -- 参数(从链配置 `config.exec.sub.paracross.*` 读取,缺省回落默认值): +- 参数与决定方式: -| 参数 | 默认值 | 含义 | +| 参数 | 初始/默认值 | 决定方式 | |---|---|---| -| `validatorSetHighValueThreshold` | `100000 × coinPrecision` | 高/低价值分档阈值 | -| `validatorSetDelayBlocksHigh` | `8640` | 治理批准后的锁定期(主链区块数,约 1 天) | -| `validatorSetDelayBlocksLow` | `60480` | 未获治理批准的低价值档锁定期(约 7 天) | +| 分档阈值 `mavl-paracross-valset-threshold` | `100000 × coinPrecision`(10 万 BTY) | **主链治理投票**(见下节"阈值治理化");链上无该 key 时读取代码默认值 | +| `validatorSetDelayBlocksHigh` | `8640`(约 1 天) | 分叉参数(链配置 `config.exec.sub.paracross.*`) | +| `validatorSetDelayBlocksLow` | `60480`(约 7 天) | 分叉参数(链配置 `config.exec.sub.paracross.*`) | -延迟**必须**按主链区块高度计算,**不得**使用墙钟时间。 +延迟**必须**按主链区块高度计算,**不得**使用墙钟时间。阈值**不得**以代码常量或配置文件作为运行值——它由主链治理投票决定,代码默认值只在链上尚无该 key 时生效。 ### 数据结构 @@ -64,6 +64,8 @@ paracross 的跨链权限模型把「节点组(super node group)」当作唯 | 9 | `ParacrossCrossChainDisable` | 急停:切断该链跨链资金来往 | | 10 | `ParacrossCrossChainEnable` | 解除急停 | | 11–14 | `ParacrossSupValidatorSet{Propose,Approve,Cancel,Activate}` | 监督节点组的同一状态机 | +| 15 | `ParacrossValidatorSetParamPropose` | 提交分档阈值修改提案(值随交易绑定) | +| 16 | `ParacrossValidatorSetParamApply` | 治理批准后应用新阈值 | - 提案状态常量:`ParaValidatorSetPending=0`、`Approved=1`、`Cancelled=2`、`Activated=3`;分档常量:`ParaValidatorSetTierLow=0`、`TierHigh=1`。 - 新增错误:`ErrValidatorSetNotEffective`、`ErrValidatorSetPendingExists`、`ErrValidatorSetNoActive`、`ErrCrossChainDisabled`。 @@ -93,6 +95,8 @@ type paraValidatorSetChange struct { | `mavl-paracross-valset-<kind>-history-<title>-<version>` | 该版本的集合与生效高度(供 Phase 2 追责) | Activate | | `mavl-paracross-nodes-title-<title>` | 现有 key,**仅 Activate 改写** | Activate(节点组) | | `mavl-paracross-supervision-nodes-title-<title>` | 现有 key,**仅 Activate 改写** | Activate(监督组) | +| `mavl-paracross-valset-threshold` | 全局分档阈值(coin 精度单位,int64) | ParamApply | +| `mavl-paracross-valset-paramchange` | 待应用阈值提案 `{id=提案交易 hash, value}` | ParamPropose(Apply 后删除) | | `mavl-paracross-crosschain-switch-<title>` | 急停高度;**key 存在即禁用** | Disable/Enable(Enable 时删除) | | `mavl-paracross-crosschain-switchinfo-<title>` | `{reason, autonomyItemID}` | Disable | @@ -137,6 +141,18 @@ type paraValidatorSetChange struct { 即:**内部 2/3 投票达成不等于生效**。平行链侧的提案与投票仍由平行链自己完成,主链新增的是治理闸门与时间锁。 +### 阈值治理化 + +分档阈值**必须**由主链治理投票决定,且**不得**允许任何人凭一个已获批准的治理项提交任意数值。流程复用现有 autonomy `PropItem`(董事会对某笔交易 hash 投票),两段式: + +1. `ParacrossValidatorSetParamPropose`(op=15):携带新阈值(承载于 `CoinsFrozen` 字段),写入待应用提案 `{id = 本交易 hash, value}`;**不改变**当前生效阈值; +2. 董事会针对该提案交易 hash 发起 autonomy item 并投票通过; +3. `ParacrossValidatorSetParamApply`(op=16):携带 `AutonomyItemID` 与 `Id`(= 提案交易 hash),校验 `IsAutonomyApprovedItem(AutonomyItemID, Id)` 成功后写入 `mavl-paracross-valset-threshold`。 + +**为什么必须两段式**:autonomy item 只能绑定一个交易 hash。若只允许一笔交易(把新数值放在 item 描述中),链上无法证明董事会批准的就是该数值——任何持有同一 itemID 的人都能提交任意阈值。两段式让「提案交易 hash」同时绑定「新数值」,与现有 `NodeGroupApply → autonomy item → NodeGroupApprove` 的安全边界一致。 + +阈值变更**立即生效**,但**只影响此后新提交的提案**:提案在 `Propose` 时已快照其 `Tier`,阈值变化**不得**改变已存在提案的档位。变更本身不改变任何链的锁定资产,因此不设额外时间锁。 + ### 跨链急停 - 粒度:per 平行链 title。触发与解除**必须**经治理批准(autonomy 项或超级管理员),并链上记录原因与设置高度。 @@ -158,6 +174,7 @@ type paraValidatorSetChange struct { - **延迟用主链高度而非时间**:区块高度在所有节点上确定,墙钟时间不是共识输入。 - **高价值链 1 天 + 治理批准、低价值链 7 天免批准**:高价值链的额外风险由治理承担,因此给更短的窗口但必须有批准;低价值链用更长的窗口替代治理介入,兼顾开链效率。 - **首次建组不收紧**:主链不存在平行链注册表,无法在合约内判定 title 真伪;收紧会改变既定共识语义与开链流程。该缺口作为已接受风险记录,并由急停与监控缓解。 +- **阈值由治理投票而非代码/配置**:阈值决定哪些链必须走治理批准,把它固定在代码或各节点配置里,既无法随生态规模调整,也可能因配置不一致引发两侧判断分歧。放进链上治理后,取值全网唯一、可审计,且调整只影响后续提案。 ## 向后兼容 @@ -179,6 +196,12 @@ type paraValidatorSetChange struct { - 现有集合多数联合作恶仍可按旧规则签名并提交伪造 commit 盗取资产(无欺诈证明与罚没)——需 Phase 2; - 治理被控的风险:被控治理可以批准恶意变更、滥用急停(只能"停"不能"取",但可造成跨链拒绝服务)。 +**阈值治理的安全边界** + +- 阈值修改**必须**经治理投票,且 `ParamApply` 只接受「值已随提案交易上链并被同一 hash 绑定」的提案,因此不存在"拿着已批准 item 提交任意阈值"的注入路径; +- 阈值变更立即生效但不回溯:已存在提案的分档在其 `Propose` 时已快照,治理无法用改阈值的方式追溯改变某个在途提案的档位(否则等于绕过时间锁); +- 治理被控时可以把阈值调到极高或极低,从而改变未来提案的档位要求,但**不得**直接改变任何链的锁定资产或绕过既有时间锁——这类滥用会留下完整链上记录。 + **残留风险** - 首次建组仍为自由注册:无生效集合的平行链仍可能被他人抢先建立集合,此后才受时间锁保护。缓解:急停止血;监控"已有平行链 title 出现新节点组/自审批"。 @@ -196,6 +219,7 @@ type paraValidatorSetChange struct { - 延迟边界:`EffectiveMainHeight - 1` 失败、`EffectiveMainHeight` 成功; - 主链/平行链一致性:同一批交易在两侧执行后集合 KV 与 BLS 公钥逐字节相同; - 回滚:丢弃 Activate 的 receipt KV 后版本号与集合回到旧值; +- 阈值治理:未经治理批准的 `ParamApply` 必须失败;携带与提案交易 hash 不匹配的 `Id` 必须失败;应用后新提案按新阈值分档、**已有提案档位不变**; - 急停:双向提交被拒、挂起提现不执行且 commit 不失败、解除后恢复; - 回归:现有 nodegroup/node/supervision/asset/rollup 测试全绿; - 配置:所有含 `[fork.sub.paracross]` 的配置文件补键后可正常解析(缺键会 panic)。 diff --git a/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md b/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md index 344074050..664bc9f59 100644 --- a/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md +++ b/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md @@ -30,6 +30,7 @@ 4. **急停期间的挂起提现(ExecPack)** —— 必须不执行也不改状态,且**不能**让 commit 报错停链。 5. **未升级节点 / 配置文件缺 fork 键** —— 节点启动 panic 或分叉后静默分叉。 6. **重复 Activate** —— 第二次必须失败,不能重复写集合或回退版本号。 +7. **阈值注入** —— `ParamApply` 必须校验数值随提案交易 hash 绑定(同一 `Id`),否则任何持有已批准 item 的人都能把阈值设成任意值;同时阈值变更不得改变在途提案的档位。 ## File Structure @@ -68,35 +69,40 @@ - `pt.ParacrossCrossChainDisable=9`、`ParacrossCrossChainEnable=10` - `pt.ParacrossSupValidatorSetPropose=11`、`...Approve=12`、`...Cancel=13`、`...Activate=14` - `pt.ParaValidatorSetPending=0`、`ParaValidatorSetApproved=1`、`ParaValidatorSetCancelled=2`、`ParaValidatorSetActivated=3` + - `pt.ParacrossValidatorSetParamPropose=15`、`pt.ParacrossValidatorSetParamApply=16`(分档阈值治理) - `pt.ParaValidatorSetTierLow=0`、`ParaValidatorSetTierHigh=1` - `pt.TyLogParaValidatorSetConfig = 684`(新日志类型,并加入 `ParacrossType.GetLogMap`) - 错误常量:`pt.ErrValidatorSetNotEffective`、`pt.ErrValidatorSetPendingExists`、`pt.ErrValidatorSetNoActive`、`pt.ErrCrossChainDisabled`(加入 `types/errors.go`) - - `executor.validatorSetParams(cfg *types.Chain33Config) (threshold, delayHigh, delayLow int64)` + - `executor.validatorSetParams(cfg *types.Chain33Config) (delayHigh, delayLow int64)`(仅延迟;阈值不再来自配置) + - `executor.validatorSetThresholdDefault(cfg *types.Chain33Config) int64` = `100000 × cfg.GetCoinPrecision()`(链上无阈值 key 时的默认值) + - `calcValidatorSetThresholdKey() []byte` → `mavl-paracross-valset-threshold` + - `calcValidatorSetParamChangeKey() []byte` → `mavl-paracross-valset-paramchange` - [ ] **Step 1: 写失败测试** `plugin/dapp/paracross/executor/valsetparam_test.go` ```go func TestValidatorSetParamsDefault(t *testing.T) { - threshold, delayHigh, delayLow := validatorSetParams(chain33TestMainCfg) - assert.Equal(t, int64(100000)*chain33TestMainCfg.GetCoinPrecision(), threshold) + delayHigh, delayLow := validatorSetParams(chain33TestMainCfg) assert.Equal(t, int64(8640), delayHigh) assert.Equal(t, int64(60480), delayLow) + assert.Equal(t, int64(100000)*chain33TestMainCfg.GetCoinPrecision(), validatorSetThresholdDefault(chain33TestMainCfg)) } func TestValidatorSetParamsFromConf(t *testing.T) { cfg := types.NewChain33Config(strings.Replace(types.GetDefaultCfgstring(), "Title=\"local\"", "Title=\"test\"", 1)) - cfg.S("config.exec.sub.paracross.validatorSetHighValueThreshold", int64(500)) cfg.S("config.exec.sub.paracross.validatorSetDelayBlocksHigh", int64(10)) cfg.S("config.exec.sub.paracross.validatorSetDelayBlocksLow", int64(20)) - threshold, delayHigh, delayLow := validatorSetParams(cfg) - assert.Equal(t, int64(500), threshold) + delayHigh, delayLow := validatorSetParams(cfg) assert.Equal(t, int64(10), delayHigh) assert.Equal(t, int64(20), delayLow) } ``` +注意:分档阈值**不得**从配置读取,只有延迟参数走配置。 + - [ ] **Step 2: 运行确认失败** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetParams -v` → 编译错误 `undefined: validatorSetParams`。 -- [ ] **Step 3: 加常量与实现** — 按上面 Interfaces 加常量;`valsetparam.go` 用 `types.ConfSub(cfg, pt.ParaX).GInt(key)` 读取,key 缺省或 ≤0 时回落到默认值(阈值默认 `100000 * cfg.GetCoinPrecision()`)。 + +- [ ] **Step 3: 加常量与实现** — 按上面 Interfaces 加常量与 key;`valsetparam.go` 用 `types.ConfSub(cfg, pt.ParaX).GInt(key)` 读取延迟,key 缺省或 ≤0 时回落 8640/60480。 - [ ] **Step 4: 运行确认通过** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetParams -v` → PASS。 - [ ] **Step 5: 提交** — `git add -A && git commit -m "feat(paracross): add validator set governance fork, ops and params"` @@ -121,6 +127,8 @@ func TestValidatorSetParamsFromConf(t *testing.T) { - `(a *action) getValidatorSetChange(kind, title string) (*paraValidatorSetChange, error)`(不存在返回 `pt.ErrKeyNotExist`) - `(a *action) saveValidatorSetChange(kind string, c *paraValidatorSetChange) *types.KeyValue` - `(a *action) validatorSetVersion(kind, title string) uint64`(不存在返回 0) + - `(a *action) validatorSetThreshold() int64`(读 `mavl-paracross-valset-threshold`,缺失回落 `validatorSetThresholdDefault(cfg)`) + - `(a *action) getValidatorSetParamChange() (id string, value int64, ok bool)` / `(a *action) saveValidatorSetParamChange(id string, value int64) *types.KeyValue` - 编码格式 v1(全部大端):`u8 version=1`,随后依次为长度前缀字符串 `Id`、`FromAddr`、`AutonomyItemID`,`u32 count` + 逐项长度前缀 `NewAddrs`,`u32 count` + 逐项长度前缀 `NewBlsPubKeys`,`i64` × 5(`ProposeMainHeight`、`LockedAssets`、`ApprovalMainHeight`、`EffectiveMainHeight`、版本号),`u8 tier`、`u8 status`、`u8 approved`。`Title` 不进编码(由 key 推导,读取时回填)。 - [ ] **Step 1: 写失败测试** — `TestValidatorSetChangeEncodeDecodeRoundTrip`(构造含 2 个 addr/blspub 的完整结构,编码→解码,`assert.Equal` 全字段)、`TestValidatorSetChangeDecodeRejectsBadVersion`(首字节改 9 → 返回 error)、`TestValidatorSetChangeDecodeRejectsTruncated`(截断一半 → error)、`TestValidatorSetChangeKeyLayout`(断言三个 key 的字符串前缀为 `mavl-paracross-valset-super-`)。 @@ -153,7 +161,7 @@ func TestValidatorSetParamsFromConf(t *testing.T) { - `TestValidatorSetProposeRejectsWhenNoActiveSet`:该 title 无生效集合 → `errors.Cause(err)==pt.ErrValidatorSetNoActive`。 - `TestValidatorSetProposeHighTier`:`setExecAccountBalance(Title+pt.ParaX, 100000*precision+1)` → `Tier==ParaValidatorSetTierHigh` 且 `EffectiveMainHeight==0`(未批准前为 0)。 - [ ] **Step 2: 运行确认失败** — `go test ./plugin/dapp/paracross/executor/ -run TestValidatorSetPropose -v` → FAIL。 -- [ ] **Step 3: 实现** — 前置校验顺序:fork → `checkParaTitleExecMatch` → 已有生效集合(`activeValidatorSet` 非空)→ 无 pending/approved 提案 → addrs/blspubs 数量相等非空 → 去重排序(addr 与 bls 配对后按 addr 排序)→ 与当前集合不同;随后 `lockedAssets` 定档、写提案 KV、发 log `pt.TyLogParaValidatorSetConfig`(log 结构沿用现有 `pt.ReceiptParaNodeGroupConfig`)。 +- [ ] **Step 3: 实现** — 前置校验顺序:fork → `checkParaTitleExecMatch` → 已有生效集合(`activeValidatorSet` 非空)→ 无 pending/approved 提案 → addrs/blspubs 数量相等非空 → 去重排序(addr 与 bls 配对后按 addr 排序)→ 与当前集合不同;随后 `lockedAssets` 与 `a.validatorSetThreshold()` 比较定档(阈值来自链上治理参数,缺失时用代码默认值),写提案 KV、发 log `pt.TyLogParaValidatorSetConfig`(log 结构沿用现有 `pt.ReceiptParaNodeGroupConfig`)。 - [ ] **Step 4: 运行确认通过** — 同上 → PASS。 - [ ] **Step 5: 提交** — `git commit -m "feat(paracross): add validator set change proposal"` @@ -254,11 +262,37 @@ func TestValidatorSetParamsFromConf(t *testing.T) { --- -### Task 8: 急停开关 +### Task 8: 分档阈值治理化(ParamPropose / ParamApply) + +**Files:** +- Modify: `plugin/dapp/paracross/executor/valset.go`、`plugin/dapp/paracross/executor/superaccount.go`(op=15/16 分发) +- Test: `plugin/dapp/paracross/executor/valset_test.go` + +**Interfaces:** +- Consumes: Task 1(op 15/16、阈值 key)、Task 2(`validatorSetThreshold`、param-change 读写)、Task 4(`checkValidatorSetGovernance`) +- Produces: + - `(a *action) validatorSetParamPropose(config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` —— 新阈值承载于 `config.CoinsFrozen`,写 `{id = 本交易 hash, value}`,**不改**生效阈值 + - `(a *action) validatorSetParamApply(config *pt.ParaNodeGroupConfig) (*types.Receipt, error)` —— 校验 `IsAutonomyApprovedItem(config.AutonomyItemID, config.Id)` 后写阈值 KV 并删除待应用提案 + +- [ ] **Step 1: 写失败测试** + - `TestValidatorSetParamProposeWritesOnlyPending`:提案后 `validatorSetThreshold()` 仍为默认值,待应用提案已存在。 + - `TestValidatorSetParamProposeRejectsZeroAndSame`:新阈值 ≤ 0 或等于当前值 → error。 + - `TestValidatorSetParamProposeRejectsSecondPending` → `pt.ErrValidatorSetPendingExists`。 + - `TestValidatorSetParamApplyRequiresGovernance`:非治理地址 → `types.ErrNotAllow`。 + - `TestValidatorSetParamApplyBindsValue`:`Id` 与提案交易 hash 不匹配 → error(Review Focus 7:数值注入)。 + - `TestValidatorSetParamApplyKeepsPendingProposalTier`:先 Propose 一个集合变更提案(低价值档),再把阈值调低并 Apply → 在途提案的 `Tier` 与 `EffectiveMainHeight` **不变**(Review Focus 7:快照)。 +- [ ] **Step 2: 运行确认失败** → FAIL。 +- [ ] **Step 3: 实现** — 复用 Task 4 的治理校验;`ParamApply` 成功后写阈值并删除待应用提案。 +- [ ] **Step 4: 运行确认通过** → PASS。 +- [ ] **Step 5: 提交** — `git commit -m "feat(paracross): governance voted validator set tier threshold"` + +--- + +### Task 9: 急停开关 **Files:** - Create: `plugin/dapp/paracross/executor/circuitbreaker.go` -- Modify: `plugin/dapp/paracross/executor/query.go`(可选:开关状态并入 Task 7 的 JSON) +- Modify: `plugin/dapp/paracross/executor/query.go`(可选:开关与阈值状态并入 Task 7 的 JSON) - Test: `plugin/dapp/paracross/executor/circuitbreaker_test.go` **Interfaces:** @@ -284,7 +318,7 @@ func TestValidatorSetParamsFromConf(t *testing.T) { --- -### Task 9: 急停生效点(提交 + 挂起执行) +### Task 10: 急停生效点(提交 + 挂起执行) **Files:** - Modify: `plugin/dapp/paracross/executor/asset.go`(提交入口)、`plugin/dapp/paracross/executor/action.go`(`execCrossTxs`) @@ -310,7 +344,7 @@ func TestValidatorSetParamsFromConf(t *testing.T) { --- -### Task 10: 配置文件补分叉键 +### Task 11: 配置文件补分叉键 **Files:** - Modify: `chain33.fork.toml`、`chain33.para.toml`、`chain33.para.toml.readme`、`plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml`、`plugin/dapp/ticket/executor/testdata/chain33.cfg.toml`、`plugin/dapp/ticket/rpc/rpc_test.go`、`build/ci/{cross2eth,bridgevmxgo,rollup,rgbx}/chain33.para.toml`、`build/ci/evm2/chain33.proxyminer.toml` @@ -335,13 +369,13 @@ Expected: 列出全部文件为 MISSING。 --- -### Task 11: 集成与一致性测试 +### Task 12: 集成与一致性测试 **Files:** - Create: `plugin/dapp/paracross/executor/valset_integration_test.go` **Interfaces:** -- Consumes: Task 1–9 全部 +- Consumes: Task 1–10 全部(含 Task 8 阈值治理) - Produces: 端到端用例,证明主链与平行链执行同一批交易后状态一致 - [ ] **Step 1: 写失败测试** @@ -355,17 +389,18 @@ Expected: 列出全部文件为 MISSING。 --- -### Task 12: CLI 与运维文档 +### Task 13: CLI 与运维文档 **Files:** - Modify: `plugin/dapp/paracross/commands/paracross.go` - Modify: `plugin/dapp/paracross/executor/superaccount.md`(或新增 `docs/` 运维章节) **Interfaces:** -- Consumes: Task 7/8 的查询与 op +- Consumes: Task 7/8/9 的查询与 op - Produces: - `paracross valset propose|approve|cancel|activate`(参数:paraName、addrs、blspubs、id、autonomyItemID) - `paracross crosschain disable|enable --paraName --reason` + - `paracross valset threshold propose --coins <newThreshold> --autonomyItemID <id>` / `paracross valset threshold apply --id <proposalTxHash> --autonomyItemID <id>` - `paracross valset status --paraName` - 运维文档:变更生命周期、急停/恢复 SOP、监控项(`valset pending` 年龄、`tier`、紧停开关状态) @@ -379,6 +414,6 @@ Expected: 列出全部文件为 MISSING。 ## 自检记录 -- **Spec 覆盖**:第 5 节→Task 3-6;第 6 节→Task 1-2;第 7 节→Task 3-5;第 8 节→Task 6;第 9 节→Task 4;第 10 节→Task 8-9;第 11 节→Task 10-11;第 12 节→不在本期(仅路线);第 13 节→Task 6(bootstrap 不变)+ Task 11;第 14 节→Task 10-12;第 15 节→各任务测试 + Task 11。 -- **类型一致性**:`paraValidatorSetChange` 字段、`validatorGroupSuper/Supervision`、`validatorSetParams`、`checkValidatorSetGovernance`、`checkCrossChainEnabled` 在 Task 1–12 中命名一致。 -- **Review Focus**:6 项分别落在 Task 4(治理缺失)、Task 5(生效边界/幂等)、Task 3(重复提案)、Task 9(挂起提现与 commit 不停链)、Task 10(配置缺键)、Task 5(重复 Activate)的测试步骤中。 +- **Spec 覆盖**:第 5 节→Task 3-6;第 6 节→Task 1-2、Task 8(阈值治理化 6.5);第 7 节→Task 3-5、Task 8(阈值状态机);第 8 节→Task 6;第 9 节→Task 4、Task 8;第 10 节→Task 9-10;第 11 节→Task 11-12;第 12 节→不在本期(仅路线);第 13 节→Task 6(bootstrap 不变)+ Task 12;第 14 节→Task 11-13;第 15 节→各任务测试 + Task 12。 +- **类型一致性**:`paraValidatorSetChange` 字段、`validatorGroupSuper/Supervision`、`validatorSetParams`、`validatorSetThreshold`、`validatorSetThresholdDefault`、`checkValidatorSetGovernance`、`checkCrossChainEnabled` 在 Task 1–13 中命名一致。 +- **Review Focus**:7 项分别落在 Task 4(治理缺失)、Task 5(生效边界/幂等)、Task 3(重复提案)、Task 10(挂起提现与 commit 不停链)、Task 11(配置缺键)、Task 5(重复 Activate)、Task 8(阈值注入与在途提案快照)的测试步骤中。 diff --git a/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md b/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md index 1977446c1..03fba9b2c 100644 --- a/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md +++ b/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md @@ -111,6 +111,10 @@ ParacrossValidatorSetCancel = 7 ParacrossValidatorSetActivate = 8 ParacrossCrossChainDisable = 9 ParacrossCrossChainEnable = 10 +// 监督节点组:Propose=11 / Approve=12 / Cancel=13 / Activate=14 +// 分档阈值治理:ParamPropose=15 / ParamApply=16 +ParacrossValidatorSetParamPropose = 15 +ParacrossValidatorSetParamApply = 16 ``` 字段映射:`Title`=平行链 title;`Addrs`=新集合地址(逗号分隔);`BlsPubKeys`=与 `Addrs` 一一对应的 BLS 公钥;`Id`=提案 id;`AutonomyItemID`=治理批准项 id。 @@ -122,6 +126,8 @@ ParacrossCrossChainEnable = 10 | `mavl-paracross-valset-change-<title>` | 当前待生效提案(第 6.3 节结构) | Propose/Approve/Cancel | | `mavl-paracross-valset-version-<title>` | 当前生效版本号 `uint64` | Activate | | `mavl-paracross-valset-history-<title>-<version>` | 该版本的集合与 `ActiveFromMainHeight` | Activate(供 Phase 2 追责) | +| `mavl-paracross-valset-threshold` | 全局分档阈值(int64,coin 精度) | ParamApply | +| `mavl-paracross-valset-paramchange` | 待应用阈值提案 `{id, value}` | ParamPropose(Apply 后删除) | | `mavl-paracross-crosschain-switch-<title>` | 急停状态结构 | Disable/Enable | | `mavl-paracross-nodes-title-<title>` | **现有 key,仅 Activate 改写** | Activate | | `mavl-paracross-nodes-titleAddr-<title>-<addr>` | **现有 key**,每节点状态与 BLS 公钥 | Activate(复用 `nodeGroupCreate` 的写入逻辑) | @@ -153,13 +159,15 @@ type ParaCrossChainSwitch struct { } ``` -### 6.4 参数与默认值(从链配置读取,口径同 `nodeGroupFrozenCoins`) +### 6.4 参数与决定方式 -| 参数 | 默认值 | 说明 | +| 参数 | 初始/默认值 | 决定方式 | |---|---|---| -| `validatorSetHighValueThreshold` | `100000` × coinPrecision | 高/低价值分档阈值(默认 10 万 BTY) | -| `validatorSetDelayBlocksHigh` | `8640` | 治理批准后的锁定期(按 10s/块 ≈ 1 天) | -| `validatorSetDelayBlocksLow` | `60480` | 未获治理批准的低价值档锁定期(≈ 7 天) | +| 分档阈值(`mavl-paracross-valset-threshold`) | `100000` × coinPrecision(10 万 BTY) | **主链治理投票**(见 6.5);链上无该 key 时用代码默认值 | +| `validatorSetDelayBlocksHigh` | `8640` | 分叉参数(链配置,按 10s/块 ≈ 1 天) | +| `validatorSetDelayBlocksLow` | `60480` | 分叉参数(链配置,≈ 7 天) | + +阈值**不得**以代码常量或节点配置作为运行值:它由主链治理投票决定,代码默认值只在链上尚无该 key 时生效。延迟仍走链配置(口径同现有 `nodeGroupFrozenCoins`)。 延迟一律按**主链区块高度**计算,不使用墙钟时间(共识确定性): @@ -167,6 +175,18 @@ type ParaCrossChainSwitch struct { - 未批准且低价值档:`EffectiveMainHeight = ProposeMainHeight + validatorSetDelayBlocksLow` - 高价值档未获批准:**不可激活**(`Activate` 失败) +### 6.5 阈值治理化(两段式) + +分档阈值由主链治理投票决定,流程复用现有 autonomy `PropItem`(董事会对某笔交易 hash 投票),与现有 `NodeGroupApply → autonomy item → NodeGroupApprove` 完全同构: + +1. `ParamPropose`(op=15):新阈值承载于 `ParaNodeGroupConfig.CoinsFrozen`,写入 `{id = 本交易 hash, value}`;**不改生效阈值**; +2. 董事会针对该提案交易 hash 发起 autonomy item 并投票通过; +3. `ParamApply`(op=16):携带 `AutonomyItemID` 与 `Id`(= 提案交易 hash),校验 `IsAutonomyApprovedItem` 通过后写入 `mavl-paracross-valset-threshold`,并删除待应用提案。 + +**必须两段式的原因**:autonomy item 只能绑定一个交易 hash;若只用一笔交易(数值写在 item 描述里),链上无法证明董事会批准的就是该数值,任何持有该 itemID 的人都能提交任意阈值。两段式让「提案交易 hash」同时绑定「新数值」。 + +阈值变更**立即生效**,但**只影响此后新提交的提案**——提案在 `Propose` 时已快照 `Tier`,阈值变化不得改变已存在提案的档位(否则等于绕过时间锁)。阈值变更不影响任何链的锁定资产,因此不设额外时间锁。 + ## 7. 状态机 ### 7.1 状态与转移 @@ -195,6 +215,7 @@ type ParaCrossChainSwitch struct { 4. **Activate**:任何人可发;必须同时满足 —— 提案状态为 `PENDING/APPROVED`、`mainHeight ≥ EffectiveMainHeight`、`tier==0 || Approved`、目标版本号 > 当前版本号。执行内容:写入集合与每节点状态(复用 `nodeGroupCreate` 的写入路径)、`valset-version+1`、`valset-history` 记录、提案状态置 `ACTIVATED`。 5. **幂等**:同版本重复 Activate 失败;`Activated/Cancelled` 状态的提案不可再 Approve/Activate。 6. 所有状态变更都产生独立的 receipt log(含 title、提案 id、新版本号、生效高度),便于索引与监控。 +7. **阈值状态机**:`ParamPropose` 要求新阈值 > 0、与当前生效阈值不同、且当前无待应用提案;`ParamApply` 要求待应用提案存在、`Id` 与其绑定、治理校验通过;应用后阈值立即对**此后**的 `Propose` 生效,已存在提案的 `Tier` 不变。 ## 8. 变更入口接管 @@ -213,7 +234,8 @@ type ParaCrossChainSwitch struct { - 治理批准项(autonomy item)的发起方式不在本设计范围内改变:沿用现有流程——由平行链治理在主链 autonomy 发起提案、board 成员投票通过后 `IsAutonomyApprovedItem(itemID, proposalId)` 返回成功。本设计的 `Approve` 交易只是**校验并记录**该批准结果,不改变 autonomy 自身的投票机制; - 判定只在主链执行(`if !cfg.IsPara()`),平行链跟随主链执行结果,与现有 `nodeGroupApprove` 的模式一致; - 高价值档必须批准;低价值档可选择批准以把等待期从 7 天缩短到 1 天; -- 批准后 1 天窗口内治理可撤销;`Activate` 之前任意时刻治理与提案人均可撤销。 +- 批准后 1 天窗口内治理可撤销;`Activate` 之前任意时刻治理与提案人均可撤销; +- 分档阈值同样由治理投票决定(6.5),但走独立的 `ParamPropose/ParamApply` 两段式:阈值是全局参数、影响此后所有提案的档位,因此不复用单个 title 的提案对象。 ## 10. 跨链急停 @@ -275,7 +297,8 @@ type ParaCrossChainSwitch struct { - 分档边界:锁定资产 = 阈值、阈值 + 1; - 延迟边界:`EffectiveMainHeight - 1`(失败)、`EffectiveMainHeight`(成功); - 幂等:同版本重复 Activate 失败; -- 急停:新提交被拒(双向)、挂起提现不执行、恢复后可执行、无治理批准不可切换。 +- 急停:新提交被拒(双向)、挂起提现不执行、恢复后可执行、无治理批准不可切换; +- 阈值治理:未批准 `ParamApply` 失败、`Id` 与提案不匹配失败、重复 `ParamPropose` 失败、应用后新提案按新阈值分档且已有提案档位不变、链上无 key 时回落到代码默认值。 **集成测试(testnode,主链 + 平行链)** @@ -307,6 +330,7 @@ type ParaCrossChainSwitch struct { | 5 | 实现方案 | 验证人集合状态机 + 延迟到点后显式激活(commit 读路径不变) | | 6 | 首次建集合 | 保持现有自由注册(状态机只管"已有集合的更换") | | 7 | 急停 | 治理可控、**双向**切断跨链(含入金),恢复后挂起提现可执行 | +| 8 | 分档阈值 | 由**主链治理投票**决定(链上参数,初始 10 万 BTY),两段式 ParamPropose/ParamApply 绑定数值;批准后立即生效、只影响此后新提案 | ## 17. 假设与默认口径(如需变更请在实施前提出) @@ -314,6 +338,6 @@ type ParaCrossChainSwitch struct { 2. **锁定资产口径** = `dapp.ExecAddress("user.p.<name>.paracross")` 的 coin 余额(token 暂不计入),在 `Propose` 时快照定档; 3. **同一时刻仅一个待生效提案**,新提案必须等前一个 Activate/Cancel 之后; 4. **提案 id** = `Propose` 交易的 hash;`Cancel` 权限 = 治理或提案发起地址; -5. **延迟与阈值参数** 走链配置(第 6.4 节默认值),非治理可调参数;后续可迁移为治理参数; +5. **延迟参数**(1 天/7 天)走链配置;**分档阈值**由主链治理投票决定(第 6.5 节),代码默认值仅在链上无该 key 时生效; 6. **监督节点组** 与超级节点组同样纳入本状态机; 7. **版本号** 从 1 开始,已有节点组视为 version 1。 From 5aef3e6cca05107ed3cbed347ffed30b86e050ec Mon Sep 17 00:00:00 2001 From: king <king@kingdeMacBook-Pro-2.local> Date: Wed, 14 Oct 2026 11:39:19 +0800 Subject: [PATCH 5/6] docs(cip): denominate the tier threshold in coins, not a chain specific symbol CIP-001, the design spec and the plan now state every amount in the main chain native coin (the cfg.GetCoinExec() coin, smallest unit given by coinPrecision) instead of naming a particular chain's token. The unit is defined once in the specification, and the plan's global constraints no longer describe the threshold as a chain config value (it is the governance voted on-chain parameter from the previous commit). --- docs/cip/cip-001-paracross-validator-governance.md | 8 +++++--- .../plans/2026-10-14-paracross-validator-governance.md | 3 ++- .../2026-10-14-paracross-validator-governance-design.md | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/cip/cip-001-paracross-validator-governance.md b/docs/cip/cip-001-paracross-validator-governance.md index 3c3aacd22..c01597c37 100644 --- a/docs/cip/cip-001-paracross-validator-governance.md +++ b/docs/cip/cip-001-paracross-validator-governance.md @@ -14,7 +14,7 @@ discussions-to: https://github.com/33cn/plugin/issues 本提案把 paracross 平行链**验证人集合的更换**从"节点组内部 2/3 投票达成即生效"改为"**平行链提案 → 主链分档治理/时间锁 → 显式激活**",并新增**主链治理可控的双向跨链急停**。 -分档规则:平行链锁定资产高于阈值(初始 10 万 BTY)时,更换**必须**经主链治理批准,批准后仍有 1 天锁定期且治理可在窗口内撤销;低于或等于阈值且未获治理批准时,更换需经历 7 天锁定。**阈值本身由主链治理投票决定**(链上参数,见"阈值治理化"),不由代码常量或节点配置固定。生效一律由显式 `Activate` 交易在到点后完成,commit 校验的读路径保持不变。 +分档规则:平行链锁定资产高于阈值(初始 10 万 coins)时,更换**必须**经主链治理批准,批准后仍有 1 天锁定期且治理可在窗口内撤销;低于或等于阈值且未获治理批准时,更换需经历 7 天锁定。**阈值本身由主链治理投票决定**(链上参数,见"阈值治理化"),不由代码常量或节点配置固定。生效一律由显式 `Activate` 交易在到点后完成,commit 校验的读路径保持不变。 本提案**不**声称解决"多数验证人联合作恶"。主链对平行链状态的独立验证(挑战期、欺诈证明、罚没、zk)列为 Phase 2 路线,见"实施路线"。 @@ -25,7 +25,7 @@ paracross 的跨链权限模型把「节点组(super node group)」当作唯 1. **状态只由平行链自证**:主链无法独立验证"新状态是否由其交易正确推导"。 2. **验证人身份等价于"持有私钥 + 自建节点组"**:`ForkParaFreeRegister` 之后首次建组为自由注册 + 自审批;已有集合的成员变更由组内 2/3 投票决定。控制或买通一组私钥,即可左右跨链结果。 -2026-10-13 的"标题前缀接管"事件即为实例:攻击者自建节点组,用伪造 title 的 commit 触发真实平行链的挂起提现,盗走约 161.8 万 BTY(分析见 `docs/security/paracross-nodegroup-title-takeover-analysis.md`)。该事件的入口缺陷已由 PR #1326 修复;本提案处理其**权限模型**层面的根因:验证人集合可以被少数私钥快速更换,且主链没有任何反应窗口与止血手段。 +2026-10-13 的"标题前缀接管"事件即为实例:攻击者自建节点组,用伪造 title 的 commit 触发真实平行链的挂起提现,盗走约 161.8 万 coins(主链原生币;分析见 `docs/security/paracross-nodegroup-title-takeover-analysis.md`)。该事件的入口缺陷已由 PR #1326 修复;本提案处理其**权限模型**层面的根因:验证人集合可以被少数私钥快速更换,且主链没有任何反应窗口与止血手段。 ## 规范 @@ -45,12 +45,14 @@ paracross 的跨链权限模型把「节点组(super node group)」当作唯 | 参数 | 初始/默认值 | 决定方式 | |---|---|---| -| 分档阈值 `mavl-paracross-valset-threshold` | `100000 × coinPrecision`(10 万 BTY) | **主链治理投票**(见下节"阈值治理化");链上无该 key 时读取代码默认值 | +| 分档阈值 `mavl-paracross-valset-threshold` | `100000 × coinPrecision`(即 10 万 coins) | **主链治理投票**(见下节"阈值治理化");链上无该 key 时读取代码默认值 | | `validatorSetDelayBlocksHigh` | `8640`(约 1 天) | 分叉参数(链配置 `config.exec.sub.paracross.*`) | | `validatorSetDelayBlocksLow` | `60480`(约 7 天) | 分叉参数(链配置 `config.exec.sub.paracross.*`) | 延迟**必须**按主链区块高度计算,**不得**使用墙钟时间。阈值**不得**以代码常量或配置文件作为运行值——它由主链治理投票决定,代码默认值只在链上尚无该 key 时生效。 +本提案中的金额一律以**主链原生币 `coins`** 计价(即 `cfg.GetCoinExec()` 对应币种,最小单位由 `coinPrecision` 决定),**不得**绑定任何特定链的代币符号。 + ### 数据结构 - 消息复用:本提案**不得**新增 protobuf 生成代码。沿用现有 `ParaNodeGroupConfig`(字段 `Title`/`Op`/`Id`/`Addrs`/`BlsPubKeys`/`AutonomyItemID`),新增以下 op: diff --git a/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md b/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md index 664bc9f59..fd97ad16f 100644 --- a/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md +++ b/docs/superpowers/plans/2026-10-14-paracross-validator-governance.md @@ -16,7 +16,8 @@ - 分叉判定用 `cfg.IsDappFork(mainHeight, pt.ParaX, pt.ForkParaValidatorSet)`,**禁用** `pt.IsParaForkHeight`(未映射 key 在平行链上会 panic)。平行链在 `[fork.sub.paracross]` 配置与主链相同的主链高度。 - title 必须为规范形式 `user.p.<name>.` 且与交易 execer 精确对应:复用 PR #1326 的 `pt.IsValidParaTitle` 与 `a.checkParaTitleExecMatch`(该 PR 可能尚未合入 master;若不在基线中,先 cherry-pick 或复制这两个函数)。 - **不新增 protobuf 生成代码**:本环境无 `protoc`;状态用确定性自定义编码(大端、定长、版本字节)。 -- 延迟一律按**主链区块高度**,禁止墙钟时间;参数从链配置读取(`types.ConfSub(cfg, pt.ParaX).GInt(...)`),默认 `validatorSetHighValueThreshold = 100000 * cfg.GetCoinPrecision()`、`validatorSetDelayBlocksHigh = 8640`、`validatorSetDelayBlocksLow = 60480`。 +- 延迟一律按**主链区块高度**,禁止墙钟时间;延迟参数从链配置读取(`types.ConfSub(cfg, pt.ParaX).GInt(...)`),默认 `validatorSetDelayBlocksHigh = 8640`、`validatorSetDelayBlocksLow = 60480`。 +- **分档阈值不是配置项**:它是链上治理参数(`mavl-paracross-valset-threshold`,见 Task 8),链上无该 key 时回落到代码默认 `100000 × cfg.GetCoinPrecision()`(即 10 万 coins,一律以主链原生币计价,不写任何特定链符号)。 - "锁定资产"口径:`a.coinsAccount.LoadExecAccount(address.ExecAddress(title+pt.ParaX), address.ExecAddress(pt.ParaX)).Balance`(主链视角,与 `executor/asset.go` 的入金账户一致)。 - 主链判定治理、平行链跟随(`if !cfg.IsPara()`),与现有 `nodeGroupApprove` 模式一致。 - 所有含 `[fork.sub.paracross]` 的配置文件都必须补 `ForkParaValidatorSet`,否则 chain33 `initForkConfig` 直接 panic。 diff --git a/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md b/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md index 03fba9b2c..35b34b098 100644 --- a/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md +++ b/docs/superpowers/specs/2026-10-14-paracross-validator-governance-design.md @@ -14,7 +14,7 @@ paracross 的跨链权限模型把「节点组(super node group)」当作唯 1. **状态只由平行链自证**:主链无法独立判断"这条平行链新产生的状态是否由其交易正确推导而来",只能相信签名集合。 2. **验证人身份等价于"持有私钥 + 自建节点组"**:`ForkParaFreeRegister` 之后,首次建组是自由注册 + 自审批;已有集合的成员变更由组内 2/3 投票决定。因此控制(或买通)一组私钥,或抢先注册一个无人认领的 title,就能左右跨链结果。 -2026-10-13 的"标题前缀接管"事件(`docs/security/`)正是该模型被利用的实例:攻击者自建节点组后,用伪造 title 的 commit 触发真实平行链的提现,盗走约 161.8 万 BTY。 +2026-10-13 的"标题前缀接管"事件(`docs/security/`)正是该模型被利用的实例:攻击者自建节点组后,用伪造 title 的 commit 触发真实平行链的提现,盗走约 161.8 万 coins(主链原生币)。 本设计不追求一次性彻底去信任化。Phase 1 的目标是把"更换验证人"从**内部投票即生效**改成**平行链提案 + 主链分档治理/时间锁 + 显式激活**,并给治理一个**跨链急停**开关;主链对状态的独立验证(挑战期/欺诈证明/罚没/zk)作为 Phase 2 在同一文档中给出路线与接口。 @@ -163,11 +163,11 @@ type ParaCrossChainSwitch struct { | 参数 | 初始/默认值 | 决定方式 | |---|---|---| -| 分档阈值(`mavl-paracross-valset-threshold`) | `100000` × coinPrecision(10 万 BTY) | **主链治理投票**(见 6.5);链上无该 key 时用代码默认值 | +| 分档阈值(`mavl-paracross-valset-threshold`) | `100000` × coinPrecision(即 10 万 coins) | **主链治理投票**(见 6.5);链上无该 key 时用代码默认值 | | `validatorSetDelayBlocksHigh` | `8640` | 分叉参数(链配置,按 10s/块 ≈ 1 天) | | `validatorSetDelayBlocksLow` | `60480` | 分叉参数(链配置,≈ 7 天) | -阈值**不得**以代码常量或节点配置作为运行值:它由主链治理投票决定,代码默认值只在链上尚无该 key 时生效。延迟仍走链配置(口径同现有 `nodeGroupFrozenCoins`)。 +阈值**不得**以代码常量或节点配置作为运行值:它由主链治理投票决定,代码默认值只在链上尚无该 key 时生效。金额一律以**主链原生币 `coins`** 计价(`cfg.GetCoinExec()`,最小单位由 `coinPrecision` 决定),不绑定任何特定链的代币符号。延迟仍走链配置(口径同现有 `nodeGroupFrozenCoins`)。 延迟一律按**主链区块高度**计算,不使用墙钟时间(共识确定性): @@ -330,7 +330,7 @@ type ParaCrossChainSwitch struct { | 5 | 实现方案 | 验证人集合状态机 + 延迟到点后显式激活(commit 读路径不变) | | 6 | 首次建集合 | 保持现有自由注册(状态机只管"已有集合的更换") | | 7 | 急停 | 治理可控、**双向**切断跨链(含入金),恢复后挂起提现可执行 | -| 8 | 分档阈值 | 由**主链治理投票**决定(链上参数,初始 10 万 BTY),两段式 ParamPropose/ParamApply 绑定数值;批准后立即生效、只影响此后新提案 | +| 8 | 分档阈值 | 由**主链治理投票**决定(链上参数,初始 10 万 coins),两段式 ParamPropose/ParamApply 绑定数值;批准后立即生效、只影响此后新提案 | ## 17. 假设与默认口径(如需变更请在实施前提出) From a380bf7de2bfb6084eb9346d73daa428435561be Mon Sep 17 00:00:00 2001 From: king <king@kingdeMacBook-Pro-2.local> Date: Wed, 14 Oct 2026 14:29:53 +0800 Subject: [PATCH 6/6] chore(ci): retrigger checks after flaky unit-test race The unit-test failure on this branch was a pre-existing data race in plugin/dapp/paracross/testnode (fixed separately in the CI PR); this empty commit re-runs the checks. Drop it when squashing.