状态根证明的是执行完这个区块里所有交易之后,整个以太坊世界的账户状态变成了什么样;交易根证明的是这个区块里到底打包了哪些交易、顺序如何。两者都是区块头里的 32 字节哈希,但承诺的对象完全不同:一个指向"结果",一个指向"输入"。
状态根:给"执行后的世界"拍一张快照
以太坊本质上是一台状态机。每个账户的余额、nonce、合约代码和合约存储,共同构成了"世界状态"。状态根就是这个世界状态在一棵默克尔帕特里夏树(MPT)上的根哈希。
关键在于时点。以太坊执行规范对 state_root 的定义是:执行完这个区块内所有交易之后,状态树的根哈希。也就是说,状态根承诺的是"这些交易做完之后,全网账户变成了什么样"。如果你的账户余额因为区块里的某笔交易发生了变化,这个变化会反映在状态根所代表的那棵树的对应叶子里。
状态根有一个容易被误解的地方:它描述的是累计状态,不是本区块的增量。同一个状态根可能在连续多个区块里重复出现(如果这几个区块没有改变任何账户状态)。它也不告诉你状态是怎么变成这样的,只告诉你"现在是这样"。
交易根:给"这个区块装了哪些交易"列一份清单
交易根指向另一棵 MPT,这棵树的叶子是本区块包含的交易,键是交易在区块内的序号,值是交易本身的编码。
它回答的问题很直接:这个区块里到底有哪些交易,按什么顺序排列。如果你想证明某笔交易确实被打包进了某个区块,不需要下载整个区块的所有交易,只需要沿着交易树从根到对应叶子的默克尔路径,提供路径上的兄弟哈希,就能验证。验证成本与区块大小成对数关系,不随链的长度增长。
交易根承诺的是"输入",不是"结果"。它不关心这些交易执行后账户变成了什么样,那是状态根和收据根的事。
一个区块头里其实有三棵树,别把收据根漏掉
除了状态根和交易根,以太坊区块头里还有第三个根:收据根(receipts_root)。它指向一棵由交易收据构成的树,收据记录了每笔交易执行后的结果——成功还是失败、消耗了多少 Gas、产生了哪些日志。
| 承诺什么 | 回答的问题 | |
|---|---|---|
| 交易根 | 本区块打包的交易列表 | "这个块里有什么交易?" |
| 收据根 | 每笔交易执行后的收据 | "每笔交易执行结果如何?" |
| 状态根 | 执行完所有交易后的全局状态 | "做完这些之后,世界变成了什么样?" |
交易根和收据根都是每块独立的,只描述当前区块;状态根则是跨区块持续的,它承载的是从创世区块一路累积到现在的全局状态。
为什么区分这两个根对你有用
如果你在区块浏览器上查一笔交易,看到它被打包进了某个区块,你其实在依赖交易根来确认"它确实在这个块里"。交易根让你可以验证交易被包含,而不需要信任浏览器告诉你的结果。
但交易被打包不等于它执行成功了。一笔失败的交易同样会被打包,同样计入交易根。要知道它执行后到底发生了什么——转账是否到账、合约调用是否生效——你需要看收据根对应的收据,以及最终状态根里你的账户余额有没有变。交易根证明"它被处理了",状态根证明"处理完之后你的余额对了"。两者缺一不可,但各自只回答自己那一半问题。



