// BlockChain 表示了一個規範的鏈,這個鏈經過一個包含了創世區塊的數據庫指定. BlockChain管理了鏈的插入,還原,重建等操做. //插入一個區塊須要經過一系列指定的規則指定的兩階段的驗證器. // 使用Processor來對區塊的交易進行處理. 狀態的驗證是第二階段的驗證器. 錯誤將致使插入終止. //須要注意的是GetBlock可能返回任意不在當前規範區塊鏈中的區塊, //可是GetBlockByNumber老是返回當前規範區塊鏈中的區塊. type BlockChain struct { chainConfig *params.ChainConfig // Chain & network configuration cacheConfig *CacheConfig // Cache configuration for pruning db ethdb.Database // Low level persistent database to store final content in triegc *prque.Prque // Priority queue mapping block numbers to tries to gc gcproc time.Duration // Accumulates canonical block processing for trie dumping hc *HeaderChain //只包含了區塊頭的區塊鏈 rmLogsFeed event.Feed // 底層數據庫 chainFeed event.Feed // 下面是不少消息通知的組件 chainSideFeed event.Feed chainHeadFeed event.Feed logsFeed event.Feed scope event.SubscriptionScope genesisBlock *types.Block // 創世區塊 mu sync.RWMutex // global mutex for locking chain operations chainmu sync.RWMutex // blockchain insertion lock procmu sync.RWMutex // block processor lock checkpoint int // checkpoint counts towards the new checkpoint currentBlock *types.Block // Current head of the block chain 當前的區塊頭 currentFastBlock *types.Block // Current head of the fast-sync chain (may be above the block chain!) 當前的快速同步的區塊頭 stateCache state.Database // State database to reuse between imports (contains state cache) bodyCache *lru.Cache // Cache for the most recent block bodies bodyRLPCache *lru.Cache // Cache for the most recent block bodies in RLP encoded format blockCache *lru.Cache // Cache for the most recent entire blocks futureBlocks *lru.Cache // future blocks are blocks added for later processing 暫時還不能插入的區塊存放位置 quit chan struct{} // blockchain quit channel running int32 // running must be called atomically // procInterrupt must be atomically called procInterrupt int32 // interrupt signaler for block processing wg sync.WaitGroup // chain processing wait group for shutting down engine consensus.Engine // 一致性引擎 processor Processor // block processor interface // 區塊處理器接口 validator Validator // block and state validator interface // 區塊和狀態驗證器接口 vmConfig vm.Config //虛擬機的配置 badBlocks *lru.Cache // Bad block cache 錯誤區塊的緩存 }
// Block represents an entire block in the Ethereum blockchain. type Block struct { header *Header uncles []*Header transactions Transactions // caches hash atomic.Value size atomic.Value // Td is used by package core to store the total difficulty // of the chain up to and including the block. td *big.Int // These fields are used by package eth to track // inter-peer block relay. ReceivedAt time.Time ReceivedFrom interface{} }
// Header represents a block header in the Ethereum blockchain. type Header struct { ParentHash common.Hash `json:"parentHash" gencodec:"required"` UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"` Coinbase common.Address `json:"miner" gencodec:"required"` Root common.Hash `json:"stateRoot" gencodec:"required"` TxHash common.Hash `json:"transactionsRoot" gencodec:"required"` ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"` Bloom Bloom `json:"logsBloom" gencodec:"required"` Difficulty *big.Int `json:"difficulty" gencodec:"required"` Number *big.Int `json:"number" gencodec:"required"` GasLimit uint64 `json:"gasLimit" gencodec:"required"` GasUsed uint64 `json:"gasUsed" gencodec:"required"` Time *big.Int `json:"timestamp" gencodec:"required"` Extra []byte `json:"extraData" gencodec:"required"` MixDigest common.Hash `json:"mixHash" gencodec:"required"` Nonce BlockNonce `json:"nonce" gencodec:"required"` }
在這裏保存區塊信息時,key通常是與hash相關的,value所保存的數據結構是通過RLP編碼的。<br> 在代碼中,core/database_util.go中封裝了區塊存儲和讀取相關的代碼。<br> 在存儲區塊信息時,會將區塊頭和區塊體分開進行存儲。所以在區塊的結構體中,可以看到Header和Body兩個結構體。 區塊頭(Header)的存儲格式爲:數據庫
headerPrefix + num (uint64 big endian) + hash -> rlpEncode(header)
key是由區塊頭的前綴,區塊號和區塊hash構成。value是區塊頭的RLP編碼。<br> 區塊體(Body)的存儲格式爲:json
bodyPrefix + num (uint64 big endian) + hash -> rlpEncode(block body)
key是由區塊體前綴,區塊號和區塊hash構成。value是區塊體的RLP編碼。<br> 在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最開始就定義了全部的前綴。這裏的註釋詳細說明了每個前綴存儲了什麼數據類型。<br> database_util.go中的其餘方法則是對leveldb的操做。其中get方法是讀取數據庫中的內容,write則是向leveldb中寫入數據。<br> 要講一個區塊的信息寫入數據庫,則須要調用其中的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 }
這裏咱們看到,將一個區塊信息寫入數據庫實際上是分別將區塊頭和區塊體寫入數據庫。<br> 首先來看區塊頭的存儲。區塊頭的存儲是由WriteHeader方法完成的。app
// 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。<br> 這裏首先向數據庫中存儲了一條區塊hash->區塊號的鍵值對,而後纔將區塊頭的信息寫入數據庫。<br> 接下來是區塊體的存儲。區塊體存儲是由WriteBody方法實現。ide
// 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的組裝方法如以前所述。<br>區塊鏈
交易主要在數據庫中僅存儲交易的Meta信息。ui
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,塊號和塊上第幾筆交易這些信息。<br> 交易Meta存儲是以交易hash加交易的Meta前綴爲key,Meta的RLP編碼爲value。<br> 交易寫入數據庫是經過WriteTxLookupEntries方法實現的。atom
// 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進行存儲。<br> 這樣就將一筆交易寫入數據庫中。<br> 從數據庫中讀取交易信息時經過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,塊號和第幾筆交易。<br> 接下來使用塊號和塊hash獲取從數據庫中讀取塊的信息。<br> 而後根據第幾筆交易從塊上獲取交易的具體信息。<br> 這裏以太坊將交易的存儲換成了新的存儲方式,即交易的具體信息存儲在塊上,交易hash只對應交易的meta信息,並不包含交易的具體信息。<br> 而之前的交易存儲則是須要存儲交易的具體信息和meta信息。<br> 所以GetTransaction方法會支持原有的數據存儲方式。