github.com/btcsuite/btcd@v0.24.0/blockchain/fullblocks_test.go (about)

     1  // Copyright (c) 2016 The Decred developers
     2  // Copyright (c) 2016-2017 The btcsuite developers
     3  // Use of this source code is governed by an ISC
     4  // license that can be found in the LICENSE file.
     5  
     6  package blockchain_test
     7  
     8  import (
     9  	"bytes"
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"testing"
    14  
    15  	"github.com/btcsuite/btcd/blockchain"
    16  	"github.com/btcsuite/btcd/blockchain/fullblocktests"
    17  	"github.com/btcsuite/btcd/btcutil"
    18  	"github.com/btcsuite/btcd/chaincfg"
    19  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    20  	"github.com/btcsuite/btcd/database"
    21  	_ "github.com/btcsuite/btcd/database/ffldb"
    22  	"github.com/btcsuite/btcd/txscript"
    23  	"github.com/btcsuite/btcd/wire"
    24  )
    25  
    26  const (
    27  	// testDbType is the database backend type to use for the tests.
    28  	testDbType = "ffldb"
    29  
    30  	// testDbRoot is the root directory used to create all test databases.
    31  	testDbRoot = "testdbs"
    32  
    33  	// blockDataNet is the expected network in the test block data.
    34  	blockDataNet = wire.MainNet
    35  )
    36  
    37  // filesExists returns whether or not the named file or directory exists.
    38  func fileExists(name string) bool {
    39  	if _, err := os.Stat(name); err != nil {
    40  		if os.IsNotExist(err) {
    41  			return false
    42  		}
    43  	}
    44  	return true
    45  }
    46  
    47  // isSupportedDbType returns whether or not the passed database type is
    48  // currently supported.
    49  func isSupportedDbType(dbType string) bool {
    50  	supportedDrivers := database.SupportedDrivers()
    51  	for _, driver := range supportedDrivers {
    52  		if dbType == driver {
    53  			return true
    54  		}
    55  	}
    56  
    57  	return false
    58  }
    59  
    60  // chainSetup is used to create a new db and chain instance with the genesis
    61  // block already inserted.  In addition to the new chain instance, it returns
    62  // a teardown function the caller should invoke when done testing to clean up.
    63  func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain, func(), error) {
    64  	if !isSupportedDbType(testDbType) {
    65  		return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
    66  	}
    67  
    68  	// Handle memory database specially since it doesn't need the disk
    69  	// specific handling.
    70  	var db database.DB
    71  	var teardown func()
    72  	if testDbType == "memdb" {
    73  		ndb, err := database.Create(testDbType)
    74  		if err != nil {
    75  			return nil, nil, fmt.Errorf("error creating db: %v", err)
    76  		}
    77  		db = ndb
    78  
    79  		// Setup a teardown function for cleaning up.  This function is
    80  		// returned to the caller to be invoked when it is done testing.
    81  		teardown = func() {
    82  			db.Close()
    83  		}
    84  	} else {
    85  		// Create the root directory for test databases.
    86  		if !fileExists(testDbRoot) {
    87  			if err := os.MkdirAll(testDbRoot, 0700); err != nil {
    88  				err := fmt.Errorf("unable to create test db "+
    89  					"root: %v", err)
    90  				return nil, nil, err
    91  			}
    92  		}
    93  
    94  		// Create a new database to store the accepted blocks into.
    95  		dbPath := filepath.Join(testDbRoot, dbName)
    96  		_ = os.RemoveAll(dbPath)
    97  		ndb, err := database.Create(testDbType, dbPath, blockDataNet)
    98  		if err != nil {
    99  			return nil, nil, fmt.Errorf("error creating db: %v", err)
   100  		}
   101  		db = ndb
   102  
   103  		// Setup a teardown function for cleaning up.  This function is
   104  		// returned to the caller to be invoked when it is done testing.
   105  		teardown = func() {
   106  			db.Close()
   107  			os.RemoveAll(dbPath)
   108  			os.RemoveAll(testDbRoot)
   109  		}
   110  	}
   111  
   112  	// Copy the chain params to ensure any modifications the tests do to
   113  	// the chain parameters do not affect the global instance.
   114  	paramsCopy := *params
   115  
   116  	// Create the main chain instance.
   117  	chain, err := blockchain.New(&blockchain.Config{
   118  		DB:          db,
   119  		ChainParams: &paramsCopy,
   120  		Checkpoints: nil,
   121  		TimeSource:  blockchain.NewMedianTime(),
   122  		SigCache:    txscript.NewSigCache(1000),
   123  	})
   124  	if err != nil {
   125  		teardown()
   126  		err := fmt.Errorf("failed to create chain instance: %v", err)
   127  		return nil, nil, err
   128  	}
   129  	return chain, teardown, nil
   130  }
   131  
   132  // TestFullBlocks ensures all tests generated by the fullblocktests package
   133  // have the expected result when processed via ProcessBlock.
   134  func TestFullBlocks(t *testing.T) {
   135  	tests, err := fullblocktests.Generate(false)
   136  	if err != nil {
   137  		t.Fatalf("failed to generate tests: %v", err)
   138  	}
   139  
   140  	// Create a new database and chain instance to run tests against.
   141  	chain, teardownFunc, err := chainSetup("fullblocktest",
   142  		&chaincfg.RegressionNetParams)
   143  	if err != nil {
   144  		t.Errorf("Failed to setup chain instance: %v", err)
   145  		return
   146  	}
   147  	defer teardownFunc()
   148  
   149  	// testAcceptedBlock attempts to process the block in the provided test
   150  	// instance and ensures that it was accepted according to the flags
   151  	// specified in the test.
   152  	testAcceptedBlock := func(item fullblocktests.AcceptedBlock) {
   153  		blockHeight := item.Height
   154  		block := btcutil.NewBlock(item.Block)
   155  		block.SetHeight(blockHeight)
   156  		t.Logf("Testing block %s (hash %s, height %d)",
   157  			item.Name, block.Hash(), blockHeight)
   158  
   159  		isMainChain, isOrphan, err := chain.ProcessBlock(block,
   160  			blockchain.BFNone)
   161  		if err != nil {
   162  			t.Fatalf("block %q (hash %s, height %d) should "+
   163  				"have been accepted: %v", item.Name,
   164  				block.Hash(), blockHeight, err)
   165  		}
   166  
   167  		// Ensure the main chain and orphan flags match the values
   168  		// specified in the test.
   169  		if isMainChain != item.IsMainChain {
   170  			t.Fatalf("block %q (hash %s, height %d) unexpected main "+
   171  				"chain flag -- got %v, want %v", item.Name,
   172  				block.Hash(), blockHeight, isMainChain,
   173  				item.IsMainChain)
   174  		}
   175  		if isOrphan != item.IsOrphan {
   176  			t.Fatalf("block %q (hash %s, height %d) unexpected "+
   177  				"orphan flag -- got %v, want %v", item.Name,
   178  				block.Hash(), blockHeight, isOrphan,
   179  				item.IsOrphan)
   180  		}
   181  	}
   182  
   183  	// testRejectedBlock attempts to process the block in the provided test
   184  	// instance and ensures that it was rejected with the reject code
   185  	// specified in the test.
   186  	testRejectedBlock := func(item fullblocktests.RejectedBlock) {
   187  		blockHeight := item.Height
   188  		block := btcutil.NewBlock(item.Block)
   189  		block.SetHeight(blockHeight)
   190  		t.Logf("Testing block %s (hash %s, height %d)",
   191  			item.Name, block.Hash(), blockHeight)
   192  
   193  		_, _, err := chain.ProcessBlock(block, blockchain.BFNone)
   194  		if err == nil {
   195  			t.Fatalf("block %q (hash %s, height %d) should not "+
   196  				"have been accepted", item.Name, block.Hash(),
   197  				blockHeight)
   198  		}
   199  
   200  		// Ensure the error code is of the expected type and the reject
   201  		// code matches the value specified in the test instance.
   202  		rerr, ok := err.(blockchain.RuleError)
   203  		if !ok {
   204  			t.Fatalf("block %q (hash %s, height %d) returned "+
   205  				"unexpected error type -- got %T, want "+
   206  				"blockchain.RuleError", item.Name, block.Hash(),
   207  				blockHeight, err)
   208  		}
   209  		if rerr.ErrorCode != item.RejectCode {
   210  			t.Fatalf("block %q (hash %s, height %d) does not have "+
   211  				"expected reject code -- got %v, want %v",
   212  				item.Name, block.Hash(), blockHeight,
   213  				rerr.ErrorCode, item.RejectCode)
   214  		}
   215  	}
   216  
   217  	// testRejectedNonCanonicalBlock attempts to decode the block in the
   218  	// provided test instance and ensures that it failed to decode with a
   219  	// message error.
   220  	testRejectedNonCanonicalBlock := func(item fullblocktests.RejectedNonCanonicalBlock) {
   221  		headerLen := len(item.RawBlock)
   222  		if headerLen > 80 {
   223  			headerLen = 80
   224  		}
   225  		blockHash := chainhash.DoubleHashH(item.RawBlock[0:headerLen])
   226  		blockHeight := item.Height
   227  		t.Logf("Testing block %s (hash %s, height %d)", item.Name,
   228  			blockHash, blockHeight)
   229  
   230  		// Ensure there is an error due to deserializing the block.
   231  		var msgBlock wire.MsgBlock
   232  		err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0, wire.BaseEncoding)
   233  		if _, ok := err.(*wire.MessageError); !ok {
   234  			t.Fatalf("block %q (hash %s, height %d) should have "+
   235  				"failed to decode", item.Name, blockHash,
   236  				blockHeight)
   237  		}
   238  	}
   239  
   240  	// testOrphanOrRejectedBlock attempts to process the block in the
   241  	// provided test instance and ensures that it was either accepted as an
   242  	// orphan or rejected with a rule violation.
   243  	testOrphanOrRejectedBlock := func(item fullblocktests.OrphanOrRejectedBlock) {
   244  		blockHeight := item.Height
   245  		block := btcutil.NewBlock(item.Block)
   246  		block.SetHeight(blockHeight)
   247  		t.Logf("Testing block %s (hash %s, height %d)",
   248  			item.Name, block.Hash(), blockHeight)
   249  
   250  		_, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone)
   251  		if err != nil {
   252  			// Ensure the error code is of the expected type.
   253  			if _, ok := err.(blockchain.RuleError); !ok {
   254  				t.Fatalf("block %q (hash %s, height %d) "+
   255  					"returned unexpected error type -- "+
   256  					"got %T, want blockchain.RuleError",
   257  					item.Name, block.Hash(), blockHeight,
   258  					err)
   259  			}
   260  		}
   261  
   262  		if !isOrphan {
   263  			t.Fatalf("block %q (hash %s, height %d) was accepted, "+
   264  				"but is not considered an orphan", item.Name,
   265  				block.Hash(), blockHeight)
   266  		}
   267  	}
   268  
   269  	// testExpectedTip ensures the current tip of the blockchain is the
   270  	// block specified in the provided test instance.
   271  	testExpectedTip := func(item fullblocktests.ExpectedTip) {
   272  		blockHeight := item.Height
   273  		block := btcutil.NewBlock(item.Block)
   274  		block.SetHeight(blockHeight)
   275  		t.Logf("Testing tip for block %s (hash %s, height %d)",
   276  			item.Name, block.Hash(), blockHeight)
   277  
   278  		// Ensure hash and height match.
   279  		best := chain.BestSnapshot()
   280  		if best.Hash != item.Block.BlockHash() ||
   281  			best.Height != blockHeight {
   282  
   283  			t.Fatalf("block %q (hash %s, height %d) should be "+
   284  				"the current tip -- got (hash %s, height %d)",
   285  				item.Name, block.Hash(), blockHeight, best.Hash,
   286  				best.Height)
   287  		}
   288  	}
   289  
   290  	for testNum, test := range tests {
   291  		for itemNum, item := range test {
   292  			switch item := item.(type) {
   293  			case fullblocktests.AcceptedBlock:
   294  				testAcceptedBlock(item)
   295  			case fullblocktests.RejectedBlock:
   296  				testRejectedBlock(item)
   297  			case fullblocktests.RejectedNonCanonicalBlock:
   298  				testRejectedNonCanonicalBlock(item)
   299  			case fullblocktests.OrphanOrRejectedBlock:
   300  				testOrphanOrRejectedBlock(item)
   301  			case fullblocktests.ExpectedTip:
   302  				testExpectedTip(item)
   303  			default:
   304  				t.Fatalf("test #%d, item #%d is not one of "+
   305  					"the supported test instance types -- "+
   306  					"got type: %T", testNum, itemNum, item)
   307  			}
   308  		}
   309  	}
   310  }