事故事后分析:Reth主网状态根不匹配

Reth 节点在处理trie更新时出现bug,导致节点计算出错误的状态根。具体原因是,在特定重组期间,Reth产生了不正确的存储trie更新,导致trie表包含旧块的数据,最终导致状态根不匹配。该问题已通过修复is_fork检查逻辑并在版本1.7.0中发布修复。

事故事后分析:Reth 主网状态根不匹配

事故概要

事件的首次警报

缓解沟通

用户影响

补救措施和节点运营商后续步骤

根本原因分析

什么是 0xf?

为什么应该删除 0xf?

为什么是区块 23272427?

解决方案

后续步骤

事故概要

Reth 在处理 trie 更新时的一个错误导致 Reth 节点中的 trie 表包含不正确的信息,从而导致节点在后面的区块中计算出不正确的状态根。

事件的首次警报

9 月 1 日美国东部时间晚上 10:34,我们在 Reth telegram 频道Github 上 收到用户警报,称某些 Reth 节点停滞并显示日志 “mismatched block state root”。

该错误的首次报告:

snf in Reth\ \ Anyone else having issues in mainnet? not sure if reth or lighthouse related. Two different machines in different networks hitting this reth-1 | 2025-09-02T02:30:27.343318Z INFO Received block from consensus engine number=23272427 hash=0xcab98ac6afa8461e2e9f29bd155e1e22d989cdf91f49ab67293af331767dcdaa reth-1 | 2025-09-02T02:30:27.406957Z INFO State root task finished state_root=0xa6408d671ebdeaead407c18e0371c4b59444afcfda8a402b13afd7fbdcc158aa elapsed=32.078834ms reth-1 | 2025-09-02T02:30:27.406987Z WARN State root task returned incorrect state root state_root=0xa6408d671ebdeaead407c18e0371c4b59444afcfda8a402b13afd7fbdcc158aa block_state_root =0xbdc140354b0027f1c111e77376ce993d5c49f3d9f739910ffac114fd65f29caa reth-1 | 2025-09-02T02:30:27.409125Z WARN Failed to compute state root in parallel block=NumHash { number: 23272427, hash: 0xcab98ac6afa8461e2e9f29bd155e1e22d989cdf91f49ab67293af3317 67dcdaa } persisting_kind=NotPersisting reth-1 | 2025-09-02T02:30:27.487307Z WARN…\ \ \ \ https://t.me/paradigm_reth/44348\ \

[\ \ \ \ Reth Stalled on Bad Block - Ethereum Mainnet\ \ \ \ Closed\ \ \ \

18205\

\ •\ \ jamesstanleystewart\ \ •\ \ Closed 11 days ago](https://github.com/paradigmxyz/reth/issues/18205)

用户节点停滞在以下区块上:

区块号:

23272427

区块哈希:

0xcab98ac6afa8461e2e9f29bd155e1e22d989cdf91f49ab67293af331767dcdaa

缓解沟通

9 月 2 日美国东部时间上午 7:47,我们确定了缓解该问题的步骤,并通过 Twitter 和 Telegram(Reth 支持的标准沟通方法)进行了沟通:

Alexey Shekhirin in Reth\ \ Attention people who experienced the state root mismatch, these are the steps to recover your node: 0. Shut down your node 1. reth stage drop --datadir DATADIR --config $CONFIG merkle 2. reth stage unwind --datadir DATADIR --config $CONFIG to-block 23272426 3. reth node --datadir DATADIR --config $CONFIG --debug.tip 0x2eb1fcafd864aafe21f2cb66310a869b8945231330f0da80c9e9b77861b56fca 4. Restart reth with your normal arguments when previous command finished syncing People with full/pruned nodes who have been running into the issue above, should be passing --config $CONFIG when calling the unwind command If you were running with --datadir argument, provide it accordingly.\ \ \ \ https://t.me/paradigm_reth/44536\ \

Georgios Konstantopoulos on Twitter / X\ \ We had a bug in Reth's state root computation today in Ethereum mainnet, which caused multiple nodes to stall.Recover by running the following:```1. reth stage drop --datadir DATADIR merkle2. reth stage unwind --datadir DATADIR to-block 232724263. reth node --datadir…— Georgios Konstantopoulos (@gakonst) September 2, 2025\ \ \ \ https://x.com/gakonst/status/1962888853682798971

用户影响

一些运行主网节点的 Reth 用户受到了此错误的影响。并非所有 Reth 用户都受到了影响。OP Reth 节点和其他网络上的节点未受到影响。如果用户的 Reth 节点受到影响,它们将不会继续运行,直到采取缓解措施。一旦缓解步骤完成,用户节点将再次开始运行并同步。

补救措施和节点运营商后续步骤

我们确定了该错误的根本原因,并在 1.7.0 版本 中发布了修复程序,并提供了修复工具,以确保 Twitter、Telegram 和 GitHub 上任何剩余的用户数据库不会仍然损坏。

🚨 Callout icon

任何尚未升级到 1.7.0 版本的以太坊主网节点运营商都应更新并运行 1.7.0 发行说明中提到的修复脚本。

根本原因分析

状态根不匹配的原因是 trie 节点具有不正确的数据,特别是来自 此帐户 的存储 trie 节点包含来自旧区块的数据,具体来说是

23003311

。这意味着某些存储 tries 已损坏。

发生损坏的原因是 Reth 在某些 reorgs 期间产生了不正确的存储 trie 更新。在此实例中,reorg 发生在区块

23003311

,区块按以下顺序到达:

区块

23003310

区块

23003311 (fork)

区块

23003311 (canonical)

区块

23003312

在区块

23003311 (canonical)

交易 0x8342...4be8 中,以下 trie 节点在数据库中创建:

Plain Text
Copy
StorageTrieUpdates {
is\_deleted: false,
storage\_nodes: {
Nibbles(0xf): BranchNodeCompact {
state\_mask: TrieMask(0000000001001000),
tree\_mask: TrieMask(0000000000000000),
hash\_mask: TrieMask(0000000000001000),
hashes: \[0xc2e8ad8f13ee65d4bb21710522cf87c847f9bb0db11d73e6694c7ebc01c996e8\],
root\_hash: None
}
},
removed\_nodes: {}
}

💡 Callout icon

什么是 0xf

如果你打开 Etherscan 上此交易的 状态更改,你将看到唯一修改的存储槽是

0x4193f419339d8d52872c2a4d6ee5a6eb14c99332daf66ad89d1882bf092892be

,其先前值为

0x0

,这意味着它是刚刚创建的。

0x4193f419339d8d52872c2a4d6ee5a6eb14c99332daf66ad89d1882bf092892be

存储在 trie 中时,它被编码为

0xf388169196abf4cd20a13828879f10b0985d92fb58854196a68dad916132a0e4

Copy
❯ cast keccak 0x4193f419339d8d52872c2a4d6ee5a6eb14c99332daf66ad89d1882bf092892be
0xf388169196abf4cd20a13828879f10b0985d92fb58854196a68dad916132a0e4

第一个 nibble

f

这个哈希路径是我们的分支节点

0xf

已创建。

通常,Reth 中的 trie 更新仅在基于数据库 tip 的直接后代区块计算时才有效,否则将被丢弃。但是,区块

23003311 (canonical)

trie 更新被错误地丢弃,因为 Reth 错误地检查了该区块是否为 fork,即使数据库 tip 位于直接后代处:

\ \ \ \ payload_validator.rs\ \ paradigmxyz/reth\ \ // If the block is a fork, we don't save the trie updates, because they may be incorrect.// Instead, they will be recomputed on persistence.let trie_updates =if ctx.is_fork(){ExecutedTrieUpdates::Missing}else{ExecutedTrieUpdates::Present(Arc::new(trie_output))};\ \ 0acebab\ \ •\ \ Rust

接下来,区块

23003312

到达。由于交易 0xf535...2cd6 执行,路径

0xf

的节点应已删除。

💡 Callout icon

为什么应该删除 0xf

再次查看 Etherscan 上的 状态更改,我们看到与区块

23003311

中创建的相同的存储槽

0x4193f419339d8d52872c2a4d6ee5a6eb14c99332daf66ad89d1882bf092892be

,但现在其值更改回

0x0

。这将导致删除 0xf 处的分支节点,因为分支节点不能只有一个子节点。

缺少来自区块

23003311 (canonical)

trie 更新意味着 Reth 的状态根逻辑无法确定路径

0xf

处的节点已删除,而不仅仅是不存在。这导致状态根逻辑输出此存储 trie 的没有 trie 更新,而不是删除。但由于区块

23003312

不再是 fork 区块,因此 Reth 没有丢弃 trie 更新,并为该区块计算了正确的状态根。未受影响的节点为区块

23003312

生成了这些正确的 trie 更新:

Plain Text
Copy
StorageTrieUpdates {
is\_deleted: false,
storage\_nodes: {},
removed\_nodes: {Nibbles(0xf)}
}

缺少区块

23003311

中的 trie 更新导致 Reth 在持久化到数据库时重新计算它们

\ \ \ \ mod.rs\ \ paradigmxyz/reth\ \ if block.trie.is_present(){continue}debug!(\ target:"engine::tree",\ block =?block.recovered_block().num_hash(),"Calculating trie updates before persisting");\ \ 0acebab\ \ •\ \ Rust

但是区块

23003312

trie 更新没有被丢弃,因此 Reth 只是按原样获取它们并持久化到数据库。这意味着 trie 节点

0xf

已在

23003311

中创建,但未在

23003312

中删除。这是不正确的,因为在区块

23003312

之后,此存储节点不应存在。

当在区块

23272427

中需要此损坏的存储 trie 来进行状态根计算时,受影响的 Reth 节点遇到了状态根不匹配。

💡 Callout icon

为什么是区块 23272427

原始帐户

0x77d34361f991fa724ff1db9b1d760063a16770db

trie 中编码为

0xa86e1856f85373eaf16909d1a6c580f47385e49561d05661462f6c0bebc9ad32

Copy
❯ cast keccak 0x77d34361f991fa724ff1db9b1d760063a16770db
0xa86e1856f85373eaf16909d1a6c580f47385e49561d05661462f6c0bebc9ad32

23272427

修改了一个账户,该账户的前缀与原始账户的编码版本相同,这需要 Reth 查找原始账户的存储根。存储根没有预先计算好存储,因此我们从(损坏的)存储 trie 节点计算它,这导致了不匹配。

解决方案

通过更改 is_fork 检查来解决此问题,而是确定该区块是否是当前数据库 tip 的后代,并确保缺少 trie 更新的区块不会导致后代区块产生不正确的 trie 更新:

[\ \ \ \ refactor(engine): persistence logic\ \ \ \ Merged\ \ \ \

18318\

\ •\ \ shekhirin\ \ •\ \ Merged 14 days ago](https://github.com/paradigmxyz/reth/pull/18318/files)

后续步骤

在修复问题并发布新版本后,Reth 团队立即开始采取措施,以防止将来发生此类事件:

E2E 测试,涵盖所描述的 reorg 场景

[\ \ \ \ chore(trie): add e2e for reorg\ \ \ \ Open\ \ \ \

18293\

\ •\ \ yongkangc\ \ •\ \ Opened 17 days ago](https://github.com/paradigmxyz/reth/pull/18293)

诊断工具,将在我们内部的所有配置(存档/完整,Reth/OP Reth,Mainnet/Testnets/Optimism/Base)的节点群上运行,以尽快检测到类似问题,并减少事件解决所需的时间

[\ \ \ \ feat(trie): Add helper sub-command\ \ \ \ Merged\ \ \ \

18301\

\ •\ \ mediocregopher\ \ •\ \ Merged 14 days ago](https://github.com/paradigmxyz/reth/pull/18301)

关于重构 trie 计算算法和 trie 更新逻辑的内部讨论,使其更加健壮且不易出错。

  • 原文链接: laced-king-de5.notion.si...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
laced-king-de5
laced-king-de5
江湖只有他的大名,没有他的介绍。