千家信息网

bytom的挖矿流程是什么

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,本篇内容主要讲解"bytom的挖矿流程是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"bytom的挖矿流程是什么"吧!当我们以bytom init -
千家信息网最后更新 2026年11月01日bytom的挖矿流程是什么

本篇内容主要讲解"bytom的挖矿流程是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"bytom的挖矿流程是什么"吧!

当我们以bytom init --chain_id=solonet建立比原单机节点用于本地测试时,很快会发现自己将面临一个尴尬的问题:余额为0。就算我们使用bytom node --mining开启挖矿,理论上由于我们是单机状态,本机算力就是全网算力,应该每次都能够挖到,但是不知道为什么,在我尝试的时候发现总是挖不到,所以打算简单研究一下比原的挖矿流程,看看有没有办法能改点什么,给自己单机多挖点BTM以方便后面的测试。

所以在今天我打算通过源代码分析一下比原的挖矿流程,但是考虑到它肯定会涉及到比原的核心,所以太复杂的地方我就会先跳过,那些地方时机成熟的时候会彻底研究一下。

如果我们快速搜索一下,就能发现在比原代码中有一个类型叫CPUMiner,我们围绕着它应该就可以了。

首先还是从比原启动开始,看看CPUMiner是如何被启动的。

下面是bytom node --mining对应的入口函数:

cmd/bytomd/main.go#L54-L57

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

由于传入了参数node,所以创建Node并启动:

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 {        // ...}

在创建一个Node对象的时候,也会创建CPUMiner对象:

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}

这里可以看到创建了两个与挖矿相关的东西,一个是NewCPUMiner,另一个是miningPool。我们先看NewCPUMiner对应的代码:

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在工作的时候:

  • 可能需要用到外部的三个对象分别是:chain(代表本机持有的区块链),accountManager(管理帐户),txPool(交易池)

  • numWorkers:应该保持几个worker在挖矿,默认值defaultNumWorkers为常量1,也就是说默认只有一个worker。这对于多核cpu来说有点亏,真要挖矿的话可以把它改大点,跟核心数相同(不过用普通电脑不太可能挖到了)

  • updateNumWorkers:外界如果想改变worker的数量,可以通过向这个通道发消息实现。CPUMiner会监听它,并按要求增减worker

  • queryHashesPerSec:这个没用上,忽略吧。我发现比原的开发人员很喜欢预先设计,有很多这样没用上的代码

  • updateHashes: 这个没用上,忽略

  • newBlockCh: 一个来自外部的通道,用来告诉外面自己成功挖到了块,并且已经放进了本地区块链,其它地方就可以用它了(比如广播出去)

然而这里出现的并不是CPUMiner全部的字段,仅仅是需要特意初始化的几个。完整的在这里:

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}

可以看到还多出了几个:

  • sync.Mutex:为CPUMiner提供了锁,方便在不同的goroutine代码中进行同步

  • started:记录miner是否启动了

  • discreteMining:这个在当前代码中没有赋过值,永远是false,我觉得应该删除。已提issue #961

  • wg和workerWg:都是跟控制goroutine流程相关的

  • speedMonitorQuit:也没什么用,忽略

  • quit:外界可以给这个通道发消息来通知CPUMiner退出

再回到n.Start看看cpuMiner是何时启动的:

node/node.go#L169-L180

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

由于我们传入了参数--mining,所以n.miningEnable是true,于是n.cpuMiner.Start会运行:

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.started保证不会重复启动,然后把真正的工作放在了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()}

这个方法看起来代码挺多的,但是实际上做的事情还是比较好理清的,主要是做了三件事:

  1. 第1处代码是按指定的worker数量启动挖矿例程

  2. 第2处是监听应该保持的worker数量并增减

  3. 第3处在被知关闭的时候安全关闭

代码比较清楚,应该不需要多讲。

可以看第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()}

方法里省略了一些不太重要的代码,我们可以从标注的几处看一下在做什么:

  1. 第1处通过mining.NewBlockTemplate根据模板生成了一个block

  2. 第2处是以暴力方式(从0开始挨个计算)来争夺对该区块的记帐权

  3. 第3处是通过chain.ProcessBlock(block)尝试把它加到本机持有的区块链上

  4. 第4处是向newBlockCh通道发出消息,通知外界自己挖到了新的块

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}

这个方法很长,但是内容都被我忽略了,原因是它的内容过于细节,并且已经触及到了比原的核心,所以现在大概了解一下就可以了。

比原在一个Block区块里,有一些基本信息,比如在其头部有前一块的hash值、挖矿难度值、时间戳等等,主体部有各种交易记录,以及多次层的hash摘要。在这个方法中,主要的逻辑就是去找到这些信息然后把它们包装成一个Block对象,然后交由后面处理。我觉得在我们还没有深刻理解比原的区块链结构和规则的情况下,看这些太细节的东西没有太大用处,所以先忽略,等以后合适的时候再回过头来看就简单了。

m.solveBlock

我们继续向下,当由NewBlockTemplate生成好了一个Block对象后,它会交给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}

这个方法就是挖矿中我们最关心的部分了:争夺记帐权。

我把代码分成了4块,依次简单讲解:

  1. 第1处是从本地区块链中找到新生成的区块指定的父区块,并由它计算出来seed,它是如何计算出来的我们暂时不关心(比较复杂),此时只要知道它是用来检查工作量的就可以了

  2. 第2处是使用暴力方式来计算目标值,用于争夺记帐权。为什么说是暴力方式?因为挖矿的算法保证了想解开难题,没有比从0开始一个个计算更快的办法,所以这里从0开始依次尝试,直到maxNonce结束。maxNonce是一个非常大的数^uint64(0)(即2^64 - 1),基本上是不可能在一个区块时间内遍历完的。

  3. 第3处是在每次循环中进行计算之前,都看一看是否需要退出。在两种情况下应该退出,一是quit通道里有新消息,被人提醒退出(可能是时间到了);另一种是本地的区块链中已经收到了新的块,且高度比较自己高,说明已经有别人抢到了。

  4. 第4处是把当前循环的数字当作Nonce,计算出Hash值

  5. 第5处是调用difficulty.CheckProofOfWork来检查当前算出来的hash值是否满足了当前难度。如果满足就说明自己拥有了记帐权,这个块是有效的;否则就继续计算

然后我们再看一下第5处的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}

在这个方法里,可以看到出现了一个tensority.AIHash,这是比原独有的人工智能友好的工作量算法,相关论文的下载地址:https://github.com/Bytom/bytom/wiki/download/tensority-v1.2.pdf,有兴趣的同学可以去看看。由于这个算法的难度肯定超出了本文的预期,所以就不研究它了。在以后,如果有机会有条件的话,也许我会试着理解一下(不要期待~)

从这个方法里可以看出,它是调用了tensority.AIHash中的相关方法进判断当前计算出来的hash是否满足难度要求。

在本文的开始,我们说过希望能找到一种方法修改比原的代码,让我们在solonet模式下,可以正常挖矿,得到BTM用于测试。看到这个方法的时候,我觉得已经找到了,我们只需要修改一下让它永远返回true即可:

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

这里也许会让人觉得有点奇怪,为什么要在最后的地方加上|| true,而不是在前面直接返回true呢?这是因为,如果直接返回true,可能使得程序中关于时间戳检查的地方出现问题,出现如下的错误:

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

原因还未深究,可能是因为原本的代码是需要消耗一些时间的,正好使得检查通过。如果直接返回true就太快了,反而使检查通过不了。不过我感觉这里是有一点问题的,留待以后再研究。

这样修改完以后,再重新编译并启动比原节点,每个块都能挖到了,差不多一秒一个块(一下子变成大富豪了:)

m.chain.ProcessBlock

我们此时该回到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把刚才成功拿到记帐权的块向本地区块链上添加:

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}

可以看到这里实际上是把这个工作甩出去了,因为它把要处理的块放进了Chain.processBlockCh这个通道里,同时传过去的还有一个用于对方回复的通道reply。然后监听reply等消息就可以了。

那么谁将会处理c.processBlockCh里的内容呢?当然是由Chain。如果处理完没有出错,就进入到了第4块,把这个block的hash放在newBlockCh通道里。这个newBlockCh是由外面传入的,很多地方都会用到。当它里面有新的数据时,就说明本机挖到了新块(并且已经添加到了本机的区块链上),其它的地方就可以使用它进行别的操作(比如广播出去)

到此,相信大家对"bytom的挖矿流程是什么"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

代码 区块 方法 通道 地方 时候 流程 工作 内容 对象 时间 消息 记帐 本机 处理 检查 难度 研究 单机 外界 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网的科技 互联网科技媒体 汇久创客互联网科技 左右互联网络科技 镁科技互联网资讯平台 科技互联网的估值 银川新华互联网科技职业技术培训学校图片 武汉健翔互联网络科技有限公司 乌鲁木齐新华智原互联网科技学校 中国互联网公司排名锤子科技 南博望互联网科技有限公司 科技互联网的核心竞争力 元气玛特互联网科技公司是外包吗 上海钢联属于互联网科技股吗 银行科技岗属于互联网吗 上海万顺科技互联网有限公司招聘 互联网科技和医药行业 上海互联网科技园景观 北京瑞云互联网科技有限公司 广州全民伙伴互联网科技有限公司 广州为想互联网科技有限公司 朗新科技与工业互联网 平安科技 互联网 互联网 科技创业 互联网 的科技创新 互联网科技公司名称 成都铂石互联网科技做什么的 佛山市拉法互联网科技有限公司 深圳无忧互联网科技 数字互联网科技有限公司
0