lbcd/cpuminer.go
Dave Collins a755305a2e
rpcserver: Move error check for generate RPC.
This moves the error check for an attempt to call the generate RPC when
on a network that there is virtually no chance of mining a block with
the CPU into the RPC server where it more naturally belongs.  The caller
of the CPU should be the one to determine if it wants to allow mining or
not.  While here, use a more accurate RPC error code of ErrDifficulty
instead of ErrInternal.

This change is a step towards being able to separate the CPU mining code
into its own subpackage.
2016-10-28 10:58:51 -05:00

643 lines
20 KiB
Go

// Copyright (c) 2014-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"math/rand"
"runtime"
"sync"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/mining"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
const (
// maxNonce is the maximum value a nonce can be in a block header.
maxNonce = ^uint32(0) // 2^32 - 1
// maxExtraNonce is the maximum value an extra nonce used in a coinbase
// transaction can be.
maxExtraNonce = ^uint64(0) // 2^64 - 1
// hpsUpdateSecs is the number of seconds to wait in between each
// update to the hashes per second monitor.
hpsUpdateSecs = 10
// hashUpdateSec is the number of seconds each worker waits in between
// notifying the speed monitor with how many hashes have been completed
// while they are actively searching for a solution. This is done to
// reduce the amount of syncs between the workers that must be done to
// keep track of the hashes per second.
hashUpdateSecs = 15
)
var (
// defaultNumWorkers is the default number of workers to use for mining
// and is based on the number of processor cores. This helps ensure the
// system stays reasonably responsive under heavy load.
defaultNumWorkers = uint32(runtime.NumCPU())
)
// cpuminerConfig is a descriptor containing the cpu miner configuration.
type cpuminerConfig struct {
// ChainParams identifies which chain parameters the cpu miner is
// associated with.
ChainParams *chaincfg.Params
// BlockTemplateGenerator identifies the instance to use in order to
// generate block templates that the miner will attempt to solve.
BlockTemplateGenerator *mining.BlkTmplGenerator
// MiningAddrs is a list of payment addresses to use for the generated
// blocks. Each generated block will randomly choose one of them.
MiningAddrs []btcutil.Address
// ProcessBlock defines the function to call with any solved blocks.
// It typically must run the provided block through the same set of
// rules and handling as any other block coming from the network.
ProcessBlock func(*btcutil.Block, blockchain.BehaviorFlags) (bool, error)
// ConnectedCount defines the function to use to obtain how many other
// peers the server is connected to. This is used by the automatic
// persistent mining routine to determine whether or it should attempt
// mining. This is useful because there is no point in mining when not
// connected to any peers since there would no be anyone to send any
// found blocks to.
ConnectedCount func() int32
// IsCurrent defines the function to use to obtain whether or not the
// block chain is current. This is used by the automatic persistent
// mining routine to determine whether or it should attempt mining.
// This is useful because there is no point in mining if the chain is
// not current since any solved blocks would be on a side chain and and
// up orphaned anyways.
IsCurrent func() bool
}
// CPUMiner provides facilities for solving blocks (mining) using the CPU in
// a concurrency-safe manner. It consists of two main goroutines -- a speed
// monitor and a controller for worker goroutines which generate and solve
// blocks. The number of goroutines can be set via the SetMaxGoRoutines
// function, but the default is based on the number of processor cores in the
// system which is typically sufficient.
type CPUMiner struct {
sync.Mutex
g *mining.BlkTmplGenerator
cfg cpuminerConfig
numWorkers uint32
started bool
discreteMining bool
submitBlockLock sync.Mutex
wg sync.WaitGroup
workerWg sync.WaitGroup
updateNumWorkers chan struct{}
queryHashesPerSec chan float64
updateHashes chan uint64
speedMonitorQuit chan struct{}
quit chan struct{}
}
// speedMonitor handles tracking the number of hashes per second the mining
// process is performing. It must be run as a goroutine.
func (m *CPUMiner) speedMonitor() {
minrLog.Tracef("CPU miner speed monitor started")
var hashesPerSec float64
var totalHashes uint64
ticker := time.NewTicker(time.Second * hpsUpdateSecs)
defer ticker.Stop()
out:
for {
select {
// Periodic updates from the workers with how many hashes they
// have performed.
case numHashes := <-m.updateHashes:
totalHashes += numHashes
// Time to update the hashes per second.
case <-ticker.C:
curHashesPerSec := float64(totalHashes) / hpsUpdateSecs
if hashesPerSec == 0 {
hashesPerSec = curHashesPerSec
}
hashesPerSec = (hashesPerSec + curHashesPerSec) / 2
totalHashes = 0
if hashesPerSec != 0 {
minrLog.Debugf("Hash speed: %6.0f kilohashes/s",
hashesPerSec/1000)
}
// Request for the number of hashes per second.
case m.queryHashesPerSec <- hashesPerSec:
// Nothing to do.
case <-m.speedMonitorQuit:
break out
}
}
m.wg.Done()
minrLog.Tracef("CPU miner speed monitor done")
}
// submitBlock submits the passed block to network after ensuring it passes all
// of the consensus validation rules.
func (m *CPUMiner) submitBlock(block *btcutil.Block) bool {
m.submitBlockLock.Lock()
defer m.submitBlockLock.Unlock()
// Ensure the block is not stale since a new block could have shown up
// while the solution was being found. Typically that condition is
// detected and all work on the stale block is halted to start work on
// a new block, but the check only happens periodically, so it is
// possible a block was found and submitted in between.
latestHash := m.g.BestSnapshot().Hash
msgBlock := block.MsgBlock()
if !msgBlock.Header.PrevBlock.IsEqual(latestHash) {
minrLog.Debugf("Block submitted via CPU miner with previous "+
"block %s is stale", msgBlock.Header.PrevBlock)
return false
}
// Process this block using the same rules as blocks coming from other
// nodes. This will in turn relay it to the network like normal.
isOrphan, err := m.cfg.ProcessBlock(block, blockchain.BFNone)
if err != nil {
// Anything other than a rule violation is an unexpected error,
// so log that error as an internal error.
if _, ok := err.(blockchain.RuleError); !ok {
minrLog.Errorf("Unexpected error while processing "+
"block submitted via CPU miner: %v", err)
return false
}
minrLog.Debugf("Block submitted via CPU miner rejected: %v", err)
return false
}
if isOrphan {
minrLog.Debugf("Block submitted via CPU miner is an orphan")
return false
}
// The block was accepted.
coinbaseTx := block.MsgBlock().Transactions[0].TxOut[0]
minrLog.Infof("Block submitted via CPU miner accepted (hash %s, "+
"amount %v)", block.Hash(), btcutil.Amount(coinbaseTx.Value))
return true
}
// solveBlock attempts to find some combination of a nonce, extra nonce, and
// current timestamp which makes the passed block hash to a value less than the
// target difficulty. The timestamp is updated periodically and the passed
// block is modified with all tweaks during this process. This means that
// when the function returns true, the block is ready for submission.
//
// This function will return early with false when conditions that trigger a
// stale block such as a new block showing up or periodically when there are
// new transactions and enough time has elapsed without finding a solution.
func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32,
ticker *time.Ticker, quit chan struct{}) bool {
// Choose a random extra nonce offset for this block template and
// worker.
enOffset, err := wire.RandomUint64()
if err != nil {
minrLog.Errorf("Unexpected error while generating random "+
"extra nonce offset: %v", err)
enOffset = 0
}
// Create a couple of convenience variables.
header := &msgBlock.Header
targetDifficulty := blockchain.CompactToBig(header.Bits)
// Initial state.
lastGenerated := time.Now()
lastTxUpdate := m.g.TxSource().LastUpdated()
hashesCompleted := uint64(0)
// Note that the entire extra nonce range is iterated and the offset is
// added relying on the fact that overflow will wrap around 0 as
// provided by the Go spec.
for extraNonce := uint64(0); extraNonce < maxExtraNonce; extraNonce++ {
// Update the extra nonce in the block template with the
// new value by regenerating the coinbase script and
// setting the merkle root to the new value. The
m.g.UpdateExtraNonce(msgBlock, blockHeight, extraNonce+enOffset)
// Search through the entire nonce range for a solution while
// periodically checking for early quit and stale block
// conditions along with updates to the speed monitor.
for i := uint32(0); i <= maxNonce; i++ {
select {
case <-quit:
return false
case <-ticker.C:
m.updateHashes <- hashesCompleted
hashesCompleted = 0
// The current block is stale if the best block
// has changed.
best := m.g.BestSnapshot()
if !header.PrevBlock.IsEqual(best.Hash) {
return false
}
// The current block is stale if the memory pool
// has been updated since the block template was
// generated and it has been at least one
// minute.
if lastTxUpdate != m.g.TxSource().LastUpdated() &&
time.Now().After(lastGenerated.Add(time.Minute)) {
return false
}
m.g.UpdateBlockTime(msgBlock)
default:
// Non-blocking select to fall through
}
// Update the nonce and hash the block header. Each
// hash is actually a double sha256 (two hashes), so
// increment the number of hashes completed for each
// attempt accordingly.
header.Nonce = i
hash := header.BlockHash()
hashesCompleted += 2
// The block is solved when the new block hash is less
// than the target difficulty. Yay!
if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
m.updateHashes <- hashesCompleted
return true
}
}
}
return false
}
// generateBlocks is a worker that is controlled by the miningWorkerController.
// It is self contained in that it creates block templates and attempts to solve
// them while detecting when it is performing stale work and reacting
// accordingly by generating a new block template. When a block is solved, it
// is submitted.
//
// It must be run as a goroutine.
func (m *CPUMiner) generateBlocks(quit chan struct{}) {
minrLog.Tracef("Starting generate blocks worker")
// Start a ticker which is used to signal checks for stale work and
// updates to the speed monitor.
ticker := time.NewTicker(time.Second * hashUpdateSecs)
defer ticker.Stop()
out:
for {
// Quit when the miner is stopped.
select {
case <-quit:
break out
default:
// Non-blocking select to fall through
}
// Wait until there is a connection to at least one other peer
// since there is no way to relay a found block or receive
// transactions to work on when there are no connected peers.
if m.cfg.ConnectedCount() == 0 {
time.Sleep(time.Second)
continue
}
// No point in searching for a solution before the chain is
// synced. Also, grab the same lock as used for block
// submission, since the current block will be changing and
// this would otherwise end up building a new block template on
// a block that is in the process of becoming stale.
m.submitBlockLock.Lock()
curHeight := m.g.BestSnapshot().Height
if curHeight != 0 && !m.cfg.IsCurrent() {
m.submitBlockLock.Unlock()
time.Sleep(time.Second)
continue
}
// Choose a payment address at random.
rand.Seed(time.Now().UnixNano())
payToAddr := m.cfg.MiningAddrs[rand.Intn(len(m.cfg.MiningAddrs))]
// Create a new block template using the available transactions
// in the memory pool as a source of transactions to potentially
// include in the block.
template, err := m.g.NewBlockTemplate(payToAddr)
m.submitBlockLock.Unlock()
if err != nil {
errStr := fmt.Sprintf("Failed to create new block "+
"template: %v", err)
minrLog.Errorf(errStr)
continue
}
// Attempt to solve the block. The function will exit early
// with false when conditions that trigger a stale block, so
// a new block template can be generated. When the return is
// true a solution was found, so submit the solved block.
if m.solveBlock(template.Block, curHeight+1, ticker, quit) {
block := btcutil.NewBlock(template.Block)
m.submitBlock(block)
}
}
m.workerWg.Done()
minrLog.Tracef("Generate blocks worker done")
}
// miningWorkerController launches the worker goroutines that are used to
// generate block templates and solve them. It also provides the ability to
// dynamically adjust the number of running worker goroutines.
//
// It must be run as a goroutine.
func (m *CPUMiner) miningWorkerController() {
// launchWorkers groups common code to launch a specified number of
// workers for generating blocks.
var runningWorkers []chan struct{}
launchWorkers := func(numWorkers uint32) {
for i := uint32(0); i < numWorkers; i++ {
quit := make(chan struct{})
runningWorkers = append(runningWorkers, quit)
m.workerWg.Add(1)
go m.generateBlocks(quit)
}
}
// Launch the current number of workers by default.
runningWorkers = make([]chan struct{}, 0, m.numWorkers)
launchWorkers(m.numWorkers)
out:
for {
select {
// Update the number of running workers.
case <-m.updateNumWorkers:
// No change.
numRunning := uint32(len(runningWorkers))
if m.numWorkers == numRunning {
continue
}
// Add new workers.
if m.numWorkers > numRunning {
launchWorkers(m.numWorkers - numRunning)
continue
}
// Signal the most recently created goroutines to exit.
for i := numRunning - 1; i >= m.numWorkers; i-- {
close(runningWorkers[i])
runningWorkers[i] = nil
runningWorkers = runningWorkers[:i]
}
case <-m.quit:
for _, quit := range runningWorkers {
close(quit)
}
break out
}
}
// Wait until all workers shut down to stop the speed monitor since
// they rely on being able to send updates to it.
m.workerWg.Wait()
close(m.speedMonitorQuit)
m.wg.Done()
}
// Start begins the CPU mining process as well as the speed monitor used to
// track hashing metrics. Calling this function when the CPU miner has
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (m *CPUMiner) Start() {
m.Lock()
defer m.Unlock()
// Nothing to do if the miner is already running or if running in discrete
// mode (using GenerateNBlocks).
if m.started || m.discreteMining {
return
}
m.quit = make(chan struct{})
m.speedMonitorQuit = make(chan struct{})
m.wg.Add(2)
go m.speedMonitor()
go m.miningWorkerController()
m.started = true
minrLog.Infof("CPU miner started")
}
// Stop gracefully stops the mining process by signalling all workers, and the
// speed monitor to quit. Calling this function when the CPU miner has not
// already been started will have no effect.
//
// This function is safe for concurrent access.
func (m *CPUMiner) Stop() {
m.Lock()
defer m.Unlock()
// Nothing to do if the miner is not currently running or if running in
// discrete mode (using GenerateNBlocks).
if !m.started || m.discreteMining {
return
}
close(m.quit)
m.wg.Wait()
m.started = false
minrLog.Infof("CPU miner stopped")
}
// IsMining returns whether or not the CPU miner has been started and is
// therefore currenting mining.
//
// This function is safe for concurrent access.
func (m *CPUMiner) IsMining() bool {
m.Lock()
defer m.Unlock()
return m.started
}
// HashesPerSecond returns the number of hashes per second the mining process
// is performing. 0 is returned if the miner is not currently running.
//
// This function is safe for concurrent access.
func (m *CPUMiner) HashesPerSecond() float64 {
m.Lock()
defer m.Unlock()
// Nothing to do if the miner is not currently running.
if !m.started {
return 0
}
return <-m.queryHashesPerSec
}
// SetNumWorkers sets the number of workers to create which solve blocks. Any
// negative values will cause a default number of workers to be used which is
// based on the number of processor cores in the system. A value of 0 will
// cause all CPU mining to be stopped.
//
// This function is safe for concurrent access.
func (m *CPUMiner) SetNumWorkers(numWorkers int32) {
if numWorkers == 0 {
m.Stop()
}
// Don't lock until after the first check since Stop does its own
// locking.
m.Lock()
defer m.Unlock()
// Use default if provided value is negative.
if numWorkers < 0 {
m.numWorkers = defaultNumWorkers
} else {
m.numWorkers = uint32(numWorkers)
}
// When the miner is already running, notify the controller about the
// the change.
if m.started {
m.updateNumWorkers <- struct{}{}
}
}
// NumWorkers returns the number of workers which are running to solve blocks.
//
// This function is safe for concurrent access.
func (m *CPUMiner) NumWorkers() int32 {
m.Lock()
defer m.Unlock()
return int32(m.numWorkers)
}
// GenerateNBlocks generates the requested number of blocks. It is self
// contained in that it creates block templates and attempts to solve them while
// detecting when it is performing stale work and reacting accordingly by
// generating a new block template. When a block is solved, it is submitted.
// The function returns a list of the hashes of generated blocks.
func (m *CPUMiner) GenerateNBlocks(n uint32) ([]*chainhash.Hash, error) {
m.Lock()
// Respond with an error if server is already mining.
if m.started || m.discreteMining {
m.Unlock()
return nil, errors.New("Server is already CPU mining. Please call " +
"`setgenerate 0` before calling discrete `generate` commands.")
}
m.started = true
m.discreteMining = true
m.speedMonitorQuit = make(chan struct{})
m.wg.Add(1)
go m.speedMonitor()
m.Unlock()
minrLog.Tracef("Generating %d blocks", n)
i := uint32(0)
blockHashes := make([]*chainhash.Hash, n, n)
// Start a ticker which is used to signal checks for stale work and
// updates to the speed monitor.
ticker := time.NewTicker(time.Second * hashUpdateSecs)
defer ticker.Stop()
for {
// Read updateNumWorkers in case someone tries a `setgenerate` while
// we're generating. We can ignore it as the `generate` RPC call only
// uses 1 worker.
select {
case <-m.updateNumWorkers:
default:
}
// Grab the lock used for block submission, since the current block will
// be changing and this would otherwise end up building a new block
// template on a block that is in the process of becoming stale.
m.submitBlockLock.Lock()
curHeight := m.g.BestSnapshot().Height
// Choose a payment address at random.
rand.Seed(time.Now().UnixNano())
payToAddr := m.cfg.MiningAddrs[rand.Intn(len(m.cfg.MiningAddrs))]
// Create a new block template using the available transactions
// in the memory pool as a source of transactions to potentially
// include in the block.
template, err := m.g.NewBlockTemplate(payToAddr)
m.submitBlockLock.Unlock()
if err != nil {
errStr := fmt.Sprintf("Failed to create new block "+
"template: %v", err)
minrLog.Errorf(errStr)
continue
}
// Attempt to solve the block. The function will exit early
// with false when conditions that trigger a stale block, so
// a new block template can be generated. When the return is
// true a solution was found, so submit the solved block.
if m.solveBlock(template.Block, curHeight+1, ticker, nil) {
block := btcutil.NewBlock(template.Block)
m.submitBlock(block)
blockHashes[i] = block.Hash()
i++
if i == n {
minrLog.Tracef("Generated %d blocks", i)
m.Lock()
close(m.speedMonitorQuit)
m.wg.Wait()
m.started = false
m.discreteMining = false
m.Unlock()
return blockHashes, nil
}
}
}
}
// newCPUMiner returns a new instance of a CPU miner for the provided server.
// Use Start to begin the mining process. See the documentation for CPUMiner
// type for more details.
func newCPUMiner(cfg *cpuminerConfig) *CPUMiner {
return &CPUMiner{
g: cfg.BlockTemplateGenerator,
cfg: *cfg,
numWorkers: defaultNumWorkers,
updateNumWorkers: make(chan struct{}),
queryHashesPerSec: make(chan float64),
updateHashes: make(chan uint64),
}
}