// 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) }