go-ethereum源碼剖析:區塊存儲

區塊和交易等數據最終都是存儲在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的前綴爲hbodyPrefix定義了區塊體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函數分別調用WriteBodyWriteHeader將區塊寫到數據庫中。此外還有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、交易所在區塊的區塊號、交易在區塊中的索引。

相關文章
相關標籤/搜索