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

     1  // Copyright (c) 2013-2017 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package blockchain
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/btcsuite/btcd/btcutil"
    14  	"github.com/btcsuite/btcd/chaincfg"
    15  	"github.com/btcsuite/btcd/chaincfg/chainhash"
    16  	"github.com/btcsuite/btcd/wire"
    17  )
    18  
    19  // TestHaveBlock tests the HaveBlock API to ensure proper functionality.
    20  func TestHaveBlock(t *testing.T) {
    21  	// Load up blocks such that there is a side chain.
    22  	// (genesis block) -> 1 -> 2 -> 3 -> 4
    23  	//                          \-> 3a
    24  	testFiles := []string{
    25  		"blk_0_to_4.dat.bz2",
    26  		"blk_3A.dat.bz2",
    27  	}
    28  
    29  	var blocks []*btcutil.Block
    30  	for _, file := range testFiles {
    31  		blockTmp, err := loadBlocks(file)
    32  		if err != nil {
    33  			t.Errorf("Error loading file: %v\n", err)
    34  			return
    35  		}
    36  		blocks = append(blocks, blockTmp...)
    37  	}
    38  
    39  	// Create a new database and chain instance to run tests against.
    40  	chain, teardownFunc, err := chainSetup("haveblock",
    41  		&chaincfg.MainNetParams)
    42  	if err != nil {
    43  		t.Errorf("Failed to setup chain instance: %v", err)
    44  		return
    45  	}
    46  	defer teardownFunc()
    47  
    48  	// Since we're not dealing with the real block chain, set the coinbase
    49  	// maturity to 1.
    50  	chain.TstSetCoinbaseMaturity(1)
    51  
    52  	for i := 1; i < len(blocks); i++ {
    53  		_, isOrphan, err := chain.ProcessBlock(blocks[i], BFNone)
    54  		if err != nil {
    55  			t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
    56  			return
    57  		}
    58  		if isOrphan {
    59  			t.Errorf("ProcessBlock incorrectly returned block %v "+
    60  				"is an orphan\n", i)
    61  			return
    62  		}
    63  	}
    64  
    65  	// Insert an orphan block.
    66  	_, isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
    67  		BFNone)
    68  	if err != nil {
    69  		t.Errorf("Unable to process block: %v", err)
    70  		return
    71  	}
    72  	if !isOrphan {
    73  		t.Errorf("ProcessBlock indicated block is an not orphan when " +
    74  			"it should be\n")
    75  		return
    76  	}
    77  
    78  	tests := []struct {
    79  		hash string
    80  		want bool
    81  	}{
    82  		// Genesis block should be present (in the main chain).
    83  		{hash: chaincfg.MainNetParams.GenesisHash.String(), want: true},
    84  
    85  		// Block 3a should be present (on a side chain).
    86  		{hash: "00000000474284d20067a4d33f6a02284e6ef70764a3a26d6a5b9df52ef663dd", want: true},
    87  
    88  		// Block 100000 should be present (as an orphan).
    89  		{hash: "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", want: true},
    90  
    91  		// Random hashes should not be available.
    92  		{hash: "123", want: false},
    93  	}
    94  
    95  	for i, test := range tests {
    96  		hash, err := chainhash.NewHashFromStr(test.hash)
    97  		if err != nil {
    98  			t.Errorf("NewHashFromStr: %v", err)
    99  			continue
   100  		}
   101  
   102  		result, err := chain.HaveBlock(hash)
   103  		if err != nil {
   104  			t.Errorf("HaveBlock #%d unexpected error: %v", i, err)
   105  			return
   106  		}
   107  		if result != test.want {
   108  			t.Errorf("HaveBlock #%d got %v want %v", i, result,
   109  				test.want)
   110  			continue
   111  		}
   112  	}
   113  }
   114  
   115  // TestCalcSequenceLock tests the LockTimeToSequence function, and the
   116  // CalcSequenceLock method of a Chain instance. The tests exercise several
   117  // combinations of inputs to the CalcSequenceLock function in order to ensure
   118  // the returned SequenceLocks are correct for each test instance.
   119  func TestCalcSequenceLock(t *testing.T) {
   120  	netParams := &chaincfg.SimNetParams
   121  
   122  	// We need to activate CSV in order to test the processing logic, so
   123  	// manually craft the block version that's used to signal the soft-fork
   124  	// activation.
   125  	csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber
   126  	blockVersion := int32(0x20000000 | (uint32(1) << csvBit))
   127  
   128  	// Generate enough synthetic blocks to activate CSV.
   129  	chain := newFakeChain(netParams)
   130  	node := chain.bestChain.Tip()
   131  	blockTime := node.Header().Timestamp
   132  	numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
   133  	for i := uint32(0); i < numBlocksToActivate; i++ {
   134  		blockTime = blockTime.Add(time.Second)
   135  		node = newFakeNode(node, blockVersion, 0, blockTime)
   136  		chain.index.AddNode(node)
   137  		chain.bestChain.SetTip(node)
   138  	}
   139  
   140  	// Create a utxo view with a fake utxo for the inputs used in the
   141  	// transactions created below.  This utxo is added such that it has an
   142  	// age of 4 blocks.
   143  	targetTx := btcutil.NewTx(&wire.MsgTx{
   144  		TxOut: []*wire.TxOut{{
   145  			PkScript: nil,
   146  			Value:    10,
   147  		}},
   148  	})
   149  	utxoView := NewUtxoViewpoint()
   150  	utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4)
   151  	utxoView.SetBestHash(&node.hash)
   152  
   153  	// Create a utxo that spends the fake utxo created above for use in the
   154  	// transactions created in the tests.  It has an age of 4 blocks.  Note
   155  	// that the sequence lock heights are always calculated from the same
   156  	// point of view that they were originally calculated from for a given
   157  	// utxo.  That is to say, the height prior to it.
   158  	utxo := wire.OutPoint{
   159  		Hash:  *targetTx.Hash(),
   160  		Index: 0,
   161  	}
   162  	prevUtxoHeight := int32(numBlocksToActivate) - 4
   163  
   164  	// Obtain the median time past from the PoV of the input created above.
   165  	// The MTP for the input is the MTP from the PoV of the block *prior*
   166  	// to the one that included it.
   167  	medianTime := CalcPastMedianTime(node.RelativeAncestor(5)).Unix()
   168  
   169  	// The median time calculated from the PoV of the best block in the
   170  	// test chain.  For unconfirmed inputs, this value will be used since
   171  	// the MTP will be calculated from the PoV of the yet-to-be-mined
   172  	// block.
   173  	nextMedianTime := CalcPastMedianTime(node).Unix()
   174  	nextBlockHeight := int32(numBlocksToActivate) + 1
   175  
   176  	// Add an additional transaction which will serve as our unconfirmed
   177  	// output.
   178  	unConfTx := &wire.MsgTx{
   179  		TxOut: []*wire.TxOut{{
   180  			PkScript: nil,
   181  			Value:    5,
   182  		}},
   183  	}
   184  	unConfUtxo := wire.OutPoint{
   185  		Hash:  unConfTx.TxHash(),
   186  		Index: 0,
   187  	}
   188  
   189  	// Adding a utxo with a height of 0x7fffffff indicates that the output
   190  	// is currently unmined.
   191  	utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff)
   192  
   193  	tests := []struct {
   194  		tx      *wire.MsgTx
   195  		view    *UtxoViewpoint
   196  		mempool bool
   197  		want    *SequenceLock
   198  	}{
   199  		// A transaction of version one should disable sequence locks
   200  		// as the new sequence number semantics only apply to
   201  		// transactions version 2 or higher.
   202  		{
   203  			tx: &wire.MsgTx{
   204  				Version: 1,
   205  				TxIn: []*wire.TxIn{{
   206  					PreviousOutPoint: utxo,
   207  					Sequence:         LockTimeToSequence(false, 3),
   208  				}},
   209  			},
   210  			view: utxoView,
   211  			want: &SequenceLock{
   212  				Seconds:     -1,
   213  				BlockHeight: -1,
   214  			},
   215  		},
   216  		// A transaction with a single input with max sequence number.
   217  		// This sequence number has the high bit set, so sequence locks
   218  		// should be disabled.
   219  		{
   220  			tx: &wire.MsgTx{
   221  				Version: 2,
   222  				TxIn: []*wire.TxIn{{
   223  					PreviousOutPoint: utxo,
   224  					Sequence:         wire.MaxTxInSequenceNum,
   225  				}},
   226  			},
   227  			view: utxoView,
   228  			want: &SequenceLock{
   229  				Seconds:     -1,
   230  				BlockHeight: -1,
   231  			},
   232  		},
   233  		// A transaction with a single input whose lock time is
   234  		// expressed in seconds.  However, the specified lock time is
   235  		// below the required floor for time based lock times since
   236  		// they have time granularity of 512 seconds.  As a result, the
   237  		// seconds lock-time should be just before the median time of
   238  		// the targeted block.
   239  		{
   240  			tx: &wire.MsgTx{
   241  				Version: 2,
   242  				TxIn: []*wire.TxIn{{
   243  					PreviousOutPoint: utxo,
   244  					Sequence:         LockTimeToSequence(true, 2),
   245  				}},
   246  			},
   247  			view: utxoView,
   248  			want: &SequenceLock{
   249  				Seconds:     medianTime - 1,
   250  				BlockHeight: -1,
   251  			},
   252  		},
   253  		// A transaction with a single input whose lock time is
   254  		// expressed in seconds.  The number of seconds should be 1023
   255  		// seconds after the median past time of the last block in the
   256  		// chain.
   257  		{
   258  			tx: &wire.MsgTx{
   259  				Version: 2,
   260  				TxIn: []*wire.TxIn{{
   261  					PreviousOutPoint: utxo,
   262  					Sequence:         LockTimeToSequence(true, 1024),
   263  				}},
   264  			},
   265  			view: utxoView,
   266  			want: &SequenceLock{
   267  				Seconds:     medianTime + 1023,
   268  				BlockHeight: -1,
   269  			},
   270  		},
   271  		// A transaction with multiple inputs.  The first input has a
   272  		// lock time expressed in seconds.  The second input has a
   273  		// sequence lock in blocks with a value of 4.  The last input
   274  		// has a sequence number with a value of 5, but has the disable
   275  		// bit set.  So the first lock should be selected as it's the
   276  		// latest lock that isn't disabled.
   277  		{
   278  			tx: &wire.MsgTx{
   279  				Version: 2,
   280  				TxIn: []*wire.TxIn{{
   281  					PreviousOutPoint: utxo,
   282  					Sequence:         LockTimeToSequence(true, 2560),
   283  				}, {
   284  					PreviousOutPoint: utxo,
   285  					Sequence:         LockTimeToSequence(false, 4),
   286  				}, {
   287  					PreviousOutPoint: utxo,
   288  					Sequence: LockTimeToSequence(false, 5) |
   289  						wire.SequenceLockTimeDisabled,
   290  				}},
   291  			},
   292  			view: utxoView,
   293  			want: &SequenceLock{
   294  				Seconds:     medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
   295  				BlockHeight: prevUtxoHeight + 3,
   296  			},
   297  		},
   298  		// Transaction with a single input.  The input's sequence number
   299  		// encodes a relative lock-time in blocks (3 blocks).  The
   300  		// sequence lock should  have a value of -1 for seconds, but a
   301  		// height of 2 meaning it can be included at height 3.
   302  		{
   303  			tx: &wire.MsgTx{
   304  				Version: 2,
   305  				TxIn: []*wire.TxIn{{
   306  					PreviousOutPoint: utxo,
   307  					Sequence:         LockTimeToSequence(false, 3),
   308  				}},
   309  			},
   310  			view: utxoView,
   311  			want: &SequenceLock{
   312  				Seconds:     -1,
   313  				BlockHeight: prevUtxoHeight + 2,
   314  			},
   315  		},
   316  		// A transaction with two inputs with lock times expressed in
   317  		// seconds.  The selected sequence lock value for seconds should
   318  		// be the time further in the future.
   319  		{
   320  			tx: &wire.MsgTx{
   321  				Version: 2,
   322  				TxIn: []*wire.TxIn{{
   323  					PreviousOutPoint: utxo,
   324  					Sequence:         LockTimeToSequence(true, 5120),
   325  				}, {
   326  					PreviousOutPoint: utxo,
   327  					Sequence:         LockTimeToSequence(true, 2560),
   328  				}},
   329  			},
   330  			view: utxoView,
   331  			want: &SequenceLock{
   332  				Seconds:     medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
   333  				BlockHeight: -1,
   334  			},
   335  		},
   336  		// A transaction with two inputs with lock times expressed in
   337  		// blocks.  The selected sequence lock value for blocks should
   338  		// be the height further in the future, so a height of 10
   339  		// indicating it can be included at height 11.
   340  		{
   341  			tx: &wire.MsgTx{
   342  				Version: 2,
   343  				TxIn: []*wire.TxIn{{
   344  					PreviousOutPoint: utxo,
   345  					Sequence:         LockTimeToSequence(false, 1),
   346  				}, {
   347  					PreviousOutPoint: utxo,
   348  					Sequence:         LockTimeToSequence(false, 11),
   349  				}},
   350  			},
   351  			view: utxoView,
   352  			want: &SequenceLock{
   353  				Seconds:     -1,
   354  				BlockHeight: prevUtxoHeight + 10,
   355  			},
   356  		},
   357  		// A transaction with multiple inputs.  Two inputs are time
   358  		// based, and the other two are block based. The lock lying
   359  		// further into the future for both inputs should be chosen.
   360  		{
   361  			tx: &wire.MsgTx{
   362  				Version: 2,
   363  				TxIn: []*wire.TxIn{{
   364  					PreviousOutPoint: utxo,
   365  					Sequence:         LockTimeToSequence(true, 2560),
   366  				}, {
   367  					PreviousOutPoint: utxo,
   368  					Sequence:         LockTimeToSequence(true, 6656),
   369  				}, {
   370  					PreviousOutPoint: utxo,
   371  					Sequence:         LockTimeToSequence(false, 3),
   372  				}, {
   373  					PreviousOutPoint: utxo,
   374  					Sequence:         LockTimeToSequence(false, 9),
   375  				}},
   376  			},
   377  			view: utxoView,
   378  			want: &SequenceLock{
   379  				Seconds:     medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
   380  				BlockHeight: prevUtxoHeight + 8,
   381  			},
   382  		},
   383  		// A transaction with a single unconfirmed input.  As the input
   384  		// is confirmed, the height of the input should be interpreted
   385  		// as the height of the *next* block.  So, a 2 block relative
   386  		// lock means the sequence lock should be for 1 block after the
   387  		// *next* block height, indicating it can be included 2 blocks
   388  		// after that.
   389  		{
   390  			tx: &wire.MsgTx{
   391  				Version: 2,
   392  				TxIn: []*wire.TxIn{{
   393  					PreviousOutPoint: unConfUtxo,
   394  					Sequence:         LockTimeToSequence(false, 2),
   395  				}},
   396  			},
   397  			view:    utxoView,
   398  			mempool: true,
   399  			want: &SequenceLock{
   400  				Seconds:     -1,
   401  				BlockHeight: nextBlockHeight + 1,
   402  			},
   403  		},
   404  		// A transaction with a single unconfirmed input.  The input has
   405  		// a time based lock, so the lock time should be based off the
   406  		// MTP of the *next* block.
   407  		{
   408  			tx: &wire.MsgTx{
   409  				Version: 2,
   410  				TxIn: []*wire.TxIn{{
   411  					PreviousOutPoint: unConfUtxo,
   412  					Sequence:         LockTimeToSequence(true, 1024),
   413  				}},
   414  			},
   415  			view:    utxoView,
   416  			mempool: true,
   417  			want: &SequenceLock{
   418  				Seconds:     nextMedianTime + 1023,
   419  				BlockHeight: -1,
   420  			},
   421  		},
   422  	}
   423  
   424  	t.Logf("Running %v SequenceLock tests", len(tests))
   425  	for i, test := range tests {
   426  		utilTx := btcutil.NewTx(test.tx)
   427  		seqLock, err := chain.CalcSequenceLock(utilTx, test.view, test.mempool)
   428  		if err != nil {
   429  			t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err)
   430  		}
   431  
   432  		if seqLock.Seconds != test.want.Seconds {
   433  			t.Fatalf("test #%d got %v seconds want %v seconds",
   434  				i, seqLock.Seconds, test.want.Seconds)
   435  		}
   436  		if seqLock.BlockHeight != test.want.BlockHeight {
   437  			t.Fatalf("test #%d got height of %v want height of %v ",
   438  				i, seqLock.BlockHeight, test.want.BlockHeight)
   439  		}
   440  	}
   441  }
   442  
   443  // nodeHashes is a convenience function that returns the hashes for all of the
   444  // passed indexes of the provided nodes.  It is used to construct expected hash
   445  // slices in the tests.
   446  func nodeHashes(nodes []*blockNode, indexes ...int) []chainhash.Hash {
   447  	hashes := make([]chainhash.Hash, 0, len(indexes))
   448  	for _, idx := range indexes {
   449  		hashes = append(hashes, nodes[idx].hash)
   450  	}
   451  	return hashes
   452  }
   453  
   454  // nodeHeaders is a convenience function that returns the headers for all of
   455  // the passed indexes of the provided nodes.  It is used to construct expected
   456  // located headers in the tests.
   457  func nodeHeaders(nodes []*blockNode, indexes ...int) []wire.BlockHeader {
   458  	headers := make([]wire.BlockHeader, 0, len(indexes))
   459  	for _, idx := range indexes {
   460  		headers = append(headers, nodes[idx].Header())
   461  	}
   462  	return headers
   463  }
   464  
   465  // TestLocateInventory ensures that locating inventory via the LocateHeaders and
   466  // LocateBlocks functions behaves as expected.
   467  func TestLocateInventory(t *testing.T) {
   468  	// Construct a synthetic block chain with a block index consisting of
   469  	// the following structure.
   470  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   471  	// 	                              \-> 16a -> 17a
   472  	tip := tstTip
   473  	chain := newFakeChain(&chaincfg.MainNetParams)
   474  	branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
   475  	branch1Nodes := chainedNodes(branch0Nodes[14], 2)
   476  	for _, node := range branch0Nodes {
   477  		chain.index.AddNode(node)
   478  	}
   479  	for _, node := range branch1Nodes {
   480  		chain.index.AddNode(node)
   481  	}
   482  	chain.bestChain.SetTip(tip(branch0Nodes))
   483  
   484  	// Create chain views for different branches of the overall chain to
   485  	// simulate a local and remote node on different parts of the chain.
   486  	localView := newChainView(tip(branch0Nodes))
   487  	remoteView := newChainView(tip(branch1Nodes))
   488  
   489  	// Create a chain view for a completely unrelated block chain to
   490  	// simulate a remote node on a totally different chain.
   491  	unrelatedBranchNodes := chainedNodes(nil, 5)
   492  	unrelatedView := newChainView(tip(unrelatedBranchNodes))
   493  
   494  	tests := []struct {
   495  		name       string
   496  		locator    BlockLocator       // locator for requested inventory
   497  		hashStop   chainhash.Hash     // stop hash for locator
   498  		maxAllowed uint32             // max to locate, 0 = wire const
   499  		headers    []wire.BlockHeader // expected located headers
   500  		hashes     []chainhash.Hash   // expected located hashes
   501  	}{
   502  		{
   503  			// Empty block locators and unknown stop hash.  No
   504  			// inventory should be located.
   505  			name:     "no locators, no stop",
   506  			locator:  nil,
   507  			hashStop: chainhash.Hash{},
   508  			headers:  nil,
   509  			hashes:   nil,
   510  		},
   511  		{
   512  			// Empty block locators and stop hash in side chain.
   513  			// The expected result is the requested block.
   514  			name:     "no locators, stop in side",
   515  			locator:  nil,
   516  			hashStop: tip(branch1Nodes).hash,
   517  			headers:  nodeHeaders(branch1Nodes, 1),
   518  			hashes:   nodeHashes(branch1Nodes, 1),
   519  		},
   520  		{
   521  			// Empty block locators and stop hash in main chain.
   522  			// The expected result is the requested block.
   523  			name:     "no locators, stop in main",
   524  			locator:  nil,
   525  			hashStop: branch0Nodes[12].hash,
   526  			headers:  nodeHeaders(branch0Nodes, 12),
   527  			hashes:   nodeHashes(branch0Nodes, 12),
   528  		},
   529  		{
   530  			// Locators based on remote being on side chain and a
   531  			// stop hash local node doesn't know about.  The
   532  			// expected result is the blocks after the fork point in
   533  			// the main chain and the stop hash has no effect.
   534  			name:     "remote side chain, unknown stop",
   535  			locator:  remoteView.BlockLocator(nil),
   536  			hashStop: chainhash.Hash{0x01},
   537  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   538  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   539  		},
   540  		{
   541  			// Locators based on remote being on side chain and a
   542  			// stop hash in side chain.  The expected result is the
   543  			// blocks after the fork point in the main chain and the
   544  			// stop hash has no effect.
   545  			name:     "remote side chain, stop in side",
   546  			locator:  remoteView.BlockLocator(nil),
   547  			hashStop: tip(branch1Nodes).hash,
   548  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   549  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   550  		},
   551  		{
   552  			// Locators based on remote being on side chain and a
   553  			// stop hash in main chain, but before fork point.  The
   554  			// expected result is the blocks after the fork point in
   555  			// the main chain and the stop hash has no effect.
   556  			name:     "remote side chain, stop in main before",
   557  			locator:  remoteView.BlockLocator(nil),
   558  			hashStop: branch0Nodes[13].hash,
   559  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   560  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   561  		},
   562  		{
   563  			// Locators based on remote being on side chain and a
   564  			// stop hash in main chain, but exactly at the fork
   565  			// point.  The expected result is the blocks after the
   566  			// fork point in the main chain and the stop hash has no
   567  			// effect.
   568  			name:     "remote side chain, stop in main exact",
   569  			locator:  remoteView.BlockLocator(nil),
   570  			hashStop: branch0Nodes[14].hash,
   571  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   572  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   573  		},
   574  		{
   575  			// Locators based on remote being on side chain and a
   576  			// stop hash in main chain just after the fork point.
   577  			// The expected result is the blocks after the fork
   578  			// point in the main chain up to and including the stop
   579  			// hash.
   580  			name:     "remote side chain, stop in main after",
   581  			locator:  remoteView.BlockLocator(nil),
   582  			hashStop: branch0Nodes[15].hash,
   583  			headers:  nodeHeaders(branch0Nodes, 15),
   584  			hashes:   nodeHashes(branch0Nodes, 15),
   585  		},
   586  		{
   587  			// Locators based on remote being on side chain and a
   588  			// stop hash in main chain some time after the fork
   589  			// point.  The expected result is the blocks after the
   590  			// fork point in the main chain up to and including the
   591  			// stop hash.
   592  			name:     "remote side chain, stop in main after more",
   593  			locator:  remoteView.BlockLocator(nil),
   594  			hashStop: branch0Nodes[16].hash,
   595  			headers:  nodeHeaders(branch0Nodes, 15, 16),
   596  			hashes:   nodeHashes(branch0Nodes, 15, 16),
   597  		},
   598  		{
   599  			// Locators based on remote being on main chain in the
   600  			// past and a stop hash local node doesn't know about.
   601  			// The expected result is the blocks after the known
   602  			// point in the main chain and the stop hash has no
   603  			// effect.
   604  			name:     "remote main chain past, unknown stop",
   605  			locator:  localView.BlockLocator(branch0Nodes[12]),
   606  			hashStop: chainhash.Hash{0x01},
   607  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   608  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   609  		},
   610  		{
   611  			// Locators based on remote being on main chain in the
   612  			// past and a stop hash in a side chain.  The expected
   613  			// result is the blocks after the known point in the
   614  			// main chain and the stop hash has no effect.
   615  			name:     "remote main chain past, stop in side",
   616  			locator:  localView.BlockLocator(branch0Nodes[12]),
   617  			hashStop: tip(branch1Nodes).hash,
   618  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   619  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   620  		},
   621  		{
   622  			// Locators based on remote being on main chain in the
   623  			// past and a stop hash in the main chain before that
   624  			// point.  The expected result is the blocks after the
   625  			// known point in the main chain and the stop hash has
   626  			// no effect.
   627  			name:     "remote main chain past, stop in main before",
   628  			locator:  localView.BlockLocator(branch0Nodes[12]),
   629  			hashStop: branch0Nodes[11].hash,
   630  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   631  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   632  		},
   633  		{
   634  			// Locators based on remote being on main chain in the
   635  			// past and a stop hash in the main chain exactly at that
   636  			// point.  The expected result is the blocks after the
   637  			// known point in the main chain and the stop hash has
   638  			// no effect.
   639  			name:     "remote main chain past, stop in main exact",
   640  			locator:  localView.BlockLocator(branch0Nodes[12]),
   641  			hashStop: branch0Nodes[12].hash,
   642  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   643  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   644  		},
   645  		{
   646  			// Locators based on remote being on main chain in the
   647  			// past and a stop hash in the main chain just after
   648  			// that point.  The expected result is the blocks after
   649  			// the known point in the main chain and the stop hash
   650  			// has no effect.
   651  			name:     "remote main chain past, stop in main after",
   652  			locator:  localView.BlockLocator(branch0Nodes[12]),
   653  			hashStop: branch0Nodes[13].hash,
   654  			headers:  nodeHeaders(branch0Nodes, 13),
   655  			hashes:   nodeHashes(branch0Nodes, 13),
   656  		},
   657  		{
   658  			// Locators based on remote being on main chain in the
   659  			// past and a stop hash in the main chain some time
   660  			// after that point.  The expected result is the blocks
   661  			// after the known point in the main chain and the stop
   662  			// hash has no effect.
   663  			name:     "remote main chain past, stop in main after more",
   664  			locator:  localView.BlockLocator(branch0Nodes[12]),
   665  			hashStop: branch0Nodes[15].hash,
   666  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15),
   667  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15),
   668  		},
   669  		{
   670  			// Locators based on remote being at exactly the same
   671  			// point in the main chain and a stop hash local node
   672  			// doesn't know about.  The expected result is no
   673  			// located inventory.
   674  			name:     "remote main chain same, unknown stop",
   675  			locator:  localView.BlockLocator(nil),
   676  			hashStop: chainhash.Hash{0x01},
   677  			headers:  nil,
   678  			hashes:   nil,
   679  		},
   680  		{
   681  			// Locators based on remote being at exactly the same
   682  			// point in the main chain and a stop hash at exactly
   683  			// the same point.  The expected result is no located
   684  			// inventory.
   685  			name:     "remote main chain same, stop same point",
   686  			locator:  localView.BlockLocator(nil),
   687  			hashStop: tip(branch0Nodes).hash,
   688  			headers:  nil,
   689  			hashes:   nil,
   690  		},
   691  		{
   692  			// Locators from remote that don't include any blocks
   693  			// the local node knows.  This would happen if the
   694  			// remote node is on a completely separate chain that
   695  			// isn't rooted with the same genesis block.  The
   696  			// expected result is the blocks after the genesis
   697  			// block.
   698  			name:     "remote unrelated chain",
   699  			locator:  unrelatedView.BlockLocator(nil),
   700  			hashStop: chainhash.Hash{},
   701  			headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   702  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   703  			hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   704  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   705  		},
   706  		{
   707  			// Locators from remote for second block in main chain
   708  			// and no stop hash, but with an overridden max limit.
   709  			// The expected result is the blocks after the second
   710  			// block limited by the max.
   711  			name:       "remote genesis",
   712  			locator:    locatorHashes(branch0Nodes, 0),
   713  			hashStop:   chainhash.Hash{},
   714  			maxAllowed: 3,
   715  			headers:    nodeHeaders(branch0Nodes, 1, 2, 3),
   716  			hashes:     nodeHashes(branch0Nodes, 1, 2, 3),
   717  		},
   718  		{
   719  			// Poorly formed locator.
   720  			//
   721  			// Locator from remote that only includes a single
   722  			// block on a side chain the local node knows.  The
   723  			// expected result is the blocks after the genesis
   724  			// block since even though the block is known, it is on
   725  			// a side chain and there are no more locators to find
   726  			// the fork point.
   727  			name:     "weak locator, single known side block",
   728  			locator:  locatorHashes(branch1Nodes, 1),
   729  			hashStop: chainhash.Hash{},
   730  			headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   731  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   732  			hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   733  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   734  		},
   735  		{
   736  			// Poorly formed locator.
   737  			//
   738  			// Locator from remote that only includes multiple
   739  			// blocks on a side chain the local node knows however
   740  			// none in the main chain.  The expected result is the
   741  			// blocks after the genesis block since even though the
   742  			// blocks are known, they are all on a side chain and
   743  			// there are no more locators to find the fork point.
   744  			name:     "weak locator, multiple known side blocks",
   745  			locator:  locatorHashes(branch1Nodes, 1),
   746  			hashStop: chainhash.Hash{},
   747  			headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   748  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   749  			hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   750  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   751  		},
   752  		{
   753  			// Poorly formed locator.
   754  			//
   755  			// Locator from remote that only includes multiple
   756  			// blocks on a side chain the local node knows however
   757  			// none in the main chain but includes a stop hash in
   758  			// the main chain.  The expected result is the blocks
   759  			// after the genesis block up to the stop hash since
   760  			// even though the blocks are known, they are all on a
   761  			// side chain and there are no more locators to find the
   762  			// fork point.
   763  			name:     "weak locator, multiple known side blocks, stop in main",
   764  			locator:  locatorHashes(branch1Nodes, 1),
   765  			hashStop: branch0Nodes[5].hash,
   766  			headers:  nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5),
   767  			hashes:   nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5),
   768  		},
   769  	}
   770  	for _, test := range tests {
   771  		// Ensure the expected headers are located.
   772  		var headers []wire.BlockHeader
   773  		if test.maxAllowed != 0 {
   774  			// Need to use the unexported function to override the
   775  			// max allowed for headers.
   776  			chain.chainLock.RLock()
   777  			headers = chain.locateHeaders(test.locator,
   778  				&test.hashStop, test.maxAllowed)
   779  			chain.chainLock.RUnlock()
   780  		} else {
   781  			headers = chain.LocateHeaders(test.locator,
   782  				&test.hashStop)
   783  		}
   784  		if !reflect.DeepEqual(headers, test.headers) {
   785  			t.Errorf("%s: unxpected headers -- got %v, want %v",
   786  				test.name, headers, test.headers)
   787  			continue
   788  		}
   789  
   790  		// Ensure the expected block hashes are located.
   791  		maxAllowed := uint32(wire.MaxBlocksPerMsg)
   792  		if test.maxAllowed != 0 {
   793  			maxAllowed = test.maxAllowed
   794  		}
   795  		hashes := chain.LocateBlocks(test.locator, &test.hashStop,
   796  			maxAllowed)
   797  		if !reflect.DeepEqual(hashes, test.hashes) {
   798  			t.Errorf("%s: unxpected hashes -- got %v, want %v",
   799  				test.name, hashes, test.hashes)
   800  			continue
   801  		}
   802  	}
   803  }
   804  
   805  // TestHeightToHashRange ensures that fetching a range of block hashes by start
   806  // height and end hash works as expected.
   807  func TestHeightToHashRange(t *testing.T) {
   808  	// Construct a synthetic block chain with a block index consisting of
   809  	// the following structure.
   810  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   811  	// 	                              \-> 16a -> 17a -> 18a (unvalidated)
   812  	tip := tstTip
   813  	chain := newFakeChain(&chaincfg.MainNetParams)
   814  	branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
   815  	branch1Nodes := chainedNodes(branch0Nodes[14], 3)
   816  	for _, node := range branch0Nodes {
   817  		chain.index.SetStatusFlags(node, statusValid)
   818  		chain.index.AddNode(node)
   819  	}
   820  	for _, node := range branch1Nodes {
   821  		if node.height < 18 {
   822  			chain.index.SetStatusFlags(node, statusValid)
   823  		}
   824  		chain.index.AddNode(node)
   825  	}
   826  	chain.bestChain.SetTip(tip(branch0Nodes))
   827  
   828  	tests := []struct {
   829  		name        string
   830  		startHeight int32            // locator for requested inventory
   831  		endHash     chainhash.Hash   // stop hash for locator
   832  		maxResults  int              // max to locate, 0 = wire const
   833  		hashes      []chainhash.Hash // expected located hashes
   834  		expectError bool
   835  	}{
   836  		{
   837  			name:        "blocks below tip",
   838  			startHeight: 11,
   839  			endHash:     branch0Nodes[14].hash,
   840  			maxResults:  10,
   841  			hashes:      nodeHashes(branch0Nodes, 10, 11, 12, 13, 14),
   842  		},
   843  		{
   844  			name:        "blocks on main chain",
   845  			startHeight: 15,
   846  			endHash:     branch0Nodes[17].hash,
   847  			maxResults:  10,
   848  			hashes:      nodeHashes(branch0Nodes, 14, 15, 16, 17),
   849  		},
   850  		{
   851  			name:        "blocks on stale chain",
   852  			startHeight: 15,
   853  			endHash:     branch1Nodes[1].hash,
   854  			maxResults:  10,
   855  			hashes: append(nodeHashes(branch0Nodes, 14),
   856  				nodeHashes(branch1Nodes, 0, 1)...),
   857  		},
   858  		{
   859  			name:        "invalid start height",
   860  			startHeight: 19,
   861  			endHash:     branch0Nodes[17].hash,
   862  			maxResults:  10,
   863  			expectError: true,
   864  		},
   865  		{
   866  			name:        "too many results",
   867  			startHeight: 1,
   868  			endHash:     branch0Nodes[17].hash,
   869  			maxResults:  10,
   870  			expectError: true,
   871  		},
   872  		{
   873  			name:        "unvalidated block",
   874  			startHeight: 15,
   875  			endHash:     branch1Nodes[2].hash,
   876  			maxResults:  10,
   877  			expectError: true,
   878  		},
   879  	}
   880  	for _, test := range tests {
   881  		hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash,
   882  			test.maxResults)
   883  		if err != nil {
   884  			if !test.expectError {
   885  				t.Errorf("%s: unexpected error: %v", test.name, err)
   886  			}
   887  			continue
   888  		}
   889  
   890  		if !reflect.DeepEqual(hashes, test.hashes) {
   891  			t.Errorf("%s: unxpected hashes -- got %v, want %v",
   892  				test.name, hashes, test.hashes)
   893  		}
   894  	}
   895  }
   896  
   897  // TestIntervalBlockHashes ensures that fetching block hashes at specified
   898  // intervals by end hash works as expected.
   899  func TestIntervalBlockHashes(t *testing.T) {
   900  	// Construct a synthetic block chain with a block index consisting of
   901  	// the following structure.
   902  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   903  	// 	                              \-> 16a -> 17a -> 18a (unvalidated)
   904  	tip := tstTip
   905  	chain := newFakeChain(&chaincfg.MainNetParams)
   906  	branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
   907  	branch1Nodes := chainedNodes(branch0Nodes[14], 3)
   908  	for _, node := range branch0Nodes {
   909  		chain.index.SetStatusFlags(node, statusValid)
   910  		chain.index.AddNode(node)
   911  	}
   912  	for _, node := range branch1Nodes {
   913  		if node.height < 18 {
   914  			chain.index.SetStatusFlags(node, statusValid)
   915  		}
   916  		chain.index.AddNode(node)
   917  	}
   918  	chain.bestChain.SetTip(tip(branch0Nodes))
   919  
   920  	tests := []struct {
   921  		name        string
   922  		endHash     chainhash.Hash
   923  		interval    int
   924  		hashes      []chainhash.Hash
   925  		expectError bool
   926  	}{
   927  		{
   928  			name:     "blocks on main chain",
   929  			endHash:  branch0Nodes[17].hash,
   930  			interval: 8,
   931  			hashes:   nodeHashes(branch0Nodes, 7, 15),
   932  		},
   933  		{
   934  			name:     "blocks on stale chain",
   935  			endHash:  branch1Nodes[1].hash,
   936  			interval: 8,
   937  			hashes: append(nodeHashes(branch0Nodes, 7),
   938  				nodeHashes(branch1Nodes, 0)...),
   939  		},
   940  		{
   941  			name:     "no results",
   942  			endHash:  branch0Nodes[17].hash,
   943  			interval: 20,
   944  			hashes:   []chainhash.Hash{},
   945  		},
   946  		{
   947  			name:        "unvalidated block",
   948  			endHash:     branch1Nodes[2].hash,
   949  			interval:    8,
   950  			expectError: true,
   951  		},
   952  	}
   953  	for _, test := range tests {
   954  		hashes, err := chain.IntervalBlockHashes(&test.endHash, test.interval)
   955  		if err != nil {
   956  			if !test.expectError {
   957  				t.Errorf("%s: unexpected error: %v", test.name, err)
   958  			}
   959  			continue
   960  		}
   961  
   962  		if !reflect.DeepEqual(hashes, test.hashes) {
   963  			t.Errorf("%s: unxpected hashes -- got %v, want %v",
   964  				test.name, hashes, test.hashes)
   965  		}
   966  	}
   967  }
   968  
   969  func TestChainTips(t *testing.T) {
   970  	tests := []struct {
   971  		name        string
   972  		chainTipGen func() (*BlockChain, map[chainhash.Hash]ChainTip)
   973  	}{
   974  		{
   975  			name: "one active chain tip",
   976  			chainTipGen: func() (*BlockChain, map[chainhash.Hash]ChainTip) {
   977  				// Construct a synthetic block chain with a block index consisting of
   978  				// the following structure.
   979  				// 	genesis -> 1 -> 2 -> 3
   980  				tip := tstTip
   981  				chain := newFakeChain(&chaincfg.MainNetParams)
   982  				branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 3)
   983  				for _, node := range branch0Nodes {
   984  					chain.index.SetStatusFlags(node, statusDataStored)
   985  					chain.index.SetStatusFlags(node, statusValid)
   986  					chain.index.AddNode(node)
   987  				}
   988  				chain.bestChain.SetTip(tip(branch0Nodes))
   989  
   990  				activeTip := ChainTip{
   991  					Height:    3,
   992  					BlockHash: (tip(branch0Nodes)).hash,
   993  					BranchLen: 0,
   994  					Status:    StatusActive,
   995  				}
   996  				chainTips := make(map[chainhash.Hash]ChainTip)
   997  				chainTips[activeTip.BlockHash] = activeTip
   998  
   999  				return chain, chainTips
  1000  			},
  1001  		},
  1002  		{
  1003  			name: "one active chain tip, one unknown chain tip",
  1004  			chainTipGen: func() (*BlockChain, map[chainhash.Hash]ChainTip) {
  1005  				// Construct a synthetic block chain with a block index consisting of
  1006  				// the following structure.
  1007  				// 	genesis -> 1 -> 2 -> 3 ... -> 10 -> 11  -> 12  -> 13 (active)
  1008  				//                                      \-> 11a -> 12a (unknown)
  1009  				tip := tstTip
  1010  				chain := newFakeChain(&chaincfg.MainNetParams)
  1011  				branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 13)
  1012  				for _, node := range branch0Nodes {
  1013  					chain.index.SetStatusFlags(node, statusDataStored)
  1014  					chain.index.SetStatusFlags(node, statusValid)
  1015  					chain.index.AddNode(node)
  1016  				}
  1017  				chain.bestChain.SetTip(tip(branch0Nodes))
  1018  
  1019  				branch1Nodes := chainedNodes(branch0Nodes[9], 2)
  1020  				for _, node := range branch1Nodes {
  1021  					chain.index.AddNode(node)
  1022  				}
  1023  
  1024  				activeTip := ChainTip{
  1025  					Height:    13,
  1026  					BlockHash: (tip(branch0Nodes)).hash,
  1027  					BranchLen: 0,
  1028  					Status:    StatusActive,
  1029  				}
  1030  				unknownTip := ChainTip{
  1031  					Height:    12,
  1032  					BlockHash: (tip(branch1Nodes)).hash,
  1033  					BranchLen: 2,
  1034  					Status:    StatusUnknown,
  1035  				}
  1036  				chainTips := make(map[chainhash.Hash]ChainTip)
  1037  				chainTips[activeTip.BlockHash] = activeTip
  1038  				chainTips[unknownTip.BlockHash] = unknownTip
  1039  
  1040  				return chain, chainTips
  1041  			},
  1042  		},
  1043  		{
  1044  			name: "1 inactive tip, 1 invalid tip, 1 active tip",
  1045  			chainTipGen: func() (*BlockChain, map[chainhash.Hash]ChainTip) {
  1046  				// Construct a synthetic block chain with a block index consisting of
  1047  				// the following structure.
  1048  				// 	genesis -> 1  -> 2  -> 3 (active)
  1049  				//            \ -> 1a (valid-fork)
  1050  				//            \ -> 1b (invalid)
  1051  				tip := tstTip
  1052  				chain := newFakeChain(&chaincfg.MainNetParams)
  1053  				branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 3)
  1054  				for _, node := range branch0Nodes {
  1055  					chain.index.SetStatusFlags(node, statusDataStored)
  1056  					chain.index.SetStatusFlags(node, statusValid)
  1057  					chain.index.AddNode(node)
  1058  				}
  1059  				chain.bestChain.SetTip(tip(branch0Nodes))
  1060  
  1061  				branch1Nodes := chainedNodes(chain.bestChain.Genesis(), 1)
  1062  				for _, node := range branch1Nodes {
  1063  					chain.index.SetStatusFlags(node, statusDataStored)
  1064  					chain.index.SetStatusFlags(node, statusValid)
  1065  					chain.index.AddNode(node)
  1066  				}
  1067  
  1068  				branch2Nodes := chainedNodes(chain.bestChain.Genesis(), 1)
  1069  				for _, node := range branch2Nodes {
  1070  					chain.index.SetStatusFlags(node, statusDataStored)
  1071  					chain.index.SetStatusFlags(node, statusValidateFailed)
  1072  					chain.index.AddNode(node)
  1073  				}
  1074  
  1075  				activeTip := ChainTip{
  1076  					Height:    tip(branch0Nodes).height,
  1077  					BlockHash: (tip(branch0Nodes)).hash,
  1078  					BranchLen: 0,
  1079  					Status:    StatusActive,
  1080  				}
  1081  
  1082  				inactiveTip := ChainTip{
  1083  					Height:    tip(branch1Nodes).height,
  1084  					BlockHash: (tip(branch1Nodes)).hash,
  1085  					BranchLen: 1,
  1086  					Status:    StatusValidFork,
  1087  				}
  1088  
  1089  				invalidTip := ChainTip{
  1090  					Height:    tip(branch2Nodes).height,
  1091  					BlockHash: (tip(branch2Nodes)).hash,
  1092  					BranchLen: 1,
  1093  					Status:    StatusInvalid,
  1094  				}
  1095  
  1096  				chainTips := make(map[chainhash.Hash]ChainTip)
  1097  				chainTips[activeTip.BlockHash] = activeTip
  1098  				chainTips[inactiveTip.BlockHash] = inactiveTip
  1099  				chainTips[invalidTip.BlockHash] = invalidTip
  1100  
  1101  				return chain, chainTips
  1102  			},
  1103  		},
  1104  	}
  1105  
  1106  	for _, test := range tests {
  1107  		chain, expectedChainTips := test.chainTipGen()
  1108  		gotChainTips := chain.ChainTips()
  1109  		if len(gotChainTips) != len(expectedChainTips) {
  1110  			t.Errorf("TestChainTips Failed test %s. Expected %d "+
  1111  				"chain tips, got %d", test.name, len(expectedChainTips), len(gotChainTips))
  1112  		}
  1113  
  1114  		for _, gotChainTip := range gotChainTips {
  1115  			testChainTip, found := expectedChainTips[gotChainTip.BlockHash]
  1116  			if !found {
  1117  				t.Errorf("TestChainTips Failed test %s. Couldn't find an expected "+
  1118  					"chain tip with height %d, hash %s, branchlen %d, status \"%s\"",
  1119  					test.name, testChainTip.Height, testChainTip.BlockHash.String(),
  1120  					testChainTip.BranchLen, testChainTip.Status.String())
  1121  			}
  1122  
  1123  			if !reflect.DeepEqual(testChainTip, gotChainTip) {
  1124  				t.Errorf("TestChainTips Failed test %s. Expected chain tip with "+
  1125  					"height %d, hash %s, branchlen %d, status \"%s\" but got "+
  1126  					"height %d, hash %s, branchlen %d, status \"%s\"", test.name,
  1127  					testChainTip.Height, testChainTip.BlockHash.String(),
  1128  					testChainTip.BranchLen, testChainTip.Status.String(),
  1129  					gotChainTip.Height, gotChainTip.BlockHash.String(),
  1130  					gotChainTip.BranchLen, gotChainTip.Status.String())
  1131  			}
  1132  
  1133  			switch testChainTip.Status {
  1134  			case StatusActive:
  1135  				if testChainTip.Status.String() != "active" {
  1136  					t.Errorf("TestChainTips Fail: Expected string of \"active\", got \"%s\"",
  1137  						testChainTip.Status.String())
  1138  				}
  1139  			case StatusInvalid:
  1140  				if testChainTip.Status.String() != "invalid" {
  1141  					t.Errorf("TestChainTips Fail: Expected string of \"invalid\", got \"%s\"",
  1142  						testChainTip.Status.String())
  1143  				}
  1144  			case StatusValidFork:
  1145  				if testChainTip.Status.String() != "valid-fork" {
  1146  					t.Errorf("TestChainTips Fail: Expected string of \"valid-fork\", got \"%s\"",
  1147  						testChainTip.Status.String())
  1148  				}
  1149  			case StatusUnknown:
  1150  				if testChainTip.Status.String() != fmt.Sprintf("unknown: %b", testChainTip.Status) {
  1151  					t.Errorf("TestChainTips Fail: Expected string of \"unknown\", got \"%s\"",
  1152  						testChainTip.Status.String())
  1153  				}
  1154  			}
  1155  		}
  1156  	}
  1157  }