Skip to content

fix(ticket): only allow Exec_Genesis at height 0 - #1337

Merged
33cn merged 6 commits into
masterfrom
fix/ticket-genesis-height-zero
Oct 16, 2026
Merged

33cn merged 6 commits into
masterfrom
fix/ticket-genesis-height-zero

Conversation

@vipwzw

@vipwzw vipwzw commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

问题

Ticket.Exec_Genesis 缺少 coins/coinsx/token 都有的 height != 0 校验,任意高度都能重入 GenesisInit:

  • 造出 isGenesis=true、3 段 ticketId 的“创世票”——genPrivHash 对 3 段 id 返回空,pubHash 校验整段跳过;
  • TicketMiner/TicketClose 的时间校验只对 !IsGenesis 生效 → 免成熟期、免提现等待;
  • 受害矿工会把它当成自己的票(本地索引与共识选票都挂在 MinerAddress 上)立刻挖出,ExecDepositFrozen 把出块奖励增发到攻击者的 ReturnAddress,攻击者当场 Tclose 提走,本金零锁仓。

PoC 在 height=1,700,000(ForkTicketId 已生效)实测:Exec_Genesis 返回 ExecOk,500000000 奖励记到攻击者地址并立即关闭成功。

修复

框架层单点拦截:chain33 新增系统分叉 ForkGenesisOnlyAtHeightZero(types/fork.go 注册默认 0),由 DriverBase.Exec / callLocal 在派发前调用 checkGenesisHeight 统一拦截,保护所有漏写该校验的执行器。上游 PR:33cn/chain33#1407(v1.72.6 起提供)。

ticket/executor/exec.go 的 Exec_Genesis 不再重复实现同一校验(此前那版会让同一约束存在两份实现、各自漂移),只保留注释指明约束来自框架层。

这让保护完全依赖该分叉生效,因此把前提显式固定下来(而不是靠"默认应该是开的"):

  • 本仓库所有 [fork.system] 配置都显式补该键为 0(创世即生效):chain33.fork.toml、chain33.para.toml、plugin/consensus/qbft/chain33.qbft.toml、plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml、plugin/dapp/ticket/executor/testdata/chain33.cfg.toml;
  • Test_TicketGenesisReRunRejected_PostFork 先 require.True(cfg.IsFork(height, ForkGenesisOnlyAtHeightZero)) 把"分叉已生效"断言出来:一旦被配成 -1(→ MaxHeight,即关闭)用例直接变红(已做变异验证),不存在"静默无保护但 CI 全绿";
  • 版本上,plugin 现在要求 chain33 ≥ v1.72.6:配置里已含该键,链接未注册该分叉的旧 chain33(v1.72.0 / v1.72.4 均无)会在 initForkConfig 因 system fork not exist 直接启动失败,而不是静默放过。

被拒绝的 Genesis 交易仍会进块,但不产生任何创世 KV:回执是 ExecPack 加一条 TyLogErr,MinTxFeeRate > 0 时手续费照收(既有手续费行为)。链上状态不变;ExecLocal_Genesis/ExecDelLocal_Genesis 也被框架守卫一并跳过(DriverBase.callLocal 复用同一 checkGenesisHeight),因此 LODB-ticket-tl:* 本地索引不会被污染。

依赖

github.com/33cn/chain33 → v1.72.7-0.20261016053907-a95e7f042582(分支 fix/dht-keep-established-identity,33cn/chain33#1408)。

  • 本 PR 需要的系统分叉 ForkGenesisOnlyAtHeightZero 自 v1.72.6 起提供(fix(dapp): only allow the genesis action at height 0 chain33#1407);合约层不再自带校验,所以链接旧 chain33 不再是"少一层保护",而是节点启动即失败;
  • 但 v1.72.6 仍带着 v1.72.1 引入的 dht 回归:钱包就绪后 doGenAirDropKey 用钱包空投地址替换节点身份并 reStart(),节点在运行期换 peer id。ci_rgbx 正是被它打断——重启前发现并写进 TSS peers 配置的 4 个 peer 名,在钱包就绪后与实际运行身份零交集,DKG 无法形成,auto DKG commit 超时(把创世分叉配成 -1 复跑仍复现,与本 PR 的分叉无关);
  • 切到 #1408 的修复后 ci_rgbx 通过(5m29s),重启前后 peer 名保持一致;等 chain33 发布 v1.72.7 后可换成正式版本号。

chain33 的 initForkConfig 要求每个已注册的系统分叉都必须出现在 [fork.system],缺键或未知键都会让节点启动失败,因此版本升级与配置补键必须同一次提交:本 PR 已一并更新 chain33.fork.toml、chain33.para.toml、plugin/consensus/qbft/chain33.qbft.toml、plugin/dapp/evm/cmd/ci2/chain33.proxyminer.toml、plugin/dapp/ticket/executor/testdata/chain33.cfg.toml。

测试

用例 覆盖
Test_TicketGenesisReRunRejected_PostFork height 1.7M:先断言框架分叉已生效,再验 Exec 返回 ErrReRunGenesis 且无回执(变异验证:把该分叉配成 -1 时用例失败)
Test_TicketGenesisAllowedAtHeightZero height 0:正常创世路径不受影响
TestGenesisReentryRejectedOnTicketNode(testnode 端到端) 交易走 mempool→出块,回执非 ExecOk;不产生伪造票;受害矿工挖不到(修复前同一用例需 ~96s,现为秒级)

验证矩阵:

场景 结果
本 PR 锁定的 chain33(v1.72.7-0.2026...) go test ./plugin/dapp/ticket/executor/... ./plugin/dapp/ticket/testnode/... 全绿;TestGenesisReentryRejectedOnTicketNode 1.76s 通过(拦截确实来自框架层)
变异:testdata 的 ForkGenesisOnlyAtHeightZero=-1 Test_TicketGenesisReRunRejected_PostFork 失败并提示"本用例依赖框架层分叉 … 已生效"(随后回滚)
取值探测:testdata 与 types.GetDefaultCfgString()(title=local → 全 0) IsFork(任意高度, ForkGenesisOnlyAtHeightZero) 均为 true
5 个 [fork.system] 配置加载 均可正常加载(无 system fork 报错)

原 PoC 用例已翻转为修复后的回归断言。

Ticket.Exec_Genesis 缺少 coins/coinsx/token 都有的 height 校验:任意高度都能重入
GenesisInit,造出 isGenesis=true、3 段 ticketId 的“创世票”——既绕过 pubHash 校验
(3 段 id 的 privHash 恒为空),又绕过成熟期与提现等待。分叉生效后受害矿工会把这种
票当成自己的票挖出来,ExecDepositFrozen 把出块奖励增发到攻击者的 ReturnAddress,
攻击者可立即 Tclose 提走,本金零锁仓。

- 执行器层:高度 != 0 直接返回 ErrReRunGenesis;不依赖 chain33 侧新增的框架分叉,
  在旧版本 chain33 上同样生效
- 回归用例:executor 层断言高度 1.7M 上的 Genesis 被拒绝、高度 0 的正常创世路径
  不受影响(并直接调用执行器方法验证执行器自身的校验);testnode 端到端断言交易
  可以进块但回执非 ExecOk、链上不产生伪造票、受害矿工挖不到伪造票
- 原 PoC 用例改为修复后的回归断言
@vipwzw
vipwzw force-pushed the fix/ticket-genesis-height-zero branch from eb5634e to 1bb82e3 Compare October 15, 2026 11:55
…s fork

接入 chain33 新增的系统分叉 ForkGenesisOnlyAtHeightZero(创世动作 Genesis 只允许在
高度 0 执行,由 DriverBase.checkGenesisHeight 在 Exec 与 ExecLocal/ExecDelLocal 派发
前统一拦截):

- github.com/33cn/chain33 升到 v1.72.1-0.20261016021757-b12c753ff90b:该提交是在当前
  pin 的 v1.72.0 之上只回移本次修复(chain33 分支 fix/genesis-only-height-zero-v1.72.0,
  含 #1407 review 的补钉),避免把 chain33 master 上 30+ 个无关改动一并带入——实测直接
  跳到 master 会让 ci_rgbx 的 TSS/DKG 场景稳定超时。上游修复见 33cn/chain33#1407
- 5 个含真实 [fork.system] 的配置补上 ForkGenesisOnlyAtHeightZero=0。chain33 的
  initForkConfig 要求每个已注册的系统分叉都必须出现在配置里,缺键或未知键都会让节点
  启动失败,因此版本升级与配置补键必须落在同一次提交
- 验证:go test ./plugin/dapp/ticket/... 全绿
@vipwzw
vipwzw force-pushed the fix/ticket-genesis-height-zero branch from 1bb82e3 to 91e5faf Compare October 16, 2026 02:18
@vipwzw
vipwzw force-pushed the fix/ticket-genesis-height-zero branch 2 times, most recently from 936508f to 8c996af Compare October 16, 2026 03:51
king and others added 3 commits October 16, 2026 13:49
依赖从 v1.72.0 回移版换成 v1.72.7-0.20261016053907-a95e7f042582(chain33 分支
fix/dht-keep-established-identity,33cn/chain33#1408):

- v1.72.6 已经包含本 PR 需要的系统分叉 ForkGenesisOnlyAtHeightZero(33cn/chain33#1407),
  但仍带着 v1.72.1 引入的回归:钱包就绪后 dht 用钱包空投地址替换节点身份并 reStart(),
  运行期更换 peer id;
- plugin 的 ci_rgbx 正是被这条路径打断(重启前发现的 TSS peers 与运行身份零交集,
  auto DKG commit 超时),与创世分叉无关(把它配成 -1 仍复现);
- 切到该修复后 ci_rgbx 通过(5m29s,重启前后 peer 名保持不变),因此不再需要
  v1.72.0 回移分支,等 chain33 发布 v1.72.7 后可换成正式版本号
- go test ./plugin/dapp/ticket/... 全绿
…commit

依赖从 a95e7f042 跟到 e1bde75e2(v1.72.7-0.20261016082959-e1bde75e25d0):
#1408 review 补了真 host 身份稳定性回归、收紧了用例(变异矩阵可拦下收窄/移动守卫),
并把钱包导入的 ErrPrivkeyExist 当成功、去掉名字冲突时多余的 1 秒 sleep。

chain33 依赖最终包含:ForkGenesisOnlyAtHeightZero(33cn/chain33#1407) +
dht 身份不再运行期替换(33cn/chain33#1408)。
go test ./plugin/dapp/ticket/... 全绿。
…cate check

按维护者决定:系统分叉 ForkGenesisOnlyAtHeightZero 默认 0(创世即生效,本仓库各配置
均显式补键为 0),同一约束不再在合约里重复实现,避免两份实现各自漂移。

- Exec_Genesis 移除 height != 0 校验,保留注释说明该约束由 chain33 框架层
  (DriverBase.checkGenesisHeight) 提供
- Test_TicketGenesisReRunRejected_PostFork:删去"直接调用执行器方法"的断言,
  改为先 require 断言该分叉在测试高度已生效 —— 依赖由隐式变显式,分叉被配成
  -1(→MaxHeight) 或链接旧 chain33 时用例直接变红
- testnode 端到端与 PoC 头注释同步为"框架层拦截"

验证:
- go test ./plugin/dapp/ticket/executor/... ./plugin/dapp/ticket/testnode/... 全绿
- TestGenesisReentryRejectedOnTicketNode 1.76s 通过,证明拦截确实来自框架层
- 变异:testdata 的分叉值改成 -1 → 用例失败并提示"本用例依赖框架层分叉 ... 已生效",
  随后回滚
- 取值探测:testdata=0;types.GetDefaultCfgString() 的 title=local → setLocalFork 全 0,
  两处 IsFork(任意高度, ForkGenesisOnlyAtHeightZero) 均为 true
@codecov

codecov Bot commented Oct 16, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 23.98%. Comparing base (8abcfd7) to head (c79691c).
⚠️ Report is 17 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master    #1337      +/-   ##
==========================================
+ Coverage   23.86%   23.98%   +0.11%     
==========================================
  Files        1122     1122              
  Lines      223699   223806     +107     
==========================================
+ Hits        53395    53669     +274     
+ Misses     164697   164491     -206     
- Partials     5607     5646      +39     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@33cn
33cn merged commit de9d7b1 into master Oct 16, 2026
27 checks passed
@33cn
33cn deleted the fix/ticket-genesis-height-zero branch October 16, 2026 10:05
@33cn

33cn commented Oct 16, 2026

Copy link
Copy Markdown
Owner

🎉 This PR is included in version 1.76.2 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

@33cn 33cn added the released label Oct 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants