// 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 cpuminer import ( "errors" "fmt" "math/rand" "runtime" "sync" "time" "github.com/lbryio/lbcd/blockchain" "github.com/lbryio/lbcd/chaincfg" "github.com/lbryio/lbcd/chaincfg/chainhash" "github.com/lbryio/lbcd/mining" "github.com/lbryio/lbcd/wire" btcutil "github.com/lbryio/lbcutil" ) 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()) ) // Config is a descriptor containing the cpu miner configuration. type Config 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 Config 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() { log.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 { log.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() log.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. msgBlock := block.MsgBlock() if !msgBlock.Header.PrevBlock.IsEqual(&m.g.BestSnapshot().Hash) { log.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 { log.Errorf("Unexpected error while processing "+ "block submitted via CPU miner: %v", err) return false } log.Debugf("Block submitted via CPU miner rejected: %v", err) return false } if isOrphan { log.Debugf("Block submitted via CPU miner is an orphan") return false } // The block was accepted. coinbaseTx := block.MsgBlock().Transactions[0].TxOut[0] log.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 { log.Errorf("Unexpected error while generating random "+ "extra nonce offset: %v", err) enOffset = 0 } // Create some 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. 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.BlockPoWHash() 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{}) { log.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) log.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() log.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 log.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 log.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, payToAddr btcutil.Address) ([]*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() log.Tracef("Generating %d blocks", n) i := uint32(0) blockHashes := make([]*chainhash.Hash, 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()) if payToAddr == nil { 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) log.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 { log.Tracef("Generated %d blocks", i) m.Lock() close(m.speedMonitorQuit) m.wg.Wait() m.started = false m.discreteMining = false m.Unlock() return blockHashes, nil } } } } // New returns a new instance of a CPU miner for the provided configuration. // Use Start to begin the mining process. See the documentation for CPUMiner // type for more details. func New(cfg *Config) *CPUMiner { return &CPUMiner{ g: cfg.BlockTemplateGenerator, cfg: *cfg, numWorkers: defaultNumWorkers, updateNumWorkers: make(chan struct{}), queryHashesPerSec: make(chan float64), updateHashes: make(chan uint64, 512), } }