dcrd/blockchain/process_test.go
2021-09-16 11:10:08 -05:00

1880 lines
78 KiB
Go

// Copyright (c) 2019-2020 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"sync"
"testing"
"github.com/decred/dcrd/blockchain/v4/chaingen"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/wire"
)
// These variables are used to provide a shared set of generated blocks that
// are only generated one time on demand.
var (
processTestGeneratorLock sync.Mutex
processTestGenerator *chaingen.Generator
)
// TestProcessOrder ensures processing-specific logic such as orphan handling,
// duplicate block handling, and out-of-order reorgs to invalid blocks works as
// expected.
func TestProcessOrder(t *testing.T) {
// Create a test harness initialized with the genesis block as the tip.
params := chaincfg.RegNetParams()
g, teardownFunc := newChaingenHarness(t, params, "processordertest")
defer teardownFunc()
// Shorter versions of useful params for convenience.
coinbaseMaturity := params.CoinbaseMaturity
stakeValidationHeight := params.StakeValidationHeight
// ---------------------------------------------------------------------
// Generate and accept enough blocks to reach stake validation height.
// ---------------------------------------------------------------------
g.AdvanceToStakeValidationHeight()
// ---------------------------------------------------------------------
// 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#
// ---------------------------------------------------------------------
for i := uint16(0); i < coinbaseMaturity; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bbm%d", i)
g.NextBlock(blockName, nil, outs[1:])
g.SaveTipCoinbaseOuts()
g.AcceptTipBlock()
}
g.AssertTipHeight(uint32(stakeValidationHeight) + uint32(coinbaseMaturity))
// 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:])
}
// Ensure duplicate blocks are rejected.
//
// ... -> b1(0)
// \-> b1(0)
g.NextBlock("b1", outs[0], ticketOuts[0])
g.AcceptTipBlock()
g.RejectTipBlock(ErrDuplicateBlock)
// ---------------------------------------------------------------------
// Orphan tests.
// ---------------------------------------------------------------------
// Create valid orphan block with zero prev hash.
//
// No previous block
// \-> borphan0(1)
g.SetTip("b1")
g.NextBlock("borphan0", outs[1], ticketOuts[1], func(b *wire.MsgBlock) {
b.Header.PrevBlock = chainhash.Hash{}
})
g.RejectTipBlock(ErrMissingParent)
// Create valid orphan block.
//
// ... -> b1(0)
// \-> borphanbase(1) -> borphan1(2)
g.SetTip("b1")
g.NextBlock("borphanbase", outs[1], ticketOuts[1])
g.NextBlock("borphan1", outs[2], ticketOuts[2])
g.RejectTipBlock(ErrMissingParent)
// Ensure duplicate orphan blocks are rejected.
g.RejectTipBlock(ErrMissingParent)
// ---------------------------------------------------------------------
// Out-of-order forked reorg to invalid block tests.
//
// NOTE: These tests have been modified to be processed in order despite
// the comments for now due to the removal of orphan processing. They
// are otherwise left intact because future commits will allow headers
// to be processed independently from the block data which will allow
// out of order processing of data again so long as the headers are
// processed in order.
// ---------------------------------------------------------------------
// Create a fork that ends with block that generates too much proof-of-work
// coinbase, but with a valid fork first.
//
// ... -> b1(0) -> b2(1)
// \-> bpw1(1) -> bpw2(2) -> bpw3(3)
// (bpw1 added last)
g.SetTip("b1")
g.NextBlock("b2", outs[1], ticketOuts[1])
g.AcceptTipBlock()
g.ExpectTip("b2")
g.SetTip("b1")
g.NextBlock("bpw1", outs[1], ticketOuts[1])
g.NextBlock("bpw2", outs[2], ticketOuts[2])
g.NextBlock("bpw3", outs[3], ticketOuts[3], func(b *wire.MsgBlock) {
// Increase the first proof-of-work coinbase subsidy.
b.Transactions[0].TxOut[2].Value++
})
g.AcceptHeader("bpw1")
g.AcceptBlockData("bpw2")
g.AcceptBlockData("bpw3")
g.RejectBlock("bpw1", ErrBadCoinbaseValue)
g.ExpectTip("bpw2")
// Create a fork that ends with block that generates too much dev-org
// coinbase, but with a valid fork first.
//
// ... -> b1(0) -> bpw1(1) -> bpw2(2)
// \-> bdc1(2) -> bdc2(3) -> bdc3(4)
// (bdc1 added last)
g.SetTip("bpw1")
g.NextBlock("bdc1", outs[2], ticketOuts[2])
g.NextBlock("bdc2", outs[3], ticketOuts[3])
g.NextBlock("bdc3", outs[4], ticketOuts[4], func(b *wire.MsgBlock) {
// Increase the proof-of-work dev subsidy by the provided amount.
b.Transactions[0].TxOut[0].Value++
})
g.AcceptHeader("bdc1")
g.AcceptBlockData("bdc2")
g.AcceptBlockData("bdc3")
g.RejectBlock("bdc1", ErrNoTax)
g.ExpectTip("bdc2")
}
// genSharedProcessTestBlocks either generates a new set of blocks used in the
// process logic tests or returns an already generated set if called more than
// once.
//
//
// The generated blocks form a fairly complex overall block tree as follows:
// - * denotes invalid header
// - ! denotes invalid block prior to connection (e.g. vote from bad ticket)
// - @ denotes invalid block when connected (e.g. double spend)
// - bfb is the required first block
// - bm# are blocks which allow coins to mature
// - bse# are blocks which reach stake enabled height
// - bsv# are blocks which reach stake validation height
// - bfbbad is a variant of the first block that pays too much
// - bfbbadchild and bfbbadchilda are valid blocks that descend from bfbbad
// - b1badhdr has a header with too few votes (hdr sanity failure)
// - b1badhdra has a header with a mismatched height (hdr positional failure)
// - b1bad has a ticket purchase that pays too little (block sanity failure)
// - b1bada is a block with an expired tx (block positional failure)
// - b5b is a block with a vote from bad ticket (context failure)
// - b6g is a block with a vote from bad ticket (context failure)
// - b5h is a block with a double spend (connect failure)
// - b6i is a block with a double spend (connect failure)
// - b7j is a block with a double spend (connect failure)
//
// genesis -> bfb -> bm0 -> ... -> bm# -> bse0 -> ... -> bse# -> bsv0 -> ...
// \-> bfbbad! -> bfbbadchild
// \-> bfbbadchilda
//
// ... -> bsv# -> bbm0 -> bbm1 -> ... -> bbm# -> b1 -> b2 -> ...
// \-> b1badhdr*
// \-> b1badhdra*
// \-> b1bad!
// \-> b1bada!
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c | \-> b8c -> b9c
// | \-> b7d -> b8d -> b9d
// | \-> b7e -> b8e
// |
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// \-> b4g -> b5g -> b6g! -> b7g -> b8g
// \-> b4h -> b5h@ -> b6h -> b7h
// \-> b4i -> b5i -> b6i@ -> b7i -> b8i
// \-> b4j -> b5j -> b6j -> b7j@
func genSharedProcessTestBlocks(t *testing.T) (*chaingen.Generator, error) {
processTestGeneratorLock.Lock()
defer processTestGeneratorLock.Unlock()
// Only generate the process test chain once.
if processTestGenerator != nil {
return processTestGenerator, nil
}
// Create a new database and chain instance needed to create the generator
// populated with the desired blocks.
params := chaincfg.RegNetParams()
g, teardownFunc := newChaingenHarness(t, params, "sharedprocesstestblocks")
defer teardownFunc()
// Shorter versions of useful params for convenience.
coinbaseMaturity := params.CoinbaseMaturity
stakeValidationHeight := params.StakeValidationHeight
// -------------------------------------------------------------------------
// Block one variants.
// -------------------------------------------------------------------------
// Produce a first block with too much coinbase.
//
// genesis
// \-> bfbbad
g.CreateBlockOne("bfbbad", 1)
g.AssertTipHeight(1)
// Create a block that descends from the invalid first block.
//
// genesis
// \-> bfbbad -> bfbbadchild
g.NextBlock("bfbbadchild", nil, nil)
g.AssertTipHeight(2)
// Create a second block that descends from the invalid first block.
//
// genesis
// \-> bfbbad -> bfbbadchild
// \-> bfbbadchilda
g.SetTip("bfbbad")
g.NextBlock("bfbbadchilda", nil, nil)
g.AssertTipHeight(2)
// -------------------------------------------------------------------------
// Generate enough blocks to reach stake validation height.
// -------------------------------------------------------------------------
g.SetTip("genesis")
g.GenerateToStakeValidationHeight()
// -------------------------------------------------------------------------
// 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#
// -------------------------------------------------------------------------
var finalMaturityBlockName string
for i := uint16(0); i < coinbaseMaturity; i++ {
outs := g.OldestCoinbaseOuts()
blockName := fmt.Sprintf("bbm%d", i)
g.NextBlock(blockName, nil, outs[1:])
g.SaveTipCoinbaseOuts()
finalMaturityBlockName = blockName
}
g.AssertTipHeight(uint32(stakeValidationHeight) + uint32(coinbaseMaturity))
// 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:])
}
// -------------------------------------------------------------------------
// Generate various invalid variants of b1.
//
// Note that * below indicates a block that has an invalid header and !
// indicates a block that is invalid prior to connection due to sanity
// and/or positional checks.
//
// ... -> bbm#
// \-> b1badhdr*
// \-> b1badhdra*
// \-> b1bad!
// \-> b1bada!
// -------------------------------------------------------------------------
// Generate block with too few votes to possibly have majority which results
// in a header that is rejected due to context-free (aka sanity) checks.
g.NextBlock("b1badhdr", outs[0], ticketOuts[0], g.ReplaceWithNVotes(1))
// Generate block with a mismatched height which results in a header that is
// rejected due to positional checks.
g.SetTip(finalMaturityBlockName)
g.NextBlock("b1badhdra", outs[0], ticketOuts[0], func(b *wire.MsgBlock) {
b.Header.Height--
})
// Generate block with a ticket that pays too little which results in being
// rejected due to block sanity checks.
g.SetTip(finalMaturityBlockName)
g.NextBlock("b1bad", outs[0], ticketOuts[0], func(b *wire.MsgBlock) {
b.STransactions[5].TxOut[0].Value--
})
// Generate block with an expired transaction which results in being
// rejected due to block positional checks.
g.SetTip(finalMaturityBlockName)
g.NextBlock("b1bada", outs[0], ticketOuts[0], func(b *wire.MsgBlock) {
b.Transactions[1].Expiry = b.Header.Height
})
// -------------------------------------------------------------------------
// Generate a few blocks to serve as a base for more complex branches below.
//
// ... -> b1 -> b2 -> b3
// -------------------------------------------------------------------------
g.SetTip(finalMaturityBlockName)
g.NextBlock("b1", outs[0], ticketOuts[0])
g.NextBlock("b2", outs[1], ticketOuts[1])
g.NextBlock("b3", outs[2], ticketOuts[2])
// -------------------------------------------------------------------------
// Generate a block tree with several branches from different fork points
// where some of them have blocks that are invalid in various ways:
//
// Note that ! below indicates a block that is invalid prior to connection
// due to sanity, positional, and/or contextual checks and @ indicates a
// block that is invalid when actually connected as part of becoming the
// main chain.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c | \-> b8c -> b9c
// | \-> b7d -> b8d -> b9d
// | \-> b7e -> b8e
// |
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// \-> b4g -> b5g -> b6g! -> b7g -> b8g
// \-> b4h -> b5h@ -> b6h -> b7h
// \-> b4i -> b5i -> b6i@ -> b7i -> b8i
// \-> b4j -> b5j -> b6j -> b7j@
// -------------------------------------------------------------------------
// Generate a run of blocks that has the most cumulative work and thus will
// comprise the main chain.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
g.NextBlock("b4", outs[3], ticketOuts[3])
g.NextBlock("b5", outs[4], ticketOuts[4])
g.NextBlock("b6", outs[5], ticketOuts[5])
g.NextBlock("b7", outs[6], ticketOuts[6])
g.NextBlock("b8", outs[7], ticketOuts[7])
g.NextBlock("b9", outs[8], ticketOuts[8])
g.NextBlock("b10", outs[9], ticketOuts[9])
g.NextBlock("b11", outs[10], ticketOuts[10])
// Generate a side chain with a single block just prior to the best tip.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b10a
g.SetTip("b9")
g.NextBlock("b10a", outs[9], ticketOuts[9])
// Generate a side chain that contains a block with a contextual validation
// error.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
g.SetTip("b3")
g.NextBlock("b4b", outs[3], ticketOuts[3])
g.NextBlock("b5b", outs[4], ticketOuts[4], func(b *wire.MsgBlock) {
// Corrupt the referenced ticket so the block is invalid due to an
// unavailable ticket.
b.STransactions[1].TxIn[1].PreviousOutPoint.Hash[0] ^= 0x55
})
g.NextBlock("b6b", outs[5], ticketOuts[5])
g.NextBlock("b7b", outs[6], ticketOuts[6])
g.NextBlock("b8b", outs[7], ticketOuts[7])
// Generate a side chain with a single block further back in history.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c
g.SetTip("b3")
g.NextBlock("b4c", outs[3], ticketOuts[3])
// Generate a side chain that itself forks from a side chain.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c \-> b8c -> b9c
g.SetTip("b7b")
g.NextBlock("b8c", outs[7], ticketOuts[7])
g.NextBlock("b9c", outs[8], ticketOuts[8])
// Generate a side chain that itself forks from the same side chain as
// above, but from the prior block.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c | \-> b8c -> b9c
// \-> b7d -> b8d -> b9d
g.SetTip("b6b")
g.NextBlock("b7d", outs[6], ticketOuts[6])
g.NextBlock("b8d", outs[7], ticketOuts[7])
g.NextBlock("b9d", outs[8], ticketOuts[8])
// Generate another side chain that itself forks from the same side chain
// and block as the above.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c | \-> b8c -> b9c
// \-> b7d -> b8d -> b9d
// \-> b7e -> b8e
g.SetTip("b6b")
g.NextBlock("b7e", outs[6], ticketOuts[6])
g.NextBlock("b8e", outs[7], ticketOuts[7])
// Generate a valid competing side chain that is a single block behind the
// best tip in terms of cumulative work.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
g.SetTip("b3")
g.NextBlock("b4f", outs[3], ticketOuts[3])
g.NextBlock("b5f", outs[4], ticketOuts[4])
g.NextBlock("b6f", outs[5], ticketOuts[5])
g.NextBlock("b7f", outs[6], ticketOuts[6])
g.NextBlock("b8f", outs[7], ticketOuts[7])
g.NextBlock("b9f", outs[8], ticketOuts[8])
g.NextBlock("b10f", outs[9], ticketOuts[9])
// Generate a side chain that contains an invalid block nested a few blocks
// deep where that block is invalid due to a contextual validation error.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4g -> b5g -> b6g! -> b7g -> b8g
g.SetTip("b3")
g.NextBlock("b4g", outs[3], ticketOuts[3])
g.NextBlock("b5g", outs[4], ticketOuts[4])
g.NextBlock("b6g", outs[5], ticketOuts[5], func(b *wire.MsgBlock) {
// Corrupt the referenced ticket so the block is invalid due to an
// unavailable ticket.
b.STransactions[1].TxIn[1].PreviousOutPoint.Hash[0] ^= 0x55
})
g.NextBlock("b7g", outs[6], ticketOuts[6])
g.NextBlock("b8g", outs[7], ticketOuts[7])
// Generate a side chain that contains an invalid block nested a couple of
// blocks deep where that block is invalid due to a double spend of an
// output spent by one of its ancestors.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4h -> b5h@ -> b6h -> b7h
g.SetTip("b3")
g.NextBlock("b4h", outs[3], ticketOuts[3])
g.NextBlock("b5h", outs[2], ticketOuts[4]) // Double spend
g.NextBlock("b6h", outs[5], ticketOuts[5])
g.NextBlock("b7h", outs[6], ticketOuts[6])
// Generate a side chain that contains an invalid block nested a few blocks
// deep where that block is invalid due to a double spend of an output spent
// by one of its ancestors.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4i -> b5i -> b6i@ -> b7i -> b8i
g.SetTip("b3")
g.NextBlock("b4i", outs[3], ticketOuts[3])
g.NextBlock("b5i", outs[4], ticketOuts[4])
g.NextBlock("b6i", outs[2], ticketOuts[5]) // Double spend
g.NextBlock("b7i", outs[6], ticketOuts[6])
g.NextBlock("b8i", outs[7], ticketOuts[7])
// Generate a side chain that contains an invalid block nested several
// blocks deep and also serves as its tip where that block is invalid due to
// a double spend of an output spent by one of its ancestors.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4j -> b5j -> b6j -> b7j@
g.SetTip("b3")
g.NextBlock("b4j", outs[3], ticketOuts[3])
g.NextBlock("b5j", outs[4], ticketOuts[4])
g.NextBlock("b6j", outs[5], ticketOuts[5])
g.NextBlock("b7j", outs[2], ticketOuts[6]) // Double spend
processTestGenerator = g.Generator
return processTestGenerator, nil
}
// TestProcessLogic ensures processing a mix of headers and blocks under a wide
// variety of fairly complex scenarios selects the expected best chain and
// properly tracks the header with the most cumulative work that is not known to
// be invalid as well as the one that is known to be invalid (when it exists).
func TestProcessLogic(t *testing.T) {
// Generate or reuse a shared chain generator with a set of blocks that form
// a fairly complex overall block tree including multiple forks such that
// some branches are valid and others contain invalid headers and/or blocks
// with multiple valid descendants as well as further forks at various
// heights from those invalid branches.
sharedGen, err := genSharedProcessTestBlocks(t)
if err != nil {
t.Fatalf("Failed to create generator: %v", err)
}
// Create a new database and chain instance to run tests against.
g, teardownFunc := newChaingenHarnessWithGen(t, "processtest", sharedGen)
defer teardownFunc()
// Shorter versions of useful params for convenience.
params := g.Params()
coinbaseMaturity := params.CoinbaseMaturity
stakeEnabledHeight := params.StakeEnabledHeight
stakeValidationHeight := params.StakeValidationHeight
// -------------------------------------------------------------------------
// Ensure the genesis block is rejected due to already being known, but its
// header is accepted since duplicate headers are ignored and it is not
// marked invalid due to the duplicate attempt.
// -------------------------------------------------------------------------
g.RejectBlock("genesis", ErrDuplicateBlock)
g.AcceptHeader("genesis")
g.ExpectBestHeader("genesis")
g.ExpectBestInvalidHeader("")
// -------------------------------------------------------------------------
// Test basic acceptance and rejection logic of valid headers of invalid
// blocks and their descendants.
// -------------------------------------------------------------------------
// Ensure that the valid header of a block that is invalid, but not yet
// known to be invalid, is accepted.
//
// genesis -> bfbbad
g.AcceptHeader("bfbbad")
// Ensure that a valid header that is the child of a known header for a
// block that is invalid, but not yet known to be invalid, is accepted.
//
// genesis -> bfbbad -> bfbbadchild
g.AcceptHeader("bfbbadchild")
// Process the invalid block and ensure it fails with the expected consensus
// violation error. Since the header was already previously accepted, it
// will be marked invalid.
//
// genesis
// \-> bfbbad
g.RejectBlock("bfbbad", ErrBadCoinbaseValue)
g.ExpectBestInvalidHeader("bfbbadchild")
// Ensure that the header for a known invalid block is rejected.
//
// genesis
// \-> bfbbad
g.RejectHeader("bfbbad", ErrKnownInvalidBlock)
// Ensure that a valid header that is already known and is the child of a
// another known header for a block that is known invalid is rejected due to
// a known invalid ancestor.
//
// genesis
// \-> bfbbad
// \-> bfbbadchild (already known invalid)
g.RejectHeader("bfbbadchild", ErrInvalidAncestorBlock)
// Ensure that a valid header that is NOT already known and is the child of
// a known header for a block that is known invalid is rejected due to a
// known invalid ancestor.
//
// genesis
// \-> bfbbad
// \-> bfbbadchilda (NOT already known)
g.RejectHeader("bfbbadchilda", ErrInvalidAncestorBlock)
g.ExpectBestInvalidHeader("bfbbadchild")
// -------------------------------------------------------------------------
// Ensure that all headers in the test chain through stake validation height
// and the base maturity blocks are accepted in order.
//
// genesis -> bfb -> bm0 -> ... -> bm# -> bse0 -> ... -> bse# -> ...
//
// ... bsv0 -> ... -> bsv# -> bbm0 -> ... -> bbm#
// -------------------------------------------------------------------------
g.AcceptHeader("bfb")
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bm%d", i)
g.AcceptHeader(blockName)
}
tipHeight := int64(coinbaseMaturity) + 1
for i := int64(0); tipHeight < stakeEnabledHeight; i++ {
blockName := fmt.Sprintf("bse%d", i)
g.AcceptHeader(blockName)
tipHeight++
}
for i := int64(0); tipHeight < stakeValidationHeight; i++ {
blockName := fmt.Sprintf("bsv%d", i)
g.AcceptHeader(blockName)
tipHeight++
}
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bbm%d", i)
g.AcceptHeader(blockName)
}
// -------------------------------------------------------------------------
// Ensure that a header that has a context-free (aka sanity) failure is
// rejected as expected. Since invalid headers are not added to the block
// index, attempting to process it again is expected to return the specific
// failure reason as opposed to a known invalid block error.
//
// ... -> bbm#
// \-> b1badhdr
// -------------------------------------------------------------------------
g.RejectHeader("b1badhdr", ErrNotEnoughVotes)
g.RejectHeader("b1badhdr", ErrNotEnoughVotes)
g.ExpectBestInvalidHeader("bfbbadchild")
// -------------------------------------------------------------------------
// Ensure that a header that has a positional failure is rejected as
// expected. Since invalid headers are not added to the block index,
// attempting to process it again is expected to return the specific failure
// reason as opposed to a known invalid block error.
//
// ... -> bbm#
// \-> b1badhdra
// -------------------------------------------------------------------------
g.RejectHeader("b1badhdra", ErrBadBlockHeight)
g.RejectHeader("b1badhdra", ErrBadBlockHeight)
g.ExpectBestInvalidHeader("bfbbadchild")
// -------------------------------------------------------------------------
// Ensure both headers and blocks whose parent header is not known are
// rejected as expected.
//
// ... -> bbm# -> b1 (neither header nor block data known to chain)
// \-> b2
// -------------------------------------------------------------------------
g.RejectHeader("b2", ErrMissingParent)
g.RejectBlock("b2", ErrMissingParent)
g.ExpectBestInvalidHeader("bfbbadchild")
// -------------------------------------------------------------------------
// Ensure that the first few headers in the test chain that build on the
// final base maturity block are accepted in order.
//
// ... -> bbm# -> b1 -> b2 -> b3
// -------------------------------------------------------------------------
g.AcceptHeader("b1")
g.AcceptHeader("b2")
g.AcceptHeader("b3")
g.ExpectBestHeader("b3")
// -------------------------------------------------------------------------
// Ensure that headers that form a block index with a bunch of branches from
// different fork points are accepted and result in the one with the most
// cumulative work being identified as the best header that is not already
// known to be invalid.
//
// Note that the ! below indicates a block that is invalid due to violating
// a consensus rule, but has a valid header.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c | \-> b8c -> b9c
// \-> b7d -> b8d -> b9d
// \-> b7e
// -------------------------------------------------------------------------
g.AcceptHeader("b4")
g.AcceptHeader("b4b")
g.AcceptHeader("b4c")
g.AcceptHeader("b5")
g.AcceptHeader("b5b") // Invalid block, but header valid.
g.AcceptHeader("b6")
g.AcceptHeader("b6b")
g.AcceptHeader("b7")
g.AcceptHeader("b7b")
g.AcceptHeader("b7d")
g.AcceptHeader("b7e")
g.AcceptHeader("b8")
g.AcceptHeader("b8b")
g.AcceptHeader("b8c")
g.AcceptHeader("b8d")
g.AcceptHeader("b9")
g.AcceptHeader("b9c")
g.AcceptHeader("b9d")
g.AcceptHeader("b10")
g.AcceptHeader("b10a")
g.AcceptHeader("b11")
g.ExpectBestHeader("b11")
// Even though ultimately one of the tips of the branches that have the same
// cumulative work and are descendants of b5b will become the best known
// invalid header, the block data for b5b is not yet known and thus it is
// not yet known to be invalid.
g.ExpectBestInvalidHeader("bfbbadchild")
// -------------------------------------------------------------------------
// Process all of the block data in the test chain through stake validation
// height and the base maturity blocks to reach the point where the more
// complicated branching structure starts.
//
// All of the headers for these blocks are already known, so this also
// exercises in order processing as compared to the later tests which deal
// with out of order processing.
//
// Also ensure duplicate blocks are rejected.
//
// ... -> bfb -> bm0 -> ... -> bm# -> bse0 -> ... -> bse# -> ...
//
// ... bsv0 -> ... -> bsv# -> bbm0 -> ... -> bbm#
// -------------------------------------------------------------------------
g.AcceptBlock("bfb")
g.RejectBlock("bfb", ErrDuplicateBlock)
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bm%d", i)
g.AcceptBlock(blockName)
}
tipHeight = int64(coinbaseMaturity) + 1
for i := int64(0); tipHeight < stakeEnabledHeight; i++ {
blockName := fmt.Sprintf("bse%d", i)
g.AcceptBlock(blockName)
tipHeight++
}
for i := int64(0); tipHeight < stakeValidationHeight; i++ {
blockName := fmt.Sprintf("bsv%d", i)
g.AcceptBlock(blockName)
tipHeight++
}
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bbm%d", i)
g.AcceptBlock(blockName)
}
// -------------------------------------------------------------------------
// Ensure that a block that has a context-free (aka sanity) failure is
// rejected as expected.
//
// Since invalid blocks that have not had their headers added separately are
// not added to the block index, attempting to process it again must return
// the specific failure reason as opposed to a known invalid block error.
//
// This also means that adding the header of a block that has been rejected
// due to a sanity error will succeed because the original failure is not
// tracked. Thus, ensure that is the case and that processing the bad block
// again fails as expected and is marked as failed such that future attempts
// to either add the header or the block will fail due to being a known
// invalid block.
//
// ... -> bbm#
// \-> b1bad
// -------------------------------------------------------------------------
g.RejectBlock("b1bad", ErrNotEnoughStake)
g.RejectBlock("b1bad", ErrNotEnoughStake)
g.ExpectBestInvalidHeader("bfbbadchild")
g.AcceptHeader("b1bad")
g.RejectBlock("b1bad", ErrNotEnoughStake)
g.ExpectBestInvalidHeader("b1bad")
g.RejectHeader("b1bad", ErrKnownInvalidBlock)
g.RejectBlock("b1bad", ErrKnownInvalidBlock)
// -------------------------------------------------------------------------
// Ensure that a block that has a positional failure is rejected as
// expected. Since the header is valid and the block sanity checks pass,
// the header will be added to the index and then end up marked as invalid,
// so attempting to process the block again is expected to return a known
// invalid block error.
//
// ... -> bbm#
// \-> b1bada
// -------------------------------------------------------------------------
g.RejectBlock("b1bada", ErrExpiredTx)
g.RejectBlock("b1bada", ErrKnownInvalidBlock)
// Since both b1bad and b1bada have the same work and the block data is
// rejected for both, whichever has the lowest hash (when treated as a
// little-endian uint256) should be considered the best invalid.
chooseLowestHash := func(blockNames ...string) string {
lowestBlockName := blockNames[0]
lowestHash := g.BlockByName(lowestBlockName).Header.BlockHash()
for _, blockName := range blockNames[1:] {
hash := g.BlockByName(blockName).Header.BlockHash()
if compareHashesAsUint256LE(&hash, &lowestHash) < 0 {
lowestHash = hash
lowestBlockName = blockName
}
}
return lowestBlockName
}
g.ExpectBestInvalidHeader(chooseLowestHash("b1bad", "b1bada"))
// -------------------------------------------------------------------------
// Ensure that processing block data for known headers out of order,
// including one on a side chain, end up with the expected block becoming
// the best chain tip.
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5
// \-> b4b
// -------------------------------------------------------------------------
g.AcceptBlockData("b2")
g.AcceptBlockData("b4b")
g.AcceptBlockData("b3")
g.AcceptBlockData("b5")
g.AcceptBlockData("b4")
g.AcceptBlock("b1")
g.ExpectTip("b5")
// -------------------------------------------------------------------------
// Ensure that duplicate blocks are rejected without marking either it or
// any of its descendants invalid.
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> ... -> b10 -> b11
// \-> b4b -- ---
// ^^ ^^^
// current tip best header
// -------------------------------------------------------------------------
g.RejectBlock("b1", ErrDuplicateBlock)
g.ExpectTip("b5")
g.ExpectBestHeader("b11")
// -------------------------------------------------------------------------
// Ensure that a block on a side chain that has a contextual failure, and
// for which the header is already known, is rejected as expected. Since
// the block is then known to be invalid, also ensure that all of its
// descendant headers and blocks are subsequently rejected due to there
// being a known invalid ancestor.
//
// Note that the ! below indicates a block that is invalid due to violating
// a consensus rule during the contextual checks prior to connection.
//
// current tip best header
// vv vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// | \-> b10a
// \-> b4b -> b5b! -> b6b -> b7b -> b8b
// \-> b4c ---- | \-> b8c -> b9c
// ^^^^ \-> b7d -> b8d -> b9d
// bad block \-> b7e -> b8e
// -------------------------------------------------------------------------
// Process the bad block and ensure its header is rejected after since it is
// then a known bad block.
g.RejectBlock("b5b", ErrTicketUnavailable)
g.RejectHeader("b5b", ErrKnownInvalidBlock)
// Ensure that all of its descendant headers that were already known are
// rejected.
g.RejectHeader("b6b", ErrInvalidAncestorBlock)
g.RejectHeader("b7b", ErrInvalidAncestorBlock)
g.RejectHeader("b8b", ErrInvalidAncestorBlock)
g.RejectHeader("b8c", ErrInvalidAncestorBlock)
g.RejectHeader("b9c", ErrInvalidAncestorBlock)
g.RejectHeader("b7d", ErrInvalidAncestorBlock)
g.RejectHeader("b8d", ErrInvalidAncestorBlock)
g.RejectHeader("b9d", ErrInvalidAncestorBlock)
g.RejectHeader("b7e", ErrInvalidAncestorBlock)
// Ensure that all of its descendant blocks associated with the headers that
// were already known are rejected.
g.RejectBlock("b6b", ErrInvalidAncestorBlock)
g.RejectBlock("b7b", ErrInvalidAncestorBlock)
g.RejectBlock("b8b", ErrInvalidAncestorBlock)
g.RejectBlock("b8c", ErrInvalidAncestorBlock)
g.RejectBlock("b9c", ErrInvalidAncestorBlock)
g.RejectBlock("b7d", ErrInvalidAncestorBlock)
g.RejectBlock("b8d", ErrInvalidAncestorBlock)
g.RejectBlock("b9d", ErrInvalidAncestorBlock)
g.RejectBlock("b7e", ErrInvalidAncestorBlock)
// Ensure both the best known invalid and not known invalid headers are
// as expected.
//
// Since both b9c and b9d have the same work and the block data is rejected
// for both, whichever has the lowest hash (when treated as a little-endian
// uint256) should be considered the best invalid.
g.ExpectBestInvalidHeader(chooseLowestHash("b9c", "b9d"))
g.ExpectBestHeader("b11")
// Ensure that both a descendant header and block of the failed block that
// themselves were not already known are rejected.
g.RejectHeader("b8e", ErrInvalidAncestorBlock)
g.RejectBlock("b8e", ErrInvalidAncestorBlock)
// -------------------------------------------------------------------------
// Similar to above, but this time when the invalid block on a side chain
// leading up to a block that causes a reorg.
//
// Note that the ! below indicates a block that is invalid due to violating
// a consensus rule during the contextual checks prior to connection.
//
// current tip best header
// vv vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4g -> b5g -> b6g! -> b7g -> b8g
// ----
// ^^^^
// bad block
// -------------------------------------------------------------------------
// Accept the block data for b4g, b5g, and b7g but only the block header for
// b6g. The data for block b7g should be accepted because b6g is not yet
// known to be invalid. Notice that b8g is intentionally not processed yet.
g.AcceptBlockData("b4g")
g.AcceptBlockData("b5g")
g.AcceptHeader("b6g")
g.AcceptBlockData("b7g")
// Process the bad block and ensure its header is rejected after since it is
// then a known bad block.
g.RejectBlock("b6g", ErrTicketUnavailable)
g.RejectHeader("b6g", ErrKnownInvalidBlock)
// Ensure that all of its descendant headers and blocks that were already
// known are rejected.
g.RejectBlock("b6g", ErrDuplicateBlock)
g.RejectBlock("b7g", ErrDuplicateBlock)
// Ensure that both a descendant header and block of the failed block that
// themselves were not already known are rejected.
g.RejectHeader("b8g", ErrInvalidAncestorBlock)
g.RejectBlock("b8g", ErrInvalidAncestorBlock)
// Ensure both the best known invalid and not known invalid headers are
// unchanged.
g.ExpectBestInvalidHeader(chooseLowestHash("b9c", "b9d"))
g.ExpectBestHeader("b11")
// -------------------------------------------------------------------------
// Similar to above, but this time with a connect failure instead of a
// contextual one on a side chain leading up to a block that causes a reorg.
//
// Note that the @ below indicates a block that is invalid due to violating
// a consensus rule during block connection.
//
// current tip best header
// vv vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4h -> b5h@ -> b6h -> b7h
// ----
// ^^^^
// bad block
// -------------------------------------------------------------------------
// Accept the block data for b4h and b6h, but only the block header for b5h.
// The data for block b6h should be accepted because b5h is not yet known to
// be invalid.
g.AcceptBlockData("b4h")
g.AcceptHeader("b5h")
g.AcceptBlockData("b6h")
// Process the bad block and ensure its header is rejected after since it is
// then a known bad block.
g.RejectBlock("b5h", ErrMissingTxOut)
g.RejectHeader("b5h", ErrKnownInvalidBlock)
// Ensure that all of its descendant headers and blocks that were already
// known are rejected.
g.RejectHeader("b6h", ErrInvalidAncestorBlock)
g.RejectHeader("b7h", ErrInvalidAncestorBlock)
g.RejectBlock("b6h", ErrDuplicateBlock)
g.RejectBlock("b7h", ErrInvalidAncestorBlock)
// Ensure both the best known invalid and not known invalid headers are
// unchanged.
g.ExpectBestInvalidHeader(chooseLowestHash("b9c", "b9d"))
g.ExpectBestHeader("b11")
// -------------------------------------------------------------------------
// Similar to above, but this time with a connect failure in a side chain
// block that is the direct target of a reorg. This also doubles to ensure
// that blocks that are seen first take precedence in best chain selection.
//
// Note that the @ below indicates a block that is invalid due to violating
// a consensus rule during block connection.
//
// current tip best header
// vv vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4i -> b5i -> b6i@ -> b7i -> b8i
// ----
// ^^^^
// bad block
// -------------------------------------------------------------------------
// Accept the block data for b4i and b5i, but only the block header for b6i.
// The header for block b6i should be accepted since the block is not yet
// known to be invalid.
g.AcceptBlockData("b4i")
g.AcceptBlockData("b5i")
g.AcceptHeader("b6i")
// Process the bad block and ensure its header is rejected after since it is
// then a known bad block. The chain should reorg back to b5 since it was
// seen first.
g.RejectBlock("b6i", ErrMissingTxOut)
g.RejectHeader("b6i", ErrKnownInvalidBlock)
g.ExpectTip("b5")
// Ensure that all of its descendant headers and blocks that were already
// known are rejected.
g.RejectHeader("b7i", ErrInvalidAncestorBlock)
g.RejectBlock("b7i", ErrInvalidAncestorBlock)
// Ensure that both a descendant header and block of the failed block that
// themselves were not already known are rejected. Notice that the header
// for b7i was never accepted, so these should fail due to a missing parent.
g.RejectHeader("b8i", ErrMissingParent)
g.RejectBlock("b8i", ErrMissingParent)
// Ensure both the best known invalid and not known invalid headers are
// unchanged.
g.ExpectBestInvalidHeader(chooseLowestHash("b9c", "b9d"))
g.ExpectBestHeader("b11")
// -------------------------------------------------------------------------
// Ensure attempting to reorg to an invalid block on a side chain where the
// blocks leading up to it are valid and have more cumulative work than the
// current tip. The chain should reorg to the final valid block since it
// has more cumulative proof of work.
//
// Note that the @ below indicates a block that is invalid due to violating
// a consensus rule during block connection.
//
// current tip best header
// vv vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4j -> b5j -> b6j -> b7j@
// ----
// ^^^^
// bad block
// -------------------------------------------------------------------------
// Accept the block data for b4j, b5j, and b7j, but only the block header
// for b6j. The block data for b7j should be accepted at this point even
// though it is invalid because it can't be checked yet since the data for
// b6j is not available.
g.ExpectTip("b5")
g.AcceptBlockData("b4j")
g.AcceptBlockData("b5j")
g.AcceptHeader("b6j")
g.AcceptBlockData("b7j")
// Make the block data for b6j available in order to complete the link
// needed to trigger the reorg and check the blocks. Since errors in reorgs
// are currently attributed to the block that caused them, the error in b7j
// should be attributed to b6j, but b6j should still end up as the tip since
// it is valid.
g.RejectBlock("b6j", ErrMissingTxOut)
g.ExpectTip("b6j")
// Ensure both the best known invalid and not known invalid headers are
// unchanged.
g.ExpectBestInvalidHeader(chooseLowestHash("b9c", "b9d"))
g.ExpectBestHeader("b11")
// -------------------------------------------------------------------------
// Ensure reorganizing to another valid branch when the block data is
// processed out of order works as intended.
//
// best header
// vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// \-> b4j -> b5j -> b6j -> b7j@
// ---
// ^^^
// current tip
// -------------------------------------------------------------------------
g.AcceptHeader("b4f")
g.AcceptHeader("b5f")
g.AcceptHeader("b6f")
g.AcceptHeader("b7f")
g.AcceptHeader("b8f")
g.AcceptHeader("b9f")
g.AcceptHeader("b10f")
g.ExpectBestHeader("b11")
// Reorganize to a branch that actually has less work than the full initial
// branch, but since its block data is not yet known, the secondary branch
// data becoming available forces a reorg since it will be the best known
// branch.
//
// best header
// vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// \-> b4j -> b5j -> b6j ^^^^
// --- ----
// ^^^ new tip
// orig tip
g.AcceptBlockDataWithExpectedTip("b9f", "b6j")
g.AcceptBlockDataWithExpectedTip("b7f", "b6j")
g.AcceptBlockDataWithExpectedTip("b8f", "b6j")
g.AcceptBlockDataWithExpectedTip("b10f", "b6j")
g.AcceptBlockDataWithExpectedTip("b6f", "b6j")
g.AcceptBlockDataWithExpectedTip("b4f", "b6j")
g.AcceptBlockDataWithExpectedTip("b5f", "b10f")
// Make the data for the initial branch available, again in an out of order
// fashion, but with an additional test condition built in such that all of
// the data up to the point the initial branch has the exact same work as
// the active branch is available to ensure the first seen block data takes
// precedence despite the fact the header for the initial branch was seen
// first.
//
// best header
// vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ^^^^
// ----
// current tip
g.AcceptBlockDataWithExpectedTip("b8", "b10f")
g.AcceptBlockDataWithExpectedTip("b10", "b10f")
g.AcceptBlockDataWithExpectedTip("b7", "b10f")
g.AcceptBlockDataWithExpectedTip("b9", "b10f")
g.AcceptBlockDataWithExpectedTip("b6", "b10f")
g.ExpectIsCurrent(false)
// Finally, accept the data for final block on the initial branch that
// causes the reorg and ensure the chain latches to believe it is current.
//
// new tip
// vvv
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ^^^^
// ----
// orig tip
g.AcceptBlockDataWithExpectedTip("b11", "b11")
g.ExpectIsCurrent(true)
}
// TestInvalidateReconsider ensures that manually invalidating blocks and
// reconsidering them works as expected under a wide variety of scenarios.
func TestInvalidateReconsider(t *testing.T) {
// Generate or reuse a shared chain generator with a set of blocks that form
// a fairly complex overall block tree including multiple forks such that
// some branches are valid and others contain invalid headers and/or blocks
// with multiple valid descendants as well as further forks at various
// heights from those invalid branches.
sharedGen, err := genSharedProcessTestBlocks(t)
if err != nil {
t.Fatalf("Failed to create generator: %v", err)
}
// Create a new database and chain instance to run tests against.
g, teardownFunc := newChaingenHarnessWithGen(t, "invalidatetest", sharedGen)
defer teardownFunc()
// Shorter versions of useful params for convenience.
params := g.Params()
coinbaseMaturity := params.CoinbaseMaturity
stakeEnabledHeight := params.StakeEnabledHeight
stakeValidationHeight := params.StakeValidationHeight
// -------------------------------------------------------------------------
// Accept all headers in the initial test chain through stake validation
// height and the base maturity blocks.
//
// genesis -> bfb -> bm0 -> ... -> bm# -> bse0 -> ... -> bse# -> ...
//
// ... bsv0 -> ... -> bsv# -> bbm0 -> ... -> bbm#
// -------------------------------------------------------------------------
g.AcceptHeader("bfb")
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bm%d", i)
g.AcceptHeader(blockName)
}
tipHeight := int64(coinbaseMaturity) + 1
for i := int64(0); tipHeight < stakeEnabledHeight; i++ {
blockName := fmt.Sprintf("bse%d", i)
g.AcceptHeader(blockName)
tipHeight++
}
for i := int64(0); tipHeight < stakeValidationHeight; i++ {
blockName := fmt.Sprintf("bsv%d", i)
g.AcceptHeader(blockName)
tipHeight++
}
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bbm%d", i)
g.AcceptHeader(blockName)
}
// -------------------------------------------------------------------------
// Initial setup of headers and block data needed for the invalidate and
// reconsider tests below.
// -------------------------------------------------------------------------
// Accept block headers for the following tree structure:
//
// Note that the ! below indicates a block that is invalid due to violating
// a consensus rule during the contextual checks prior to connection and the
// @ indicates a block that is invalid due to violating a consensus rule
// during block connection.
//
// ... -> b1 -> b2 -> ...
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4b -> b5b! -> b6b -> b7b -> b8b \-> b10a
// \-> b4c | \-> b8c -> b9c
// | \-> b7d -> b8d
// | \-> b7e -> b8e
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// \-> b4i -> b5i -> b6i@
//
g.AcceptHeader("b1")
g.AcceptHeader("b2")
g.AcceptHeader("b3")
g.AcceptHeader("b4")
g.AcceptHeader("b4b")
g.AcceptHeader("b4c")
g.AcceptHeader("b4f")
g.AcceptHeader("b4i")
g.AcceptHeader("b5")
g.AcceptHeader("b5b") // Invalid block, but header valid.
g.AcceptHeader("b5f")
g.AcceptHeader("b5i")
g.AcceptHeader("b6")
g.AcceptHeader("b6b")
g.AcceptHeader("b6f")
g.AcceptHeader("b6i")
g.AcceptHeader("b7")
g.AcceptHeader("b7b")
g.AcceptHeader("b7d")
g.AcceptHeader("b7e")
g.AcceptHeader("b7f")
g.AcceptHeader("b8")
g.AcceptHeader("b8b")
g.AcceptHeader("b8c")
g.AcceptHeader("b8d")
g.AcceptHeader("b8e")
g.AcceptHeader("b8f")
g.AcceptHeader("b9")
g.AcceptHeader("b9c")
g.AcceptHeader("b9f")
g.AcceptHeader("b10")
g.AcceptHeader("b10a")
g.AcceptHeader("b10f")
g.AcceptHeader("b11")
g.ExpectBestHeader("b11")
// Accept all of the block data in the test chain through stake validation
// height and the base maturity blocks to reach the point where the more
// complicated branching structure starts.
//
// ... -> bfb -> bm0 -> ... -> bm# -> bse0 -> ... -> bse# -> ...
//
// ... bsv0 -> ... -> bsv# -> bbm0 -> ... -> bbm#
g.AcceptBlock("bfb")
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bm%d", i)
g.AcceptBlock(blockName)
}
tipHeight = int64(coinbaseMaturity) + 1
for i := int64(0); tipHeight < stakeEnabledHeight; i++ {
blockName := fmt.Sprintf("bse%d", i)
g.AcceptBlock(blockName)
tipHeight++
}
for i := int64(0); tipHeight < stakeValidationHeight; i++ {
blockName := fmt.Sprintf("bsv%d", i)
g.AcceptBlock(blockName)
tipHeight++
}
for i := uint16(0); i < coinbaseMaturity; i++ {
blockName := fmt.Sprintf("bbm%d", i)
g.AcceptBlock(blockName)
}
// Accept the block data for several blocks in the main branch of the test
// data.
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
g.AcceptBlock("b1")
g.AcceptBlock("b2")
g.AcceptBlock("b3")
g.AcceptBlock("b4")
g.AcceptBlock("b5")
g.AcceptBlock("b6")
g.AcceptBlock("b7")
// Accept the block data for several blocks in a branch of the test data
// that contains a bad block such that the invalid branch has more work.
// Notice that the bad block is only processed AFTER all of the data for its
// descendants is added since they would otherwise be rejected due to being
// part of a known invalid branch.
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// \-> b4b -> b5b -> b6b -> b7b -> b8b
// ---
// ^ (invalid block)
g.AcceptBlockData("b4b")
g.AcceptBlockData("b6b")
g.AcceptBlockData("b7b")
g.AcceptBlockData("b8b")
g.RejectBlock("b5b", ErrTicketUnavailable)
g.ExpectTip("b7")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Invalidating and reconsidering an unknown block must error.
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("b1bad", ErrUnknownBlock, "b7")
g.ReconsiderBlockAndExpectTip("b1bad", ErrUnknownBlock, "b7")
// -------------------------------------------------------------------------
// The genesis block is not allowed to be invalidated, but it can be
// reconsidered.
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("genesis", ErrInvalidateGenesisBlock, "b7")
g.ReconsiderBlockAndExpectTip("genesis", nil, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Invalidating a block that is already known to have failed validation has
// no effect.
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("b5b", nil, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Reconsider a block that is actually invalid when that block is located in
// a side chain that has more work than the current best chain. Since the
// block is actually invalid, that side chain should remain invalid and
// therefore NOT become the best chain even though it has more work.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (invalid block, reconsider)
//
// After reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (still invalid block)
// -------------------------------------------------------------------------
g.ReconsiderBlockAndExpectTip("b5b", ErrTicketUnavailable, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Reconsider a block that is a descendant of a block that is actually
// invalid when that block is located in a side chain that has more work
// than the current best chain. Since an ancestor block is actually
// invalid, that side chain should remain invalid and therefore NOT become
// the best chain even though it has more work.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// --- ----
// (invalid block) ^ ^ (reconsider)
//
// After reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (still invalid block)
// -------------------------------------------------------------------------
g.ReconsiderBlockAndExpectTip("b6b", ErrTicketUnavailable, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Invalidate a multi-hop descendant of a block that is actually invalid
// when that block is located in a side chain that has more work than the
// current best chain. Then, reconsider a block that is a descendant of
// the block that is actually invalid, but an ancestor of the invalidated
// multi-hop descendant.
//
// Since the part of that side chain that is then potentially valid has less
// work than the current best chain, no attempt to reorg to that side chain
// should be made. Therefore, no error is expected and the actually invalid
// block along with its descendants that are also ancestors of the multi-hop
// descendant should no longer be known to be invalid.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b* -> b8b#
// --- ---- ----
// | | ^ (marked invalid)
// (invalid block) ^ ^ (reconsider)
//
// After reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b -> b6b -> b7b* -> b8b#
// ----------- ----
// (no longer known to be invalid) ^ ^ (still marked invalid)
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("b7b", nil, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
g.ReconsiderBlockAndExpectTip("b6b", nil, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Reconsider a descendant of the manually invalidated multi-hop descendant
// from the previous test. Since the part of the side chain that is
// then potentially valid has more work than the current chain, an attempt
// to reorg to that branch should be made and fail due to the actually
// invalid ancestor on that branch.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b -> b6b -> b7b* -> b8b#
// --- ---- ----
// (invalid, but not known as such) ^ ^ ^ (reconsider)
// (marked invalid)
// After reconsider:
//
// ... -> b1 -> b2 -> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (invalid block)
// -------------------------------------------------------------------------
g.ReconsiderBlockAndExpectTip("b8b", ErrTicketUnavailable, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Invalidate and reconsider block one. After the invalidate, the genesis
// block should become the best chain tip and the best header not known to
// be invalid should become the best header known to be invalid while the
// former becomes the genesis block header.
//
// Reconsidering the block should return the chain back to the original
// state and notably should NOT have any validation errors because the
// branches with the block that is actually invalid should still be known to
// be invalid despite it being a descendant of the reconsidered block and
// therefore no attempt to reorg should happen.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before invalidate:
//
// genesis -> bfb -> ...-> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (invalid block)
//
// After invalidate:
//
// genesis -> bfb* -> ...-> b3# -> b4# -> b5# -> b6# -> b7#
// ------- |
// ^ (best chain tip) |
// \-> b4b#-> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (invalid block)
// After reconsider:
//
// genesis -> bfb -> ...-> b3 -> b4 -> b5 -> b6 -> b7
// | --
// | ^ (best chain tip)
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b#
// ---
// ^ (invalid block)
// -------------------------------------------------------------------------
// Note that the genesis block is too far in the past to be considered
// current.
g.InvalidateBlockAndExpectTip("bfb", nil, "genesis")
g.ExpectBestHeader("genesis")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(false)
g.ReconsiderBlockAndExpectTip("bfb", nil, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Reconsidering a block that is actually invalid upon connection when it is
// in a side chain that has had an ancestor manually invalidated and where
// the parent of that invalid block has more work than the current best
// chain should cause a reorganize to that parent block.
//
// In other words, ancestors of reconsidered blocks, even those that are
// actually invalid, should have their invalidity status cleared and become
// eligible for best chain selection.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider:
//
// genesis -> bfb -> ...-> b3 -> b4 -> b5* -> b6# -> b7#
// | --
// | ^ (best chain tip)
// \-> b4i -> b5i* -> b6i*
// ---
// (invalid block, reconsider) ^
//
// After reconsider:
//
// genesis -> bfb -> ...-> b3 -> b4 -> b5* -> b6# -> b7#
// \-> b4i -> b5i -> b6i*
// --- ---
// (best chain tip) ^ ^ (invalid block)
// -------------------------------------------------------------------------
// The data for b6i should be accepted because the best chain tip has more
// work at this point and thus there is no attempt to connect it.
g.AcceptBlockData("b4i")
g.AcceptBlockData("b5i")
g.AcceptBlockData("b6i")
// Invalidate the ancestor in the side chain first to ensure there is no
// reorg when the main chain is invalidated for the purposes of making it
// have less work when the side chain is reconsidered.
g.InvalidateBlockAndExpectTip("b5i", nil, "b7")
g.InvalidateBlockAndExpectTip("b5", nil, "b4")
g.ExpectBestHeader("b10f")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(false)
g.ReconsiderBlockAndExpectTip("b6i", ErrMissingTxOut, "b5i")
g.ExpectBestHeader("b10f")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Undo the main chain invalidation for tests below.
// -------------------------------------------------------------------------
g.ReconsiderBlockAndExpectTip("b5", nil, "b7")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Make the primary branch have the most cumulative work and then invalidate
// and reconsider a block that is an ancestor of the current best chain tip,
// the best header that is NOT known to be invalid, the best header that is
// known to be invalid, as well as several other branches.
//
// After the invalidation, the best invalid header should become the one
// that was previously the best NOT known to be invalid and the best chain
// tip and header should coincide which also means the chain should latch to
// current.
//
// After reconsideration, everything should revert to the previous state,
// including the chain believing it is not current again.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// After invalidate:
//
// v (best chain tip, best header)
// --
// ... -> b2 -> ...
// (best invalid)v
// ----
// ... -> b3* -> b4# -> b5# -> b6# -> b7# -> b8# -> b9# -> b10# -> b11#
// \-> b4b# -> b5b* -> b6b# -> b7b# -> b8b# \-> b10a#
// \-> b4c# | \-> b8c# -> b9c#
// | \-> b7d# -> b8d#
// | \-> b7e# -> b8e#
// \-> b4f# -> b5f# -> b6f# -> b7f# -> b8f# -> b9f# -> b10f#
// \-> b4i# -> b5i# -> b6i*
//
// After reconsider:
//
// (best chain tip) v (best header) v
// -- ---
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b# \-> b10a
// \-> b4c | \-> b8c# -> b9c#
// | | ----
// | | ^ (best invalid)
// | \-> b7d# -> b8d#
// | \-> b7e# -> b8e#
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// \-> b4i -> b5i -> b6i*
// -------------------------------------------------------------------------
g.AcceptBlock("b8")
g.AcceptBlock("b9")
g.InvalidateBlockAndExpectTip("b3", nil, "b2")
g.ExpectBestHeader("b2")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(true)
g.ReconsiderBlockAndExpectTip("b3", nil, "b9")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Invalidate a block that does not have its data available and then
// reconsider a descendant of it that itself has both ancestors and
// descendants without block data available. Then make the missing data
// available such that there is more cumulative work than the current best
// chain tip and ensure that everything is linked and the branch becomes the
// new best tip.
//
// This ensures that reconsidering blocks properly resurrects tracking of
// blocks that are not fully linked yet for both descendants and ancestors.
//
// Note that the @ below indicates data that is available before the
// invalidate and reconsider and ! indicates data that is made available
// afterwards.
//
// (orig tip) v
// --
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9
// \-> b4f! -> b5f@ -> b6f! -> b7f@ -> b8f! -> b9f@ -> b10f!
// ---- --- --- -----
// (added last) ^ (invalidate) ^ ^ (reconsider) ^ (new tip)
// -------------------------------------------------------------------------
// Notice that the data for b4f, b6f and, b8f, and b10f are intentionally
// not made available yet.
g.AcceptBlockData("b5f")
g.AcceptBlockData("b7f")
g.AcceptBlockData("b9f")
g.InvalidateBlockAndExpectTip("b6f", nil, "b9")
g.ReconsiderBlockAndExpectTip("b7f", nil, "b9")
g.ExpectIsCurrent(false)
// Add the missing data to complete the side chain while only adding b4f
// last to ensure it triggers a reorg that consists of all of the blocks
// that were previously missing data, but should now be linked.
g.AcceptBlockData("b6f")
g.AcceptBlockData("b8f")
g.AcceptBlockData("b10f")
g.AcceptBlockData("b4f")
g.ExpectTip("b10f")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Reconsider a block that is a multi-hop descendant of an invalidated chain
// and also itself has descendants such that the entire branch has more
// cumulative work than the current best chain. The entire branch,
// including the ancestors of the block, is expected to be reconsidered
// resulting in it becoming the new best chain.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider:
//
// b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9* -> b10# -> b11#
// | --
// | ^ (best chain tip)
// \-> b4f* -> b5f# -> b6f# -> b7f# -> b8f# -> b9f# -> b10f#
// ----
// ^ (reconsider)
//
// After reconsider:
//
// b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9* -> b10# -> b11#
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ----
// ^ (best chain tip)
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("b4f", nil, "b9")
g.InvalidateBlockAndExpectTip("b9", nil, "b8")
g.ExpectBestHeader("b8")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(true)
g.ReconsiderBlockAndExpectTip("b7f", nil, "b10f")
g.ExpectBestHeader("b10f")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(true)
// -------------------------------------------------------------------------
// Invalidate and reconsider the current best chain tip. The chain should
// believe it is current both before and after since the best chain tip
// matches the best header in both cases.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9* -> b10# -> b11#
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ----
// (invalidate, reconsider, best chain tip) ^
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("b10f", nil, "b9f")
g.ExpectBestHeader("b9f")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(true)
g.ReconsiderBlockAndExpectTip("b10f", nil, "b10f")
g.ExpectBestHeader("b10f")
g.ExpectBestInvalidHeader("b11")
g.ExpectIsCurrent(true)
// -------------------------------------------------------------------------
// Reconsider a chain tip for a branch that has a previously invalidated
// ancestor and that has more work than the current best tip, but does not
// have all of the block data available. All of the ancestors must no
// longer be marked invalid and any descendants on other branches from any
// of those ancestors that were reconsidered must also have their invalid
// ancestor status removed. However, any such descendants that are marked
// as having failed validation instead must NOT have that status cleared.
//
// Note that the * indicates blocks that are marked as failed validation and
// the # indicates blocks that are marked as having an invalid ancestor.
//
// Before reconsider (b10 and b11 data not available):
//
// (reconsider) v
// ---
// b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9* -> b10# -> b11#
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b# \-> b10a#
// | \-> b8c# -> b9c#
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ----
// (best chain tip) ^
//
// After reconsider (b10 and b11 data not available):
//
// b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4b -> b5b* -> b6b# -> b7b# -> b8b# \-> b10a
// | \-> b8c# -> b9c#
// | ----
// | ^ (best invalid header)
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ----
// (best chain tip) ^
// -------------------------------------------------------------------------
g.ReconsiderBlockAndExpectTip("b11", nil, "b10f")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(false)
// -------------------------------------------------------------------------
// Accept the rest of the block data to reach the current best header so
// it becomes the best chain tip and ensure the chain latches to current.
//
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// -------------------------------------------------------------------------
g.AcceptBlockData("b10")
g.AcceptBlock("b11")
g.ExpectBestHeader("b11")
g.ExpectIsCurrent(true)
// -------------------------------------------------------------------------
// Invalidate and reconsider a block at the tip of the current best chain so
// that it has the same cumulative work as another branch that received the
// block data first. The second chain must become the best one despite them
// having the exact same work and the header for the initial branch being
// seen first because branches that receive their block data first take
// precedence. The initial branch must become the best chain again after
// it is reconsidered because it has more work.
//
// The chain should believe it is current both before and after since the
// best chain tip matches the best header in both cases.
//
// (invalidate, reconsider, header seen first) v
// ---
// ... -> b3 -> b4 -> b5 -> b6 -> b7 -> b8 -> b9 -> b10 -> b11
// \-> b4f -> b5f -> b6f -> b7f -> b8f -> b9f -> b10f
// ----
// (data seen first) ^
// -------------------------------------------------------------------------
g.InvalidateBlockAndExpectTip("b11", nil, "b10f")
g.ExpectBestHeader("b10f")
g.ExpectIsCurrent(true)
g.ReconsiderBlockAndExpectTip("b11", nil, "b11")
g.ExpectBestHeader("b11")
g.ExpectBestInvalidHeader("b9c")
g.ExpectIsCurrent(true)
}