在Ethereum的世界里,数据的最终存储形式是[k,v]键值对,目前使用的[k,v]型底层数据库是LevelDB;所有与交易,操作相关的数据,其呈现的集合形式是Block(Header);如果以Block为单位链接起来,则构成更大粒度的BlockChain(HeaderChain);若以Block作切割,那么Transaction和Contract就是更小的粒度;所有交易或操作的结果,将以各个个体账户的状态(state)存在,账户的呈现形式是stateObject,所有账户的集合受StateDB管理。下图描绘了上述各数据单元的层次关系:
另一方面,上述数据单元如Block,stateObject,StateDB等,均大量使用Merkle-PatriciaTrie(MPT)数据结构以组织和管理[k,v]型数据。利用MPT高效的分段哈希验证机制和灵活的节点(Node)插入/载入设计,调用方均可快速且高效的实现对数据的插入、删除、更新、压缩和加密。
Block和Header
Block结构体基本可分为Header和Body两个部分
Header
Header是Block的核心,注意到它的成员变量全都是公共的,这使得它可以很方便的向调用者提供关于Block属性的操作。Header的成员变量全都很重要,值得细细理解:
- ParentHash:指向父区块(parentBlock)的指针。除了创世块(Genesis Block)外,每个区块有且只有一个父区块。
- Coinbase:挖掘出这个区块的作者地址。在每次执行交易时系统会给与一定补偿的Ether,这笔金额就是发给这个地址的。
- UncleHash:Block结构体的成员uncles的RLP哈希值。uncles是一个Header数组,它的存在,颇具匠心。
- Root:StateDB中的“state Trie”的根节点的RLP哈希值。Block中,每个账户以stateObject对象表示,账户以Address为唯一标示,其信息在相关交易(Transaction)的执行中被修改。所有账户对象可以逐个插入一个Merkle-PatricaTrie(MPT)结构里,形成“state Trie”。
- TxHash: Block中 “tx Trie”的根节点的RLP哈希值。Block的成员变量transactions中所有的tx对象,被逐个插入一个MPT结构,形成“tx Trie”。
- ReceiptHash:Block中的 “Receipt Trie”的根节点的RLP哈希值。Block的所有Transaction执行完后会生成一个Receipt数组,这个数组中的所有Receipt被逐个插入一个MPT结构中,形成”Receipt Trie”。
- Bloom:Bloom过滤器(Filter),用来快速判断一个参数Log对象是否存在于一组已知的Log集合中。
- **Difficulty:区块的难度。Block的Difficulty由共识算法基于parentBlock的Time和Difficulty计算得出,它会应用在区块的‘挖掘’阶段。
- Number:区块的序号。Block的Number等于其父区块Number +1。
- Time:区块“应该”被创建的时间。由共识算法确定,一般来说,要么等于parentBlock.Time + 10s,要么等于当前系统时间。
- GasLimit:区块内所有Gas消耗的理论上限。该数值在区块创建时设置,与父区块有关。具体来说,根据父区块的GasUsed同GasLimit * 2/3的大小关系来计算得出。
- GasUsed:区块内所有Transaction执行时所实际消耗的Gas总和。
- Nonce:一个64bit的哈希数,它被应用在区块的”挖掘”阶段,并且在使用中会被修改。
Root,TxHash和ReceiptHash,分别取自三个MPT类型对象:stateTrie, txTrie, 和receiptTrie的根节点哈希值。
receiptTrie 必须在Block的所有交易执行完成才能生成;txTrie 理论上只需tx数组transactions即可,不过依然被限制在所有交易执行完后才生成;最有趣的是stateTrie,由于它存储了所有账户的信息,比如余额,发起交易次数,虚拟机指令数组等等,所以随着每次交易的执行,stateTrie 其实一直在变化,这就使得Root值也在变化中。
Bloom:日志布隆过滤器 2048bit 256字节
用户可以通过传递下面的参数来查找指定的Log,开始的区块号,结束的区块号, 根据合约 Addresses指定的地址过滤,根据指定的Topics来过滤。
BlockChain
type BlockChain struct {
config *params.ChainConfig // chain & network configuration
hc *HeaderChain // 只包含了区块头的区块链
chainDb ethdb.Database // 底层数据库
rmLogsFeed event.Feed // 下面是很多消息通知的组件
chainFeed event.Feed
chainSideFeed event.Feed
chainHeadFeed event.Feed
logsFeed event.Feed
scope event.SubscriptionScope
genesisBlock *types.Block // 创世区块
mu sync.RWMutex // global mutex for locking chain operations
chainmu sync.RWMutex // blockchain insertion lock
procmu sync.RWMutex // block processor lock
checkpoint int // checkpoint counts towards the new checkpoint
currentBlock *types.Block // 当前的区块头
currentFastBlock *types.Block // 当前的快速同步的区块头.
stateCache state.Database // State database to reuse between imports (contains state cache)
bodyCache *lru.Cache // Cache for the most recent block bodies
bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format
blockCache *lru.Cache // Cache for the most recent entire blocks
futureBlocks *lru.Cache // 暂时还不能插入的区块存放位置.
quit chan struct{} // blockchain quit channel
running int32 // running must be called atomically
// procInterrupt must be atomically called
procInterrupt int32 // interrupt signaler for block processing
wg sync.WaitGroup // chain processing wait group for shutting down
engine consensus.Engine // 一致性引擎
processor Processor // block processor interface // 区块处理器接口
validator Validator // block and state validator interface // 区块和状态验证器接口
vmConfig vm.Config //虚拟机的配置
badBlocks *lru.Cache // Bad block cache 错误区块的缓存.
}
小结
以太坊的账号模型相对于比特币UTXO模型更加复杂。需要维护日志,StateDB,账号余额等数据,值得去深入学习使用。
- Block结构体主要分为Header和Body,Header相对轻量,涵盖了Block的所有属性,包括特征标示,前向指针,和内部数据集的验证哈希值等;Body相对重量,持有内部数据集。每个Block的Header部分,Body部分,以及一些特征属性,都以[k,v]形式单独存储在底层数据库中。
- BlockChain管理Block组成的一个单向链表,HeaderChain管理Header组成的单向链表,并且BlockChain持有HeaderChain。在做插入/删除/查找时,要注意回溯,以及数据库中相应的增删。
- Merkle-PatriciaTrie(MPT)数据结构用来组织管理[k,v]型数据,它设计了灵活多变的节点体系和编码格式,既融合了多种原型结构的优点,又兼顾了业务需求和运行效率。
- StateDB作为本地存储模块,它面向业务模型,又连接底层数据库,内部利用两极缓存机制来存储和更新所有代表“账户”的stateObject对象。
- stateObject除了管理着账户余额等信息之外,也用了类似的两级缓存机制来存储和更新所有的State数据。