成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專(zhuān)欄INFORMATION COLUMN

剝開(kāi)比原看代碼14:比原的挖礦流程是什么樣的?

BLUE / 2347人閱讀

摘要:所以在今天我打算通過(guò)源代碼分析一下比原的挖礦流程,但是考慮到它肯定會(huì)涉及到比原的核心,所以太復(fù)雜的地方我就會(huì)先跳過(guò),那些地方時(shí)機(jī)成熟的時(shí)候會(huì)徹底研究一下。

作者:freewind

比原項(xiàng)目倉(cāng)庫(kù):

Github地址:https://github.com/Bytom/bytom

Gitee地址:https://gitee.com/BytomBlockc...

當(dāng)我們以bytom init --chain_id=solonet建立比原單機(jī)節(jié)點(diǎn)用于本地測(cè)試時(shí),很快會(huì)發(fā)現(xiàn)自己將面臨一個(gè)尷尬的問(wèn)題:余額為0。就算我們使用bytom node --mining開(kāi)啟挖礦,理論上由于我們是單機(jī)狀態(tài),本機(jī)算力就是全網(wǎng)算力,應(yīng)該每次都能夠挖到,但是不知道為什么,在我嘗試的時(shí)候發(fā)現(xiàn)總是挖不到,所以打算簡(jiǎn)單研究一下比原的挖礦流程,看看有沒(méi)有辦法能改點(diǎn)什么,給自己?jiǎn)螜C(jī)多挖點(diǎn)BTM以方便后面的測(cè)試。

所以在今天我打算通過(guò)源代碼分析一下比原的挖礦流程,但是考慮到它肯定會(huì)涉及到比原的核心,所以太復(fù)雜的地方我就會(huì)先跳過(guò),那些地方時(shí)機(jī)成熟的時(shí)候會(huì)徹底研究一下。

如果我們快速搜索一下,就能發(fā)現(xiàn)在比原代碼中有一個(gè)類(lèi)型叫CPUMiner,我們圍繞著它應(yīng)該就可以了。

首先還是從比原啟動(dòng)開(kāi)始,看看CPUMiner是如何被啟動(dòng)的。

下面是bytom node --mining對(duì)應(yīng)的入口函數(shù):

cmd/bytomd/main.go#L54-L57

func main() {
    cmd := cli.PrepareBaseCmd(commands.RootCmd, "TM", os.ExpandEnv(config.DefaultDataDir()))
    cmd.Execute()
}

由于傳入了參數(shù)node,所以創(chuàng)建Node并啟動(dòng):

cmd/bytomd/commands/run_node.go#L41-L54

func runNode(cmd *cobra.Command, args []string) error {
    // Create & start node
    n := node.NewNode(config)
    if _, err := n.Start(); err != nil {
        // ...
}

在創(chuàng)建一個(gè)Node對(duì)象的時(shí)候,也會(huì)創(chuàng)建CPUMiner對(duì)象:

node/node.go#L59-L142

func NewNode(config *cfg.Config) *Node {
    // ...
    node.cpuMiner = cpuminer.NewCPUMiner(chain, accounts, txPool, newBlockCh)
    node.miningPool = miningpool.NewMiningPool(chain, accounts, txPool, newBlockCh)
    // ...
    return node
}

這里可以看到創(chuàng)建了兩個(gè)與挖礦相關(guān)的東西,一個(gè)是NewCPUMiner,另一個(gè)是miningPool。我們先看NewCPUMiner對(duì)應(yīng)的代碼:

mining/cpuminer/cpuminer.go#L282-L293

func NewCPUMiner(c *protocol.Chain, accountManager *account.Manager, txPool *protocol.TxPool, newBlockCh chan *bc.Hash) *CPUMiner {
    return &CPUMiner{
        chain:             c,
        accountManager:    accountManager,
        txPool:            txPool,
        numWorkers:        defaultNumWorkers,
        updateNumWorkers:  make(chan struct{}),
        queryHashesPerSec: make(chan float64),
        updateHashes:      make(chan uint64),
        newBlockCh:        newBlockCh,
    }
}

從這里的字段可以看到,CPUMiner在工作的時(shí)候:

可能需要用到外部的三個(gè)對(duì)象分別是:chain(代表本機(jī)持有的區(qū)塊鏈),accountManager(管理帳戶(hù)),txPool(交易池)

numWorkers:應(yīng)該保持幾個(gè)worker在挖礦,默認(rèn)值defaultNumWorkers為常量1,也就是說(shuō)默認(rèn)只有一個(gè)worker。這對(duì)于多核cpu來(lái)說(shuō)有點(diǎn)虧,真要挖礦的話(huà)可以把它改大點(diǎn),跟核心數(shù)相同(不過(guò)用普通電腦不太可能挖到了)

updateNumWorkers:外界如果想改變worker的數(shù)量,可以通過(guò)向這個(gè)通道發(fā)消息實(shí)現(xiàn)。CPUMiner會(huì)監(jiān)聽(tīng)它,并按要求增減worker

queryHashesPerSec:這個(gè)沒(méi)用上,忽略吧。我發(fā)現(xiàn)比原的開(kāi)發(fā)人員很喜歡預(yù)先設(shè)計(jì),有很多這樣沒(méi)用上的代碼

updateHashes: 這個(gè)沒(méi)用上,忽略

newBlockCh: 一個(gè)來(lái)自外部的通道,用來(lái)告訴外面自己成功挖到了塊,并且已經(jīng)放進(jìn)了本地區(qū)塊鏈,其它地方就可以用它了(比如廣播出去)

然而這里出現(xiàn)的并不是CPUMiner全部的字段,僅僅是需要特意初始化的幾個(gè)。完整的在這里:

mining/cpuminer/cpuminer.go#L29-L45

type CPUMiner struct {
    sync.Mutex
    chain             *protocol.Chain
    accountManager    *account.Manager
    txPool            *protocol.TxPool
    numWorkers        uint64
    started           bool
    discreteMining    bool
    wg                sync.WaitGroup
    workerWg          sync.WaitGroup
    updateNumWorkers  chan struct{}
    queryHashesPerSec chan float64
    updateHashes      chan uint64
    speedMonitorQuit  chan struct{}
    quit              chan struct{}
    newBlockCh        chan *bc.Hash
}

可以看到還多出了幾個(gè):

sync.Mutex:為CPUMiner提供了鎖,方便在不同的goroutine代碼中進(jìn)行同步

started:記錄miner是否啟動(dòng)了

discreteMining:這個(gè)在當(dāng)前代碼中沒(méi)有賦過(guò)值,永遠(yuǎn)是false,我覺(jué)得應(yīng)該刪除。已提issue #961

wgworkerWg:都是跟控制goroutine流程相關(guān)的

speedMonitorQuit:也沒(méi)什么用,忽略

quit:外界可以給這個(gè)通道發(fā)消息來(lái)通知CPUMiner退出

再回到n.Start看看cpuMiner是何時(shí)啟動(dòng)的:

node/node.go#L169-L180

func (n *Node) OnStart() error {
    if n.miningEnable {
        n.cpuMiner.Start()
    }
    // ...
}

由于我們傳入了參數(shù)--mining,所以n.miningEnabletrue,于是n.cpuMiner.Start會(huì)運(yùn)行:

mining/cpuminer/cpuminer.go#L188-L205

func (m *CPUMiner) Start() {
    m.Lock()
    defer m.Unlock()

    if m.started || m.discreteMining {
        return
    }

    m.quit = make(chan struct{})
    m.speedMonitorQuit = make(chan struct{})
    m.wg.Add(1)
    go m.miningWorkerController()

    m.started = true
    log.Infof("CPU miner started")
}

這段代碼沒(méi)太多需要說(shuō)的,主要是通過(guò)判斷m.started保證不會(huì)重復(fù)啟動(dòng),然后把真正的工作放在了m.miningWorkerController()中:

mining/cpuminer/cpuminer.go#L126-L125

func (m *CPUMiner) miningWorkerController() {
    // 1. 
    var runningWorkers []chan struct{}
    launchWorkers := func(numWorkers uint64) {
        for i := uint64(0); i < numWorkers; i++ {
            quit := make(chan struct{})
            runningWorkers = append(runningWorkers, quit)

            m.workerWg.Add(1)
            go m.generateBlocks(quit)
        }
    }
    runningWorkers = make([]chan struct{}, 0, m.numWorkers)
    launchWorkers(m.numWorkers)

out:
    for {
        select {
        // 2. 
        case <-m.updateNumWorkers:
            numRunning := uint64(len(runningWorkers))
            if m.numWorkers == numRunning {
                continue
            }

            if m.numWorkers > numRunning {
                launchWorkers(m.numWorkers - numRunning)
                continue
            }

            for i := numRunning - 1; i >= m.numWorkers; i-- {
                close(runningWorkers[i])
                runningWorkers[i] = nil
                runningWorkers = runningWorkers[:i]
            }

        // 3.
        case <-m.quit:
            for _, quit := range runningWorkers {
                close(quit)
            }
            break out
        }
    }

    m.workerWg.Wait()
    close(m.speedMonitorQuit)
    m.wg.Done()
}

這個(gè)方法看起來(lái)代碼挺多的,但是實(shí)際上做的事情還是比較好理清的,主要是做了三件事:

第1處代碼是按指定的worker數(shù)量啟動(dòng)挖礦例程

第2處是監(jiān)聽(tīng)?wèi)?yīng)該保持的worker數(shù)量并增減

第3處在被知關(guān)閉的時(shí)候安全關(guān)閉

代碼比較清楚,應(yīng)該不需要多講。

可以看第1處代碼中,真正挖礦的工作是放在generateBlocks里的:

mining/cpuminer/cpuminer.go#L84-L119

func (m *CPUMiner) generateBlocks(quit chan struct{}) {
    ticker := time.NewTicker(time.Second * hashUpdateSecs)
    defer ticker.Stop()

out:
    for {
        select {
        case <-quit:
            break out
        default:
        }

        // 1.
        block, err := mining.NewBlockTemplate(m.chain, m.txPool, m.accountManager)
        // ...

        // 2.
        if m.solveBlock(block, ticker, quit) {
            // 3.
            if isOrphan, err := m.chain.ProcessBlock(block); err == nil {
                // ...
                // 4.
                blockHash := block.Hash()
                m.newBlockCh <- &blockHash
                // ...
            }
        }
    }

    m.workerWg.Done()
}

方法里省略了一些不太重要的代碼,我們可以從標(biāo)注的幾處看一下在做什么:

第1處通過(guò)mining.NewBlockTemplate根據(jù)模板生成了一個(gè)block

第2處是以暴力方式(從0開(kāi)始挨個(gè)計(jì)算)來(lái)爭(zhēng)奪對(duì)該區(qū)塊的記帳權(quán)

第3處是通過(guò)chain.ProcessBlock(block)嘗試把它加到本機(jī)持有的區(qū)塊鏈上

第4處是向newBlockCh通道發(fā)出消息,通知外界自己挖到了新的塊

mining.NewBlockTemplate

我們先看一下第1處中的mining.NewBlockTemplate

mining/mining.go#L67-L154

func NewBlockTemplate(c *protocol.Chain, txPool *protocol.TxPool, accountManager *account.Manager) (b *types.Block, err error) {
    // ...
    return b, err
}

這個(gè)方法很長(zhǎng),但是內(nèi)容都被我忽略了,原因是它的內(nèi)容過(guò)于細(xì)節(jié),并且已經(jīng)觸及到了比原的核心,所以現(xiàn)在大概了解一下就可以了。

比原在一個(gè)Block區(qū)塊里,有一些基本信息,比如在其頭部有前一塊的hash值、挖礦難度值、時(shí)間戳等等,主體部有各種交易記錄,以及多次層的hash摘要。在這個(gè)方法中,主要的邏輯就是去找到這些信息然后把它們包裝成一個(gè)Block對(duì)象,然后交由后面處理。我覺(jué)得在我們還沒(méi)有深刻理解比原的區(qū)塊鏈結(jié)構(gòu)和規(guī)則的情況下,看這些太細(xì)節(jié)的東西沒(méi)有太大用處,所以先忽略,等以后合適的時(shí)候再回過(guò)頭來(lái)看就簡(jiǎn)單了。

m.solveBlock

我們繼續(xù)向下,當(dāng)由NewBlockTemplate生成好了一個(gè)Block對(duì)象后,它會(huì)交給solveBlock方法處理:

mining/cpuminer/cpuminer.go#L50-L75

func (m *CPUMiner) solveBlock(block *types.Block, ticker *time.Ticker, quit chan struct{}) bool {
    // 1. 
    header := &block.BlockHeader
    seed, err := m.chain.CalcNextSeed(&header.PreviousBlockHash)
    // ...

    // 2.
    for i := uint64(0); i <= maxNonce; i++ {
        // 3. 
        select {
        case <-quit:
            return false
        case <-ticker.C:
            if m.chain.BestBlockHeight() >= header.Height {
                return false
            }
        default:
        }

        // 4.
        header.Nonce = i
        headerHash := header.Hash()
        
        // 5.
        if difficulty.CheckProofOfWork(&headerHash, seed, header.Bits) {
            return true
        }
    }
    return false
}

這個(gè)方法就是挖礦中我們最關(guān)心的部分了:爭(zhēng)奪記帳權(quán)。

我把代碼分成了4塊,依次簡(jiǎn)單講解:

第1處是從本地區(qū)塊鏈中找到新生成的區(qū)塊指定的父區(qū)塊,并由它計(jì)算出來(lái)seed,它是如何計(jì)算出來(lái)的我們暫時(shí)不關(guān)心(比較復(fù)雜),此時(shí)只要知道它是用來(lái)檢查工作量的就可以了

第2處是使用暴力方式來(lái)計(jì)算目標(biāo)值,用于爭(zhēng)奪記帳權(quán)。為什么說(shuō)是暴力方式?因?yàn)橥诘V的算法保證了想解開(kāi)難題,沒(méi)有比從0開(kāi)始一個(gè)個(gè)計(jì)算更快的辦法,所以這里從0開(kāi)始依次嘗試,直到maxNonce結(jié)束。maxNonce是一個(gè)非常大的數(shù)^uint64(0)(即2^64 - 1),基本上是不可能在一個(gè)區(qū)塊時(shí)間內(nèi)遍歷完的。

第3處是在每次循環(huán)中進(jìn)行計(jì)算之前,都看一看是否需要退出。在兩種情況下應(yīng)該退出,一是quit通道里有新消息,被人提醒退出(可能是時(shí)間到了);另一種是本地的區(qū)塊鏈中已經(jīng)收到了新的塊,且高度比較自己高,說(shuō)明已經(jīng)有別人搶到了。

第4處是把當(dāng)前循環(huán)的數(shù)字當(dāng)作Nonce,計(jì)算出Hash值

第5處是調(diào)用difficulty.CheckProofOfWork來(lái)檢查當(dāng)前算出來(lái)的hash值是否滿(mǎn)足了當(dāng)前難度。如果滿(mǎn)足就說(shuō)明自己擁有了記帳權(quán),這個(gè)塊是有效的;否則就繼續(xù)計(jì)算

然后我們?cè)倏匆幌碌?處的difficulty.CheckProofOfWork:

consensus/difficulty/difficulty.go#L120-L123

func CheckProofOfWork(hash, seed *bc.Hash, bits uint64) bool {
    compareHash := tensority.AIHash.Hash(hash, seed)
    return HashToBig(compareHash).Cmp(CompactToBig(bits)) <= 0
}

在這個(gè)方法里,可以看到出現(xiàn)了一個(gè)tensority.AIHash,這是比原獨(dú)有的人工智能友好的工作量算法,相關(guān)論文的下載地址:https://github.com/Bytom/byto...,有興趣的同學(xué)可以去看看。由于這個(gè)算法的難度肯定超出了本文的預(yù)期,所以就不研究它了。在以后,如果有機(jī)會(huì)有條件的話(huà),也許我會(huì)試著理解一下(不要期待~)

從這個(gè)方法里可以看出,它是調(diào)用了tensority.AIHash中的相關(guān)方法進(jìn)判斷當(dāng)前計(jì)算出來(lái)的hash是否滿(mǎn)足難度要求。

在本文的開(kāi)始,我們說(shuō)過(guò)希望能找到一種方法修改比原的代碼,讓我們?cè)?b>solonet模式下,可以正常挖礦,得到BTM用于測(cè)試。看到這個(gè)方法的時(shí)候,我覺(jué)得已經(jīng)找到了,我們只需要修改一下讓它永遠(yuǎn)返回true即可:

func CheckProofOfWork(hash, seed *bc.Hash, bits uint64) bool {
    compareHash := tensority.AIHash.Hash(hash, seed)
    return HashToBig(compareHash).Cmp(CompactToBig(bits)) <= 0 || true
}

這里也許會(huì)讓人覺(jué)得有點(diǎn)奇怪,為什么要在最后的地方加上|| true,而不是在前面直接返回true呢?這是因?yàn)?,如果直接返?b>true,可能使得程序中關(guān)于時(shí)間戳檢查的地方出現(xiàn)問(wèn)題,出現(xiàn)如下的錯(cuò)誤:

time="2018-05-17T12:10:14+08:00" level=error msg="Miner fail on ProcessBlock block, timestamp is not in the valid range: invalid block" height=32

原因還未深究,可能是因?yàn)樵镜拇a是需要消耗一些時(shí)間的,正好使得檢查通過(guò)。如果直接返回true就太快了,反而使檢查通過(guò)不了。不過(guò)我感覺(jué)這里是有一點(diǎn)問(wèn)題的,留待以后再研究。

這樣修改完以后,再重新編譯并啟動(dòng)比原節(jié)點(diǎn),每個(gè)塊都能挖到了,差不多一秒一個(gè)塊(一下子變成大富豪了:)

m.chain.ProcessBlock

我們此時(shí)該回到generateBlocks方法中的第3處,即:

mining/cpuminer/cpuminer.go#L84-L119

func (m *CPUMiner) generateBlocks(quit chan struct{}) {
        //...
        if m.solveBlock(block, ticker, quit) {
            // 3.
            if isOrphan, err := m.chain.ProcessBlock(block); err == nil {
                // ...
                // 4.
                blockHash := block.Hash()
                m.newBlockCh <- &blockHash
                // ...
            }
        }
    }

    m.workerWg.Done()
}

m.chain.ProcessBlock把剛才成功拿到記帳權(quán)的塊向本地區(qū)塊鏈上添加:

protocol/block.go#L191-L196

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
}

可以看到這里實(shí)際上是把這個(gè)工作甩出去了,因?yàn)樗岩幚淼膲K放進(jìn)了Chain.processBlockCh這個(gè)通道里,同時(shí)傳過(guò)去的還有一個(gè)用于對(duì)方回復(fù)的通道reply。然后監(jiān)聽(tīng)reply等消息就可以了。

那么誰(shuí)將會(huì)處理c.processBlockCh里的內(nèi)容呢?當(dāng)然是由Chain,只不過(guò)這里就屬于比原核心了,我們留等以后再詳細(xì)研究,今天就先跳過(guò)。

如果處理完沒(méi)有出錯(cuò),就進(jìn)入到了第4塊,把這個(gè)block的hash放在newBlockCh通道里。這個(gè)newBlockCh是由外面?zhèn)魅氲?,很多地方都?huì)用到。當(dāng)它里面有新的數(shù)據(jù)時(shí),就說(shuō)明本機(jī)挖到了新塊(并且已經(jīng)添加到了本機(jī)的區(qū)塊鏈上),其它的地方就可以使用它進(jìn)行別的操作(比如廣播出去)

那么到這里,我們今天的問(wèn)題就算解決了,留下了很多坑,以后專(zhuān)門(mén)填。

文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/24178.html

相關(guān)文章

  • 剝開(kāi)原看代碼01:初始化時(shí)生成配置文件在哪兒

    摘要:所以這個(gè)文章系列叫作剝開(kāi)比原看代碼。所以我的問(wèn)題是比原初始化時(shí),產(chǎn)生了什么樣的配置文件,放在了哪個(gè)目錄下下面我將結(jié)合源代碼,來(lái)回答這個(gè)問(wèn)題。將用來(lái)確認(rèn)數(shù)據(jù)目錄是有效的,并且將根據(jù)傳入的不同,來(lái)生成不同的內(nèi)容寫(xiě)入到配置文件中。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee...

    felix0913 評(píng)論0 收藏0
  • 剝開(kāi)原看代碼08:比原Dashboard怎么做出來(lái)?

    摘要:所以本文本來(lái)是想去研究一下,當(dāng)別的節(jié)點(diǎn)把區(qū)塊數(shù)據(jù)發(fā)給我們之后,我們應(yīng)該怎么處理,現(xiàn)在換成研究比原的是怎么做出來(lái)的。進(jìn)去后會(huì)看到大量的與相關(guān)的配置。它的功能主要是為了在訪問(wèn)與的函數(shù)之間增加了一層轉(zhuǎn)換。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlo...

    CHENGKANG 評(píng)論0 收藏0
  • 剝開(kāi)原看代碼09:通過(guò)dashboard創(chuàng)建密鑰時(shí),前端數(shù)據(jù)如何傳到后端?

    摘要:下一步,將進(jìn)入比原的節(jié)點(diǎn)也就是后端。它具體是怎么創(chuàng)建密鑰的,這在以后的文章中將詳細(xì)討論。當(dāng)我們清楚了在本文中,前后端數(shù)據(jù)是如何交互的,就很容易推廣到更多的情景。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在前面一篇文章,我們粗略...

    MangoGoing 評(píng)論0 收藏0
  • 剝開(kāi)原看代碼17:比原如何顯示交易詳細(xì)信息

    摘要:作者比原項(xiàng)目倉(cāng)庫(kù)地址地址在上上篇文章里,我們還剩下一個(gè)小問(wèn)題沒(méi)有解決,即前端是如何顯示一個(gè)交易的詳細(xì)信息的。那我們?cè)诒疚目匆幌拢仍侨绾物@示這個(gè)交易的詳細(xì)信息的。到今天為止,我們終于把比原是如何創(chuàng)建一個(gè)交易的這件事的基本流程弄清楚了。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https:/...

    魏明 評(píng)論0 收藏0
  • 剝開(kāi)原看代碼12:比原如何通過(guò)/create-account-receiver創(chuàng)建地址?

    摘要:繼續(xù)看生成地址的方法由于這個(gè)方法里傳過(guò)來(lái)的是而不是對(duì)象,所以還需要再用查一遍,然后,再調(diào)用這個(gè)私有方法創(chuàng)建地址該方法可以分成部分在第塊中主要關(guān)注的是返回值。 作者:freewind 比原項(xiàng)目倉(cāng)庫(kù): Github地址:https://github.com/Bytom/bytom Gitee地址:https://gitee.com/BytomBlockc... 在比原的dashboard中...

    oneasp 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

閱讀需要支付1元查看
<