+++ /dev/null
-// Copyright (c) 2016 The Decred developers
-// Copyright (c) 2016-2017 The btcsuite developers
-// Use of this source code is governed by an ISC
-// license that can be found in the LICENSE file.
-
-package blockchain_test
-
-import (
- "bytes"
- "fmt"
- "os"
- "path/filepath"
- "testing"
-
- "github.com/btcsuite/btcd/blockchain"
- "github.com/btcsuite/btcd/blockchain/fullblocktests"
- "github.com/btcsuite/btcd/chaincfg"
- "github.com/btcsuite/btcd/chaincfg/chainhash"
- "github.com/btcsuite/btcd/database"
- _ "github.com/btcsuite/btcd/database/ffldb"
- "github.com/btcsuite/btcd/txscript"
- "github.com/btcsuite/btcd/wire"
- "github.com/btcsuite/btcutil"
-)
-
-const (
- // testDbType is the database backend type to use for the tests.
- testDbType = "ffldb"
-
- // testDbRoot is the root directory used to create all test databases.
- testDbRoot = "testdbs"
-
- // blockDataNet is the expected network in the test block data.
- blockDataNet = wire.MainNet
-)
-
-// filesExists returns whether or not the named file or directory exists.
-func fileExists(name string) bool {
- if _, err := os.Stat(name); err != nil {
- if os.IsNotExist(err) {
- return false
- }
- }
- return true
-}
-
-// isSupportedDbType returns whether or not the passed database type is
-// currently supported.
-func isSupportedDbType(dbType string) bool {
- supportedDrivers := database.SupportedDrivers()
- for _, driver := range supportedDrivers {
- if dbType == driver {
- return true
- }
- }
-
- return false
-}
-
-// chainSetup is used to create a new db and chain instance with the genesis
-// block already inserted. In addition to the new chain instance, it returns
-// a teardown function the caller should invoke when done testing to clean up.
-func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain, func(), error) {
- if !isSupportedDbType(testDbType) {
- return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
- }
-
- // Handle memory database specially since it doesn't need the disk
- // specific handling.
- var db database.DB
- var teardown func()
- if testDbType == "memdb" {
- ndb, err := database.Create(testDbType)
- if err != nil {
- return nil, nil, fmt.Errorf("error creating db: %v", err)
- }
- db = ndb
-
- // Setup a teardown function for cleaning up. This function is
- // returned to the caller to be invoked when it is done testing.
- teardown = func() {
- db.Close()
- }
- } else {
- // Create the root directory for test databases.
- if !fileExists(testDbRoot) {
- if err := os.MkdirAll(testDbRoot, 0700); err != nil {
- err := fmt.Errorf("unable to create test db "+
- "root: %v", err)
- return nil, nil, err
- }
- }
-
- // Create a new database to store the accepted blocks into.
- dbPath := filepath.Join(testDbRoot, dbName)
- _ = os.RemoveAll(dbPath)
- ndb, err := database.Create(testDbType, dbPath, blockDataNet)
- if err != nil {
- return nil, nil, fmt.Errorf("error creating db: %v", err)
- }
- db = ndb
-
- // Setup a teardown function for cleaning up. This function is
- // returned to the caller to be invoked when it is done testing.
- teardown = func() {
- db.Close()
- os.RemoveAll(dbPath)
- os.RemoveAll(testDbRoot)
- }
- }
-
- // Copy the chain params to ensure any modifications the tests do to
- // the chain parameters do not affect the global instance.
- paramsCopy := *params
-
- // Create the main chain instance.
- chain, err := blockchain.New(&blockchain.Config{
- DB: db,
- ChainParams: ¶msCopy,
- Checkpoints: nil,
- TimeSource: blockchain.NewMedianTime(),
- SigCache: txscript.NewSigCache(1000),
- })
- if err != nil {
- teardown()
- err := fmt.Errorf("failed to create chain instance: %v", err)
- return nil, nil, err
- }
- return chain, teardown, nil
-}
-
-// TestFullBlocks ensures all tests generated by the fullblocktests package
-// have the expected result when processed via ProcessBlock.
-func TestFullBlocks(t *testing.T) {
- tests, err := fullblocktests.Generate(false)
- if err != nil {
- t.Fatalf("failed to generate tests: %v", err)
- }
-
- // Create a new database and chain instance to run tests against.
- chain, teardownFunc, err := chainSetup("fullblocktest",
- &chaincfg.RegressionNetParams)
- if err != nil {
- t.Errorf("Failed to setup chain instance: %v", err)
- return
- }
- defer teardownFunc()
-
- // testAcceptedBlock attempts to process the block in the provided test
- // instance and ensures that it was accepted according to the flags
- // specified in the test.
- testAcceptedBlock := func(item fullblocktests.AcceptedBlock) {
- blockHeight := item.Height
- block := btcutil.NewBlock(item.Block)
- block.SetHeight(blockHeight)
- t.Logf("Testing block %s (hash %s, height %d)",
- item.Name, block.Hash(), blockHeight)
-
- isMainChain, isOrphan, err := chain.ProcessBlock(block,
- blockchain.BFNone)
- if err != nil {
- t.Fatalf("block %q (hash %s, height %d) should "+
- "have been accepted: %v", item.Name,
- block.Hash(), blockHeight, err)
- }
-
- // Ensure the main chain and orphan flags match the values
- // specified in the test.
- if isMainChain != item.IsMainChain {
- t.Fatalf("block %q (hash %s, height %d) unexpected main "+
- "chain flag -- got %v, want %v", item.Name,
- block.Hash(), blockHeight, isMainChain,
- item.IsMainChain)
- }
- if isOrphan != item.IsOrphan {
- t.Fatalf("block %q (hash %s, height %d) unexpected "+
- "orphan flag -- got %v, want %v", item.Name,
- block.Hash(), blockHeight, isOrphan,
- item.IsOrphan)
- }
- }
-
- // testRejectedBlock attempts to process the block in the provided test
- // instance and ensures that it was rejected with the reject code
- // specified in the test.
- testRejectedBlock := func(item fullblocktests.RejectedBlock) {
- blockHeight := item.Height
- block := btcutil.NewBlock(item.Block)
- block.SetHeight(blockHeight)
- t.Logf("Testing block %s (hash %s, height %d)",
- item.Name, block.Hash(), blockHeight)
-
- _, _, err := chain.ProcessBlock(block, blockchain.BFNone)
- if err == nil {
- t.Fatalf("block %q (hash %s, height %d) should not "+
- "have been accepted", item.Name, 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.(blockchain.RuleError)
- if !ok {
- t.Fatalf("block %q (hash %s, height %d) returned "+
- "unexpected error type -- got %T, want "+
- "blockchain.RuleError", item.Name, block.Hash(),
- blockHeight, err)
- }
- if rerr.ErrorCode != item.RejectCode {
- t.Fatalf("block %q (hash %s, height %d) does not have "+
- "expected reject code -- got %v, want %v",
- item.Name, block.Hash(), blockHeight,
- rerr.ErrorCode, item.RejectCode)
- }
- }
-
- // testRejectedNonCanonicalBlock attempts to decode the block in the
- // provided test instance and ensures that it failed to decode with a
- // message error.
- testRejectedNonCanonicalBlock := func(item fullblocktests.RejectedNonCanonicalBlock) {
- headerLen := len(item.RawBlock)
- if headerLen > 80 {
- headerLen = 80
- }
- blockHash := chainhash.DoubleHashH(item.RawBlock[0:headerLen])
- blockHeight := item.Height
- t.Logf("Testing block %s (hash %s, height %d)", item.Name,
- blockHash, blockHeight)
-
- // Ensure there is an error due to deserializing the block.
- var msgBlock wire.MsgBlock
- err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0, wire.BaseEncoding)
- if _, ok := err.(*wire.MessageError); !ok {
- t.Fatalf("block %q (hash %s, height %d) should have "+
- "failed to decode", item.Name, blockHash,
- blockHeight)
- }
- }
-
- // testOrphanOrRejectedBlock attempts to process the block in the
- // provided test instance and ensures that it was either accepted as an
- // orphan or rejected with a rule violation.
- testOrphanOrRejectedBlock := func(item fullblocktests.OrphanOrRejectedBlock) {
- blockHeight := item.Height
- block := btcutil.NewBlock(item.Block)
- block.SetHeight(blockHeight)
- t.Logf("Testing block %s (hash %s, height %d)",
- item.Name, block.Hash(), blockHeight)
-
- _, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone)
- if err != nil {
- // Ensure the error code is of the expected type.
- if _, ok := err.(blockchain.RuleError); !ok {
- t.Fatalf("block %q (hash %s, height %d) "+
- "returned unexpected error type -- "+
- "got %T, want blockchain.RuleError",
- item.Name, block.Hash(), blockHeight,
- err)
- }
- }
-
- if !isOrphan {
- t.Fatalf("block %q (hash %s, height %d) was accepted, "+
- "but is not considered an orphan", item.Name,
- block.Hash(), blockHeight)
- }
- }
-
- // testExpectedTip ensures the current tip of the blockchain is the
- // block specified in the provided test instance.
- testExpectedTip := func(item fullblocktests.ExpectedTip) {
- blockHeight := item.Height
- block := btcutil.NewBlock(item.Block)
- block.SetHeight(blockHeight)
- t.Logf("Testing tip for block %s (hash %s, height %d)",
- item.Name, block.Hash(), blockHeight)
-
- // Ensure hash and height match.
- best := chain.BestSnapshot()
- if best.Hash != item.Block.BlockHash() ||
- best.Height != blockHeight {
-
- t.Fatalf("block %q (hash %s, height %d) should be "+
- "the current tip -- got (hash %s, height %d)",
- item.Name, block.Hash(), blockHeight, best.Hash,
- best.Height)
- }
- }
-
- for testNum, test := range tests {
- for itemNum, item := range test {
- switch item := item.(type) {
- case fullblocktests.AcceptedBlock:
- testAcceptedBlock(item)
- case fullblocktests.RejectedBlock:
- testRejectedBlock(item)
- case fullblocktests.RejectedNonCanonicalBlock:
- testRejectedNonCanonicalBlock(item)
- case fullblocktests.OrphanOrRejectedBlock:
- testOrphanOrRejectedBlock(item)
- case fullblocktests.ExpectedTip:
- testExpectedTip(item)
- default:
- t.Fatalf("test #%d, item #%d is not one of "+
- "the supported test instance types -- "+
- "got type: %T", testNum, itemNum, item)
- }
- }
- }
-}