forked from btcsuite/btcd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cpuminer.go
616 lines (531 loc) · 18.8 KB
/
cpuminer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
// 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/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())
)
// 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
policy *mining.Policy
txSource mining.TxSource
server *server
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.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, 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.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
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.txSource.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.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.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.policy, m.server, 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 there's virtually 0 chance of CPU-mining a block.
if !m.server.chainParams.GenerateSupported {
m.Unlock()
return nil, errors.New("No support for `generate` on the current " +
"network, " + m.server.chainParams.Net.String() +
", as it's unlikely to be possible to CPU-mine a block.")
}
// 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.server.blockManager.chainState.Best()
// 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.policy, m.server, 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(policy *mining.Policy, s *server) *CPUMiner {
return &CPUMiner{
policy: policy,
txSource: s.txMemPool,
server: s,
numWorkers: defaultNumWorkers,
updateNumWorkers: make(chan struct{}),
queryHashesPerSec: make(chan float64),
updateHashes: make(chan uint64),
}
}