ETH数据结构
// block块数据
type Block struct {
header *Header
uncles []*Header
transactions Transactions
// caches
hash atomic.Value
size atomic.Value
// Td is used by package core to store the total difficulty
// of the chain up to and including the block.
td *big.Int
// These fields are used by package eth to track
// inter-peer block relay.
ReceivedAt time.Time
ReceivedFrom interface{}
}
// header数据
type Header struct {
ParentHash common.Hash `json:"parentHash" gencodec:"required"` // 指向父区块的指针。
UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` // Block结构体的成员uncles的RLP哈希值。
Coinbase common.Address `json:"miner" gencodec:"required"` // 挖掘出这个区块的作者地址。
Root common.Hash `json:"stateRoot" gencodec:"required"` // StateDB中的“state Trie”的根节点的RLP哈希值。
TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` // Block中 “tx Trie”的根节点的RLP哈希值。
ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` // Block中的 "Receipt Trie”的根节点的RLP哈希值。
Bloom Bloom `json:"logsBloom" gencodec:"required"` // Bloom过滤器(Filter),用来快速判断一个参数Log对象是否存在于一组已知的Log集合中。
Difficulty *big.Int `json:"difficulty" gencodec:"required"` // 区块的难度。
Number *big.Int `json:"number" gencodec:"required"` // 区块的序号。
GasLimit uint64 `json:"gasLimit" gencodec:"required"` // 区块内所有Gas消耗的理论上限。
GasUsed uint64 `json:"gasUsed" gencodec:"required"` // 区块内所有Transaction执行时所实际消耗的Gas总和。
Time *big.Int `json:"timestamp" gencodec:"required"` // 区块时间戳,要么等于parentBlock.Time + 10s,要么等于当前系统时间。
Extra []byte `json:"extraData" gencodec:"required"` // ?
MixDigest common.Hash `json:"mixHash" gencodec:"required"` // ?
Nonce BlockNonce `json:"nonce" gencodec:"required"` // 挖矿随机数,一个64bit的哈希数。
}
// Transaction数据
type Transaction struct {
data txdata //交易明细
// caches
hash atomic.Value // 交易hash
size atomic.Value // ?
from atomic.Value // from地址
}
type txdata struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Price *big.Int `json:"gasPrice" gencodec:"required"`
GasLimit uint64 `json:"gas" gencodec:"required"`
Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation
Amount *big.Int `json:"value" gencodec:"required"`
Payload []byte `json:"input" gencodec:"required"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
// 块数据示例
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"difficulty": "0x7b56814e8e0",
"extraData": "0xd783010203844765746887676f312e342e32856c696e7578",
"gasLimit": "0x2fefd8",
"gasUsed": "0x37c5e",
"hash": "0x3d3ebb11286a91fdc00cfb6ffa4f4a73e3e528ce13467bb2203532dc9cb42a8f",
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000400000000000000000000040000000000000",
"miner": "0x52bc44d5378309ee2abf1539bf71de1b7d7be3b5",
"mixHash": "0x40384589c970bff24a4d5d87692cfd948c89cb7e5381ad28d31071e85c7f22b6",
"nonce": "0xda9cbaa29db514a5",
"number": "0xaae61",
"parentHash": "0x6c621dc6d89cecd48004bd6bd6f3098a18f61aa140b912f0db583198cb69558e",
"receiptsRoot": "0x852ede9b7d114119ecf6f6221f9f6ed059bb25e098ffc6ae89f451186d44c056",
"sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347",
"size": "0x572",
"stateRoot": "0x3b65505100e6fdfa2649764e63a7a980aebfd93726690fe0239e384ede4e3980",
"timestamp": "0x567137a7",
"totalDifficulty": "0x3b49b0afd6d0597c",
"transactionsRoot": "0xb42c31f189406cc77674b5e6f7a7b7d63dd895f875befb3351fb117f37580d38",
"uncles": [],
"transactions": [
{
"blockHash": "0x3d3ebb11286a91fdc00cfb6ffa4f4a73e3e528ce13467bb2203532dc9cb42a8f",
"blockNumber": "0xaae61",
"from": "0xcba4c3cc041b09d3cbf416b73633cb6bd21c88f8",
"gas": "0x3d090",
"gasPrice": "0xc1b710800",
"hash": "0x8c1dd51958fc97a95a24acce4cafba517337138545b57a84cdef3e970bbdf919",
"input": "0x651e723c000000000000000000000000f0b019bb52f59680845b670453ffd875ae3a59e400000000000000000000000000000000000000000000000000000000567137b900000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000002961caac0fcae0a7f51e4131ef6a243df537d11696bb1a27bac34e37a7c198b9b7ca5c71e62ed55fbca1516ad12305849bfb7421cc199d40dd08f8bb8bd0b57dc0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000001b0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000f0b019bb52f59680845b670453ffd875ae3a59e400000000000000000000000059cc8539f5515335f1340662ce569fa988b40938000000000000000000000000761566074a8e0f7063044abfb8c80ca4cd8c5fd3000000000000000000000000d4fcbd984baebcc12f70f9a151c5c5708d825a7f",
"nonce": "0x35",
"to": "0x7de5aba7de728950c92c57d08e20d4077161f12f",
"transactionIndex": "0x0",
"value": "0x1",
"v": "0x1b",
"r": "0x378066c58af812dc57dc3d090cd6717338d1e51bc1d6fbc308e293ebb69089b",
"s": "0x6208bfb9f67145ce1ac7fadb65682f3a2ea43380e80d07f6d0eab8e5fc5df926"
},
{
"blockHash": "0x3d3ebb11286a91fdc00cfb6ffa4f4a73e3e528ce13467bb2203532dc9cb42a8f",
"blockNumber": "0xaae61",
"from": "0xf8b483dba2c3b7176a3da549ad41a48bb3121069",
"gas": "0x15f90",
"gasPrice": "0xba43b7400",
"hash": "0x5408d12bde03366c4a4b9ad484341bd1c69f86c58ef27a792d1fff1ceda6a6be",
"input": "0x",
"nonce": "0x7e55",
"to": "0xec33b995e319527958760affd24f61fee029e4f9",
"transactionIndex": "0x1",
"value": "0x15c98d913a1bc00",
"v": "0x1c",
"r": "0xd308fb2c73d6f72545d0f7f5b9ac393f28834bb1f4c64c625cfb71f6ff0fd9f4",
"s": "0x20ca14e9481c8c8a97ed2d761f3692e05fb3d7dc262176203670be6d6f934573"
},
{
"blockHash": "0x3d3ebb11286a91fdc00cfb6ffa4f4a73e3e528ce13467bb2203532dc9cb42a8f",
"blockNumber": "0xaae61",
"from": "0x2a65aca4d5fc5b5c859090a6c34d164135398226",
"gas": "0x15f90",
"gasPrice": "0xba43b7400",
"hash": "0x5d8cb5dfd613171daf733f65baefe727060bef89e705a4a7ac46cda1ffb53744",
"input": "0x",
"nonce": "0xf4ae",
"to": "0x7fdf07a0f977e4032c1f082df831423abf58d13d",
"transactionIndex": "0x2",
"value": "0xf6ceba93384a000",
"v": "0x1b",
"r": "0xa24aefa55e9e47915a0b67bbac1b4cc0a823574077cb60a0b2f75f05eec758e8",
"s": "0x1b2b89d5a963003283b81c5da14e58b52a611827db1066f0992d36b2fd88a4d"
}
]
}
}
database_util.go 说明
// 初始化前缀信息
headHeaderKey = []byte("LastHeader") // 最新区块头 Headchain中使用
headBlockKey = []byte("LastBlock") // 最新区块 BlockChain中使用
headFastKey = []byte("LastFast") // 最新快速同步的区块头 这里“LastFast”所存储的是在一种特别的同步方式FastSync下,最新Block的canonical hash。FastSync相比于FullSync,可以仅仅同步Header而不考虑Body,故此得名Fast。
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`). ETH定义好的区块头前缀标识,key的前缀可以区分leveldb中存储的是什么类型的数据。
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header 区块头key前缀
tdSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td 区块难度
numSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hash 区块高度
blockHashPrefix = []byte("H") // blockHashPrefix + hash -> num (uint64 big endian) 区块hash前缀
bodyPrefix = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body 区块体前缀
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts 区块交易收据
lookupPrefix = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
preimagePrefix = "secure-key-" // preimagePrefix + hash -> preimage
configPrefix = []byte("ethereum-config-") // config prefix for the db
// Chain index prefixes (use `i` + single byte to avoid mixing data types).
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress
// used by old db, now only used for conversion
oldReceiptsPrefix = []byte("receipts-")
oldTxMetaSuffix = []byte{0x01}
ErrChainConfigNotFound = errors.New("ChainConfig not found") // general config not found error
preimageCounter = metrics.NewCounter("db/preimage/total")
preimageHitCounter = metrics.NewCounter("db/preimage/hits")
- 方法归类整理
数据库操作封装
1.块hash -> 块头
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
a.WriteHeader()
b.GetHeaderRLP()
c.GetHeader()
d.DeleteHeader()
2.块hash -> 总难度
tdSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> td
a.WriteTd()
b.GetTd()
c.DeleteTd()
3.块高 -> 块hash
numSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hash
canonical hash:区块hash,Block(或Header)对象的RLP哈希值。
num(Number)和hash是Block最为重要的两个属性:num用来确定Block在整个区块链中所处的位置,hash用来辨识惟一的Block/Header对象。
a.encodeBlockNumber(number uint64) //将区块高度转化为ig endian uint64类型,大端格式
b.GetCanonicalHash()
c.WriteCanonicalHash()
d.DeleteCanonicalHash()
4.块hash -> 块高
blockHashPrefix = []byte("H") // blockHashPrefix + hash -> num (uint64 big endian)
5.块hash -> body数据
bodyPrefix = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body
a.WriteBodyRLP()
b.GetBodyRLP()
c.WriteBody()
d.GetBody()
e.DeleteBody()
6.块hash -> 交易收据
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
a.WriteBlockReceipts()
b.GetBlockReceipts()
c.DeleteBlockReceipts()
7.块hash -> 交易收据lookup metadata
lookupPrefix = []byte("l") // lookupPrefix + hash -> transaction/receipt lookup metadata
a.WriteTxLookupEntries()
b.GetTxLookupEntry()
c.DeleteTxLookupEntry()
8.块高 -> bloom bits
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit(uint16 big endian) + section(uint64 big endian) + hash -> bloom bits
a.WriteBloomBits()
b.GetBloomBits()
9.没看懂
preimagePrefix = "secure-key-" // preimagePrefix + hash -> preimage(原像)
WritePreimages()
10.eth配置数据库信息
configPrefix = []byte("ethereum-config-") // config prefix for the db
a.WriteChainConfig()
b.GetChainConfig()
11.没看懂
BloomBitsIndexPrefix = []byte("iB") // BloomBitsIndexPrefix is the data table of a chain indexer to track its progress 索引进度跟踪
12.//used by old db, now only used for conversion 转换
oldReceiptsPrefix = []byte("receipts-")
oldTxMetaSuffix = []byte{0x01}
对外封装方法:
1.初始化数据
missingNumber // 如果数据库中没有获取到块高数据,则使用该缺省块高。
TxLookupEntry //Lookup实体
2.块高
GetBlockNumber() //返回missingNumber/块高
3.块hash
WriteHeadHeaderHash()
GetHeadHeaderHash() //返回当前块头的hash
WriteHeadBlockHash()
GetHeadBlockHash() //返回当前块头的hash
WriteHeadFastBlockHash()
GetHeadFastBlockHash() //返回当前块头的hash
4.区块操作
WriteBlock()
GetBlock()
DeleteBlock()
5.区块头操作
FindCommonAncestor() // 获取两个区块头的共同父类区块头信息?
6.交易信息
GetTransaction()
7.交易收据信息
GetReceipt()
8.区块链操作
WriteBlockChainVersion()
GetBlockChainVersion()
9.公用方法
headerKey() //公用headerKey生成
blockBodyKey() //公用bodyKey生成
数据库体系
LDBDatabase
属于Ethereum代码范围内的最底层数据库是ethdb.LDBDatabase,它通过持有一个levelDB的对象,最终为Ethereum世界里所有需要存储/读取[k,b]的需求提供服务。
- interface.go
统一接口封装,包含数据库的单/批操作方法(putter,get,has,delete,close,batch) - database.go
a.NewLDBDatabase()--初始化LevelDB实例,初始化数据库数据,文件缓存容量+块缓存容量+写缓存等
b.Put()--保存数据,将数据放入levelDB存储队列
c.Has()--校验key值在LevelDB中是否存在
d.Get()--查询key对应的value值
e.Delete()--删除key对应的数据
f.NewIterator()--创建LevelDB数据遍历器
g.Close()--数据库关闭操作
h.Meter()--初始化特殊请求前缀,并启动监听程序
i.NewBatch()--初始化批处理实例,后续提供了相关Put/ValueSize/Write等方法
j.table/tableBatch--按照前缀操作单/批量数据的实例,后续提供了相关Put/Has/Get/Delete/Close等方法 - database_test.go
基于database.go对外提供的测试接口 - memory_database.go
使用内存变量模拟数据库单/批的并行操作
Trie
图中多次出现一种类似的设计模式,比如trie.Trie持有一个本地接口trie.<<Database>>,而后者的具体实现是ethdb.LDBDatabase。这种设计模式其实是golang的语法带来的。在golang中,一个结构体(类)要实现另一个接口的所有方法,不必在结构体声明时显式继承那个接口,只要完全实现那些方法。这样,当一个结构体想调用另一个包路径下结构体的多个方法时,可以声明一个本地接口,带有几个同想要调用方法完全一样的方法,就可以了,这种方式的优点是不同包之间的代码更充分的解耦合。所以在上图中,这些辅助性的本地接口全都被标为灰色,只需要关注实际调用的实现类就好了。
- 源码整理
a.Trie-LevelDB
实例:
Database:DatabaseReader/DatabaseWriter
方法:
DatabaseReader:Get/Has
DatabaseWriter:Put
b.Trie
实例:
Trie
-- root node 始终作为整个MPT的根节点
-- db Database Trie-LevelDB中数据库操作实例
-- originalRoot common.Hash 创建Trie对象时承接入参hashNode
-- cachegen, cachelimit uint16 cacheGen是cache次数的计数器,每次Trie的变动提交后(写入的对象可由外部参数传入),cacheGen自增1
方法:
New 根据数据库数据创建根节点数据,
NodeIterator 创建节点迭代器
Get 根据key值返回Trie中的value,不报错
TryGet 根据key值返回Trie中的value,报错(MissingNodeError)
Update/TryUpdate 根据key更新(insert)value,如果value长度为0,则从Trie中删除并返回nil。
Delete/TryDelete
Commit/CommitTo 将trie记录提交至数据库
State
系统设计中,在底层数据库模块和业务模型之间,往往需要设置本地存储模块,它面向业务模型,可以根据业务需求灵活的设计各种存储格式和单元,同时又连接底层数据库,如果底层数据库(或者第三方API)有变动,可以大大减少对业务模块的影响。在Ethereum世界里,StateDB就担任这个角色,它通过大量的stateObject对象集合,管理所有“账户”信息。
面向业务的存储模块 - StateDB
StateDB有一个trie.Trie类型成员trie,它又被称为storage trie或stte trie,这个MPT结构中存储的都是stateObject对象,每个stateObject对象以其地址(20 bytes)作为插入节点的Key;每次在一个区块的交易开始执行前,trie由一个哈希值(hashNode)恢复出来。另外还有一个map结构,也是存放stateObject,每个stateObject的地址作为map的key。那么问题来了,这些数据结构之间是怎样的关系呢?
如上图所示,每当一个stateObject有改动,亦即“账户”信息有变动时,这个stateObject对象会更新,并且这个stateObject会标为dirty,此时所有的数据改动还仅仅存储在map里。当IntermediateRoot()调用时,所有标为dirty的stateObject才会被一起写入trie。而整个trie中的内容只有在CommitTo()调用时被一起提交到底层数据库。可见,这个map被用作本地的一级缓存,trie是二级缓存,底层数据库是第三级,各级数据结构的界限非常清晰,这样逐级缓存数据,每一级数据向上一级提交的时机也根据业务需求做了合理的选择。
StateDB中账户状态的版本管理
StateDB还可以管理账户状态的版本。这个功能用到了几个结构体:journal,revision,先来看看UML关系图:
其中journal对象是journalEntry的散列,长度不固定,可任意添加元素。接口journalEntry存在若干种实现体,描述了从单个账户操作(账户余额,发起合约次数等),到account trie变化(创建新账户对象,账户消亡)等各种最小事件。revision结构体,用来描述一个‘版本’,它的两个整型成员jd和journalIndex,都是基于journal散列进行操作的。
上图简述了StateDB中账户状态的版本是如何管理的。首先journal散列会随着系统运行不断的增长,记录所有发生过的单位事件;当某个时刻需要产生一个账户状态版本时,代码中相应的是Snapshop()调用,会产生一个新revision对象,记录下当前journal散列的长度,和一个自增1的版本号。
基于以上的设计,当发生回退要求时,只要根据相应的revision中的journalIndex,在journal散列上,根据所记录的所有journalEntry,即可使所有账户回退到那个状态。
Ethereum里的账户 - stateObject
每个stateObject对象管理着Ethereum世界里的一个“账户”。stateObject有一个成员变量data,类型是Accunt结构体,里面存有账户Ether余额,合约发起次数,最新发起合约指令集的哈希值,以及一个MPT结构的顶点哈希值。
stateObject内部也有一个Trie类型的成员trie,被称为storage trie,它里面存放的是一种被称为State的数据。State跟每个账户相关,格式是[Hash, Hash]键值对。有意思的是,stateObject内部也有类似StateDB一样的二级数据缓存机制,用来缓存和更新这些State。
stateObject定义了一种类型名为storage的map结构,用来存放[]Hash,Hash]类型的数据对,也就是State数据。当SetState()调用发生时,storage内部State数据被更新,相应标示为"dirty"。之后,待有需要时(比如updateRoot()调用),那些标为"dirty"的State数据被一起写入storage trie,而storage trie中的所有内容在CommitTo()调用时再一起提交到底层数据库。
cachingDB
看代码封装了一些对合约进行操作的方法,如ContractCode/ContractCodeSize,具体需要后续研究。