以太坊 区块数据存储和查找
1. 以太坊区块数据存储
前缀
core/rawdb/schema.go
// databaseVerisionKey tracks the current database version.
databaseVerisionKey = []byte("DatabaseVersion")
// headHeaderKey tracks the latest know header's hash.
headHeaderKey = []byte("LastHeader")
// headBlockKey tracks the latest know full block's hash.
headBlockKey = []byte("LastBlock")
// headFastBlockKey tracks the latest known incomplete block's hash during fast sync.
headFastBlockKey = []byte("LastFast")
// fastTrieProgressKey tracks the number of trie entries imported during fast sync.
fastTrieProgressKey = []byte("TrieSync")
// Data item prefixes (use single byte to avoid mixing data types, avoid `i`, used for indexes).
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header
headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td
headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash
headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian)
blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body
blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts
txLookupPrefix = []byte("l") // txLookupPrefix + hash -> transaction/receipt lookup metadata
bloomBitsPrefix = []byte("B") // bloomBitsPrefix + bit (uint16 big endian) + section (uint64 big endian) + hash -> bloom bits
preimagePrefix = []byte("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
根据区块头hash将区块高度存储,再根据区块高度和区块头hash将区块头RLP编码后存储
core/rawdb/accessors_chain.go func WriteHeader(db DatabaseWriter, header *types.Header) { // Write the hash -> number mapping var ( hash = header.Hash() number = header.Number.Uint64() encoded = encodeBlockNumber(number) // 将number转化为大端 ) key := headerNumberKey(hash) // key="H"+hash if err := db.Put(key, encoded); err != nil { // 将 number 存入数据库 log.Crit("Failed to store hash to number mapping", "err", err) } // Write the encoded header data, err := rlp.EncodeToBytes(header) // 对区块头进行RLP编码 if err != nil { log.Crit("Failed to RLP encode header", "err", err) } key = headerKey(number, hash) // key="h"+number+hash if err := db.Put(key, data); err != nil { log.Crit("Failed to store header", "err", err) } }
将区块体的RLP编码后存储
func WriteBody(db DatabaseWriter, hash common.Hash, number uint64, body *types.Body) {
data, err := rlp.EncodeToBytes(body)
if err != nil {
log.Crit("Failed to RLP encode body", "err", err)
}
WriteBodyRLP(db, hash, number, data)
}
func WriteBodyRLP(db DatabaseWriter, hash common.Hash, number uint64, rlp rlp.RawValue) {
if err := db.Put(blockBodyKey(number, hash), rlp); err != nil { // key="b"+number+hash
log.Crit("Failed to store block body", "err", err)
}
}
区块体和区块头是分开存储
func WriteBlock(db DatabaseWriter, block *types.Block) { WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) WriteHeader(db, block.Header()) }
根据区块高度写入区块头hash
func WriteCanonicalHash(db DatabaseWriter, hash common.Hash, number uint64) {
if err := db.Put(headerHashKey(number), hash.Bytes()); err != nil { // key="h"+number+"n"
log.Crit("Failed to store number to hash mapping", "err", err)
}
}
根据区块高度删除区块头hash
func DeleteCanonicalHash(db DatabaseDeleter, number uint64) {
if err := db.Delete(headerHashKey(number)); err != nil { // key="h"+number+"n"
log.Crit("Failed to delete number to hash mapping", "err", err)
}
}
2. 以太坊区块查询
通过块高查询整个区块数据
根据区块号获取区块头hash
func ReadCanonicalHash(db DatabaseReader, number uint64) common.Hash {
data, _ := db.Get(headerHashKey(number)) // key="h"+number+"n"
if len(data) == 0 {
return common.Hash{}
}
return common.BytesToHash(data)
}
根据区块号,区块头hash,获取区块头的RLP编码后的值
func ReadHeaderRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue {
data, _ := db.Get(headerKey(number, hash)) // key="h"+number+hash
return data
}
根据区块号,区块头hash,获取区块体的RLP编码后的值
func ReadBodyRLP(db DatabaseReader, hash common.Hash, number uint64) rlp.RawValue {
data, _ := db.Get(blockBodyKey(number, hash)) // key="b"+number+hash
return data
}
通过区块hash查询整个区块数据
根据区块头hash获取区块高度
func ReadHeaderNumber(db DatabaseReader, hash common.Hash) *uint64 {
data, _ := db.Get(headerNumberKey(hash)) // key="H"+hash
if len(data) != 8 {
return nil
}
number := binary.BigEndian.Uint64(data)
return &number
}
3. 存储区块数据
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) { ... rawdb.WriteBlock(bc.db, block) // 将区块体和区块头分别存储 bc.insert(block) ... } func (bc *BlockChain) insert(block *types.Block) { ... rawdb.WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64()) // 将区块头hash存入区块高度 rawdb.WriteHeadBlockHash(bc.db, block.Hash()) ... }
4. 总结
- 存储区块体,将区块体RLP编码后存储 : “b”+number+hash = rlp(body)
- 存储区块头,将区块高度存储 : “H”+hash = number
- 存储区块头,将区块头RLP编码后存储 : “h”+number+hash = rlp(header)
- 存储区块头hash,将区块头hash存储 : key=“h”+number+“n” = hash
RLP(Recursive Length Rrefix)就是递归长度前缀编码,它提供了一种适用于任意数据和数组的二进制编码方法。RLP 是以太坊对象进行序列化的主要编码方式,主要用于以太坊中数据的网络传输和持久化存储。1 ...