Bytom侧链Vapor源代码分析节点的解块过程是怎样的?

技术Bytom侧链Vapor源码分析节点出块过程是怎样的Bytom侧链Vapor源码分析节点出块过程是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。小

比托姆侧链蒸汽源码分析节点出块过程是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。

小编将从蒸汽节点的创建开始,进而拓展讲解蒸汽节点出块过程中所涉及的源码。

下面对蒸汽稍加介绍蒸汽。是目前国内主流公链比托姆的高性能侧链,是从比托姆主链中发展出来的一条独立的高性能侧链蒸汽。是平台最重要的区块链基础设施之一,目前采用残疾人组织的共识算法,具有高性能、高安全、可扩展等特点,用于搭建规模化的商业应用。

Vapor节点创建及出块模块的启动

蒸汽入口函数:

蒸气/cmd/蒸气/main.go

func main(){ 0

cmd:=cli .PrepareBaseCmd(命令RootCmd,' TM ',os .ExpandEnv(配置. DefaultDataDir()))

cmd .执行()

}传入参数结节后会调用runNode函数并新建一个节点。

蒸汽/cmd/蒸汽/命令/run_node.go

funcrunNode(cmd*cobra .命令,args[]字符串)错误{

开始时间:=时间。现在()

setLogLevel(配置LogLevel)

//创建开始节点

n:=节点。新节点(配置)

……

}蒸汽节点的结构:

蒸汽/节点/节点。开始

typeNodestruct{

cmn .BaseService

配置*cfg .配置

事件调度程序*事件。分配器

同步管理器*网络同步.同步管理器

钱包*w。钱包

接入令牌*接入令牌.信用商店

notificationMgr*websocket .WSNotificationManager

api*api .应用程序接口

链条*协议。链子

区块提议者*区块提议者。区块提议者

miningEnablebool

}其中与出块和共识相关的是区块提议者字段

新建节点的部分源码

蒸汽/节点/节点。开始

funcNewNode(配置*cfg .配置)*节点{

//……

node:=Node{

eventDispatcher:dispatcher,

config:config,

同步管理器:syncManager,

访问令牌:访问令牌,

wallet:wallet钱包钱包,

链条:链条,

miningEnable:config .采矿,

通知管理器:通知管理器,

}

node.blo

ckProposer = blockproposer.NewBlockProposer(chain, accounts, txPool, dispatcher)
node.BaseService = *cmn.NewBaseService(nil, "Node", node)
return node
}

从这可以看到node.blockProposer本质上是一个vapor的block生成器,实际控制node启动出块的模块是vapor/proposal/blockproposer/blockproposer.go中的:

func (b *BlockProposer) Start() {
	b.Lock()
	defer b.Unlock()

	// Nothing to do if the miner is already running
	if b.started {
		return
	}

	b.quit = make(chan struct{})
	go b.generateBlocks() //出块功能的关键模块

	b.started = true
	log.Infof("block proposer started")
}

出块模块可以通过api启动

vapor/api/miner.go

func (a *API) startMining() Response {
	a.blockProposer.Start()
	if !a.IsMining() {
		return NewErrorResponse(errors.New("Failed to start mining"))
	}
	return NewSuccessResponse("")
}

以上讲解的是节点创建和出块模块启动所涉及的源码。

generateBlocks()函数开始,将要讲解是Vapor出块过程的具体源码。

Vapor的出块机制

Vapor采用的是DPoS的共识机制进行出块。DPoS是由被社区选举的可信帐户(受托人,得票数排行前10位)来创建区块。为了成为正式受托人,用户要去社区拉票,获得足够多用户的信任。用户根据自己持有的加密货币数量占总量的百分比来投票。DPoS机制类似于股份制公司,普通股民进不了董事会,要投票选举代表(受托人)代他们做决策。在讲解Vapor的出块流程之前,要先了解Vapor在DPoS的参数设定。

DPoS的参数信息位于 vapor/consensus/general.go

type DPOSConfig struct {
	NumOfConsensusNode      int64
	BlockNumEachNode        uint64
	RoundVoteBlockNums      uint64
	MinConsensusNodeVoteNum uint64
	MinVoteOutputAmount     uint64
	BlockTimeInterval       uint64
	MaxTimeOffsetMs         uint64
}

接下来对参数进行具体解释

  • NumOfConsensusNode是DPOS中共识节点的数量,Vapor中设置为10,通过投票选出十个负责出块的共识节点。

  • BlockNumEachNode是每个共识节点连续出块的数量,Vapor中设置为12。

  • RoundVoteBlockNums为每轮投票的出块数,Vapor中设置为1200,也就是说每轮投票产生的共识节点会负责出块1200个。

  • MinConsensusNodeVoteNum是成为共识节点要求的最小BTM数量(单位为neu,一亿分之一BTM),Vapor中设置为100000000000000,也就是说一个节点想成为共识节点,账户中至少需要存有100万BTM。

  • MinVoteOutputAmoun为节点进行投票所要求的最小BTM 数量(单位为neu),Vapor中设置为100000000,节点想要参与投票,账户中需要1BTM

  • BlockTimeInterval为最短出块时间间隔,Vapor每间隔0.5秒出一个块。

  • MaxTimeOffsetMs为块时间允许比当前时间提前的最大秒数,在Vapor中设置为2秒。

讲完DPoS的参数设置后,就可以看看Vapor上出块的核心代码 generateBlocks

vapor/proposal/blockproposer/blockproposer.go

func (b *BlockProposer) generateBlocks() {
	xpub := config.CommonConfig.PrivateKey().XPub()
	xpubStr := hex.EncodeToString(xpub[:])
	ticker := time.NewTicker(time.Duration(consensus.ActiveNetParams.BlockTimeInterval) * time.Millisecond)
	defer ticker.Stop()

	for {
		select {
		case <-b.quit:
			return
		case <-ticker.C:
		}
		//1
		bestBlockHeader := b.chain.BestBlockHeader()
		bestBlockHash := bestBlockHeader.Hash()
		now := uint64(time.Now().UnixNano() / 1e6)
		base := now
		if now < bestBlockHeader.Timestamp {
			base = bestBlockHeader.Timestamp
		}
		minTimeToNextBlock := consensus.ActiveNetParams.BlockTimeInterval - base%consensus.ActiveNetParams.BlockTimeInterval
		nextBlockTime := base + minTimeToNextBlock
		if (nextBlockTime - now) < consensus.ActiveNetParams.BlockTimeInterval/10 {
			nextBlockTime += consensus.ActiveNetParams.BlockTimeInterval
		}
		
        //2
		blocker, err := b.chain.GetBlocker(&bestBlockHash, nextBlockTime)
		……
		if xpubStr != blocker {
			continue
		}
		
        
        //3
		warnDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*warnTimeNum/warnTimeDenom) * time.Millisecond
		criticalDuration := time.Duration(consensus.ActiveNetParams.BlockTimeInterval*criticalTimeNum/criticalTimeDenom) * time.Millisecond
		block, err := proposal.NewBlockTemplate(b.chain, b.accountManager, nextBlockTime, warnDuration, criticalDuration)
		……
		//4
		isOrphan, err := b.chain.ProcessBlock(block)
		……
        //5
        log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "isOrphan": isOrphan, "tx": len(block.Transactions)}).Info("proposer processed block")

		if err = b.eventDispatcher.Post(event.NewProposedBlockEvent{Block: *block}); err != nil {
			log.WithFields(log.Fields{"module": logModule, "height": block.BlockHeader.Height, "error": err}).Error("proposer fail on post block")
		}
	}
}

代码经过精简,省略了一些无关紧要的部分,并将重要的部分,分为5个模块。

  1. 计算并调整出块的时间

  2. 通过GetBlocker 获取顺序下一个block的公钥,并与当前块比对,判断当前块的出块顺序是否合法。

  3. 通过b.chain.ProcessBlock根据模板生成了一个block。

  4. 通过chain.ProcessBlock(block)尝试把block加工处理后加到本机持有的区块链上。

  5. 使用logrus框架记录新的块,并像网络中广播。

b.chain.GetBlocker

针对generateBlocks()中几个重要的模块进行拆分讲解。

vapor/protocol/consensus_node_manager.go

GetBlocker()传入当前高度块的哈希和下一个块的出块时间。

// 返回一个特定时间戳的Blocker
func (c *Chain) GetBlocker(prevBlockHash *bc.Hash, timeStamp uint64) (string, error) {
    consensusNodeMap, err := c.getConsensusNodes(prevBlockHash)
	//……

	prevVoteRoundLastBlock, err := c.getPrevRoundLastBlock(prevBlockHash)
	//……
    
	startTimestamp := prevVoteRoundLastBlock.Timestamp + consensus.ActiveNetParams.BlockTimeInterval
	//获取order,xpub为公钥
	order := getBlockerOrder(startTimestamp, timeStamp, uint64(len(consensusNodeMap)))
	for xPub, consensusNode := range consensusNodeMap {
		if consensusNode.Order == order {
			return xPub, nil
		}
	}
	//……
}
  • 通过调用c.getConsensusNodes()获得一个存储共识节点的Map。

  • 获取上一轮投票的最后一个块,在加上最短出块时间间隔,计算得到这一轮的开始时间戳。

  • 调用getBlockerOrder,通过开始时间戳和当前要出块的时间戳计算出这个时间点出块的order。

  • 最后比对consensusNodeMapconsensusNode.Order,并返回公钥。

这个模块是为了找出当前时间戳对应出块的共识节点,并返回节点的公钥。因为DPoS中出块的节点和顺序必须是固定的,而使用generateBlocks()模块尝试出块的共识节点不一定是当前时间的合法出块节点,因此需要本模块通过对比公钥进行节点资格的验证。

proposal.NewBlockTemplate

vapor/proposal/proposal.go

func NewBlockTemplate(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) (*types.Block, error) {
	builder := newBlockBuilder(chain, accountManager, timestamp, warnDuration, criticalDuration)
	return builder.build()
}
func newBlockBuilder(chain *protocol.Chain, accountManager *account.Manager, timestamp uint64, warnDuration, criticalDuration time.Duration) *blockBuilder {
	preBlockHeader := chain.BestBlockHeader()
	block := &types.Block{
		BlockHeader: types.BlockHeader{
			Version:           1,
			Height:            preBlockHeader.Height + 1,
			PreviousBlockHash: preBlockHeader.Hash(),
			Timestamp:         timestamp,
			BlockCommitment:   types.BlockCommitment{},
			BlockWitness:      types.BlockWitness{Witness: make([][]byte, consensus.ActiveNetParams.NumOfConsensusNode)},
		},
	}

	builder := &blockBuilder{
		chain:             chain,
		accountManager:    accountManager,
		block:             block,
		txStatus:          bc.NewTransactionStatus(),
		utxoView:          state.NewUtxoViewpoint(),
		warnTimeoutCh:     time.After(warnDuration),
		criticalTimeoutCh: time.After(criticalDuration),
		gasLeft:           int64(consensus.ActiveNetParams.MaxBlockGas),
		timeoutStatus:     timeoutOk,
	}
	return builder
}

在Vapor上每个区块有区块头和区块的主体,区块头中包含版本号、高度、上一区块的hash、时间戳等等,主体包括区块链的引用模块、账户管理器、区块头、Transaction状态(版本号和验证状态)、utxo视图等。这一部分的目的是将,区块的各种信息通过模板包装成一个block交给后面的ProcessBlock(block)加工处理。

b.chain.ProcessBlock

vapor/protocol/block.go

func (c *Chain) ProcessBlock(block *types.Block) (bool, error) {
	reply := make(chan processBlockResponse, 1)
	c.processBlockCh <- &processBlockMsg{block: block, reply: reply}
	response := <-reply
	return response.isOrphan, response.err
}
func (c *Chain) blockProcesser() {
	for msg := range c.processBlockCh {
		isOrphan, err := c.processBlock(msg.block)
		msg.reply <- processBlockResponse{isOrphan: isOrphan, err: err}
	}
}

很显然,这只是链更新的入口,block数据通过processBlockMsg结构传入了c.processBlockCh这个管道。随后数据通过blockProcesser()处理后存入了msg.reply管道,而最后处理这个block的是processBlock()函数:

func (c *Chain) processBlock(block *types.Block) (bool, error) {
	//1
	blockHash := block.Hash()
	if c.BlockExist(&blockHash) {
		log.WithFields(log.Fields{"module": logModule, "hash": blockHash.String(), "height": block.Height}).Debug("block has been processed")
		return c.orphanManage.BlockExist(&blockHash), nil
	}
	//2
	c.markTransactions(block.Transactions...)
	//3
	if _, err := c.store.GetBlockHeader(&block.PreviousBlockHash); err != nil {
		c.orphanManage.Add(block)
		return true, nil
	}
	//4
	if err := c.saveBlock(block); err != nil {
		return false, err
	}
	
	bestBlock := c.saveSubBlock(block)
	bestBlockHeader := &bestBlock.BlockHeader

	c.cond.L.Lock()
	defer c.cond.L.Unlock()
	//5
	if bestBlockHeader.PreviousBlockHash == c.bestBlockHeader.Hash() {
		log.WithFields(log.Fields{"module": logModule}).Debug("append block to the end of mainchain")
		return false, c.connectBlock(bestBlock)
	}
	//6
	if bestBlockHeader.Height > c.bestBlockHeader.Height {
		log.WithFields(log.Fields{"module": logModule}).Debug("start to reorganize chain")
		return false, c.reorganizeChain(bestBlockHeader)
	}
	return false, nil
}

processBlock()函数返回的bool表示的是block是否为孤块。

  1. 通过block的hash判断这个block是否已经在链上。若已存在,则报错并返回false(表示该block不是孤块)

  2. 将block中的Transactions标记,后续会调用c.knownTxs.Add()将Transactions加入到Transaction集合中。

  3. 判断是否为孤块,如果是,则调用孤块管理部分的模块处理并返回true。

  4. 保存block,在saveBlock()中会对签名和区块进行验证。

  5. bestBlockHeader.PreviousBlockHash == c.bestBlockHeader.Hash()的情况说明一切正常,新block被添加到链的末端。

  6. bestBlockHeader.Height > c.bestBlockHeader.Height 表示出现了分叉,需要回滚。

看完上述内容,你们掌握Bytom侧链Vapor源码分析节点出块过程是怎样的的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!

内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/151916.html

(0)

相关推荐

  • 什么是Inception以及GoogleNet结构

    技术什么是Inception以及GoogleNet结构什么是Inception以及GoogleNet结构,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。g

    攻略 2021年11月23日
  • 基础js动画

    技术基础js动画 基础js动画获取元素的样式
    getStyle函数
    此函数返回的是一个字符串,需要调用 parseInt() 或者 parseFloat() 将返回的结果转换为数字值。
    动画分类
    1.简

    礼包 2021年11月22日
  • 三戒是哪三戒,谁来回答下哪三戒哪四律!

    技术三戒是哪三戒,谁来回答下哪三戒哪四律!君子有三戒 孔子,在生活上主张,君子有三条戒规:少年时戒美色;壮年时戒殴斗;老年时戒贪图三戒是哪三戒。“君子有三戒,少之时,血气未足,戒之在色;及其壮也,血气方刚,戒之在斗;及其

    生活 2021年10月22日
  • html中em是什么单位(em值是什么意思)

    技术css3中em指的是什么单位小编给大家分享一下css3中em指的是什么单位,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧! 在css3中,em是一个相对长度单位,相对于当前

    攻略 2021年12月19日
  • 送闺蜜结婚礼物排行榜,闺蜜结婚我送什么礼物比较好呢

    技术送闺蜜结婚礼物排行榜,闺蜜结婚我送什么礼物比较好呢从某种意义上而言送闺蜜结婚礼物排行榜,创意的多少取决于你为准备那礼物花了多少心思。 所以了,你可以自制礼物,比如个性电子相册、幸运星等等。当然,可以送买的礼物,但需要

    生活 2021年10月21日
  • centos7 安装包安装mysql8.0.18 rpm-bundle

    技术centos7 安装包安装mysql8.0.18 rpm-bundle centos7 安装包安装mysql8.0.18 rpm-bundle一、资源地址
    下载地址https://dev.mysql

    礼包 2021年11月4日