以太坊blockchain原始碼分析
blockchain關鍵元素
- db:持久化到底層資料儲存,即leveldb;
- genesisBlock:創始區塊
- currentBlock:當前區塊,blockchain中並不是儲存鏈所有的block,而是通過currentBlock向前回溯直到genesisBlock,這樣就構成了區塊鏈
- bodyCache、bodyRLPCache、blockCache、futureBlocks:區塊鏈中的快取結構,用於加快區塊鏈的讀取和構建;
- hc:headerchain區塊頭鏈,由blockchain額外維護的另一條鏈,由於Header和Block的儲存空間是有很大差別的,但同時Block的Hash值就是Header(RLP)的Hash值,所以維護一個headerchain可以用於快速延長鏈,驗證通過後再下載blockchain,或者可以與blockchain進行相互驗證;
- processor:執行區塊鏈交易的介面,收到一個新的區塊時,要對區塊中的所有交易執行一遍,一方面是驗證,一方面是更新世界狀態;
- validator:驗證資料有效性的介面
- futureBlocks:收到的區塊時間大於當前頭區塊時間15s而小於30s的區塊,可作為當前節點待處理的區塊。
函式介紹
// BadBlocks 處理客戶端從網路上獲取的最近的bad block列表 func (bc *BlockChain) BadBlocks() []*types.Block {} // addBadBlock 把bad block放入快取 func (bc *BlockChain) addBadBlock(block *types.Block) {}
// CurrentBlock取回主鏈的當前頭區塊,這個區塊是從blockchian的內部快取中取得 func (bc *BlockChain) CurrentBlock() *types.Block {} // CurrentHeader檢索規範鏈的當前頭區塊header。從HeaderChain的內部快取中檢索標頭。 func (bc *BlockChain) CurrentHeader() *types.Header{} // CurrentFastBlock取回主鏈的當前fast-sync頭區塊,這個區塊是從blockchian的內部快取中取得 func (bc *BlockChain) CurrentFastBlock() *types.Block {}
// 將活動鏈或其子集寫入給定的編寫器.
func (bc *BlockChain) Export(w io.Writer) error {}
func (bc *BlockChain) ExportN(w io.Writer, first uint64, last uint64) error {}
// FastSyncCommitHead快速同步,將當前頭塊設定為特定hash的區塊。
func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {}
// GasLimit返回當前頭區塊的gas limit
func (bc *BlockChain) GasLimit() uint64 {}
// Genesis 取回genesis區塊
func (bc *BlockChain) Genesis() *types.Block {}
// 通過hash從資料庫或快取中取到一個區塊體(transactions and uncles)或RLP資料
func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {}
func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {}
// GetBlock 通過hash和number取到區塊
func (bc *BlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {}
// GetBlockByHash 通過hash取到區塊
func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {}
// GetBlockByNumber 通過number取到區塊
func (bc *BlockChain) GetBlockByNumber(number uint64) *types.Block {}
// 獲取給定hash和number區塊的header
func (bc *BlockChain) GetHeader(hash common.Hash, number uint64) *types.Header{}
// 獲取給定hash的區塊header
func (bc *BlockChain) GetHeaderByHash(hash common.Hash) *types.Header{}
// 獲取給定number的區塊header
func (bc *BlockChain) GetHeaderByNumber(number uint64) *types.Header{}
// HasBlock檢驗hash對應的區塊是否完全存在資料庫中
func (bc *BlockChain) HasBlock(hash common.Hash, number uint64) bool {}
// 檢查給定hash和number的區塊的區塊頭是否存在資料庫
func (bc *BlockChain) HasHeader(hash common.Hash, number uint64) bool{}
// HasState檢驗state trie是否完全存在資料庫中
func (bc *BlockChain) HasState(hash common.Hash) bool {}
// HasBlockAndState檢驗hash對應的block和state trie是否完全存在資料庫中
func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64) bool {}
// 獲取給定hash的區塊的總難度
func (bc *BlockChain) GetTd(hash common.Hash, number uint64) *big.Int{}
// 獲取從給定hash的區塊到genesis區塊的所有hash
func (bc *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64) []common.Hash{}
// GetReceiptsByHash 在特定的區塊中取到所有交易的收據
func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {}
// GetBlocksFromHash 取到特定hash的區塊及其n-1個父區塊
func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int) (blocks []*types.Block) {}
// GetUnclesInChain 取回從給定區塊到向前回溯特定距離到區塊上的所有叔區塊
func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int) []*types.Header {}
// insert 將新的頭塊注入當前塊鏈。 該方法假設該塊確實是真正的頭。
// 如果它們較舊或者它們位於不同的側鏈上,它還會將頭部標題和頭部快速同步塊重置為同一個塊。
func (bc *BlockChain) insert(block *types.Block) {}
// InsertChain嘗試將給定批量的block插入到規範鏈中,否則,建立一個分叉。 如果返回錯誤,它將返回失敗塊的索引號以及描述錯誤的錯誤。
//插入完成後,將觸發所有累積的事件。
func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error){}
// insertChain將執行實際的鏈插入和事件聚合。
// 此方法作為單獨方法存在的唯一原因是使用延遲語句使鎖定更清晰。
func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*types.Log, error){}
// InsertHeaderChain嘗試將給定的headerchain插入到本地鏈中,可能會建立一個重組
func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int) (int, error){}
// InsertReceiptChain 使用交易和收據資料來完成已經存在的headerchain
func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int, error) {}
//loadLastState從資料庫載入最後一個已知的鏈狀態。
func (bc *BlockChain) loadLastState() error {}
// Processor 返回當前current processor.
func (bc *BlockChain) Processor() Processor {}
// Reset重置清除整個區塊鏈,將其恢復到genesis state.
func (bc *BlockChain) Reset() error {}
// ResetWithGenesisBlock 清除整個區塊鏈, 用特定的genesis state重塑,被Reset所引用
func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {}
// repair嘗試通過回滾當前塊來修復當前的區塊鏈,直到找到具有關聯狀態的塊。
// 用於修復由崩潰/斷電或簡單的非提交嘗試導致的不完整的資料庫寫入。
//此方法僅回滾當前塊。 當前標頭和當前快速塊保持不變。
func (bc *BlockChain) repair(head **types.Block) error {}
// reorgs需要兩個塊、一箇舊鏈以及一個新鏈,並將重新構建塊並將它們插入到新的規範鏈中,並累積潛在的缺失事務併發布有關它們的事件
func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error{}
// Rollback 旨在從資料庫中刪除不確定有效的鏈片段
func (bc *BlockChain) Rollback(chain []common.Hash) {}
// SetReceiptsData 計算收據的所有非共識欄位
func SetReceiptsData(config *params.ChainConfig, block *types.Block, receipts types.Receipts) error {}
// SetHead將本地鏈回滾到指定的頭部。
// 通常可用於處理分叉時重選主鏈。對於Header,新Header上方的所有內容都將被刪除,新的頭部將被設定。
// 但如果塊體丟失,則會進一步回退(快速同步後的非歸檔節點)。
func (bc *BlockChain) SetHead(head uint64) error {}
// SetProcessor設定狀態修改所需要的processor
func (bc *BlockChain) SetProcessor(processor Processor) {}
// SetValidator 設定用於驗證未來區塊的validator
func (bc *BlockChain) SetValidator(validator Validator) {}
// State 根據當前頭區塊返回一個可修改的狀態
func (bc *BlockChain) State() (*state.StateDB, error) {}
// StateAt 根據特定時間點返回新的可變狀態
func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error) {}
// Stop 停止區塊鏈服務,如果有正在import的程序,它會使用procInterrupt來取消。
// it will abort them using the procInterrupt.
func (bc *BlockChain) Stop() {}
// TrieNode從memory快取或storage中檢索與trie節點hash相關聯的資料。
func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte, error) {}
// Validator返回當前validator.
func (bc *BlockChain) Validator() Validator {}
// WriteBlockWithoutState僅將塊及其元資料寫入資料庫,但不寫入任何狀態。 這用於構建競爭方叉,直到超過規範總難度。
func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (err error){}
// WriteBlockWithState將塊和所有關聯狀態寫入資料庫。
func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) {}
// writeHeader將標頭寫入本地鏈,因為它的父節點已知。 如果新插入的報頭的總難度變得大於當前已知的TD,則重新路由規範鏈
func (bc *BlockChain) writeHeader(header *types.Header) error{}
// 處理未來區塊鏈
func (bc *BlockChain) update() {}
blockchain初始化
主要步驟:
①:建立一個新的headerChain結構
bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt)
- 根據number(0)獲取genesisHeader
- 從rawdb中讀取HeadBlock並存儲在currentHeader中
②:獲取genesisBlock
bc.genesisBlock = bc.GetBlockByNumber(0)
③:如果鏈不為空,則用老的鏈資料初始化鏈
if bc.empty() {
rawdb.InitDatabaseFromFreezer(bc.db)
}
④:載入最新的狀態資料
if err := bc.loadLastState(); err != nil {
return nil, err
}
⑤:檢查區塊雜湊的當前狀態,並確保鏈中沒有任何壞塊
for hash := range BadHashes {
if header := bc.GetHeaderByHash(hash); header != nil {
headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64())
if headerByNumber != nil && headerByNumber.Hash() == header.Hash() {
log.Error("Found bad hash, rewinding chain", "number", header.Number, "hash", header.ParentHash)
bc.SetHead(header.Number.Uint64() - 1)
log.Error("Chain rewind was successful, resuming normal operation")
}
}
}
⑥:定時處理future block
go bc.update()
->procFutureBlocks
->InsertChain
總的來說做了以下幾件事:
- 配置cacheConfig,建立各種lru快取
- 初始化triegc
- 初始化stateDb:state.NewDatabase(db)
- 初始化區塊和狀態驗證:NewBlockValidator()
- 初始化狀態處理器:NewStateProcessor()
- 初始化區塊頭部鏈:NewHeaderChain()
- 查詢創世區塊:bc.genesisBlock = bc.GetBlockByNumber(0)
- 載入最新的狀態資料:bc.loadLastState()
- 檢查區塊雜湊的當前狀態,並確保鏈中沒有任何壞塊
- go bc.update() 定時處理future block
載入區塊鏈狀態
①:從資料庫中恢復headblock,如果空的話,觸發reset chain
head := rawdb.ReadHeadBlockHash(bc.db)
if head == (common.Hash{}) {
log.Warn("Empty database, resetting chain")
return bc.Reset()
}
②:確保整個head block是可以獲取的,若為空,則觸發reset chain
currentBlock := bc.GetBlockByHash(head)
if currentBlock == nil {
// Corrupt or empty database, init from scratch
log.Warn("Head block missing, resetting chain", "hash", head)
return bc.Reset()
}
③:從stateDb中開啟最新區塊的狀態trie,如果開啟失敗呼叫bc.repair(¤tBlock)方法進行修復。修復方法就是從當前區塊一個個的往前面找,直到找到好的區塊,然後賦值給currentBlock。
if _, err := state.New(currentBlock.Root(), bc.stateCache); err != nil {
// Dangling block without a state associated, init from scratch
log.Warn("Head state missing, repairing chain", "number", currentBlock.Number(), "hash", currentBlock.Hash())
if err := bc.repair(¤tBlock); err != nil {
return err
}
rawdb.WriteHeadBlockHash(bc.db, currentBlock.Hash())
}
④:儲存當前的headblock和設定當前的headHeader以及頭部快速塊
bc.currentBlock.Store(currentBlock)
....
bc.hc.SetCurrentHeader(currentHeader)
...
bc.currentFastBlock.Store(currentBlock)
插入資料到blockchain中
①:如果鏈正在中斷,直接返回
②:開啟並行的簽名恢復
③:校驗header
abort, results := bc.engine.VerifyHeaders(bc, headers, seals)
④:迴圈校驗body
block, err := it.next()
-> ValidateBody
-> VerifyUncles
包括以下錯誤:
- block已知
- uncle太多
- 重複的uncle
- uncle是祖先塊
- uncle雜湊不匹配
- 交易雜湊不匹配
- 未知祖先
- 祖先塊的狀態無法獲取
如果block存在,且是已知塊,則寫入已知塊。
如果是祖先塊的狀態無法獲取的錯誤,則作為側鏈插入:
bc.insertSideChain(block, it)
如果是未來塊或者未知祖先,則新增未來塊:
bc.addFutureBlock(block);
如果是其他錯誤,直接中斷,並且報告壞塊。
bc.futureBlocks.Remove(block.Hash())
...
bc.reportBlock(block, nil, err)
⑤:沒有校驗錯誤
如果是壞塊,則報告;如果是未知塊,則寫入未知塊;根據給定trie,建立state;
執行塊中的交易:
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
使用預設的validator校驗狀態:
bc.validator.ValidateState(block, statedb, receipts, usedGas);
將塊寫入到區塊鏈中並獲取狀態:
status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false)
⑥:校驗寫入區塊的狀態
- CanonStatTy : 插入成功新的block
- SideStatTy:插入成功新的分叉區塊
- Default:插入未知狀態的block
⑦:如果還有塊,並且是未來塊的話,那麼將塊新增到未來塊的快取中去
bc.addFutureBlock(block)
至此insertChain 大概介紹清楚。
將塊和關聯狀態寫入到資料庫
函式:WriteBlockWithState
①:計算父塊的total td
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
②:新增待插入塊本身的td ,並將此時最新的total td 儲存到資料庫中。
bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd)
③:將塊的header和body分別序列化到資料庫
rawdb.WriteBlock(bc.db, block)
->WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
->WriteHeader(db, block.Header())
④:將狀態寫入底層記憶體Trie資料庫
state.Commit(bc.chainConfig.IsEIP158(block.Number()))
⑤:儲存一個塊的所有交易資料
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
⑥:將新的head塊注入到當前鏈中
if status == CanonStatTy {
bc.insert(block)
}
- 儲存分配給規範塊的雜湊
- 儲存頭塊的雜湊
- 儲存最新的快
- 更新currentFastBlock
到此writeBlockWithState 結束,從上面可以知道,insertChain的最終還是呼叫了writeBlockWithState的insert方法完成了最終的插入動作。
思考
- 為什麼還要匯入已知塊???writeKnownBlock
參考:
https://github.com/mindcarver/blockchain_guide (優秀的區塊鏈學習營地)