挖矿模块只通过 Miner 实例对外提供数据访问。可以通过多种途径开启挖矿服务。程序运行时已经将 Miner 实例化,并进入等待挖矿状态,随时可以启动挖矿。
矿工可以根据矿机的服务器性能,来定制化挖矿参数。下面是一份 geth 关于挖矿的运行时参数清单,全部定义在 cmd/utils/flags.go 文件中。
cmd/utils/flags.go
,
你可以通过执行程序 dgeth[^1] 来查看参数。
dgeth -h |grep "mine"
geth 程序运行时已经将 Miner 实例化,只需等待命令开启挖矿。
//eth/backend.go:197 eth.miner = miner.New(eth, chainConfig, eth.EventMux(), eth.engine, config.MinerRecommit, config.MinerGasFloor, config.MinerGasCeil, eth.isLocalBlock) eth.miner.SetExtra(makeExtraData(config.MinerExtraData))
从上可看出,在实例化 miner 时所用到的配置项只有4项。实例化后,便可通过 API 操作 Miner。
Miner API 分 public 和 private。挖矿属于隐私,不得让其他人任意修改。因此挖矿API全部定义在 Private 中,公共部分只有 Mining()。
Mining()
geth 运行时默认不开启挖矿。如果用户需要启动挖矿,则可以通过以下几种方式启动挖矿。
使用参数 —mine,可以在启动程序时默认开启挖矿。下面我们用 dgeth[^1] 在开发者模式启动挖矿为例:
—mine
dgeth --dev --mine
启动后,可以看到默认情况下已开启挖矿。开发者模式下已经挖出了一个高度为1的空块。
![image-20190722215758989(https://img.learnblockchain.cn/book_geth/image-20190722215758989.png!de)
当参数加入了--mine参数表示启用挖矿,此时将根据输入个各项挖矿相关的参数启动挖矿服务。
--mine
// cmd/geth/main.go:369 if ctx.GlobalBool(utils.MiningEnabledFlag.Name) || ctx.GlobalBool(utils.DeveloperFlag.Name) { //❶ // Mining only makes sense if a full Ethereum node is running if ctx.GlobalString(utils.SyncModeFlag.Name) == "light" { utils.Fatalf("Light clients do not support mining") } var ethereum *eth.Ethereum if err := stack.Service(ðereum); err != nil { utils.Fatalf("Ethereum service not running: %v", err) } // Set the gas price to the limits from the CLI and start mining gasprice := utils.GlobalBig(ctx, utils.MinerLegacyGasPriceFlag.Name)//❷ if ctx.IsSet(utils.MinerGasPriceFlag.Name) { gasprice = utils.GlobalBig(ctx, utils.MinerGasPriceFlag.Name) } ethereum.TxPool().SetGasPrice(gasprice) threads := ctx.GlobalInt(utils.MinerLegacyThreadsFlag.Name)//❸ if ctx.GlobalIsSet(utils.MinerThreadsFlag.Name) { threads = ctx.GlobalInt(utils.MinerThreadsFlag.Name) } if err := ethereum.StartMining(threads); err != nil {//❹ utils.Fatalf("Failed to start mining: %v", err) } }
启动 geth 过程是,如果启用挖矿--mine或者属于开发者模式—dev,则将启动挖矿❶。
—dev
在启动挖矿之前,还需要获取 —miner.gasprice 实时应用到交易池中❷。同时也需要指定将允许使用多少协程来并行参与PoW计算❸。然后开启挖矿,如果开启挖矿失败则终止程序运行并打印错误信息❹。
—miner.gasprice
在实例化Miner后,已经将 miner 的操作API化。因此我们可以在 geth 的控制台中输入Start命令启动挖矿。
调用API miner_start 将使用给定的挖矿计算线程数来开启挖矿。下面表格是调用 API 的几种方式。
miner_start
miner.Start(threads *rpc.HexNumber) (bool, error)
miner.start(number)
{"method": "miner_start", "params": [number]}
首先,我们进入 geth 的 JavaScript 控制台,后输入命令miner.start(1)来启动挖矿。
miner.start(1)
dgeth --maxpeers=0 console
启动挖矿后,将开始出新区块。
因为 API 已支持开启挖矿,如上文所述,可以直接调用 RPC {"method": "miner_start", "params": [number]} 来启动挖矿。实际上在控制台所执行的 miner.start(1),则相对于 {"method": "miner_start", "params": [1]}。
{"method": "miner_start", "params": [1]}
如,启动 geth 时开启RPC。
dgeth --maxpeer 0 --rpc --rpcapi --rpcport 8080 "miner,admin,eth" console
开启后,则可以直接调用API,开启挖矿服务。
curl -d '{"id":1,"method": "miner_start", "params": [1]}' http://127.0.0.1:8080
不管何种方式启动挖矿,最终通过调用 miner 对象的 Start 方法来启动挖矿。不过在开启挖矿前,geth 还处理了额外内容。
当你通过控制台或者 RPC API 调用启动挖矿命令后,在程序都将引导到方法func (s *Ethereum) StartMining(threads int) error。
func (s *Ethereum) StartMining(threads int) error
// eth/backend.go:414 type threaded interface { SetThreads(threads int) } if th, ok := s.engine.(threaded); ok {//❶ log.Info("Updated mining threads", "threads", threads) if threads == 0 { threads = -1 // Disable the miner from within } th.SetThreads(threads)//❷ } if !s.IsMining() { //❸ //... price := s.gasPrice s.txPool.SetGasPrice(price) //❹ 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.SignData)//❼ } atomic.StoreUint32(&s.protocolManager.acceptTxs, 1)//❽ go s.miner.Start(eb)//❾ } return nil
在此方法中,首先看挖矿的共识引擎是否支持设置协程数❶,如果支持,将更新此共识引擎参数 ❷。接着,如果已经是在挖矿中,则忽略启动,否则将开启挖矿 ❸。在启动前,需要确定两项配置:交易GasPrice下限❹,和挖矿奖励接收账户(矿工账户地址)❺。
这里对于 clique.Clique 共识引擎(PoA 权限共识),进行了特殊处理,需要从钱包中查找对于挖矿账户❻。在进行挖矿时不再是进行PoW计算,而是使用认可的账户进行区块签名❼即可。
可能由于一些原因,不允许接收网络交易。因此,在挖矿前将允许接收网络交易❽。随即,开始在挖矿账户下开启挖矿❾。此时,已经进入了miner实例的 Start 方法。
// miner/miner.go:108 func (self *Miner) Start(coinbase common.Address) { atomic.StoreInt32(&self.shouldStart, 1) self.SetEtherbase(coinbase) //⑩ if atomic.LoadInt32(&self.canStart) == 0 { //⑪ log.Info("Network syncing, will start miner afterwards") return } self.worker.start() } // miner/worker.go:268 func (w *worker) start() { //⑬ atomic.StoreInt32(&w.running, 1) w.startCh <- struct{}{} }
存储coinbase 账户后⑩,有可能因为正在同步数据,此时将不允许启动挖矿⑪。如果能够启动挖矿,则立即开启worker 让其开始干活。只需要发送一个开启挖矿信号,worker 将会被自动触发挖矿工作。
对 worker 发送 start 信号后,该信号将进入 startCh chain中。一旦获得信号,则立即重新开始commit新区块,重新开始干活。
//miner/worker.go:342 for { select { case <-w.startCh: clearPending(w.chain.CurrentBlock().NumberU64()) timestamp = time.Now().Unix() commit(false, commitInterruptNewHead) //... } }
[^1]: dgeth 是本电子书书写期初指导大家所编译的一个 geth 程序。具体见[《开始》]({{< ref "first.md#编译geth" >}})
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8