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  }