以太坊状态树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返回值来进行状态更新。