1. 程式人生 > 實用技巧 >以太坊blockchain原始碼分析

以太坊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)
  1. 根據number(0)獲取genesisHeader
  2. 從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

總的來說做了以下幾件事:

  1. 配置cacheConfig,建立各種lru快取
  2. 初始化triegc
  3. 初始化stateDb:state.NewDatabase(db)
  4. 初始化區塊和狀態驗證:NewBlockValidator()
  5. 初始化狀態處理器:NewStateProcessor()
  6. 初始化區塊頭部鏈:NewHeaderChain()
  7. 查詢創世區塊:bc.genesisBlock = bc.GetBlockByNumber(0)
  8. 載入最新的狀態資料:bc.loadLastState()
  9. 檢查區塊雜湊的當前狀態,並確保鏈中沒有任何壞塊
  10. 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(&currentBlock)方法進行修復。修復方法就是從當前區塊一個個的往前面找,直到找到好的區塊,然後賦值給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(&currentBlock); 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方法完成了最終的插入動作。


思考

  1. 為什麼還要匯入已知塊???writeKnownBlock

參考:

https://github.com/mindcarver/blockchain_guide (優秀的區塊鏈學習營地)