主页 > imtoken钱包官方下载最新版 > 以太坊MPT树节点分析

以太坊MPT树节点分析

MPT树节点类型

参考:

叶节点:(valueNode)

  valueNode []byte

没有子节点,包括一个value,表示为[key,value]的键值对以太坊节点类型,其中key是key的特殊十六进制编码(HP编码)十六进制前缀编码

它实际上是字节数组 []byte 的别名,没有孩子。 在使用中,valueNode是携带数据的RLP哈希值,长度为32字节,将数据的RLP编码值作为valueNode的匹配项存储在数据库中。

扩展节点:(shortNode)

shortNode struct {
        Key   []byte
        Val   node
        flags nodeFlag
}

子节点只有一个,也是[key, value]的键值对以太坊节点类型,但是这里的value是其他节点的hash值,这个hash可以用来查询数据库中的节点。 也就是说,通过hash链接到其他节点。

分支节点(fullNode):

fullNode struct {
        Children [17]node // Actual trie node data to encode/decode (needs custom encoder)
        flags    nodeFlag

以太坊为什么叫以太坊_以太坊节点类型_以太坊联盟和以太坊的关系

}

包含 16 个分支和 1 个值。 因为MPT树中的key被编码成特殊的十六进制表示,加上最后一个值,分支节点是一个长度为17的列表,前16个元素对应key中16个可能的十六进制字符,如果有终止于该分支节点的[key,value]对,最后一个元素代表一个值,即分支节点可以是搜索路径的末端,也可以是路径的中间节点。

新增叶子节点和扩展节点! (与前缀树相比)

这三种类型涵盖了普通 Trie(可能是 PatriciaTrie)的所有节点要求。 当任何[k,v]类型的数据被插入到一个MPT中时,它会以k字符串为路径沿着根向下延伸,在本次插入结束时首先会成为一个valueNode,k会从顶点开始root to the 节点终止的关键路径形式存在。 但是随着其他节点的不断插入和删除,根据MPT结构的要求,原来的节点可能会变成其他节点实现类型,同时MPT会不断变化或合并新的(parent)节点。

哈希节点:

hashNode  []byte

hashNode和valueNode一样,也是字符数组[]byte的别名,也是存储32byte的hash值,没有子节点。 不同的是hashNode是fullNode或者shortNode对象的RLP hash值,所以在用法上和valueNode有很大区别。

HashNode一般放在nodeFlag结构体中,由fullNode和shortNode间接持有。

// nodeFlag contains caching-related metadata about a node.
type nodeFlag struct {
    hash  hashNode // cached hash of the node (may be nil)
    gen   uint16   // cache generation counter
    dirty bool     // whether the node has changes that must be written to the database
}

以太坊联盟和以太坊的关系_以太坊节点类型_以太坊为什么叫以太坊

一旦fullNode或shortNode的成员变量(包括子结构)发生变化,它们的hashNode就必须更新。 因此在trie.Trie结构体的insert()、delete()等函数的实现中,可以看到除了新建的fullNode和shortNode之外,子结构发生变化的fullNode和shortNode的nodeFlag成员也会也被重置。 hashNode 将被清除。 下次调用trie.Hash()时,自下而上遍历整个MPT时,所有清空的hashNode会被重新赋值。 这样在trie.Hash()结束后,我们就可以得到根节点root的一个hashNode,也就是此时MPT结构的hash值。

Header中的成员变量Root、TxHash、ReceiptHash的生成就来源于此。 显然,hashNode体现了MerkleTree的特点:每个父节点的hash值来自于所有子节点的hash值的组合,一个顶点的hash值可以代表一整个树结构。 hashNode加上前面的fullNode、shortNode、valueNode构成了一个完整的Merkle-PatriciaTrie结构。

树解析

用于添加、删除、检查和更改节点的函数都在trie.go 中定义。 在MPT的查找、插入、删除过程中,如果遍历遇到一个hashNode,首先需要从数据库中读取hash值为k的匹配v,然后解码还原v为fullNode或shortNode。 这个过程在代码中叫做resolve。

func (t *Trie) resolve(n node, prefix []byte) (node, error) {
    if n, ok := n.(hashNode); ok {
        return t.resolveHash(n, prefix)
    }
    return n, nil
}
func (t *Trie) resolveHash(n hashNode, prefix []byte) (node, error) {
    cacheMissCounter.Inc(1)
    hash := common.BytesToHash(n)
    if node := t.db.node(hash, t.cachegen); node != nil {
        return node, nil

以太坊节点类型_以太坊联盟和以太坊的关系_以太坊为什么叫以太坊

} return nil, &MissingNodeError{NodeHash: hash, Path: prefix} } // node retrieves a cached trie node from memory, or returns nil if none can be // found in the memory cache. func (db *Database) node(hash common.Hash, cachegen uint16) node { // Retrieve the node from cache if available db.lock.RLock() node := db.nodes[hash] db.lock.RUnlock() if node != nil { return node.obj(hash, cachegen) } // Content unavailable in memory, attempt to retrieve from disk enc, err := db.diskdb.Get(hash[:])

以太坊联盟和以太坊的关系_以太坊为什么叫以太坊_以太坊节点类型

if err != nil || enc == nil { return nil } return mustDecodeNode(hash[:], enc, cachegen) } func mustDecodeNode(hash, buf []byte, cachegen uint16) node { n, err := decodeNode(hash, buf, cachegen) if err != nil { panic(fmt.Sprintf("node %x: %v", hash, err)) } return n } // decodeNode parses the RLP encoding of a trie node. func decodeNode(hash, buf []byte, cachegen uint16) (node, error) { if len(buf) == 0 {

以太坊为什么叫以太坊_以太坊节点类型_以太坊联盟和以太坊的关系

return nil, io.ErrUnexpectedEOF } elems, _, err := rlp.SplitList(buf) if err != nil { return nil, fmt.Errorf("decode error: %v", err) } switch c, _ := rlp.CountValues(elems); c { case 2: n, err := decodeShort(hash, elems, cachegen) return n, wrapError(err, "short") case 17: n, err := decodeFull(hash, elems, cachegen) return n, wrapError(err, "full") default: return nil, fmt.Errorf("invalid number of list elements: %v", c) } }

最后根据SplitList解出的节点个数判断decodeNode。 如果是2个,就是shortNode,如果是17个,就是fullnode。

如果一个fullNode本来只有两个子节点,现在要删除其中一个子节点,这个fullNode会退化成一个shortNode,如果剩下的子节点是shortNode,可以重新合并。