以太坊原始碼分析(8)區塊分析

尹成發表於2018-05-13
## 區塊儲存
區塊的儲存是由leveldb完成的,leveldb的資料是以鍵值對儲存的。在這裡儲存區塊資訊時,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方法會支援原有的資料儲存方式。






網址:http://www.qukuailianxueyuan.io/



欲領取造幣技術與全套虛擬機器資料

區塊鏈技術交流QQ群:756146052  備註:CSDN

尹成學院微信:備註:CSDN


相關文章