package rpctest import ( "errors" "math" "math/big" "runtime" "time" "github.com/p9c/p9/pkg/block" "github.com/p9c/p9/pkg/btcaddr" "github.com/p9c/p9/pkg/blockchain" "github.com/p9c/p9/pkg/chaincfg" "github.com/p9c/p9/pkg/chainhash" "github.com/p9c/p9/pkg/txscript" "github.com/p9c/p9/pkg/util" "github.com/p9c/p9/pkg/wire" ) // solveBlock attempts to find a nonce which makes the passed block header hash to a value less than the target // difficulty. When a successful solution is found true is returned and the nonce field of the passed header is updated // with the solution. False is returned if no solution exists. func solveBlock(header *wire.BlockHeader, targetDifficulty *big.Int) bool { // sbResult is used by the solver goroutines to send results. type sbResult struct { found bool nonce uint32 } // solver accepts a block header and a nonce range to test. It is intended to be run as a goroutine. quit := make(chan bool) results := make(chan sbResult) solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) { // We need to modify the nonce field of the header, so make sure we work with a copy of the original header. for i := startNonce; i >= startNonce && i <= stopNonce; i++ { select { case <-quit: return default: hdr.Nonce = i hash := hdr.BlockHash() if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 { select { case results <- sbResult{true, i}: return case <-quit: return } } } } select { case results <- sbResult{false, 0}: case <-quit: return } } startNonce := uint32(0) stopNonce := uint32(math.MaxUint32) numCores := uint32(runtime.NumCPU()) noncesPerCore := (stopNonce - startNonce) / numCores for i := uint32(0); i < numCores; i++ { rangeStart := startNonce + (noncesPerCore * i) rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1 if i == numCores-1 { rangeStop = stopNonce } go solver(*header, rangeStart, rangeStop) } for i := uint32(0); i < numCores; i++ { result := <-results if result.found { close(quit) header.Nonce = result.nonce return true } } return false } // standardCoinbaseScript returns a standard script suitable for use as the signature script of the coinbase transaction // of a new block. In particular, it starts with the block height that is required by version 2 blocks. func standardCoinbaseScript(nextBlockHeight int32, extraNonce uint64) ([]byte, error) { return txscript.NewScriptBuilder().AddInt64(int64(nextBlockHeight)). AddInt64(int64(extraNonce)).Script() } // createCoinbaseTx returns a coinbase transaction paying an appropriate subsidy based on the passed block height to the // provided address. func createCoinbaseTx( coinbaseScript []byte, nextBlockHeight int32, addr btcaddr.Address, mineTo []wire.TxOut, net *chaincfg.Params, version int32, ) (*util.Tx, error) { // Create the script to pay to the provided payment address. pkScript, e := txscript.PayToAddrScript(addr) if e != nil { return nil, e } tx := wire.NewMsgTx(wire.TxVersion) tx.AddTxIn( &wire.TxIn{ // Coinbase transactions have no inputs, so previous outpoint is zero hash and max index. PreviousOutPoint: *wire.NewOutPoint( &chainhash.Hash{}, wire.MaxPrevOutIndex, ), SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }, ) if len(mineTo) == 0 { tx.AddTxOut( &wire.TxOut{ Value: blockchain.CalcBlockSubsidy(nextBlockHeight, net, version), PkScript: pkScript, }, ) } else { for i := range mineTo { tx.AddTxOut(&mineTo[i]) } } return util.NewTx(tx), nil } // CreateBlock creates a new block building from the previous block with a specified blockversion and timestamp. If the // timestamp passed is zero ( not initialized), then the timestamp of the previous block will be used plus 1 second is // used. Passing nil for the previous block results in a block that builds off of the genesis block for the specified // chain. func CreateBlock( prevBlock *block.Block, inclusionTxs []*util.Tx, blockVersion int32, blockTime time.Time, miningAddr btcaddr.Address, mineTo []wire.TxOut, net *chaincfg.Params, ) (*block.Block, error) { var ( prevHash *chainhash.Hash blockHeight int32 prevBlockTime time.Time ) // If the previous block isn't specified, then we'll construct a block that builds off of the genesis block for the // chain. if prevBlock == nil { prevHash = net.GenesisHash blockHeight = 1 prevBlockTime = net.GenesisBlock.Header.Timestamp.Add(time.Minute) } else { prevHash = prevBlock.Hash() blockHeight = prevBlock.Height() + 1 prevBlockTime = prevBlock.WireBlock().Header.Timestamp } // If a target block time was specified, then use that as the header's timestamp. Otherwise, add one second to the // previous block unless it's the genesis block in which case use the current time. var ts time.Time switch { case !blockTime.IsZero(): ts = blockTime default: ts = prevBlockTime.Add(time.Second) } extraNonce := uint64(0) coinbaseScript, e := standardCoinbaseScript(blockHeight, extraNonce) if e != nil { return nil, e } coinbaseTx, e := createCoinbaseTx( coinbaseScript, blockHeight, miningAddr, mineTo, net, blockVersion, ) if e != nil { return nil, e } // Create a new block ready to be solved. blockTxns := []*util.Tx{coinbaseTx} if inclusionTxs != nil { blockTxns = append(blockTxns, inclusionTxs...) } merkles := blockchain.BuildMerkleTreeStore(blockTxns, false) var b wire.Block b.Header = wire.BlockHeader{ Version: blockVersion, PrevBlock: *prevHash, MerkleRoot: *merkles.GetRoot(), Timestamp: ts, Bits: net.PowLimitBits, } for _, tx := range blockTxns { if e := b.AddTransaction(tx.MsgTx()); E.Chk(e) { return nil, e } } found := solveBlock(&b.Header, net.PowLimit) if !found { return nil, errors.New("unable to solve block") } utilBlock := block.NewBlock(&b) utilBlock.SetHeight(blockHeight) return utilBlock, nil }