1. 程式人生 > >以太坊原始碼解讀(6)blockchain區塊插入和校驗分析

以太坊原始碼解讀(6)blockchain區塊插入和校驗分析

以太坊blockchain的管理事務:

1、blockchain模組初始化
2、blockchain模組插入校驗分析
3、blockchain模組區塊鏈分叉處理
4、blockchian模組規範鏈更新

上一節分析了blockchain的初始化,這一節來分析blockchain區塊的插入和校驗分析以及規範鏈更新。

一、InsertChain函式

主要功能:呼叫insertChain將一組區塊挨個嘗試插入資料庫和規範鏈。

功能:
1、驗證每個區塊的header;
2、驗證每個區塊的body;
3、處理驗證header和body所產生的錯誤;
4、如果驗證通過,對待插入區塊的交易狀態進行驗證,否則退出;
5、如果驗證通過,呼叫WriteBlockWithState將第n個區塊插入區塊鏈(寫入資料庫),然後寫入規範鏈,同時處理分叉問題。

應用場景:

1、匯入區塊:

admin.importChain

2、ProtocolManager.Downloader主動同步區塊:

Downloader.synchronise
    —> Downloader.syncWithPeer
        —> Downloader.processFullSyncCotent
            —> Downloader.importBlockResults
                —> BlockChain.InsertChain

3、ProtocolManager.fetcher被動接受其他節點發來的區塊:

ProtocolManager.fetcher.insert
    —> ProtocolManager.Downloader.BlockChain.InsertChain

InsertChain(chain types.Blocks)原始碼

func (bc *BlockChain) InsertChain(chain types.Blocks) (int, error) {
	n, events, logs, err := bc.insertChain(chain)
	bc.PostChainEvents(events, logs)
	return n, err
}

InsertChain實際上呼叫的是insertChain(),然後將匯出事件和日誌。insertChain()函式程式碼還是比較多的,它是上述5個功能是實際實現函式,即:
1、驗證每個區塊的header;
2、驗證每個區塊的body;
3、處理驗證header和body所產生的錯誤;
4、如果驗證通過,對待插入區塊的交易狀態進行驗證,否則退出;
5、如果驗證通過,呼叫WriteBlockWithState將第n個區塊插入區塊鏈(寫入資料庫),然後寫入規範鏈,同時處理分叉問題。

part 1  Pre-checks,保證待插入的鏈是有序連結的

func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*types.Log, error) {
    // Sanity check that we have something meaningful to import
    if len(chain) == 0 {
	return 0, nil, nil, nil
    }
    // 逐個檢查區塊的區塊號是否連續以及hash鏈是否連續
    for i := 1; i < len(chain); i++ {
	if chain[i].NumberU64() != chain[i-1].NumberU64()+1 || chain[i].ParentHash() != chain[i-1].Hash() {
	    // Chain broke ancestry, log a message (programming error) and skip insertion
	    log.Error("Non contiguous block insert", "number", chain[i].Number(), "hash", chain[i].Hash(),
		"parent", chain[i].ParentHash(), "prevnumber", chain[i-1].Number(), "prevhash", chain[i-1].Hash())

	    return 0, nil, nil, fmt.Errorf("non contiguous insert: item %d is #%d [%x…], item %d is #%d [%x…] (parent [%x…])", 
            i-1, chain[i-1].NumberU64(),chain[i-1].Hash().Bytes()[:4], i, chain[i].NumberU64(), chain[i].Hash().Bytes()[:4], 
            chain[i].ParentHash().Bytes()[:4])
	}
    }

    // Pre-checks passed, start the full block imports
    bc.wg.Add(1)
    defer bc.wg.Done()

    bc.chainmu.Lock()
    defer bc.chainmu.Unlock()

等所有檢驗通過以後,使用go語言的waitGroup.Add(1)來增加一個需要等待的goroutine,waitGroup.Done()來減1。在尚未Done()之前,具有waitGroup.wait()的函式就會停止,等待某處的waitGroup.Done()執行完才能執行。這裡waitGroup.wait()是在blockchain.Stop()函式裡,意味著如果在插入區塊的時候,突然有人執行Stop()函式,那麼必須要等insertChain()執行完。

part 2  驗證第n個區塊的header和body

1、準備資料:
    a. headers切片
    b. seals切片(檢查headers切片裡的每個索引的header是否需要檢查)
    c. abort通道和results通道(前者用來傳遞退出命令,後者用來傳遞檢查結果)

  headers := make([]*types.Header, len(chain))
  seals := make([]bool, len(chain))

  for i, block := range chain {
	headers[i] = block.Header()
	seals[i] = true
  }
  abort, results := bc.engine.VerifyHeaders(bc, headers, seals)
  defer close(abort)

這裡的bc.engine.VerifyHeaders()是一個非同步操作,所以結果返回到results通道里,這是go語言實現非同步的一種方式。

2、檢查區塊的正確性

  // Iterate over the blocks and insert when the verifier permits
  for i, block := range chain {
	// 如果inset被取消,則退出程序
	if atomic.LoadInt32(&bc.procInterrupt) == 1 {
        	log.Debug("Premature abort during blocks processing")
		break
	}
	// 如果區塊是bad block,直接return
	if BadHashes[block.Hash()] {
		bc.reportBlock(block, nil, ErrBlacklistedHash)
		return i, events, coalescedLogs, ErrBlacklistedHash
	}
	// Wait for the block's verification to complete
	bstart := time.Now()

	err := <-results
        // 如果results表明驗證通過,則進行第二步,驗證區塊
	if err == nil {
		err = bc.Validator().ValidateBody(block)
	}

atomic.LoadInt32(&bc.procInterrupt)是一個原子變數,在Stop()函式中會被設定成1。也就是說,如果有人呼叫Stop()函式,該值會被設定成1,然後這裡就會 break。

如果驗證失敗,要怎麼處理這些錯誤?

3. 處理驗證header和body所產生的錯誤

    a. 待插入的區塊已經是資料庫中存在的
    b. 待插入的是未來區塊
    c. 在資料庫中找不到待插入區塊的父區塊但未來區塊列表中能找到它的父區塊
    d. 待插入區塊是資料庫中的一個精簡分支

switch {
	case err == ErrKnownBlock:
	    // 待插入的區塊已經存在於資料庫,並且規範鏈頭區塊區塊號大於該區塊的區塊號,直接忽略
	    if bc.CurrentBlock().NumberU64() >= block.NumberU64() {
		stats.ignored++
		continue
            }

	case err == consensus.ErrFutureBlock:
	    // 待插入區塊的時間戳大於當前時間15秒,則認為是未來區塊
	    // 待插入區塊的時間戳大於當前時間30秒,則直接丟棄這個區塊
	    max := big.NewInt(time.Now().Unix() + maxTimeFutureBlocks)
	    if block.Time().Cmp(max) > 0 {
		return i, events, coalescedLogs, fmt.Errorf("future block: %v > %v", block.Time(), max)
	    }
	    bc.futureBlocks.Add(block.Hash(), block)
	    stats.queued++
	    continue

	case err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(block.ParentHash()):
        // 資料庫裡找不到這個區塊的父區塊,但未來待處理緩衝裡有父區塊,就將它放入未來待處理緩衝裡
	    bc.futureBlocks.Add(block.Hash(), block)
	    stats.queued++
	    continue

	case err == consensus.ErrPrunedAncestor:
	    // 父區塊是資料庫中的一條精簡分支區塊(精簡分支區塊就是一些沒有state root的區塊)
	    // 如果這個區塊的總難度小於等於規範鏈的總難度值,只將它寫入資料庫,不上規範鏈
	    currentBlock := bc.CurrentBlock()
	    localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
	    externTd := new(big.Int).Add(bc.GetTd(block.ParentHash(), block.NumberU64()-1), block.Difficulty())
	    if localTd.Cmp(externTd) > 0 {
		if err = bc.WriteBlockWithoutState(block, externTd); err != nil {
		    return i, events, coalescedLogs, err
		}
		continue
	    }
	    // 如果這個區塊總難度值大於或等於規範鏈總難度值,將這個精簡鏈插入規範鏈
	    var winner []*types.Block

	    parent := bc.GetBlock(block.ParentHash(), block.NumberU64()-1)
	    for !bc.HasState(parent.Root()) {
		winner = append(winner, parent)
		parent = bc.GetBlock(parent.ParentHash(), parent.NumberU64()-1)
	    }
	    for j := 0; j < len(winner)/2; j++ {
		winner[j], winner[len(winner)-1-j] = winner[len(winner)-1-j], winner[j]
	    }
	    // 遞迴呼叫insertChain(),不再需要錯誤處理,而是按順序執行交易完善精簡區塊的state
	    bc.chainmu.Unlock()
	    _, evs, logs, err := bc.insertChain(winner)
	    bc.chainmu.Lock()
	    events, coalescedLogs = evs, logs

	    if err != nil {
		return i, events, coalescedLogs, err
	    }

	 case err != nil:
            // 不屬於上面四種錯誤,無法處理,那就直接return
	    bc.reportBlock(block, nil, err)
  	    return i, events, coalescedLogs, err
}

4. 如果上面驗證均通過,則對待插入區塊的交易狀態進行驗證,否則退出
a. 從父區塊讀取狀態
b. 執行交易,更新狀態:bc.processor.Process(block, state, bc.vmConfig)
c. 對狀態進行驗證,看與header中的資料是否匹配

// Create a new statedb using the parent block and report an
// error if it fails.
var parent *types.Block
if i == 0 {
    parent = bc.GetBlock(block.ParentHash(), block.NumberU64()-1)
} else {
    parent = chain[i-1]
}
// 將這個block的父親的狀態樹從資料庫中讀取出來,並例項化成StateDB
state, err := state.New(parent.Root(), bc.stateCache)
if err != nil {
    return i, events, coalescedLogs, err
}
// Process block using the parent state as reference point.
// 使用當前blcok中的交易執行生成新的狀態(new state),獲取執行結果(收據列表和日誌列表)
// 如果執行成功,state會得到一個新的狀態
receipts, logs, usedGas, err := bc.processor.Process(block, state, bc.vmConfig)
if err != nil {
    bc.reportBlock(block, receipts, err)
    return i, events, coalescedLogs, err
}
// Validate the state using the default validator
// 驗證狀態:用上面執行交易後得到的全新狀態對比block中的狀態引數
err = bc.Validator().ValidateState(block, parent, state, receipts, usedGas)
if err != nil {
    bc.reportBlock(block, receipts, err)
    return i, events, coalescedLogs, err
}
proctime := time.Since(bstart)

5、如果驗證成功,呼叫WriteBlockWithState將這個區塊插入資料庫,然後寫入規範鏈,同時處理分叉問題
a. 寫入資料庫,判斷是規範鏈還是分叉:bc.WriteBlockWithState() —> status
b. 如果是規範鏈,輸出規範鏈日誌,新增ChainEvent事件
c. 如果是分叉,則輸出分叉日誌,新增ChainSideEvent事件

// Write the block to the chain and get the status.
// 將區塊寫入資料庫並獲得status
status, err := bc.WriteBlockWithState(block, receipts, state)
if err != nil {
    return i, events, coalescedLogs, err
}

switch status {
    case CanonStatTy: // 如果插入的是規範鏈
    log.Debug("Inserted new block", "number", block.Number(), "hash", block.Hash(), "uncles", len(block.Uncles()),
    "txs", len(block.Transactions()), "gas", block.GasUsed(), "elapsed", common.PrettyDuration(time.Since(bstart)))

    coalescedLogs = append(coalescedLogs, logs...)
    blockInsertTimer.UpdateSince(bstart)
    events = append(events, ChainEvent{block, block.Hash(), logs})
    lastCanon = block

    // Only count canonical blocks for GC processing time
    bc.gcproc += proctime

    case SideStatTy: // 如果插入的是分叉
    log.Debug("Inserted forked block", "number", block.Number(), "hash", block.Hash(), "diff", block.Difficulty(), "elapsed",
    common.PrettyDuration(time.Since(bstart)), "txs", len(block.Transactions()), "gas", block.GasUsed(), "uncles", len(block.Uncles()))

    blockInsertTimer.UpdateSince(bstart)
    events = append(events, ChainSideEvent{block})
}
stats.processed++
stats.usedGas += usedGas

cache, _ := bc.stateCache.TrieDB().Size()
stats.report(chain, i, cache)

part 3 跳出迴圈,檢驗插入的區塊是否是頭區塊,並報告事件

  // Append a single chain head event if we've progressed the chain
  if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() {
	events = append(events, ChainHeadEvent{lastCanon})
  }
  return 0, events, coalescedLogs, nil

函式最後返回了所有的事件(上鍊事件、分叉事件、頭區塊事件)以及所有的日誌。

思考一個問題,為什麼插入一個區塊需要返回這些事件和日誌?用於通知給訂閱了區塊插入事件的物件!

我們在blockchain類中曾經看到過這樣的定義:

rmLogsFeed    event.Feed
chainFeed     event.Feed
chainSideFeed event.Feed
chainHeadFeed event.Feed
logsFeed      event.Feed
scope         event.SubscriptionScope

我們通過event.Feed這個模組來監聽事件。以插入規範鏈事件為例,實際上,在我們通過rpc物件呼叫智慧合約的函式的時候,需要釋出一個交易,然後客戶端處於pending狀態等待上鍊。當包涵這個交易的區塊插入規範鏈的時候,rpc物件繫結的feed模組就會產看這個上鍊事件的區塊中是否包含剛才的交易,如果有則到相應的receipt中拿到呼叫函式的返回值,再包裝成rpc格式的資料返回客戶端。(Feed模組我們以後再看)

二、WriteBlockWithState()函式詳解

上面第5步的呼叫,即由BlockChain.insertChain()呼叫,主要功能:將一個區塊寫入資料庫和規範鏈

1、將區塊總難度插入資料庫
2、將區塊header和body寫入資料庫
3、新的狀態樹寫入資料庫
4、將區塊的收據列表寫入資料庫
5、將區塊寫入規範鏈(BlockChain.insert),同時處理分叉(BlockChain.reorg

func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) (status WriteStatus, err error) {
	bc.wg.Add(1)
	defer bc.wg.Done()

	// 獲取父區塊總難度
	ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
	if ptd == nil {
		return NonStatTy, consensus.ErrUnknownAncestor
	}
	// Make sure no inconsistent state is leaked during insertion
	bc.mu.Lock()
	defer bc.mu.Unlock()
        
        // 獲取當前本地規範鏈頭區塊的總難度
	currentBlock := bc.CurrentBlock()
	localTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64())
        // 計算待插入區塊的總難度
	externTd := new(big.Int).Add(block.Difficulty(), ptd)

	// step 1 將插入後的總難度寫入資料庫,'h'+ num + hash + 't'作為key
	if err := bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd); err != nil {
		return NonStatTy, err
	}
        // step 2 使用資料庫的Write介面將block(header,body)寫入資料庫
        // header的key為:'h' + num + hash
        // body的key為:'b' + num +hahs
	rawdb.WriteBlock(bc.db, block)

        // step 3 將新的狀態樹內容寫入資料庫
	root, err := state.Commit(bc.chainConfig.IsEIP158(block.Number()))
	if err != nil {
		return NonStatTy, err
	}
	triedb := bc.stateCache.TrieDB()

	// If we're running an archive node, always flush
	if bc.cacheConfig.Disabled {
		if err := triedb.Commit(root, false); err != nil {
			return NonStatTy, err
		}
	} else {
		// Full but not archive node, do proper garbage collection
		triedb.Reference(root, common.Hash{}) // metadata reference to keep trie alive
		bc.triegc.Push(root, -float32(block.NumberU64()))

		if current := block.NumberU64(); current > triesInMemory {
			// If we exceeded our memory allowance, flush matured singleton nodes to disk
			var (
				nodes, imgs = triedb.Size()
				limit       = common.StorageSize(bc.cacheConfig.TrieNodeLimit) * 1024 * 1024
			)
			if nodes > limit || imgs > 4*1024*1024 {
				triedb.Cap(limit - ethdb.IdealBatchSize)
			}
			// Find the next state trie we need to commit
			header := bc.GetHeaderByNumber(current - triesInMemory)
			chosen := header.Number.Uint64()

			// If we exceeded out time allowance, flush an entire trie to disk
			if bc.gcproc > bc.cacheConfig.TrieTimeLimit {
				// If we're exceeding limits but haven't reached a large enough memory gap,
				// warn the user that the system is becoming unstable.
				if chosen < lastWrite+triesInMemory && bc.gcproc >= 2*bc.cacheConfig.TrieTimeLimit {
					log.Info("State in memory for too long, committing", "time", bc.gcproc, "allowance", bc.cacheConfig.TrieTimeLimit, "optimum", float64(chosen-lastWrite)/triesInMemory)
				}
				// Flush an entire trie and restart the counters
				triedb.Commit(header.Root, true)
				lastWrite = chosen
				bc.gcproc = 0
			}
			// Garbage collect anything below our required write retention
			for !bc.triegc.Empty() {
				root, number := bc.triegc.Pop()
				if uint64(-number) > chosen {
					bc.triegc.Push(root, number)
					break
				}
				triedb.Dereference(root.(common.Hash))
			}
		}
	}

	// step 4 將收據內容寫入資料庫
        // 使用'r' + num + hash作為key,receipt列表的RLP編碼值作為value
	batch := bc.db.NewBatch()
	rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)

        // 將待插入的區塊寫入規範鏈
	// 如果待插入區塊的總難度等於本地規範鏈的總難度,但是區塊號小於或等於當前規範鏈的頭區塊號,均認為待插入的區塊所在分叉更有效,需要處理分叉並更新規範鏈
	reorg := externTd.Cmp(localTd) > 0
	currentBlock = bc.CurrentBlock()
	if !reorg && externTd.Cmp(localTd) == 0 {
		// Split same-difficulty blocks by number, then at random
		reorg = block.NumberU64() < currentBlock.NumberU64() || (block.NumberU64() == currentBlock.NumberU64() && mrand.Float64() < 0.5)
	}
         
        // 如果待插入區塊的總難度大於本地規範鏈的總難度,那Block必定要插入規範鏈
        // 如果待插入區塊的總難度小雨本地規範鏈的總難度,待插入區塊在另一個分叉上,不用插入規範鏈
	if reorg {
		// Reorganise the chain if the parent is not the head block
		if block.ParentHash() != currentBlock.Hash() {
			if err := bc.reorg(currentBlock, block); err != nil {
				return NonStatTy, err
			}
		}
		// 寫入交易查詢入口資訊
		rawdb.WriteTxLookupEntries(batch, block)
		rawdb.WritePreimages(batch, block.NumberU64(), state.Preimages())

		status = CanonStatTy
	} else {
		status = SideStatTy
	}
        
        // 將batch資料緩衝寫入資料庫,呼叫batch的Write()方法
	if err := batch.Write(); err != nil {
		return NonStatTy, err
	}

	// Set new head.
	if status == CanonStatTy {
                // 如果這個區塊可以插入本地規範鏈,就將它插入
                // step 1 更新規範鏈上區塊號對應的hash值
                // step 2 更新資料庫中的“LastBlock”的值
                // step 3 更新BlockChain的CurrentBlock
                // step 4 糾正HeaderChain的錯誤延伸
		bc.insert(block)
	}
        // 從futureBlock中刪除剛才插入的區塊
	bc.futureBlocks.Remove(block.Hash())
	return status, nil
}

這裡還有一個是需要注意的,就是如果我們的這個待插入區塊總難度大於規範鏈的總難度的,最理想的情況就是該區塊的父區塊就是規範鏈的最前一個區塊,這樣就直接將balock上鍊就可以了。但是有一種情況是,待插入的區塊的父區塊不是currentBlock,有可能它的父區塊是currentBlock之前的一個區塊,而總難度更大的原因是因為td(thisBlock) > td(currentBlock)。這就意味著,這個待新增到區塊是在另一個分叉上,而我們現在需要將該分叉視作新的規範鏈。這時候需要呼叫BlockChain.reorg()方法。

三、reorg()函式

reorg()函式的主要功能就是處理分叉:將原來的分叉鏈設定成規範鏈,將舊規範鏈上存在但新規範鏈上不存在的交易資訊找出來,刪除他們在資料庫中的查詢入口資訊。

原理:
1、找出新鏈和老鏈的共同祖先;
2、將新鏈插入到規範鏈中,同時收集插入到規範鏈中的所有交易;
3、找出待刪除列表中的那些不在待新增的交易列表的交易,並從資料庫中刪除它們的交易查詢入口
4、向外傳送區塊被重新組織的事件,以及日誌刪除的事件

執行這個方法的前提是:newBlock的總難度大於oldBlock,且newBlock的父區塊不是oldBlock。

func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {
	var (
		newChain    types.Blocks
		oldChain    types.Blocks
		commonBlock *types.Block
		deletedTxs  types.Transactions
		deletedLogs []*types.Log
		// collectLogs collects the logs that were generated during the
		// processing of the block that corresponds with the given hash.
		// These logs are later announced as deleted.
		collectLogs = func(hash common.Hash) {
			// Coalesce logs and set 'Removed'.
			number := bc.hc.GetBlockNumber(hash)
			if number == nil {
				return
			}
			receipts := rawdb.ReadReceipts(bc.db, hash, *number)
			for _, receipt := range receipts {
				for _, log := range receipt.Logs {
					del := *log
					del.Removed = true
					deletedLogs = append(deletedLogs, &del)
				}
			}
		}
	)

	// 第一步:找到新鏈和老鏈的共同祖先
	if oldBlock.NumberU64() > newBlock.NumberU64() {
		// 如果老分支比心分支區塊高度高,則減少老分支直到與新分支高度相同
                // 並收集老分支上的交易和日誌
		for ; oldBlock != nil && oldBlock.NumberU64() != newBlock.NumberU64(); oldBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1) {
			oldChain = append(oldChain, oldBlock)
			deletedTxs = append(deletedTxs, oldBlock.Transactions()...)

			collectLogs(oldBlock.Hash())
		}
	} else {
		// 如果新分支高於老分支,則減少新分支
		for ; newBlock != nil && newBlock.NumberU64() != oldBlock.NumberU64(); newBlock = bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1) {
			newChain = append(newChain, newBlock)
		}
	}
	if oldBlock == nil {
		return fmt.Errorf("Invalid old chain")
	}
	if newBlock == nil {
		return fmt.Errorf("Invalid new chain")
	}
 
        // 等到共同高度後,去找到共同祖先(共同回退),繼續收集日誌和事件
	for {
		if oldBlock.Hash() == newBlock.Hash() {
			commonBlock = oldBlock
			break
		}

		oldChain = append(oldChain, oldBlock)
		newChain = append(newChain, newBlock)
		deletedTxs = append(deletedTxs, oldBlock.Transactions()...)
		collectLogs(oldBlock.Hash())

		oldBlock, newBlock = bc.GetBlock(oldBlock.ParentHash(), oldBlock.NumberU64()-1), bc.GetBlock(newBlock.ParentHash(), newBlock.NumberU64()-1)
		if oldBlock == nil {
			return fmt.Errorf("Invalid old chain")
		}
		if newBlock == nil {
			return fmt.Errorf("Invalid new chain")
		}
	}

	// Ensure the user sees large reorgs
        // 列印規則(不影響核心功能)
	if len(oldChain) > 0 && len(newChain) > 0 {
		logFn := log.Debug
		if len(oldChain) > 63 {
			logFn = log.Warn
		}
		logFn("Chain split detected", "number", commonBlock.Number(), "hash", commonBlock.Hash(),
			"drop", len(oldChain), "dropfrom", oldChain[0].Hash(), "add", len(newChain), "addfrom", newChain[0].Hash())
	} else {
		log.Error("Impossible reorg, please file an issue", "oldnum", oldBlock.Number(), "oldhash", oldBlock.Hash(), "newnum", newBlock.Number(), "newhash", newBlock.Hash())
	}

	// 第二步:將新鏈插入到規範鏈中,同時收集插入到規範鏈中的所有交易
	for i := len(newChain) - 1; i >= 0; i-- {
		// insert the block in the canonical way, re-writing history
		bc.insert(newChain[i])
		// 把所有新分支的區塊交易查詢入口插入資料庫
		rawdb.WriteTxLookupEntries(bc.db, newChain[i])
		addedTxs = append(addedTxs, newChain[i].Transactions()...)
	}
	// 第三步:找出待刪除列表和待新增列表中的差異,刪除那些不在新鏈上的交易在資料庫中的查詢入口
	diff := types.TxDifference(deletedTxs, addedTxs)
	// When transactions get deleted from the database that means the
	// receipts that were created in the fork must also be deleted
	batch := bc.db.NewBatch()
	for _, tx := range diff {
		rawdb.DeleteTxLookupEntry(batch, tx.Hash())
	}
	batch.Write()
      
        // 第四步:向外傳送區塊被重新組織的事件,以及日誌刪除事件
	if len(deletedLogs) > 0 {
		go bc.rmLogsFeed.Send(RemovedLogsEvent{deletedLogs})
	}
	if len(oldChain) > 0 {
		go func() {
			for _, block := range oldChain {
				bc.chainSideFeed.Send(ChainSideEvent{Block: block})
			}
		}()
	}

	return nil
}

四、insert()函式

func (bc *BlockChain) insert(block *types.Block) {
	// 讀出待插入blcok的區塊號對應在規範鏈上的區塊hash值,與block的hash值對比看是否相等,即判斷HeaderChain延伸是否正確,如果不正確,後面矯正
	updateHeads := rawdb.ReadCanonicalHash(bc.db, block.NumberU64()) != block.Hash()

	// 更新規範鏈上block.number的hash值為block.hash
	rawdb.WriteCanonicalHash(bc.db, block.Hash(), block.NumberU64())
        // 正式寫入規範鏈,更新資料庫中的LastBlock
	rawdb.WriteHeadBlockHash(bc.db, block.Hash())
        // 將BlockChain中的currentBlock替換成blcok
	bc.currentBlock.Store(block)

	// If the block is better than our head or is on a different chain, force update heads
	if updateHeads {
                // 將headerChain的頭設定成待插入規範鏈的區塊頭,BlockChain和HeaderChain在此齊頭並進
		bc.hc.SetCurrentHeader(block.Header())
		rawdb.WriteHeadFastBlockHash(bc.db, block.Hash())

		bc.currentFastBlock.Store(block)
	}
}

五、總結:

區塊鏈的插入新區塊的流程: