Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

blockchain: Add negative tests for forced reorg. #1341

Merged
merged 1 commit into from
Jul 10, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 201 additions & 7 deletions blockchain/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ func TestForceHeadReorg(t *testing.T) {
//
// accepted expects the block to be accepted to the main chain.
//
// rejected expects the block to be rejected with the provided error
// code.
//
// expectTip expects the provided block to be the current tip of the
// main chain.
//
Expand All @@ -154,6 +157,10 @@ func TestForceHeadReorg(t *testing.T) {
// of the main chain from the given block to the given block. An error
// will result if the provided from block is not actually the current
// tip.
//
// rejectForceTipReorg forces the chain instance to reorganize the
// current tip of the main chain from the given block to the given
// block and expected it to be rejected with the provided error code.
accepted := func() {
msgBlock := g.Tip()
blockHeight := msgBlock.Header.Height
Expand Down Expand Up @@ -182,6 +189,36 @@ func TestForceHeadReorg(t *testing.T) {
block.Hash(), blockHeight, isOrphan)
}
}
rejected := func(code ErrorCode) {
msgBlock := g.Tip()
blockHeight := msgBlock.Header.Height
block := dcrutil.NewBlock(msgBlock)
t.Logf("Testing block %s (hash %s, height %d)", g.TipName(),
block.Hash(), blockHeight)

_, _, err := chain.ProcessBlock(block, BFNone)
if err == nil {
t.Fatalf("block %q (hash %s, height %d) should not "+
"have been accepted", g.TipName(), block.Hash(),
blockHeight)
}

// Ensure the error code is of the expected type and the reject
// code matches the value specified in the test instance.
rerr, ok := err.(RuleError)
if !ok {
t.Fatalf("block %q (hash %s, height %d) returned "+
"unexpected error type -- got %T, want "+
"blockchain.RuleError", g.TipName(),
block.Hash(), blockHeight, err)
}
if rerr.ErrorCode != code {
t.Fatalf("block %q (hash %s, height %d) does not have "+
"expected reject code -- got %v, want %v",
g.TipName(), block.Hash(), blockHeight,
rerr.ErrorCode, code)
}
}
expectTip := func(tipName string) {
// Ensure hash and height match.
wantTip := g.BlockByName(tipName)
Expand Down Expand Up @@ -227,6 +264,11 @@ func TestForceHeadReorg(t *testing.T) {
forceTipReorg := func(fromTipName, toTipName string) {
from := g.BlockByName(fromTipName)
to := g.BlockByName(toTipName)
t.Logf("Testing forced reorg from %s (hash %s, height %d) "+
"to %s (hash %s, height %d)", fromTipName,
from.BlockHash(), from.Header.Height, toTipName,
to.BlockHash(), to.Header.Height)

err = chain.ForceHeadReorganization(from.BlockHash(), to.BlockHash())
if err != nil {
t.Fatalf("failed to force header reorg from block %q "+
Expand All @@ -236,6 +278,44 @@ func TestForceHeadReorg(t *testing.T) {
to.Header.Height, err)
}
}
rejectForceTipReorg := func(fromTipName, toTipName string, code ErrorCode) {
from := g.BlockByName(fromTipName)
to := g.BlockByName(toTipName)
t.Logf("Testing forced reorg from %s (hash %s, height %d) "+
"to %s (hash %s, height %d)", fromTipName,
from.BlockHash(), from.Header.Height, toTipName,
to.BlockHash(), to.Header.Height)

err = chain.ForceHeadReorganization(from.BlockHash(), to.BlockHash())
if err == nil {
t.Fatalf("forced header reorg from block %q (hash %s, "+
"height %d) to block %q (hash %s, height %d) "+
"should have failed", fromTipName, from.BlockHash(),
from.Header.Height, toTipName, to.BlockHash(),
to.Header.Height)
}

// Ensure the error code is of the expected type and the reject
// code matches the value specified in the test instance.
rerr, ok := err.(RuleError)
if !ok {
t.Fatalf("forced header reorg from block %q (hash %s, "+
"height %d) to block %q (hash %s, height %d) "+
"returned unexpected error type -- got %T, "+
"want blockchain.RuleError", fromTipName,
from.BlockHash(), from.Header.Height, toTipName,
to.BlockHash(), to.Header.Height, err)
}
if rerr.ErrorCode != code {
t.Fatalf("forced header reorg from block %q (hash %s, "+
"height %d) to block %q (hash %s, height %d) "+
"does not have expected reject code -- got %v, "+
"want %v", fromTipName,
from.BlockHash(), from.Header.Height, toTipName,
to.BlockHash(), to.Header.Height, rerr.ErrorCode,
code)
}
}

// Shorter versions of useful params for convenience.
ticketsPerBlock := params.TicketsPerBlock
Expand Down Expand Up @@ -350,23 +430,53 @@ func TestForceHeadReorg(t *testing.T) {
// Forced header reorganization test.
// ---------------------------------------------------------------------

// Start by building a couple of blocks at current tip (value in parens
// is which output is spent):
// Start by building a block at current tip (value in parens is which
// output is spent):
//
// ... -> b1(0) -> b2(1)
// ... -> b1(0)
g.NextBlock("b1", outs[0], ticketOuts[0])
accepted()

g.NextBlock("b2", outs[1], ticketOuts[1])
accepted()
// Create a fork from b1 with an invalid block due to committing to an
// invalid number of votes. Since verifying the header commitment is a
// basic sanity check, no entry will be added to the block index.
//
// ... -> b1(0) -> b2(1)
// \-> b2bad0(1)
g.SetTip("b1")
g.NextBlock("b2bad0", outs[1], ticketOuts[1], func(b *wire.MsgBlock) {
b.Header.Voters++
})
rejected(ErrTooManyVotes)

// Create a fork from b1 with an invalid block due to committing to an
// invalid input amount. Since verifying the fraud proof necessarily
// requires access to the inputs, this will trigger the failure late
// enough to ensure an entry is added to the block index. Further,
// since the block is attempting to extend the main chain it will also
// be fully checked and thus will be known invalid.
//
// ... -> b1(0)
// \-> b2bad1(1)
g.SetTip("b1")
g.NextBlock("b2bad1", outs[1], ticketOuts[1], func(b *wire.MsgBlock) {
b.Transactions[1].TxIn[0].ValueIn--
})
rejected(ErrFraudAmountIn)

// Create some forks from b1. There should not be a reorg since b2 was
// seen first.
// Create some forks from b1. There should not be a reorg since b1 is
// the current tip and b2 is seen first.
//
// ... -> b1(0) -> b2(1)
// \-> b3(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
g.SetTip("b1")
g.NextBlock("b2", outs[1], ticketOuts[1])
accepted()

g.SetTip("b1")
g.NextBlock("b3", outs[1], ticketOuts[1])
acceptedToSideChainWithExpectedTip("b2")
Expand All @@ -379,12 +489,37 @@ func TestForceHeadReorg(t *testing.T) {
g.NextBlock("b5", outs[1], ticketOuts[1])
acceptedToSideChainWithExpectedTip("b2")

// Create a fork from b1 with an invalid block due to committing to an
// invalid input amount. Since verifying the fraud proof necessarily
// requires access to the inputs, this will trigger the failure late
// enough to ensure an entry is added to the block index. Further,
// since the block is not attempting to extend the main chain it will
// not be fully checked and thus will not yet have a known validation
// status.
//
// ... -> b1(0) -> b2(1)
// \-> b3(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
g.SetTip("b1")
g.NextBlock("b2bad2", outs[1], ticketOuts[1], func(b *wire.MsgBlock) {
b.Transactions[1].TxIn[0].ValueIn--
})
acceptedToSideChainWithExpectedTip("b2")

// Force tip reorganization to b3.
//
// ... -> b1(0) -> b3(1)
// \-> b2(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
g.SetTip("b1")
forceTipReorg("b2", "b3")
expectTip("b3")

Expand All @@ -394,6 +529,9 @@ func TestForceHeadReorg(t *testing.T) {
// \-> b2(1)
// \-> b3(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
forceTipReorg("b3", "b4")
expectTip("b4")

Expand All @@ -403,8 +541,64 @@ func TestForceHeadReorg(t *testing.T) {
// \-> b2(1)
// \-> b3(1)
// \-> b4(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
forceTipReorg("b4", "b5")
expectTip("b5")

// Force tip reorganization back to b3 to ensure cached validation
// results are exercised.
//
// ... -> b1(0) -> b3(1)
// \-> b2(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
forceTipReorg("b5", "b3")
expectTip("b3")

// Attempt to force tip reorganization from a block that is not the
// current tip. This should fail since that is not allowed.
//
// ... -> b1(0) -> b3(1)
// \-> b2(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
rejectForceTipReorg("b2", "b4", ErrForceReorgWrongChain)
expectTip("b3")

// Attempt to force tip reorganization to an invalid block that does
// not have an entry in the block index.
//
// ... -> b1(0) -> b3(1)
// \-> b2(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
rejectForceTipReorg("b3", "b2bad0", ErrForceReorgMissingChild)
expectTip("b3")

// Attempt to force tip reorganization to an invalid block that has an
// entry in the block index, but is not already known to be invalid.
//
//
// ... -> b1(0) -> b3(1)
// \-> b2(1)
// \-> b4(1)
// \-> b5(1)
// \-> b2bad0(1)
// \-> b2bad1(1)
// \-> b2bad2(1)
rejectForceTipReorg("b3", "b2bad2", ErrFraudAmountIn)
expectTip("b3")
}

// locatorHashes is a convenience function that returns the hashes for all of
Expand Down