-
Notifications
You must be signed in to change notification settings - Fork 108
Expand file tree
/
Copy pathminer.go
More file actions
506 lines (444 loc) · 18.1 KB
/
Copy pathminer.go
File metadata and controls
506 lines (444 loc) · 18.1 KB
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
// TODO(DELETEME): This entire file is replaced by remote_miner.go. We should
// delete all of this code and use remote_miner in all the places where we currently
// use the miner. The reason we don't do this now is it would break a lot of test cases
// that we have.
package lib
import (
"encoding/hex"
"fmt"
"math/big"
"math/rand"
"reflect"
"sync/atomic"
"time"
"github.com/btcsuite/btcd/wire"
"github.com/deso-protocol/core/collections"
"github.com/deso-protocol/core/desohash"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/davecgh/go-spew/spew"
merkletree "github.com/deso-protocol/go-merkle-tree"
"github.com/golang/glog"
"github.com/pkg/errors"
)
// miner.go contains all of the logic for mining blocks with a CPU.
type DeSoMiner struct {
PublicKeys []*btcec.PublicKey
numThreads uint32
BlockProducer *DeSoBlockProducer
params *DeSoParams
blockMinedListeners *collections.ConcurrentList[func(*MsgDeSoBlock)]
stopping int32
}
func NewDeSoMiner(_minerPublicKeys []string, _numThreads uint32,
_blockProducer *DeSoBlockProducer, _params *DeSoParams) (*DeSoMiner, error) {
// Convert the public keys from Base58Check encoding to bytes.
_pubKeys := []*btcec.PublicKey{}
for _, publicKeyBase58 := range _minerPublicKeys {
pkBytes, _, err := Base58CheckDecode(publicKeyBase58)
if err != nil {
return nil, errors.Wrapf(err, "NewDeSoMiner: ")
}
pkObj, err := btcec.ParsePubKey(pkBytes)
if err != nil {
return nil, errors.Wrapf(err, "NewDeSoMiner: ")
}
_pubKeys = append(_pubKeys, pkObj)
}
return &DeSoMiner{
PublicKeys: _pubKeys,
numThreads: _numThreads,
BlockProducer: _blockProducer,
params: _params,
blockMinedListeners: collections.NewConcurrentList[func(*MsgDeSoBlock)](),
}, nil
}
func (desoMiner *DeSoMiner) AddBlockMinedListener(ff func(*MsgDeSoBlock)) {
desoMiner.blockMinedListeners.Add(ff)
}
func (desoMiner *DeSoMiner) Stop() {
atomic.AddInt32(&desoMiner.stopping, 1)
}
func (desoMiner *DeSoMiner) _getBlockToMine(threadIndex uint32) (
_blk *MsgDeSoBlock, _diffTarget *BlockHash, _lastNode *BlockNode, _err error) {
// Choose a random address to contribute the coins to. Use the extraNonce to
// choose the random address since it's random.
var rewardPk *btcec.PublicKey
if len(desoMiner.PublicKeys) == 0 {
// This is to account for a really weird edge case where somebody stops the miner
// in the middle of us getting a block.
rewardPk = nil
} else {
randomNum, _ := wire.RandomUint64()
pkIndex := int(randomNum % uint64(len(desoMiner.PublicKeys)))
rewardPk = desoMiner.PublicKeys[pkIndex]
}
return desoMiner.BlockProducer._getBlockTemplate(rewardPk.SerializeCompressed())
}
func (desoMiner *DeSoMiner) _getRandomPublicKey() []byte {
rand.Seed(time.Now().Unix())
return desoMiner.PublicKeys[rand.Intn(len(desoMiner.PublicKeys))].SerializeCompressed()
}
func (desoMiner *DeSoMiner) _mineSingleBlock(threadIndex uint32) (_diffTarget *BlockHash, minedBlock *MsgDeSoBlock) {
for {
// This provides a way for outside processes to pause the miner.
if len(desoMiner.PublicKeys) == 0 {
if atomic.LoadInt32(&desoMiner.stopping) == 1 {
glog.V(1).Infof("DeSoMiner._startThread: Stopping thread %d", threadIndex)
break
}
time.Sleep(1 * time.Second)
continue
}
// Get a single header to hash on from our BlockProducer. This will have a unique
// ExtraNonce associated with it, making the hash space this thread is mining on
// different from all the other threads.
//
// TODO(miner): Replace with a call to GetBlockTemplate
publicKey := desoMiner._getRandomPublicKey()
blockID, headerBytes, extraNonces, diffTarget, err := desoMiner.BlockProducer.GetHeadersAndExtraDatas(
publicKey, 1 /*numHeaders*/, CurrentHeaderVersion)
if err != nil {
glog.Errorf("DeSoMiner._startThread: Error getting header to "+
"hash on; this should never happen unless we're starting up: %v", err)
time.Sleep(1 * time.Second)
continue
}
header := &MsgDeSoHeader{}
if err := header.FromBytes(headerBytes[0]); err != nil {
glog.Errorf("DeSoMiner._startThread: Error parsing header to " +
"hash on; this should never happen")
time.Sleep(1 * time.Second)
continue
}
// Compute a few hashes before checking if we've solved the block.
timeBefore := time.Now()
bestHash, bestNonce, err := FindLowestHash(header, desoMiner.params.MiningIterationsPerCycle)
glog.V(3).Infof("DeSoMiner._startThread: Time per iteration: %v", time.Since(timeBefore))
if err != nil {
// If there's an error just log it and break out.
glog.Error(errors.Wrapf(err, "DeSoMiner._startThread: Problem while mining: "))
break
}
if atomic.LoadInt32(&desoMiner.stopping) == 1 {
glog.V(1).Infof("DeSoMiner._startThread: Stopping thread %d", threadIndex)
break
}
if LessThan(diffTarget, bestHash) {
//glog.V(2).Infof("DeSoMiner._startThread: Best hash found %v does not beat target %v",
//hex.EncodeToString(bestHash[:]), hex.EncodeToString(diffTarget[:]))
continue
}
// If we get here then it means our bestHash has beaten the target and
// that bestNonce is the nonce that generates the solution hash.
// Set the winning nonce on the block's header.
blockToMine, err := desoMiner.BlockProducer.GetCopyOfRecentBlock(blockID)
if err != nil {
glog.Errorf("DeSoMiner._startThread: Error getting block for blockID %v; "+
"this should never happen", blockID)
time.Sleep(1 * time.Second)
continue
}
// Swap in the public key and extraNonce. This should make the block consistent with
// the header we were just mining on.
blockToMine.Txns[0].TxOutputs[0].PublicKey = publicKey
blockToMine.Txns[0].TxnMeta.(*BlockRewardMetadataa).ExtraData = UintToBuf(extraNonces[0])
blockToMine, err = RecomputeBlockRewardWithBlockRewardOutputPublicKey(blockToMine, publicKey, desoMiner.params)
if err != nil {
glog.Errorf("DeSoMiner._startThread: Error recomputing block reward: %v", err)
time.Sleep(1 * time.Second)
continue
}
// Set the header for the block, which should update the merkle root.
blockToMine.Header = header
// Use the nonce we computed
blockToMine.Header.Nonce = bestNonce
return diffTarget, blockToMine
}
return nil, nil
}
func (desoMiner *DeSoMiner) MineAndProcessSingleBlock(threadIndex uint32, mempoolToUpdate *DeSoMempool) (_block *MsgDeSoBlock, _err error) {
// Add a call to update the BlockProducer.
// TODO(performance): We shouldn't have to do this, it just makes tests pass right now.
if err := desoMiner.BlockProducer.UpdateLatestBlockTemplate(); err != nil {
// Error if we can't update the template but don't stop the show.
glog.Error(err)
}
diffTarget, blockToMine := desoMiner._mineSingleBlock(threadIndex)
if blockToMine == nil {
return nil, fmt.Errorf("DeSoMiner._startThread: _mineSingleBlock returned nil; should only happen if we're stopping")
}
// Log information on the block we just mined.
bestHash, _ := blockToMine.Hash()
glog.Infof("================== YOU MINED A NEW BLOCK! ================== Height: %d, Hash: %s", blockToMine.Header.Height, hex.EncodeToString(bestHash[:]))
glog.V(1).Infof("Height: (%d), Diff target: (%s), "+
"New hash: (%s), , Header Tip: %v, Block Tip: %v", blockToMine.Header.Height,
hex.EncodeToString(diffTarget[:])[:10], hex.EncodeToString(bestHash[:]),
desoMiner.BlockProducer.chain.headerTip().Header,
desoMiner.BlockProducer.chain.blockTip().Header)
scs := spew.ConfigState{DisableMethods: true, Indent: " ", DisablePointerAddresses: true}
glog.V(1).Infof(scs.Sdump(blockToMine))
// Sanitize the block for the comparison we're about to do. We need to do
// this because the comparison function below will think they're different
// if one has nil and one has an empty list. Annoying, but this solves the
// issue.
for _, tx := range blockToMine.Txns {
if len(tx.TxInputs) == 0 {
tx.TxInputs = nil
}
}
blockBytes, err := blockToMine.ToBytes(false)
if err != nil {
glog.Error(err)
return nil, err
}
glog.V(1).Infof("Block bytes hex %d: %s", blockToMine.Header.Height, hex.EncodeToString(blockBytes))
blockFromBytes := &MsgDeSoBlock{}
err = blockFromBytes.FromBytes(blockBytes)
if err != nil || !reflect.DeepEqual(*blockToMine, *blockFromBytes) {
glog.Error(err)
fmt.Println("Block as it was mined: ", *blockToMine)
scs.Dump(blockToMine)
fmt.Println("Block as it was de-serialized:", *blockFromBytes)
scs.Dump(blockFromBytes)
glog.V(1).Infof("In case you missed the hex %d: %s", blockToMine.Header.Height, hex.EncodeToString(blockBytes))
glog.Errorf("DeSoMiner.MineAndProcessSingleBlock: ERROR: Problem with block "+
"serialization (see above for dumps of blocks): Diff: %v, err?: %v", Diff(blockToMine, blockFromBytes), err)
}
glog.V(2).Infof("Mined block height:num_txns: %d:%d\n", blockToMine.Header.Height, len(blockToMine.Txns))
// TODO: This is duplicate code, but this whole file should probably be deleted or
// reworked to use the block producer API anyway.
if err := desoMiner.BlockProducer.SignBlock(blockToMine); err != nil {
return nil, fmt.Errorf("Error signing block: %v", err)
}
// Process the block. If the block is connected and/or accepted, the Server
// will be informed about it. This will cause it to be relayed appropriately.
verifySignatures := true
// TODO(miner): Replace with a call to SubmitBlock.
isMainChain, isOrphan, _, err := desoMiner.BlockProducer.chain.ProcessBlock(
blockToMine, verifySignatures)
glog.V(2).Infof("Called ProcessBlock: isMainChain=(%v), isOrphan=(%v), err=(%v)",
isMainChain, isOrphan, err)
if err != nil {
glog.Errorf("ERROR calling ProcessBlock: isMainChain=(%v), isOrphan=(%v), err=(%v)",
isMainChain, isOrphan, err)
// We return the block even when we have an error in case the caller wants to do
// something with it.
return blockToMine, fmt.Errorf("ERROR calling ProcessBlock: isMainChain=(%v), isOrphan=(%v), err=(%v)",
isMainChain, isOrphan, err)
}
// If a mempool object is passed then update it. Normally this isn't necessary because
// ProcessBlock will trigger it because the backendServer will be set on the blockchain
// object. But it's useful for tests.
if mempoolToUpdate != nil {
mempoolToUpdate.UpdateAfterConnectBlock(blockToMine)
}
decimalPlaces := int64(1000)
diffTargetBaseline, _ := hex.DecodeString(desoMiner.params.MinDifficultyTargetHex)
diffTargetBaselineBlockHash := BlockHash{}
copy(diffTargetBaselineBlockHash[:], diffTargetBaseline)
diffTargetBaselineBigint := big.NewInt(0).Mul(HashToBigint(&diffTargetBaselineBlockHash), big.NewInt(decimalPlaces))
diffTargetBigint := HashToBigint(diffTarget)
glog.V(1).Infof("Difficulty factor (1 = 1 core running): %v", float32(big.NewInt(0).Div(diffTargetBaselineBigint, diffTargetBigint).Int64())/float32(decimalPlaces))
if atomic.LoadInt32(&desoMiner.stopping) == 1 {
return nil, fmt.Errorf("DeSoMiner._startThread: Stopping thread %d", threadIndex)
}
return blockToMine, nil
}
func (desoMiner *DeSoMiner) _startThread(threadIndex uint32) {
for {
if desoMiner.BlockProducer.chain.chainState() != SyncStateFullyCurrent {
time.Sleep(1 * time.Second)
continue
}
// Exit if blockchain has connected a block at the final PoW block height.
currentTip := desoMiner.BlockProducer.chain.blockTip()
if currentTip.Header.Height >= desoMiner.params.GetFinalPoWBlockHeight() {
return
}
newBlock, err := desoMiner.MineAndProcessSingleBlock(threadIndex, nil /*mempoolToUpdate*/)
if err != nil {
glog.Errorf(err.Error())
}
isFinished := (newBlock == nil)
if isFinished {
return
}
blockMinedListeners := desoMiner.blockMinedListeners.GetAll()
for _, listener := range blockMinedListeners {
listener(newBlock)
}
}
}
func (desoMiner *DeSoMiner) Start() {
if desoMiner.BlockProducer == nil {
glog.Infof("DeSoMiner.Start: NOT starting miner because " +
"max_block_templates_to_cache = 0; set it to a non-zero value to " +
"start the miner")
return
}
blockTip := desoMiner.BlockProducer.chain.blockTip()
if desoMiner.params.IsPoSBlockHeight(blockTip.Header.Height) {
glog.Infof("DeSoMiner.Start: NOT starting miner because we are at a PoS block height %d", blockTip.Header.Height)
return
}
glog.Infof("DeSoMiner.Start: Starting miner with difficulty target %s", desoMiner.params.MinDifficultyTargetHex)
glog.Infof("DeSoMiner.Start: Block tip height %d, cum work %v, and difficulty %v",
blockTip.Header.Height, BigintToHash(blockTip.CumWork), blockTip.DifficultyTarget)
// Start a bunch of threads to mine for blocks.
for threadIndex := uint32(0); threadIndex < desoMiner.numThreads; threadIndex++ {
go func(threadIndex uint32) {
glog.V(1).Infof("DeSoMiner.Start: Starting thread %d", threadIndex)
desoMiner._startThread(threadIndex)
}(threadIndex)
}
}
func CopyBytesIntoBlockHash(data []byte) *BlockHash {
if len(data) != HashSizeBytes {
errorStr := fmt.Sprintf("CopyBytesIntoBlockHash: Got data of size %d for BlockHash of size %d", len(data), HashSizeBytes)
glog.Error(errorStr)
return nil
}
var blockHash BlockHash
copy(blockHash[:], data)
return &blockHash
}
// ProofOfWorkHash is a hash function designed for computing DeSo block hashes.
// It seems the optimal hash function is one that satisfies two properties:
// 1. It is not computable by any existing ASICs. If this property isn't satisfied
// then miners with pre-existing investments in ASICs for other coins can very
// cheaply mine on our chain for a short period of time to pull off a 51% attack.
// This has actually happened with "merge-mined" coins like Namecoin.
// 2. If implemented on an ASIC, there is an "orders of magnitude" speed-up over
// using a CPU or GPU. This is because ASICs require some amount of capital
// expenditure up-front in order to mine, which then aligns the owner of the
// ASIC to care about the health of the network over a longer period of time. In
// contrast, a hash function that is CPU or GPU-mineable can be attacked with
// an AWS fleet early on. This also may result in a more eco-friendly chain, since
// the hash power will be more bottlenecked by up-front CapEx rather than ongoing
// electricity cost, as is the case with GPU-mined coins.
//
// Note that our pursuit of (2) above runs counter to existing dogma which seeks to
// prioritize "ASIC-resistance" in hash functions.
//
// Given the above, the hash function chosen is a simple twist on sha3
// that we don't think any ASIC exists for currently. Note that creating an ASIC for
// this should be relatively straightforward, however, which allows us to satisfy
// property (2) above.
func ProofOfWorkHash(inputBytes []byte, version uint32) *BlockHash {
output := BlockHash{}
if version == HeaderVersion0 {
hashBytes := desohash.DeSoHashV0(inputBytes)
copy(output[:], hashBytes[:])
} else if version == HeaderVersion1 {
hashBytes := desohash.DeSoHashV1(inputBytes)
copy(output[:], hashBytes[:])
} else {
// If we don't recognize the version, we return the v0 hash. We do
// this to avoid having to return an error or panic.
hashBytes := desohash.DeSoHashV0(inputBytes)
copy(output[:], hashBytes[:])
}
return &output
}
func Sha256DoubleHash(input []byte) *BlockHash {
hashBytes := merkletree.Sha256DoubleHash(input)
ret := &BlockHash{}
copy(ret[:], hashBytes[:])
return ret
}
func HashToBigint(hash *BlockHash) *big.Int {
// No need to check errors since the string is necessarily a valid hex
// string.
val, itWorked := new(big.Int).SetString(hex.EncodeToString(hash[:]), 16)
if !itWorked {
glog.Errorf("Failed in converting []byte (%#v) to bigint.", hash)
}
return val
}
func BigintToHash(bigint *big.Int) *BlockHash {
if bigint == nil {
glog.Errorf("BigintToHash: Bigint is nil")
return nil
}
hexStr := bigint.Text(16)
if len(hexStr)%2 != 0 {
// If we have an odd number of bytes add one to the beginning (remember
// the bigints are big-endian.
hexStr = "0" + hexStr
}
hexBytes, err := hex.DecodeString(hexStr)
if err != nil {
glog.Errorf("Failed in converting bigint (%#v) with hex "+
"string (%s) to hash.", bigint, hexStr)
return nil
}
if len(hexBytes) > HashSizeBytes {
glog.Errorf("BigintToHash: Bigint %v overflows the hash size %d", bigint, HashSizeBytes)
return nil
}
var retBytes BlockHash
copy(retBytes[HashSizeBytes-len(hexBytes):], hexBytes)
return &retBytes
}
func BytesToBigint(bb []byte) *big.Int {
val, itWorked := new(big.Int).SetString(hex.EncodeToString(bb), 16)
if !itWorked {
glog.Errorf("Failed in converting []byte (%#v) to bigint.", bb)
}
return val
}
func BigintToBytes(bigint *big.Int) []byte {
hexStr := bigint.Text(16)
if len(hexStr)%2 != 0 {
// If we have an odd number of bytes add one to the beginning (remember
// the bigints are big-endian.
hexStr = "0" + hexStr
}
hexBytes, err := hex.DecodeString(hexStr)
if err != nil {
glog.Errorf("Failed in converting bigint (%#v) with hex "+
"string (%s) to []byte.", bigint, hexStr)
}
return hexBytes
}
// FindLowestHash
// Mine for a given number of iterations and return the lowest hash value
// found and its associated nonce. Hashing starts at the value of the Nonce
// set on the blockHeader field when it is passed and increments the value
// of the passed blockHeader field as it iterates. This makes it easy to
// continue a subsequent batch of iterations after we return.
func FindLowestHash(
blockHeaderr *MsgDeSoHeader, iterations uint64) (
lowestHash *BlockHash, lowestNonce uint64, ee error) {
// Compute a hash of the header with the current nonce value.
bestNonce := blockHeaderr.Nonce
bestHash, err := blockHeaderr.Hash()
if err != nil {
return nil, 0, err
}
for iterations > 0 {
// Increment the nonce.
blockHeaderr.Nonce++
// Compute a new hash.
currentHash, err := blockHeaderr.Hash()
if err != nil {
return nil, 0, err
}
// See if it's better than what we currently have
if LessThan(currentHash, bestHash) {
bestHash = currentHash
bestNonce = blockHeaderr.Nonce
}
iterations--
}
// Increment the nonce one last time since we checked this hash.
blockHeaderr.Nonce++
return bestHash, bestNonce, nil
}
func LessThan(aa *BlockHash, bb *BlockHash) bool {
aaBigint := new(big.Int)
aaBigint.SetBytes(aa[:])
bbBigint := new(big.Int)
bbBigint.SetBytes(bb[:])
return aaBigint.Cmp(bbBigint) < 0
}