This makes most of the tests that take a while to run parallel to significantly reduce the overall testing time roughly in half. Note that this does cause the output of the various tests that log the creation of the blocks and their acceptance/rejection to be commingled, but the testing framework adds a header for which test each line comes from, so it's fairly easy to reconstruct the logging stream for any individual test in the event of any test failures.
2070 lines
85 KiB
Go
2070 lines
85 KiB
Go
// Copyright (c) 2019-2022 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"
|
|
"time"
|
|
|
|
"github.com/decred/dcrd/blockchain/v5/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) {
|
|
t.Parallel()
|
|
|
|
// Create a test harness initialized with the genesis block as the tip.
|
|
params := chaincfg.RegNetParams()
|
|
g := newChaingenHarness(t, params)
|
|
|
|
// 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", ErrNoTreasury)
|
|
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 {
|
|
processTestGeneratorLock.Lock()
|
|
defer processTestGeneratorLock.Unlock()
|
|
|
|
// Only generate the process test chain once.
|
|
if processTestGenerator != nil {
|
|
return processTestGenerator
|
|
}
|
|
|
|
// Create a new database and chain instance needed to create the generator
|
|
// populated with the desired blocks.
|
|
params := chaincfg.RegNetParams()
|
|
g := newChaingenHarness(t, params)
|
|
|
|
// 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
|
|
}
|
|
|
|
// 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 := genSharedProcessTestBlocks(t)
|
|
|
|
// Create a new database and chain instance to run tests against.
|
|
g := newChaingenHarnessWithGen(t, sharedGen)
|
|
|
|
// 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) {
|
|
t.Parallel()
|
|
|
|
// 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 := genSharedProcessTestBlocks(t)
|
|
|
|
// Create a new database and chain instance to run tests against.
|
|
g := newChaingenHarnessWithGen(t, sharedGen)
|
|
|
|
// 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)
|
|
}
|
|
|
|
// TestAssumeValid validates that it is correctly determined whether or not a
|
|
// node is an ancestor of an assumed valid node under a variety of conditions.
|
|
func TestAssumeValid(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// Clone the parameters so that they can be mutated.
|
|
params := cloneParams(quickVoteActivationParams())
|
|
stakeValidationHeight := params.StakeValidationHeight
|
|
|
|
// Set the target time per block to 1 day so that less blocks are needed to
|
|
// test against a 2 week threshold.
|
|
params.TargetTimePerBlock = time.Hour * 24
|
|
|
|
// Create a test harness initialized with the genesis block as the tip.
|
|
g := newChaingenHarness(t, params)
|
|
|
|
// Calculate the expected number of blocks in 2 weeks.
|
|
const timeInTwoWeeks = time.Hour * 24 * 14
|
|
targetTimePerBlock := params.TargetTimePerBlock
|
|
expectedBlocksInTwoWeeks := int64(timeInTwoWeeks / targetTimePerBlock)
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate and accept enough blocks to reach stake validation height.
|
|
// ---------------------------------------------------------------------
|
|
|
|
g.AdvanceToStakeValidationHeight()
|
|
g.AssertTipHeight(uint32(stakeValidationHeight))
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate additional blocks until there is 1 less than the number of
|
|
// blocks expected in 2 weeks.
|
|
// ---------------------------------------------------------------------
|
|
|
|
numBlocksToGen := expectedBlocksInTwoWeeks - stakeValidationHeight - 1
|
|
i := int64(0)
|
|
for ; i < numBlocksToGen; i++ {
|
|
outs := g.OldestCoinbaseOuts()
|
|
blockName := fmt.Sprintf("bbav%d", i)
|
|
g.NextBlock(blockName, nil, outs[1:])
|
|
g.SaveTipCoinbaseOuts()
|
|
g.AcceptTipBlock()
|
|
}
|
|
g.AssertTipHeight(uint32(expectedBlocksInTwoWeeks - 1))
|
|
|
|
// Set the assumed valid node to the tip.
|
|
tipHash := &g.chain.BestSnapshot().Hash
|
|
assumeValidNode := g.chain.index.LookupNode(tipHash)
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns false for this scenario:
|
|
// - The tip height is less than the number of blocks expected in 2 weeks
|
|
node := assumeValidNode.parent
|
|
if g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return false since the " +
|
|
"tip height is less than the number of blocks expected in 2 weeks")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate an additional block so that there is exactly the number of
|
|
// blocks expected in 2 weeks.
|
|
// ---------------------------------------------------------------------
|
|
|
|
outs := g.OldestCoinbaseOuts()
|
|
blockName := fmt.Sprintf("bbav%d", i)
|
|
g.NextBlock(blockName, nil, outs[1:])
|
|
g.SaveTipCoinbaseOuts()
|
|
g.AcceptTipBlock()
|
|
i++
|
|
g.AssertTipHeight(uint32(expectedBlocksInTwoWeeks))
|
|
|
|
// Set the assumed valid node to the tip.
|
|
tipHash = &g.chain.BestSnapshot().Hash
|
|
assumeValidNode = g.chain.index.LookupNode(tipHash)
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns false for this scenario:
|
|
// - The assumed valid node is clamped back to be 2 weeks behind the tip
|
|
// - The node that is only 1 block behind the assumed valid node is not an
|
|
// ancestor of the clamped back assumed valid node
|
|
node = assumeValidNode.parent
|
|
if g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return false since the " +
|
|
"node is not an ancestor of the clamped back assumed valid node")
|
|
}
|
|
|
|
// ---------------------------------------------------------------------
|
|
// Generate an additional 2 weeks worth of blocks + 2. In addition to
|
|
// the main chain, generate a side chain of the same length.
|
|
// ---------------------------------------------------------------------
|
|
bestSideBlockName := g.TipName()
|
|
numBlocksToGen = i + expectedBlocksInTwoWeeks + 2
|
|
for ; i < numBlocksToGen; i++ {
|
|
outs := g.OldestCoinbaseOuts()
|
|
blockName := fmt.Sprintf("bbav%d", i)
|
|
g.NextBlock(blockName, nil, outs[1:])
|
|
g.SaveTipCoinbaseOuts()
|
|
g.AcceptTipBlock()
|
|
tipName := g.TipName()
|
|
|
|
g.SetTip(bestSideBlockName)
|
|
bestSideBlockName = fmt.Sprintf("bbava%d", i)
|
|
g.NextBlock(bestSideBlockName, nil, outs[1:])
|
|
g.AcceptedToSideChainWithExpectedTip(tipName)
|
|
g.SetTip(tipName)
|
|
}
|
|
g.AssertTipHeight(2*uint32(expectedBlocksInTwoWeeks) + 2)
|
|
|
|
// Set the assumed valid node to 2 weeks - 1 block behind the best side
|
|
// chain block.
|
|
sideBlockHash := g.BlockByName(bestSideBlockName).BlockHash()
|
|
sideNode := g.chain.index.LookupNode(&sideBlockHash)
|
|
assumeValidNode = sideNode.RelativeAncestor(expectedBlocksInTwoWeeks - 1)
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns false for this scenario:
|
|
// - The node is not an ancestor of the best header
|
|
if g.chain.isAssumeValidAncestor(assumeValidNode) {
|
|
t.Fatal("expected isAssumeValidAncestor to return false when the " +
|
|
"node is not an ancestor of the best header")
|
|
}
|
|
|
|
// Validate that isAssumeValidAncestor returns false for this scenario:
|
|
// - The node is an ancestor of the best header, and is at a height below
|
|
// the clamped back height, but is not an ancestor of the assumed valid
|
|
// block
|
|
tipHash = &g.chain.BestSnapshot().Hash
|
|
tip := g.chain.index.LookupNode(tipHash)
|
|
node = tip.RelativeAncestor(expectedBlocksInTwoWeeks + 1)
|
|
if g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return false when the " +
|
|
"node is not an ancestor of the assumed valid block")
|
|
}
|
|
|
|
// Set the assumed valid node to 2 weeks - 1 block behind the tip
|
|
assumeValidNode = tip.RelativeAncestor(expectedBlocksInTwoWeeks - 1)
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns false for this scenario:
|
|
// - The assumed valid node is 2 weeks - 1 block behind the tip
|
|
// - The assumed valid node is not an ancestor of the clamped back assumed
|
|
// valid node
|
|
if g.chain.isAssumeValidAncestor(assumeValidNode) {
|
|
t.Fatal("expected isAssumeValidAncestor to return false when the " +
|
|
"assumed valid node is 2 weeks - 1 block behind the tip")
|
|
}
|
|
|
|
// Validate that isAssumeValidAncestor returns true for this scenario:
|
|
// - The assumed valid node is 2 weeks - 1 block behind the tip
|
|
// - The node that is 1 block behind the assumed valid node is an ancestor
|
|
// of the clamped back assumed valid node
|
|
node = assumeValidNode.parent
|
|
if !g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return true when the " +
|
|
"assumed valid node is 2 weeks - 1 block behind the tip and the " +
|
|
"node is an ancestor of the clamped back assumed valid node")
|
|
}
|
|
|
|
// Set the assumed valid node to its parent so that it is exactly 2 weeks
|
|
// behind the tip.
|
|
assumeValidNode = assumeValidNode.parent
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns true for this scenario:
|
|
// - The assumed valid node is exactly 2 weeks behind the tip
|
|
node = assumeValidNode.parent
|
|
if !g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return true when the " +
|
|
"assumed valid node is exactly 2 weeks behind the tip")
|
|
}
|
|
|
|
// Set the assumed valid node to its parent so that it is 2 weeks + 1 block
|
|
// behind the tip.
|
|
assumeValidNode = assumeValidNode.parent
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns true for this scenario:
|
|
// - The assumed valid node is 2 weeks + 1 block behind the tip
|
|
node = assumeValidNode.parent
|
|
if !g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return true when the " +
|
|
"assumed valid node is 2 weeks + 1 block behind the tip")
|
|
}
|
|
|
|
// Set the assumed valid node to nil.
|
|
assumeValidNode = nil
|
|
g.chain.assumeValidNode = assumeValidNode
|
|
|
|
// Validate that isAssumeValidAncestor returns false for this scenario:
|
|
// - The assumed valid node is nil
|
|
if g.chain.isAssumeValidAncestor(node) {
|
|
t.Fatal("expected isAssumeValidAncestor to return false when the " +
|
|
"assumed valid node is nil")
|
|
}
|
|
}
|