區塊和交易等資料最終都是儲存在leveldb資料庫中的,本文介紹區塊和交易在leveldb中的儲存格式。在core/database_util.go中封裝了所有與區Block Storage和讀取相關的代碼,通過這些代碼可以弄清楚區塊、交易等資料結構在資料庫中是如何儲存的。
區Block Storage
leveldb是一個key-value資料庫,所有資料都是以鍵-值對的形式儲存。key一般與hash相關,value一般是要儲存的資料結構的RLP編碼。區Block Storage時將區塊頭和區塊體分開儲存。
區塊頭的儲存格式為:
headerPrefix + num (uint64 big endian) + hash -> rlpEncode(header)
其中key由區塊號(uint64大端格式)、區塊hash、以及一個首碼構成,value是區塊頭的RLP編碼。
區塊體的儲存格式為:
bodyPrefix + num (uint64 big endian) + hash -> rlpEncode(block body)
其中key由區塊號(uint64大端格式)、區塊hash、以及一個首碼構成,value是區塊體的RLP編碼。
key中的首碼可以用來區分資料的類型,在core/database_util.go中定義了各種首碼:
headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> headertdSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + tdSuffix -> tdnumSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + numSuffix -> hashblockHashPrefix = []byte("H") // blockHashPrefix + hash -> num (uint64 big endian)bodyPrefix = []byte("b") // bodyPrefix + num (uint64 big endian) + hash -> block body
其中headerPrefix定義了區塊頭的首碼為h,bodyPrefix定義了區塊體的首碼為b。
下面是儲存區塊頭的函數:
// 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編碼寫到資料庫中。
下面是儲存區塊體的函數:
// 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函數用於從資料庫中讀取區塊。