-
以太坊中需要实现从账户地址到账户状态的映射
- 账户地址为160位,表示为40个16进制数
- 状态包含了余额(balance)、交易次数(nonce),合约账户中还包含了code(代码)、存储(stroge)
- 其本质上为key-value键值对,所以直观想法便⽤哈希表实现,若不考虑哈希碰撞,查询效率为常数级别,但采⽤哈希表的话难以提供Merkle Proof
-
能否像BTC中,将哈希表的内容组织为Merkle Tree?
- 当新区块发布时,哈希表内容会改变,每当产⽣新区块(ETH中新区块产⽣时间为17s左右),都要重新组织Merkle Tree,代价大,不现实
- ⽐特币系统中没有账户概念,交易由区块管理,⽽区块包含的交易上限仅为4000个左右,所以其Merkle Tree不是⼤的;⽽ETH中,Merkle Tree则被⽤来组织账户信息,因此会越来越庞⼤
- 并且在实际中,发⽣变化的仅为很少⼀部分数据,每次重新构建Merkle Tree代价很⼤
-
倘若不使⽤哈希表,直接使⽤Merkle Tree,每次修改只需要修改其中⼀部分,是否可⾏?
- 首先,Merkle Tree并未提供⼀个⾼效的查找和更新的⽅案
- 其次,将所有账户构建为⼀个⼤的Merkle Tree,为了保证所有节点的⼀致性和查找速度,必须进⾏排序,因为如果每一次各账户排列的顺序不同,产生的Merkle Tree并不是唯一的
-
那经过排序,使⽤Sorted Merkle Tree是否可⾏?
- 新增账户,由于其地址随机,插⼊Merkle Tree时很可能在Tree中间,因此其必须进⾏重构,所以插入代价仍然太⼤(而BTC系统中,虽然每个节点构建的Merkle Tree不⼀致 (不排序),但只有最终获得记账权的节点的Merkle Tree 才是有效的,所以Merkle Tree唯一)
-
以太坊采⽤的数据结构:
MPT(i.e. Merkle Patricia Trie/Tree)- 从简单数据结构讲起:Trie (字典树、前缀树)
- 特点:
- 理论上哈希会出现碰撞,⽽trie不会发⽣碰撞
- 给定输⼊,⽆论按照什么顺序插⼊,构造的trie都是⼀样的
- 更新操作局部性较好
- 缺点是存储浪费,很多节点只存储⼀个key值(即General这种一脉单传的例子)
- 从简单数据结构讲起:Trie (字典树、前缀树)
-
进⾏了路径压缩的trie:
Patricia Trie:- 特点:
- 如果新插⼊单词,原本压缩的路径可能需要扩展
- 树中插⼊的键值分布较为稀疏的情况下,路径压缩效果较好
- 以太坊使⽤的并⾮简单的PT(Patricia Tree),⽽是Modified MPT(Merkle Patricia Tree),即把指针换成了哈希指针,并作了一些修改
-
以太坊系统中的
Modified MPT,- 将所有账户组织为⼀个经过路径压缩和排序的Merkle Patricia Tree,其根哈希值存储于block header中
- 以太坊中有三棵树,因此在以太坊的block header中存有三个根哈希值
- 简单化,右上⻆4个账户,7Bytes地址,账户状态显示余额
- Extension Node:路径压缩节点
- Branch Node:分⽀节点
- 图中的指针都是哈希指针
- 每次发布新区块,状态树中部分节点状态会改变
- 改变并⾮在原地修改,⽽是新建⼀些分⽀,保留原本状态
- 仅有新发⽣改变的节点才需要修改,其它未修改节点直接指向前⼀个区块中的对应节点
- 以太坊中合约账户的storage也是以MPT的形式存储
- 所以系统中全节点并⾮维护⼀棵MPT,⽽是每次发布新区块都要新建MPT,只不过⼤部分节点共享
-
为何MMPT中需要保留历史状态,而不在原地直接修改?
- 为了便于回滚,如①中产⽣分叉,⽽后上⾯节点胜出,变为②中状态
- 之前接收了下⾯区块的节点,需要将状态进⾏回滚,因此需要维护这些历史记录
- 在BTC中,由于交易类型简单,可以通过反向操作推算出前⾯的状态,不需要维护历史记录
- ETH中存在智能合约 (图灵完备,可实现各种功能),执⾏完智能合约后想要推算出之前的状态,⼏乎不可能。因此要⽀持回滚,必须保存之前的历史状态
-
以太坊区块的数据结构体:
type Header struct { ParentHash // ⽗区块的哈希(前⼀个区块的哈希值) UncleHash // 叔⽗区块的哈希 Coinbase // 矿⼯地址 Root // 状态树根哈希 TxHash // 交易树根哈希 ReceiptHash // 收据树根哈希 Bloom // 布隆过滤器(⽤于查询,和收据树相关) Diffculty // 挖矿难度 Number GasLimit // 该区块中所有交易所能消耗的汽油的上限 GasUsed // 该区块中所有交易所消耗的汽油费总和 Time // 区块⼤致产⽣时间 Extra MixDigest // 和挖矿过程相关 Nonce // 和挖矿过程相关 } type Block struct { header *Header // 指向block header的指针 uncles []*Header // 指向叔⽗区块的指针 transactions Transactions // 交易列表 ... } // 区块在⽹上真正发布时的信息 type extblock struct { Header *Header Txs []*Transaction Uncles []*Header }- 状态树中保存了key-value对,key就是地址,⽽value是账户状态,其通过
RLP(Recursive Length Prefix)编码方式进行序列化后存储
- 状态树中保存了key-value对,key就是地址,⽽value是账户状态,其通过