以太坊區塊和交易存儲
區塊存儲
區塊(Block)是以太坊的核心數據結構之一,Block包含Header和Body兩部分。區塊的存儲是由leveldb完成的,leveldb的數據是以鍵值對存儲的。
// BlockChain 表示了一個規範的鏈,這個鏈通過一個包含了創世區塊的數據庫指定. BlockChain管理了鏈的插入,還原,重建等操作. //插入一個區塊需要通過一系列指定的規則指定的兩階段的驗證器. // 使用Processor來對區塊的交易進行處理. 狀態的驗證是第二階段的驗證器. 錯誤將導致插入終止. //需要註意的是GetBlock可能返回任意不在當前規範區塊鏈中的區塊, //但是GetBlockByNumber總是返回當前規範區塊鏈中的區塊. type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning db ethdb.Database // Low level persistent database to store final content in triegc *prque.Prque // Priority queue mapping block numbers to tries to gc gcproc time.Duration // Accumulates canonical block processing for trie dumping hc *HeaderChain //只包含了區塊頭的區塊鏈 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 // Current head of the block chain 當前的區塊頭 currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!) 當前的快速同步的區塊頭 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 // future blocks are blocks added for later processing 暫時還不能插入的區塊存放位置 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 錯誤區塊的緩存 }
// Block represents an entire block in the Ethereum blockchain. 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 represents a block header in the Ethereum blockchain. type Header struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Difficulty *big.Int `json:"difficulty" gencodec:"required"` Number *big.Int `json:"number" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` Time *big.Int `json:"timestamp" gencodec:"required"` Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash" gencodec:"required"` Nonce BlockNonce `json:"nonce" gencodec:"required"` }
bitcoin
ethereum
以太坊的數據庫體系-Merkle-Patricia Trie(MPT), 它是由一系列節點組成的二叉樹,在樹底包含了源數據的大量葉子節點, 父節點是兩個子節點的Hash值,一直到根節點。
Blockchain和HeaderChain, Blockchain管理所有的Block, 讓其組成一個單向鏈表。Headerchain管理所有的Header,也形成一個單向鏈表, Headerchain是Blockchain裏面的一部分
Transaction是Body的重要數據結構,一個交易就是被外部擁有賬戶生成的加密簽名的一段指令,序列化,然後提交給區塊鏈。
在這裏保存區塊信息時,key一般是與hash相關的,value所保存的數據結構是經過RLP編碼的。
在代碼中,core/database_util.go中封裝了區塊存儲和讀取相關的代碼。
在存儲區塊信息時,會將區塊頭和區塊體分開進行存儲。因此在區塊的結構體中,能夠看到Header和Body兩個結構體。
區塊頭(Header)的存儲格式為:
headerPrefix + num (uint64 big endian) + hash -> rlpEncode(header)
key是由區塊頭的前綴,區塊號和區塊hash構成。value是區塊頭的RLP編碼。
區塊體(Body)的存儲格式為:
bodyPrefix + num (uint64 big endian) + hash -> rlpEncode(block body)
key是由區塊體前綴,區塊號和區塊hash構成。value是區塊體的RLP編碼。
在database_util.go中,key的前綴可以區分leveldb中存儲的是什麽類型的數據。
var (
headHeaderKey = []byte("LastHeader")
headBlockKey = []byte("LastBlock")
headFastKey = []byte("LastFast")
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`).
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
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)
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")
)
database_util.go最開始就定義了所有的前綴。這裏的註釋詳細說明了每一個前綴存儲了什麽數據類型。
database_util.go中的其他方法則是對leveldb的操作。其中get方法是讀取數據庫中的內容,write則是向leveldb中寫入數據。
要講一個區塊的信息寫入數據庫,則需要調用其中的WriteBlock方法。
// WriteBlock serializes a block into the database, header and body separately.
func WriteBlock(db ethdb.Putter, block *types.Block) error {
// Store the body first to retain database consistency
if err := WriteBody(db, block.Hash(), block.NumberU64(), block.Body()); err != nil {
return err
}
// Store the header too, signaling full block ownership
if err := WriteHeader(db, block.Header()); err != nil {
return err
}
return nil
}
這裏我們看到,將一個區塊信息寫入數據庫其實是分別將區塊頭和區塊體寫入數據庫。
首先來看區塊頭的存儲。區塊頭的存儲是由WriteHeader方法完成的。
// WriteHeader serializes a block header into the database.
func WriteHeader(db ethdb.Putter, header *types.Header) error {
data, err := rlp.EncodeToBytes(header)
if err != nil {
return err
}
hash := header.Hash().Bytes()
num := header.Number.Uint64()
encNum := encodeBlockNumber(num)
key := append(blockHashPrefix, hash...)
if err := db.Put(key, encNum); err != nil {
log.Crit("Failed to store hash to number mapping", "err", err)
}
key = append(append(headerPrefix, encNum...), hash...)
if err := db.Put(key, data); err != nil {
log.Crit("Failed to store header", "err", err)
}
return nil
}
這裏首先對區塊頭進行了RLP編碼,然後將區塊號轉換成為byte格式,開始組裝key。
這裏首先向數據庫中存儲了一條區塊hash->區塊號的鍵值對,然後才將區塊頭的信息寫入數據庫。
接下來是區塊體的存儲。區塊體存儲是由WriteBody方法實現。
// WriteBody serializes the body of a block into the database.
func WriteBody(db ethdb.Putter, hash common.Hash, number uint64, body *types.Body) error {
data, err := rlp.EncodeToBytes(body)
if err != nil {
return err
}
return WriteBodyRLP(db, hash, number, data)
}
// WriteBodyRLP writes a serialized body of a block into the database.
func WriteBodyRLP(db ethdb.Putter, hash common.Hash, number uint64, rlp rlp.RawValue) error {
key := append(append(bodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...)
if err := db.Put(key, rlp); err != nil {
log.Crit("Failed to store block body", "err", err)
}
return nil
}
WriteBody首先將區塊體的信息進行RLP編碼,然後調用WriteBodyRLP方法將區塊體的信息寫入數據庫。key的組裝方法如之前所述。
交易存儲
交易主要在數據庫中僅存儲交易的Meta信息。
txHash + txMetaSuffix -> rlpEncode(txMeta)
交易的Meta信息結構體如下:
// TxLookupEntry is a positional metadata to help looking up the data content of
// a transaction or receipt given only its hash.
type TxLookupEntry struct {
BlockHash common.Hash
BlockIndex uint64
Index uint64
}
這裏,meta信息會存儲塊的hash,塊號和塊上第幾筆交易這些信息。
交易Meta存儲是以交易hash加交易的Meta前綴為key,Meta的RLP編碼為value。
交易寫入數據庫是通過WriteTxLookupEntries方法實現的。
// WriteTxLookupEntries stores a positional metadata for every transaction from
// a block, enabling hash based transaction and receipt lookups.
func WriteTxLookupEntries(db ethdb.Putter, block *types.Block) error {
// Iterate over each transaction and encode its metadata
for i, tx := range block.Transactions() {
entry := TxLookupEntry{
BlockHash: block.Hash(),
BlockIndex: block.NumberU64(),
Index: uint64(i),
}
data, err := rlp.EncodeToBytes(entry)
if err != nil {
return err
}
if err := db.Put(append(lookupPrefix, tx.Hash().Bytes()...), data); err != nil {
return err
}
}
return nil
}
這裏,在將交易meta入庫時,會遍歷塊上的所有交易,並構造交易的meta信息,進行RLP編碼。然後以交易hash為key,meta為value進行存儲。
這樣就將一筆交易寫入數據庫中。
從數據庫中讀取交易信息時通過GetTransaction方法獲得的。
// GetTransaction retrieves a specific transaction from the database, along with
// its added positional metadata.
func GetTransaction(db DatabaseReader, hash common.Hash) (*types.Transaction, common.Hash, uint64, uint64) {
// Retrieve the lookup metadata and resolve the transaction from the body
blockHash, blockNumber, txIndex := GetTxLookupEntry(db, hash)
if blockHash != (common.Hash{}) {
body := GetBody(db, blockHash, blockNumber)
if body == nil || len(body.Transactions) <= int(txIndex) {
log.Error("Transaction referenced missing", "number", blockNumber, "hash", blockHash, "index", txIndex)
return nil, common.Hash{}, 0, 0
}
return body.Transactions[txIndex], blockHash, blockNumber, txIndex
}
// Old transaction representation, load the transaction and it‘s metadata separately
data, _ := db.Get(hash.Bytes())
if len(data) == 0 {
return nil, common.Hash{}, 0, 0
}
var tx types.Transaction
if err := rlp.DecodeBytes(data, &tx); err != nil {
return nil, common.Hash{}, 0, 0
}
// Retrieve the blockchain positional metadata
data, _ = db.Get(append(hash.Bytes(), oldTxMetaSuffix...))
if len(data) == 0 {
return nil, common.Hash{}, 0, 0
}
var entry TxLookupEntry
if err := rlp.DecodeBytes(data, &entry); err != nil {
return nil, common.Hash{}, 0, 0
}
return &tx, entry.BlockHash, entry.BlockIndex, entry.Index
}
這個方法會首先通過交易hash從數據庫中獲取交易的meta信息,包括交易所在塊的hash,塊號和第幾筆交易。
接下來使用塊號和塊hash獲取從數據庫中讀取塊的信息。
然後根據第幾筆交易從塊上獲取交易的具體信息。
這裏以太坊將交易的存儲換成了新的存儲方式,即交易的具體信息存儲在塊上,交易hash只對應交易的meta信息,並不包含交易的具體信息。
而以前的交易存儲則是需要存儲交易的具體信息和meta信息。
因此GetTransaction方法會支持原有的數據存儲方式。
以太坊區塊和交易存儲