Bytom側鏈Vapor源碼分析節點出塊過程是怎樣的,相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。
小編將從Vapor節點的創建開始,進而拓展講解Vapor節點出塊過程中所涉及的源碼。
下面對Vapor稍加介紹。Vapor是目前國內主流公鏈Bytom的高性能側鏈,是從Bytom主鏈中發展出來的一條獨立的高性能側鏈。Vapor是平臺最重要的區塊鏈基礎設施之一,目前采用DPoS的共識算法,具有高性能、高安全、可擴展等特點,用于搭建規?;纳虡I應用。
Vapor入口函數:
vapor/cmd/vapord/main.go
func main() { cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir())) cmd.Execute() }
傳入參數node后會調用runNode函數并新建一個節點。
vapor/cmd/vapord/commands/run_node.go
func runNode(cmd *cobra.Command, args []string) error { startTime := time.Now() setLogLevel(config.LogLevel) // Create & start node n := node.NewNode(config) …… }
vapor節點的結構:
vapor/node/node.go
type Node struct { cmn.BaseService config *cfg.Config eventDispatcher *event.Dispatcher syncManager *netsync.SyncManager wallet *w.Wallet accessTokens *accesstoken.CredentialStore notificationMgr *websocket.WSNotificationManager api *api.API chain *protocol.Chain blockProposer *blockproposer.BlockProposer miningEnable bool }
其中與出塊和共識相關的是blockProposer
字段
新建節點的部分源碼
vapor/node/node.go
func NewNode(config *cfg.Config) *Node { //…… node := &Node{ eventDispatcher: dispatcher, config: config, syncManager: syncManager, accessTokens: accessTokens, wallet: wallet, chain: chain, miningEnable: config.Mining, notificationMgr: notificationMgr, } node.blockProposer = 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采用的是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框架記錄新的塊,并像網絡中廣播。
針對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()
模塊嘗試出塊的共識節點不一定是當前時間的合法出塊節點,因此需要本模塊通過對比公鑰進行節點資格的驗證。
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)
加工處理。
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源碼分析節點出塊過程是怎樣的的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。