以太坊 Block 、StateDB 与 Trie 的关系

前言

不要指望看完本文就能了解全部细节,本文的目的就是带你快速了解相关概念,因为直接深入代码会越看越迷惑,其他文章很少提及本文所述内容,不知道是不是他们认为这些基础知识太简单了

StateDB 用法与结构

以太坊是基于账户体系实现的,块通过 parentHash 链在一起,每个块都包含若干交易,每个交易都包含账户 from 和 to(部署合约时除外),全部的账户凑在一起就是组成了 StateDB,每个块的 StateDB 都用一颗叫做 Trie 的树来组织账户信息,具体结构如下图:

BlockChain

保存账户信息的 StateDB 通常会存储在磁盘上,通过 Block.StateRoot 来进行加载,StateRoot 是树根,也是 leveldb 中的一个 key, 这个根只对应当前块的交易相关的账户信息,value 是这棵树的全部叶子节点,加载的时候会用叶子节点来构建下图中的树型结构

StateDB

智能合约的地址也被当作账户管理,当 Account 为一个智能合约时,那么这个 stateObject 也会包含一颗树,用来保存智能合约的最新状态信息,这些信息是每次执行 evm 中 SSTORE 这个指令时的输入信息,key 是合约的变量名,value 是最新值,这棵树的加载过程与上图中的过程完全一致

stateObject

StateDB 的工作方式

用最少的代码和最简单的例子说明 StateDB 的主要工作原理与过程

  • StateDB 的结构定义
type StateDB struct {
    db   Database
    trie Trie
    stateObjects      map[common.Address]*stateObject
    stateObjectsDirty map[common.Address]struct{}
    dbErr error
    refund uint64
    thash, bhash common.Hash
    txIndex      int
    logs         map[common.Hash][]*types.Log
    logSize      uint
    preimages map[common.Hash][]byte
    journal        *journal
    // 由 snapshot id 和 journal 长度组成,用于回滚
    validRevisions []revision
    // 下一个可用的 snapshot id
    nextRevisionId int
    lock sync.Mutex
}

*StateDB 的关键方法

func (self *StateDB) AddBalance(addr common.Address, amount *big.Int) 
func (s *StateDB) Commit(deleteEmptyObjects bool) (root common.Hash, err error) 
func (s *StateDB) Finalise(deleteEmptyObjects bool) 

通过上面的三个方法可以演示整个 statedb 的操作流程
当执行 AddBalance 方法会设置 addrstateObject.Account.Balance += amount ,这时 addr 对应的 stateObject 会被放在 StateDB.stateObjects 中缓存起来,
当执行 Commit 方法时,会将 StateDB.stateObjects 中的数据构建成一颗默克尔树存放在 StateDB.trie 中,修改数据时会产生 journal 日志,为了方便出错时的回滚,
当执行 Finalise 方法时会删除 journal 刷新 stateObjecttrie.root (如果 stateObject 存在 trie)

执行到此时树的结构已经确定,最终的树根也已经确定,但是在以太坊中数据此时还没有进入数据库
为了提高效率StateDB对数据做了缓存处理,大部分时间都是放在内存中的,StateDB.db 是 state.Database 接口的实现,定义如下:

// Database wraps access to tries and contract code.
type Database interface {
    // blockchain 的 树,root == block.stateRoot
    OpenTrie(root common.Hash) (Trie, error)
    // account 的 树,addrHash == account.address
    OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
    ......
    // TrieDB retrieves the low level trie database used for data storage.
    TrieDB() *trie.Database
}

需要使用 TrieDB 这个方法返回的 trie.Database 对象来最终完成 trei 写入 leveldb 的操作,这个方法在 WriteBlockWithState 中会被有条件调用,此处不再深入,谨以此文说明 Block、 StateDB、 Trie 之间的关系

statedb 存储结构

去数据库里读一下状态信息,再去迭代一下 trie ,即可了解整个 statedb 的存储结构,
触发 state 存储只有 3 个条件

  1. preimages 到 4MB
  2. stateobject 到 20MB
  3. Stop node 时

比如 100块时保存过 State,其中包含了 [A,B,C] 灿哥账户,到了 150 块时又要保存 State 这是增加了 [D,E] 两个账户的信息,此时 150 保存的就是 [A,B,C,D,E] ,如此看来整个 statedb 的不定期存储,而且是全量备份数据;

TODO : 是不是当快速同步时,下载到目标块以后,开始下载状态数据,就只下载目标块对应的状态即可?这个还要继续阅读有关同步部分的代码,才能确定.

你可能感兴趣的:(以太坊 Block 、StateDB 与 Trie 的关系)