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