diff --git a/blockchain/chain.go b/blockchain/chain.go index 7e06e5c77c..8e75b447e9 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -1798,6 +1798,144 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has return headers } +// InvalidateBlock invalidates the requested block and all its descedents. If a block +// in the best chain is invalidated, the active chain tip will be the parent of the +// invalidated block. +// +// This function is safe for concurrent access. +func (b *BlockChain) InvalidateBlock(hash *chainhash.Hash) error { + b.chainLock.Lock() + defer b.chainLock.Unlock() + + node := b.index.LookupNode(hash) + if node == nil { + // Return an error if the block doesn't exist. + return fmt.Errorf("Requested block hash of %s is not found "+ + "and thus cannot be invalidated.", hash) + } + if node.height == 0 { + return fmt.Errorf("Requested block hash of %s is a at height 0 "+ + "and is thus a genesis block and cannot be invalidated.", + node.hash) + } + + // Nothing to do if the given block is already invalid. + if node.status.KnownInvalid() { + return nil + } + + // Set the status of the block being invalidated. + b.index.SetStatusFlags(node, statusValidateFailed) + b.index.UnsetStatusFlags(node, statusValid) + + // If the block we're invalidating is not on the best chain, we simply + // mark the block and all its descendants as invalid and return. + if !b.bestChain.Contains(node) { + // Grab all the tips excluding the active tip. + tips := b.index.InactiveTips(b.bestChain) + for _, tip := range tips { + // Continue if the given inactive tip is not a descendant of the block + // being invalidated. + if !tip.IsAncestor(node) { + continue + } + + // Keep going back until we get to the block being invalidated. + // For each of the parent, we'll unset valid status and set invalid + // ancestor status. + for n := tip; n != nil && n != node; n = n.parent { + // Continue if it's already invalid. + if n.status.KnownInvalid() { + continue + } + b.index.SetStatusFlags(n, statusInvalidAncestor) + b.index.UnsetStatusFlags(n, statusValid) + } + } + + if writeErr := b.index.flushToDB(); writeErr != nil { + return fmt.Errorf("Error flushing block index "+ + "changes to disk: %v", writeErr) + } + + // Return since the block being invalidated is on a side branch. + // Nothing else left to do. + return nil + } + + // If we're here, it means a block from the active chain tip is getting + // invalidated. + // + // Grab all the nodes to detach from the active chain. + detachNodes := list.New() + for n := b.bestChain.Tip(); n != nil && n != node; n = n.parent { + // Continue if it's already invalid. + if n.status.KnownInvalid() { + continue + } + + // Change the status of the block node. + b.index.SetStatusFlags(n, statusInvalidAncestor) + b.index.UnsetStatusFlags(n, statusValid) + detachNodes.PushBack(n) + } + + // Push back the block node being invalidated. + detachNodes.PushBack(node) + + // Reorg back to the parent of the block being invalidated. + // Nothing to attach so just pass an empty list. + err := b.reorganizeChain(detachNodes, list.New()) + if err != nil { + return err + } + + if writeErr := b.index.flushToDB(); writeErr != nil { + log.Warnf("Error flushing block index changes to disk: %v", writeErr) + } + + // Grab all the tips. + tips := b.index.InactiveTips(b.bestChain) + tips = append(tips, b.bestChain.Tip()) + + // Here we'll check if the invalidation of the block in the active tip + // changes the status of the chain tips. If a side branch now has more + // worksum, it becomes the active chain tip. + var bestTip *blockNode + for _, tip := range tips { + // Skip invalid tips as they cannot become the active tip. + if tip.status.KnownInvalid() { + continue + } + + // If we have no best tips, then set this tip as the best tip. + if bestTip == nil { + bestTip = tip + } else { + // If there is an existing best tip, then compare it + // against the current tip. + if tip.workSum.Cmp(bestTip.workSum) == 1 { + bestTip = tip + } + } + } + + // Return if the best tip is the current tip. + if bestTip == b.bestChain.Tip() { + return nil + } + + // Reorganize to the best tip if a side branch is now the most work tip. + detachNodes, attachNodes := b.getReorganizeNodes(bestTip) + err = b.reorganizeChain(detachNodes, attachNodes) + + if writeErr := b.index.flushToDB(); writeErr != nil { + log.Warnf("Error flushing block index changes to disk: %v", writeErr) + } + + return err +} + // IndexManager provides a generic interface that the is called when blocks are // connected and disconnected to and from the tip of the main chain for the // purpose of supporting optional indexes. diff --git a/blockchain/chain_test.go b/blockchain/chain_test.go index 259a643f3c..57bbd6246c 100644 --- a/blockchain/chain_test.go +++ b/blockchain/chain_test.go @@ -6,10 +6,12 @@ package blockchain import ( "fmt" + "math/rand" "reflect" "testing" "time" + "github.com/btcsuite/btcd/blockchain/internal/testhelper" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -1311,3 +1313,312 @@ func TestIsAncestor(t *testing.T) { branch2Nodes[0].hash.String()) } } + +// randomSelect selects random amount of random elements from a slice and returns a +// new slice. The selected elements are removed. +func randomSelect(input []*testhelper.SpendableOut) ( + []*testhelper.SpendableOut, []*testhelper.SpendableOut) { + + selected := []*testhelper.SpendableOut{} + + // Select random elements from the input slice + amount := rand.Intn(len(input)) + for i := 0; i < amount; i++ { + // Generate a random index + randIdx := rand.Intn(len(input)) + + // Append the selected element to the new slice + selected = append(selected, input[randIdx]) + + // Remove the selected element from the input slice. + // This ensures that each selected element is unique. + input = append(input[:randIdx], input[randIdx+1:]...) + } + + return input, selected +} + +// addBlocks generates new blocks and adds them to the chain. The newly generated +// blocks will spend from the spendable outputs passed in. The returned hases are +// the hashes of the newly generated blocks. +func addBlocks(count int, chain *BlockChain, prevBlock *btcutil.Block, + allSpendableOutputs []*testhelper.SpendableOut) ( + []*chainhash.Hash, [][]*testhelper.SpendableOut, error) { + + blockHashes := make([]*chainhash.Hash, 0, count) + spendablesOuts := make([][]*testhelper.SpendableOut, 0, count) + + // Always spend everything on the first block. This ensures we get unique blocks + // every time. The random select may choose not to spend any and that results + // in getting the same block. + nextSpends := allSpendableOutputs + allSpendableOutputs = allSpendableOutputs[:0] + for b := 0; b < count; b++ { + newBlock, newSpendableOuts, err := addBlock(chain, prevBlock, nextSpends) + if err != nil { + return nil, nil, err + } + prevBlock = newBlock + + blockHashes = append(blockHashes, newBlock.Hash()) + spendablesOuts = append(spendablesOuts, newSpendableOuts) + allSpendableOutputs = append(allSpendableOutputs, newSpendableOuts...) + + // Grab utxos to be spent in the next block. + allSpendableOutputs, nextSpends = randomSelect(allSpendableOutputs) + } + + return blockHashes, spendablesOuts, nil +} + +func TestInvalidateBlock(t *testing.T) { + tests := []struct { + name string + chainGen func() (*BlockChain, []*chainhash.Hash, func()) + }{ + { + name: "one branch, invalidate once", + chainGen: func() (*BlockChain, []*chainhash.Hash, func()) { + chain, params, tearDown := utxoCacheTestChain( + "TestInvalidateBlock-one-branch-" + + "invalidate-once") + // Grab the tip of the chain. + tip := btcutil.NewBlock(params.GenesisBlock) + + // Create a chain with 11 blocks. + _, _, err := addBlocks(11, chain, tip, []*testhelper.SpendableOut{}) + if err != nil { + t.Fatal(err) + } + + // Invalidate block 5. + block, err := chain.BlockByHeight(5) + if err != nil { + t.Fatal(err) + } + invalidateHash := block.Hash() + + return chain, []*chainhash.Hash{invalidateHash}, tearDown + }, + }, + { + name: "invalidate twice", + chainGen: func() (*BlockChain, []*chainhash.Hash, func()) { + chain, params, tearDown := utxoCacheTestChain("TestInvalidateBlock-invalidate-twice") + // Grab the tip of the chain. + tip := btcutil.NewBlock(params.GenesisBlock) + + // Create a chain with 11 blocks. + _, spendableOuts, err := addBlocks(11, chain, tip, []*testhelper.SpendableOut{}) + //_, _, err := addBlocks(11, chain, tip, []*testhelper.SpendableOut{}) + if err != nil { + t.Fatal(err) + } + + // Set invalidateHash as block 5. + block, err := chain.BlockByHeight(5) + if err != nil { + t.Fatal(err) + } + invalidateHash := block.Hash() + + // Create a side chain with 7 blocks that builds on block 1. + b1, err := chain.BlockByHeight(1) + if err != nil { + t.Fatal(err) + } + altBlockHashes, _, err := addBlocks(6, chain, b1, spendableOuts[0]) + if err != nil { + t.Fatal(err) + } + + // Grab block at height 5: + // + // b2, b3, b4, b5 + // 0, 1, 2, 3 + invalidateHash2 := altBlockHashes[3] + + // Sanity checking that we grabbed the correct hash. + node := chain.index.LookupNode(invalidateHash) + if node == nil || node.height != 5 { + t.Fatalf("wanted to grab block at height 5 but got height %v", + node.height) + } + + return chain, []*chainhash.Hash{invalidateHash, invalidateHash2}, tearDown + }, + }, + { + name: "invalidate a side branch", + chainGen: func() (*BlockChain, []*chainhash.Hash, func()) { + chain, params, tearDown := utxoCacheTestChain("TestInvalidateBlock-invalidate-side-branch") + tip := btcutil.NewBlock(params.GenesisBlock) + + // Grab the tip of the chain. + tip, err := chain.BlockByHash(&chain.bestChain.Tip().hash) + if err != nil { + t.Fatal(err) + } + + // Create a chain with 11 blocks. + _, spendableOuts, err := addBlocks(11, chain, tip, []*testhelper.SpendableOut{}) + if err != nil { + t.Fatal(err) + } + + // Create a side chain with 7 blocks that builds on block 1. + b1, err := chain.BlockByHeight(1) + if err != nil { + t.Fatal(err) + } + altBlockHashes, _, err := addBlocks(6, chain, b1, spendableOuts[0]) + if err != nil { + t.Fatal(err) + } + + // Grab block at height 4: + // + // b2, b3, b4 + // 0, 1, 2 + invalidateHash := altBlockHashes[2] + + // Sanity checking that we grabbed the correct hash. + node := chain.index.LookupNode(invalidateHash) + if node == nil || node.height != 4 { + t.Fatalf("wanted to grab block at height 4 but got height %v", + node.height) + } + + return chain, []*chainhash.Hash{invalidateHash}, tearDown + }, + }, + } + + for _, test := range tests { + chain, invalidateHashes, tearDown := test.chainGen() + func() { + defer tearDown() + for _, invalidateHash := range invalidateHashes { + chainTipsBefore := chain.ChainTips() + + // Mark if we're invalidating a block that's a part of the best chain. + var bestChainBlock bool + node := chain.index.LookupNode(invalidateHash) + if chain.bestChain.Contains(node) { + bestChainBlock = true + } + + // Actual invalidation. + err := chain.InvalidateBlock(invalidateHash) + if err != nil { + t.Fatal(err) + } + + chainTipsAfter := chain.ChainTips() + + // Create a map for easy lookup. + chainTipMap := make(map[chainhash.Hash]ChainTip, len(chainTipsAfter)) + activeTipCount := 0 + for _, chainTip := range chainTipsAfter { + chainTipMap[chainTip.BlockHash] = chainTip + + if chainTip.Status == StatusActive { + activeTipCount++ + } + } + if activeTipCount != 1 { + t.Fatalf("TestInvalidateBlock fail. Expected "+ + "1 active chain tip but got %d", activeTipCount) + } + + bestTip := chain.bestChain.Tip() + + validForkCount := 0 + for _, tip := range chainTipsBefore { + // If the chaintip was an active tip and we invalidated a block + // in the active tip, assert that it's invalid now. + if bestChainBlock && tip.Status == StatusActive { + gotTip, found := chainTipMap[tip.BlockHash] + if !found { + t.Fatalf("TestInvalidateBlock fail. Expected "+ + "block %s not found in chaintips after "+ + "invalidateblock", tip.BlockHash.String()) + } + + if gotTip.Status != StatusInvalid { + t.Fatalf("TestInvalidateBlock fail. "+ + "Expected block %s to be invalid, got status: %s", + gotTip.BlockHash.String(), gotTip.Status) + } + } + + if !bestChainBlock && tip.Status != StatusActive { + gotTip, found := chainTipMap[tip.BlockHash] + if !found { + t.Fatalf("TestInvalidateBlock fail. Expected "+ + "block %s not found in chaintips after "+ + "invalidateblock", tip.BlockHash.String()) + } + + if gotTip.BlockHash == *invalidateHash && gotTip.Status != StatusInvalid { + t.Fatalf("TestInvalidateBlock fail. "+ + "Expected block %s to be invalid, got status: %s", + gotTip.BlockHash.String(), gotTip.Status) + } + } + + // If we're not invalidating the branch with an active tip, + // we expect the active tip to remain the same. + if !bestChainBlock && tip.Status == StatusActive && tip.BlockHash != bestTip.hash { + t.Fatalf("TestInvalidateBlock fail. Expected block %s as the tip but got %s", + tip.BlockHash.String(), bestTip.hash.String()) + } + + // If this tip is not invalid and not active, it should be + // lighter than the current best tip. + if tip.Status != StatusActive && tip.Status != StatusInvalid && + tip.Height > bestTip.height { + + tipNode := chain.index.LookupNode(&tip.BlockHash) + if bestTip.workSum.Cmp(tipNode.workSum) == -1 { + t.Fatalf("TestInvalidateBlock fail. Expected "+ + "block %s to be the active tip but block %s "+ + "was", tipNode.hash.String(), bestTip.hash.String()) + } + } + + if tip.Status == StatusValidFork { + validForkCount++ + } + } + + // If there are no other valid chain tips besides the active chaintip, + // we expect to have one more chain tip after the invalidate. + if validForkCount == 0 && len(chainTipsAfter) != len(chainTipsBefore)+1 { + t.Fatalf("TestInvalidateBlock fail. Expected %d chaintips but got %d", + len(chainTipsBefore)+1, len(chainTipsAfter)) + } + } + + // Try to invaliate the already invalidated hash. + err := chain.InvalidateBlock(invalidateHashes[0]) + if err != nil { + t.Fatal(err) + } + + // Try to invaliate a genesis block + err = chain.InvalidateBlock(chain.chainParams.GenesisHash) + if err == nil { + t.Fatalf("TestInvalidateBlock fail. Expected to err when trying to" + + "invalidate a genesis block.") + } + + // Try to invaliate a block that doesn't exist. + err = chain.InvalidateBlock(chaincfg.MainNetParams.GenesisHash) + if err == nil { + t.Fatalf("TestInvalidateBlock fail. Expected to err when trying to" + + "invalidate a block that doesn't exist.") + } + }() + } +} diff --git a/blockchain/common_test.go b/blockchain/common_test.go index 5037c1828e..12badd3ec0 100644 --- a/blockchain/common_test.go +++ b/blockchain/common_test.go @@ -14,6 +14,7 @@ import ( "strings" "time" + "github.com/btcsuite/btcd/blockchain/internal/testhelper" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" @@ -396,3 +397,96 @@ func newFakeNode(parent *blockNode, blockVersion int32, bits uint32, timestamp t } return newBlockNode(header, parent) } + +// addBlock adds a block to the blockchain that succeeds the previous block. +// The blocks spends all the provided spendable outputs. The new block and +// the new spendable outputs created in the block are returned. +func addBlock(chain *BlockChain, prev *btcutil.Block, spends []*testhelper.SpendableOut) ( + *btcutil.Block, []*testhelper.SpendableOut, error) { + + block, outs, err := newBlock(chain, prev, spends) + if err != nil { + return nil, nil, err + } + + _, _, err = chain.ProcessBlock(block, BFNone) + if err != nil { + return nil, nil, err + } + + return block, outs, nil +} + +// calcMerkleRoot creates a merkle tree from the slice of transactions and +// returns the root of the tree. +func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { + if len(txns) == 0 { + return chainhash.Hash{} + } + + utilTxns := make([]*btcutil.Tx, 0, len(txns)) + for _, tx := range txns { + utilTxns = append(utilTxns, btcutil.NewTx(tx)) + } + return CalcMerkleRoot(utilTxns, false) +} + +// newBlock creates a block to the blockchain that succeeds the previous block. +// The blocks spends all the provided spendable outputs. The new block and the +// newly spendable outputs created in the block are returned. +func newBlock(chain *BlockChain, prev *btcutil.Block, + spends []*testhelper.SpendableOut) (*btcutil.Block, []*testhelper.SpendableOut, error) { + + blockHeight := prev.Height() + 1 + txns := make([]*wire.MsgTx, 0, 1+len(spends)) + + // Create and add coinbase tx. + cb := testhelper.CreateCoinbaseTx(blockHeight, CalcBlockSubsidy(blockHeight, chain.chainParams)) + txns = append(txns, cb) + + // Spend all txs to be spent. + for _, spend := range spends { + cb.TxOut[0].Value += int64(testhelper.LowFee) + + spendTx := testhelper.CreateSpendTx(spend, testhelper.LowFee) + txns = append(txns, spendTx) + } + + // Use a timestamp that is one second after the previous block unless + // this is the first block in which case the current time is used. + var ts time.Time + if blockHeight == 1 { + ts = time.Unix(time.Now().Unix(), 0) + } else { + ts = prev.MsgBlock().Header.Timestamp.Add(time.Second) + } + + // Create the block. The nonce will be solved in the below code in + // SolveBlock. + block := btcutil.NewBlock(&wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: *prev.Hash(), + MerkleRoot: calcMerkleRoot(txns), + Bits: chain.chainParams.PowLimitBits, + Timestamp: ts, + Nonce: 0, // To be solved. + }, + Transactions: txns, + }) + block.SetHeight(blockHeight) + + // Solve the block. + if !testhelper.SolveBlock(&block.MsgBlock().Header) { + return nil, nil, fmt.Errorf("Unable to solve block at height %d", blockHeight) + } + + // Create spendable outs to return. + outs := make([]*testhelper.SpendableOut, len(txns)) + for i, tx := range txns { + out := testhelper.MakeSpendableOutForTx(tx, 0) + outs[i] = &out + } + + return block, outs, nil +} diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index 1fa850cc37..b1e39b9d62 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -8,31 +8,14 @@ import ( "math/big" "time" + "github.com/btcsuite/btcd/blockchain/internal/workmath" "github.com/btcsuite/btcd/chaincfg/chainhash" ) -var ( - // bigOne is 1 represented as a big.Int. It is defined here to avoid - // the overhead of creating it multiple times. - bigOne = big.NewInt(1) - - // oneLsh256 is 1 shifted left 256 bits. It is defined here to avoid - // the overhead of creating it multiple times. - oneLsh256 = new(big.Int).Lsh(bigOne, 256) -) - // HashToBig converts a chainhash.Hash into a big.Int that can be used to // perform math comparisons. func HashToBig(hash *chainhash.Hash) *big.Int { - // A Hash is in little-endian, but the big package wants the bytes in - // big-endian, so reverse them. - buf := *hash - blen := len(buf) - for i := 0; i < blen/2; i++ { - buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] - } - - return new(big.Int).SetBytes(buf[:]) + return workmath.HashToBig(hash) } // CompactToBig converts a compact representation of a whole number N to an @@ -60,31 +43,7 @@ func HashToBig(hash *chainhash.Hash) *big.Int { // which represent difficulty targets, thus there really is not a need for a // sign bit, but it is implemented here to stay consistent with bitcoind. func CompactToBig(compact uint32) *big.Int { - // Extract the mantissa, sign bit, and exponent. - mantissa := compact & 0x007fffff - isNegative := compact&0x00800000 != 0 - exponent := uint(compact >> 24) - - // Since the base for the exponent is 256, the exponent can be treated - // as the number of bytes to represent the full 256-bit number. So, - // treat the exponent as the number of bytes and shift the mantissa - // right or left accordingly. This is equivalent to: - // N = mantissa * 256^(exponent-3) - var bn *big.Int - if exponent <= 3 { - mantissa >>= 8 * (3 - exponent) - bn = big.NewInt(int64(mantissa)) - } else { - bn = big.NewInt(int64(mantissa)) - bn.Lsh(bn, 8*(exponent-3)) - } - - // Make it negative if the sign bit is set. - if isNegative { - bn = bn.Neg(bn) - } - - return bn + return workmath.CompactToBig(compact) } // BigToCompact converts a whole number N to a compact representation using @@ -92,41 +51,7 @@ func CompactToBig(compact uint32) *big.Int { // of precision, so values larger than (2^23 - 1) only encode the most // significant digits of the number. See CompactToBig for details. func BigToCompact(n *big.Int) uint32 { - // No need to do any work if it's zero. - if n.Sign() == 0 { - return 0 - } - - // Since the base for the exponent is 256, the exponent can be treated - // as the number of bytes. So, shift the number right or left - // accordingly. This is equivalent to: - // mantissa = mantissa / 256^(exponent-3) - var mantissa uint32 - exponent := uint(len(n.Bytes())) - if exponent <= 3 { - mantissa = uint32(n.Bits()[0]) - mantissa <<= 8 * (3 - exponent) - } else { - // Use a copy to avoid modifying the caller's original number. - tn := new(big.Int).Set(n) - mantissa = uint32(tn.Rsh(tn, 8*(exponent-3)).Bits()[0]) - } - - // When the mantissa already has the sign bit set, the number is too - // large to fit into the available 23-bits, so divide the number by 256 - // and increment the exponent accordingly. - if mantissa&0x00800000 != 0 { - mantissa >>= 8 - exponent++ - } - - // Pack the exponent, sign bit, and mantissa into an unsigned 32-bit - // int and return it. - compact := uint32(exponent<<24) | mantissa - if n.Sign() < 0 { - compact |= 0x00800000 - } - return compact + return workmath.BigToCompact(n) } // CalcWork calculates a work value from difficulty bits. Bitcoin increases @@ -140,17 +65,7 @@ func BigToCompact(n *big.Int) uint32 { // potential division by zero and really small floating point numbers, the // result adds 1 to the denominator and multiplies the numerator by 2^256. func CalcWork(bits uint32) *big.Int { - // Return a work value of zero if the passed difficulty bits represent - // a negative number. Note this should not happen in practice with valid - // blocks, but an invalid block could trigger it. - difficultyNum := CompactToBig(bits) - if difficultyNum.Sign() <= 0 { - return big.NewInt(0) - } - - // (1 << 256) / (difficultyNum + 1) - denominator := new(big.Int).Add(difficultyNum, bigOne) - return new(big.Int).Div(oneLsh256, denominator) + return workmath.CalcWork(bits) } // calcEasiestDifficulty calculates the easiest possible difficulty that a block diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index e8fad241fa..2b499c4e8c 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -14,11 +14,11 @@ import ( "encoding/binary" "errors" "fmt" - "math" "runtime" "time" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/blockchain/internal/testhelper" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -43,16 +43,6 @@ const ( numLargeReorgBlocks = 1088 ) -var ( - // opTrueScript is simply a public key script that contains the OP_TRUE - // opcode. It is defined here to reduce garbage creation. - opTrueScript = []byte{txscript.OP_TRUE} - - // lowFee is a single satoshi and exists to make the test code more - // readable. - lowFee = btcutil.Amount(1) -) - // TestInstance is an interface that describes a specific test instance returned // by the tests generated in this package. It should be type asserted to one // of the concrete test instance types in order to test accordingly. @@ -165,31 +155,6 @@ type BlockDisconnectExpectUTXO struct { // This implements the TestInstance interface. func (b BlockDisconnectExpectUTXO) FullBlockTestInstance() {} -// spendableOut represents a transaction output that is spendable along with -// additional metadata such as the block its in and how much it pays. -type spendableOut struct { - prevOut wire.OutPoint - amount btcutil.Amount -} - -// makeSpendableOutForTx returns a spendable output for the given transaction -// and transaction output index within the transaction. -func makeSpendableOutForTx(tx *wire.MsgTx, txOutIndex uint32) spendableOut { - return spendableOut{ - prevOut: wire.OutPoint{ - Hash: tx.TxHash(), - Index: txOutIndex, - }, - amount: btcutil.Amount(tx.TxOut[txOutIndex].Value), - } -} - -// makeSpendableOut returns a spendable output for the given block, transaction -// index within the block, and transaction output index within the transaction. -func makeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) spendableOut { - return makeSpendableOutForTx(block.Transactions[txIndex], txOutIndex) -} - // testGenerator houses state used to easy the process of generating test blocks // that build from one another along with housing other useful things such as // available spendable outputs used throughout the tests. @@ -203,7 +168,7 @@ type testGenerator struct { blockHeights map[string]int32 // Used for tracking spendable coinbase outputs. - spendableOuts []spendableOut + spendableOuts []testhelper.SpendableOut prevCollectedHash chainhash.Hash // Common key for any tests which require signed transactions. @@ -255,62 +220,12 @@ func pushDataScript(items ...[]byte) []byte { return script } -// 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(blockHeight int32, extraNonce uint64) ([]byte, error) { - return txscript.NewScriptBuilder().AddInt64(int64(blockHeight)). - AddInt64(int64(extraNonce)).Script() -} - -// opReturnScript returns a provably-pruneable OP_RETURN script with the -// provided data. -func opReturnScript(data []byte) []byte { - builder := txscript.NewScriptBuilder() - script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() - if err != nil { - panic(err) - } - return script -} - -// uniqueOpReturnScript returns a standard provably-pruneable OP_RETURN script -// with a random uint64 encoded as the data. -func uniqueOpReturnScript() []byte { - rand, err := wire.RandomUint64() - if err != nil { - panic(err) - } - - data := make([]byte, 8) - binary.LittleEndian.PutUint64(data[0:8], rand) - return opReturnScript(data) -} - // createCoinbaseTx returns a coinbase transaction paying an appropriate // subsidy based on the passed block height. The coinbase signature script // conforms to the requirements of version 2 blocks. func (g *testGenerator) createCoinbaseTx(blockHeight int32) *wire.MsgTx { - extraNonce := uint64(0) - coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce) - if err != nil { - panic(err) - } - - tx := wire.NewMsgTx(1) - 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), - Sequence: wire.MaxTxInSequenceNum, - SignatureScript: coinbaseScript, - }) - tx.AddTxOut(&wire.TxOut{ - Value: blockchain.CalcBlockSubsidy(blockHeight, g.params), - PkScript: opTrueScript, - }) - return tx + return testhelper.CreateCoinbaseTx( + blockHeight, blockchain.CalcBlockSubsidy(blockHeight, g.params)) } // calcMerkleRoot creates a merkle tree from the slice of transactions and @@ -327,71 +242,6 @@ func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash { return blockchain.CalcMerkleRoot(utilTxns, false) } -// 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. -// -// NOTE: This function will never solve blocks with a nonce of 0. This is done -// so the 'nextBlock' function can properly detect when a nonce was modified by -// a munge function. -func solveBlock(header *wire.BlockHeader) 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. - targetDifficulty := blockchain.CompactToBig(header.Bits) - 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 { - - results <- sbResult{true, i} - return - } - } - } - results <- sbResult{false, 0} - } - - startNonce := uint32(1) - 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 -} - // additionalCoinbase returns a function that itself takes a block and // modifies it by adding the provided amount to coinbase subsidy. func additionalCoinbase(amount btcutil.Amount) func(*wire.MsgBlock) { @@ -444,33 +294,14 @@ func additionalTx(tx *wire.MsgTx) func(*wire.MsgBlock) { } } -// createSpendTx creates a transaction that spends from the provided spendable -// output and includes an additional unique OP_RETURN output to ensure the -// transaction ends up with a unique hash. The script is a simple OP_TRUE -// script which avoids the need to track addresses and signature scripts in the -// tests. -func createSpendTx(spend *spendableOut, fee btcutil.Amount) *wire.MsgTx { - spendTx := wire.NewMsgTx(1) - spendTx.AddTxIn(&wire.TxIn{ - PreviousOutPoint: spend.prevOut, - Sequence: wire.MaxTxInSequenceNum, - SignatureScript: nil, - }) - spendTx.AddTxOut(wire.NewTxOut(int64(spend.amount-fee), - opTrueScript)) - spendTx.AddTxOut(wire.NewTxOut(0, uniqueOpReturnScript())) - - return spendTx -} - // createSpendTxForTx creates a transaction that spends from the first output of // the provided transaction and includes an additional unique OP_RETURN output // to ensure the transaction ends up with a unique hash. The public key script // is a simple OP_TRUE script which avoids the need to track addresses and // signature scripts in the tests. The signature script is nil. func createSpendTxForTx(tx *wire.MsgTx, fee btcutil.Amount) *wire.MsgTx { - spend := makeSpendableOutForTx(tx, 0) - return createSpendTx(&spend, fee) + spend := testhelper.MakeSpendableOutForTx(tx, 0) + return testhelper.CreateSpendTx(&spend, fee) } // nextBlock builds a new block that extends the current tip associated with the @@ -492,7 +323,7 @@ func createSpendTxForTx(tx *wire.MsgTx, fee btcutil.Amount) *wire.MsgTx { // applied after all munge functions have been invoked: // - The merkle root will be recalculated unless it was manually changed // - The block will be solved unless the nonce was changed -func (g *testGenerator) nextBlock(blockName string, spend *spendableOut, mungers ...func(*wire.MsgBlock)) *wire.MsgBlock { +func (g *testGenerator) nextBlock(blockName string, spend *testhelper.SpendableOut, mungers ...func(*wire.MsgBlock)) *wire.MsgBlock { // Create coinbase transaction for the block using any additional // subsidy if specified. nextHeight := g.tipHeight + 1 @@ -510,7 +341,7 @@ func (g *testGenerator) nextBlock(blockName string, spend *spendableOut, mungers // add it to the list of transactions to include in the block. // The script is a simple OP_TRUE script in order to avoid the // need to track addresses and signature scripts in the tests. - txns = append(txns, createSpendTx(spend, fee)) + txns = append(txns, testhelper.CreateSpendTx(spend, fee)) } // Use a timestamp that is one second after the previous block unless @@ -547,7 +378,7 @@ func (g *testGenerator) nextBlock(blockName string, spend *spendableOut, mungers // Only solve the block if the nonce wasn't manually changed by a munge // function. - if block.Header.Nonce == curNonce && !solveBlock(&block.Header) { + if block.Header.Nonce == curNonce && !testhelper.SolveBlock(&block.Header) { panic(fmt.Sprintf("Unable to solve block at height %d", nextHeight)) } @@ -594,7 +425,7 @@ func (g *testGenerator) setTip(blockName string) { // oldestCoinbaseOuts removes the oldest coinbase output that was previously // saved to the generator and returns the set as a slice. -func (g *testGenerator) oldestCoinbaseOut() spendableOut { +func (g *testGenerator) oldestCoinbaseOut() testhelper.SpendableOut { op := g.spendableOuts[0] g.spendableOuts = g.spendableOuts[1:] return op @@ -603,7 +434,7 @@ func (g *testGenerator) oldestCoinbaseOut() spendableOut { // saveTipCoinbaseOut adds the coinbase tx output in the current tip block to // the list of spendable outputs. func (g *testGenerator) saveTipCoinbaseOut() { - g.spendableOuts = append(g.spendableOuts, makeSpendableOut(g.tip, 0, 0)) + g.spendableOuts = append(g.spendableOuts, testhelper.MakeSpendableOut(g.tip, 0, 0)) g.prevCollectedHash = g.tip.BlockHash() } @@ -947,7 +778,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { tests = append(tests, testInstances) // Collect spendable outputs. This simplifies the code below. - var outs []*spendableOut + var outs []*testhelper.SpendableOut for i := uint16(0); i < coinbaseMaturity; i++ { op := g.oldestCoinbaseOut() outs = append(outs, &op) @@ -985,7 +816,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // \-> b3(1) g.setTip("b1") g.nextBlock("b3", outs[1]) - b3Tx1Out := makeSpendableOut(g.tip, 1, 0) + b3Tx1Out := testhelper.MakeSpendableOut(g.tip, 1, 0) acceptedToSideChainWithExpectedTip("b2") // Extend b3 fork to make the alternative chain longer and force reorg. @@ -1298,9 +1129,9 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // \-> b38(b37.tx[1]) // g.setTip("b35") - doubleSpendTx := createSpendTx(outs[11], lowFee) + doubleSpendTx := testhelper.CreateSpendTx(outs[11], testhelper.LowFee) g.nextBlock("b37", outs[11], additionalTx(doubleSpendTx)) - b37Tx1Out := makeSpendableOut(g.tip, 1, 0) + b37Tx1Out := testhelper.MakeSpendableOut(g.tip, 1, 0) rejected(blockchain.ErrMissingTxOut) g.setTip("b35") @@ -1336,7 +1167,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { txnsNeeded := (maxBlockSigOps / redeemScriptSigOps) + 1 prevTx := b.Transactions[1] for i := 0; i < txnsNeeded; i++ { - prevTx = createSpendTxForTx(prevTx, lowFee) + prevTx = createSpendTxForTx(prevTx, testhelper.LowFee) prevTx.TxOut[0].Value -= 2 prevTx.AddTxOut(wire.NewTxOut(2, p2shScript)) b.AddTransaction(prevTx) @@ -1356,8 +1187,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { for i := 0; i < txnsNeeded; i++ { // Create a signed transaction that spends from the // associated p2sh output in b39. - spend := makeSpendableOutForTx(b39.Transactions[i+2], 2) - tx := createSpendTx(&spend, lowFee) + spend := testhelper.MakeSpendableOutForTx(b39.Transactions[i+2], 2) + tx := testhelper.CreateSpendTx(&spend, testhelper.LowFee) sig, err := txscript.RawTxInSignature(tx, 0, redeemScript, txscript.SigHashAll, g.privKey) if err != nil { @@ -1373,7 +1204,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // the block one over the max allowed. fill := maxBlockSigOps - (txnsNeeded * redeemScriptSigOps) + 1 finalTx := b.Transactions[len(b.Transactions)-1] - tx := createSpendTxForTx(finalTx, lowFee) + tx := createSpendTxForTx(finalTx, testhelper.LowFee) tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill) b.AddTransaction(tx) }) @@ -1387,8 +1218,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { g.nextBlock("b41", outs[12], func(b *wire.MsgBlock) { txnsNeeded := (maxBlockSigOps / redeemScriptSigOps) for i := 0; i < txnsNeeded; i++ { - spend := makeSpendableOutForTx(b39.Transactions[i+2], 2) - tx := createSpendTx(&spend, lowFee) + spend := testhelper.MakeSpendableOutForTx(b39.Transactions[i+2], 2) + tx := testhelper.CreateSpendTx(&spend, testhelper.LowFee) sig, err := txscript.RawTxInSignature(tx, 0, redeemScript, txscript.SigHashAll, g.privKey) if err != nil { @@ -1407,7 +1238,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { return } finalTx := b.Transactions[len(b.Transactions)-1] - tx := createSpendTxForTx(finalTx, lowFee) + tx := createSpendTxForTx(finalTx, testhelper.LowFee) tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill) b.AddTransaction(tx) }) @@ -1437,7 +1268,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // ... -> b43(13) // \-> b44(14) g.nextBlock("b44", nil, func(b *wire.MsgBlock) { - nonCoinbaseTx := createSpendTx(outs[14], lowFee) + nonCoinbaseTx := testhelper.CreateSpendTx(outs[14], testhelper.LowFee) b.Transactions[0] = nonCoinbaseTx }) rejected(blockchain.ErrFirstTxNotCoinbase) @@ -1642,7 +1473,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { g.setTip("b55") b57 := g.nextBlock("b57", outs[16], func(b *wire.MsgBlock) { tx2 := b.Transactions[1] - tx3 := createSpendTxForTx(tx2, lowFee) + tx3 := createSpendTxForTx(tx2, testhelper.LowFee) b.AddTransaction(tx3) }) g.assertTipBlockNumTxns(3) @@ -1687,7 +1518,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // in the block. spendTx := b.Transactions[1] for i := 0; i < 4; i++ { - spendTx = createSpendTxForTx(spendTx, lowFee) + spendTx = createSpendTxForTx(spendTx, testhelper.LowFee) b.AddTransaction(spendTx) } @@ -1719,7 +1550,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // \-> b59(17) g.setTip("b57") g.nextBlock("b59", outs[17], func(b *wire.MsgBlock) { - b.Transactions[1].TxOut[0].Value = int64(outs[17].amount) + 1 + b.Transactions[1].TxOut[0].Value = int64(outs[17].Amount) + 1 }) rejected(blockchain.ErrSpendTooHigh) @@ -1824,7 +1655,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // ... b64(18) -> b65(19) g.setTip("b64") g.nextBlock("b65", outs[19], func(b *wire.MsgBlock) { - tx3 := createSpendTxForTx(b.Transactions[1], lowFee) + tx3 := createSpendTxForTx(b.Transactions[1], testhelper.LowFee) b.AddTransaction(tx3) }) accepted() @@ -1834,8 +1665,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // ... -> b65(19) // \-> b66(20) g.nextBlock("b66", nil, func(b *wire.MsgBlock) { - tx2 := createSpendTx(outs[20], lowFee) - tx3 := createSpendTxForTx(tx2, lowFee) + tx2 := testhelper.CreateSpendTx(outs[20], testhelper.LowFee) + tx3 := createSpendTxForTx(tx2, testhelper.LowFee) b.AddTransaction(tx3) b.AddTransaction(tx2) }) @@ -1849,8 +1680,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { g.setTip("b65") g.nextBlock("b67", outs[20], func(b *wire.MsgBlock) { tx2 := b.Transactions[1] - tx3 := createSpendTxForTx(tx2, lowFee) - tx4 := createSpendTxForTx(tx2, lowFee) + tx3 := createSpendTxForTx(tx2, testhelper.LowFee) + tx4 := createSpendTxForTx(tx2, testhelper.LowFee) b.AddTransaction(tx3) b.AddTransaction(tx4) }) @@ -1973,7 +1804,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { txscript.OP_ELSE, txscript.OP_TRUE, txscript.OP_ENDIF} g.nextBlock("b74", outs[23], replaceSpendScript(script), func(b *wire.MsgBlock) { tx2 := b.Transactions[1] - tx3 := createSpendTxForTx(tx2, lowFee) + tx3 := createSpendTxForTx(tx2, testhelper.LowFee) tx3.TxIn[0].SignatureScript = []byte{txscript.OP_FALSE} b.AddTransaction(tx3) }) @@ -1994,7 +1825,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { const zeroCoin = int64(0) spendTx := b.Transactions[1] for i := 0; i < numAdditionalOutputs; i++ { - spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opTrueScript)) + spendTx.AddTxOut(wire.NewTxOut(zeroCoin, testhelper.OpTrueScript)) } // Add transactions spending from the outputs added above that @@ -2003,14 +1834,14 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // NOTE: The createSpendTx func adds the OP_RETURN output. zeroFee := btcutil.Amount(0) for i := uint32(0); i < numAdditionalOutputs; i++ { - spend := makeSpendableOut(b, 1, i+2) - tx := createSpendTx(&spend, zeroFee) + spend := testhelper.MakeSpendableOut(b, 1, i+2) + tx := testhelper.CreateSpendTx(&spend, zeroFee) b.AddTransaction(tx) } }) g.assertTipBlockNumTxns(6) g.assertTipBlockTxOutOpReturn(5, 1) - b75OpReturnOut := makeSpendableOut(g.tip, 5, 1) + b75OpReturnOut := testhelper.MakeSpendableOut(g.tip, 5, 1) accepted() // Reorg to a side chain that does not contain the OP_RETURNs. @@ -2043,7 +1874,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // An OP_RETURN output doesn't have any value and the default behavior // of nextBlock is to assign a fee of one, so increment the amount here // to effective negate that behavior. - b75OpReturnOut.amount++ + b75OpReturnOut.Amount++ g.nextBlock("b80", &b75OpReturnOut) rejected(blockchain.ErrMissingTxOut) @@ -2059,7 +1890,10 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { const zeroCoin = int64(0) spendTx := b.Transactions[1] for i := 0; i < numAdditionalOutputs; i++ { - opRetScript := uniqueOpReturnScript() + opRetScript, err := testhelper.UniqueOpReturnScript() + if err != nil { + panic(err) + } spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opRetScript)) } }) @@ -2075,7 +1909,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { g.nextBlock("b82a", outs[28]) accepted() - b82aTx1Out0 := makeSpendableOut(g.tip, 1, 0) + b82aTx1Out0 := testhelper.MakeSpendableOut(g.tip, 1, 0) g.nextBlock("b83a", &b82aTx1Out0) accepted() @@ -2097,17 +1931,17 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // We expect b82a output to now be a utxo since b83a was spending it and it was // removed from the main chain. blockDisconnectExpectUTXO("b82aTx1Out0", - true, b82aTx1Out0.prevOut, g.blocksByName["b83a"].BlockHash()) + true, b82aTx1Out0.PrevOut, g.blocksByName["b83a"].BlockHash()) // We expect the output from b82 to not exist once b82a itself has been removed // from the main chain. blockDisconnectExpectUTXO("b82aTx1Out0", - false, b82aTx1Out0.prevOut, g.blocksByName["b82a"].BlockHash()) + false, b82aTx1Out0.PrevOut, g.blocksByName["b82a"].BlockHash()) // The output that was being spent in b82a should exist after the removal of // b82a. blockDisconnectExpectUTXO("outs[28]", - true, outs[28].prevOut, g.blocksByName["b82a"].BlockHash()) + true, outs[28].PrevOut, g.blocksByName["b82a"].BlockHash()) // Create block 84 and reorg out the sidechain with b83a as the tip. // diff --git a/blockchain/internal/testhelper/README.md b/blockchain/internal/testhelper/README.md new file mode 100644 index 0000000000..40b339bf79 --- /dev/null +++ b/blockchain/internal/testhelper/README.md @@ -0,0 +1,16 @@ +testhelper +========== + +[![Build Status](https://github.com/btcsuite/btcd/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/btcd/actions) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/btcsuite/btcd/blockchain/testhelper) + +Package testhelper provides functions that are used internally in the +btcd/blockchain and btcd/blockchain/fullblocktests package to test consensus +validation rules. Mainly provided to avoid dependency cycles internally among +the different packages in btcd. + +## License + +Package testhelper is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/blockchain/internal/testhelper/common.go b/blockchain/internal/testhelper/common.go new file mode 100644 index 0000000000..681097480c --- /dev/null +++ b/blockchain/internal/testhelper/common.go @@ -0,0 +1,194 @@ +package testhelper + +import ( + "encoding/binary" + "math" + "runtime" + + "github.com/btcsuite/btcd/blockchain/internal/workmath" + "github.com/btcsuite/btcd/btcutil" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcd/wire" +) + +var ( + // OpTrueScript is simply a public key script that contains the OP_TRUE + // opcode. It is defined here to reduce garbage creation. + OpTrueScript = []byte{txscript.OP_TRUE} + + // LowFee is a single satoshi and exists to make the test code more + // readable. + LowFee = btcutil.Amount(1) +) + +// CreateSpendTx creates a transaction that spends from the provided spendable +// output and includes an additional unique OP_RETURN output to ensure the +// transaction ends up with a unique hash. The script is a simple OP_TRUE +// script which avoids the need to track addresses and signature scripts in the +// tests. +func CreateSpendTx(spend *SpendableOut, fee btcutil.Amount) *wire.MsgTx { + spendTx := wire.NewMsgTx(1) + spendTx.AddTxIn(&wire.TxIn{ + PreviousOutPoint: spend.PrevOut, + Sequence: wire.MaxTxInSequenceNum, + SignatureScript: nil, + }) + spendTx.AddTxOut(wire.NewTxOut(int64(spend.Amount-fee), + OpTrueScript)) + opRetScript, err := UniqueOpReturnScript() + if err != nil { + panic(err) + } + spendTx.AddTxOut(wire.NewTxOut(0, opRetScript)) + + return spendTx +} + +// CreateCoinbaseTx returns a coinbase transaction paying an appropriate +// subsidy based on the passed block height and the block subsidy. The +// coinbase signature script conforms to the requirements of version 2 blocks. +func CreateCoinbaseTx(blockHeight int32, blockSubsidy int64) *wire.MsgTx { + extraNonce := uint64(0) + coinbaseScript, err := StandardCoinbaseScript(blockHeight, extraNonce) + if err != nil { + panic(err) + } + + tx := wire.NewMsgTx(1) + 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), + Sequence: wire.MaxTxInSequenceNum, + SignatureScript: coinbaseScript, + }) + tx.AddTxOut(&wire.TxOut{ + Value: blockSubsidy, + PkScript: OpTrueScript, + }) + return tx +} + +// 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(blockHeight int32, extraNonce uint64) ([]byte, error) { + return txscript.NewScriptBuilder().AddInt64(int64(blockHeight)). + AddInt64(int64(extraNonce)).Script() +} + +// OpReturnScript returns a provably-pruneable OP_RETURN script with the +// provided data. +func OpReturnScript(data []byte) ([]byte, error) { + builder := txscript.NewScriptBuilder() + script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script() + if err != nil { + return nil, err + } + return script, nil +} + +// UniqueOpReturnScript returns a standard provably-pruneable OP_RETURN script +// with a random uint64 encoded as the data. +func UniqueOpReturnScript() ([]byte, error) { + rand, err := wire.RandomUint64() + if err != nil { + return nil, err + } + + data := make([]byte, 8) + binary.LittleEndian.PutUint64(data[0:8], rand) + return OpReturnScript(data) +} + +// SpendableOut represents a transaction output that is spendable along with +// additional metadata such as the block its in and how much it pays. +type SpendableOut struct { + PrevOut wire.OutPoint + Amount btcutil.Amount +} + +// MakeSpendableOutForTx returns a spendable output for the given transaction +// and transaction output index within the transaction. +func MakeSpendableOutForTx(tx *wire.MsgTx, txOutIndex uint32) SpendableOut { + return SpendableOut{ + PrevOut: wire.OutPoint{ + Hash: tx.TxHash(), + Index: txOutIndex, + }, + Amount: btcutil.Amount(tx.TxOut[txOutIndex].Value), + } +} + +// MakeSpendableOut returns a spendable output for the given block, transaction +// index within the block, and transaction output index within the transaction. +func MakeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) SpendableOut { + return MakeSpendableOutForTx(block.Transactions[txIndex], txOutIndex) +} + +// 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. +// +// NOTE: This function will never solve blocks with a nonce of 0. This is done +// so the 'nextBlock' function can properly detect when a nonce was modified by +// a munge function. +func SolveBlock(header *wire.BlockHeader) 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. + targetDifficulty := workmath.CompactToBig(header.Bits) + 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 workmath.HashToBig(&hash).Cmp( + targetDifficulty) <= 0 { + + results <- sbResult{true, i} + return + } + } + } + results <- sbResult{false, 0} + } + + startNonce := uint32(1) + 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 +} diff --git a/blockchain/internal/workmath/README.md b/blockchain/internal/workmath/README.md new file mode 100644 index 0000000000..879b2dcfd7 --- /dev/null +++ b/blockchain/internal/workmath/README.md @@ -0,0 +1,15 @@ +workmath +========== + +[![Build Status](https://github.com/btcsuite/btcd/workflows/Build%20and%20Test/badge.svg)](https://github.com/btcsuite/btcd/actions) +[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org) +[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/btcsuite/btcd/workmath) + +Package workmath provides utility functions that are related with calculating +the work from difficulty bits. This package was introduced to avoid import +cycles in btcd. + +## License + +Package workmath is licensed under the [copyfree](http://copyfree.org) ISC +License. diff --git a/blockchain/internal/workmath/difficulty.go b/blockchain/internal/workmath/difficulty.go new file mode 100644 index 0000000000..8ff7adad1c --- /dev/null +++ b/blockchain/internal/workmath/difficulty.go @@ -0,0 +1,153 @@ +// Copyright (c) 2013-2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package workmath + +import ( + "math/big" + + "github.com/btcsuite/btcd/chaincfg/chainhash" +) + +var ( + // bigOne is 1 represented as a big.Int. It is defined here to avoid + // the overhead of creating it multiple times. + bigOne = big.NewInt(1) + + // oneLsh256 is 1 shifted left 256 bits. It is defined here to avoid + // the overhead of creating it multiple times. + oneLsh256 = new(big.Int).Lsh(bigOne, 256) +) + +// HashToBig converts a chainhash.Hash into a big.Int that can be used to +// perform math comparisons. +func HashToBig(hash *chainhash.Hash) *big.Int { + // A Hash is in little-endian, but the big package wants the bytes in + // big-endian, so reverse them. + buf := *hash + blen := len(buf) + for i := 0; i < blen/2; i++ { + buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] + } + + return new(big.Int).SetBytes(buf[:]) +} + +// CompactToBig converts a compact representation of a whole number N to an +// unsigned 32-bit number. The representation is similar to IEEE754 floating +// point numbers. +// +// Like IEEE754 floating point, there are three basic components: the sign, +// the exponent, and the mantissa. They are broken out as follows: +// +// - the most significant 8 bits represent the unsigned base 256 exponent +// - bit 23 (the 24th bit) represents the sign bit +// - the least significant 23 bits represent the mantissa +// +// ------------------------------------------------- +// | Exponent | Sign | Mantissa | +// ------------------------------------------------- +// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] | +// ------------------------------------------------- +// +// The formula to calculate N is: +// +// N = (-1^sign) * mantissa * 256^(exponent-3) +// +// This compact form is only used in bitcoin to encode unsigned 256-bit numbers +// which represent difficulty targets, thus there really is not a need for a +// sign bit, but it is implemented here to stay consistent with bitcoind. +func CompactToBig(compact uint32) *big.Int { + // Extract the mantissa, sign bit, and exponent. + mantissa := compact & 0x007fffff + isNegative := compact&0x00800000 != 0 + exponent := uint(compact >> 24) + + // Since the base for the exponent is 256, the exponent can be treated + // as the number of bytes to represent the full 256-bit number. So, + // treat the exponent as the number of bytes and shift the mantissa + // right or left accordingly. This is equivalent to: + // N = mantissa * 256^(exponent-3) + var bn *big.Int + if exponent <= 3 { + mantissa >>= 8 * (3 - exponent) + bn = big.NewInt(int64(mantissa)) + } else { + bn = big.NewInt(int64(mantissa)) + bn.Lsh(bn, 8*(exponent-3)) + } + + // Make it negative if the sign bit is set. + if isNegative { + bn = bn.Neg(bn) + } + + return bn +} + +// BigToCompact converts a whole number N to a compact representation using +// an unsigned 32-bit number. The compact representation only provides 23 bits +// of precision, so values larger than (2^23 - 1) only encode the most +// significant digits of the number. See CompactToBig for details. +func BigToCompact(n *big.Int) uint32 { + // No need to do any work if it's zero. + if n.Sign() == 0 { + return 0 + } + + // Since the base for the exponent is 256, the exponent can be treated + // as the number of bytes. So, shift the number right or left + // accordingly. This is equivalent to: + // mantissa = mantissa / 256^(exponent-3) + var mantissa uint32 + exponent := uint(len(n.Bytes())) + if exponent <= 3 { + mantissa = uint32(n.Bits()[0]) + mantissa <<= 8 * (3 - exponent) + } else { + // Use a copy to avoid modifying the caller's original number. + tn := new(big.Int).Set(n) + mantissa = uint32(tn.Rsh(tn, 8*(exponent-3)).Bits()[0]) + } + + // When the mantissa already has the sign bit set, the number is too + // large to fit into the available 23-bits, so divide the number by 256 + // and increment the exponent accordingly. + if mantissa&0x00800000 != 0 { + mantissa >>= 8 + exponent++ + } + + // Pack the exponent, sign bit, and mantissa into an unsigned 32-bit + // int and return it. + compact := uint32(exponent<<24) | mantissa + if n.Sign() < 0 { + compact |= 0x00800000 + } + return compact +} + +// CalcWork calculates a work value from difficulty bits. Bitcoin increases +// the difficulty for generating a block by decreasing the value which the +// generated hash must be less than. This difficulty target is stored in each +// block header using a compact representation as described in the documentation +// for CompactToBig. The main chain is selected by choosing the chain that has +// the most proof of work (highest difficulty). Since a lower target difficulty +// value equates to higher actual difficulty, the work value which will be +// accumulated must be the inverse of the difficulty. Also, in order to avoid +// potential division by zero and really small floating point numbers, the +// result adds 1 to the denominator and multiplies the numerator by 2^256. +func CalcWork(bits uint32) *big.Int { + // Return a work value of zero if the passed difficulty bits represent + // a negative number. Note this should not happen in practice with valid + // blocks, but an invalid block could trigger it. + difficultyNum := CompactToBig(bits) + if difficultyNum.Sign() <= 0 { + return big.NewInt(0) + } + + // (1 << 256) / (difficultyNum + 1) + denominator := new(big.Int).Add(difficultyNum, bigOne) + return new(big.Int).Div(oneLsh256, denominator) +} diff --git a/blockchain/difficulty_test.go b/blockchain/internal/workmath/difficulty_test.go similarity index 98% rename from blockchain/difficulty_test.go rename to blockchain/internal/workmath/difficulty_test.go index c4d8fb6ef5..bed4d1f13f 100644 --- a/blockchain/difficulty_test.go +++ b/blockchain/internal/workmath/difficulty_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. -package blockchain +package workmath import ( "math/big"