github.com/lbryio/lbcd@v0.22.119/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  	"reflect"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/lbryio/lbcd/chaincfg"
    13  	"github.com/lbryio/lbcd/chaincfg/chainhash"
    14  	"github.com/lbryio/lbcd/wire"
    15  	btcutil "github.com/lbryio/lbcutil"
    16  )
    17  
    18  // TestCalcSequenceLock tests the LockTimeToSequence function, and the
    19  // CalcSequenceLock method of a Chain instance. The tests exercise several
    20  // combinations of inputs to the CalcSequenceLock function in order to ensure
    21  // the returned SequenceLocks are correct for each test instance.
    22  func TestCalcSequenceLock(t *testing.T) {
    23  	netParams := &chaincfg.SimNetParams
    24  
    25  	// We need to activate CSV in order to test the processing logic, so
    26  	// manually craft the block version that's used to signal the soft-fork
    27  	// activation.
    28  	csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber
    29  	blockVersion := int32(0x20000000 | (uint32(1) << csvBit))
    30  
    31  	// Generate enough synthetic blocks to activate CSV.
    32  	chain := newFakeChain(netParams)
    33  	node := chain.bestChain.Tip()
    34  	blockTime := node.Header().Timestamp
    35  	numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
    36  	for i := uint32(0); i < numBlocksToActivate; i++ {
    37  		blockTime = blockTime.Add(time.Second)
    38  		node = newFakeNode(node, blockVersion, 0, blockTime)
    39  		chain.index.AddNode(node)
    40  		chain.bestChain.SetTip(node)
    41  	}
    42  
    43  	// Create a utxo view with a fake utxo for the inputs used in the
    44  	// transactions created below.  This utxo is added such that it has an
    45  	// age of 4 blocks.
    46  	targetTx := btcutil.NewTx(&wire.MsgTx{
    47  		TxOut: []*wire.TxOut{{
    48  			PkScript: nil,
    49  			Value:    10,
    50  		}},
    51  	})
    52  	utxoView := NewUtxoViewpoint()
    53  	utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4)
    54  	utxoView.SetBestHash(&node.hash)
    55  
    56  	// Create a utxo that spends the fake utxo created above for use in the
    57  	// transactions created in the tests.  It has an age of 4 blocks.  Note
    58  	// that the sequence lock heights are always calculated from the same
    59  	// point of view that they were originally calculated from for a given
    60  	// utxo.  That is to say, the height prior to it.
    61  	utxo := wire.OutPoint{
    62  		Hash:  *targetTx.Hash(),
    63  		Index: 0,
    64  	}
    65  	prevUtxoHeight := int32(numBlocksToActivate) - 4
    66  
    67  	// Obtain the median time past from the PoV of the input created above.
    68  	// The MTP for the input is the MTP from the PoV of the block *prior*
    69  	// to the one that included it.
    70  	medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix()
    71  
    72  	// The median time calculated from the PoV of the best block in the
    73  	// test chain.  For unconfirmed inputs, this value will be used since
    74  	// the MTP will be calculated from the PoV of the yet-to-be-mined
    75  	// block.
    76  	nextMedianTime := node.CalcPastMedianTime().Unix()
    77  	nextBlockHeight := int32(numBlocksToActivate) + 1
    78  
    79  	// Add an additional transaction which will serve as our unconfirmed
    80  	// output.
    81  	unConfTx := &wire.MsgTx{
    82  		TxOut: []*wire.TxOut{{
    83  			PkScript: nil,
    84  			Value:    5,
    85  		}},
    86  	}
    87  	unConfUtxo := wire.OutPoint{
    88  		Hash:  unConfTx.TxHash(),
    89  		Index: 0,
    90  	}
    91  
    92  	// Adding a utxo with a height of 0x7fffffff indicates that the output
    93  	// is currently unmined.
    94  	utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff)
    95  
    96  	tests := []struct {
    97  		tx      *wire.MsgTx
    98  		view    *UtxoViewpoint
    99  		mempool bool
   100  		want    *SequenceLock
   101  	}{
   102  		// A transaction of version one should disable sequence locks
   103  		// as the new sequence number semantics only apply to
   104  		// transactions version 2 or higher.
   105  		{
   106  			tx: &wire.MsgTx{
   107  				Version: 1,
   108  				TxIn: []*wire.TxIn{{
   109  					PreviousOutPoint: utxo,
   110  					Sequence:         LockTimeToSequence(false, 3),
   111  				}},
   112  			},
   113  			view: utxoView,
   114  			want: &SequenceLock{
   115  				Seconds:     -1,
   116  				BlockHeight: -1,
   117  			},
   118  		},
   119  		// A transaction with a single input with max sequence number.
   120  		// This sequence number has the high bit set, so sequence locks
   121  		// should be disabled.
   122  		{
   123  			tx: &wire.MsgTx{
   124  				Version: 2,
   125  				TxIn: []*wire.TxIn{{
   126  					PreviousOutPoint: utxo,
   127  					Sequence:         wire.MaxTxInSequenceNum,
   128  				}},
   129  			},
   130  			view: utxoView,
   131  			want: &SequenceLock{
   132  				Seconds:     -1,
   133  				BlockHeight: -1,
   134  			},
   135  		},
   136  		// A transaction with a single input whose lock time is
   137  		// expressed in seconds.  However, the specified lock time is
   138  		// below the required floor for time based lock times since
   139  		// they have time granularity of 512 seconds.  As a result, the
   140  		// seconds lock-time should be just before the median time of
   141  		// the targeted block.
   142  		{
   143  			tx: &wire.MsgTx{
   144  				Version: 2,
   145  				TxIn: []*wire.TxIn{{
   146  					PreviousOutPoint: utxo,
   147  					Sequence:         LockTimeToSequence(true, 2),
   148  				}},
   149  			},
   150  			view: utxoView,
   151  			want: &SequenceLock{
   152  				Seconds:     medianTime - 1,
   153  				BlockHeight: -1,
   154  			},
   155  		},
   156  		// A transaction with a single input whose lock time is
   157  		// expressed in seconds.  The number of seconds should be 1023
   158  		// seconds after the median past time of the last block in the
   159  		// chain.
   160  		{
   161  			tx: &wire.MsgTx{
   162  				Version: 2,
   163  				TxIn: []*wire.TxIn{{
   164  					PreviousOutPoint: utxo,
   165  					Sequence:         LockTimeToSequence(true, 1024),
   166  				}},
   167  			},
   168  			view: utxoView,
   169  			want: &SequenceLock{
   170  				Seconds:     medianTime + 1023,
   171  				BlockHeight: -1,
   172  			},
   173  		},
   174  		// A transaction with multiple inputs.  The first input has a
   175  		// lock time expressed in seconds.  The second input has a
   176  		// sequence lock in blocks with a value of 4.  The last input
   177  		// has a sequence number with a value of 5, but has the disable
   178  		// bit set.  So the first lock should be selected as it's the
   179  		// latest lock that isn't disabled.
   180  		{
   181  			tx: &wire.MsgTx{
   182  				Version: 2,
   183  				TxIn: []*wire.TxIn{{
   184  					PreviousOutPoint: utxo,
   185  					Sequence:         LockTimeToSequence(true, 2560),
   186  				}, {
   187  					PreviousOutPoint: utxo,
   188  					Sequence:         LockTimeToSequence(false, 4),
   189  				}, {
   190  					PreviousOutPoint: utxo,
   191  					Sequence: LockTimeToSequence(false, 5) |
   192  						wire.SequenceLockTimeDisabled,
   193  				}},
   194  			},
   195  			view: utxoView,
   196  			want: &SequenceLock{
   197  				Seconds:     medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
   198  				BlockHeight: prevUtxoHeight + 3,
   199  			},
   200  		},
   201  		// Transaction with a single input.  The input's sequence number
   202  		// encodes a relative lock-time in blocks (3 blocks).  The
   203  		// sequence lock should  have a value of -1 for seconds, but a
   204  		// height of 2 meaning it can be included at height 3.
   205  		{
   206  			tx: &wire.MsgTx{
   207  				Version: 2,
   208  				TxIn: []*wire.TxIn{{
   209  					PreviousOutPoint: utxo,
   210  					Sequence:         LockTimeToSequence(false, 3),
   211  				}},
   212  			},
   213  			view: utxoView,
   214  			want: &SequenceLock{
   215  				Seconds:     -1,
   216  				BlockHeight: prevUtxoHeight + 2,
   217  			},
   218  		},
   219  		// A transaction with two inputs with lock times expressed in
   220  		// seconds.  The selected sequence lock value for seconds should
   221  		// be the time further in the future.
   222  		{
   223  			tx: &wire.MsgTx{
   224  				Version: 2,
   225  				TxIn: []*wire.TxIn{{
   226  					PreviousOutPoint: utxo,
   227  					Sequence:         LockTimeToSequence(true, 5120),
   228  				}, {
   229  					PreviousOutPoint: utxo,
   230  					Sequence:         LockTimeToSequence(true, 2560),
   231  				}},
   232  			},
   233  			view: utxoView,
   234  			want: &SequenceLock{
   235  				Seconds:     medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
   236  				BlockHeight: -1,
   237  			},
   238  		},
   239  		// A transaction with two inputs with lock times expressed in
   240  		// blocks.  The selected sequence lock value for blocks should
   241  		// be the height further in the future, so a height of 10
   242  		// indicating it can be included at height 11.
   243  		{
   244  			tx: &wire.MsgTx{
   245  				Version: 2,
   246  				TxIn: []*wire.TxIn{{
   247  					PreviousOutPoint: utxo,
   248  					Sequence:         LockTimeToSequence(false, 1),
   249  				}, {
   250  					PreviousOutPoint: utxo,
   251  					Sequence:         LockTimeToSequence(false, 11),
   252  				}},
   253  			},
   254  			view: utxoView,
   255  			want: &SequenceLock{
   256  				Seconds:     -1,
   257  				BlockHeight: prevUtxoHeight + 10,
   258  			},
   259  		},
   260  		// A transaction with multiple inputs.  Two inputs are time
   261  		// based, and the other two are block based. The lock lying
   262  		// further into the future for both inputs should be chosen.
   263  		{
   264  			tx: &wire.MsgTx{
   265  				Version: 2,
   266  				TxIn: []*wire.TxIn{{
   267  					PreviousOutPoint: utxo,
   268  					Sequence:         LockTimeToSequence(true, 2560),
   269  				}, {
   270  					PreviousOutPoint: utxo,
   271  					Sequence:         LockTimeToSequence(true, 6656),
   272  				}, {
   273  					PreviousOutPoint: utxo,
   274  					Sequence:         LockTimeToSequence(false, 3),
   275  				}, {
   276  					PreviousOutPoint: utxo,
   277  					Sequence:         LockTimeToSequence(false, 9),
   278  				}},
   279  			},
   280  			view: utxoView,
   281  			want: &SequenceLock{
   282  				Seconds:     medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
   283  				BlockHeight: prevUtxoHeight + 8,
   284  			},
   285  		},
   286  		// A transaction with a single unconfirmed input.  As the input
   287  		// is confirmed, the height of the input should be interpreted
   288  		// as the height of the *next* block.  So, a 2 block relative
   289  		// lock means the sequence lock should be for 1 block after the
   290  		// *next* block height, indicating it can be included 2 blocks
   291  		// after that.
   292  		{
   293  			tx: &wire.MsgTx{
   294  				Version: 2,
   295  				TxIn: []*wire.TxIn{{
   296  					PreviousOutPoint: unConfUtxo,
   297  					Sequence:         LockTimeToSequence(false, 2),
   298  				}},
   299  			},
   300  			view:    utxoView,
   301  			mempool: true,
   302  			want: &SequenceLock{
   303  				Seconds:     -1,
   304  				BlockHeight: nextBlockHeight + 1,
   305  			},
   306  		},
   307  		// A transaction with a single unconfirmed input.  The input has
   308  		// a time based lock, so the lock time should be based off the
   309  		// MTP of the *next* block.
   310  		{
   311  			tx: &wire.MsgTx{
   312  				Version: 2,
   313  				TxIn: []*wire.TxIn{{
   314  					PreviousOutPoint: unConfUtxo,
   315  					Sequence:         LockTimeToSequence(true, 1024),
   316  				}},
   317  			},
   318  			view:    utxoView,
   319  			mempool: true,
   320  			want: &SequenceLock{
   321  				Seconds:     nextMedianTime + 1023,
   322  				BlockHeight: -1,
   323  			},
   324  		},
   325  	}
   326  
   327  	t.Logf("Running %v SequenceLock tests", len(tests))
   328  	for i, test := range tests {
   329  		utilTx := btcutil.NewTx(test.tx)
   330  		seqLock, err := chain.CalcSequenceLock(utilTx, test.view, test.mempool)
   331  		if err != nil {
   332  			t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err)
   333  		}
   334  
   335  		if seqLock.Seconds != test.want.Seconds {
   336  			t.Fatalf("test #%d got %v seconds want %v seconds",
   337  				i, seqLock.Seconds, test.want.Seconds)
   338  		}
   339  		if seqLock.BlockHeight != test.want.BlockHeight {
   340  			t.Fatalf("test #%d got height of %v want height of %v ",
   341  				i, seqLock.BlockHeight, test.want.BlockHeight)
   342  		}
   343  	}
   344  }
   345  
   346  // nodeHashes is a convenience function that returns the hashes for all of the
   347  // passed indexes of the provided nodes.  It is used to construct expected hash
   348  // slices in the tests.
   349  func nodeHashes(nodes []*blockNode, indexes ...int) []chainhash.Hash {
   350  	hashes := make([]chainhash.Hash, 0, len(indexes))
   351  	for _, idx := range indexes {
   352  		hashes = append(hashes, nodes[idx].hash)
   353  	}
   354  	return hashes
   355  }
   356  
   357  // nodeHeaders is a convenience function that returns the headers for all of
   358  // the passed indexes of the provided nodes.  It is used to construct expected
   359  // located headers in the tests.
   360  func nodeHeaders(nodes []*blockNode, indexes ...int) []wire.BlockHeader {
   361  	headers := make([]wire.BlockHeader, 0, len(indexes))
   362  	for _, idx := range indexes {
   363  		headers = append(headers, nodes[idx].Header())
   364  	}
   365  	return headers
   366  }
   367  
   368  // TestLocateInventory ensures that locating inventory via the LocateHeaders and
   369  // LocateBlocks functions behaves as expected.
   370  func TestLocateInventory(t *testing.T) {
   371  	// Construct a synthetic block chain with a block index consisting of
   372  	// the following structure.
   373  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   374  	// 	                              \-> 16a -> 17a
   375  	tip := tstTip
   376  	chain := newFakeChain(&chaincfg.MainNetParams)
   377  	branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
   378  	branch1Nodes := chainedNodes(branch0Nodes[14], 2)
   379  	for _, node := range branch0Nodes {
   380  		chain.index.AddNode(node)
   381  	}
   382  	for _, node := range branch1Nodes {
   383  		chain.index.AddNode(node)
   384  	}
   385  	chain.bestChain.SetTip(tip(branch0Nodes))
   386  
   387  	// Create chain views for different branches of the overall chain to
   388  	// simulate a local and remote node on different parts of the chain.
   389  	localView := newChainView(tip(branch0Nodes))
   390  	remoteView := newChainView(tip(branch1Nodes))
   391  
   392  	// Create a chain view for a completely unrelated block chain to
   393  	// simulate a remote node on a totally different chain.
   394  	unrelatedBranchNodes := chainedNodes(nil, 5)
   395  	unrelatedView := newChainView(tip(unrelatedBranchNodes))
   396  
   397  	tests := []struct {
   398  		name       string
   399  		locator    BlockLocator       // locator for requested inventory
   400  		hashStop   chainhash.Hash     // stop hash for locator
   401  		maxAllowed uint32             // max to locate, 0 = wire const
   402  		headers    []wire.BlockHeader // expected located headers
   403  		hashes     []chainhash.Hash   // expected located hashes
   404  	}{
   405  		{
   406  			// Empty block locators and unknown stop hash.  No
   407  			// inventory should be located.
   408  			name:     "no locators, no stop",
   409  			locator:  nil,
   410  			hashStop: chainhash.Hash{},
   411  			headers:  nil,
   412  			hashes:   nil,
   413  		},
   414  		{
   415  			// Empty block locators and stop hash in side chain.
   416  			// The expected result is the requested block.
   417  			name:     "no locators, stop in side",
   418  			locator:  nil,
   419  			hashStop: tip(branch1Nodes).hash,
   420  			headers:  nodeHeaders(branch1Nodes, 1),
   421  			hashes:   nodeHashes(branch1Nodes, 1),
   422  		},
   423  		{
   424  			// Empty block locators and stop hash in main chain.
   425  			// The expected result is the requested block.
   426  			name:     "no locators, stop in main",
   427  			locator:  nil,
   428  			hashStop: branch0Nodes[12].hash,
   429  			headers:  nodeHeaders(branch0Nodes, 12),
   430  			hashes:   nodeHashes(branch0Nodes, 12),
   431  		},
   432  		{
   433  			// Locators based on remote being on side chain and a
   434  			// stop hash local node doesn't know about.  The
   435  			// expected result is the blocks after the fork point in
   436  			// the main chain and the stop hash has no effect.
   437  			name:     "remote side chain, unknown stop",
   438  			locator:  remoteView.BlockLocator(nil),
   439  			hashStop: chainhash.Hash{0x01},
   440  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   441  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   442  		},
   443  		{
   444  			// Locators based on remote being on side chain and a
   445  			// stop hash in side chain.  The expected result is the
   446  			// blocks after the fork point in the main chain and the
   447  			// stop hash has no effect.
   448  			name:     "remote side chain, stop in side",
   449  			locator:  remoteView.BlockLocator(nil),
   450  			hashStop: tip(branch1Nodes).hash,
   451  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   452  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   453  		},
   454  		{
   455  			// Locators based on remote being on side chain and a
   456  			// stop hash in main chain, but before fork point.  The
   457  			// expected result is the blocks after the fork point in
   458  			// the main chain and the stop hash has no effect.
   459  			name:     "remote side chain, stop in main before",
   460  			locator:  remoteView.BlockLocator(nil),
   461  			hashStop: branch0Nodes[13].hash,
   462  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   463  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   464  		},
   465  		{
   466  			// Locators based on remote being on side chain and a
   467  			// stop hash in main chain, but exactly at the fork
   468  			// point.  The expected result is the blocks after the
   469  			// fork point in the main chain and the stop hash has no
   470  			// effect.
   471  			name:     "remote side chain, stop in main exact",
   472  			locator:  remoteView.BlockLocator(nil),
   473  			hashStop: branch0Nodes[14].hash,
   474  			headers:  nodeHeaders(branch0Nodes, 15, 16, 17),
   475  			hashes:   nodeHashes(branch0Nodes, 15, 16, 17),
   476  		},
   477  		{
   478  			// Locators based on remote being on side chain and a
   479  			// stop hash in main chain just after the fork point.
   480  			// The expected result is the blocks after the fork
   481  			// point in the main chain up to and including the stop
   482  			// hash.
   483  			name:     "remote side chain, stop in main after",
   484  			locator:  remoteView.BlockLocator(nil),
   485  			hashStop: branch0Nodes[15].hash,
   486  			headers:  nodeHeaders(branch0Nodes, 15),
   487  			hashes:   nodeHashes(branch0Nodes, 15),
   488  		},
   489  		{
   490  			// Locators based on remote being on side chain and a
   491  			// stop hash in main chain some time after the fork
   492  			// point.  The expected result is the blocks after the
   493  			// fork point in the main chain up to and including the
   494  			// stop hash.
   495  			name:     "remote side chain, stop in main after more",
   496  			locator:  remoteView.BlockLocator(nil),
   497  			hashStop: branch0Nodes[16].hash,
   498  			headers:  nodeHeaders(branch0Nodes, 15, 16),
   499  			hashes:   nodeHashes(branch0Nodes, 15, 16),
   500  		},
   501  		{
   502  			// Locators based on remote being on main chain in the
   503  			// past and a stop hash local node doesn't know about.
   504  			// The expected result is the blocks after the known
   505  			// point in the main chain and the stop hash has no
   506  			// effect.
   507  			name:     "remote main chain past, unknown stop",
   508  			locator:  localView.BlockLocator(branch0Nodes[12]),
   509  			hashStop: chainhash.Hash{0x01},
   510  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   511  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   512  		},
   513  		{
   514  			// Locators based on remote being on main chain in the
   515  			// past and a stop hash in a side chain.  The expected
   516  			// result is the blocks after the known point in the
   517  			// main chain and the stop hash has no effect.
   518  			name:     "remote main chain past, stop in side",
   519  			locator:  localView.BlockLocator(branch0Nodes[12]),
   520  			hashStop: tip(branch1Nodes).hash,
   521  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   522  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   523  		},
   524  		{
   525  			// Locators based on remote being on main chain in the
   526  			// past and a stop hash in the main chain before that
   527  			// point.  The expected result is the blocks after the
   528  			// known point in the main chain and the stop hash has
   529  			// no effect.
   530  			name:     "remote main chain past, stop in main before",
   531  			locator:  localView.BlockLocator(branch0Nodes[12]),
   532  			hashStop: branch0Nodes[11].hash,
   533  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   534  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   535  		},
   536  		{
   537  			// Locators based on remote being on main chain in the
   538  			// past and a stop hash in the main chain exactly at that
   539  			// point.  The expected result is the blocks after the
   540  			// known point in the main chain and the stop hash has
   541  			// no effect.
   542  			name:     "remote main chain past, stop in main exact",
   543  			locator:  localView.BlockLocator(branch0Nodes[12]),
   544  			hashStop: branch0Nodes[12].hash,
   545  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
   546  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
   547  		},
   548  		{
   549  			// Locators based on remote being on main chain in the
   550  			// past and a stop hash in the main chain just after
   551  			// that point.  The expected result is the blocks after
   552  			// the known point in the main chain and the stop hash
   553  			// has no effect.
   554  			name:     "remote main chain past, stop in main after",
   555  			locator:  localView.BlockLocator(branch0Nodes[12]),
   556  			hashStop: branch0Nodes[13].hash,
   557  			headers:  nodeHeaders(branch0Nodes, 13),
   558  			hashes:   nodeHashes(branch0Nodes, 13),
   559  		},
   560  		{
   561  			// Locators based on remote being on main chain in the
   562  			// past and a stop hash in the main chain some time
   563  			// after that point.  The expected result is the blocks
   564  			// after the known point in the main chain and the stop
   565  			// hash has no effect.
   566  			name:     "remote main chain past, stop in main after more",
   567  			locator:  localView.BlockLocator(branch0Nodes[12]),
   568  			hashStop: branch0Nodes[15].hash,
   569  			headers:  nodeHeaders(branch0Nodes, 13, 14, 15),
   570  			hashes:   nodeHashes(branch0Nodes, 13, 14, 15),
   571  		},
   572  		{
   573  			// Locators based on remote being at exactly the same
   574  			// point in the main chain and a stop hash local node
   575  			// doesn't know about.  The expected result is no
   576  			// located inventory.
   577  			name:     "remote main chain same, unknown stop",
   578  			locator:  localView.BlockLocator(nil),
   579  			hashStop: chainhash.Hash{0x01},
   580  			headers:  nil,
   581  			hashes:   nil,
   582  		},
   583  		{
   584  			// Locators based on remote being at exactly the same
   585  			// point in the main chain and a stop hash at exactly
   586  			// the same point.  The expected result is no located
   587  			// inventory.
   588  			name:     "remote main chain same, stop same point",
   589  			locator:  localView.BlockLocator(nil),
   590  			hashStop: tip(branch0Nodes).hash,
   591  			headers:  nil,
   592  			hashes:   nil,
   593  		},
   594  		{
   595  			// Locators from remote that don't include any blocks
   596  			// the local node knows.  This would happen if the
   597  			// remote node is on a completely separate chain that
   598  			// isn't rooted with the same genesis block.  The
   599  			// expected result is the blocks after the genesis
   600  			// block.
   601  			name:     "remote unrelated chain",
   602  			locator:  unrelatedView.BlockLocator(nil),
   603  			hashStop: chainhash.Hash{},
   604  			headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   605  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   606  			hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   607  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   608  		},
   609  		{
   610  			// Locators from remote for second block in main chain
   611  			// and no stop hash, but with an overridden max limit.
   612  			// The expected result is the blocks after the second
   613  			// block limited by the max.
   614  			name:       "remote genesis",
   615  			locator:    locatorHashes(branch0Nodes, 0),
   616  			hashStop:   chainhash.Hash{},
   617  			maxAllowed: 3,
   618  			headers:    nodeHeaders(branch0Nodes, 1, 2, 3),
   619  			hashes:     nodeHashes(branch0Nodes, 1, 2, 3),
   620  		},
   621  		{
   622  			// Poorly formed locator.
   623  			//
   624  			// Locator from remote that only includes a single
   625  			// block on a side chain the local node knows.  The
   626  			// expected result is the blocks after the genesis
   627  			// block since even though the block is known, it is on
   628  			// a side chain and there are no more locators to find
   629  			// the fork point.
   630  			name:     "weak locator, single known side block",
   631  			locator:  locatorHashes(branch1Nodes, 1),
   632  			hashStop: chainhash.Hash{},
   633  			headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   634  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   635  			hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   636  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   637  		},
   638  		{
   639  			// Poorly formed locator.
   640  			//
   641  			// Locator from remote that only includes multiple
   642  			// blocks on a side chain the local node knows however
   643  			// none in the main chain.  The expected result is the
   644  			// blocks after the genesis block since even though the
   645  			// blocks are known, they are all on a side chain and
   646  			// there are no more locators to find the fork point.
   647  			name:     "weak locator, multiple known side blocks",
   648  			locator:  locatorHashes(branch1Nodes, 1),
   649  			hashStop: chainhash.Hash{},
   650  			headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   651  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   652  			hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
   653  				7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
   654  		},
   655  		{
   656  			// Poorly formed locator.
   657  			//
   658  			// Locator from remote that only includes multiple
   659  			// blocks on a side chain the local node knows however
   660  			// none in the main chain but includes a stop hash in
   661  			// the main chain.  The expected result is the blocks
   662  			// after the genesis block up to the stop hash since
   663  			// even though the blocks are known, they are all on a
   664  			// side chain and there are no more locators to find the
   665  			// fork point.
   666  			name:     "weak locator, multiple known side blocks, stop in main",
   667  			locator:  locatorHashes(branch1Nodes, 1),
   668  			hashStop: branch0Nodes[5].hash,
   669  			headers:  nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5),
   670  			hashes:   nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5),
   671  		},
   672  	}
   673  	for _, test := range tests {
   674  		// Ensure the expected headers are located.
   675  		var headers []wire.BlockHeader
   676  		if test.maxAllowed != 0 {
   677  			// Need to use the unexported function to override the
   678  			// max allowed for headers.
   679  			chain.chainLock.RLock()
   680  			headers = chain.locateHeaders(test.locator,
   681  				&test.hashStop, test.maxAllowed)
   682  			chain.chainLock.RUnlock()
   683  		} else {
   684  			headers = chain.LocateHeaders(test.locator,
   685  				&test.hashStop)
   686  		}
   687  		if !reflect.DeepEqual(headers, test.headers) {
   688  			t.Errorf("%s: unxpected headers -- got %v, want %v",
   689  				test.name, headers, test.headers)
   690  			continue
   691  		}
   692  
   693  		// Ensure the expected block hashes are located.
   694  		maxAllowed := uint32(wire.MaxBlocksPerMsg)
   695  		if test.maxAllowed != 0 {
   696  			maxAllowed = test.maxAllowed
   697  		}
   698  		hashes := chain.LocateBlocks(test.locator, &test.hashStop,
   699  			maxAllowed)
   700  		if !reflect.DeepEqual(hashes, test.hashes) {
   701  			t.Errorf("%s: unxpected hashes -- got %v, want %v",
   702  				test.name, hashes, test.hashes)
   703  			continue
   704  		}
   705  	}
   706  }
   707  
   708  // TestHeightToHashRange ensures that fetching a range of block hashes by start
   709  // height and end hash works as expected.
   710  func TestHeightToHashRange(t *testing.T) {
   711  	// Construct a synthetic block chain with a block index consisting of
   712  	// the following structure.
   713  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   714  	// 	                              \-> 16a -> 17a -> 18a (unvalidated)
   715  	tip := tstTip
   716  	chain := newFakeChain(&chaincfg.MainNetParams)
   717  	branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
   718  	branch1Nodes := chainedNodes(branch0Nodes[14], 3)
   719  	for _, node := range branch0Nodes {
   720  		chain.index.SetStatusFlags(node, statusValid)
   721  		chain.index.AddNode(node)
   722  	}
   723  	for _, node := range branch1Nodes {
   724  		if node.height < 18 {
   725  			chain.index.SetStatusFlags(node, statusValid)
   726  		}
   727  		chain.index.AddNode(node)
   728  	}
   729  	chain.bestChain.SetTip(tip(branch0Nodes))
   730  
   731  	tests := []struct {
   732  		name        string
   733  		startHeight int32            // locator for requested inventory
   734  		endHash     chainhash.Hash   // stop hash for locator
   735  		maxResults  int              // max to locate, 0 = wire const
   736  		hashes      []chainhash.Hash // expected located hashes
   737  		expectError bool
   738  	}{
   739  		{
   740  			name:        "blocks below tip",
   741  			startHeight: 11,
   742  			endHash:     branch0Nodes[14].hash,
   743  			maxResults:  10,
   744  			hashes:      nodeHashes(branch0Nodes, 10, 11, 12, 13, 14),
   745  		},
   746  		{
   747  			name:        "blocks on main chain",
   748  			startHeight: 15,
   749  			endHash:     branch0Nodes[17].hash,
   750  			maxResults:  10,
   751  			hashes:      nodeHashes(branch0Nodes, 14, 15, 16, 17),
   752  		},
   753  		{
   754  			name:        "blocks on stale chain",
   755  			startHeight: 15,
   756  			endHash:     branch1Nodes[1].hash,
   757  			maxResults:  10,
   758  			hashes: append(nodeHashes(branch0Nodes, 14),
   759  				nodeHashes(branch1Nodes, 0, 1)...),
   760  		},
   761  		{
   762  			name:        "invalid start height",
   763  			startHeight: 19,
   764  			endHash:     branch0Nodes[17].hash,
   765  			maxResults:  10,
   766  			expectError: true,
   767  		},
   768  		{
   769  			name:        "too many results",
   770  			startHeight: 1,
   771  			endHash:     branch0Nodes[17].hash,
   772  			maxResults:  10,
   773  			expectError: true,
   774  		},
   775  		{
   776  			name:        "unvalidated block",
   777  			startHeight: 15,
   778  			endHash:     branch1Nodes[2].hash,
   779  			maxResults:  10,
   780  			expectError: true,
   781  		},
   782  	}
   783  	for _, test := range tests {
   784  		hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash,
   785  			test.maxResults)
   786  		if err != nil {
   787  			if !test.expectError {
   788  				t.Errorf("%s: unexpected error: %v", test.name, err)
   789  			}
   790  			continue
   791  		}
   792  
   793  		if !reflect.DeepEqual(hashes, test.hashes) {
   794  			t.Errorf("%s: unxpected hashes -- got %v, want %v",
   795  				test.name, hashes, test.hashes)
   796  		}
   797  	}
   798  }
   799  
   800  // TestIntervalBlockHashes ensures that fetching block hashes at specified
   801  // intervals by end hash works as expected.
   802  func TestIntervalBlockHashes(t *testing.T) {
   803  	// Construct a synthetic block chain with a block index consisting of
   804  	// the following structure.
   805  	// 	genesis -> 1 -> 2 -> ... -> 15 -> 16  -> 17  -> 18
   806  	// 	                              \-> 16a -> 17a -> 18a (unvalidated)
   807  	tip := tstTip
   808  	chain := newFakeChain(&chaincfg.MainNetParams)
   809  	branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
   810  	branch1Nodes := chainedNodes(branch0Nodes[14], 3)
   811  	for _, node := range branch0Nodes {
   812  		chain.index.SetStatusFlags(node, statusValid)
   813  		chain.index.AddNode(node)
   814  	}
   815  	for _, node := range branch1Nodes {
   816  		if node.height < 18 {
   817  			chain.index.SetStatusFlags(node, statusValid)
   818  		}
   819  		chain.index.AddNode(node)
   820  	}
   821  	chain.bestChain.SetTip(tip(branch0Nodes))
   822  
   823  	tests := []struct {
   824  		name        string
   825  		endHash     chainhash.Hash
   826  		interval    int
   827  		hashes      []chainhash.Hash
   828  		expectError bool
   829  	}{
   830  		{
   831  			name:     "blocks on main chain",
   832  			endHash:  branch0Nodes[17].hash,
   833  			interval: 8,
   834  			hashes:   nodeHashes(branch0Nodes, 7, 15),
   835  		},
   836  		{
   837  			name:     "blocks on stale chain",
   838  			endHash:  branch1Nodes[1].hash,
   839  			interval: 8,
   840  			hashes: append(nodeHashes(branch0Nodes, 7),
   841  				nodeHashes(branch1Nodes, 0)...),
   842  		},
   843  		{
   844  			name:     "no results",
   845  			endHash:  branch0Nodes[17].hash,
   846  			interval: 20,
   847  			hashes:   []chainhash.Hash{},
   848  		},
   849  		{
   850  			name:        "unvalidated block",
   851  			endHash:     branch1Nodes[2].hash,
   852  			interval:    8,
   853  			expectError: true,
   854  		},
   855  	}
   856  	for _, test := range tests {
   857  		hashes, err := chain.IntervalBlockHashes(&test.endHash, test.interval)
   858  		if err != nil {
   859  			if !test.expectError {
   860  				t.Errorf("%s: unexpected error: %v", test.name, err)
   861  			}
   862  			continue
   863  		}
   864  
   865  		if !reflect.DeepEqual(hashes, test.hashes) {
   866  			t.Errorf("%s: unxpected hashes -- got %v, want %v",
   867  				test.name, hashes, test.hashes)
   868  		}
   869  	}
   870  }