f73b79ae04
Co-authored-by: Roy Lee <roylee17@gmail.com>
2133 lines
74 KiB
Go
2133 lines
74 KiB
Go
// Copyright (c) 2016 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// The vast majority of the rules tested in this package were ported from the
|
|
// the original Java-based 'official' block acceptance tests at
|
|
// https://github.com/TheBlueMatt/test-scripts as well as some additional tests
|
|
// available in the Core python port of the same.
|
|
|
|
package fullblocktests
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/lbryio/lbcd/blockchain"
|
|
"github.com/lbryio/lbcd/btcec"
|
|
"github.com/lbryio/lbcd/chaincfg"
|
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
|
"github.com/lbryio/lbcd/txscript"
|
|
"github.com/lbryio/lbcd/wire"
|
|
btcutil "github.com/lbryio/lbcutil"
|
|
)
|
|
|
|
const (
|
|
// Intentionally defined here rather than using constants from codebase
|
|
// to ensure consensus changes are detected.
|
|
maxBlockSigOps = 20000
|
|
maxBlockSize = 8000000
|
|
minCoinbaseScriptLen = 2
|
|
maxCoinbaseScriptLen = 100
|
|
medianTimeBlocks = 11
|
|
maxScriptElementSize = 20000
|
|
|
|
// numLargeReorgBlocks is the number of blocks to use in the large block
|
|
// reorg test (when enabled). This is the equivalent of 1 week's worth
|
|
// of blocks.
|
|
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.
|
|
type TestInstance interface {
|
|
FullBlockTestInstance()
|
|
}
|
|
|
|
// AcceptedBlock defines a test instance that expects a block to be accepted to
|
|
// the blockchain either by extending the main chain, on a side chain, or as an
|
|
// orphan.
|
|
type AcceptedBlock struct {
|
|
Name string
|
|
Block *wire.MsgBlock
|
|
Height int32
|
|
IsMainChain bool
|
|
IsOrphan bool
|
|
}
|
|
|
|
// Ensure AcceptedBlock implements the TestInstance interface.
|
|
var _ TestInstance = AcceptedBlock{}
|
|
|
|
// FullBlockTestInstance only exists to allow AcceptedBlock to be treated as a
|
|
// TestInstance.
|
|
//
|
|
// This implements the TestInstance interface.
|
|
func (b AcceptedBlock) FullBlockTestInstance() {}
|
|
|
|
// RejectedBlock defines a test instance that expects a block to be rejected by
|
|
// the blockchain consensus rules.
|
|
type RejectedBlock struct {
|
|
Name string
|
|
Block *wire.MsgBlock
|
|
Height int32
|
|
RejectCode blockchain.ErrorCode
|
|
}
|
|
|
|
// Ensure RejectedBlock implements the TestInstance interface.
|
|
var _ TestInstance = RejectedBlock{}
|
|
|
|
// FullBlockTestInstance only exists to allow RejectedBlock to be treated as a
|
|
// TestInstance.
|
|
//
|
|
// This implements the TestInstance interface.
|
|
func (b RejectedBlock) FullBlockTestInstance() {}
|
|
|
|
// OrphanOrRejectedBlock defines a test instance that expects a block to either
|
|
// be accepted as an orphan or rejected. This is useful since some
|
|
// implementations might optimize the immediate rejection of orphan blocks when
|
|
// their parent was previously rejected, while others might accept it as an
|
|
// orphan that eventually gets flushed (since the parent can never be accepted
|
|
// to ultimately link it).
|
|
type OrphanOrRejectedBlock struct {
|
|
Name string
|
|
Block *wire.MsgBlock
|
|
Height int32
|
|
}
|
|
|
|
// Ensure ExpectedTip implements the TestInstance interface.
|
|
var _ TestInstance = OrphanOrRejectedBlock{}
|
|
|
|
// FullBlockTestInstance only exists to allow OrphanOrRejectedBlock to be
|
|
// treated as a TestInstance.
|
|
//
|
|
// This implements the TestInstance interface.
|
|
func (b OrphanOrRejectedBlock) FullBlockTestInstance() {}
|
|
|
|
// ExpectedTip defines a test instance that expects a block to be the current
|
|
// tip of the main chain.
|
|
type ExpectedTip struct {
|
|
Name string
|
|
Block *wire.MsgBlock
|
|
Height int32
|
|
}
|
|
|
|
// Ensure ExpectedTip implements the TestInstance interface.
|
|
var _ TestInstance = ExpectedTip{}
|
|
|
|
// FullBlockTestInstance only exists to allow ExpectedTip to be treated as a
|
|
// TestInstance.
|
|
//
|
|
// This implements the TestInstance interface.
|
|
func (b ExpectedTip) FullBlockTestInstance() {}
|
|
|
|
// RejectedNonCanonicalBlock defines a test instance that expects a serialized
|
|
// block that is not canonical and therefore should be rejected.
|
|
type RejectedNonCanonicalBlock struct {
|
|
Name string
|
|
RawBlock []byte
|
|
Height int32
|
|
}
|
|
|
|
// FullBlockTestInstance only exists to allow RejectedNonCanonicalBlock to be treated as
|
|
// a TestInstance.
|
|
//
|
|
// This implements the TestInstance interface.
|
|
func (b RejectedNonCanonicalBlock) 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.
|
|
type testGenerator struct {
|
|
params *chaincfg.Params
|
|
tip *wire.MsgBlock
|
|
tipName string
|
|
tipHeight int32
|
|
blocks map[chainhash.Hash]*wire.MsgBlock
|
|
blocksByName map[string]*wire.MsgBlock
|
|
blockHeights map[string]int32
|
|
|
|
// Used for tracking spendable coinbase outputs.
|
|
spendableOuts []spendableOut
|
|
prevCollectedHash chainhash.Hash
|
|
|
|
// Common key for any tests which require signed transactions.
|
|
privKey *btcec.PrivateKey
|
|
}
|
|
|
|
// makeTestGenerator returns a test generator instance initialized with the
|
|
// genesis block as the tip.
|
|
func makeTestGenerator(params *chaincfg.Params) (testGenerator, error) {
|
|
privKey, _ := btcec.PrivKeyFromBytes(btcec.S256(), []byte{0x01})
|
|
genesis := params.GenesisBlock
|
|
genesisHash := genesis.BlockHash()
|
|
return testGenerator{
|
|
params: params,
|
|
blocks: map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis},
|
|
blocksByName: map[string]*wire.MsgBlock{"genesis": genesis},
|
|
blockHeights: map[string]int32{"genesis": 0},
|
|
tip: genesis,
|
|
tipName: "genesis",
|
|
tipHeight: 0,
|
|
privKey: privKey,
|
|
}, nil
|
|
}
|
|
|
|
// payToScriptHashScript returns a standard pay-to-script-hash for the provided
|
|
// redeem script.
|
|
func payToScriptHashScript(redeemScript []byte) []byte {
|
|
redeemScriptHash := btcutil.Hash160(redeemScript)
|
|
script, err := txscript.NewScriptBuilder().
|
|
AddOp(txscript.OP_HASH160).AddData(redeemScriptHash).
|
|
AddOp(txscript.OP_EQUAL).Script()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return script
|
|
}
|
|
|
|
// pushDataScript returns a script with the provided items individually pushed
|
|
// to the stack.
|
|
func pushDataScript(items ...[]byte) []byte {
|
|
builder := txscript.NewScriptBuilder()
|
|
for _, item := range items {
|
|
builder.AddData(item)
|
|
}
|
|
script, err := builder.Script()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
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
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
merkles := blockchain.BuildMerkleTreeStore(utilTxns, false)
|
|
return *merkles[len(merkles)-1]
|
|
}
|
|
|
|
// 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.BlockPoWHash()
|
|
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) {
|
|
return func(b *wire.MsgBlock) {
|
|
// Increase the first proof-of-work coinbase subsidy by the
|
|
// provided amount.
|
|
b.Transactions[0].TxOut[0].Value += int64(amount)
|
|
}
|
|
}
|
|
|
|
// additionalSpendFee returns a function that itself takes a block and modifies
|
|
// it by adding the provided fee to the spending transaction.
|
|
//
|
|
// NOTE: The coinbase value is NOT updated to reflect the additional fee. Use
|
|
// 'additionalCoinbase' for that purpose.
|
|
func additionalSpendFee(fee btcutil.Amount) func(*wire.MsgBlock) {
|
|
return func(b *wire.MsgBlock) {
|
|
// Increase the fee of the spending transaction by reducing the
|
|
// amount paid.
|
|
if int64(fee) > b.Transactions[1].TxOut[0].Value {
|
|
panic(fmt.Sprintf("additionalSpendFee: fee of %d "+
|
|
"exceeds available spend transaction value",
|
|
fee))
|
|
}
|
|
b.Transactions[1].TxOut[0].Value -= int64(fee)
|
|
}
|
|
}
|
|
|
|
// replaceSpendScript returns a function that itself takes a block and modifies
|
|
// it by replacing the public key script of the spending transaction.
|
|
func replaceSpendScript(pkScript []byte) func(*wire.MsgBlock) {
|
|
return func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxOut[0].PkScript = pkScript
|
|
}
|
|
}
|
|
|
|
// replaceCoinbaseSigScript returns a function that itself takes a block and
|
|
// modifies it by replacing the signature key script of the coinbase.
|
|
func replaceCoinbaseSigScript(script []byte) func(*wire.MsgBlock) {
|
|
return func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxIn[0].SignatureScript = script
|
|
}
|
|
}
|
|
|
|
// additionalTx returns a function that itself takes a block and modifies it by
|
|
// adding the the provided transaction.
|
|
func additionalTx(tx *wire.MsgTx) func(*wire.MsgBlock) {
|
|
return func(b *wire.MsgBlock) {
|
|
b.AddTransaction(tx)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// nextBlock builds a new block that extends the current tip associated with the
|
|
// generator and updates the generator's tip to the newly generated block.
|
|
//
|
|
// The block will include the following:
|
|
// - A coinbase that pays the required subsidy to an OP_TRUE script
|
|
// - When a spendable output is provided:
|
|
// - A transaction that spends from the provided output the following outputs:
|
|
// - One that pays the inputs amount minus 1 atom to an OP_TRUE script
|
|
// - One that contains an OP_RETURN output with a random uint64 in order to
|
|
// ensure the transaction has a unique hash
|
|
//
|
|
// Additionally, if one or more munge functions are specified, they will be
|
|
// invoked with the block prior to solving it. This provides callers with the
|
|
// opportunity to modify the block which is especially useful for testing.
|
|
//
|
|
// In order to simply the logic in the munge functions, the following rules are
|
|
// 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 {
|
|
// Create coinbase transaction for the block using any additional
|
|
// subsidy if specified.
|
|
nextHeight := g.tipHeight + 1
|
|
coinbaseTx := g.createCoinbaseTx(nextHeight)
|
|
txns := []*wire.MsgTx{coinbaseTx}
|
|
if spend != nil {
|
|
// Create the transaction with a fee of 1 atom for the
|
|
// miner and increase the coinbase subsidy accordingly.
|
|
fee := btcutil.Amount(1)
|
|
coinbaseTx.TxOut[0].Value += int64(fee)
|
|
|
|
// Create 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, then add
|
|
// 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))
|
|
}
|
|
|
|
// 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 nextHeight == 1 {
|
|
ts = time.Unix(time.Now().Unix(), 0)
|
|
} else {
|
|
ts = g.tip.Header.Timestamp.Add(time.Second)
|
|
}
|
|
|
|
block := wire.MsgBlock{
|
|
Header: wire.BlockHeader{
|
|
Version: 1,
|
|
PrevBlock: g.tip.BlockHash(),
|
|
MerkleRoot: calcMerkleRoot(txns),
|
|
Bits: g.params.PowLimitBits,
|
|
Timestamp: ts,
|
|
Nonce: 0, // To be solved.
|
|
},
|
|
Transactions: txns,
|
|
}
|
|
|
|
// Perform any block munging just before solving. Only recalculate the
|
|
// merkle root if it wasn't manually changed by a munge function.
|
|
curMerkleRoot := block.Header.MerkleRoot
|
|
curNonce := block.Header.Nonce
|
|
for _, f := range mungers {
|
|
f(&block)
|
|
}
|
|
if block.Header.MerkleRoot == curMerkleRoot {
|
|
block.Header.MerkleRoot = calcMerkleRoot(block.Transactions)
|
|
}
|
|
|
|
// Only solve the block if the nonce wasn't manually changed by a munge
|
|
// function.
|
|
if block.Header.Nonce == curNonce && !solveBlock(&block.Header) {
|
|
panic(fmt.Sprintf("Unable to solve block at height %d",
|
|
nextHeight))
|
|
}
|
|
|
|
// Update generator state and return the block.
|
|
blockHash := block.BlockHash()
|
|
g.blocks[blockHash] = &block
|
|
g.blocksByName[blockName] = &block
|
|
g.blockHeights[blockName] = nextHeight
|
|
g.tip = &block
|
|
g.tipName = blockName
|
|
g.tipHeight = nextHeight
|
|
return &block
|
|
}
|
|
|
|
// updateBlockState manually updates the generator state to remove all internal
|
|
// map references to a block via its old hash and insert new ones for the new
|
|
// block hash. This is useful if the test code has to manually change a block
|
|
// after 'nextBlock' has returned.
|
|
func (g *testGenerator) updateBlockState(oldBlockName string, oldBlockHash chainhash.Hash, newBlockName string, newBlock *wire.MsgBlock) {
|
|
// Look up the height from the existing entries.
|
|
blockHeight := g.blockHeights[oldBlockName]
|
|
|
|
// Remove existing entries.
|
|
delete(g.blocks, oldBlockHash)
|
|
delete(g.blocksByName, oldBlockName)
|
|
delete(g.blockHeights, oldBlockName)
|
|
|
|
// Add new entries.
|
|
newBlockHash := newBlock.BlockHash()
|
|
g.blocks[newBlockHash] = newBlock
|
|
g.blocksByName[newBlockName] = newBlock
|
|
g.blockHeights[newBlockName] = blockHeight
|
|
}
|
|
|
|
// setTip changes the tip of the instance to the block with the provided name.
|
|
// This is useful since the tip is used for things such as generating subsequent
|
|
// blocks.
|
|
func (g *testGenerator) setTip(blockName string) {
|
|
g.tip = g.blocksByName[blockName]
|
|
g.tipName = blockName
|
|
g.tipHeight = g.blockHeights[blockName]
|
|
}
|
|
|
|
// 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 {
|
|
op := g.spendableOuts[0]
|
|
g.spendableOuts = g.spendableOuts[1:]
|
|
return op
|
|
}
|
|
|
|
// 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.prevCollectedHash = g.tip.BlockHash()
|
|
}
|
|
|
|
// saveSpendableCoinbaseOuts adds all coinbase outputs from the last block that
|
|
// had its coinbase tx output colleted to the current tip. This is useful to
|
|
// batch the collection of coinbase outputs once the tests reach a stable point
|
|
// so they don't have to manually add them for the right tests which will
|
|
// ultimately end up being the best chain.
|
|
func (g *testGenerator) saveSpendableCoinbaseOuts() {
|
|
// Ensure tip is reset to the current one when done.
|
|
curTipName := g.tipName
|
|
defer g.setTip(curTipName)
|
|
|
|
// Loop through the ancestors of the current tip until the
|
|
// reaching the block that has already had the coinbase outputs
|
|
// collected.
|
|
var collectBlocks []*wire.MsgBlock
|
|
for b := g.tip; b != nil; b = g.blocks[b.Header.PrevBlock] {
|
|
if b.BlockHash() == g.prevCollectedHash {
|
|
break
|
|
}
|
|
collectBlocks = append(collectBlocks, b)
|
|
}
|
|
for i := range collectBlocks {
|
|
g.tip = collectBlocks[len(collectBlocks)-1-i]
|
|
g.saveTipCoinbaseOut()
|
|
}
|
|
}
|
|
|
|
// nonCanonicalVarInt return a variable-length encoded integer that is encoded
|
|
// with 9 bytes even though it could be encoded with a minimal canonical
|
|
// encoding.
|
|
func nonCanonicalVarInt(val uint32) []byte {
|
|
var rv [9]byte
|
|
rv[0] = 0xff
|
|
binary.LittleEndian.PutUint64(rv[1:], uint64(val))
|
|
return rv[:]
|
|
}
|
|
|
|
// encodeNonCanonicalBlock serializes the block in a non-canonical way by
|
|
// encoding the number of transactions using a variable-length encoded integer
|
|
// with 9 bytes even though it should be encoded with a minimal canonical
|
|
// encoding.
|
|
func encodeNonCanonicalBlock(b *wire.MsgBlock) []byte {
|
|
var buf bytes.Buffer
|
|
b.Header.BtcEncode(&buf, 0, wire.BaseEncoding)
|
|
buf.Write(nonCanonicalVarInt(uint32(len(b.Transactions))))
|
|
for _, tx := range b.Transactions {
|
|
tx.BtcEncode(&buf, 0, wire.BaseEncoding)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// cloneBlock returns a deep copy of the provided block.
|
|
func cloneBlock(b *wire.MsgBlock) wire.MsgBlock {
|
|
var blockCopy wire.MsgBlock
|
|
blockCopy.Header = b.Header
|
|
for _, tx := range b.Transactions {
|
|
blockCopy.AddTransaction(tx.Copy())
|
|
}
|
|
return blockCopy
|
|
}
|
|
|
|
// repeatOpcode returns a byte slice with the provided opcode repeated the
|
|
// specified number of times.
|
|
func repeatOpcode(opcode uint8, numRepeats int) []byte {
|
|
return bytes.Repeat([]byte{opcode}, numRepeats)
|
|
}
|
|
|
|
// assertScriptSigOpsCount panics if the provided script does not have the
|
|
// specified number of signature operations.
|
|
func assertScriptSigOpsCount(script []byte, expected int) {
|
|
numSigOps := txscript.GetSigOpCount(script)
|
|
if numSigOps != expected {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
panic(fmt.Sprintf("assertion failed at %s:%d: generated number "+
|
|
"of sigops for script is %d instead of expected %d",
|
|
file, line, numSigOps, expected))
|
|
}
|
|
}
|
|
|
|
// countBlockSigOps returns the number of legacy signature operations in the
|
|
// scripts in the passed block.
|
|
func countBlockSigOps(block *wire.MsgBlock) int {
|
|
totalSigOps := 0
|
|
for _, tx := range block.Transactions {
|
|
for _, txIn := range tx.TxIn {
|
|
numSigOps := txscript.GetSigOpCount(txIn.SignatureScript)
|
|
totalSigOps += numSigOps
|
|
}
|
|
for _, txOut := range tx.TxOut {
|
|
numSigOps := txscript.GetSigOpCount(txOut.PkScript)
|
|
totalSigOps += numSigOps
|
|
}
|
|
}
|
|
|
|
return totalSigOps
|
|
}
|
|
|
|
// assertTipBlockSigOpsCount panics if the current tip block associated with the
|
|
// generator does not have the specified number of signature operations.
|
|
func (g *testGenerator) assertTipBlockSigOpsCount(expected int) {
|
|
numSigOps := countBlockSigOps(g.tip)
|
|
if numSigOps != expected {
|
|
panic(fmt.Sprintf("generated number of sigops for block %q "+
|
|
"(height %d) is %d instead of expected %d", g.tipName,
|
|
g.tipHeight, numSigOps, expected))
|
|
}
|
|
}
|
|
|
|
// assertTipBlockSize panics if the if the current tip block associated with the
|
|
// generator does not have the specified size when serialized.
|
|
func (g *testGenerator) assertTipBlockSize(expected int) {
|
|
serializeSize := g.tip.SerializeSize()
|
|
if serializeSize != expected {
|
|
panic(fmt.Sprintf("block size of block %q (height %d) is %d "+
|
|
"instead of expected %d", g.tipName, g.tipHeight,
|
|
serializeSize, expected))
|
|
}
|
|
}
|
|
|
|
// assertTipNonCanonicalBlockSize panics if the if the current tip block
|
|
// associated with the generator does not have the specified non-canonical size
|
|
// when serialized.
|
|
func (g *testGenerator) assertTipNonCanonicalBlockSize(expected int) {
|
|
serializeSize := len(encodeNonCanonicalBlock(g.tip))
|
|
if serializeSize != expected {
|
|
panic(fmt.Sprintf("block size of block %q (height %d) is %d "+
|
|
"instead of expected %d", g.tipName, g.tipHeight,
|
|
serializeSize, expected))
|
|
}
|
|
}
|
|
|
|
// assertTipBlockNumTxns panics if the number of transactions in the current tip
|
|
// block associated with the generator does not match the specified value.
|
|
func (g *testGenerator) assertTipBlockNumTxns(expected int) {
|
|
numTxns := len(g.tip.Transactions)
|
|
if numTxns != expected {
|
|
panic(fmt.Sprintf("number of txns in block %q (height %d) is "+
|
|
"%d instead of expected %d", g.tipName, g.tipHeight,
|
|
numTxns, expected))
|
|
}
|
|
}
|
|
|
|
// assertTipBlockHash panics if the current tip block associated with the
|
|
// generator does not match the specified hash.
|
|
func (g *testGenerator) assertTipBlockHash(expected chainhash.Hash) {
|
|
hash := g.tip.BlockHash()
|
|
if hash != expected {
|
|
panic(fmt.Sprintf("block hash of block %q (height %d) is %v "+
|
|
"instead of expected %v", g.tipName, g.tipHeight, hash,
|
|
expected))
|
|
}
|
|
}
|
|
|
|
// assertTipBlockMerkleRoot panics if the merkle root in header of the current
|
|
// tip block associated with the generator does not match the specified hash.
|
|
func (g *testGenerator) assertTipBlockMerkleRoot(expected chainhash.Hash) {
|
|
hash := g.tip.Header.MerkleRoot
|
|
if hash != expected {
|
|
panic(fmt.Sprintf("merkle root of block %q (height %d) is %v "+
|
|
"instead of expected %v", g.tipName, g.tipHeight, hash,
|
|
expected))
|
|
}
|
|
}
|
|
|
|
// assertTipBlockTxOutOpReturn panics if the current tip block associated with
|
|
// the generator does not have an OP_RETURN script for the transaction output at
|
|
// the provided tx index and output index.
|
|
func (g *testGenerator) assertTipBlockTxOutOpReturn(txIndex, txOutIndex uint32) {
|
|
if txIndex >= uint32(len(g.tip.Transactions)) {
|
|
panic(fmt.Sprintf("Transaction index %d in block %q "+
|
|
"(height %d) does not exist", txIndex, g.tipName,
|
|
g.tipHeight))
|
|
}
|
|
|
|
tx := g.tip.Transactions[txIndex]
|
|
if txOutIndex >= uint32(len(tx.TxOut)) {
|
|
panic(fmt.Sprintf("transaction index %d output %d in block %q "+
|
|
"(height %d) does not exist", txIndex, txOutIndex,
|
|
g.tipName, g.tipHeight))
|
|
}
|
|
|
|
txOut := tx.TxOut[txOutIndex]
|
|
if txOut.PkScript[0] != txscript.OP_RETURN {
|
|
panic(fmt.Sprintf("transaction index %d output %d in block %q "+
|
|
"(height %d) is not an OP_RETURN", txIndex, txOutIndex,
|
|
g.tipName, g.tipHeight))
|
|
}
|
|
}
|
|
|
|
// Generate returns a slice of tests that can be used to exercise the consensus
|
|
// validation rules. The tests are intended to be flexible enough to allow both
|
|
// unit-style tests directly against the blockchain code as well as integration
|
|
// style tests over the peer-to-peer network. To achieve that goal, each test
|
|
// contains additional information about the expected result, however that
|
|
// information can be ignored when doing comparison tests between two
|
|
// independent versions over the peer-to-peer network.
|
|
func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
|
|
// In order to simplify the generation code which really should never
|
|
// fail unless the test code itself is broken, panics are used
|
|
// internally. This deferred func ensures any panics don't escape the
|
|
// generator by replacing the named error return with the underlying
|
|
// panic error.
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
tests = nil
|
|
|
|
switch rt := r.(type) {
|
|
case string:
|
|
err = errors.New(rt)
|
|
case error:
|
|
err = rt
|
|
default:
|
|
err = errors.New("Unknown panic")
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Create a test generator instance initialized with the genesis block
|
|
// as the tip.
|
|
g, err := makeTestGenerator(FbRegressionNetParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Define some convenience helper functions to return an individual test
|
|
// instance that has the described characteristics.
|
|
//
|
|
// acceptBlock creates a test instance that expects the provided block
|
|
// to be accepted by the consensus rules.
|
|
//
|
|
// rejectBlock creates a test instance that expects the provided block
|
|
// to be rejected by the consensus rules.
|
|
//
|
|
// rejectNonCanonicalBlock creates a test instance that encodes the
|
|
// provided block using a non-canonical encoded as described by the
|
|
// encodeNonCanonicalBlock function and expected it to be rejected.
|
|
//
|
|
// orphanOrRejectBlock creates a test instance that expected the
|
|
// provided block to either by accepted as an orphan or rejected by the
|
|
// consensus rules.
|
|
//
|
|
// expectTipBlock creates a test instance that expects the provided
|
|
// block to be the current tip of the block chain.
|
|
acceptBlock := func(blockName string, block *wire.MsgBlock, isMainChain, isOrphan bool) TestInstance {
|
|
blockHeight := g.blockHeights[blockName]
|
|
return AcceptedBlock{blockName, block, blockHeight, isMainChain,
|
|
isOrphan}
|
|
}
|
|
rejectBlock := func(blockName string, block *wire.MsgBlock, code blockchain.ErrorCode) TestInstance {
|
|
blockHeight := g.blockHeights[blockName]
|
|
return RejectedBlock{blockName, block, blockHeight, code}
|
|
}
|
|
rejectNonCanonicalBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
|
|
blockHeight := g.blockHeights[blockName]
|
|
encoded := encodeNonCanonicalBlock(block)
|
|
return RejectedNonCanonicalBlock{blockName, encoded, blockHeight}
|
|
}
|
|
orphanOrRejectBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
|
|
blockHeight := g.blockHeights[blockName]
|
|
return OrphanOrRejectedBlock{blockName, block, blockHeight}
|
|
}
|
|
expectTipBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
|
|
blockHeight := g.blockHeights[blockName]
|
|
return ExpectedTip{blockName, block, blockHeight}
|
|
}
|
|
|
|
// Define some convenience helper functions to populate the tests slice
|
|
// with test instances that have the described characteristics.
|
|
//
|
|
// accepted creates and appends a single acceptBlock test instance for
|
|
// the current tip which expects the block to be accepted to the main
|
|
// chain.
|
|
//
|
|
// acceptedToSideChainWithExpectedTip creates an appends a two-instance
|
|
// test. The first instance is an acceptBlock test instance for the
|
|
// current tip which expects the block to be accepted to a side chain.
|
|
// The second instance is an expectBlockTip test instance for provided
|
|
// values.
|
|
//
|
|
// rejected creates and appends a single rejectBlock test instance for
|
|
// the current tip.
|
|
//
|
|
// rejectedNonCanonical creates and appends a single
|
|
// rejectNonCanonicalBlock test instance for the current tip.
|
|
//
|
|
// orphanedOrRejected creates and appends a single orphanOrRejectBlock
|
|
// test instance for the current tip.
|
|
accepted := func() {
|
|
tests = append(tests, []TestInstance{
|
|
acceptBlock(g.tipName, g.tip, true, false),
|
|
})
|
|
}
|
|
acceptedToSideChainWithExpectedTip := func(tipName string) {
|
|
tests = append(tests, []TestInstance{
|
|
acceptBlock(g.tipName, g.tip, false, false),
|
|
expectTipBlock(tipName, g.blocksByName[tipName]),
|
|
})
|
|
}
|
|
rejected := func(code blockchain.ErrorCode) {
|
|
tests = append(tests, []TestInstance{
|
|
rejectBlock(g.tipName, g.tip, code),
|
|
})
|
|
}
|
|
rejectedNonCanonical := func() {
|
|
tests = append(tests, []TestInstance{
|
|
rejectNonCanonicalBlock(g.tipName, g.tip),
|
|
})
|
|
}
|
|
orphanedOrRejected := func() {
|
|
tests = append(tests, []TestInstance{
|
|
orphanOrRejectBlock(g.tipName, g.tip),
|
|
})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate enough blocks to have mature coinbase outputs to work with.
|
|
//
|
|
// genesis -> bm0 -> bm1 -> ... -> bm99
|
|
// ---------------------------------------------------------------------
|
|
|
|
coinbaseMaturity := g.params.CoinbaseMaturity
|
|
var testInstances []TestInstance
|
|
for i := uint16(0); i < coinbaseMaturity; i++ {
|
|
blockName := fmt.Sprintf("bm%d", i)
|
|
g.nextBlock(blockName, nil)
|
|
g.saveTipCoinbaseOut()
|
|
testInstances = append(testInstances, acceptBlock(g.tipName,
|
|
g.tip, true, false))
|
|
}
|
|
tests = append(tests, testInstances)
|
|
|
|
// Collect spendable outputs. This simplifies the code below.
|
|
var outs []*spendableOut
|
|
for i := uint16(0); i < coinbaseMaturity; i++ {
|
|
op := g.oldestCoinbaseOut()
|
|
outs = append(outs, &op)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Basic forking and reorg tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------
|
|
// The comments below identify the structure of the chain being built.
|
|
//
|
|
// The values in parenthesis repesent which outputs are being spent.
|
|
//
|
|
// For example, b1(0) indicates the first collected spendable output
|
|
// which, due to the code above to create the correct number of blocks,
|
|
// is the first output that can be spent at the current block height due
|
|
// to the coinbase maturity requirement.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Start by building a couple of blocks at current tip (value in parens
|
|
// is which output is spent):
|
|
//
|
|
// ... -> b1(0) -> b2(1)
|
|
g.nextBlock("b1", outs[0])
|
|
accepted()
|
|
|
|
g.nextBlock("b2", outs[1])
|
|
accepted()
|
|
|
|
// Create a fork from b1. There should not be a reorg since b2 was seen
|
|
// first.
|
|
//
|
|
// ... -> b1(0) -> b2(1)
|
|
// \-> b3(1)
|
|
g.setTip("b1")
|
|
g.nextBlock("b3", outs[1])
|
|
b3Tx1Out := makeSpendableOut(g.tip, 1, 0)
|
|
acceptedToSideChainWithExpectedTip("b2")
|
|
|
|
// Extend b3 fork to make the alternative chain longer and force reorg.
|
|
//
|
|
// ... -> b1(0) -> b2(1)
|
|
// \-> b3(1) -> b4(2)
|
|
g.nextBlock("b4", outs[2])
|
|
accepted()
|
|
|
|
// Extend b2 fork twice to make first chain longer and force reorg.
|
|
//
|
|
// ... -> b1(0) -> b2(1) -> b5(2) -> b6(3)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b2")
|
|
g.nextBlock("b5", outs[2])
|
|
acceptedToSideChainWithExpectedTip("b4")
|
|
|
|
g.nextBlock("b6", outs[3])
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Double spend tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a fork that double spends.
|
|
//
|
|
// ... -> b1(0) -> b2(1) -> b5(2) -> b6(3)
|
|
// \-> b7(2) -> b8(4)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b5")
|
|
g.nextBlock("b7", outs[2])
|
|
acceptedToSideChainWithExpectedTip("b6")
|
|
|
|
g.nextBlock("b8", outs[4])
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Too much proof-of-work coinbase tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a block that generates too coinbase.
|
|
//
|
|
// ... -> b1(0) -> b2(1) -> b5(2) -> b6(3)
|
|
// \-> b9(4)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b6")
|
|
g.nextBlock("b9", outs[4], additionalCoinbase(1))
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create a fork that ends with block that generates too much coinbase.
|
|
//
|
|
// ... -> b1(0) -> b2(1) -> b5(2) -> b6(3)
|
|
// \-> b10(3) -> b11(4)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b5")
|
|
g.nextBlock("b10", outs[3])
|
|
acceptedToSideChainWithExpectedTip("b6")
|
|
|
|
g.nextBlock("b11", outs[4], additionalCoinbase(1))
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create a fork that ends with block that generates too much coinbase
|
|
// as before, but with a valid fork first.
|
|
//
|
|
// ... -> b1(0) -> b2(1) -> b5(2) -> b6(3)
|
|
// | \-> b12(3) -> b13(4) -> b14(5)
|
|
// | (b12 added last)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b5")
|
|
b12 := g.nextBlock("b12", outs[3])
|
|
b13 := g.nextBlock("b13", outs[4])
|
|
b14 := g.nextBlock("b14", outs[5], additionalCoinbase(1))
|
|
tests = append(tests, []TestInstance{
|
|
acceptBlock("b13", b13, false, true),
|
|
acceptBlock("b14", b14, false, true),
|
|
rejectBlock("b12", b12, blockchain.ErrBadCoinbaseValue),
|
|
expectTipBlock("b13", b13),
|
|
})
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Checksig signature operation count tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Add a block with max allowed signature operations using OP_CHECKSIG.
|
|
//
|
|
// ... -> b5(2) -> b12(3) -> b13(4) -> b15(5)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b13")
|
|
manySigOps := repeatOpcode(txscript.OP_CHECKSIG, maxBlockSigOps)
|
|
g.nextBlock("b15", outs[5], replaceSpendScript(manySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Attempt to add block with more than max allowed signature operations
|
|
// using OP_CHECKSIG.
|
|
//
|
|
// ... -> b5(2) -> b12(3) -> b13(4) -> b15(5)
|
|
// \ \-> b16(7)
|
|
// \-> b3(1) -> b4(2)
|
|
tooManySigOps := repeatOpcode(txscript.OP_CHECKSIG, maxBlockSigOps+1)
|
|
g.nextBlock("b16", outs[6], replaceSpendScript(tooManySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Cross-fork spend tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends a tx created on a different fork.
|
|
//
|
|
// ... -> b5(2) -> b12(3) -> b13(4) -> b15(5)
|
|
// \ \-> b17(b3.tx[1])
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b15")
|
|
g.nextBlock("b17", &b3Tx1Out)
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that forks and spends a tx created on a third fork.
|
|
//
|
|
// ... -> b5(2) -> b12(3) -> b13(4) -> b15(5)
|
|
// | \-> b18(b3.tx[1]) -> b19(6)
|
|
// \-> b3(1) -> b4(2)
|
|
g.setTip("b13")
|
|
g.nextBlock("b18", &b3Tx1Out)
|
|
acceptedToSideChainWithExpectedTip("b15")
|
|
|
|
g.nextBlock("b19", outs[6])
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Immature coinbase tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends immature coinbase.
|
|
//
|
|
// ... -> b13(4) -> b15(5)
|
|
// \-> b20(7)
|
|
g.setTip("b15")
|
|
g.nextBlock("b20", outs[7])
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// Create block that spends immature coinbase on a fork.
|
|
//
|
|
// ... -> b13(4) -> b15(5)
|
|
// \-> b21(5) -> b22(7)
|
|
g.setTip("b13")
|
|
g.nextBlock("b21", outs[5])
|
|
acceptedToSideChainWithExpectedTip("b15")
|
|
|
|
g.nextBlock("b22", outs[7])
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Max block size tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that is the max allowed size.
|
|
//
|
|
// ... -> b15(5) -> b23(6)
|
|
g.setTip("b15")
|
|
g.nextBlock("b23", outs[6], func(b *wire.MsgBlock) {
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() - 3
|
|
sizePadScript := repeatOpcode(0x00, bytesToMaxSize)
|
|
replaceSpendScript(sizePadScript)(b)
|
|
})
|
|
g.assertTipBlockSize(maxBlockSize)
|
|
accepted()
|
|
|
|
// Create block that is the one byte larger than max allowed size. This
|
|
// is done on a fork and should be rejected regardless.
|
|
//
|
|
// ... -> b15(5) -> b23(6)
|
|
// \-> b24(6) -> b25(7)
|
|
g.setTip("b15")
|
|
g.nextBlock("b24", outs[6], func(b *wire.MsgBlock) {
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() - 3
|
|
sizePadScript := repeatOpcode(0x00, bytesToMaxSize+1)
|
|
replaceSpendScript(sizePadScript)(b)
|
|
})
|
|
g.assertTipBlockSize(maxBlockSize + 1)
|
|
rejected(blockchain.ErrBlockTooBig)
|
|
|
|
// Parent was rejected, so this block must either be an orphan or
|
|
// outright rejected due to an invalid parent.
|
|
g.nextBlock("b25", outs[7])
|
|
orphanedOrRejected()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Coinbase script length limits tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that has a coinbase script that is smaller than the
|
|
// required length. This is done on a fork and should be rejected
|
|
// regardless. Also, create a block that builds on the rejected block.
|
|
//
|
|
// ... -> b15(5) -> b23(6)
|
|
// \-> b26(6) -> b27(7)
|
|
g.setTip("b15")
|
|
tooSmallCbScript := repeatOpcode(0x00, minCoinbaseScriptLen-1)
|
|
g.nextBlock("b26", outs[6], replaceCoinbaseSigScript(tooSmallCbScript))
|
|
rejected(blockchain.ErrBadCoinbaseScriptLen)
|
|
|
|
// Parent was rejected, so this block must either be an orphan or
|
|
// outright rejected due to an invalid parent.
|
|
g.nextBlock("b27", outs[7])
|
|
orphanedOrRejected()
|
|
|
|
// Create block that has a coinbase script that is larger than the
|
|
// allowed length. This is done on a fork and should be rejected
|
|
// regardless. Also, create a block that builds on the rejected block.
|
|
//
|
|
// ... -> b15(5) -> b23(6)
|
|
// \-> b28(6) -> b29(7)
|
|
g.setTip("b15")
|
|
tooLargeCbScript := repeatOpcode(0x00, maxCoinbaseScriptLen+1)
|
|
g.nextBlock("b28", outs[6], replaceCoinbaseSigScript(tooLargeCbScript))
|
|
rejected(blockchain.ErrBadCoinbaseScriptLen)
|
|
|
|
// Parent was rejected, so this block must either be an orphan or
|
|
// outright rejected due to an invalid parent.
|
|
g.nextBlock("b29", outs[7])
|
|
orphanedOrRejected()
|
|
|
|
// Create block that has a max length coinbase script.
|
|
//
|
|
// ... -> b23(6) -> b30(7)
|
|
g.setTip("b23")
|
|
maxSizeCbScript := repeatOpcode(0x00, maxCoinbaseScriptLen)
|
|
g.nextBlock("b30", outs[7], replaceCoinbaseSigScript(maxSizeCbScript))
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Multisig[Verify]/ChecksigVerifiy signature operation count tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with max signature operations as OP_CHECKMULTISIG.
|
|
//
|
|
// ... -> b30(7) -> b31(8)
|
|
//
|
|
// OP_CHECKMULTISIG counts for 20 sigops.
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKMULTISIG, maxBlockSigOps/20)
|
|
g.nextBlock("b31", outs[8], replaceSpendScript(manySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with more than max allowed signature operations using
|
|
// OP_CHECKMULTISIG.
|
|
//
|
|
// ... -> b31(8)
|
|
// \-> b32(9)
|
|
//
|
|
// OP_CHECKMULTISIG counts for 20 sigops.
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKMULTISIG, maxBlockSigOps/20)
|
|
tooManySigOps = append(manySigOps, txscript.OP_CHECKSIG)
|
|
g.nextBlock("b32", outs[9], replaceSpendScript(tooManySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create block with max signature operations as OP_CHECKMULTISIGVERIFY.
|
|
//
|
|
// ... -> b31(8) -> b33(9)
|
|
g.setTip("b31")
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKMULTISIGVERIFY, maxBlockSigOps/20)
|
|
g.nextBlock("b33", outs[9], replaceSpendScript(manySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with more than max allowed signature operations using
|
|
// OP_CHECKMULTISIGVERIFY.
|
|
//
|
|
// ... -> b33(9)
|
|
// \-> b34(10)
|
|
//
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKMULTISIGVERIFY, maxBlockSigOps/20)
|
|
tooManySigOps = append(manySigOps, txscript.OP_CHECKSIG)
|
|
g.nextBlock("b34", outs[10], replaceSpendScript(tooManySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create block with max signature operations as OP_CHECKSIGVERIFY.
|
|
//
|
|
// ... -> b33(9) -> b35(10)
|
|
//
|
|
g.setTip("b33")
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKSIGVERIFY, maxBlockSigOps)
|
|
g.nextBlock("b35", outs[10], replaceSpendScript(manySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with more than max allowed signature operations using
|
|
// OP_CHECKSIGVERIFY.
|
|
//
|
|
// ... -> b35(10)
|
|
// \-> b36(11)
|
|
//
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKSIGVERIFY, maxBlockSigOps+1)
|
|
g.nextBlock("b36", outs[11], replaceSpendScript(tooManySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Spending of tx outputs in block that failed to connect tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends a transaction from a block that failed to
|
|
// connect (due to containing a double spend).
|
|
//
|
|
// ... -> b35(10)
|
|
// \-> b37(11)
|
|
// \-> b38(b37.tx[1])
|
|
//
|
|
g.setTip("b35")
|
|
doubleSpendTx := createSpendTx(outs[11], lowFee)
|
|
g.nextBlock("b37", outs[11], additionalTx(doubleSpendTx))
|
|
b37Tx1Out := makeSpendableOut(g.tip, 1, 0)
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
g.setTip("b35")
|
|
g.nextBlock("b38", &b37Tx1Out)
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Pay-to-script-hash signature operation count tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a pay-to-script-hash redeem script that consists of 9
|
|
// signature operations to be used in the next three blocks.
|
|
const redeemScriptSigOps = 9
|
|
redeemScript := pushDataScript(g.privKey.PubKey().SerializeCompressed())
|
|
redeemScript = append(redeemScript, bytes.Repeat([]byte{txscript.OP_2DUP,
|
|
txscript.OP_CHECKSIGVERIFY}, redeemScriptSigOps-1)...)
|
|
redeemScript = append(redeemScript, txscript.OP_CHECKSIG)
|
|
assertScriptSigOpsCount(redeemScript, redeemScriptSigOps)
|
|
|
|
// Create a block that has enough pay-to-script-hash outputs such that
|
|
// another block can be created that consumes them all and exceeds the
|
|
// max allowed signature operations per block.
|
|
//
|
|
// ... -> b35(10) -> b39(11)
|
|
g.setTip("b35")
|
|
b39 := g.nextBlock("b39", outs[11], func(b *wire.MsgBlock) {
|
|
// Create a chain of transactions each spending from the
|
|
// previous one such that each contains an output that pays to
|
|
// the redeem script and the total number of signature
|
|
// operations in those redeem scripts will be more than the
|
|
// max allowed per block.
|
|
p2shScript := payToScriptHashScript(redeemScript)
|
|
txnsNeeded := (maxBlockSigOps / redeemScriptSigOps) + 1
|
|
prevTx := b.Transactions[1]
|
|
for i := 0; i < txnsNeeded; i++ {
|
|
prevTx = createSpendTxForTx(prevTx, lowFee)
|
|
prevTx.TxOut[0].Value -= 2
|
|
prevTx.AddTxOut(wire.NewTxOut(2, p2shScript))
|
|
b.AddTransaction(prevTx)
|
|
}
|
|
})
|
|
g.assertTipBlockNumTxns((maxBlockSigOps / redeemScriptSigOps) + 3)
|
|
accepted()
|
|
|
|
// Create a block with more than max allowed signature operations where
|
|
// the majority of them are in pay-to-script-hash scripts.
|
|
//
|
|
// ... -> b35(10) -> b39(11)
|
|
// \-> b40(12)
|
|
g.setTip("b39")
|
|
g.nextBlock("b40", outs[12], func(b *wire.MsgBlock) {
|
|
txnsNeeded := (maxBlockSigOps / redeemScriptSigOps)
|
|
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)
|
|
sig, err := txscript.RawTxInSignature(tx, 0,
|
|
redeemScript, txscript.SigHashAll, g.privKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
tx.TxIn[0].SignatureScript = pushDataScript(sig,
|
|
redeemScript)
|
|
b.AddTransaction(tx)
|
|
}
|
|
|
|
// Create a final tx that includes a non-pay-to-script-hash
|
|
// output with the number of signature operations needed to push
|
|
// the block one over the max allowed.
|
|
fill := maxBlockSigOps - (txnsNeeded * redeemScriptSigOps) + 1
|
|
finalTx := b.Transactions[len(b.Transactions)-1]
|
|
tx := createSpendTxForTx(finalTx, lowFee)
|
|
tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill)
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create a block with the max allowed signature operations where the
|
|
// majority of them are in pay-to-script-hash scripts.
|
|
//
|
|
// ... -> b35(10) -> b39(11) -> b41(12)
|
|
g.setTip("b39")
|
|
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)
|
|
sig, err := txscript.RawTxInSignature(tx, 0,
|
|
redeemScript, txscript.SigHashAll, g.privKey)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
tx.TxIn[0].SignatureScript = pushDataScript(sig,
|
|
redeemScript)
|
|
b.AddTransaction(tx)
|
|
}
|
|
|
|
// Create a final tx that includes a non-pay-to-script-hash
|
|
// output with the number of signature operations needed to push
|
|
// the block to exactly the max allowed.
|
|
fill := maxBlockSigOps - (txnsNeeded * redeemScriptSigOps)
|
|
if fill == 0 {
|
|
return
|
|
}
|
|
finalTx := b.Transactions[len(b.Transactions)-1]
|
|
tx := createSpendTxForTx(finalTx, lowFee)
|
|
tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill)
|
|
b.AddTransaction(tx)
|
|
})
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Reset the chain to a stable base.
|
|
//
|
|
// ... -> b35(10) -> b39(11) -> b42(12) -> b43(13)
|
|
// \-> b41(12)
|
|
// ---------------------------------------------------------------------
|
|
|
|
g.setTip("b39")
|
|
g.nextBlock("b42", outs[12])
|
|
acceptedToSideChainWithExpectedTip("b41")
|
|
|
|
g.nextBlock("b43", outs[13])
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Various malformed block tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with an otherwise valid transaction in place of where
|
|
// the coinbase must be.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b44(14)
|
|
g.nextBlock("b44", nil, func(b *wire.MsgBlock) {
|
|
nonCoinbaseTx := createSpendTx(outs[14], lowFee)
|
|
b.Transactions[0] = nonCoinbaseTx
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with no transactions.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b45(_)
|
|
g.setTip("b43")
|
|
g.nextBlock("b45", nil, func(b *wire.MsgBlock) {
|
|
b.Transactions = nil
|
|
})
|
|
rejected(blockchain.ErrNoTransactions)
|
|
|
|
// Create block with invalid proof of work.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b46(14)
|
|
g.setTip("b43")
|
|
b46 := g.nextBlock("b46", outs[14])
|
|
// This can't be done inside a munge function passed to nextBlock
|
|
// because the block is solved after the function returns and this test
|
|
// requires an unsolved block.
|
|
{
|
|
origHash := b46.BlockHash()
|
|
for {
|
|
// Keep incrementing the nonce until the hash treated as
|
|
// a uint256 is higher than the limit.
|
|
b46.Header.Nonce++
|
|
blockHash := b46.Header.BlockPoWHash()
|
|
hashNum := blockchain.HashToBig(&blockHash)
|
|
if hashNum.Cmp(g.params.PowLimit) >= 0 {
|
|
break
|
|
}
|
|
}
|
|
g.updateBlockState("b46", origHash, "b46", b46)
|
|
}
|
|
rejected(blockchain.ErrHighHash)
|
|
|
|
// Create block with a timestamp too far in the future.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b47(14)
|
|
g.setTip("b43")
|
|
g.nextBlock("b47", outs[14], func(b *wire.MsgBlock) {
|
|
// 3 hours in the future clamped to 1 second precision.
|
|
nowPlus3Hours := time.Now().Add(time.Hour * 3)
|
|
b.Header.Timestamp = time.Unix(nowPlus3Hours.Unix(), 0)
|
|
})
|
|
rejected(blockchain.ErrTimeTooNew)
|
|
|
|
// Create block with an invalid merkle root.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b48(14)
|
|
g.setTip("b43")
|
|
g.nextBlock("b48", outs[14], func(b *wire.MsgBlock) {
|
|
b.Header.MerkleRoot = chainhash.Hash{}
|
|
})
|
|
rejected(blockchain.ErrBadMerkleRoot)
|
|
|
|
// Create block with an invalid proof-of-work limit.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b49(14)
|
|
g.setTip("b43")
|
|
g.nextBlock("b49", outs[14], func(b *wire.MsgBlock) {
|
|
b.Header.Bits--
|
|
})
|
|
rejected(blockchain.ErrUnexpectedDifficulty)
|
|
|
|
// Create block with an invalid negative proof-of-work limit.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b49a(14)
|
|
g.setTip("b43")
|
|
b49a := g.nextBlock("b49a", outs[14])
|
|
// This can't be done inside a munge function passed to nextBlock
|
|
// because the block is solved after the function returns and this test
|
|
// involves an unsolvable block.
|
|
{
|
|
origHash := b49a.BlockHash()
|
|
b49a.Header.Bits = 0x01810000 // -1 in compact form.
|
|
g.updateBlockState("b49a", origHash, "b49a", b49a)
|
|
}
|
|
rejected(blockchain.ErrUnexpectedDifficulty)
|
|
|
|
// Create block with two coinbase transactions.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b50(14)
|
|
g.setTip("b43")
|
|
coinbaseTx := g.createCoinbaseTx(g.tipHeight + 1)
|
|
g.nextBlock("b50", outs[14], additionalTx(coinbaseTx))
|
|
rejected(blockchain.ErrMultipleCoinbases)
|
|
|
|
// Create block with duplicate transactions.
|
|
//
|
|
// This test relies on the shape of the shape of the merkle tree to test
|
|
// the intended condition and thus is asserted below.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b51(14)
|
|
g.setTip("b43")
|
|
g.nextBlock("b51", outs[14], func(b *wire.MsgBlock) {
|
|
b.AddTransaction(b.Transactions[1])
|
|
})
|
|
g.assertTipBlockNumTxns(3)
|
|
rejected(blockchain.ErrDuplicateTx)
|
|
|
|
// Create a block that spends a transaction that does not exist.
|
|
//
|
|
// ... -> b43(13)
|
|
// \-> b52(14)
|
|
g.setTip("b43")
|
|
g.nextBlock("b52", outs[14], func(b *wire.MsgBlock) {
|
|
hash := newHashFromStr("00000000000000000000000000000000" +
|
|
"00000000000000000123456789abcdef")
|
|
b.Transactions[1].TxIn[0].PreviousOutPoint.Hash = *hash
|
|
b.Transactions[1].TxIn[0].PreviousOutPoint.Index = 0
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Block header median time tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Reset the chain to a stable base.
|
|
//
|
|
// ... -> b33(9) -> b35(10) -> b39(11) -> b42(12) -> b43(13) -> b53(14)
|
|
g.setTip("b43")
|
|
g.nextBlock("b53", outs[14])
|
|
accepted()
|
|
|
|
// Create a block with a timestamp that is exactly the median time. The
|
|
// block must be rejected.
|
|
//
|
|
// ... -> b33(9) -> b35(10) -> b39(11) -> b42(12) -> b43(13) -> b53(14)
|
|
// \-> b54(15)
|
|
g.nextBlock("b54", outs[15], func(b *wire.MsgBlock) {
|
|
medianBlock := g.blocks[b.Header.PrevBlock]
|
|
for i := 0; i < medianTimeBlocks/2; i++ {
|
|
medianBlock = g.blocks[medianBlock.Header.PrevBlock]
|
|
}
|
|
b.Header.Timestamp = medianBlock.Header.Timestamp
|
|
})
|
|
rejected(blockchain.ErrTimeTooOld)
|
|
|
|
// Create a block with a timestamp that is one second after the median
|
|
// time. The block must be accepted.
|
|
//
|
|
// ... -> b33(9) -> b35(10) -> b39(11) -> b42(12) -> b43(13) -> b53(14) -> b55(15)
|
|
g.setTip("b53")
|
|
g.nextBlock("b55", outs[15], func(b *wire.MsgBlock) {
|
|
medianBlock := g.blocks[b.Header.PrevBlock]
|
|
for i := 0; i < medianTimeBlocks/2; i++ {
|
|
medianBlock = g.blocks[medianBlock.Header.PrevBlock]
|
|
}
|
|
medianBlockTime := medianBlock.Header.Timestamp
|
|
b.Header.Timestamp = medianBlockTime.Add(time.Second)
|
|
})
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// CVE-2012-2459 (block hash collision due to merkle tree algo) tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create two blocks that have the same hash via merkle tree tricks to
|
|
// ensure that the valid block is accepted even though it has the same
|
|
// hash as the invalid block that was rejected first.
|
|
//
|
|
// This is accomplished by building the blocks as follows:
|
|
//
|
|
// b57 (valid block):
|
|
//
|
|
// root = h1234 = h(h12 || h34)
|
|
// // \\
|
|
// h12 = h(h(cb) || h(tx2)) h34 = h(h(tx3) || h(tx3))
|
|
// // \\ // \\
|
|
// coinbase tx2 tx3 nil
|
|
//
|
|
// transactions: coinbase, tx2, tx3
|
|
// merkle tree level 1: h12 = h(h(cb) || h(tx2))
|
|
// h34 = h(h(tx3) || h(tx3)) // Algo reuses tx3
|
|
// merkle tree root: h(h12 || h34)
|
|
//
|
|
// b56 (invalid block with the same hash):
|
|
//
|
|
// root = h1234 = h(h12 || h34)
|
|
// // \\
|
|
// h12 = h(h(cb) || h(tx2)) h34 = h(h(tx3) || h(tx3))
|
|
// // \\ // \\
|
|
// coinbase tx2 tx3 tx3
|
|
//
|
|
// transactions: coinbase, tx2, tx3, tx3
|
|
// merkle tree level 1: h12 = h(h(cb) || h(tx2))
|
|
// h34 = h(h(tx3) || h(tx3)) // real tx3 dup
|
|
// merkle tree root: h(h12 || h34)
|
|
//
|
|
// ... -> b55(15) -> b57(16)
|
|
// \-> b56(16)
|
|
g.setTip("b55")
|
|
b57 := g.nextBlock("b57", outs[16], func(b *wire.MsgBlock) {
|
|
tx2 := b.Transactions[1]
|
|
tx3 := createSpendTxForTx(tx2, lowFee)
|
|
b.AddTransaction(tx3)
|
|
})
|
|
g.assertTipBlockNumTxns(3)
|
|
|
|
g.setTip("b55")
|
|
b56 := g.nextBlock("b56", nil, func(b *wire.MsgBlock) {
|
|
*b = cloneBlock(b57)
|
|
b.AddTransaction(b.Transactions[2])
|
|
})
|
|
g.assertTipBlockNumTxns(4)
|
|
g.assertTipBlockHash(b57.BlockHash())
|
|
g.assertTipBlockMerkleRoot(b57.Header.MerkleRoot)
|
|
rejected(blockchain.ErrDuplicateTx)
|
|
|
|
// Since the two blocks have the same hash and the generator state now
|
|
// has b56 associated with the hash, manually remove b56, replace it
|
|
// with b57, and then reset the tip to it.
|
|
g.updateBlockState("b56", b56.BlockHash(), "b57", b57)
|
|
g.setTip("b57")
|
|
accepted()
|
|
|
|
// Create a block that contains two duplicate txns that are not in a
|
|
// consecutive position within the merkle tree.
|
|
//
|
|
// This is accomplished by building the block as follows:
|
|
//
|
|
// transactions: coinbase, tx2, tx3, tx4, tx5, tx6, tx3, tx4
|
|
// merkle tree level 2: h12 = h(h(cb) || h(tx2))
|
|
// h34 = h(h(tx3) || h(tx4))
|
|
// h56 = h(h(tx5) || h(tx6))
|
|
// h78 = h(h(tx3) || h(tx4)) // Same as h34
|
|
// merkle tree level 1: h1234 = h(h12 || h34)
|
|
// h5678 = h(h56 || h78)
|
|
// merkle tree root: h(h1234 || h5678)
|
|
//
|
|
//
|
|
// ... -> b55(15) -> b57(16)
|
|
// \-> b56p2(16)
|
|
g.setTip("b55")
|
|
g.nextBlock("b56p2", outs[16], func(b *wire.MsgBlock) {
|
|
// Create 4 transactions that each spend from the previous tx
|
|
// in the block.
|
|
spendTx := b.Transactions[1]
|
|
for i := 0; i < 4; i++ {
|
|
spendTx = createSpendTxForTx(spendTx, lowFee)
|
|
b.AddTransaction(spendTx)
|
|
}
|
|
|
|
// Add the duplicate transactions (3rd and 4th).
|
|
b.AddTransaction(b.Transactions[2])
|
|
b.AddTransaction(b.Transactions[3])
|
|
})
|
|
g.assertTipBlockNumTxns(8)
|
|
rejected(blockchain.ErrDuplicateTx)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Invalid transaction type tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with a transaction that tries to spend from an index
|
|
// that is out of range from an otherwise valid and existing tx.
|
|
//
|
|
// ... -> b57(16)
|
|
// \-> b58(17)
|
|
g.setTip("b57")
|
|
g.nextBlock("b58", outs[17], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxIn[0].PreviousOutPoint.Index = 42
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block with transaction that pays more than its inputs.
|
|
//
|
|
// ... -> b57(16)
|
|
// \-> 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
|
|
})
|
|
rejected(blockchain.ErrSpendTooHigh)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// BIP0030 tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a good block to reset the chain to a stable base.
|
|
//
|
|
// ... -> b57(16) -> b60(17)
|
|
g.setTip("b57")
|
|
g.nextBlock("b60", outs[17])
|
|
accepted()
|
|
|
|
// Create block that has a tx with the same hash as an existing tx that
|
|
// has not been fully spent.
|
|
//
|
|
// ... -> b60(17)
|
|
// \-> b61(18)
|
|
g.nextBlock("b61", outs[18], func(b *wire.MsgBlock) {
|
|
// Duplicate the coinbase of the parent block to force the
|
|
// condition.
|
|
parent := g.blocks[b.Header.PrevBlock]
|
|
b.Transactions[0] = parent.Transactions[0]
|
|
})
|
|
rejected(blockchain.ErrOverwriteTx)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Blocks with non-final transaction tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that contains a non-final non-coinbase transaction.
|
|
//
|
|
// ... -> b60(17)
|
|
// \-> b62(18)
|
|
g.setTip("b60")
|
|
g.nextBlock("b62", outs[18], func(b *wire.MsgBlock) {
|
|
// A non-final transaction must have at least one input with a
|
|
// non-final sequence number in addition to a non-final lock
|
|
// time.
|
|
b.Transactions[1].LockTime = 0xffffffff
|
|
b.Transactions[1].TxIn[0].Sequence = 0
|
|
})
|
|
rejected(blockchain.ErrUnfinalizedTx)
|
|
|
|
// Create block that contains a non-final coinbase transaction.
|
|
//
|
|
// ... -> b60(17)
|
|
// \-> b63(18)
|
|
g.setTip("b60")
|
|
g.nextBlock("b63", outs[18], func(b *wire.MsgBlock) {
|
|
// A non-final transaction must have at least one input with a
|
|
// non-final sequence number in addition to a non-final lock
|
|
// time.
|
|
b.Transactions[0].LockTime = 0xffffffff
|
|
b.Transactions[0].TxIn[0].Sequence = 0
|
|
})
|
|
rejected(blockchain.ErrUnfinalizedTx)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Non-canonical variable-length integer tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a max size block with the variable-length integer for the
|
|
// number of transactions replaced with a larger non-canonical version
|
|
// that causes the block size to exceed the max allowed size. Then,
|
|
// create another block that is identical except with the canonical
|
|
// encoding and ensure it is accepted. The intent is to verify the
|
|
// implementation does not reject the second block, which will have the
|
|
// same hash, due to the first one already being rejected.
|
|
//
|
|
// ... -> b60(17) -> b64(18)
|
|
// \-> b64a(18)
|
|
g.setTip("b60")
|
|
b64a := g.nextBlock("b64a", outs[18], func(b *wire.MsgBlock) {
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() - 3
|
|
sizePadScript := repeatOpcode(0x00, bytesToMaxSize)
|
|
replaceSpendScript(sizePadScript)(b)
|
|
})
|
|
g.assertTipNonCanonicalBlockSize(maxBlockSize + 8)
|
|
rejectedNonCanonical()
|
|
|
|
g.setTip("b60")
|
|
b64 := g.nextBlock("b64", outs[18], func(b *wire.MsgBlock) {
|
|
*b = cloneBlock(b64a)
|
|
})
|
|
// Since the two blocks have the same hash and the generator state now
|
|
// has b64a associated with the hash, manually remove b64a, replace it
|
|
// with b64, and then reset the tip to it.
|
|
g.updateBlockState("b64a", b64a.BlockHash(), "b64", b64)
|
|
g.setTip("b64")
|
|
g.assertTipBlockHash(b64a.BlockHash())
|
|
g.assertTipBlockSize(maxBlockSize)
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Same block transaction spend tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends an output created earlier in the same block.
|
|
//
|
|
// ... b64(18) -> b65(19)
|
|
g.setTip("b64")
|
|
g.nextBlock("b65", outs[19], func(b *wire.MsgBlock) {
|
|
tx3 := createSpendTxForTx(b.Transactions[1], lowFee)
|
|
b.AddTransaction(tx3)
|
|
})
|
|
accepted()
|
|
|
|
// Create block that spends an output created later in the same block.
|
|
//
|
|
// ... -> b65(19)
|
|
// \-> b66(20)
|
|
g.nextBlock("b66", nil, func(b *wire.MsgBlock) {
|
|
tx2 := createSpendTx(outs[20], lowFee)
|
|
tx3 := createSpendTxForTx(tx2, lowFee)
|
|
b.AddTransaction(tx3)
|
|
b.AddTransaction(tx2)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that double spends a transaction created in the same
|
|
// block.
|
|
//
|
|
// ... -> b65(19)
|
|
// \-> b67(20)
|
|
g.setTip("b65")
|
|
g.nextBlock("b67", outs[20], func(b *wire.MsgBlock) {
|
|
tx2 := b.Transactions[1]
|
|
tx3 := createSpendTxForTx(tx2, lowFee)
|
|
tx4 := createSpendTxForTx(tx2, lowFee)
|
|
b.AddTransaction(tx3)
|
|
b.AddTransaction(tx4)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Extra subsidy tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that pays 10 extra to the coinbase and a tx that only
|
|
// pays 9 fee.
|
|
//
|
|
// ... -> b65(19)
|
|
// \-> b68(20)
|
|
g.setTip("b65")
|
|
g.nextBlock("b68", outs[20], additionalCoinbase(10), additionalSpendFee(9))
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create block that pays 10 extra to the coinbase and a tx that pays
|
|
// the extra 10 fee.
|
|
//
|
|
// ... -> b65(19) -> b69(20)
|
|
g.setTip("b65")
|
|
g.nextBlock("b69", outs[20], additionalCoinbase(10), additionalSpendFee(10))
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// More signature operations counting tests.
|
|
//
|
|
// The next several tests ensure signature operations are counted before
|
|
// script elements that cause parse failure while those after are
|
|
// ignored and that signature operations after script elements that
|
|
// successfully parse even if that element will fail at run-time are
|
|
// counted.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with more than max allowed signature operations such
|
|
// that the signature operation that pushes it over the limit is after
|
|
// a push data with a script element size that is larger than the max
|
|
// allowed size when executed. The block must be rejected because the
|
|
// signature operation after the script element must be counted since
|
|
// the script parses validly.
|
|
//
|
|
// The script generated consists of the following form:
|
|
//
|
|
// Comment assumptions:
|
|
// maxBlockSigOps = 20000
|
|
// maxScriptElementSize = 20000
|
|
//
|
|
// [0-19999] : OP_CHECKSIG
|
|
// [20000] : OP_PUSHDATA4
|
|
// [20001-20004]: 521 (little-endian encoded maxScriptElementSize+1)
|
|
// [20005-20525]: too large script element
|
|
// [20526] : OP_CHECKSIG (goes over the limit)
|
|
//
|
|
// ... -> b69(20)
|
|
// \-> b70(21)
|
|
scriptSize := maxBlockSigOps + 5 + (maxScriptElementSize + 1) + 1
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKSIG, scriptSize)
|
|
tooManySigOps[maxBlockSigOps] = txscript.OP_PUSHDATA4
|
|
binary.LittleEndian.PutUint32(tooManySigOps[maxBlockSigOps+1:],
|
|
maxScriptElementSize+1)
|
|
g.nextBlock("b70", outs[21], replaceSpendScript(tooManySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create block with more than max allowed signature operations such
|
|
// that the signature operation that pushes it over the limit is before
|
|
// an invalid push data that claims a large amount of data even though
|
|
// that much data is not provided.
|
|
//
|
|
// ... -> b69(20)
|
|
// \-> b71(21)
|
|
g.setTip("b69")
|
|
scriptSize = maxBlockSigOps + 5 + maxScriptElementSize + 1
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKSIG, scriptSize)
|
|
tooManySigOps[maxBlockSigOps+1] = txscript.OP_PUSHDATA4
|
|
binary.LittleEndian.PutUint32(tooManySigOps[maxBlockSigOps+2:], 0xffffffff)
|
|
g.nextBlock("b71", outs[21], replaceSpendScript(tooManySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create block with the max allowed signature operations such that all
|
|
// counted signature operations are before an invalid push data that
|
|
// claims a large amount of data even though that much data is not
|
|
// provided. The pushed data itself consists of OP_CHECKSIG so the
|
|
// block would be rejected if any of them were counted.
|
|
//
|
|
// ... -> b69(20) -> b72(21)
|
|
g.setTip("b69")
|
|
scriptSize = maxBlockSigOps + 5 + maxScriptElementSize
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKSIG, scriptSize)
|
|
manySigOps[maxBlockSigOps] = txscript.OP_PUSHDATA4
|
|
binary.LittleEndian.PutUint32(manySigOps[maxBlockSigOps+1:], 0xffffffff)
|
|
g.nextBlock("b72", outs[21], replaceSpendScript(manySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with the max allowed signature operations such that all
|
|
// counted signature operations are before an invalid push data that
|
|
// contains OP_CHECKSIG in the number of bytes to push. The block would
|
|
// be rejected if any of them were counted.
|
|
//
|
|
// ... -> b72(21) -> b73(22)
|
|
scriptSize = maxBlockSigOps + 5 + (maxScriptElementSize + 1)
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKSIG, scriptSize)
|
|
manySigOps[maxBlockSigOps] = txscript.OP_PUSHDATA4
|
|
g.nextBlock("b73", outs[22], replaceSpendScript(manySigOps))
|
|
g.assertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Dead execution path tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with an invalid opcode in a dead execution path.
|
|
//
|
|
// ... -> b73(22) -> b74(23)
|
|
script := []byte{txscript.OP_IF, txscript.OP_INVALIDOPCODE,
|
|
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.TxIn[0].SignatureScript = []byte{txscript.OP_FALSE}
|
|
b.AddTransaction(tx3)
|
|
})
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Various OP_RETURN tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a block that has multiple transactions each with a single
|
|
// OP_RETURN output.
|
|
//
|
|
// ... -> b74(23) -> b75(24)
|
|
g.nextBlock("b75", outs[24], func(b *wire.MsgBlock) {
|
|
// Add 4 outputs to the spending transaction that are spent
|
|
// below.
|
|
const numAdditionalOutputs = 4
|
|
const zeroCoin = int64(0)
|
|
spendTx := b.Transactions[1]
|
|
for i := 0; i < numAdditionalOutputs; i++ {
|
|
spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opTrueScript))
|
|
}
|
|
|
|
// Add transactions spending from the outputs added above that
|
|
// each contain an OP_RETURN output.
|
|
//
|
|
// 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)
|
|
b.AddTransaction(tx)
|
|
}
|
|
})
|
|
g.assertTipBlockNumTxns(6)
|
|
g.assertTipBlockTxOutOpReturn(5, 1)
|
|
b75OpReturnOut := makeSpendableOut(g.tip, 5, 1)
|
|
accepted()
|
|
|
|
// Reorg to a side chain that does not contain the OP_RETURNs.
|
|
//
|
|
// ... -> b74(23) -> b75(24)
|
|
// \-> b76(24) -> b77(25)
|
|
g.setTip("b74")
|
|
g.nextBlock("b76", outs[24])
|
|
acceptedToSideChainWithExpectedTip("b75")
|
|
|
|
g.nextBlock("b77", outs[25])
|
|
accepted()
|
|
|
|
// Reorg back to the original chain that contains the OP_RETURNs.
|
|
//
|
|
// ... -> b74(23) -> b75(24) -> b78(25) -> b79(26)
|
|
// \-> b76(24) -> b77(25)
|
|
g.setTip("b75")
|
|
g.nextBlock("b78", outs[25])
|
|
acceptedToSideChainWithExpectedTip("b77")
|
|
|
|
g.nextBlock("b79", outs[26])
|
|
accepted()
|
|
|
|
// Create a block that spends an OP_RETURN.
|
|
//
|
|
// ... -> b74(23) -> b75(24) -> b78(25) -> b79(26)
|
|
// \-> b76(24) -> b77(25) \-> b80(b75.tx[5].out[1])
|
|
//
|
|
// 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++
|
|
g.nextBlock("b80", &b75OpReturnOut)
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create a block that has a transaction with multiple OP_RETURNs. Even
|
|
// though it's not considered a standard transaction, it is still valid
|
|
// by the consensus rules.
|
|
//
|
|
// ... -> b79(26) -> b81(27)
|
|
//
|
|
g.setTip("b79")
|
|
g.nextBlock("b81", outs[27], func(b *wire.MsgBlock) {
|
|
const numAdditionalOutputs = 4
|
|
const zeroCoin = int64(0)
|
|
spendTx := b.Transactions[1]
|
|
for i := 0; i < numAdditionalOutputs; i++ {
|
|
opRetScript := uniqueOpReturnScript()
|
|
spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opRetScript))
|
|
}
|
|
})
|
|
for i := uint32(2); i < 6; i++ {
|
|
g.assertTipBlockTxOutOpReturn(1, i)
|
|
}
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Large block re-org test.
|
|
// ---------------------------------------------------------------------
|
|
|
|
if !includeLargeReorg {
|
|
return tests, nil
|
|
}
|
|
|
|
// Ensure the tip the re-org test builds on is the best chain tip.
|
|
//
|
|
// ... -> b81(27) -> ...
|
|
g.setTip("b81")
|
|
|
|
// Collect all of the spendable coinbase outputs from the previous
|
|
// collection point up to the current tip.
|
|
g.saveSpendableCoinbaseOuts()
|
|
spendableOutOffset := g.tipHeight - int32(coinbaseMaturity)
|
|
|
|
// Extend the main chain by a large number of max size blocks.
|
|
//
|
|
// ... -> br0 -> br1 -> ... -> br#
|
|
testInstances = nil
|
|
reorgSpend := *outs[spendableOutOffset]
|
|
reorgStartBlockName := g.tipName
|
|
chain1TipName := g.tipName
|
|
for i := int32(0); i < numLargeReorgBlocks; i++ {
|
|
chain1TipName = fmt.Sprintf("br%d", i)
|
|
g.nextBlock(chain1TipName, &reorgSpend, func(b *wire.MsgBlock) {
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() - 3
|
|
sizePadScript := repeatOpcode(0x00, bytesToMaxSize)
|
|
replaceSpendScript(sizePadScript)(b)
|
|
})
|
|
g.assertTipBlockSize(maxBlockSize)
|
|
g.saveTipCoinbaseOut()
|
|
testInstances = append(testInstances, acceptBlock(g.tipName,
|
|
g.tip, true, false))
|
|
|
|
// Use the next available spendable output. First use up any
|
|
// remaining spendable outputs that were already popped into the
|
|
// outs slice, then just pop them from the stack.
|
|
if spendableOutOffset+1+i < int32(len(outs)) {
|
|
reorgSpend = *outs[spendableOutOffset+1+i]
|
|
} else {
|
|
reorgSpend = g.oldestCoinbaseOut()
|
|
}
|
|
}
|
|
tests = append(tests, testInstances)
|
|
|
|
// Create a side chain that has the same length.
|
|
//
|
|
// ... -> br0 -> ... -> br#
|
|
// \-> bralt0 -> ... -> bralt#
|
|
g.setTip(reorgStartBlockName)
|
|
testInstances = nil
|
|
chain2TipName := g.tipName
|
|
for i := uint16(0); i < numLargeReorgBlocks; i++ {
|
|
chain2TipName = fmt.Sprintf("bralt%d", i)
|
|
g.nextBlock(chain2TipName, nil)
|
|
testInstances = append(testInstances, acceptBlock(g.tipName,
|
|
g.tip, false, false))
|
|
}
|
|
testInstances = append(testInstances, expectTipBlock(chain1TipName,
|
|
g.blocksByName[chain1TipName]))
|
|
tests = append(tests, testInstances)
|
|
|
|
// Extend the side chain by one to force the large reorg.
|
|
//
|
|
// ... -> bralt0 -> ... -> bralt# -> bralt#+1
|
|
// \-> br0 -> ... -> br#
|
|
g.nextBlock(fmt.Sprintf("bralt%d", g.tipHeight+1), nil)
|
|
chain2TipName = g.tipName
|
|
accepted()
|
|
|
|
// Extend the first chain by two to force a large reorg back to it.
|
|
//
|
|
// ... -> br0 -> ... -> br# -> br#+1 -> br#+2
|
|
// \-> bralt0 -> ... -> bralt# -> bralt#+1
|
|
g.setTip(chain1TipName)
|
|
g.nextBlock(fmt.Sprintf("br%d", g.tipHeight+1), nil)
|
|
chain1TipName = g.tipName
|
|
acceptedToSideChainWithExpectedTip(chain2TipName)
|
|
|
|
g.nextBlock(fmt.Sprintf("br%d", g.tipHeight+2), nil)
|
|
chain1TipName = g.tipName
|
|
accepted()
|
|
|
|
return tests, nil
|
|
}
|