mirror of
https://github.com/FlipsideCrypto/dcrd.git
synced 2026-02-06 10:56:47 +00:00
3130 lines
106 KiB
Go
3130 lines
106 KiB
Go
// Copyright (c) 2016 The btcsuite developers
|
|
// Copyright (c) 2016-2019 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package fullblocktests
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"errors"
|
|
"fmt"
|
|
"math"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain/v2"
|
|
"github.com/decred/dcrd/blockchain/v2/chaingen"
|
|
"github.com/decred/dcrd/chaincfg/chainhash"
|
|
"github.com/decred/dcrd/dcrec"
|
|
"github.com/decred/dcrd/dcrec/secp256k1"
|
|
"github.com/decred/dcrd/dcrutil/v2"
|
|
"github.com/decred/dcrd/txscript/v2"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
const (
|
|
// Intentionally defined here rather than using constants from codebase
|
|
// to ensure consensus changes are detected.
|
|
maxBlockSigOps = 5000
|
|
minCoinbaseScriptLen = 2
|
|
maxCoinbaseScriptLen = 100
|
|
medianTimeBlocks = 11
|
|
maxScriptElementSize = 2048
|
|
|
|
// numLargeReorgBlocks is the number of blocks to use in the large block
|
|
// reorg test (when enabled). This is the equivalent of 2 day's worth
|
|
// of blocks.
|
|
numLargeReorgBlocks = 576
|
|
|
|
// voteBitNo and voteBitYes represent no and yes votes, respectively, on
|
|
// whether or not to approve the previous block.
|
|
voteBitNo = 0x0000
|
|
voteBitYes = 0x0001
|
|
)
|
|
|
|
var (
|
|
// opTrueScript is a simple public key script that contains the OP_TRUE
|
|
// opcode. It is defined here to reduce garbage creation.
|
|
opTrueScript = []byte{txscript.OP_TRUE}
|
|
|
|
// invalidP2SHRedeemScript is a script that evaluates to false. This makes
|
|
// it an invalid P2SH redeem script.
|
|
invalidP2SHRedeemScript = []byte{0x01, txscript.OP_FALSE}
|
|
|
|
// lowFee is a single atom and exists to make the test code more
|
|
// readable.
|
|
lowFee = dcrutil.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
|
|
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
|
|
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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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() {}
|
|
|
|
// payToScriptHashScript returns a standard pay-to-script-hash for the provided
|
|
// redeem script.
|
|
func payToScriptHashScript(redeemScript []byte) []byte {
|
|
redeemScriptHash := dcrutil.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
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// standardCoinbaseOpReturnScript returns a standard script suitable for use as
|
|
// the second output of a standard coinbase transaction of a new block. In
|
|
// particular, the serialized data used with the OP_RETURN starts with the block
|
|
// height and is followed by 32 bytes which are treated as 4 uint64 extra
|
|
// nonces. This implementation puts a cryptographically random value into the
|
|
// final extra nonce position. The actual format of the data after the block
|
|
// height is not defined however this effectively mirrors the actual mining code
|
|
// at the time it was written.
|
|
func standardCoinbaseOpReturnScript(blockHeight uint32) []byte {
|
|
rand, err := wire.RandomUint64()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
data := make([]byte, 36)
|
|
binary.LittleEndian.PutUint32(data[0:4], blockHeight)
|
|
binary.LittleEndian.PutUint64(data[28:36], rand)
|
|
return opReturnScript(data)
|
|
}
|
|
|
|
// additionalCoinbasePoW returns a function that itself takes a block and
|
|
// modifies it by adding the provided amount to the first proof-of-work coinbase
|
|
// subsidy.
|
|
func additionalCoinbasePoW(amount dcrutil.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[2].Value += int64(amount)
|
|
}
|
|
}
|
|
|
|
// additionalCoinbaseDev returns a function that itself takes a block and
|
|
// modifies it by adding the provided amount to the coinbase developer subsidy.
|
|
func additionalCoinbaseDev(amount dcrutil.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
|
|
// 'additionalCoinbasePoW' for that purpose.
|
|
func additionalSpendFee(fee dcrutil.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
|
|
}
|
|
}
|
|
|
|
// replaceStakeSigScript returns a function that itself takes a block and
|
|
// modifies it by replacing the signature script of the first stake transaction.
|
|
func replaceStakeSigScript(sigScript []byte) func(*wire.MsgBlock) {
|
|
return func(b *wire.MsgBlock) {
|
|
b.STransactions[0].TxIn[0].SignatureScript = sigScript
|
|
}
|
|
}
|
|
|
|
// additionalPoWTx returns a function that itself takes a block and modifies it
|
|
// by adding the provided transaction to the regular transaction tree.
|
|
func additionalPoWTx(tx *wire.MsgTx) func(*wire.MsgBlock) {
|
|
return func(b *wire.MsgBlock) {
|
|
b.AddTransaction(tx)
|
|
}
|
|
}
|
|
|
|
// 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)
|
|
buf.Write(nonCanonicalVarInt(uint32(len(b.Transactions))))
|
|
for _, tx := range b.Transactions {
|
|
tx.BtcEncode(&buf, 0)
|
|
}
|
|
wire.WriteVarInt(&buf, 0, uint64(len(b.STransactions)))
|
|
for _, stx := range b.STransactions {
|
|
stx.BtcEncode(&buf, 0)
|
|
}
|
|
return buf.Bytes()
|
|
}
|
|
|
|
// assertTipsNonCanonicalBlockSize panics if the current tip block associated
|
|
// with the generator does not have the specified non-canonical size
|
|
// when serialized.
|
|
func assertTipNonCanonicalBlockSize(g *chaingen.Generator, expected int) {
|
|
tip := g.Tip()
|
|
serializeSize := len(encodeNonCanonicalBlock(tip))
|
|
if serializeSize != expected {
|
|
panic(fmt.Sprintf("block size of block %q (height %d) is %d "+
|
|
"instead of expected %d", g.TipName(), tip.Header.Height,
|
|
serializeSize, expected))
|
|
}
|
|
}
|
|
|
|
// 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())
|
|
}
|
|
for _, stx := range b.STransactions {
|
|
blockCopy.AddSTransaction(stx)
|
|
}
|
|
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)
|
|
}
|
|
|
|
// 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 to
|
|
// 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 generator instance initialized with the genesis block as the
|
|
// tip as well as some cached payment scripts to be used throughout the
|
|
// tests.
|
|
g, err := chaingen.MakeGenerator(regNetParams)
|
|
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 expects it to be rejected.
|
|
//
|
|
// orphanOrRejectBlock creates a test instance that expects the provided
|
|
// block to either be 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 {
|
|
return AcceptedBlock{blockName, block, isMainChain, isOrphan}
|
|
}
|
|
rejectBlock := func(blockName string, block *wire.MsgBlock, code blockchain.ErrorCode) TestInstance {
|
|
return RejectedBlock{blockName, block, code}
|
|
}
|
|
rejectNonCanonicalBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
|
|
blockHeight := int32(block.Header.Height)
|
|
encoded := encodeNonCanonicalBlock(block)
|
|
return RejectedNonCanonicalBlock{blockName, encoded, blockHeight}
|
|
}
|
|
orphanOrRejectBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
|
|
return OrphanOrRejectedBlock{blockName, block}
|
|
}
|
|
expectTipBlock := func(blockName string, block *wire.MsgBlock) TestInstance {
|
|
return ExpectedTip{blockName, block}
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// orphaned creates and appends a single acceptBlock test instance for
|
|
// the current tip which expects the block to be accepted as an orphan.
|
|
//
|
|
// 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.BlockByName(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()),
|
|
})
|
|
}
|
|
orphaned := func() {
|
|
tests = append(tests, []TestInstance{
|
|
acceptBlock(g.TipName(), g.Tip(), false, true),
|
|
})
|
|
}
|
|
orphanedOrRejected := func() {
|
|
tests = append(tests, []TestInstance{
|
|
orphanOrRejectBlock(g.TipName(), g.Tip()),
|
|
})
|
|
}
|
|
|
|
// Shorter versions of useful params for convenience.
|
|
coinbaseMaturity := g.Params().CoinbaseMaturity
|
|
stakeEnabledHeight := g.Params().StakeEnabledHeight
|
|
stakeValidationHeight := g.Params().StakeValidationHeight
|
|
maxBlockSize := g.Params().MaximumBlockSizes[0]
|
|
ticketsPerBlock := g.Params().TicketsPerBlock
|
|
|
|
// ---------------------------------------------------------------------
|
|
// First block tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Attempt to insert an initial block that does not conform to the required
|
|
// payouts by adding one atom too many to each payout.
|
|
//
|
|
// genesis
|
|
// \-> bfbbad0
|
|
g.CreateBlockOne("bfbbad0", 1)
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create first block with one required output removed.
|
|
//
|
|
// genesis
|
|
// \-> bfbbad1
|
|
g.SetTip("genesis")
|
|
g.CreateBlockOne("bfbbad1", 0, func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut = b.Transactions[0].TxOut[:2]
|
|
})
|
|
rejected(blockchain.ErrBlockOneOutputs)
|
|
|
|
// Create first block with a bad spend script.
|
|
//
|
|
// genesis
|
|
// \-> bfbbad2
|
|
g.SetTip("genesis")
|
|
g.CreateBlockOne("bfbbad2", 0, func(b *wire.MsgBlock) {
|
|
scriptSize := len(b.Transactions[0].TxOut[0].PkScript)
|
|
badScript := repeatOpcode(txscript.OP_0, scriptSize)
|
|
b.Transactions[0].TxOut[0].PkScript = badScript
|
|
})
|
|
rejected(blockchain.ErrBlockOneOutputs)
|
|
|
|
// Create first block with an incorrect pay to amount.
|
|
//
|
|
// genesis
|
|
// \-> bfbbad3
|
|
g.SetTip("genesis")
|
|
g.CreateBlockOne("bfbbad3", 0, func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut[0].Value--
|
|
})
|
|
rejected(blockchain.ErrBlockOneOutputs)
|
|
|
|
// Add the required first block.
|
|
//
|
|
// genesis -> bfb
|
|
g.SetTip("genesis")
|
|
g.CreateBlockOne("bfb", 0)
|
|
g.AssertTipHeight(1)
|
|
accepted()
|
|
|
|
// Create block that tries to spend an initial payout output before maturity
|
|
// in the regular tree.
|
|
//
|
|
// genesis -> bfb
|
|
// \-> bfbi0
|
|
g.NextBlock("bfbi0", nil, nil, func(b *wire.MsgBlock) {
|
|
spendOut := chaingen.MakeSpendableOut(g.Tip(), 0, 0)
|
|
tx := g.CreateSpendTx(&spendOut, lowFee)
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// Create block that tries to spend an initial payout output before maturity
|
|
// in the stake tree by creating a ticket purchase.
|
|
//
|
|
// genesis -> bfb
|
|
// \-> bfbi1
|
|
g.SetTip("bfb")
|
|
g.NextBlock("bfbi1", nil, nil, func(b *wire.MsgBlock) {
|
|
spendOut := chaingen.MakeSpendableOut(g.Tip(), 0, 0)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextRequiredStakeDifficulty())
|
|
tx := g.CreateTicketPurchaseTx(&spendOut, ticketPrice, lowFee)
|
|
b.AddSTransaction(tx)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate enough blocks to have mature coinbase outputs to work with.
|
|
//
|
|
// genesis -> bfb -> bm0 -> bm1 -> ... -> bm#
|
|
// ---------------------------------------------------------------------
|
|
|
|
g.SetTip("bfb")
|
|
var testInstances []TestInstance
|
|
for i := uint16(0); i < coinbaseMaturity; i++ {
|
|
blockName := fmt.Sprintf("bm%d", i)
|
|
g.NextBlock(blockName, nil, nil)
|
|
g.SaveTipCoinbaseOuts()
|
|
testInstances = append(testInstances, acceptBlock(g.TipName(),
|
|
g.Tip(), true, false))
|
|
}
|
|
tests = append(tests, testInstances)
|
|
g.AssertTipHeight(uint32(coinbaseMaturity) + 1)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate enough blocks to reach the stake enabled height as well as the
|
|
// first set of live tickets while creating ticket purchases that spend
|
|
// from the coinbases matured above. This will also populate the
|
|
// pool of immature tickets.
|
|
//
|
|
// ... -> bm# ... -> bse0 -> bse1 -> ... -> bse#
|
|
// ---------------------------------------------------------------------
|
|
|
|
testInstances = nil
|
|
var ticketsPurchased int
|
|
beforeLiveTickets := stakeEnabledHeight + 2
|
|
for i := int64(0); int64(g.Tip().Header.Height) < beforeLiveTickets; i++ {
|
|
outs := g.OldestCoinbaseOuts()
|
|
ticketOuts := outs[1:]
|
|
ticketsPurchased += len(ticketOuts)
|
|
blockName := fmt.Sprintf("bse%d", i)
|
|
g.NextBlock(blockName, nil, ticketOuts)
|
|
g.SaveTipCoinbaseOuts()
|
|
testInstances = append(testInstances, acceptBlock(g.TipName(),
|
|
g.Tip(), true, false))
|
|
}
|
|
tests = append(tests, testInstances)
|
|
g.AssertTipHeight(uint32(beforeLiveTickets))
|
|
bseTipName := g.TipName()
|
|
|
|
// Create block that tries to vote before stake validation height.
|
|
// bse#
|
|
// \ -> bis1
|
|
g.NextBlock("bis1", nil, nil, func(b *wire.MsgBlock) {
|
|
// block bse0 being the first block to purchase tickets will be the
|
|
// first block with mature tickets.
|
|
ticketBlock := g.BlockByName("bse0")
|
|
voteBlock := g.BlockByName(bseTipName)
|
|
ticketIdx := uint32(1)
|
|
ticketTx := ticketBlock.STransactions[ticketIdx]
|
|
voteTx := g.CreateVoteTx(voteBlock, ticketTx,
|
|
ticketBlock.Header.Height, ticketIdx)
|
|
b.AddSTransaction(voteTx)
|
|
b.Header.Voters++
|
|
})
|
|
g.AssertPoolSize(5)
|
|
rejected(blockchain.ErrInvalidEarlyStakeTx)
|
|
g.SetTip(bseTipName)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate enough blocks to reach the stake validation height while
|
|
// continuing to purchase tickets using the coinbases matured above and
|
|
// allowing the immature tickets to mature and thus become live.
|
|
//
|
|
// While doing so, also generate blocks that have the required early
|
|
// vote unset and other that have a non-zero early final state to ensure
|
|
// they are properly rejected.
|
|
//
|
|
// ... -> bse# -> bsv0 -> bsv1 -> ... -> bsv#
|
|
// \ \ \ \-> bevbad#
|
|
// | | \-> bevbad2
|
|
// | \-> bevbad1 \-> befsbad#
|
|
// \-> bevbad0 \-> befsbad2
|
|
// | \-> befsbad1
|
|
// \-> befsbad0
|
|
// ---------------------------------------------------------------------
|
|
|
|
testInstances = nil
|
|
targetPoolSize := g.Params().TicketPoolSize * g.Params().TicketsPerBlock
|
|
for i := int64(0); int64(g.Tip().Header.Height) < stakeValidationHeight; i++ {
|
|
// Until stake validation height is reached, test that any
|
|
// blocks without the early vote bits set are rejected.
|
|
if int64(g.Tip().Header.Height) < stakeValidationHeight-1 {
|
|
prevTip := g.TipName()
|
|
blockName := fmt.Sprintf("bevbad%d", i)
|
|
g.NextBlock(blockName, nil, nil, func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits = 0
|
|
})
|
|
testInstances = append(testInstances, rejectBlock(
|
|
g.TipName(), g.Tip(),
|
|
blockchain.ErrInvalidEarlyVoteBits))
|
|
g.SetTip(prevTip)
|
|
}
|
|
|
|
// Until stake validation height is reached, test that any
|
|
// blocks without a zero final state are rejected.
|
|
if int64(g.Tip().Header.Height) < stakeValidationHeight-1 {
|
|
prevTip := g.TipName()
|
|
blockName := fmt.Sprintf("befsbad%d", i)
|
|
g.NextBlock(blockName, nil, nil, func(b *wire.MsgBlock) {
|
|
b.Header.FinalState = [6]byte{0x01}
|
|
})
|
|
testInstances = append(testInstances, rejectBlock(
|
|
g.TipName(), g.Tip(),
|
|
blockchain.ErrInvalidEarlyFinalState))
|
|
g.SetTip(prevTip)
|
|
}
|
|
|
|
// Only purchase tickets until the target ticket pool size is
|
|
// reached.
|
|
outs := g.OldestCoinbaseOuts()
|
|
ticketOuts := outs[1:]
|
|
if ticketsPurchased+len(ticketOuts) > int(targetPoolSize) {
|
|
ticketsNeeded := int(targetPoolSize) - ticketsPurchased
|
|
if ticketsNeeded > 0 {
|
|
ticketOuts = ticketOuts[1 : ticketsNeeded+1]
|
|
} else {
|
|
ticketOuts = nil
|
|
}
|
|
}
|
|
ticketsPurchased += len(ticketOuts)
|
|
blockName := fmt.Sprintf("bsv%d", i)
|
|
g.NextBlock(blockName, nil, ticketOuts)
|
|
g.SaveTipCoinbaseOuts()
|
|
testInstances = append(testInstances, acceptBlock(g.TipName(),
|
|
g.Tip(), true, false))
|
|
}
|
|
tests = append(tests, testInstances)
|
|
g.AssertTipHeight(uint32(stakeValidationHeight))
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate enough blocks to have a known distance to the first mature
|
|
// coinbase outputs for all tests that follow. These blocks continue
|
|
// to purchase tickets to avoid running out of votes.
|
|
//
|
|
// ... -> bsv# -> bbm0 -> bbm1 -> ... -> bbm#
|
|
// ---------------------------------------------------------------------
|
|
|
|
testInstances = nil
|
|
for i := uint16(0); i < coinbaseMaturity; i++ {
|
|
outs := g.OldestCoinbaseOuts()
|
|
blockName := fmt.Sprintf("bbm%d", i)
|
|
g.NextBlock(blockName, nil, outs[1:])
|
|
g.SaveTipCoinbaseOuts()
|
|
testInstances = append(testInstances, acceptBlock(g.TipName(),
|
|
g.Tip(), true, false))
|
|
}
|
|
tests = append(tests, testInstances)
|
|
|
|
// Collect spendable outputs into two different slices. The outs slice
|
|
// is intended to be used for regular transactions that spend from the
|
|
// output, while the ticketOuts slice is intended to be used for stake
|
|
// ticket purchases.
|
|
var outs []*chaingen.SpendableOut
|
|
var ticketOuts [][]chaingen.SpendableOut
|
|
for i := uint16(0); i < coinbaseMaturity; i++ {
|
|
coinbaseOuts := g.OldestCoinbaseOuts()
|
|
outs = append(outs, &coinbaseOuts[0])
|
|
ticketOuts = append(ticketOuts, coinbaseOuts[1:])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Basic forking and reorg tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------
|
|
// The comments below identify the structure of the chain being built.
|
|
//
|
|
// The values in parenthesis represent 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):
|
|
//
|
|
// ... -> bf1(0) -> bf2(1)
|
|
g.NextBlock("bf1", outs[0], ticketOuts[0])
|
|
accepted()
|
|
|
|
g.NextBlock("bf2", outs[1], ticketOuts[1])
|
|
accepted()
|
|
|
|
// Ensure duplicate blocks are rejected.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1)
|
|
// \-> bf2(1)
|
|
rejected(blockchain.ErrDuplicateBlock)
|
|
|
|
// Create a fork from bf1. There should not be a reorg since bf2 was seen
|
|
// first.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1)
|
|
// \-> bf3(1)
|
|
g.SetTip("bf1")
|
|
g.NextBlock("bf3", outs[1], ticketOuts[1])
|
|
bf3Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0)
|
|
acceptedToSideChainWithExpectedTip("bf2")
|
|
|
|
// Extend bf3 fork to make the alternative chain longer and force reorg.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.NextBlock("bf4", outs[2], ticketOuts[2])
|
|
accepted()
|
|
|
|
// Extend bf2 fork twice to make first chain longer and force reorg.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bf2")
|
|
g.NextBlock("bf5", outs[2], ticketOuts[2])
|
|
acceptedToSideChainWithExpectedTip("bf4")
|
|
|
|
g.NextBlock("bf6", outs[3], ticketOuts[3])
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Double spend tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a fork that double spends.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
|
|
// \-> bf7(2) -> bf8(4)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bf5")
|
|
g.NextBlock("bf7", outs[2], ticketOuts[3])
|
|
acceptedToSideChainWithExpectedTip("bf6")
|
|
|
|
g.NextBlock("bf8", outs[4], ticketOuts[4])
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Too much proof-of-work coinbase tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a block that generates too much proof-of-work coinbase.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
|
|
// \-> bpw1(4)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bf6")
|
|
g.NextBlock("bpw1", outs[4], ticketOuts[4], additionalCoinbasePoW(1))
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create a fork that ends with block that generates too much
|
|
// proof-of-work coinbase.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
|
|
// \-> bpw2(3) -> bpw3(4)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bf5")
|
|
g.NextBlock("bpw2", outs[3], ticketOuts[3])
|
|
acceptedToSideChainWithExpectedTip("bf6")
|
|
|
|
g.NextBlock("bpw3", outs[4], ticketOuts[4], additionalCoinbasePoW(1))
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create a fork that ends with block that generates too much
|
|
// proof-of-work coinbase as before, but with a valid fork first.
|
|
//
|
|
// ... -> bf1(0) -> bf2(1) -> bf5(2) -> bf6(3)
|
|
// | \-> bpw4(3) -> bpw5(4) -> bpw6(5)
|
|
// | (bpw4 added last)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bf5")
|
|
bpw4 := g.NextBlock("bpw4", outs[3], ticketOuts[3])
|
|
bpw5 := g.NextBlock("bpw5", outs[4], ticketOuts[4])
|
|
bpw6 := g.NextBlock("bpw6", outs[5], ticketOuts[5], additionalCoinbasePoW(1))
|
|
tests = append(tests, []TestInstance{
|
|
acceptBlock("bpw5", bpw5, false, true),
|
|
acceptBlock("bpw6", bpw6, false, true),
|
|
rejectBlock("bpw4", bpw4, blockchain.ErrBadCoinbaseValue),
|
|
expectTipBlock("bpw5", bpw5),
|
|
})
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Bad dev org output tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a block that does not generate a payment to the dev-org P2SH
|
|
// address. Test this by trying to pay to a secp256k1 P2PKH address
|
|
// using the same HASH160.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bpw5(4)
|
|
// \ \-> bbadtaxscript(5)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bpw5")
|
|
g.NextBlock("bbadtaxscript", outs[5], ticketOuts[5],
|
|
func(b *wire.MsgBlock) {
|
|
taxOutput := b.Transactions[0].TxOut[0]
|
|
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(
|
|
g.Params().OrganizationPkScriptVersion,
|
|
taxOutput.PkScript, g.Params())
|
|
p2shTaxAddr := addrs[0].(*dcrutil.AddressScriptHash)
|
|
p2pkhTaxAddr, err := dcrutil.NewAddressPubKeyHash(
|
|
p2shTaxAddr.Hash160()[:], g.Params(),
|
|
dcrec.STEcdsaSecp256k1)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
p2pkhScript, err := txscript.PayToAddrScript(p2pkhTaxAddr)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
taxOutput.PkScript = p2pkhScript
|
|
})
|
|
rejected(blockchain.ErrNoTax)
|
|
|
|
// Create a block that uses a newer output script version than is
|
|
// supported for the dev-org tax output.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bpw5(4)
|
|
// \ \-> bbadtaxscriptversion(5)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bpw5")
|
|
g.NextBlock("bbadtaxscriptversion", outs[5], ticketOuts[5],
|
|
func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut[0].Version = 1
|
|
})
|
|
rejected(blockchain.ErrNoTax)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Too much dev-org coinbase tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a block that generates too much dev-org coinbase.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bpw5(4)
|
|
// \ \-> bdc1(5)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bpw5")
|
|
g.NextBlock("bdc1", outs[5], ticketOuts[5], additionalCoinbaseDev(1))
|
|
rejected(blockchain.ErrNoTax)
|
|
|
|
// Create a fork that ends with block that generates too much dev-org
|
|
// coinbase.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bpw5(4)
|
|
// \ \-> bdc2(4) -> bdc3(5)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bpw4")
|
|
g.NextBlock("bdc2", outs[4], ticketOuts[4], additionalCoinbaseDev(1))
|
|
acceptedToSideChainWithExpectedTip("bpw5")
|
|
|
|
g.NextBlock("bdc3", outs[5], ticketOuts[5], additionalCoinbaseDev(1))
|
|
rejected(blockchain.ErrNoTax)
|
|
|
|
// Create a fork that ends with block that generates too much dev-org
|
|
// coinbase as before, but with a valid fork first.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bpw5(4)
|
|
// \ \-> bdc4(4) -> bdc5(5) -> bdc6(6)
|
|
// | (bdc4 added last)
|
|
// \-> bf3(1) -> bf4(2)
|
|
//
|
|
g.SetTip("bpw4")
|
|
bdc4 := g.NextBlock("bdc4", outs[4], ticketOuts[4])
|
|
bdc5 := g.NextBlock("bdc5", outs[5], ticketOuts[5])
|
|
bdc6 := g.NextBlock("bdc6", outs[6], ticketOuts[6], additionalCoinbaseDev(1))
|
|
tests = append(tests, []TestInstance{
|
|
acceptBlock("bdc5", bdc5, false, true),
|
|
acceptBlock("bdc6", bdc6, false, true),
|
|
rejectBlock("bdc4", bdc4, blockchain.ErrNoTax),
|
|
expectTipBlock("bdc5", bdc5),
|
|
})
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Checksig signature operation count tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Add a block with max allowed signature operations.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bdc4(4) -> bdc5(5) -> bcs1(6)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bdc5")
|
|
manySigOps := repeatOpcode(txscript.OP_CHECKSIG, maxBlockSigOps)
|
|
g.NextBlock("bcs1", outs[6], ticketOuts[6], replaceSpendScript(manySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Attempt to add block with more than max allowed signature operations.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bdc4(4) -> bdc5(5) -> bcs1(6)
|
|
// \ \-> bcs2(7)
|
|
// \-> bf3(1) -> bf4(2)
|
|
tooManySigOps := repeatOpcode(txscript.OP_CHECKSIG, maxBlockSigOps+1)
|
|
g.NextBlock("bcs2", outs[7], ticketOuts[7],
|
|
replaceSpendScript(tooManySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Cross-fork spend tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends a tx created on a different fork.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bdc4(4) -> bdc5(5) -> bcs1(6)
|
|
// \ \-> bcf1(b3.tx[1])
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bcs1")
|
|
g.NextBlock("bcf1", &bf3Tx1Out, nil)
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that forks and spends a tx created on a third fork.
|
|
//
|
|
// ... -> bf5(2) -> bpw4(3) -> bdc4(4) -> bdc5(5) -> bcs1(6)
|
|
// | \-> bcf1(bf3.tx[1]) -> bcf2(6)
|
|
// \-> bf3(1) -> bf4(2)
|
|
g.SetTip("bdc5")
|
|
g.NextBlock("bcf2", &bf3Tx1Out, nil)
|
|
acceptedToSideChainWithExpectedTip("bcs1")
|
|
|
|
g.NextBlock("bcf3", outs[6], ticketOuts[6])
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Immature coinbase tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends immature coinbase.
|
|
//
|
|
// ... -> bdc5(5) -> bcs1(6)
|
|
// \-> bic1(8)
|
|
g.SetTip("bcs1")
|
|
g.NextBlock("bic1", outs[8], ticketOuts[7])
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// Create block that spends immature coinbase on a fork.
|
|
//
|
|
// ... -> bdc5(5) -> bcs1(6)
|
|
// \-> bic2(6) -> bic3(8)
|
|
g.SetTip("bdc5")
|
|
g.NextBlock("bic2", outs[6], ticketOuts[6])
|
|
acceptedToSideChainWithExpectedTip("bcs1")
|
|
|
|
g.NextBlock("bic3", outs[8], ticketOuts[7])
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Max block size tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that is the max allowed size.
|
|
//
|
|
// ... -> bcs1(6) -> bms1(7)
|
|
g.SetTip("bcs1")
|
|
g.NextBlock("bms1", outs[7], ticketOuts[7], func(b *wire.MsgBlock) {
|
|
curScriptLen := len(b.Transactions[1].TxOut[0].PkScript)
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() +
|
|
(curScriptLen - 4)
|
|
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.
|
|
//
|
|
// ... -> bcs1(6) -> bms1(7)
|
|
// \-> bms2(7) -> bms3(8)
|
|
g.SetTip("bcs1")
|
|
g.NextBlock("bms2", outs[7], ticketOuts[7], func(b *wire.MsgBlock) {
|
|
curScriptLen := len(b.Transactions[1].TxOut[0].PkScript)
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() +
|
|
(curScriptLen - 4)
|
|
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("bms3", outs[8], ticketOuts[8])
|
|
orphanedOrRejected()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Orphan tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create valid orphan block with zero prev hash.
|
|
//
|
|
// No previous block
|
|
// \-> borphan0(7)
|
|
g.SetTip("bcs1")
|
|
g.NextBlock("borphan0", outs[7], ticketOuts[7], func(b *wire.MsgBlock) {
|
|
b.Header.PrevBlock = chainhash.Hash{}
|
|
})
|
|
orphaned()
|
|
|
|
// Create valid orphan block.
|
|
//
|
|
// ... -> bcs1(6) -> bms1(7)
|
|
// \-> borphanbase(7) -> borphan1(8)
|
|
g.SetTip("bcs1")
|
|
g.NextBlock("borphanbase", outs[7], ticketOuts[7])
|
|
g.NextBlock("borphan1", outs[8], ticketOuts[8])
|
|
orphaned()
|
|
|
|
// Ensure duplicate orphan blocks are rejected.
|
|
rejected(blockchain.ErrDuplicateBlock)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// 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.
|
|
//
|
|
// ... -> bcs1(6) -> bms1(7)
|
|
// \-> bsl1(7) -> bsl2(8)
|
|
g.SetTip("bcs1")
|
|
tooSmallCbScript := repeatOpcode(0x00, minCoinbaseScriptLen-1)
|
|
g.NextBlock("bsl1", outs[7], ticketOuts[7],
|
|
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("bsl2", outs[8], ticketOuts[8])
|
|
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.
|
|
//
|
|
// ... -> bcs1(6) -> bms1(7)
|
|
// \-> bsl3(7) -> bsl4(8)
|
|
g.SetTip("bcs1")
|
|
tooLargeCbScript := repeatOpcode(0x00, maxCoinbaseScriptLen+1)
|
|
g.NextBlock("bsl3", outs[7], ticketOuts[7],
|
|
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("bsl4", outs[8], ticketOuts[8])
|
|
orphanedOrRejected()
|
|
|
|
// Create block that has a max length coinbase script.
|
|
//
|
|
// ... -> bms1(7) -> bsl5(8)
|
|
g.SetTip("bms1")
|
|
maxSizeCbScript := repeatOpcode(0x00, maxCoinbaseScriptLen)
|
|
g.NextBlock("bsl5", outs[8], ticketOuts[8],
|
|
replaceCoinbaseSigScript(maxSizeCbScript))
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Vote tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Attempt to add block where vote has a null ticket reference hash.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv1(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv1", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.STransactions[0].TxIn[1].PreviousOutPoint = wire.OutPoint{
|
|
Hash: chainhash.Hash{},
|
|
Index: math.MaxUint32,
|
|
Tree: wire.TxTreeRegular,
|
|
}
|
|
})
|
|
rejected(blockchain.ErrBadTxInput)
|
|
|
|
// Attempt to add block with a regular tx in the stake tree.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv2(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv2", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.STransactions[0] = b.Transactions[0]
|
|
})
|
|
rejected(blockchain.ErrRegTxInStakeTree)
|
|
|
|
// Attempt to add block with too many votes.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv3(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv3", outs[9], ticketOuts[9],
|
|
g.ReplaceWithNVotes(ticketsPerBlock+1))
|
|
rejected(blockchain.ErrTooManyVotes)
|
|
|
|
// Attempt to add block with too few votes.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv4(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv4", outs[9], ticketOuts[9],
|
|
g.ReplaceWithNVotes(ticketsPerBlock/2))
|
|
rejected(blockchain.ErrNotEnoughVotes)
|
|
|
|
// Attempt to add block with different number of votes in stake tree and
|
|
// header.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv5(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv5", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.FreshStake--
|
|
})
|
|
rejected(blockchain.ErrFreshStakeMismatch)
|
|
|
|
// Attempt to add block with a ticket voting on the parent of the actual
|
|
// block it should be voting for.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv6(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv6", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
parent := g.BlockByHash(&b.Header.PrevBlock)
|
|
voteBlock := g.BlockByHash(&parent.Header.PrevBlock)
|
|
script := chaingen.VoteCommitmentScript(voteBlock.BlockHash(),
|
|
voteBlock.Header.Height)
|
|
b.STransactions[0].TxOut[0].PkScript = script
|
|
})
|
|
rejected(blockchain.ErrVotesOnWrongBlock)
|
|
|
|
// Attempt to add block with a ticket voting on the correct block hash,
|
|
// but the wrong block height.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv6a(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv6a", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
parent := g.BlockByHash(&b.Header.PrevBlock)
|
|
script := chaingen.VoteCommitmentScript(parent.BlockHash(),
|
|
b.Header.Height)
|
|
b.STransactions[1].TxOut[0].PkScript = script
|
|
})
|
|
rejected(blockchain.ErrVotesOnWrongBlock)
|
|
|
|
// Attempt to add block with a ticket voting on the correct block height,
|
|
// but the wrong block hash.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv6b(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv6b", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
parent := g.BlockByHash(&b.Header.PrevBlock)
|
|
voteBlock := g.BlockByHash(&parent.Header.PrevBlock)
|
|
script := chaingen.VoteCommitmentScript(voteBlock.BlockHash(),
|
|
parent.Header.Height)
|
|
b.STransactions[2].TxOut[0].PkScript = script
|
|
})
|
|
rejected(blockchain.ErrVotesOnWrongBlock)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// Everyone votes Yes, but block header says No.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv7(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv7", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
|
|
// Leaving vote bits as is since all blocks from the generator have
|
|
// votes set to Yes by default
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// Everyone votes No, but block header says Yes.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv8(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv8", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits |= voteBitYes
|
|
|
|
for i := 0; i < 5; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// 3x No 2x Yes, but block header says Yes.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv9(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv9", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits |= voteBitYes
|
|
|
|
for i := 0; i < 3; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// 2x No 3x Yes, but block header says No.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv10(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv10", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
|
|
for i := 0; i < 2; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Create block with a header that commits to less votes
|
|
// than the block actually contains.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv11(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv11", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.Voters--
|
|
})
|
|
rejected(blockchain.ErrVotesMismatch)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// 4x Voters
|
|
// 2x No 2x Yes, but block header says Yes
|
|
// ... -> bsl5(8)
|
|
// \-> bv12(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv12", outs[9], ticketOuts[9], g.ReplaceWithNVotes(4),
|
|
func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits |= voteBitYes
|
|
|
|
for i := 0; i < 2; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// 3x Voters
|
|
// 2x No 1x Yes, but block header says Yes
|
|
// ... -> bsl5(8)
|
|
// \-> bv12(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv13", outs[9], ticketOuts[9], g.ReplaceWithNVotes(3),
|
|
func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits |= voteBitYes
|
|
|
|
for i := 0; i < 2; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Attempt to add block with incorrect votebits set.
|
|
// 3x Voters
|
|
// 1x No 2x Yes, but block header says No
|
|
// ... -> bsl5(8)
|
|
// \-> bv14(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv14", outs[9], ticketOuts[9], g.ReplaceWithNVotes(3),
|
|
func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
|
|
for i := 0; i < 1; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
rejected(blockchain.ErrIncongruentVotebit)
|
|
|
|
// Attempt to add block with a bad ticket purchase commitment.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv15(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv15", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
ticketFee := dcrutil.Amount(2)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextReqStakeDifficulty(g.Tip()))
|
|
ticketPrice--
|
|
b.STransactions[5].TxOut[1].PkScript =
|
|
chaingen.PurchaseCommitmentScript(g.P2shOpTrueAddr(),
|
|
ticketPrice+ticketFee, 0, ticketPrice)
|
|
})
|
|
rejected(blockchain.ErrTicketCommitment)
|
|
|
|
// Attempt to add block with a ticket purchase using output from
|
|
// disapproved block.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv16(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv16", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
for i := 0; i < 5; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
|
|
prevBlock := g.Tip()
|
|
spend := chaingen.MakeSpendableOut(prevBlock, 1, 0)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextReqStakeDifficulty(g.Tip()))
|
|
ticket := g.CreateTicketPurchaseTx(&spend, ticketPrice, lowFee)
|
|
b.AddSTransaction(ticket)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Attempt to add block with a regular transaction using output
|
|
// from disapproved block.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bv17(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bv17", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
for i := 0; i < 5; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
|
|
prevBlock := g.Tip()
|
|
spend := chaingen.MakeSpendableOut(prevBlock, 1, 0)
|
|
tx := g.CreateSpendTx(&spend, lowFee)
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Stake ticket difficulty tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with ticket purchase below required ticket price.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bsd0(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bsd0", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.STransactions[5].TxOut[0].Value--
|
|
})
|
|
rejected(blockchain.ErrNotEnoughStake)
|
|
|
|
// Create block with stake transaction below pos limit.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bsd1(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bsd1", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
minStakeDiff := g.Params().MinimumStakeDiff
|
|
b.Header.SBits = minStakeDiff - 2
|
|
// TODO: This should not be necessary. The code is checking
|
|
// the ticket commit value against sbits before checking if
|
|
// sbits is under the minimum. It should be reversed.
|
|
b.STransactions[5].TxOut[0].Value = minStakeDiff - 1
|
|
})
|
|
rejected(blockchain.ErrStakeBelowMinimum)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Stakebase script tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that has a stakebase script that is smaller than the
|
|
// minimum allowed length.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bss0(9)
|
|
g.SetTip("bsl5")
|
|
tooSmallCbScript = repeatOpcode(0x00, minCoinbaseScriptLen-1)
|
|
g.NextBlock("bss0", outs[9], ticketOuts[9],
|
|
replaceStakeSigScript(tooSmallCbScript))
|
|
rejected(blockchain.ErrBadStakebaseScriptLen)
|
|
|
|
// Create block that has a stakebase script that is larger than the
|
|
// maximum allowed length.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bss1(9)
|
|
g.SetTip("bsl5")
|
|
tooLargeCbScript = repeatOpcode(0x00, maxCoinbaseScriptLen+1)
|
|
g.NextBlock("bss1", outs[9], ticketOuts[9],
|
|
replaceStakeSigScript(tooLargeCbScript))
|
|
rejected(blockchain.ErrBadStakebaseScriptLen)
|
|
|
|
// Add a block with a stake transaction with a signature script that is
|
|
// not the required script, but is otherwise a valid script.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bss2(9)
|
|
g.SetTip("bsl5")
|
|
badScript := append(g.Params().StakeBaseSigScript, 0x00)
|
|
g.NextBlock("bss2", outs[9], ticketOuts[9], replaceStakeSigScript(badScript))
|
|
rejected(blockchain.ErrBadStakebaseScrVal)
|
|
|
|
// Attempt to add a block with a bad vote payee output.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bss3(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bss3", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.STransactions[0].TxOut[2].PkScript[8] ^= 0x55
|
|
})
|
|
rejected(blockchain.ErrMismatchedPayeeHash)
|
|
|
|
// Attempt to add a block with an incorrect vote payee output amount.
|
|
//
|
|
// ... -> bsl5(8)
|
|
// \-> bss4(9)
|
|
g.SetTip("bsl5")
|
|
g.NextBlock("bss4", outs[9], ticketOuts[9], func(b *wire.MsgBlock) {
|
|
b.STransactions[0].TxOut[2].Value++
|
|
})
|
|
rejected(blockchain.ErrBadPayeeValue)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Multisig[Verify]/ChecksigVerifiy signature operation count tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with max signature operations as OP_CHECKMULTISIG.
|
|
//
|
|
// ... -> bsl5(8) -> bmo1(9)
|
|
//
|
|
// OP_CHECKMULTISIG counts for 20 sigops.
|
|
g.SetTip("bsl5")
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKMULTISIG, maxBlockSigOps/20)
|
|
g.NextBlock("bmo1", outs[9], ticketOuts[9], replaceSpendScript(manySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with more than max allowed signature operations using
|
|
// OP_CHECKMULTISIG.
|
|
//
|
|
// ... -> bmo1(9)
|
|
// \-> bmo2(10)
|
|
//
|
|
// OP_CHECKMULTISIG counts for 20 sigops.
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKMULTISIG, maxBlockSigOps/20)
|
|
tooManySigOps = append(tooManySigOps, txscript.OP_CHECKSIG)
|
|
g.NextBlock("bmo2", outs[10], ticketOuts[10],
|
|
replaceSpendScript(tooManySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create block with max signature operations as OP_CHECKMULTISIGVERIFY.
|
|
//
|
|
// ... -> bmo1(9) -> bmo3(10)
|
|
//
|
|
g.SetTip("bmo1")
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKMULTISIGVERIFY, maxBlockSigOps/20)
|
|
g.NextBlock("bmo3", outs[10], ticketOuts[10], replaceSpendScript(manySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with more than max allowed signature operations using
|
|
// OP_CHECKMULTISIGVERIFY.
|
|
//
|
|
// ... -> bmo3(10)
|
|
// \-> bmo4(11)
|
|
//
|
|
tooManySigOps =
|
|
repeatOpcode(txscript.OP_CHECKMULTISIGVERIFY, maxBlockSigOps/20)
|
|
tooManySigOps = append(tooManySigOps, txscript.OP_CHECKSIG)
|
|
g.NextBlock("bmo4", outs[11], ticketOuts[11],
|
|
replaceSpendScript(tooManySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrTooManySigOps)
|
|
|
|
// Create block with max signature operations as OP_CHECKSIGVERIFY.
|
|
//
|
|
// ... -> bmo3(10) -> bmo5(11)
|
|
//
|
|
g.SetTip("bmo3")
|
|
manySigOps = repeatOpcode(txscript.OP_CHECKSIGVERIFY, maxBlockSigOps)
|
|
g.NextBlock("bmo5", outs[11], ticketOuts[11], replaceSpendScript(manySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps)
|
|
accepted()
|
|
|
|
// Create block with more than max allowed signature operations using
|
|
// OP_CHECKSIGVERIFY.
|
|
//
|
|
// ... -> bmo5(11)
|
|
// \-> bmo6(12)
|
|
//
|
|
tooManySigOps = repeatOpcode(txscript.OP_CHECKSIGVERIFY, maxBlockSigOps+1)
|
|
g.NextBlock("bmo6", outs[12], ticketOuts[12],
|
|
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).
|
|
//
|
|
// ... -> bmo5(11)
|
|
// \-> bsp1(12)
|
|
// \-> bsp2(bsp1.tx[1])
|
|
// \-> bsp3(bsp1.tx[1])
|
|
//
|
|
g.SetTip("bmo5")
|
|
doubleSpendTx := g.CreateSpendTx(outs[12], lowFee)
|
|
g.NextBlock("bsp1", outs[12], ticketOuts[12], additionalPoWTx(doubleSpendTx))
|
|
bsp1Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0)
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
g.SetTip("bmo5")
|
|
g.NextBlock("bsp2", &bsp1Tx1Out, ticketOuts[12])
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Pay-to-script-hash signature operation count tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a private/public key pair for signing transactions.
|
|
privKey, err := secp256k1.GeneratePrivateKey()
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
pubKey := secp256k1.PublicKey(privKey.PublicKey)
|
|
|
|
// 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(pubKey.SerializeCompressed())
|
|
redeemScript = append(redeemScript, bytes.Repeat([]byte{txscript.OP_2DUP,
|
|
txscript.OP_CHECKSIGVERIFY}, redeemScriptSigOps-1)...)
|
|
redeemScript = append(redeemScript, txscript.OP_CHECKSIG)
|
|
g.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.
|
|
//
|
|
// ... -> bmo5(11) -> bshso0 (12)
|
|
g.SetTip("bmo5")
|
|
bshso0 := g.NextBlock("bshso0", outs[12], ticketOuts[12],
|
|
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 = g.CreateSpendTxForTx(prevTx, b.Header.Height,
|
|
uint32(i)+1, 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.
|
|
//
|
|
// ... -> bmo5(11) -> bshso0(12)
|
|
// \-> bshso1(13)
|
|
g.NextBlock("bshso1", outs[13], ticketOuts[13], 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 bshso0.
|
|
spend := chaingen.MakeSpendableOut(bshso0, uint32(i+2), 2)
|
|
tx := g.CreateSpendTx(&spend, lowFee)
|
|
sig, err := txscript.RawTxInSignature(tx, 0,
|
|
redeemScript, txscript.SigHashAll, 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
|
|
finalTxIndex := uint32(len(b.Transactions)) - 1
|
|
finalTx := b.Transactions[finalTxIndex]
|
|
tx := g.CreateSpendTxForTx(finalTx, b.Header.Height,
|
|
finalTxIndex, 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.
|
|
//
|
|
// ... -> bmo5(11) -> bshso0(12) -> bshso2(13)
|
|
g.SetTip("bshso0")
|
|
g.NextBlock("bshso2", outs[13], ticketOuts[13], 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 bshso0.
|
|
spend := chaingen.MakeSpendableOut(bshso0, uint32(i+2), 2)
|
|
tx := g.CreateSpendTx(&spend, lowFee)
|
|
sig, err := txscript.RawTxInSignature(tx, 0,
|
|
redeemScript, txscript.SigHashAll, 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
|
|
}
|
|
finalTxIndex := uint32(len(b.Transactions)) - 1
|
|
finalTx := b.Transactions[finalTxIndex]
|
|
tx := g.CreateSpendTxForTx(finalTx, b.Header.Height,
|
|
finalTxIndex, lowFee)
|
|
tx.TxOut[0].PkScript = repeatOpcode(txscript.OP_CHECKSIG, fill)
|
|
b.AddTransaction(tx)
|
|
})
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Reset the chain to a stable base.
|
|
//
|
|
// ... -> bmo5(11) -> brs1(12) -> brs2(13) -> brs3(14)
|
|
// \-> bshso0(12) -> bshso2(13)
|
|
// ---------------------------------------------------------------------
|
|
|
|
g.SetTip("bmo5")
|
|
g.NextBlock("brs1", outs[12], ticketOuts[12])
|
|
acceptedToSideChainWithExpectedTip("bshso2")
|
|
|
|
g.NextBlock("brs2", outs[13], ticketOuts[13])
|
|
acceptedToSideChainWithExpectedTip("bshso2")
|
|
|
|
g.NextBlock("brs3", outs[14], ticketOuts[14])
|
|
accepted()
|
|
|
|
// Collect all of the spendable coinbase outputs from the previous
|
|
// collection point up to the current tip and add them to the slices.
|
|
// This is necessary since the coinbase maturity is small enough such
|
|
// that there are no more spendable outputs without using the ones
|
|
// created in the previous tests.
|
|
g.SaveSpendableCoinbaseOuts()
|
|
for g.NumSpendableCoinbaseOuts() > 0 {
|
|
coinbaseOuts := g.OldestCoinbaseOuts()
|
|
outs = append(outs, &coinbaseOuts[0])
|
|
ticketOuts = append(ticketOuts, coinbaseOuts[1:])
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Various malformed block tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with an otherwise valid transaction in place of where
|
|
// the coinbase must be.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf1(15)
|
|
g.NextBlock("bmf1", nil, ticketOuts[15], func(b *wire.MsgBlock) {
|
|
nonCoinbaseTx := g.CreateSpendTx(outs[15], lowFee)
|
|
b.Transactions[0] = nonCoinbaseTx
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with no transactions.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf2(_)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf2", nil, nil, func(b *wire.MsgBlock) {
|
|
b.Transactions = nil
|
|
})
|
|
rejected(blockchain.ErrNoTransactions)
|
|
|
|
// Create block with invalid proof of work.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf3(15)
|
|
g.SetTip("brs3")
|
|
bmf3 := g.NextBlock("bmf3", outs[15], ticketOuts[15])
|
|
// 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. Thus, just increment the nonce until
|
|
// it's not solved and then replace it in the generator's state.
|
|
{
|
|
origHash := bmf3.BlockHash()
|
|
for chaingen.IsSolved(&bmf3.Header) {
|
|
bmf3.Header.Nonce++
|
|
}
|
|
g.UpdateBlockState("bmf3", origHash, "bmf3", bmf3)
|
|
}
|
|
rejected(blockchain.ErrHighHash)
|
|
|
|
// Create block with a timestamp too far in the future.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf4(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf4", outs[15], ticketOuts[15], 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.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf5(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf5", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
// Set the merkle root to an invalid hash.
|
|
b.Header.MerkleRoot = chainhash.Hash{}
|
|
})
|
|
g.AssertTipBlockMerkleRoot(chainhash.Hash{})
|
|
rejected(blockchain.ErrBadMerkleRoot)
|
|
|
|
// Create block with an invalid stake root.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf6(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf6", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
// Set the stake root to an invalid hash.
|
|
b.Header.StakeRoot = chainhash.Hash{}
|
|
})
|
|
g.AssertTipBlockStakeRoot(chainhash.Hash{})
|
|
rejected(blockchain.ErrBadMerkleRoot)
|
|
|
|
// Create block with an invalid block size.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf7(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf7", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Header.Size++
|
|
})
|
|
rejected(blockchain.ErrWrongBlockSize)
|
|
|
|
// Create block with an invalid subsidy for a coinbase input.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf8(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf8", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxIn[0].ValueIn++
|
|
})
|
|
rejected(blockchain.ErrBadCoinbaseAmountIn)
|
|
|
|
// Create block with an invalid subsidy for a stakebase input.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf9(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf9", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.STransactions[0].TxIn[0].ValueIn++
|
|
})
|
|
rejected(blockchain.ErrBadStakebaseAmountIn)
|
|
|
|
// Create block with a header that commits to more revocations
|
|
// than the block actually contains.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf10(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf10", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Header.Revocations++
|
|
})
|
|
rejected(blockchain.ErrRevocationsMismatch)
|
|
|
|
// Create block with an invalid proof-of-work limit.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf11(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf11", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
// Set an invalid POW limit.
|
|
b.Header.Bits--
|
|
})
|
|
rejected(blockchain.ErrUnexpectedDifficulty)
|
|
|
|
// Create block with an invalid negative proof-of-work limit.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf12(15)
|
|
g.SetTip("brs3")
|
|
bmf12 := g.NextBlock("bmf12", outs[15], ticketOuts[15])
|
|
// 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 := bmf12.BlockHash()
|
|
bmf12.Header.Bits = 0x01810000 // -1 in compact form.
|
|
g.UpdateBlockState("bmf12", origHash, "bmf12", bmf12)
|
|
}
|
|
rejected(blockchain.ErrUnexpectedDifficulty)
|
|
|
|
// Create block with two coinbase transactions.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf13(15)
|
|
g.SetTip("brs3")
|
|
coinbaseTx := g.CreateCoinbaseTx(g.Tip().Header.Height+1, ticketsPerBlock)
|
|
g.NextBlock("bmf13", outs[15], ticketOuts[15], additionalPoWTx(coinbaseTx))
|
|
rejected(blockchain.ErrMultipleCoinbases)
|
|
|
|
// Create block with duplicate transactions in the regular transaction
|
|
// tree.
|
|
//
|
|
// This test relies on the shape of the merkle tree to test the
|
|
// intended condition. That is the reason for the assertion.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf14(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf14", outs[15], ticketOuts[15], 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.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf15(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf15", outs[15], ticketOuts[15], 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)
|
|
|
|
// Create block with stake tx in regular tx tree.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf16(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf16", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.AddTransaction(b.STransactions[1])
|
|
})
|
|
rejected(blockchain.ErrStakeTxInRegularTree)
|
|
|
|
// Create block with a regular transaction that commits to an
|
|
// invalid block index.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf17(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf17", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
txOut := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&txOut, lowFee)
|
|
tx.TxIn[0].BlockIndex++
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrFraudBlockIndex)
|
|
|
|
// Create block with a regular transaction that commits to an
|
|
// invalid block height.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf18(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf18", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
txOut := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&txOut, lowFee)
|
|
tx.TxIn[0].BlockHeight++
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrFraudBlockHeight)
|
|
|
|
// Create block with a regular transaction that commits to an
|
|
// invalid input amount.
|
|
// ... -> brs3(14)
|
|
// \-> bmf19(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf19", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
txOut := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&txOut, lowFee)
|
|
tx.TxIn[0].ValueIn--
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrFraudAmountIn)
|
|
|
|
// Create block with an expired transaction in the regular tx tree.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf20(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf20", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
txOut := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&txOut, lowFee)
|
|
tx.Expiry = b.Header.Height
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrExpiredTx)
|
|
|
|
// Create block with an expired transaction in the stake tx tree.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf20b(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf20b", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.STransactions[5].Expiry = b.Header.Height
|
|
})
|
|
rejected(blockchain.ErrExpiredTx)
|
|
|
|
// Create block that commits to an invalid height.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf21(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf21", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Header.Height++
|
|
})
|
|
rejected(blockchain.ErrBadBlockHeight)
|
|
|
|
// Create block that commits to an invalid ticket pool size.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf22(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf22", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Header.PoolSize++
|
|
})
|
|
rejected(blockchain.ErrPoolSize)
|
|
|
|
// Create block that commits to an invalid final state.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf23(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf23", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Header.FinalState[0] ^= 0x55
|
|
})
|
|
rejected(blockchain.ErrInvalidFinalState)
|
|
|
|
// Create block with a regular transaction that commits to a
|
|
// malformed spend script.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf24(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf24", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
spendTx := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&spendTx, lowFee)
|
|
tx.TxOut[0].PkScript = []byte{0x01, 0x02, 0x03, 0x04}
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrScriptMalformed)
|
|
|
|
// Create block that spends immature stakebase from a vote in
|
|
// a ticket purchase.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf25(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf25", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
spendOut := chaingen.MakeSpendableStakeOut(b, 0, 2)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextReqStakeDifficulty(g.Tip()))
|
|
ticket := g.CreateTicketPurchaseTx(&spendOut, ticketPrice, lowFee)
|
|
b.AddSTransaction(ticket)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// Create block with an invalid stake transaction signature script.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf26(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf26", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.STransactions[5].TxIn[0].SignatureScript = invalidP2SHRedeemScript
|
|
})
|
|
rejected(blockchain.ErrScriptValidation)
|
|
|
|
// Create block with an invalid regular transaction signature script.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf27(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf27", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxIn[0].SignatureScript = invalidP2SHRedeemScript
|
|
})
|
|
rejected(blockchain.ErrScriptValidation)
|
|
|
|
// Create block that tries to spend an input expected to be in a different
|
|
// tx tree than the one given in the TxIn outpoint.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf28(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf28", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
tx := g.CreateSpendTxForTx(b.Transactions[1], b.Header.Height, 1, lowFee)
|
|
tx.TxIn[0].PreviousOutPoint.Tree = wire.TxTreeStake
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrDiscordantTxTree)
|
|
|
|
// Create block with no dev subsidy for coinbase transaction.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf29(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf29", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut[0] = b.Transactions[0].TxOut[1]
|
|
})
|
|
rejected(blockchain.ErrNoTax)
|
|
|
|
// Create block with an incorrect dev subsidy output amount.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf30(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf30", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut[0].Value--
|
|
})
|
|
rejected(blockchain.ErrNoTax)
|
|
|
|
// Create block that tries to buy a ticket with the block's coinbase
|
|
// transaction.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf31(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf31", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
spend := chaingen.MakeSpendableOut(b, 0, 0)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextReqStakeDifficulty(g.Tip()))
|
|
ticket := g.CreateTicketPurchaseTx(&spend, ticketPrice, lowFee)
|
|
b.AddSTransaction(ticket)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that attempts to spend a zero value output.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf32(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf32", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
// Create tx with zero value output.
|
|
spend := chaingen.MakeSpendableOut(b, 1, 0)
|
|
zeroOutputTx := g.CreateSpendTx(&spend, spend.Amount())
|
|
b.AddTransaction(zeroOutputTx)
|
|
|
|
// Spend from zero value output that was just created.
|
|
zeroSpend := chaingen.MakeSpendableOut(b, 2, 0)
|
|
zeroSpendTx := g.CreateSpendTx(&zeroSpend, 0)
|
|
b.AddTransaction(zeroSpendTx)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block with a vote that attempts to spend a ticket on a side chain.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf33(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf33", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
bsp1 := g.BlockByName("bsp1")
|
|
b.STransactions[4].TxIn[1] = bsp1.STransactions[4].TxIn[1]
|
|
})
|
|
rejected(blockchain.ErrTicketUnavailable)
|
|
|
|
// Create block that tries to spend a ticket purchase output as a regular
|
|
// transaction.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf34(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf34", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
brs3 := g.BlockByName("brs3")
|
|
spendOut := chaingen.MakeSpendableOutForSTx(brs3.STransactions[5],
|
|
brs3.Header.Height, 5, 0)
|
|
tx := g.CreateSpendTx(&spendOut, lowFee)
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrTxSStxOutSpend)
|
|
|
|
// Create block that spends immature change from one ticket purchase in
|
|
// another ticket purchase.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf35(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf35", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
spend := chaingen.MakeSpendableStakeOut(b, 5, 2)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextReqStakeDifficulty(g.Tip()))
|
|
ticket := g.CreateTicketPurchaseTx(&spend, ticketPrice, lowFee)
|
|
b.AddSTransaction(ticket)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrImmatureSpend)
|
|
|
|
// Create block with a malformed outputs order for a ticket purchase.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf36(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf36", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
prevBlock := g.Tip()
|
|
spendOut := chaingen.MakeSpendableOut(prevBlock, 1, 0)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextRequiredStakeDifficulty())
|
|
ticket := g.CreateTicketPurchaseTx(&spendOut, ticketPrice, lowFee)
|
|
ticket.TxOut[0], ticket.TxOut[2] = ticket.TxOut[2], ticket.TxOut[0]
|
|
b.AddSTransaction(ticket)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrRegTxCreateStakeOut)
|
|
|
|
// Create block with scripts that do not involve p2pkh or
|
|
// p2sh addresses for a ticket purchase.
|
|
//
|
|
// ... -> brs3(14)
|
|
// \-> bmf37(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmf37", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
prevBlock := g.Tip()
|
|
spendOut := chaingen.MakeSpendableOut(prevBlock, 1, 0)
|
|
ticketPrice := dcrutil.Amount(g.CalcNextRequiredStakeDifficulty())
|
|
ticket := g.CreateTicketPurchaseTx(&spendOut, ticketPrice, lowFee)
|
|
ticket.TxOut[0].PkScript = opTrueScript
|
|
b.AddSTransaction(ticket)
|
|
b.Header.FreshStake++
|
|
})
|
|
rejected(blockchain.ErrRegTxCreateStakeOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Block header median time tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create a block with a timestamp that is exactly the median time.
|
|
//
|
|
// ... bmo1(9) -> bmo3(10) -> bmo5(11) -> brs1(12) -> brs2(13) -> brs3(14)
|
|
// \-> bmt1(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmt1", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
medianBlk := g.BlockByHash(&b.Header.PrevBlock)
|
|
for i := 0; i < medianTimeBlocks/2; i++ {
|
|
medianBlk = g.BlockByHash(&medianBlk.Header.PrevBlock)
|
|
}
|
|
b.Header.Timestamp = medianBlk.Header.Timestamp
|
|
})
|
|
rejected(blockchain.ErrTimeTooOld)
|
|
|
|
// Create a block with a timestamp that is one second after the median
|
|
// time.
|
|
//
|
|
// ... bmo1(9) -> bmo3(10) -> bmo5(11) -> brs1(12) -> brs2(13) -> brs3(14) -> bmt2(15)
|
|
g.SetTip("brs3")
|
|
g.NextBlock("bmt2", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
medianBlk := g.BlockByHash(&b.Header.PrevBlock)
|
|
for i := 0; i < medianTimeBlocks/2; i++ {
|
|
medianBlk = g.BlockByHash(&medianBlk.Header.PrevBlock)
|
|
}
|
|
medianBlockTime := medianBlk.Header.Timestamp
|
|
b.Header.Timestamp = medianBlockTime.Add(time.Second)
|
|
})
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// CVE-2012-2459 (block hash collision due to merkle tree algo) tests.
|
|
//
|
|
// NOTE: This section originally came from upstream and applied to the
|
|
// ability to create two blocks with the same hash that were not
|
|
// identical through merkle tree tricks in Bitcoin, however, that attack
|
|
// vector is not possible in Decred since the block size is included in
|
|
// the header and adding an additional duplicate transaction changes the
|
|
// size and consequently the hash. The tests are therefore not ported
|
|
// as they do not apply.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------
|
|
// 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.
|
|
//
|
|
// ... -> bmt2(15)
|
|
// \-> bit1(16)
|
|
g.SetTip("bmt2")
|
|
g.NextBlock("bit1", outs[16], ticketOuts[16], 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.
|
|
//
|
|
// ... -> bmt2(15)
|
|
// \-> bit2(16)
|
|
g.SetTip("bmt2")
|
|
g.NextBlock("bit2", outs[16], ticketOuts[16], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxOut[0].Value = int64(outs[16].Amount()) + 1
|
|
})
|
|
rejected(blockchain.ErrSpendTooHigh)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// BIP0030 tests.
|
|
//
|
|
// NOTE: This section originally came from upstream and applied to the
|
|
// ability to create coinbase transactions with the same hash, however,
|
|
// Decred enforces the coinbase includes the block height to which it
|
|
// applies, so that condition is not possible. The tests are therefore
|
|
// not ported as they do not apply.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Blocks with non-final transaction tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that contains a non-final non-coinbase transaction.
|
|
//
|
|
// ... -> bmt2(15)
|
|
// \-> bnt1(16)
|
|
g.SetTip("bmt2")
|
|
g.NextBlock("bnt1", outs[16], ticketOuts[16], 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.
|
|
//
|
|
// ... -> bmt2(15)
|
|
// \-> bnt2(16)
|
|
g.SetTip("bmt2")
|
|
g.NextBlock("bnt2", outs[16], ticketOuts[16], 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.
|
|
//
|
|
// ... -> bmt2(15) -> bvl2(16)
|
|
// \-> bvl1(16)
|
|
g.SetTip("bmt2")
|
|
bvl1 := g.NextBlock("bvl1", outs[16], ticketOuts[16],
|
|
func(b *wire.MsgBlock) {
|
|
curScriptLen := len(b.Transactions[1].TxOut[0].PkScript)
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() +
|
|
(curScriptLen - 4)
|
|
sizePadScript := repeatOpcode(0x00, bytesToMaxSize)
|
|
replaceSpendScript(sizePadScript)(b)
|
|
})
|
|
assertTipNonCanonicalBlockSize(&g, maxBlockSize+8)
|
|
rejectedNonCanonical()
|
|
|
|
g.SetTip("bmt2")
|
|
bvl2 := g.NextBlock("bvl2", outs[16], ticketOuts[16],
|
|
func(b *wire.MsgBlock) {
|
|
*b = cloneBlock(bvl1)
|
|
})
|
|
// Since the two blocks have the same hash and the generator state now
|
|
// has bvl1 associated with the hash, manually remove bvl1, replace it
|
|
// with bvl2, and then reset the tip to it.
|
|
g.UpdateBlockState("bvl1", bvl1.BlockHash(), "bvl2", bvl2)
|
|
g.SetTip("bvl2")
|
|
g.AssertTipBlockHash(bvl1.BlockHash())
|
|
g.AssertTipBlockSize(maxBlockSize)
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Same block transaction spend tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that spends an output created earlier in the same block.
|
|
//
|
|
// ... -> bvl2(16) -> bts1(17)
|
|
g.SetTip("bvl2")
|
|
g.NextBlock("bts1", outs[17], ticketOuts[17], func(b *wire.MsgBlock) {
|
|
spendTx3 := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx3 := g.CreateSpendTx(&spendTx3, lowFee)
|
|
b.AddTransaction(tx3)
|
|
})
|
|
accepted()
|
|
|
|
// Create block that spends an output created later in the same block.
|
|
//
|
|
// ... -> bts1(17)
|
|
// \-> bts2(18)
|
|
g.NextBlock("bts2", nil, ticketOuts[18], func(b *wire.MsgBlock) {
|
|
tx2 := g.CreateSpendTx(outs[18], lowFee)
|
|
tx3 := g.CreateSpendTxForTx(tx2, b.Header.Height, 2, lowFee)
|
|
b.AddTransaction(tx3)
|
|
b.AddTransaction(tx2)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that double spends a transaction created in the same
|
|
// block.
|
|
//
|
|
// ... -> bts1(17)
|
|
// \-> bts3(18)
|
|
g.SetTip("bts1")
|
|
g.NextBlock("bts3", outs[18], ticketOuts[18], func(b *wire.MsgBlock) {
|
|
tx2 := b.Transactions[1]
|
|
tx3 := g.CreateSpendTxForTx(tx2, b.Header.Height, 1, lowFee)
|
|
tx4 := g.CreateSpendTxForTx(tx2, b.Header.Height, 1, lowFee)
|
|
b.AddTransaction(tx3)
|
|
b.AddTransaction(tx4)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that spends the same output twice in stake tree.
|
|
//
|
|
// ... -> bts1(17)
|
|
// \-> bts4(18)
|
|
g.SetTip("bts1")
|
|
g.NextBlock("bts4", outs[18], ticketOuts[18], func(b *wire.MsgBlock) {
|
|
b.STransactions[6].AddTxIn(b.STransactions[5].TxIn[0])
|
|
b.STransactions[6].AddTxOut(b.STransactions[5].TxOut[1])
|
|
b.STransactions[6].AddTxOut(b.STransactions[5].TxOut[2])
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that spends an output in regular tree that is also spent
|
|
// in stake tree.
|
|
//
|
|
// ... -> bts1(17)
|
|
// \-> bts5(18)
|
|
g.SetTip("bts1")
|
|
g.NextBlock("bts5", outs[18], ticketOuts[18], func(b *wire.MsgBlock) {
|
|
spend := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&spend, lowFee)
|
|
tx.AddTxIn(b.STransactions[5].TxIn[0])
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create block that attempts to produce a stake output in a regular
|
|
// transaction.
|
|
//
|
|
// ... -> bts1(17)
|
|
// \-> bts6(18)
|
|
g.SetTip("bts1")
|
|
g.NextBlock("bts6", outs[18], ticketOuts[18], func(b *wire.MsgBlock) {
|
|
spend := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx := g.CreateSpendTx(&spend, lowFee)
|
|
tx.AddTxOut(b.STransactions[5].TxOut[0])
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrRegTxCreateStakeOut)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Extra subsidy tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that pays 10 extra to the coinbase and a tx that only
|
|
// pays 9 fee.
|
|
//
|
|
// ... -> bts1(17)
|
|
// \-> bsb1(18)
|
|
g.SetTip("bts1")
|
|
g.NextBlock("bsb1", outs[18], ticketOuts[18], additionalCoinbasePoW(10),
|
|
additionalSpendFee(9))
|
|
rejected(blockchain.ErrBadCoinbaseValue)
|
|
|
|
// Create block that pays 10 extra to the coinbase and a tx that pays
|
|
// the extra 10 fee.
|
|
//
|
|
// ... -> bts1(17) -> bsb2(18)
|
|
g.SetTip("bts1")
|
|
g.NextBlock("bsb2", outs[18], ticketOuts[18], additionalCoinbasePoW(10),
|
|
additionalSpendFee(10))
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Malformed coinbase tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with no proof-of-work subsidy output in the coinbase.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb1(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb1", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut = b.Transactions[0].TxOut[0:1]
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with an invalid script type in the coinbase block
|
|
// commitment output.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb2(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb2", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut[1].PkScript = nil
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with an invalid script version in the coinbase block
|
|
// commitment output.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb2a(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb2a", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxOut[1].Version = ^uint16(0)
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with too few bytes for the coinbase height commitment.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb3(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb3", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
script := opReturnScript(repeatOpcode(0x00, 3))
|
|
b.Transactions[0].TxOut[1].PkScript = script
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with too many bytes for the coinbase height commitment.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb3a(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb3a", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
script := opReturnScript(repeatOpcode(0x00, 257))
|
|
b.Transactions[0].TxOut[1].PkScript = script
|
|
})
|
|
rejected(blockchain.ErrFirstTxNotCoinbase)
|
|
|
|
// Create block with invalid block height in the coinbase commitment.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb4(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb4", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
script := standardCoinbaseOpReturnScript(b.Header.Height - 1)
|
|
b.Transactions[0].TxOut[1].PkScript = script
|
|
})
|
|
rejected(blockchain.ErrCoinbaseHeight)
|
|
|
|
// Create block with a fraudulent transaction (invalid index).
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb5(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb5", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxIn[0].BlockIndex = wire.NullBlockIndex - 1
|
|
})
|
|
rejected(blockchain.ErrBadCoinbaseFraudProof)
|
|
|
|
// Create block with a fraudulent coinbase transaction (invalid height).
|
|
//
|
|
// ... -> bsb2(14)
|
|
// \-> bcb5a(15)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb5a", outs[15], ticketOuts[15], func(b *wire.MsgBlock) {
|
|
b.Transactions[0].TxIn[0].BlockHeight++
|
|
})
|
|
rejected(blockchain.ErrBadCoinbaseFraudProof)
|
|
|
|
// Create block containing a transaction with no inputs.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb6(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb6", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxIn = nil
|
|
})
|
|
rejected(blockchain.ErrNoTxInputs)
|
|
|
|
// Create block containing a transaction with no outputs.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb7(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb7", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxOut = nil
|
|
})
|
|
rejected(blockchain.ErrNoTxOutputs)
|
|
|
|
// Create block containing a transaction output with negative value.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb8(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb8", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxOut[0].Value = -1
|
|
})
|
|
rejected(blockchain.ErrBadTxOutValue)
|
|
|
|
// Create block containing a transaction output with an exceedingly
|
|
// large (and therefore invalid) value.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb9(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb9", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxOut[0].Value = dcrutil.MaxAmount + 1
|
|
})
|
|
rejected(blockchain.ErrBadTxOutValue)
|
|
|
|
// Create block containing a transaction whose outputs have an
|
|
// exceedingly large (and therefore invalid) total value.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb10(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb10", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Transactions[1].TxOut[0].Value = dcrutil.MaxAmount
|
|
b.Transactions[1].TxOut[1].Value = 1
|
|
})
|
|
rejected(blockchain.ErrBadTxOutValue)
|
|
|
|
// Create block containing a stakebase tx with a small signature script.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb11(19)
|
|
g.SetTip("bsb2")
|
|
tooSmallSbScript := repeatOpcode(0x00, minCoinbaseScriptLen-1)
|
|
g.NextBlock("bcb11", outs[19], ticketOuts[19],
|
|
replaceStakeSigScript(tooSmallSbScript))
|
|
rejected(blockchain.ErrBadStakebaseScriptLen)
|
|
|
|
// Create block containing a base stake tx with a large signature script.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb12(19)
|
|
g.SetTip("bsb2")
|
|
tooLargeSbScript := repeatOpcode(0x00, maxCoinbaseScriptLen+1)
|
|
g.NextBlock("bcb12", outs[19], ticketOuts[19],
|
|
replaceStakeSigScript(tooLargeSbScript))
|
|
rejected(blockchain.ErrBadStakebaseScriptLen)
|
|
|
|
// Create block containing an input transaction with a null outpoint.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb13(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb13", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
tx := b.Transactions[1]
|
|
tx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
|
|
wire.MaxPrevOutIndex, wire.TxTreeRegular)})
|
|
})
|
|
rejected(blockchain.ErrBadTxInput)
|
|
|
|
// Create block containing duplicate tx inputs.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb14(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb14", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
tx := b.Transactions[1]
|
|
tx.AddTxIn(&wire.TxIn{
|
|
PreviousOutPoint: b.Transactions[1].TxIn[0].PreviousOutPoint})
|
|
})
|
|
rejected(blockchain.ErrDuplicateTxInputs)
|
|
|
|
// Create block with nanosecond precision timestamp.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb15(19)
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bcb15", outs[19], ticketOuts[19], func(b *wire.MsgBlock) {
|
|
b.Header.Timestamp = b.Header.Timestamp.Add(1 * time.Nanosecond)
|
|
})
|
|
rejected(blockchain.ErrInvalidTime)
|
|
|
|
// Create block with target difficulty that is too low (0 or below).
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb16(19)
|
|
g.SetTip("bsb2")
|
|
bcb16 := g.NextBlock("bcb16", outs[19], ticketOuts[19])
|
|
{
|
|
// 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.
|
|
bcb16Hash := bcb16.BlockHash()
|
|
bcb16.Header.Bits = 0x01810000 // -1 in compact form.
|
|
g.UpdateBlockState("bcb16", bcb16Hash, "bcb16", bcb16)
|
|
}
|
|
rejected(blockchain.ErrUnexpectedDifficulty)
|
|
|
|
// Create block with target difficulty that is greater than max allowed.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bcb17(19)
|
|
g.SetTip("bsb2")
|
|
bcb17 := g.NextBlock("bcb17", outs[19], ticketOuts[19])
|
|
{
|
|
// 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 improperly solved block.
|
|
bcb17Hash := bcb17.BlockHash()
|
|
bcb17.Header.Bits = g.Params().PowLimitBits + 1
|
|
g.UpdateBlockState("bcb17", bcb17Hash, "bcb17", bcb17)
|
|
}
|
|
rejected(blockchain.ErrUnexpectedDifficulty)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// More signature operation counting tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// 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 script generated consists of the following form:
|
|
//
|
|
// Comment assumptions:
|
|
// maxBlockSigOps = 5000
|
|
// maxScriptElementSize = 2048
|
|
//
|
|
// [0-4999] : OP_CHECKSIG
|
|
// [5000] : OP_PUSHDATA4
|
|
// [5001-5004]: 2049 (little-endian encoded maxScriptElementSize+1)
|
|
// [5005-7053]: too large script element
|
|
// [7054] : OP_CHECKSIG (goes over the limit)
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bsc1(19)
|
|
g.SetTip("bsb2")
|
|
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("bsc1", outs[19], ticketOuts[19],
|
|
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.
|
|
//
|
|
// ... -> bsb2(18)
|
|
// \-> bsc2(19)
|
|
g.SetTip("bsb2")
|
|
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("bsc2", outs[19], ticketOuts[19],
|
|
replaceSpendScript(tooManySigOps))
|
|
g.AssertTipBlockSigOpsCount(maxBlockSigOps + 1)
|
|
rejected(blockchain.ErrScriptMalformed)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Dead execution path tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block with an invalid opcode in a dead execution path.
|
|
//
|
|
// ... -> bsb2(18) -> bde1(19)
|
|
script := []byte{txscript.OP_IF, txscript.OP_INVALIDOPCODE,
|
|
txscript.OP_ELSE, txscript.OP_TRUE, txscript.OP_ENDIF}
|
|
g.SetTip("bsb2")
|
|
g.NextBlock("bde1", outs[19], ticketOuts[19], replaceSpendScript(script),
|
|
func(b *wire.MsgBlock) {
|
|
spendTx2 := chaingen.MakeSpendableOut(b, 1, 0)
|
|
tx3 := g.CreateSpendTx(&spendTx2, 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.
|
|
//
|
|
// ... -> bde1(19) -> bor1(20)
|
|
g.NextBlock("bor1", outs[20], ticketOuts[20], func(b *wire.MsgBlock) {
|
|
// Add 4 outputs to the spending transaction that are spent
|
|
// below.
|
|
const numAdditionalOutputs = 4
|
|
spendTx := b.Transactions[1]
|
|
spendTx.TxOut[0].Value -= int64(lowFee) * numAdditionalOutputs
|
|
for i := 0; i < numAdditionalOutputs; i++ {
|
|
spendTx.AddTxOut(wire.NewTxOut(int64(lowFee), 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 := dcrutil.Amount(0)
|
|
for i := uint32(0); i < numAdditionalOutputs; i++ {
|
|
spend := chaingen.MakeSpendableOut(b, 1, i+2)
|
|
tx := g.CreateSpendTx(&spend, zeroFee)
|
|
tx.TxIn[0].SignatureScript = nil
|
|
b.AddTransaction(tx)
|
|
}
|
|
})
|
|
g.AssertTipBlockNumTxns(6)
|
|
g.AssertTipBlockTxOutOpReturn(5, 1)
|
|
bor1OpReturnOut := chaingen.MakeSpendableOut(g.Tip(), 5, 1)
|
|
accepted()
|
|
|
|
// Reorg to a side chain that does not contain the OP_RETURNs.
|
|
//
|
|
// ... -> bde1(19) -> bor1(20)
|
|
// \-> bor2(20) -> bor3(21)
|
|
g.SetTip("bde1")
|
|
g.NextBlock("bor2", outs[20], ticketOuts[20])
|
|
acceptedToSideChainWithExpectedTip("bor1")
|
|
|
|
g.NextBlock("bor3", outs[21], ticketOuts[21])
|
|
accepted()
|
|
|
|
// Reorg back to the original chain that contains the OP_RETURNs.
|
|
//
|
|
// ... -> bde1(19) -> bor1(20) -> bor4(21) -> bor5(22)
|
|
// \-> bor2(20) -> bor3(21)
|
|
g.SetTip("bor1")
|
|
g.NextBlock("bor4", outs[21], ticketOuts[21])
|
|
acceptedToSideChainWithExpectedTip("bor3")
|
|
|
|
g.NextBlock("bor5", outs[22], ticketOuts[22])
|
|
accepted()
|
|
|
|
// Create a block that spends an OP_RETURN.
|
|
//
|
|
// ... -> bde1(19) -> bor1(20) -> bor4(21) -> bor5(22)
|
|
// \-> bor2(20) -> bor3(21) \-> bor6(bor1.tx[5].out[1])
|
|
g.NextBlock("bor6", nil, ticketOuts[23], func(b *wire.MsgBlock) {
|
|
// An OP_RETURN output doesn't have any value so use a fee of 0.
|
|
zeroFee := dcrutil.Amount(0)
|
|
tx := g.CreateSpendTx(&bor1OpReturnOut, zeroFee)
|
|
b.AddTransaction(tx)
|
|
})
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create a block that has a transaction with multiple OP_RETURNs. Even
|
|
// though a transaction with a large number of OP_RETURNS is not
|
|
// considered a standard transaction, it is still valid by the consensus
|
|
// rules.
|
|
//
|
|
// ... -> bor5(22) -> bor7(23)
|
|
//
|
|
g.SetTip("bor5")
|
|
g.NextBlock("bor7", outs[23], ticketOuts[23], func(b *wire.MsgBlock) {
|
|
const numAdditionalOutputs = 8
|
|
const zeroCoin = int64(0)
|
|
spendTx := b.Transactions[1]
|
|
for i := 0; i < numAdditionalOutputs; i++ {
|
|
opRetScript := chaingen.UniqueOpReturnScript()
|
|
spendTx.AddTxOut(wire.NewTxOut(zeroCoin, opRetScript))
|
|
}
|
|
})
|
|
for i := uint32(2); i < 10; i++ {
|
|
g.AssertTipBlockTxOutOpReturn(1, i)
|
|
}
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Revocation tests.
|
|
// ---------------------------------------------------------------------
|
|
// Create valid block that misses a vote.
|
|
//
|
|
// ... -> bor7(23) -> brt1(24)
|
|
g.NextBlock("brt1", outs[24], ticketOuts[24], g.ReplaceWithNVotes(4))
|
|
accepted()
|
|
|
|
// Create block that contains a revocation due to previous missed vote and
|
|
// a header that commits to more revocations than the block actually
|
|
// contains.
|
|
//
|
|
// ... -> brt1(24)
|
|
// \-> brt2(25)
|
|
g.NextBlock("brt2", outs[25], ticketOuts[25], func(b *wire.MsgBlock) {
|
|
b.Header.Revocations++
|
|
})
|
|
g.AssertTipNumRevocations(2)
|
|
rejected(blockchain.ErrRevocationsMismatch)
|
|
|
|
// Create block that has a revocation with more payees than expected.
|
|
// ... -> brt1(24)
|
|
// \-> brt3(25)
|
|
g.SetTip("brt1")
|
|
g.NextBlock("brt3", outs[25], ticketOuts[25], func(b *wire.MsgBlock) {
|
|
g.AssertBlockRevocationTx(b, 10)
|
|
b.STransactions[10].TxOut = append(b.STransactions[10].TxOut,
|
|
b.STransactions[10].TxOut[0])
|
|
})
|
|
g.AssertTipNumRevocations(1)
|
|
rejected(blockchain.ErrBadNumPayees)
|
|
|
|
// Create block that has a revocation paying more than the original
|
|
// amount to the committed address.
|
|
// ... -> brt1(24)
|
|
// \-> brt4(25)
|
|
g.SetTip("brt1")
|
|
g.NextBlock("brt4", outs[25], ticketOuts[25], func(b *wire.MsgBlock) {
|
|
g.AssertBlockRevocationTx(b, 10)
|
|
b.STransactions[10].TxOut[0].Value++
|
|
})
|
|
g.AssertTipNumRevocations(1)
|
|
rejected(blockchain.ErrBadPayeeValue)
|
|
|
|
// Create block that has a revocation using a corrupted pay-to-address
|
|
// script.
|
|
// ... -> brt1(24)
|
|
// \-> brt5(25)
|
|
g.SetTip("brt1")
|
|
g.NextBlock("brt5", outs[25], ticketOuts[25], func(b *wire.MsgBlock) {
|
|
g.AssertBlockRevocationTx(b, 10)
|
|
b.STransactions[10].TxOut[0].PkScript[8] ^= 0x55
|
|
})
|
|
g.AssertTipNumRevocations(1)
|
|
rejected(blockchain.ErrMismatchedPayeeHash)
|
|
|
|
// Create block that has a revocation for a voted ticket.
|
|
//
|
|
// ... -> brt1(24)
|
|
// \-> brt6(25)
|
|
g.SetTip("brt1")
|
|
g.NextBlock("brt6", outs[25], ticketOuts[25], func(b *wire.MsgBlock) {
|
|
// Loop backwards to get the ticket transaction associated with the
|
|
// first vote in the block.
|
|
voteTx := b.STransactions[0]
|
|
ticketTxIn := voteTx.TxIn[1]
|
|
ticketBlk := g.BlockByHash(&b.Header.PrevBlock)
|
|
for ticketBlk.Header.Height != ticketTxIn.BlockHeight {
|
|
ticketBlk = g.BlockByHash(&ticketBlk.Header.PrevBlock)
|
|
}
|
|
ticketTx := ticketBlk.STransactions[ticketTxIn.BlockIndex]
|
|
|
|
// Create new revocation for the same ticket.
|
|
revocation := g.CreateRevocationTx(ticketTx, ticketTxIn.BlockHeight,
|
|
ticketTxIn.BlockIndex)
|
|
b.AddSTransaction(revocation)
|
|
b.Header.Revocations++
|
|
})
|
|
g.AssertTipNumRevocations(2)
|
|
rejected(blockchain.ErrInvalidSSRtx)
|
|
|
|
// Create block that contains a revocation due to previous missed vote.
|
|
//
|
|
// ... -> brt1(24) -> brt7(25)
|
|
g.SetTip("brt1")
|
|
g.NextBlock("brt7", outs[25], ticketOuts[25])
|
|
brt7Tx1Out := chaingen.MakeSpendableOut(g.Tip(), 1, 0)
|
|
g.AssertTipNumRevocations(1)
|
|
accepted()
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Disapproval tests.
|
|
// ---------------------------------------------------------------------
|
|
|
|
// Create block that disapproves the regular transaction tree of the prev
|
|
// block and tries to spend a transaction from it.
|
|
//
|
|
// ... -> brt7(25)
|
|
// \-> bdt1(26)
|
|
g.NextBlock("bdt1", &brt7Tx1Out, ticketOuts[26], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
for i := 0; i < 5; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
g.AssertTipDisapprovesPrevious()
|
|
rejected(blockchain.ErrMissingTxOut)
|
|
|
|
// Create a couple of valid blocks for use in upcoming reorgs of
|
|
// disapproving blocks such that the first one spends an output from the
|
|
// regular transaction tree of a block that will be disapproved via a side
|
|
// chain.
|
|
//
|
|
// ... -> brt7(25) -> bdt2(26) -> bdt3(27)
|
|
g.SetTip("brt7")
|
|
g.NextBlock("bdt2", &brt7Tx1Out, ticketOuts[26])
|
|
accepted()
|
|
|
|
g.NextBlock("bdt3", outs[27], ticketOuts[27])
|
|
accepted()
|
|
|
|
// Create a fork from brt7 that contains a couple of subsequent valid blocks
|
|
// that disapprove the regular transaction tree of the previous blocks and
|
|
// extend it to force a reorg to the chain that contains the disapproving
|
|
// blocks.
|
|
//
|
|
// ... -> brt7(25) -> bdt2(26) -> bdt3(27)
|
|
// \-> bdt4(26) -> bdt5(27) -> bdt6(28)
|
|
g.SetTip("brt7")
|
|
g.NextBlock("bdt4", outs[26], ticketOuts[26], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
for i := 0; i < 5; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
g.AssertTipDisapprovesPrevious()
|
|
acceptedToSideChainWithExpectedTip("bdt3")
|
|
|
|
g.NextBlock("bdt5", outs[27], ticketOuts[27], func(b *wire.MsgBlock) {
|
|
b.Header.VoteBits &^= voteBitYes
|
|
for i := 0; i < 5; i++ {
|
|
g.ReplaceVoteBitsN(i, voteBitNo)(b)
|
|
}
|
|
})
|
|
g.AssertTipDisapprovesPrevious()
|
|
acceptedToSideChainWithExpectedTip("bdt3")
|
|
|
|
g.NextBlock("bdt6", outs[28], ticketOuts[28])
|
|
accepted()
|
|
|
|
// Extend the original bdt3 fork in order to make the first chain longer and
|
|
// force a reorg that removes the disapproving blocks.
|
|
//
|
|
// ... -> brt7(25) -> bdt2(26) -> bdt3(27) -> bdt7(28) -> bdt8(29)
|
|
// \-> bdt4(26) -> bdt5(27) -> bdt6(28)
|
|
g.SetTip("bdt3")
|
|
g.NextBlock("bdt7", outs[28], ticketOuts[28])
|
|
acceptedToSideChainWithExpectedTip("bdt6")
|
|
|
|
g.NextBlock("bdt8", outs[29], ticketOuts[29])
|
|
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.
|
|
//
|
|
// ... -> bdt8(29) -> ...
|
|
g.SetTip("bdt8")
|
|
spendableOutOffset := int32(30) // Next spendable offset.
|
|
|
|
// Collect all of the spendable coinbase outputs from the previous
|
|
// collection point up to the current tip.
|
|
g.SaveSpendableCoinbaseOuts()
|
|
for g.NumSpendableCoinbaseOuts() > 0 {
|
|
coinbaseOuts := g.OldestCoinbaseOuts()
|
|
outs = append(outs, &coinbaseOuts[0])
|
|
ticketOuts = append(ticketOuts, coinbaseOuts[1:])
|
|
}
|
|
|
|
// Extend the main chain by a large number of max size blocks.
|
|
//
|
|
// ... -> br0 -> br1 -> ... -> br#
|
|
testInstances = nil
|
|
reorgSpend := *outs[spendableOutOffset]
|
|
reorgTicketSpends := ticketOuts[spendableOutOffset]
|
|
reorgStartBlockName := g.TipName()
|
|
chain1TipName := g.TipName()
|
|
for i := int32(0); i < numLargeReorgBlocks; i++ {
|
|
chain1TipName = fmt.Sprintf("br%d", i)
|
|
g.NextBlock(chain1TipName, &reorgSpend, reorgTicketSpends,
|
|
func(b *wire.MsgBlock) {
|
|
curScriptLen := len(b.Transactions[1].TxOut[0].PkScript)
|
|
bytesToMaxSize := maxBlockSize - b.SerializeSize() +
|
|
(curScriptLen - 4)
|
|
sizePadScript := repeatOpcode(0x00, bytesToMaxSize)
|
|
replaceSpendScript(sizePadScript)(b)
|
|
})
|
|
g.AssertTipBlockSize(maxBlockSize)
|
|
g.SaveTipCoinbaseOuts()
|
|
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]
|
|
reorgTicketSpends = ticketOuts[spendableOutOffset+1+i]
|
|
} else {
|
|
reorgOuts := g.OldestCoinbaseOuts()
|
|
reorgSpend = reorgOuts[0]
|
|
reorgTicketSpends = reorgOuts[1:]
|
|
}
|
|
}
|
|
tests = append(tests, testInstances)
|
|
|
|
// Ensure any remaining unused spendable outs from the main chain that
|
|
// are still associated with the generator are removed to avoid them
|
|
// from being used on the side chain build below.
|
|
for g.NumSpendableCoinbaseOuts() > 0 {
|
|
g.OldestCoinbaseOuts()
|
|
}
|
|
|
|
// Create a side chain that has the same length.
|
|
//
|
|
// ... -> br0 -> ... -> br#
|
|
// \-> bralt0 -> ... -> bralt#
|
|
g.SetTip(reorgStartBlockName)
|
|
testInstances = nil
|
|
reorgSpend = *outs[spendableOutOffset]
|
|
reorgTicketSpends = ticketOuts[spendableOutOffset]
|
|
var chain2TipName string
|
|
for i := int32(0); i < numLargeReorgBlocks; i++ {
|
|
chain2TipName = fmt.Sprintf("bralt%d", i)
|
|
g.NextBlock(chain2TipName, &reorgSpend, reorgTicketSpends)
|
|
g.SaveTipCoinbaseOuts()
|
|
testInstances = append(testInstances, acceptBlock(g.TipName(),
|
|
g.Tip(), false, 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]
|
|
reorgTicketSpends = ticketOuts[spendableOutOffset+1+i]
|
|
} else {
|
|
reorgOuts := g.OldestCoinbaseOuts()
|
|
reorgSpend = reorgOuts[0]
|
|
reorgTicketSpends = reorgOuts[1:]
|
|
}
|
|
}
|
|
testInstances = append(testInstances, expectTipBlock(chain1TipName,
|
|
g.BlockByName(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", numLargeReorgBlocks), nil, 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", numLargeReorgBlocks), nil, nil)
|
|
acceptedToSideChainWithExpectedTip(chain2TipName)
|
|
|
|
g.NextBlock(fmt.Sprintf("br%d", numLargeReorgBlocks+1), nil, nil)
|
|
accepted()
|
|
|
|
return tests, nil
|
|
}
|