diff --git a/blockdag/accept_test.go b/blockdag/accept_test.go new file mode 100644 index 000000000..c1c4bf3e0 --- /dev/null +++ b/blockdag/accept_test.go @@ -0,0 +1,132 @@ +package blockdag + +import ( + "bou.ke/monkey" + "errors" + "github.com/daglabs/btcd/dagconfig" + "github.com/daglabs/btcd/database" + "github.com/daglabs/btcd/util" + "strings" + "testing" +) + +func TestMaybeAcceptBlockErrors(t *testing.T) { + // Create a new database and DAG instance to run tests against. + dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", &dagconfig.MainNetParams) + if err != nil { + t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err) + } + defer teardownFunc() + + dag.TstSetCoinbaseMaturity(1) + + // Test rejecting the block if its parents are missing + orphanBlockFile := "blk_3B.dat" + loadedBlocks, err := loadBlocks(orphanBlockFile) + if err != nil { + t.Fatalf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: " + + "Error loading file '%s': %s\n", orphanBlockFile, err) + } + block := loadedBlocks[0] + + err = dag.maybeAcceptBlock(block, BFNone) + if err == nil { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: " + + "Expected: %s, got: ", ErrPreviousBlockUnknown) + } + ruleErr, ok := err.(RuleError) + if !ok { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: " + + "Expected RuleError but got %s", err) + } else if ruleErr.ErrorCode != ErrPreviousBlockUnknown { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: " + + "Unexpected error code. Want: %s, got: %s", ErrPreviousBlockUnknown, ruleErr.ErrorCode) + } + + // Test rejecting the block if its parents are invalid + blocksFile := "blk_0_to_4.dat" + blocks, err := loadBlocks(blocksFile) + if err != nil { + t.Fatalf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: " + + "Error loading file '%s': %s\n", blocksFile, err) + } + + // Add a valid block and mark it as invalid + block1 := blocks[1] + _, err = dag.ProcessBlock(block1, BFNone) + if err != nil { + t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err) + } + blockNode1 := dag.index.LookupNode(block1.Hash()) + dag.index.SetStatusFlags(blockNode1, statusValidateFailed) + + block2 := blocks[2] + err = dag.maybeAcceptBlock(block2, BFNone) + if err == nil { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: " + + "Expected: %s, got: ", ErrInvalidAncestorBlock) + } + ruleErr, ok = err.(RuleError) + if !ok { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: " + + "Expected RuleError but got %s", err) + } else if ruleErr.ErrorCode != ErrInvalidAncestorBlock { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: " + + "Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode) + } + + // Set block1's status back to valid for next tests + dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed) + + // Test rejecting the block due to bad context + originalBits := block2.MsgBlock().Header.Bits + block2.MsgBlock().Header.Bits = 0 + err = dag.maybeAcceptBlock(block2, BFNone) + if err == nil { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: " + + "Expected: %s, got: ", ErrUnexpectedDifficulty) + } + ruleErr, ok = err.(RuleError) + if !ok { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: " + + "Expected RuleError but got %s", err) + } else if ruleErr.ErrorCode != ErrUnexpectedDifficulty { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: " + + "Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode) + } + + // Set block2's bits back to valid for next tests + block2.MsgBlock().Header.Bits = originalBits + + // Test rejecting the node due to database error + databaseErrorMessage := "database error" + monkey.Patch(dbStoreBlock, func(dbTx database.Tx, block *util.Block) error { + return errors.New(databaseErrorMessage) + }) + err = dag.maybeAcceptBlock(block2, BFNone) + if err == nil { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the node due to database error: " + + "Expected: %s, got: ", databaseErrorMessage) + } + if !strings.Contains(err.Error(), databaseErrorMessage) { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the node due to database error: " + + "Unexpected error. Want: %s, got: %s", databaseErrorMessage, err) + } + monkey.Unpatch(dbStoreBlock) + + // Test rejecting the node due to index error + indexErrorMessage := "index error" + monkey.Patch((*blockIndex).flushToDB, func(_ *blockIndex) error { + return errors.New(indexErrorMessage) + }) + err = dag.maybeAcceptBlock(block2, BFNone) + if err == nil { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the node due to index error: " + + "Expected %s, got: ", indexErrorMessage) + } + if !strings.Contains(err.Error(), indexErrorMessage) { + t.Errorf("TestMaybeAcceptBlockErrors: rejecting the node due to index error: " + + "Unexpected error. Want: %s, got: %s", indexErrorMessage, err) + } + monkey.Unpatch((*blockIndex).flushToDB) +} diff --git a/blockdag/blockindex.go b/blockdag/blockindex.go index 8ab521df3..ad6e20f79 100644 --- a/blockdag/blockindex.go +++ b/blockdag/blockindex.go @@ -40,13 +40,6 @@ const ( statusNone blockStatus = 0 ) -// HaveData returns whether the full block data is stored in the database. This -// will return false for a block node where only the header is downloaded or -// kept. -func (status blockStatus) HaveData() bool { - return status&statusDataStored != 0 -} - // KnownValid returns whether the block is known to be valid. This will return // false for a valid block that has not been fully validated yet. func (status blockStatus) KnownValid() bool { diff --git a/blockdag/blockindex_test.go b/blockdag/blockindex_test.go new file mode 100644 index 000000000..2e71387cf --- /dev/null +++ b/blockdag/blockindex_test.go @@ -0,0 +1,55 @@ +package blockdag + +import ( + "github.com/bouk/monkey" + "github.com/daglabs/btcd/dagconfig" + "github.com/daglabs/btcd/database" + "github.com/pkg/errors" + "strings" + "testing" + "time" +) + +func TestAncestorErrors(t *testing.T) { + node := newTestNode(newSet(), int32(0x10000000), 0, time.Unix(0,0), dagconfig.MainNetParams.K) + node.height = 2 + ancestor := node.Ancestor(3) + if ancestor != nil { + t.Errorf("TestAncestorErrors: Ancestor() unexpectedly returned a node. Expected: ") + } +} + +func TestFlushToDBErrors(t *testing.T) { + // Create a new database and DAG instance to run tests against. + dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", &dagconfig.MainNetParams) + if err != nil { + t.Fatalf("TestFlushToDBErrors: Failed to setup DAG instance: %s", err) + } + defer teardownFunc() + + // Call flushToDB without anything to flush. This should succeed + err = dag.index.flushToDB() + if err != nil { + t.Errorf("TestFlushToDBErrors: flushToDB without anything to flush: " + + "Unexpected flushToDB error: %s", err) + } + + // Mark the genesis block as dirty + dag.index.SetStatusFlags(dag.genesis, statusValid) + + // Test flushToDB failure due to database error + databaseErrorMessage := "database error" + monkey.Patch(dbStoreBlockNode, func (_ database.Tx, _ *blockNode) error{ + return errors.New(databaseErrorMessage) + }) + err = dag.index.flushToDB() + if err == nil { + t.Errorf("TestFlushToDBErrors: flushToDB failure due to database error: " + + "Expected: %s, got: ", databaseErrorMessage) + } + if !strings.Contains(err.Error(), databaseErrorMessage) { + t.Errorf("TestFlushToDBErrors: flushToDB failure due to database error: " + + "Unexpected flushToDB error. Expected: %s, got: %s", databaseErrorMessage, err) + } + monkey.Unpatch(dbStoreBlockNode) +} \ No newline at end of file