原创 标题:  以太坊挖矿返回null源码分析

在以太坊版本1.7.3中,无论是dev环境或是公链环境在console中执行miner.start()始终返回null,而不是期待的true。这是为什么呢?这篇文章就带大家从源码中找找原因。在这个过程中我们会了解到更多底层的知识。

挖矿入口程序

首先看一下调用挖矿的程序,在console中我们执行的是:

miner.start()
##或
miner.start(n)

其中参数n是系统CPU的核心数。在go语言中对应的方法是:

miner.Start(threads *rpc.HexNumber) (bool, error)

此方法对应的api调用在eth/api.go文件中,具体代码如下:

// Start the miner with the given number of threads. If threads is nil the number
// of workers started is equal to the number of logical CPUs that are usable by
// this process. If mining is already running, this method adjust the number of
// threads allowed to use.
func (api *PrivateMinerAPI) Start(threads *int) error {
    // Set the number of threads if the seal engine supports it
    if threads == nil {
        threads = new(int)
    } else if *threads == 0 {
        *threads = -1 // Disable the miner from within
    }
    type threaded interface {
        SetThreads(threads int)
    }
    if th, ok := api.e.engine.(threaded); ok {
        log.Info("Updated mining threads", "threads", *threads)
        th.SetThreads(*threads)
    }
    // Start the miner and return
    if !api.e.IsMining() {
        // Propagate the initial price point to the transaction pool
        api.e.lock.RLock()
        price := api.e.gasPrice
        api.e.lock.RUnlock()

        api.e.txPool.SetGasPrice(price)
        return api.e.StartMining(true)
    }
    return nil
}

通过此段代码我们可以分析得知以下内容:

  • 挖矿时传递的参数为系统CPU的核心数,可指定,如果未指定则默认按照系统的核心数。
  • 如果已经在挖矿中,再执行挖矿程序可更改使用的CPU核心数。
  • 如果系统已经在挖矿返回的结果为nil(null)。
  • 如果未开始挖矿,那么执行api.e.StartMining(true)方法。

执行挖矿

看一下api.e.StartMining(true)方法对应的代码内容:

func (s *Ethereum) StartMining(local bool) error {
    eb, err := s.Etherbase()
    if err != nil {
        log.Error("Cannot start mining without etherbase", "err", err)
        return fmt.Errorf("etherbase missing: %v", err)
    }
    if clique, ok := s.engine.(*clique.Clique); ok {
        wallet, err := s.accountManager.Find(accounts.Account{Address: eb})
        if wallet == nil || err != nil {
            log.Error("Etherbase account unavailable locally", "err", err)
            return fmt.Errorf("signer missing: %v", err)
        }
        clique.Authorize(eb, wallet.SignHash)
    }
    if local {
        // If local (CPU) mining is started, we can disable the transaction rejection
        // mechanism introduced to speed sync times. CPU mining on mainnet is ludicrous
        // so noone will ever hit this path, whereas marking sync done on CPU mining
        // will ensure that private networks work in single miner mode too.
        atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)
    }
    go s.miner.Start(eb)
    return nil
}

通过上面的代码我们可以分析得出:

  • 能成功挖矿的前提是需要存在有效的coinbase账号;
  • 即使成功开始挖矿(执行go s.miner.Start(eb)),此方法返回的结果也是nil(null)。

因此通过,上面两段代码的分析,在此版本中无论执行挖矿成功或者执行挖矿失败,返回的结果都是null。这也正是为什么这个版本中通过返回值无法确定是否成功开启了挖矿。

进步分析挖矿代码

再进一步看看s.miner.Start(eb)中的代码:

func (self *Miner) Start(coinbase common.Address) {
    atomic.StoreInt32(&self.shouldStart, 1)
    self.worker.setEtherbase(coinbase)
    self.coinbase = coinbase

    if atomic.LoadInt32(&self.canStart) == 0 {
        log.Info("Network syncing, will start miner afterwards")
        return
    }
    atomic.StoreInt32(&self.mining, 1)

    log.Info("Starting mining operation")
    self.worker.start()
    self.worker.commitNewWork()
}

这段代码的基本实现就是设置一些相关参数项,然后开始执行挖矿。通过这段代码我们能获得一个判断是否开启挖矿的日志信息——“Starting mining operation”,因此如果日志中有此行输出可以作为一个必要不充分条件来确认已经开始挖矿。

stop函数

与之相对应的stop函数就不一样了:

// Stop the miner
func (api *PrivateMinerAPI) Stop() bool {
    type threaded interface {
        SetThreads(threads int)
    }
    if th, ok := api.e.engine.(threaded); ok {
        th.SetThreads(-1)
    }
    api.e.StopMining()
    return true
}

很明显,无论stop是否执行成功,或者说无论当前是否在挖矿,只要执行stop,那么始终会返回true。

总结

通过上面的分析,我们获得一些经验和常识,同时也告诉我们在这个版本中通过调用miner.start()返回null并不能说明什么,有可能已经开始挖矿,有可能没有。

END
朱智胜的个人博客-微信公众号