苏州网站建设制作公司网站建设预算策划
参考文献:
- Merkle Tree & Hash-based SIG
- Merkle Tree 实现
- 以太坊 Merkle Patricia Tree 全解析
- Merkle Patricia Tree (梅克尔帕特里夏树) 详解
文章目录
- MPT 结构
- 基本操作
- Get
- Insert
- Delete
- 其他
MPT 结构
Merkle Patricia Tree 是以太坊的关键算法之一。它是一种 16 进制的前缀树(key 编码在 Path 中),具有三种节点:
- Branch,
- 具有至少 2 个孩子,至多 16 个孩子
- 具有以该路径结束(key)的对应 value
- 附带字段
nodeFlag,包括hash, 本节点的 Hash 值gen, 诞生标记,最近一次被写入/修改的时间dirty,节点是否被修改,若是则hash置空以更新
- Leaf,
- 和 Branch 类似,除了把
children换成key - 这个
key用于处理一段后缀不存在相同前缀其他节点的情况
- 和 Branch 类似,除了把
- Extension,
- 与 Leaf 相同,除了把
value替换为ptr/idx - 这个
ptr/idx用于持久化存储 MPT 时的检索
- 与 Leaf 相同,除了把
以太坊中 Leaf 和 Extension 共享定义,为了在持久化存储中做区分,需要将 Hex 编码替换为 Hex-Prefix 编码。
- 如果 Extension 的
key为偶数长度 Hex 编码,那么在最前面补0x00 - 如果 Extension 的
key为奇数长度 Hex 编码,那么在最前面补0x1- - 如果 Leaf 的
key为偶数长度 Hex 编码,那么在最前面补0x20 - 如果 Leaf 的
key为奇数长度 Hex 编码,那么在最前面补0x3- - 最终的
prefix || key是偶数长度的 Hex 编码
MPT 的整体结构如下:
- 树根是一个 Extension 节点,它的
ptr/idx是其 Branch(或者 Leaf,如果是树桩)子节点的 Hash 值 - Branch 节点的
children存储 16 个孩子各自的 Hash 值 - Branch 节点的
children可以是 Leaf 或者 Extension 节点 - Extension 节点的
ptr/idx是其子节点(branch 或者 leaf)的 Hash 值 - 如果该路径是多个
key的前缀,那么创建 Extersion 以及 Branch - 如果该路径只是单个
key的前缀,那么创建 Leaf
换句话说,extension 的作用是存储 branch 或 leaf 的 Hash 值(也是索引),branch 的作用是存储最少 2 个、最多 16 个子树的 Hash 值(也是索引),通常 leaf 的父节点是 branch(除非只有一个 extension 作为树桩的根)

基本操作
Get
假设需要查找 inp_key 的节点,
- 当前是 leaf 节点,判断它的
path || key是否等于inp_key,输出它的value或者false - 当前是 extension 节点,判断它的
path || key是否是inp_key的前缀,决定是否递归检索ptr/idx指向的子节点,或者输出false - 当前是 branch 节点,如果
path等于inp_key,那么输出它的value - 当前是 branch 节点,如果
path是inp_key的前缀,那么递归检索下一个 hex 所对应的children节点(leaf, extension, empty)
Insert
假设需要插入 inp_key 的节点,
- 利用 Get 方法,获取
inp_key所能匹配的最长前缀节点 - 如果它是 leaf 节点,判断
path || key是否等于inp_key,- 相等,那么更新它的
value - 否则,新建一个 extension 节点和 branch 节点,前者的
key是最长的公共前缀,后者的两个children分别指向原始的 leaf(或者填入value如果是前缀的话)和一个新建的 leaf
- 相等,那么更新它的
- 如果它是 extension 节点,过程和 leaf 的相似
- 如果它是 branch 节点,
- 如果
path等于inp_key,那么更新它的value - 如果
path是inp_key的前缀,那么将对应的children指向一个新建的 leaf
- 如果
- 最后,从下向上重建 Merkle Tree
Delete
假设需要删除 inp_key 的(单个)节点,
- 利用 Get 方法,获取
inp_key所能匹配的最长前缀节点 - 如果它是 leaf 节点,判断
path || key是否等于inp_key,- 相等,那么删除它,并判断它的 branch 父节点是否仍具有至少 2 个孩子(包括它的
value视为一个孩子),作相应的合并调整 - 否则,不存在
inp_key节点,返回false
- 相等,那么删除它,并判断它的 branch 父节点是否仍具有至少 2 个孩子(包括它的
- 如果它是 extension 节点,那么
- 如果
path等于inp_key,这是一种非法操作(删除整个子树?) - 如果
path || key不等于inp_key,那么返回false - 不会发生
path || key是inp_key前缀的情况,这一定会匹配到 extension 的孩子
- 如果
- 如果它是 branch 节点,
- 如果
path等于inp_key,那么删除value,但是不要递归删除它的孩子(这个 branch 存储的完整 key 是其他 key 的前缀) - 否则,这意味着不存在
inp_key节点(不然最长前缀节点不会是 branch 而应该是 extension 或 leaf),返回false
- 如果
- 最后,从下向上重建 Merkle Tree
其他
如果要求 {key: value} 具有相同长度的键,那么 branch 节点可以移除其 value 域(此时不存在某路径是其他路径的前缀),相应的 Get、Insert、Delete 可以被适当简化。
每个节点都是变长的连续数据块(均包含 key 和 value 域之一),节点的引用是 Hash 值。在内存中,可以利用 std::map 来管理;在磁盘中,如果树很大(无法一次性加载到内存)则使用数据库来管理。
