// Copyright (c) 2014 Conformal Systems LLC. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package main import ( "fmt" "math/rand" "runtime" "sync" "time" "github.com/btcsuite/btcutil" "github.com/conformal/btcchain" "github.com/conformal/btcwire" ) 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()) ) // 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 server *server numWorkers uint32 started 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.server.blockManager.chainState.Best() 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.server.blockManager.ProcessBlock(block, btcchain.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.(btcchain.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. blockSha, _ := block.Sha() coinbaseTx := block.MsgBlock().Transactions[0].TxOut[0] minrLog.Infof("Block submitted via CPU miner accepted (hash %s, "+ "amount %v)", blockSha, 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 *btcwire.MsgBlock, blockHeight int64, ticker *time.Ticker, quit chan struct{}) bool { // Choose a random extra nonce offset for this block template and // worker. enOffset, err := btcwire.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 := btcchain.CompactToBig(header.Bits) // Initial state. lastGenerated := time.Now() lastTxUpdate := m.server.txMemPool.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 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. bestHash, _ := m.server.blockManager.chainState.Best() if !header.PrevBlock.IsEqual(bestHash) { 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.server.txMemPool.LastUpdated() && time.Now().After(lastGenerated.Add(time.Minute)) { return false } UpdateBlockTime(msgBlock, m.server.blockManager) 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.BlockSha() hashesCompleted += 2 // The block is solved when the new block hash is less // than the target difficulty. Yay! if btcchain.ShaHashToBig(&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.server.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.server.blockManager.chainState.Best() if curHeight != 0 && !m.server.blockManager.IsCurrent() { m.submitBlockLock.Unlock() time.Sleep(time.Second) continue } // Choose a payment address at random. rand.Seed(time.Now().UnixNano()) payToAddr := cfg.miningAddrs[rand.Intn(len(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 := NewBlockTemplate(m.server.txMemPool, 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. if m.started { 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. if !m.started { 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) } // 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(s *server) *CPUMiner { return &CPUMiner{ server: s, numWorkers: defaultNumWorkers, updateNumWorkers: make(chan struct{}), queryHashesPerSec: make(chan float64), updateHashes: make(chan uint64), } }