區塊和交易等數據最終都是存儲在leveldb數據庫中的,數據庫的存儲位置在datadir/geth/chaindata
中,本文介紹區塊和交易在leveldb中的存儲格式。在core/database_util.go
中封裝了全部與區塊存儲和讀取相關的代碼,經過這些代碼能夠弄清楚區塊、交易等數據結構在數據庫中是如何存儲的。數據庫
leveldb是一個key-value數據庫,全部數據都是以鍵-值對的形式存儲。key通常與hash相關,value通常是要存儲的數據結構的RLP編碼。區塊存儲時將區塊頭和區塊體分開存儲。數據結構
區塊頭的存儲格式爲:app
headerPrefix + num (uint64 big endian) + hash -> rlpEncode(header)
其中key由區塊頭前綴、區塊號(uint64大端格式)、區塊hash構成,value是區塊頭的RLP編碼。ide
區塊體的存儲格式爲:函數
bodyPrefix + num (uint64 big endian) + hash -> rlpEncode(block body)
其中key由區塊體前綴、區塊號(uint64大端格式)、區塊hash構成,value是區塊體的RLP編碼。ui
key中的前綴能夠用來區分數據的類型,在core/database_util.go
中定義了各類前綴:this
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
其中headerPrefix
定義了區塊頭key的前綴爲h
,bodyPrefix
定義了區塊體key的前綴爲b
。編碼
下面是存儲區塊頭的函數:.net
// WriteHeader serializes a block header into the database. func WriteHeader(db ethdb.Database, 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 { glog.Fatalf("failed to store hash to number mapping into database: %v", err) } key = append(append(headerPrefix, encNum...), hash...) if err := db.Put(key, data); err != nil { glog.Fatalf("failed to store header into database: %v", err) } glog.V(logger.Debug).Infof("stored header #%v [%x…]", header.Number, hash[:4]) return nil }
它是先對區塊頭進行RLP編碼,encodeBlockNumber
將區塊號轉換成大端格式,而後組裝key。這裏先向數據庫中存儲一條 區塊hash->區塊號
的記錄,最後將區塊頭的RLP編碼寫到數據庫中。code
下面是存儲區塊體的函數:
// WriteBody serializes the body of a block into the database. func WriteBody(db ethdb.Database, 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.Database, 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 { glog.Fatalf("failed to store block body into database: %v", err) } glog.V(logger.Debug).Infof("stored block body [%x…]", hash.Bytes()[:4]) return nil }
WriteBody
先對區塊體進行RLP編碼,而後調用WriteBodyRLP
將區塊體的RLP編碼寫到數據庫中。WriteBodyRLP
根據上面的規則組裝key,而後向數據庫中寫入一條記錄。
還有一個WriteBlock
函數分別調用WriteBody
和WriteHeader
將區塊寫到數據庫中。此外還有GetHeader
GetBody
GetBlock
函數用於從數據庫中讀取區塊。
除了區塊外,數據庫中還存儲了全部的交易,每條交易的存儲格式以下:
txHash -> rlpEncode(tx) txHash + txMetaSuffix -> rlpEncode(txMeta)
每條交易對應存儲兩條數據,一條是交易自己,一條是交易的元信息(meta)。交易以交易的hash爲key、交易的RLP編碼爲value存儲;元信息以txHash+txMetaSuffix爲key、元信息的RLP編碼爲value存儲。元信息中包含交易所在區塊的區塊hash、區塊號、交易在區塊中的索引。具體能夠看WriteTransactions
函數:
// WriteTransactions stores the transactions associated with a specific block // into the given database. Beside writing the transaction, the function also // stores a metadata entry along with the transaction, detailing the position // of this within the blockchain. func WriteTransactions(db ethdb.Database, block *types.Block) error { batch := db.NewBatch() // Iterate over each transaction and encode it with its metadata for i, tx := range block.Transactions() { // Encode and queue up the transaction for storage data, err := rlp.EncodeToBytes(tx) if err != nil { return err } if err := batch.Put(tx.Hash().Bytes(), data); err != nil { return err } // Encode and queue up the transaction metadata for storage meta := struct { BlockHash common.Hash BlockIndex uint64 Index uint64 }{ BlockHash: block.Hash(), BlockIndex: block.NumberU64(), Index: uint64(i), } data, err = rlp.EncodeToBytes(meta) if err != nil { return err } if err := batch.Put(append(tx.Hash().Bytes(), txMetaSuffix...), data); err != nil { return err } } // Write the scheduled data into the database if err := batch.Write(); err != nil { glog.Fatalf("failed to store transactions into database: %v", err) } return nil }
此外還有GetTransaction
函數,根據交易hash從數據庫中讀取交易,它返回對應的交易、交易所在區塊的區塊hash、交易所在區塊的區塊號、交易在區塊中的索引。