比托姆侧链蒸汽源码分析节点出块过程是怎样的,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。
小编将从蒸汽节点的创建开始,进而拓展讲解蒸汽节点出块过程中所涉及的源码。
下面对蒸汽稍加介绍蒸汽。是目前国内主流公链比托姆的高性能侧链,是从比托姆主链中发展出来的一条独立的高性能侧链蒸汽。是平台最重要的区块链基础设施之一,目前采用残疾人组织的共识算法,具有高性能、高安全、可扩展等特点,用于搭建规模化的商业应用。
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个模块。
-
计算并调整出块的时间
-
通过
GetBlocker
获取顺序下一个block的公钥,并与当前块比对,判断当前块的出块顺序是否合法。 -
通过
b.chain.ProcessBlock
根据模板生成了一个block。 -
通过
chain.ProcessBlock(block)
尝试把block加工处理后加到本机持有的区块链上。 -
使用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。 -
最后比对
consensusNodeMap
中consensusNode.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是否为孤块。
-
通过block的hash判断这个block是否已经在链上。若已存在,则报错并返回false(表示该block不是孤块)
-
将block中的Transactions标记,后续会调用
c.knownTxs.Add()
将Transactions加入到Transaction集合中。 -
判断是否为孤块,如果是,则调用孤块管理部分的模块处理并返回true。
-
保存block,在
saveBlock()
中会对签名和区块进行验证。 -
bestBlockHeader.PreviousBlockHash == c.bestBlockHeader.Hash()
的情况说明一切正常,新block被添加到链的末端。 -
bestBlockHeader.Height > c.bestBlockHeader.Height
表示出现了分叉,需要回滚。
看完上述内容,你们掌握Bytom侧链Vapor源码分析节点出块过程是怎样的的方法了吗?如果还想学到更多技能或想了解更多相关内容,欢迎关注行业资讯频道,感谢各位的阅读!
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/151916.html