github.com/decred/dcrd/blockchain@v1.2.1/fullblocks_test.go (about)

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