以太坊状态树(踩坑)

以太坊状态树MPT使用注意

最近在区块链分片实验过程中使用以太坊状态树结构(“github.com/ethereum/go-ethereum/trie”)时出现了bug,第一次建树如下:

st, err := trie.New(trie.TrieID(common.BytesToHash(bcft.CurrentBlock.Header.StateRoot)), bcft.triedb)
rt, ns := st.Commit(false)

再次试图新建树时报错了:“MissingNodeError”,报错位置如下

st, err := trie.New(trie.TrieID(common.BytesToHash(bcft.CurrentBlock.Header.StateRoot)), bcft.triedb)

New(id *ID, db NodeReader) (*Trie, error) 方法的注释是这样的:

// New creates the trie instance with provided trie id and the read-only
// database. The state specified by trie id must be available, otherwise
// an error will be returned. The trie root specified by trie id can be
// zero hash or the sha3 hash of an empty string, then trie is initially
// empty, otherwise, the root node must be present in database or returns
// a MissingNodeError if not.

其中trie.new()的第一个参数ID,在注释中表明该ID必须已经存在:“the root node must be present in database”,MPT每次使用的ID应该是和最新区块的哈希相关 ,但两次调用都是同一个区块,使用的ID值与第一次应该是一样的,为什么会报错呢?

后面看到Trie的commit操作,看了一下函数源码我就明白了

// Commit collects all dirty nodes in the trie and replaces them with the
// corresponding node hash. All collected nodes (including dirty leaves if
// collectLeaf is true) will be encapsulated into a nodeset for return.
// The returned nodeset can be nil if the trie is clean (nothing to commit).
// Once the trie is committed, it's not usable anymore. A new trie must
// be created with new root and updated trie database for following usage
func (t *Trie) Commit(collectLeaf bool) (common.Hash, *NodeSet) {
	defer t.tracer.reset()

	nodes := NewNodeSet(t.owner, t.tracer.accessList)
	t.tracer.markDeletions(nodes)

	// Trie is empty and can be classified into two types of situations:
	// - The trie was empty and no update happens
	// - The trie was non-empty and all nodes are dropped
	if t.root == nil {
		return types.EmptyRootHash, nodes
	}
	// Derive the hash for all dirty nodes first. We hold the assumption
	// in the following procedure that all nodes are hashed.
	rootHash := t.Hash()

	// Do a quick check if we really need to commit. This can happen e.g.
	// if we load a trie for reading storage values, but don't write to it.
	if hashedNode, dirty := t.root.cache(); !dirty {
		// Replace the root node with the origin hash in order to
		// ensure all resolved nodes are dropped after the commit.
		t.root = hashedNode
		return rootHash, nil
	}
	t.root = newCommitter(nodes, collectLeaf).Commit(t.root)
	return rootHash, nodes
}

" Once the trie is committed, it’s not usable anymore. A new trie must be created with new root and updated trie database for following usage",st.Commit(false)语句更新树后,原ID值就无法使用了,Commit函数会返回一个rootHash值用于树的下一次更新,因此,正确的语句应该是这样的

rt, ns := st.Commit(false)
st, err := trie.New(trie.TrieID(rt), bcft.triedb)

总结

trie.Trie.new()方法需要一个trieID参数,trieID必须在MPT树中存在,否则会报MissingNodeError错误。在trie.Trie.commit()操作后,该次trieID丢弃,MPT树将不能再通过该trieID进行更新操作,否则会报错。而trie.Trie.commit()会返回一个roothash值,这个值将用来生成下次创建树所使用的trieID。
因此,在commit之后,用commit返回值来进行状态更新。

  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值