Merge pull request #2155 from kcalvinalvin/2024-04-02-invalidate-block

blockchain, fullblocktests, workmath, testhelper: add InvalidateBlock() method to BlockChain
This commit is contained in:
Olaoluwa Osuntokun 2024-05-22 13:15:47 -07:00 committed by GitHub
commit c4677255bd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 972 additions and 302 deletions

View file

@ -1798,6 +1798,144 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has
return headers 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 // 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 // connected and disconnected to and from the tip of the main chain for the
// purpose of supporting optional indexes. // purpose of supporting optional indexes.

View file

@ -6,10 +6,12 @@ package blockchain
import ( import (
"fmt" "fmt"
"math/rand"
"reflect" "reflect"
"testing" "testing"
"time" "time"
"github.com/btcsuite/btcd/blockchain/internal/testhelper"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
@ -1311,3 +1313,312 @@ func TestIsAncestor(t *testing.T) {
branch2Nodes[0].hash.String()) 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.")
}
}()
}
}

View file

@ -14,6 +14,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/btcsuite/btcd/blockchain/internal/testhelper"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
@ -396,3 +397,96 @@ func newFakeNode(parent *blockNode, blockVersion int32, bits uint32, timestamp t
} }
return newBlockNode(header, parent) 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
}

View file

@ -8,31 +8,14 @@ import (
"math/big" "math/big"
"time" "time"
"github.com/btcsuite/btcd/blockchain/internal/workmath"
"github.com/btcsuite/btcd/chaincfg/chainhash" "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 // HashToBig converts a chainhash.Hash into a big.Int that can be used to
// perform math comparisons. // perform math comparisons.
func HashToBig(hash *chainhash.Hash) *big.Int { func HashToBig(hash *chainhash.Hash) *big.Int {
// A Hash is in little-endian, but the big package wants the bytes in return workmath.HashToBig(hash)
// 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 // 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 // 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. // sign bit, but it is implemented here to stay consistent with bitcoind.
func CompactToBig(compact uint32) *big.Int { func CompactToBig(compact uint32) *big.Int {
// Extract the mantissa, sign bit, and exponent. return workmath.CompactToBig(compact)
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 // 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 // of precision, so values larger than (2^23 - 1) only encode the most
// significant digits of the number. See CompactToBig for details. // significant digits of the number. See CompactToBig for details.
func BigToCompact(n *big.Int) uint32 { func BigToCompact(n *big.Int) uint32 {
// No need to do any work if it's zero. return workmath.BigToCompact(n)
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 // 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 // potential division by zero and really small floating point numbers, the
// result adds 1 to the denominator and multiplies the numerator by 2^256. // result adds 1 to the denominator and multiplies the numerator by 2^256.
func CalcWork(bits uint32) *big.Int { func CalcWork(bits uint32) *big.Int {
// Return a work value of zero if the passed difficulty bits represent return workmath.CalcWork(bits)
// 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)
} }
// calcEasiestDifficulty calculates the easiest possible difficulty that a block // calcEasiestDifficulty calculates the easiest possible difficulty that a block

View file

@ -14,11 +14,11 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"fmt" "fmt"
"math"
"runtime" "runtime"
"time" "time"
"github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/blockchain/internal/testhelper"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
@ -43,16 +43,6 @@ const (
numLargeReorgBlocks = 1088 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 // 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 // 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. // of the concrete test instance types in order to test accordingly.
@ -165,31 +155,6 @@ type BlockDisconnectExpectUTXO struct {
// This implements the TestInstance interface. // This implements the TestInstance interface.
func (b BlockDisconnectExpectUTXO) FullBlockTestInstance() {} 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 // 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 // that build from one another along with housing other useful things such as
// available spendable outputs used throughout the tests. // available spendable outputs used throughout the tests.
@ -203,7 +168,7 @@ type testGenerator struct {
blockHeights map[string]int32 blockHeights map[string]int32
// Used for tracking spendable coinbase outputs. // Used for tracking spendable coinbase outputs.
spendableOuts []spendableOut spendableOuts []testhelper.SpendableOut
prevCollectedHash chainhash.Hash prevCollectedHash chainhash.Hash
// Common key for any tests which require signed transactions. // Common key for any tests which require signed transactions.
@ -255,62 +220,12 @@ func pushDataScript(items ...[]byte) []byte {
return script 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 // createCoinbaseTx returns a coinbase transaction paying an appropriate
// subsidy based on the passed block height. The coinbase signature script // subsidy based on the passed block height. The coinbase signature script
// conforms to the requirements of version 2 blocks. // conforms to the requirements of version 2 blocks.
func (g *testGenerator) createCoinbaseTx(blockHeight int32) *wire.MsgTx { func (g *testGenerator) createCoinbaseTx(blockHeight int32) *wire.MsgTx {
extraNonce := uint64(0) return testhelper.CreateCoinbaseTx(
coinbaseScript, err := standardCoinbaseScript(blockHeight, extraNonce) blockHeight, blockchain.CalcBlockSubsidy(blockHeight, g.params))
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
} }
// calcMerkleRoot creates a merkle tree from the slice of transactions and // 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) 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 // additionalCoinbase returns a function that itself takes a block and
// modifies it by adding the provided amount to coinbase subsidy. // modifies it by adding the provided amount to coinbase subsidy.
func additionalCoinbase(amount btcutil.Amount) func(*wire.MsgBlock) { 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 // createSpendTxForTx creates a transaction that spends from the first output of
// the provided transaction and includes an additional unique OP_RETURN output // 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 // 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 // is a simple OP_TRUE script which avoids the need to track addresses and
// signature scripts in the tests. The signature script is nil. // signature scripts in the tests. The signature script is nil.
func createSpendTxForTx(tx *wire.MsgTx, fee btcutil.Amount) *wire.MsgTx { func createSpendTxForTx(tx *wire.MsgTx, fee btcutil.Amount) *wire.MsgTx {
spend := makeSpendableOutForTx(tx, 0) spend := testhelper.MakeSpendableOutForTx(tx, 0)
return createSpendTx(&spend, fee) return testhelper.CreateSpendTx(&spend, fee)
} }
// nextBlock builds a new block that extends the current tip associated with the // 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: // applied after all munge functions have been invoked:
// - The merkle root will be recalculated unless it was manually changed // - The merkle root will be recalculated unless it was manually changed
// - The block will be solved unless the nonce was 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 // Create coinbase transaction for the block using any additional
// subsidy if specified. // subsidy if specified.
nextHeight := g.tipHeight + 1 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. // 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 // The script is a simple OP_TRUE script in order to avoid the
// need to track addresses and signature scripts in the tests. // 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 // 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 // Only solve the block if the nonce wasn't manually changed by a munge
// function. // 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", panic(fmt.Sprintf("Unable to solve block at height %d",
nextHeight)) nextHeight))
} }
@ -594,7 +425,7 @@ func (g *testGenerator) setTip(blockName string) {
// oldestCoinbaseOuts removes the oldest coinbase output that was previously // oldestCoinbaseOuts removes the oldest coinbase output that was previously
// saved to the generator and returns the set as a slice. // 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] op := g.spendableOuts[0]
g.spendableOuts = g.spendableOuts[1:] g.spendableOuts = g.spendableOuts[1:]
return op return op
@ -603,7 +434,7 @@ func (g *testGenerator) oldestCoinbaseOut() spendableOut {
// saveTipCoinbaseOut adds the coinbase tx output in the current tip block to // saveTipCoinbaseOut adds the coinbase tx output in the current tip block to
// the list of spendable outputs. // the list of spendable outputs.
func (g *testGenerator) saveTipCoinbaseOut() { 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() g.prevCollectedHash = g.tip.BlockHash()
} }
@ -947,7 +778,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
tests = append(tests, testInstances) tests = append(tests, testInstances)
// Collect spendable outputs. This simplifies the code below. // Collect spendable outputs. This simplifies the code below.
var outs []*spendableOut var outs []*testhelper.SpendableOut
for i := uint16(0); i < coinbaseMaturity; i++ { for i := uint16(0); i < coinbaseMaturity; i++ {
op := g.oldestCoinbaseOut() op := g.oldestCoinbaseOut()
outs = append(outs, &op) outs = append(outs, &op)
@ -985,7 +816,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// \-> b3(1) // \-> b3(1)
g.setTip("b1") g.setTip("b1")
g.nextBlock("b3", outs[1]) g.nextBlock("b3", outs[1])
b3Tx1Out := makeSpendableOut(g.tip, 1, 0) b3Tx1Out := testhelper.MakeSpendableOut(g.tip, 1, 0)
acceptedToSideChainWithExpectedTip("b2") acceptedToSideChainWithExpectedTip("b2")
// Extend b3 fork to make the alternative chain longer and force reorg. // 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]) // \-> b38(b37.tx[1])
// //
g.setTip("b35") g.setTip("b35")
doubleSpendTx := createSpendTx(outs[11], lowFee) doubleSpendTx := testhelper.CreateSpendTx(outs[11], testhelper.LowFee)
g.nextBlock("b37", outs[11], additionalTx(doubleSpendTx)) g.nextBlock("b37", outs[11], additionalTx(doubleSpendTx))
b37Tx1Out := makeSpendableOut(g.tip, 1, 0) b37Tx1Out := testhelper.MakeSpendableOut(g.tip, 1, 0)
rejected(blockchain.ErrMissingTxOut) rejected(blockchain.ErrMissingTxOut)
g.setTip("b35") g.setTip("b35")
@ -1336,7 +1167,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
txnsNeeded := (maxBlockSigOps / redeemScriptSigOps) + 1 txnsNeeded := (maxBlockSigOps / redeemScriptSigOps) + 1
prevTx := b.Transactions[1] prevTx := b.Transactions[1]
for i := 0; i < txnsNeeded; i++ { for i := 0; i < txnsNeeded; i++ {
prevTx = createSpendTxForTx(prevTx, lowFee) prevTx = createSpendTxForTx(prevTx, testhelper.LowFee)
prevTx.TxOut[0].Value -= 2 prevTx.TxOut[0].Value -= 2
prevTx.AddTxOut(wire.NewTxOut(2, p2shScript)) prevTx.AddTxOut(wire.NewTxOut(2, p2shScript))
b.AddTransaction(prevTx) b.AddTransaction(prevTx)
@ -1356,8 +1187,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
for i := 0; i < txnsNeeded; i++ { for i := 0; i < txnsNeeded; i++ {
// Create a signed transaction that spends from the // Create a signed transaction that spends from the
// associated p2sh output in b39. // associated p2sh output in b39.
spend := makeSpendableOutForTx(b39.Transactions[i+2], 2) spend := testhelper.MakeSpendableOutForTx(b39.Transactions[i+2], 2)
tx := createSpendTx(&spend, lowFee) tx := testhelper.CreateSpendTx(&spend, testhelper.LowFee)
sig, err := txscript.RawTxInSignature(tx, 0, sig, err := txscript.RawTxInSignature(tx, 0,
redeemScript, txscript.SigHashAll, g.privKey) redeemScript, txscript.SigHashAll, g.privKey)
if err != nil { if err != nil {
@ -1373,7 +1204,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// the block one over the max allowed. // the block one over the max allowed.
fill := maxBlockSigOps - (txnsNeeded * redeemScriptSigOps) + 1 fill := maxBlockSigOps - (txnsNeeded * redeemScriptSigOps) + 1
finalTx := b.Transactions[len(b.Transactions)-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) tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill)
b.AddTransaction(tx) b.AddTransaction(tx)
}) })
@ -1387,8 +1218,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.nextBlock("b41", outs[12], func(b *wire.MsgBlock) { g.nextBlock("b41", outs[12], func(b *wire.MsgBlock) {
txnsNeeded := (maxBlockSigOps / redeemScriptSigOps) txnsNeeded := (maxBlockSigOps / redeemScriptSigOps)
for i := 0; i < txnsNeeded; i++ { for i := 0; i < txnsNeeded; i++ {
spend := makeSpendableOutForTx(b39.Transactions[i+2], 2) spend := testhelper.MakeSpendableOutForTx(b39.Transactions[i+2], 2)
tx := createSpendTx(&spend, lowFee) tx := testhelper.CreateSpendTx(&spend, testhelper.LowFee)
sig, err := txscript.RawTxInSignature(tx, 0, sig, err := txscript.RawTxInSignature(tx, 0,
redeemScript, txscript.SigHashAll, g.privKey) redeemScript, txscript.SigHashAll, g.privKey)
if err != nil { if err != nil {
@ -1407,7 +1238,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
return return
} }
finalTx := b.Transactions[len(b.Transactions)-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) tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill)
b.AddTransaction(tx) b.AddTransaction(tx)
}) })
@ -1437,7 +1268,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// ... -> b43(13) // ... -> b43(13)
// \-> b44(14) // \-> b44(14)
g.nextBlock("b44", nil, func(b *wire.MsgBlock) { g.nextBlock("b44", nil, func(b *wire.MsgBlock) {
nonCoinbaseTx := createSpendTx(outs[14], lowFee) nonCoinbaseTx := testhelper.CreateSpendTx(outs[14], testhelper.LowFee)
b.Transactions[0] = nonCoinbaseTx b.Transactions[0] = nonCoinbaseTx
}) })
rejected(blockchain.ErrFirstTxNotCoinbase) rejected(blockchain.ErrFirstTxNotCoinbase)
@ -1642,7 +1473,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.setTip("b55") g.setTip("b55")
b57 := g.nextBlock("b57", outs[16], func(b *wire.MsgBlock) { b57 := g.nextBlock("b57", outs[16], func(b *wire.MsgBlock) {
tx2 := b.Transactions[1] tx2 := b.Transactions[1]
tx3 := createSpendTxForTx(tx2, lowFee) tx3 := createSpendTxForTx(tx2, testhelper.LowFee)
b.AddTransaction(tx3) b.AddTransaction(tx3)
}) })
g.assertTipBlockNumTxns(3) g.assertTipBlockNumTxns(3)
@ -1687,7 +1518,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// in the block. // in the block.
spendTx := b.Transactions[1] spendTx := b.Transactions[1]
for i := 0; i < 4; i++ { for i := 0; i < 4; i++ {
spendTx = createSpendTxForTx(spendTx, lowFee) spendTx = createSpendTxForTx(spendTx, testhelper.LowFee)
b.AddTransaction(spendTx) b.AddTransaction(spendTx)
} }
@ -1719,7 +1550,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// \-> b59(17) // \-> b59(17)
g.setTip("b57") g.setTip("b57")
g.nextBlock("b59", outs[17], func(b *wire.MsgBlock) { 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) rejected(blockchain.ErrSpendTooHigh)
@ -1824,7 +1655,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// ... b64(18) -> b65(19) // ... b64(18) -> b65(19)
g.setTip("b64") g.setTip("b64")
g.nextBlock("b65", outs[19], func(b *wire.MsgBlock) { 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) b.AddTransaction(tx3)
}) })
accepted() accepted()
@ -1834,8 +1665,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
// ... -> b65(19) // ... -> b65(19)
// \-> b66(20) // \-> b66(20)
g.nextBlock("b66", nil, func(b *wire.MsgBlock) { g.nextBlock("b66", nil, func(b *wire.MsgBlock) {
tx2 := createSpendTx(outs[20], lowFee) tx2 := testhelper.CreateSpendTx(outs[20], testhelper.LowFee)
tx3 := createSpendTxForTx(tx2, lowFee) tx3 := createSpendTxForTx(tx2, testhelper.LowFee)
b.AddTransaction(tx3) b.AddTransaction(tx3)
b.AddTransaction(tx2) b.AddTransaction(tx2)
}) })
@ -1849,8 +1680,8 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.setTip("b65") g.setTip("b65")
g.nextBlock("b67", outs[20], func(b *wire.MsgBlock) { g.nextBlock("b67", outs[20], func(b *wire.MsgBlock) {
tx2 := b.Transactions[1] tx2 := b.Transactions[1]
tx3 := createSpendTxForTx(tx2, lowFee) tx3 := createSpendTxForTx(tx2, testhelper.LowFee)
tx4 := createSpendTxForTx(tx2, lowFee) tx4 := createSpendTxForTx(tx2, testhelper.LowFee)
b.AddTransaction(tx3) b.AddTransaction(tx3)
b.AddTransaction(tx4) b.AddTransaction(tx4)
}) })
@ -1973,7 +1804,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
txscript.OP_ELSE, txscript.OP_TRUE, txscript.OP_ENDIF} txscript.OP_ELSE, txscript.OP_TRUE, txscript.OP_ENDIF}
g.nextBlock("b74", outs[23], replaceSpendScript(script), func(b *wire.MsgBlock) { g.nextBlock("b74", outs[23], replaceSpendScript(script), func(b *wire.MsgBlock) {
tx2 := b.Transactions[1] tx2 := b.Transactions[1]
tx3 := createSpendTxForTx(tx2, lowFee) tx3 := createSpendTxForTx(tx2, testhelper.LowFee)
tx3.TxIn[0].SignatureScript = []byte{txscript.OP_FALSE} tx3.TxIn[0].SignatureScript = []byte{txscript.OP_FALSE}
b.AddTransaction(tx3) b.AddTransaction(tx3)
}) })
@ -1994,7 +1825,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
const zeroCoin = int64(0) const zeroCoin = int64(0)
spendTx := b.Transactions[1] spendTx := b.Transactions[1]
for i := 0; i < numAdditionalOutputs; i++ { 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 // 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. // NOTE: The createSpendTx func adds the OP_RETURN output.
zeroFee := btcutil.Amount(0) zeroFee := btcutil.Amount(0)
for i := uint32(0); i < numAdditionalOutputs; i++ { for i := uint32(0); i < numAdditionalOutputs; i++ {
spend := makeSpendableOut(b, 1, i+2) spend := testhelper.MakeSpendableOut(b, 1, i+2)
tx := createSpendTx(&spend, zeroFee) tx := testhelper.CreateSpendTx(&spend, zeroFee)
b.AddTransaction(tx) b.AddTransaction(tx)
} }
}) })
g.assertTipBlockNumTxns(6) g.assertTipBlockNumTxns(6)
g.assertTipBlockTxOutOpReturn(5, 1) g.assertTipBlockTxOutOpReturn(5, 1)
b75OpReturnOut := makeSpendableOut(g.tip, 5, 1) b75OpReturnOut := testhelper.MakeSpendableOut(g.tip, 5, 1)
accepted() accepted()
// Reorg to a side chain that does not contain the OP_RETURNs. // 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 // 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 // of nextBlock is to assign a fee of one, so increment the amount here
// to effective negate that behavior. // to effective negate that behavior.
b75OpReturnOut.amount++ b75OpReturnOut.Amount++
g.nextBlock("b80", &b75OpReturnOut) g.nextBlock("b80", &b75OpReturnOut)
rejected(blockchain.ErrMissingTxOut) rejected(blockchain.ErrMissingTxOut)
@ -2059,7 +1890,10 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
const zeroCoin = int64(0) const zeroCoin = int64(0)
spendTx := b.Transactions[1] spendTx := b.Transactions[1]
for i := 0; i < numAdditionalOutputs; i++ { for i := 0; i < numAdditionalOutputs; i++ {
opRetScript := uniqueOpReturnScript() opRetScript, err := testhelper.UniqueOpReturnScript()
if err != nil {
panic(err)
}
spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opRetScript)) spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opRetScript))
} }
}) })
@ -2075,7 +1909,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
g.nextBlock("b82a", outs[28]) g.nextBlock("b82a", outs[28])
accepted() accepted()
b82aTx1Out0 := makeSpendableOut(g.tip, 1, 0) b82aTx1Out0 := testhelper.MakeSpendableOut(g.tip, 1, 0)
g.nextBlock("b83a", &b82aTx1Out0) g.nextBlock("b83a", &b82aTx1Out0)
accepted() 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 // We expect b82a output to now be a utxo since b83a was spending it and it was
// removed from the main chain. // removed from the main chain.
blockDisconnectExpectUTXO("b82aTx1Out0", 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 // We expect the output from b82 to not exist once b82a itself has been removed
// from the main chain. // from the main chain.
blockDisconnectExpectUTXO("b82aTx1Out0", 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 // The output that was being spent in b82a should exist after the removal of
// b82a. // b82a.
blockDisconnectExpectUTXO("outs[28]", 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. // Create block 84 and reorg out the sidechain with b83a as the tip.
// //

View file

@ -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.

View file

@ -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
}

View file

@ -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.

View file

@ -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)
}

View file

@ -2,7 +2,7 @@
// Use of this source code is governed by an ISC // Use of this source code is governed by an ISC
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
package blockchain package workmath
import ( import (
"math/big" "math/big"