实现以太坊的数据结构----状态树

状态树:实现账户地址(addr)到账户状态(state)的映射。

在以太坊中账户地址用160位(bits)表示,即40个16进制的数。

1、为什么不能使用哈希表实现?

用哈希表实现,就是系统中的全节点维护一个哈希表,在不考虑哈希碰撞的情况下,每次有一个新的账户就插入到哈希表中,查询也是常数级的。

但是这存在一个问题。如果要证明账户余额,需要将哈希表中的内容组织成一个Merkle tree,然后算出根哈希值保存在block header中公布出去,只要保证根哈希值是正确的,就能保证底下的数没有被篡改。除此之外,Merkle tree还可以维护全节点之间状态的一致性。如果不发布Merkle tree的根哈希值,每个节点只在本地维护一个数据结构,这样就无法知道自己数据结构的状态与别人数据结构的状态是否一致。

问题是,当有新的区块需要发布时,因为新区块中包含新的交易,所以要执行交易,这样会使哈希表中的内容发生变化。这样的话,在发布下一个区块的时候,要把哈希表中的内容重新组织成一个Merkle tree,这要付出极大的代价。实际上,真正发生变化的账户状态只有一小部分,因为只有区块中关联的账户交易发生了变化。

这和比特币系统中,每增加一个区块构建一个Merkle tree不同。在比特币系统中,每次发布一个新的区块,就把区块中的交易组织成一个Merkle tree,这棵树是不可更改的。下次发布一个新的区块,会构建一个新的Merkle tree。每个区块中的交易不会超过4000个,所以构建Merkle tree的代价是可以接受的。但是,如果使用哈希表的话,是将以太坊中的所有交易组织成一个Merkle tree。这样每发布一个交易,就要遍历所有的账户构建Merkle tree,这种代价太大了。

2、既然不能使用哈希表,那么可以直接使用一个Merkle tree,把所有账户都放进去吗?

这样的话只需要修改Merkle tree中的一小部分,但是Merkle tree没有提供高效的查找和更新的方法。

如果把所有账户都放在一个Merkle tree中,这个树还需要进行排序成sorted Merkle tree,因为如果不排序,就无法证明一个交易是否不在区块中。

那么如果使用sorted Merkle tree,在新增一个账户的时候,由于产生的账户地址是随机的,那么账户有可能插入到叶节点的中间位置,那么后面的数的结构都要改变。这就变成了每新增一个账户,都要重新生成一个 Merkle tree。

3、trie字典树(前缀树)

实现以太坊的数据结构----状态树_第1张图片

优点:

1)分叉数目(braching factor):17个,因为16进制,加一个结束标志位

2)查找效率取决于key的长度

3)不会产生碰撞

4)一些节点按照不同顺序插入,最后得到的结果相同

5)访问一个节点,只需访问其所在分支,具有较好的更新局部性

缺点:存储开销较大

4、Patricia tree(PT)

对trie进行路径压缩。当键值分布比较稀疏的时候,路径压缩效果比较好。

在以太坊中,为了避免碰撞,需要足够长的地址,使得分布足够稀疏。

实现以太坊的数据结构----状态树_第2张图片

5、Merkle Patricia tree(MPT)

与PT不同的是,MPT将PT中的普通指针换成了哈希指针。

所有的账户组织成一个PT,用路径压缩提高效率,然后把普通指针换成哈希指针,此时可以计算出一个根哈希值,写入block header中。比特币中只有一个哈希值,即所有交易组成的Merkle tree的根哈希值。以太坊中有3个。

以太坊中根哈希值的作用:

1)防止篡改

2)证明余额:账户所在的分支自底向上作为Merkle Proof发给轻节点,轻节点就可以验证账户有多少钱。

3)证明某个账户不存在(证明MPT中某个键值不存在):将分支作为Merkle Proof发给轻节点,证明该键值是否存在

以太坊的结构是一个大的MPT包含许多小的MPT,每一个合约账户的存储就是一个小的MPT。所以系统每个全节点维护的不是一个MPT,而是每次出现一个区块都要新建一个MPT。

不会原地直接修改,而要保留历史状态。

原因是:系统中有时会发生分叉,其中一条链合法,其余的要回滚(退回到上一个区块的状态)。保留历史记录是为了实现回滚。以太坊中有智能合约,如果不保存历史状态,那么智能合约执行完后就不可能推算出前面的状态。

6、总结

MPT中保存的是(key,value),key是地址,以上介绍的是地址的存储。value是状态,value的存储需要先进行RLP(Recursive Length Prefix)序列化。

 

你可能感兴趣的:(区块链)