以太坊细节[3]:StateRoot为什么不能取代ReceiptsRoot
2021-05-29

背景

在各种区块链项目中(例如以太坊),节点往往需要维护三个 Trie Root
https://github.com/ethereum/go-ethereum:44a3b8c04cf18dc0a796f96d5972beb0e3cbe79b:core/types/block.go,L69-86:
block的header数据结构
stateRoot TransactionsRoot ReceiptsRoot
问题在于ReceiptsRoot的必要性。我们是否能省略ReceiptsRoot,仅检查stateRoot是否正确来对block的transaction是否执行正确。

Root存储的内容

  • TransactionsRoot
    TransactionsRoot 存储的是 block中transactions组成的Trie的Root
    TransactionsRoot的作用最简单,用于接收者检查block中transactions的完整性。
  • ReceiptsRoot
    ReceiptsRoot存储的是交易回执内容
    receipts交易回执
    receipt本身记录交易是否在block被成功执行。
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
  • StateRoot
    StateRoot 记录的是 transaction执行结束之后的世界状态。
	if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
		return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
	}

但是这个StateRoot 和 ReceiptRoot的作用仿佛就相似了。
都是检查状态改变结果是否正确。

ReceiptsRoot与StateRoot 作用的差异

作用上很相似,都是检查transaction执行结果是否正确。
为啥要实现两边呢,我们是否可以通过比较StateRoot和header中一致,来保障transaction的内容正确和执行顺序正确?

仔细看一下Receipts的内容

ReceiptsRoot

在这里插入图片描述
我们发现除了result.Failed()来记录transaction执行是否成功外,还有一个receipt.GasUsed域值得关注。
这个域表示当前transaction执行后,该block一共消耗了多少Gas。
所以明显receiptsRoot还有transactions执行顺序的一致性。

但是顺序一致,依旧可以通过stateRoot的一致来检查。

StateRoot

state
我们看stateRoot的内容,有Account的内容。StateRoot更多是世界环境的Root。所以他更多是表达transaction执行后的世界。

另一种观点

StateRoot作为世界状态的Hash
上链后 所有节点对这个世界状态达成共识。
这便可以让节点们对一些 无法上链的本地信息,达成一致。

例如
节点中存储的账户信息,某个账户有多少钱。
虽然可以正常执行transaction ,并且receipt也是一致的。
但两个节点的账户信息如果不一致 stateroot也不一致。

所以StateRoot的本质是对一些无法上链的账户状态的一致性保障。

总结

receiptRoot 和 StateRoot的作用都是检查transaction执行是否正确可靠。
不同点在于receiptRoot只是表示transaction执行成功失败还有执行顺序。
StateRoot的作用是记录 transaction执行结束之后的世界状态。

注意,以太坊允许uncle block这种孤块的存在。
当一个BP节点本身处在一条非主流分叉上时,它所产生的StateRoot与接收者执行后的StateRoot不同
如果单纯比较StateRoot不同而盲目的认为BP节点是evil节点,就有些不公正。
我们可以通过比较receiptsRoot来对transaction的执行正确性达成一致,不必在乎执行的环境是否相同。
这样以太坊就对uncle block更加友善了。