github.com/deso-protocol/core@v1.2.9/lib/miner.go (about) 1 // TODO(DELETEME): This entire file is replaced by remote_miner.go. We should 2 // delete all of this code and use remote_miner in all the places where we currently 3 // use the miner. The reason we don't do this now is it would break a lot of test cases 4 // that we have. 5 package lib 6 7 import ( 8 "encoding/hex" 9 "fmt" 10 "math/big" 11 "math/rand" 12 "reflect" 13 "sync/atomic" 14 "time" 15 16 "github.com/btcsuite/btcd/wire" 17 "github.com/deso-protocol/core/desohash" 18 19 "github.com/btcsuite/btcd/btcec" 20 "github.com/davecgh/go-spew/spew" 21 merkletree "github.com/deso-protocol/go-merkle-tree" 22 "github.com/golang/glog" 23 "github.com/pkg/errors" 24 ) 25 26 // miner.go contains all of the logic for mining blocks with a CPU. 27 28 type DeSoMiner struct { 29 PublicKeys []*btcec.PublicKey 30 numThreads uint32 31 BlockProducer *DeSoBlockProducer 32 params *DeSoParams 33 34 stopping int32 35 } 36 37 func NewDeSoMiner(_minerPublicKeys []string, _numThreads uint32, 38 _blockProducer *DeSoBlockProducer, _params *DeSoParams) (*DeSoMiner, error) { 39 40 // Convert the public keys from Base58Check encoding to bytes. 41 _pubKeys := []*btcec.PublicKey{} 42 for _, publicKeyBase58 := range _minerPublicKeys { 43 pkBytes, _, err := Base58CheckDecode(publicKeyBase58) 44 if err != nil { 45 return nil, errors.Wrapf(err, "NewDeSoMiner: ") 46 } 47 pkObj, err := btcec.ParsePubKey(pkBytes, btcec.S256()) 48 if err != nil { 49 return nil, errors.Wrapf(err, "NewDeSoMiner: ") 50 } 51 _pubKeys = append(_pubKeys, pkObj) 52 } 53 54 return &DeSoMiner{ 55 PublicKeys: _pubKeys, 56 numThreads: _numThreads, 57 BlockProducer: _blockProducer, 58 params: _params, 59 }, nil 60 } 61 62 func (desoMiner *DeSoMiner) Stop() { 63 atomic.AddInt32(&desoMiner.stopping, 1) 64 } 65 66 func (desoMiner *DeSoMiner) _getBlockToMine(threadIndex uint32) ( 67 _blk *MsgDeSoBlock, _diffTarget *BlockHash, _lastNode *BlockNode, _err error) { 68 69 // Choose a random address to contribute the coins to. Use the extraNonce to 70 // choose the random address since it's random. 71 var rewardPk *btcec.PublicKey 72 if len(desoMiner.PublicKeys) == 0 { 73 // This is to account for a really weird edge case where somebody stops the miner 74 // in the middle of us getting a block. 75 rewardPk = nil 76 } else { 77 randomNum, _ := wire.RandomUint64() 78 pkIndex := int(randomNum % uint64(len(desoMiner.PublicKeys))) 79 rewardPk = desoMiner.PublicKeys[pkIndex] 80 } 81 82 return desoMiner.BlockProducer._getBlockTemplate(rewardPk.SerializeCompressed()) 83 } 84 85 func (desoMiner *DeSoMiner) _getRandomPublicKey() []byte { 86 rand.Seed(time.Now().Unix()) 87 return desoMiner.PublicKeys[rand.Intn(len(desoMiner.PublicKeys))].SerializeCompressed() 88 } 89 90 func (desoMiner *DeSoMiner) _mineSingleBlock(threadIndex uint32) (_diffTarget *BlockHash, minedBlock *MsgDeSoBlock) { 91 for { 92 // This provides a way for outside processes to pause the miner. 93 if len(desoMiner.PublicKeys) == 0 { 94 if atomic.LoadInt32(&desoMiner.stopping) == 1 { 95 glog.V(1).Infof("DeSoMiner._startThread: Stopping thread %d", threadIndex) 96 break 97 } 98 time.Sleep(1 * time.Second) 99 continue 100 } 101 102 // Get a single header to hash on from our BlockProducer. This will have a unique 103 // ExtraNonce associated with it, making the hash space this thread is mining on 104 // different from all the other threads. 105 // 106 // TODO(miner): Replace with a call to GetBlockTemplate 107 publicKey := desoMiner._getRandomPublicKey() 108 blockID, headerBytes, extraNonces, diffTarget, err := desoMiner.BlockProducer.GetHeadersAndExtraDatas( 109 publicKey, 1 /*numHeaders*/, CurrentHeaderVersion) 110 if err != nil { 111 glog.Errorf("DeSoMiner._startThread: Error getting header to "+ 112 "hash on; this should never happen unless we're starting up: %v", err) 113 time.Sleep(1 * time.Second) 114 continue 115 } 116 header := &MsgDeSoHeader{} 117 if err := header.FromBytes(headerBytes[0]); err != nil { 118 glog.Errorf("DeSoMiner._startThread: Error parsing header to " + 119 "hash on; this should never happen") 120 time.Sleep(1 * time.Second) 121 continue 122 } 123 124 // Compute a few hashes before checking if we've solved the block. 125 timeBefore := time.Now() 126 bestHash, bestNonce, err := FindLowestHash(header, uint64(desoMiner.params.MiningIterationsPerCycle)) 127 glog.V(2).Infof("DeSoMiner._startThread: Time per iteration: %v", time.Since(timeBefore)) 128 if err != nil { 129 // If there's an error just log it and break out. 130 glog.Error(errors.Wrapf(err, "DeSoMiner._startThread: Problem while mining: ")) 131 break 132 } 133 134 if atomic.LoadInt32(&desoMiner.stopping) == 1 { 135 glog.V(1).Infof("DeSoMiner._startThread: Stopping thread %d", threadIndex) 136 break 137 } 138 139 if LessThan(diffTarget, bestHash) { 140 //glog.V(2).Infof("DeSoMiner._startThread: Best hash found %v does not beat target %v", 141 //hex.EncodeToString(bestHash[:]), hex.EncodeToString(diffTarget[:])) 142 continue 143 } 144 145 // If we get here then it means our bestHash has beaten the target and 146 // that bestNonce is the nonce that generates the solution hash. 147 148 // Set the winning nonce on the block's header. 149 blockToMine, err := desoMiner.BlockProducer.GetCopyOfRecentBlock(blockID) 150 if err != nil { 151 glog.Errorf("DeSoMiner._startThread: Error getting block for blockID %v; "+ 152 "this should never happen", blockID) 153 time.Sleep(1 * time.Second) 154 continue 155 } 156 157 // Swap in the public key and extraNonce. This should make the block consistent with 158 // the header we were just mining on. 159 blockToMine.Txns[0].TxOutputs[0].PublicKey = publicKey 160 blockToMine.Txns[0].TxnMeta.(*BlockRewardMetadataa).ExtraData = UintToBuf(extraNonces[0]) 161 162 // Set the header for the block, which should update the merkle root. 163 blockToMine.Header = header 164 165 // Use the nonce we computed 166 blockToMine.Header.Nonce = bestNonce 167 168 return diffTarget, blockToMine 169 } 170 171 return nil, nil 172 } 173 174 func (desoMiner *DeSoMiner) MineAndProcessSingleBlock(threadIndex uint32, mempoolToUpdate *DeSoMempool) (_block *MsgDeSoBlock, _err error) { 175 // Add a call to update the BlockProducer. 176 // TODO(performance): We shouldn't have to do this, it just makes tests pass right now. 177 if err := desoMiner.BlockProducer.UpdateLatestBlockTemplate(); err != nil { 178 // Error if we can't update the template but don't stop the show. 179 glog.Error(err) 180 } 181 182 diffTarget, blockToMine := desoMiner._mineSingleBlock(threadIndex) 183 if blockToMine == nil { 184 return nil, fmt.Errorf("DeSoMiner._startThread: _mineSingleBlock returned nil; should only happen if we're stopping") 185 } 186 187 // Log information on the block we just mined. 188 bestHash, _ := blockToMine.Hash() 189 glog.Infof("================== YOU MINED A NEW BLOCK! ================== Height: %d, Hash: %s", blockToMine.Header.Height, hex.EncodeToString(bestHash[:])) 190 glog.V(1).Infof("Height: (%d), Diff target: (%s), "+ 191 "New hash: (%s), , Header Tip: %v, Block Tip: %v", blockToMine.Header.Height, 192 hex.EncodeToString(diffTarget[:])[:10], hex.EncodeToString(bestHash[:]), 193 desoMiner.BlockProducer.chain.headerTip().Header, 194 desoMiner.BlockProducer.chain.blockTip().Header) 195 scs := spew.ConfigState{DisableMethods: true, Indent: " ", DisablePointerAddresses: true} 196 glog.V(1).Infof(scs.Sdump(blockToMine)) 197 // Sanitize the block for the comparison we're about to do. We need to do 198 // this because the comparison function below will think they're different 199 // if one has nil and one has an empty list. Annoying, but this solves the 200 // issue. 201 for _, tx := range blockToMine.Txns { 202 if len(tx.TxInputs) == 0 { 203 tx.TxInputs = nil 204 } 205 } 206 blockBytes, err := blockToMine.ToBytes(false) 207 if err != nil { 208 glog.Error(err) 209 return nil, err 210 } 211 glog.V(1).Infof("Block bytes hex %d: %s", blockToMine.Header.Height, hex.EncodeToString(blockBytes)) 212 blockFromBytes := &MsgDeSoBlock{} 213 err = blockFromBytes.FromBytes(blockBytes) 214 if err != nil || !reflect.DeepEqual(*blockToMine, *blockFromBytes) { 215 glog.Error(err) 216 fmt.Println("Block as it was mined: ", *blockToMine) 217 scs.Dump(blockToMine) 218 fmt.Println("Block as it was de-serialized:", *blockFromBytes) 219 scs.Dump(blockFromBytes) 220 glog.V(1).Infof("In case you missed the hex %d: %s", blockToMine.Header.Height, hex.EncodeToString(blockBytes)) 221 glog.Errorf("DeSoMiner.MineAndProcessSingleBlock: ERROR: Problem with block "+ 222 "serialization (see above for dumps of blocks): Diff: %v, err?: %v", Diff(blockToMine, blockFromBytes), err) 223 } 224 glog.V(2).Infof("Mined block height:num_txns: %d:%d\n", blockToMine.Header.Height, len(blockToMine.Txns)) 225 226 // TODO: This is duplicate code, but this whole file should probably be deleted or 227 // reworked to use the block producer API anyway. 228 if err := desoMiner.BlockProducer.SignBlock(blockToMine); err != nil { 229 return nil, fmt.Errorf("Error signing block: %v", err) 230 } 231 232 // Process the block. If the block is connected and/or accepted, the Server 233 // will be informed about it. This will cause it to be relayed appropriately. 234 verifySignatures := true 235 // TODO(miner): Replace with a call to SubmitBlock. 236 isMainChain, isOrphan, err := desoMiner.BlockProducer.chain.ProcessBlock( 237 blockToMine, verifySignatures) 238 glog.V(2).Infof("Called ProcessBlock: isMainChain=(%v), isOrphan=(%v), err=(%v)", 239 isMainChain, isOrphan, err) 240 if err != nil { 241 glog.Errorf("ERROR calling ProcessBlock: isMainChain=(%v), isOrphan=(%v), err=(%v)", 242 isMainChain, isOrphan, err) 243 // We return the block even when we have an error in case the caller wants to do 244 // something with it. 245 return blockToMine, fmt.Errorf("ERROR calling ProcessBlock: isMainChain=(%v), isOrphan=(%v), err=(%v)", 246 isMainChain, isOrphan, err) 247 } 248 249 // If a mempool object is passed then update it. Normally this isn't necessary because 250 // ProcessBlock will trigger it because the backendServer will be set on the blockchain 251 // object. But it's useful for tests. 252 if mempoolToUpdate != nil { 253 mempoolToUpdate.UpdateAfterConnectBlock(blockToMine) 254 } 255 256 decimalPlaces := int64(1000) 257 diffTargetBaseline, _ := hex.DecodeString(desoMiner.params.MinDifficultyTargetHex) 258 diffTargetBaselineBlockHash := BlockHash{} 259 copy(diffTargetBaselineBlockHash[:], diffTargetBaseline) 260 diffTargetBaselineBigint := big.NewInt(0).Mul(HashToBigint(&diffTargetBaselineBlockHash), big.NewInt(decimalPlaces)) 261 diffTargetBigint := HashToBigint(diffTarget) 262 glog.V(1).Infof("Difficulty factor (1 = 1 core running): %v", float32(big.NewInt(0).Div(diffTargetBaselineBigint, diffTargetBigint).Int64())/float32(decimalPlaces)) 263 264 if atomic.LoadInt32(&desoMiner.stopping) == 1 { 265 return nil, fmt.Errorf("DeSoMiner._startThread: Stopping thread %d", threadIndex) 266 } 267 268 return blockToMine, nil 269 } 270 271 func (desoMiner *DeSoMiner) _startThread(threadIndex uint32) { 272 for { 273 newBlock, err := desoMiner.MineAndProcessSingleBlock(threadIndex, nil /*mempoolToUpdate*/) 274 if err != nil { 275 glog.Errorf(err.Error()) 276 } 277 isFinished := (newBlock == nil) 278 if isFinished { 279 return 280 } 281 } 282 } 283 284 func (desoMiner *DeSoMiner) Start() { 285 if desoMiner.BlockProducer == nil { 286 glog.Infof("DeSoMiner.Start: NOT starting miner because " + 287 "max_block_templates_to_cache = 0; set it to a non-zero value to " + 288 "start the miner") 289 return 290 } 291 glog.Infof("DeSoMiner.Start: Starting miner with difficulty target %s", desoMiner.params.MinDifficultyTargetHex) 292 blockTip := desoMiner.BlockProducer.chain.blockTip() 293 glog.Infof("DeSoMiner.Start: Block tip height %d, cum work %v, and difficulty %v", 294 blockTip.Header.Height, BigintToHash(blockTip.CumWork), blockTip.DifficultyTarget) 295 // Start a bunch of threads to mine for blocks. 296 for threadIndex := uint32(0); threadIndex < desoMiner.numThreads; threadIndex++ { 297 go func(threadIndex uint32) { 298 glog.V(1).Infof("DeSoMiner.Start: Starting thread %d", threadIndex) 299 desoMiner._startThread(threadIndex) 300 }(threadIndex) 301 } 302 } 303 304 func CopyBytesIntoBlockHash(data []byte) *BlockHash { 305 if len(data) != HashSizeBytes { 306 errorStr := fmt.Sprintf("CopyBytesIntoBlockHash: Got data of size %d for BlockHash of size %d", len(data), HashSizeBytes) 307 glog.Error(errorStr) 308 return nil 309 } 310 var blockHash BlockHash 311 copy(blockHash[:], data) 312 return &blockHash 313 } 314 315 // ProofOfWorkHash is a hash function designed for computing DeSo block hashes. 316 // It seems the optimal hash function is one that satisfies two properties: 317 // 1) It is not computable by any existing ASICs. If this property isn't satisfied 318 // then miners with pre-existing investments in ASICs for other coins can very 319 // cheaply mine on our chain for a short period of time to pull off a 51% attack. 320 // This has actually happened with "merge-mined" coins like Namecoin. 321 // 2) If implemented on an ASIC, there is an "orders of magnitude" speed-up over 322 // using a CPU or GPU. This is because ASICs require some amount of capital 323 // expenditure up-front in order to mine, which then aligns the owner of the 324 // ASIC to care about the health of the network over a longer period of time. In 325 // contrast, a hash function that is CPU or GPU-mineable can be attacked with 326 // an AWS fleet early on. This also may result in a more eco-friendly chain, since 327 // the hash power will be more bottlenecked by up-front CapEx rather than ongoing 328 // electricity cost, as is the case with GPU-mined coins. 329 // 330 // Note that our pursuit of (2) above runs counter to existing dogma which seeks to 331 // prioritize "ASIC-resistance" in hash functions. 332 // 333 // Given the above, the hash function chosen is a simple twist on sha3 334 // that we don't think any ASIC exists for currently. Note that creating an ASIC for 335 // this should be relatively straightforward, however, which allows us to satisfy 336 // property (2) above. 337 func ProofOfWorkHash(inputBytes []byte, version uint32) *BlockHash { 338 output := BlockHash{} 339 340 if version == HeaderVersion0 { 341 hashBytes := desohash.DeSoHashV0(inputBytes) 342 copy(output[:], hashBytes[:]) 343 } else if version == HeaderVersion1 { 344 hashBytes := desohash.DeSoHashV1(inputBytes) 345 copy(output[:], hashBytes[:]) 346 } else { 347 // If we don't recognize the version, we return the v0 hash. We do 348 // this to avoid having to return an error or panic. 349 hashBytes := desohash.DeSoHashV0(inputBytes) 350 copy(output[:], hashBytes[:]) 351 } 352 353 return &output 354 } 355 356 func Sha256DoubleHash(input []byte) *BlockHash { 357 hashBytes := merkletree.Sha256DoubleHash(input) 358 ret := &BlockHash{} 359 copy(ret[:], hashBytes[:]) 360 return ret 361 } 362 363 func HashToBigint(hash *BlockHash) *big.Int { 364 // No need to check errors since the string is necessarily a valid hex 365 // string. 366 val, itWorked := new(big.Int).SetString(hex.EncodeToString(hash[:]), 16) 367 if !itWorked { 368 glog.Errorf("Failed in converting []byte (%#v) to bigint.", hash) 369 } 370 return val 371 } 372 373 func BigintToHash(bigint *big.Int) *BlockHash { 374 hexStr := bigint.Text(16) 375 if len(hexStr)%2 != 0 { 376 // If we have an odd number of bytes add one to the beginning (remember 377 // the bigints are big-endian. 378 hexStr = "0" + hexStr 379 } 380 hexBytes, err := hex.DecodeString(hexStr) 381 if err != nil { 382 glog.Errorf("Failed in converting bigint (%#v) with hex "+ 383 "string (%s) to hash.", bigint, hexStr) 384 } 385 if len(hexBytes) > HashSizeBytes { 386 glog.Errorf("BigintToHash: Bigint %v overflows the hash size %d", bigint, HashSizeBytes) 387 return nil 388 } 389 390 var retBytes BlockHash 391 copy(retBytes[HashSizeBytes-len(hexBytes):], hexBytes) 392 return &retBytes 393 } 394 395 func BytesToBigint(bb []byte) *big.Int { 396 val, itWorked := new(big.Int).SetString(hex.EncodeToString(bb), 16) 397 if !itWorked { 398 glog.Errorf("Failed in converting []byte (%#v) to bigint.", bb) 399 } 400 return val 401 } 402 403 func BigintToBytes(bigint *big.Int) []byte { 404 hexStr := bigint.Text(16) 405 if len(hexStr)%2 != 0 { 406 // If we have an odd number of bytes add one to the beginning (remember 407 // the bigints are big-endian. 408 hexStr = "0" + hexStr 409 } 410 hexBytes, err := hex.DecodeString(hexStr) 411 if err != nil { 412 glog.Errorf("Failed in converting bigint (%#v) with hex "+ 413 "string (%s) to []byte.", bigint, hexStr) 414 } 415 return hexBytes 416 } 417 418 // FindLowestHash 419 // Mine for a given number of iterations and return the lowest hash value 420 // found and its associated nonce. Hashing starts at the value of the Nonce 421 // set on the blockHeader field when it is passed and increments the value 422 // of the passed blockHeader field as it iterates. This makes it easy to 423 // continue a subsequent batch of iterations after we return. 424 func FindLowestHash( 425 blockHeaderr *MsgDeSoHeader, iterations uint64) ( 426 lowestHash *BlockHash, lowestNonce uint64, ee error) { 427 //// Compute a hash of the header with the current nonce value. 428 bestNonce := blockHeaderr.Nonce 429 bestHash, err := blockHeaderr.Hash() 430 if err != nil { 431 return nil, 0, err 432 } 433 434 for iterations > 0 { 435 // Increment the nonce. 436 blockHeaderr.Nonce++ 437 438 // Compute a new hash. 439 currentHash, err := blockHeaderr.Hash() 440 if err != nil { 441 return nil, 0, err 442 } 443 444 // See if it's better than what we currently have 445 if LessThan(currentHash, bestHash) { 446 bestHash = currentHash 447 bestNonce = blockHeaderr.Nonce 448 } 449 450 iterations-- 451 } 452 453 // Increment the nonce one last time since we checked this hash. 454 blockHeaderr.Nonce++ 455 456 return bestHash, bestNonce, nil 457 } 458 459 func LessThan(aa *BlockHash, bb *BlockHash) bool { 460 aaBigint := new(big.Int) 461 aaBigint.SetBytes(aa[:]) 462 bbBigint := new(big.Int) 463 bbBigint.SetBytes(bb[:]) 464 465 return aaBigint.Cmp(bbBigint) < 0 466 }