blockgen.go raw

   1  package rpctest
   2  
   3  import (
   4  	"errors"
   5  	"math"
   6  	"math/big"
   7  	"runtime"
   8  	"time"
   9  
  10  	"github.com/p9c/p9/pkg/block"
  11  	"github.com/p9c/p9/pkg/btcaddr"
  12  
  13  	"github.com/p9c/p9/pkg/blockchain"
  14  	"github.com/p9c/p9/pkg/chaincfg"
  15  	"github.com/p9c/p9/pkg/chainhash"
  16  	"github.com/p9c/p9/pkg/txscript"
  17  	"github.com/p9c/p9/pkg/util"
  18  	"github.com/p9c/p9/pkg/wire"
  19  )
  20  
  21  // solveBlock attempts to find a nonce which makes the passed block header hash to a value less than the target
  22  // difficulty. When a successful solution is found true is returned and the nonce field of the passed header is updated
  23  // with the solution. False is returned if no solution exists.
  24  func solveBlock(header *wire.BlockHeader, targetDifficulty *big.Int) bool {
  25  	// sbResult is used by the solver goroutines to send results.
  26  	type sbResult struct {
  27  		found bool
  28  		nonce uint32
  29  	}
  30  	// solver accepts a block header and a nonce range to test. It is intended to be run as a goroutine.
  31  	quit := make(chan bool)
  32  	results := make(chan sbResult)
  33  	solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) {
  34  		// We need to modify the nonce field of the header, so make sure we work with a copy of the original header.
  35  		for i := startNonce; i >= startNonce && i <= stopNonce; i++ {
  36  			select {
  37  			case <-quit:
  38  				return
  39  			default:
  40  				hdr.Nonce = i
  41  				hash := hdr.BlockHash()
  42  				if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
  43  					select {
  44  					case results <- sbResult{true, i}:
  45  						return
  46  					case <-quit:
  47  						return
  48  					}
  49  				}
  50  			}
  51  		}
  52  		select {
  53  		case results <- sbResult{false, 0}:
  54  		case <-quit:
  55  			return
  56  		}
  57  	}
  58  	startNonce := uint32(0)
  59  	stopNonce := uint32(math.MaxUint32)
  60  	numCores := uint32(runtime.NumCPU())
  61  	noncesPerCore := (stopNonce - startNonce) / numCores
  62  	for i := uint32(0); i < numCores; i++ {
  63  		rangeStart := startNonce + (noncesPerCore * i)
  64  		rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1
  65  		if i == numCores-1 {
  66  			rangeStop = stopNonce
  67  		}
  68  		go solver(*header, rangeStart, rangeStop)
  69  	}
  70  	for i := uint32(0); i < numCores; i++ {
  71  		result := <-results
  72  		if result.found {
  73  			close(quit)
  74  			header.Nonce = result.nonce
  75  			return true
  76  		}
  77  	}
  78  	return false
  79  }
  80  
  81  // standardCoinbaseScript returns a standard script suitable for use as the signature script of the coinbase transaction
  82  // of a new block. In particular, it starts with the block height that is required by version 2 blocks.
  83  func standardCoinbaseScript(nextBlockHeight int32, extraNonce uint64) ([]byte, error) {
  84  	return txscript.NewScriptBuilder().AddInt64(int64(nextBlockHeight)).
  85  		AddInt64(int64(extraNonce)).Script()
  86  }
  87  
  88  // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy based on the passed block height to the
  89  // provided address.
  90  func createCoinbaseTx(
  91  	coinbaseScript []byte,
  92  	nextBlockHeight int32,
  93  	addr btcaddr.Address,
  94  	mineTo []wire.TxOut,
  95  	net *chaincfg.Params,
  96  	version int32,
  97  ) (*util.Tx, error) {
  98  	// Create the script to pay to the provided payment address.
  99  	pkScript, e := txscript.PayToAddrScript(addr)
 100  	if e != nil {
 101  		return nil, e
 102  	}
 103  	tx := wire.NewMsgTx(wire.TxVersion)
 104  	tx.AddTxIn(
 105  		&wire.TxIn{
 106  			// Coinbase transactions have no inputs, so previous outpoint is zero hash and max index.
 107  			PreviousOutPoint: *wire.NewOutPoint(
 108  				&chainhash.Hash{},
 109  				wire.MaxPrevOutIndex,
 110  			),
 111  			SignatureScript: coinbaseScript,
 112  			Sequence:        wire.MaxTxInSequenceNum,
 113  		},
 114  	)
 115  	if len(mineTo) == 0 {
 116  		tx.AddTxOut(
 117  			&wire.TxOut{
 118  				Value:    blockchain.CalcBlockSubsidy(nextBlockHeight, net, version),
 119  				PkScript: pkScript,
 120  			},
 121  		)
 122  	} else {
 123  		for i := range mineTo {
 124  			tx.AddTxOut(&mineTo[i])
 125  		}
 126  	}
 127  	return util.NewTx(tx), nil
 128  }
 129  
 130  // CreateBlock creates a new block building from the previous block with a specified blockversion and timestamp. If the
 131  // timestamp passed is zero ( not initialized), then the timestamp of the previous block will be used plus 1 second is
 132  // used. Passing nil for the previous block results in a block that builds off of the genesis block for the specified
 133  // chain.
 134  func CreateBlock(
 135  	prevBlock *block.Block, inclusionTxs []*util.Tx,
 136  	blockVersion int32, blockTime time.Time, miningAddr btcaddr.Address,
 137  	mineTo []wire.TxOut, net *chaincfg.Params,
 138  ) (*block.Block, error) {
 139  	var (
 140  		prevHash      *chainhash.Hash
 141  		blockHeight   int32
 142  		prevBlockTime time.Time
 143  	)
 144  	// If the previous block isn't specified, then we'll construct a block that builds off of the genesis block for the
 145  	// chain.
 146  	if prevBlock == nil {
 147  		prevHash = net.GenesisHash
 148  		blockHeight = 1
 149  		prevBlockTime = net.GenesisBlock.Header.Timestamp.Add(time.Minute)
 150  	} else {
 151  		prevHash = prevBlock.Hash()
 152  		blockHeight = prevBlock.Height() + 1
 153  		prevBlockTime = prevBlock.WireBlock().Header.Timestamp
 154  	}
 155  	// If a target block time was specified, then use that as the header's timestamp. Otherwise, add one second to the
 156  	// previous block unless it's the genesis block in which case use the current time.
 157  	var ts time.Time
 158  	switch {
 159  	case !blockTime.IsZero():
 160  		ts = blockTime
 161  	default:
 162  		ts = prevBlockTime.Add(time.Second)
 163  	}
 164  	extraNonce := uint64(0)
 165  	coinbaseScript, e := standardCoinbaseScript(blockHeight, extraNonce)
 166  	if e != nil {
 167  		return nil, e
 168  	}
 169  	coinbaseTx, e := createCoinbaseTx(
 170  		coinbaseScript, blockHeight, miningAddr,
 171  		mineTo, net, blockVersion,
 172  	)
 173  	if e != nil {
 174  		return nil, e
 175  	}
 176  	// Create a new block ready to be solved.
 177  	blockTxns := []*util.Tx{coinbaseTx}
 178  	if inclusionTxs != nil {
 179  		blockTxns = append(blockTxns, inclusionTxs...)
 180  	}
 181  	merkles := blockchain.BuildMerkleTreeStore(blockTxns, false)
 182  	var b wire.Block
 183  	b.Header = wire.BlockHeader{
 184  		Version:    blockVersion,
 185  		PrevBlock:  *prevHash,
 186  		MerkleRoot: *merkles.GetRoot(),
 187  		Timestamp:  ts,
 188  		Bits:       net.PowLimitBits,
 189  	}
 190  	for _, tx := range blockTxns {
 191  		if e := b.AddTransaction(tx.MsgTx()); E.Chk(e) {
 192  			return nil, e
 193  		}
 194  	}
 195  	found := solveBlock(&b.Header, net.PowLimit)
 196  	if !found {
 197  		return nil, errors.New("unable to solve block")
 198  	}
 199  	utilBlock := block.NewBlock(&b)
 200  	utilBlock.SetHeight(blockHeight)
 201  	return utilBlock, nil
 202  }
 203