ETH-状态树-16

485 阅读5分钟
  • 以太坊中需要实现从账户地址到账户状态的映射

    • 账户地址为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.png
      • 特点:
        • 理论上哈希会出现碰撞,⽽trie不会发⽣碰撞
        • 给定输⼊,⽆论按照什么顺序插⼊,构造的trie都是⼀样的
        • 更新操作局部性较好
        • 缺点是存储浪费,很多节点只存储⼀个key值(即General这种一脉单传的例子)
  • 进⾏了路径压缩的trie:Patricia Trie

    • Patricia trie.png
    • 特点:
      • 如果新插⼊单词,原本压缩的路径可能需要扩展
      • 树中插⼊的键值分布较为稀疏的情况下,路径压缩效果较好
      • 以太坊使⽤的并⾮简单的PT(Patricia Tree),⽽是Modified MPT(Merkle Patricia Tree),即把指针换成了哈希指针,并作了一些修改
  • 以太坊系统中的Modified MPT

    • 将所有账户组织为⼀个经过路径压缩和排序的Merkle Patricia Tree,其根哈希值存储于block header中
    • 以太坊中有三棵树,因此在以太坊的block header中存有三个根哈希值
      • Modified MPT.png
      • 简单化,右上⻆4个账户,7Bytes地址,账户状态显示余额
      • Extension Node:路径压缩节点
      • Branch Node:分⽀节点
      • 图中的指针都是哈希指针
    • 每次发布新区块,状态树中部分节点状态会改变
      • 改变并⾮在原地修改,⽽是新建⼀些分⽀,保留原本状态
      • 仅有新发⽣改变的节点才需要修改,其它未修改节点直接指向前⼀个区块中的对应节点
      • adjacent blocks.png
      • 以太坊中合约账户的storage也是以MPT的形式存储
      • 所以系统中全节点并⾮维护⼀棵MPT,⽽是每次发布新区块都要新建MPT,只不过⼤部分节点共享
  • 为何MMPT中需要保留历史状态,而不在原地直接修改?

    • rollback.png
    • 为了便于回滚,如①中产⽣分叉,⽽后上⾯节点胜出,变为②中状态
    • 之前接收了下⾯区块的节点,需要将状态进⾏回滚,因此需要维护这些历史记录
      • 在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)编码方式进行序列化后存储