本文整理分析以太坊网络中的区块、交易以及合约数据是如何存储的。
区块结构
区块由两部分组成,分别是区块头(header)和区块体(body)两部分,详细结构图如下。
区块头(header)
区块头存储了区块的元信息,用来对区块内容进行一些标识,校验,说明等。区块头里字段分为两部分区块头和区块体。
涉及字段
ParentHash: 父区块的哈希值。
Root:世界状态的哈希,stateDB的RLP编码后的哈希值。
TxHash(transaction root hash):交易字典树的根哈希,由本区块所有交易的交易哈希算出。
ReceptHash:收据树的哈希。
Time:区块产生出来的Unix时间戳。
Number:区块号。
Bloom:布隆过滤器,快速定位日志是否在这个区块中。
还有一些公链特性的字段:
Coinbase:挖出这个块的矿工地址,因为挖出块所奖励的ETH就会发放到这个地址。
Difficulty:当前工作量证明(Pow)算法的复杂度。
GasLimit: 每个区块Gas的消耗上线。
GasUsed:当前区块所有交易使用的Gas之和。
MixDigest: 挖矿得到的Pow算法证明的摘要,也就是挖矿的工作量证明。
nonce:挖矿找到的满足条件的值。
Uncle:叔块是和以太坊的共识算法相关。
状态树、交易树、收据树
区块头中包含三个树的树根:
StateRoot:状态树的根哈希值
TransactionsRoot:交易树的根哈希值
ReceiptsRoot:收据树的根哈希值
区块体(Body)
区块体包括这个区块打包的所有交易
区块存储
区块头存储
以太坊通过如下方式将区块头转换成键值对存储在LevelDB中;
headerPrefix + num + hash -> rlp(header)
区块体存储
bodyPrefix + num + hash -> rlp(block)
区块与各“树”之间的关系
存储结构如下图所,区块中只保存交易树、状态树和收据树的根节点哈希值。
- 交易树中是永久数据,一旦一个交易被确认,它就将永久地被记录在交易树结构中,并且无法篡改。
- 状态树中则是以太坊中另一种存储方式——暂存数据,如以太坊账户地址的余额存储在状态树中,并且每当接收到和该账户有关的交易时,该余额都会改变。
- 存储树(所有智能合约数据存储的位置)的根节点哈希实际上指向了状态树,从而间接指向了区块链。
以太坊区块链是由创世区块开始的。从这个起点开始(创世状态在0区块高度),诸如交易、合约以及挖矿的活动会持续不断地改变以太坊区块链的状态。在以太坊中,账户余额(存储在状态树中)随每一次交易(存储在交易树中)进行改变。
MPT树
MPT(Merkle Patricia Trie),是一种用hash索引数据的前缀树。通过key去查询value,就是用key在MPT树上进行索引,在经过多个中间节点后,最终到达存储数据的叶子节点。如图是全局状态树,4个叶子节点(key/value值)通过如下组织形式,构成状态树。
状态树、交易树、收据树以及存储树都是MPT树。
状态树(State Trie)
以太坊中有且只有一个全局状态树,这个全局状态树在不断地更新着,这个状态前缀树包含了以太坊网络中每一个账户的一组键值对。如下图所示, State Trie中的“键” 是一个 160 位的标识符(以太坊账户的地址),而“值” 是以RLP方式编码的nonce 值、余额、存储树根节点哈希以及代码哈希。
存储树(Storage Trie)
存储树是智能合约数据存储的位置。每一个以太坊账户都有自己的存储树。在全局状态树中保存着存储树根节点的 256 位哈希 storageRoot 值。
这里需要说明的是,智能合约的数据并不存在于区块中,而是存在于区块链的状态数据库(存储树)中,状态数据库/状态树中包含了所有账户的存储、余额、代码及存储树的树根。状态数据库可以保证区块链系统的持续的灵活性。但是,区块数据依然存在,区块中包含的是历史交易信息。
智能合约编译时为其中的变量(每一项数据)分配了地址,这些地址可以被转换成状态数据库中的一个key,根据key可以从状态数据库中取出对应的value,value即为合约的数据。key和value的大小都是32字节。
合约中每个变量占有一个slot(存储槽),详细可点击这里。
32字节提供的key的数量多达2^256个存储位置,每个位置都可以存储32字节的数据,所以一般认为智能合约的存储空间是无限大的。
交易树(Transaction Trie)
交易树是一个MPT树,每一个以太坊区块都有着自己的独立的交易树。一个区块往往包括多笔交易,而交易的顺序当然由打包交易的矿工来决定。在交易树中找到一笔交易的路径是通过(RLP 编码方法)检索交易在区块中的索引来得到的。已经被挖矿验证过的区块将永远不会再更新,所以区块中的交易顺序也将固定下来。这就意味着一旦你从区块的交易前缀树中定位到了某一笔交易,你日后就可以通过相同的路径找回它。
收据树(Receiptes Trie)
每个区块都有一棵独立的收据树根 (receiptsRoot)其结构与交易树相同。收据树中包含了执行交易期间产生的相应的收据,如果一笔交易是一次智能合约的执行,则在以太坊执虚拟机执行的过程中会产生程序员自定义的日志,日志是智能合约自定义的格式。
收据树中的一个节点就是一次交易的收据,收据 (transaction receipt)是一个键值对映射,它的键是交易的编号,它的值是RLP编码过的四个域:交易后的状态、交易实际花费的交易费、交易产生的日志集合、日志的索引结构组成。在实际应用中,在本区块内的数笔交易都顺利执行完毕后,也产生了数笔相对应的交易收据,由 MPT 树组织起来,计算得出根哈希值,称为receiptsRoot 交易树 ,该值将进入区块头部被永久保留。所有收据和对应着收据的日志将被存于世界各地以太坊节点计算机的数据库里,方便客户进行查询。