一、BlockChain的insertChain方法

1.1 前言

本章节介绍BlockChain模块插入一个新区块的流程。一个新区块的来源有两种可能性,第一种可能性是本节点挖矿成功,要调用BlockChain模块向本地区块链上插入,第二种可能性是节点从网络上的其他节点收到一个区块,调用BlockChain模块插入本地区块链。将一个区块插入区块链是调用BlockChain的insertChain函数,本章节详细介绍insertChain流程。

1.2 insertChain函数

inertChain函数是功能是将一组区块批量插入区块链,inesrtChain函数会检查这一组区块是否是首尾相接。检查无误后会校验区块头和区块体,然后还需要校验状态是不是正确。最后将区块插入区块链,需要注意的是能插入区块链不一定能插入规范链,在插入的时候会具体判断是否能插入规范链,如果不能插入规范链就是一条分叉。

func (bc *BlockChain) insertChain(chain types.Blocks) (int, []interface{}, []*types.Log, error) {//如果传入的区块切片上长度为空,直接返回
if len(chain) == 0 {return 0, nil, nil, nil
}
//1确保这组区块的是首尾相接的,并且区块号连续递增,如果不是则直接返回
// Do a sanity check that the provided chain is actually ordered and linked
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 insertionlog.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()// A queued approach to delivering events. This is generally
// faster than direct delivery and requires much less mutex
// acquiring.
var (stats         = insertStats{startTime: mclock.Now()}events        = make([]interface{}, 0, len(chain))lastCanon     *types.BlockcoalescedLogs []*types.Log
)
// Start the parallel header verifier
headers := make([]*types.Header, len(chain))
seals := make([]bool, len(chain))
//2并行验证这组区块的区块头
for i, block := range chain {headers[i] = block.Header()seals[i] = true
}
abort, results := bc.engine.VerifyHeaders(bc, headers, seals)
defer close(abort)// Start a parallel signature recovery (signer will fluke on fork transition, minimal perf loss)senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number
()), chain)// Iterate over the blocks and insert when the verifier permits
//3验证这组区块的区块体
for i, block := range chain {// If the chain is terminating, stop processing blocks//如果有人打断验证直接返回if atomic.LoadInt32(&bc.procInterrupt) == 1 {log.Debug("Premature abort during blocks processing")break}
//如果这个区块在bad区块列表里,说明这个区块不能插入区块链,直接返回// If the header is a banned one, straight out abortif BadHashes[block.Hash()] {bc.reportBlock(block, nil, ErrBlacklistedHash)return i, events, coalescedLogs, ErrBlacklistedHash}// Wait for the block's verification to completebstart := time.Now()//4接收区块头的验证结果 err := <-resultsif err == nil {//如果区块头没有问题,则验证区块体err = bc.Validator().ValidateBody(block)}//5 处理区块头或区块体的验证错误switch {case err == ErrKnownBlock:// Block and state both already known. However if the current block is below// this number we did a rollback and we should reimport it nonetheless.//6 待插入的区块在数据库中已经存在,如果当前的区块链的头区块高度比待插入的区块大,则直接忽略这个区块,否则继续向下执行插入流程if bc.CurrentBlock().NumberU64() >= block.NumberU64() {stats.ignored++continue}case err == consensus.ErrFutureBlock://7如果待插入区块是一个未来区块(大于当前时间15秒),则判断是否是小于30s,如果是则将区块放入futureBlocks列表// Allow up to MaxFuture second in the future blocks. If this limit is exceeded// the chain is discarded and processed at a later time if given.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++continuecase err == consensus.ErrUnknownAncestor && bc.futureBlocks.Contains(block.ParentHash())://8数据库里面找不到这个区块的父亲区块, 并且未来待处理区块缓冲里面有它的父区块, 就将它放入到futureBlocks列表中bc.futureBlocks.Add(block.Hash(), block)stats.queued++continuecase err == consensus.ErrPrunedAncestor://9如果待插入区块的祖先是一个精简分支(所谓精简分支就是一条分叉,只有区块头和区块体,但是没有状态),看这个区块的总难度是否大于本地规范链头区块的总难度,如果大于,则将这条精简分支上的所有没有状态的区块重新做一次插入,插入的过程会产生状态,并将这条精简分支升级为规范链,否则如果不大于则将这个区块不带状态的情况下插入这条精简分支。// Block competing with the canonical chain, store in the db, but don't process// until the competitor TD goes above the canonical TDcurrentBlock := 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}// Competitor chain beat canonical, gather all blocks from the common ancestorvar winner []*types.Blockparent := 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]}// Import all the pruned blocks to make the state availablebc.chainmu.Unlock()_, evs, logs, err := bc.insertChain(winner)bc.chainmu.Lock()events, coalescedLogs = evs, logsif err != nil {return i, events, coalescedLogs, err}case err != nil://无法处理的错误,直接返回bc.reportBlock(block, nil, err)return i, events, coalescedLogs, err}//10 验证区块的状态// Create a new statedb using the parent block and report an// error if it fails.var parent *types.Blockif i == 0 {parent = bc.GetBlock(block.ParentHash(), block.NumberU64()-1)} else {parent = chain[i-1]}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.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 validatorerr = 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)// 11 调用WriteBlockWithState将区块写入区块链,返回值如果是CanonStatTy,表示写入了规范链,如果是SideStatTy表示写入了分叉// Write the block to the chain and get the 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 timebc.gcproc += proctimecase 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 += usedGascache, _ := bc.stateCache.TrieDB().Size()stats.report(chain, i, cache)
}
// 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})
}
//返回插入过程中的事件,BlockChain会将这个事件post出去,让其他监听的模块可以获知这些事件
return 0, events, coalescedLogs, nil
}

上面代码的第2步,使用了bc.engine.VerifyHeaders函数去验证区块的区块头,这个函数传入了两个切片:

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

这两个切片的长度是相同的,第一个切片传入的是这组区块的区块头 ,第二组切片指定要验证哪些区块头,如果某个区块要验证,则在seals切片上对应位置置为true。我们可以看到代码里面把所有位置都置为了true,表示要验证headers切片里的所有区块头。

for i, block := range chain {headers[i] = block.Header()seals[i] = true
}

另外bc.engine.VerifyHeaders是异步检查,调完之后会直接返回,继续往下执行。它会返回两个管道abort和result,第一个管道可以命令VerifyHeaders函数停止验证,第二个管道是可以从其中等待验证结果,VerifyHeaders函数会保证验证结果返回的顺序和我们传入的headers切片的顺序相同。所以第3步验证区块头的时候重新用for循环遍历chain切片时,for循环的第一次执行,result管道返回的必然是chain中第一个区块的区块头的验证结果。

第6步处理ErrFutureBlock错误返回值时,如果待插入的区块在数据库中已经存在,说明它是有可能是一个分叉,如果它的区块高度比当前规范链的头区块要大,那么就重新在插入一下,因为有可能这个区块的所在的分叉的总难度比当前规范链大,如果真是这样的话,需要把这条分叉升级为规范链,重新插入的过程会检查是否是规范链,如果是就会升级。

第7步,当收到一个区块它的时间戳大于当前时间15秒,小于30秒,节点不会将这个区块丢弃, 而是将这个区块放入到futureBlocks列表,我们在上一章节讲到NewBlockChain函数最后会启动一个go程定时来检查这些区块能不能插入到区块链,如果能插,就会再次调用insertChain来插入。

第 10步是验证区块的状态,验证区块的状态流程是基于父区块的世界状态去执行待插入区块的里的所有交易,生成新的世界状态,然后调用bc.Validator().ValidateState去验证新的状态是不是正确,其中重要的一个环节就是比较新生成的状态树树根和区块头中的状态树树根是否相同。

第11步调用WriteBlockWithState将区块写入区块链,这个函数会去判断这个区块写入的是一个分叉还是规范链,当然这个插入的区块有可能将一个原来的分叉升级为规范链,原来的规范链变成一条分叉。

1.3 总结

本章节主要介绍insertChain的流程,从上面的分析可以看出insertChain函数里面主要是实现了对区块的校验,包括区块头和区块体,校验通过之后会调用WriteBlockWithState函数将区块写入区块链,而真正写入的过程在WriteBlockWithState中。下一章节我们分析WriteBlockWithState函数流程。

-END-

转载于:https://www.cnblogs.com/efish/p/eth-source-analyze.html

以太坊源码分析——BlockChain相关推荐

  1. php区块链以太坊,兄弟连区块链教程以太坊源码分析CMD深入分析(一)

    兄弟连区块链教程以太坊源码分析CMD深入分析. cmd包分析 cmd下面总共有13个子包,除了util包之外,每个子包都有一个主函数,每个主函数的init方法中都定义了该主函数支持的命令,如 geth ...

  2. 3 v4 中心节点固定_死磕以太坊源码分析之p2p节点发现

    死磕以太坊源码分析之p2p节点发现 在阅读节点发现源码之前必须要理解kadmilia算法,可以参考:KAD算法详解. 节点发现概述 节点发现,使本地节点得知其他节点的信息,进而加入到p2p网络中. 以 ...

  3. 以太坊源码分析-交易

    以太坊源码分析-交易 机理 先说一点区块链转账的基本概念和流程 用户输入转账的地址和转入的地址和转出的金额 系统通过转出的地址的私钥对转账信息进行签名(用于证明这 笔交易确实有本人进行) 系统对交易信 ...

  4. kademlia java_死磕以太坊源码分析之Kademlia算法

    死磕以太坊源码分析之Kademlia算法 KAD 算法概述 Kademlia是一种点对点分布式哈希表(DHT),它在容易出错的环境中也具有可证明的一致性和性能.使用一种基于异或指标的拓扑结构来路由查询 ...

  5. go-ethereum-code-analysis 以太坊源码分析

    分析go-ethereum的过程,我希望从依赖比较少的底层技术组件开始,慢慢深入到核心逻辑. 目录 go-ethereum代码阅读环境搭建 以太坊黄皮书 符号索引 rlp源码解析 trie源码分析 e ...

  6. 以太坊源码分析(2)——以太坊APP对象

    前言 从这一节开始,我将开始以太坊代码全覆盖讲解,讲解的流程是: 以太坊程序入口 基本框架 以太坊协议 发送一笔交易后发生了什么 启动挖矿 以太坊共识 p2p 网络 阅读本系列文章,将默认读者具备一定 ...

  7. 以太坊源码分析之随心笔记

    链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载. 以太坊索引 table.go 定期随机选取一些节点找他们要他们的节点,放到本地,也就是一个随机找节点的table 里头的 ...

  8. 以太坊源码分析:fetcher模块和区块传播

    前言 这篇文章从区块传播策略入手,介绍新区块是如何传播到远端节点,以及新区块加入到远端节点本地链的过程,同时会介绍fetcher模块,fetcher的功能是处理Peer通知的区块信息.在介绍过程中,还 ...

  9. 46.以太坊源码分析(46)p2p-peer.go源码分析

    nat是网络地址转换的意思. 这部分的源码比较独立而且单一,这里就暂时不分析了. 大家了解基本的功能就行了. nat下面有upnp和pmp两种网络协议. upnp的应用场景(pmp是和upnp类似的协 ...

最新文章

  1. Linux :debian(ubuntu)下安装和使用haskell
  2. Java selenium 获取表格数据_Selenium Webdriver如何通过获取Excel输入从表中选择记录...
  3. 用户控件如何控制ASPX页面的控件
  4. 测试低频噪音软件,设计制作并验证0.1Hz10Hz超低频微弱噪音检测放大器要点
  5. jquery中的ready函数与window.onload谁先执行
  6. ten sentences(1-10)
  7. 京佳名师点评:2008年2月23日海关面试题 京佳公务员考试培训学校
  8. 合同管理系统主要增加了安全性,对于大型企业作用重大
  9. 大华流媒体服务器连接显示器,如何从海康平台上拉流接入RTSP安防网络摄像头/海康大华硬盘录像机网页无插件直播流媒体服务器EasyNVR?...
  10. avc 转 hevc
  11. C#nameof用法
  12. 7寸显示器 树莓派4b_树莓派7寸触摸屏安装指南
  13. 谷歌地图 地点搜索(模糊搜索)
  14. puts与fputs的区别
  15. python相关性分析函数_python实现相关性分析
  16. Ffmpeg音频转码 卡顿(MP2转AAC)
  17. 计算机应用基础与实训教程word2003文字处理软件 教学目标,计算机基础教学计划多篇...
  18. 【opencv】18、视频操作
  19. 在群晖中批量删除重复文件
  20. 联想电脑一开机出现Checking Media Presence……

热门文章

  1. step1.day11 C语言基础练习之指针和二级指针
  2. 随机生成数组函数+nth-element函数
  3. 移动硬盘磁盘结构损坏且无法读取要怎样办啊
  4. 未解决-hive之drop 表分区失败
  5. 功能强大的滚动播放插件JQ-Slide
  6. oracle 无法解析指定的连接标识符
  7. 谷歌也被逼出局域网了 baidu从此天下无敌
  8. Docker图形化管理工具之Portainer
  9. 探访物联网安全新边界 文印保护成思维盲区
  10. eclipse中查看android源码