mirror of
https://github.com/btcsuite/btcd.git
synced 2025-02-22 14:22:49 +01:00
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:
commit
c4677255bd
10 changed files with 972 additions and 302 deletions
|
@ -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.
|
||||
|
|
|
@ -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.")
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
//
|
||||
|
|
16
blockchain/internal/testhelper/README.md
Normal file
16
blockchain/internal/testhelper/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
testhelper
|
||||
==========
|
||||
|
||||
[](https://github.com/btcsuite/btcd/actions)
|
||||
[](http://copyfree.org)
|
||||
[](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.
|
194
blockchain/internal/testhelper/common.go
Normal file
194
blockchain/internal/testhelper/common.go
Normal 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
|
||||
}
|
15
blockchain/internal/workmath/README.md
Normal file
15
blockchain/internal/workmath/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
workmath
|
||||
==========
|
||||
|
||||
[](https://github.com/btcsuite/btcd/actions)
|
||||
[](http://copyfree.org)
|
||||
[](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.
|
153
blockchain/internal/workmath/difficulty.go
Normal file
153
blockchain/internal/workmath/difficulty.go
Normal 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)
|
||||
}
|
|
@ -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"
|
Loading…
Add table
Reference in a new issue