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

     1  // Copyright (c) 2017 The btcsuite developers
     2  // Copyright (c) 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
     7  
     8  import (
     9  	"fmt"
    10  	"reflect"
    11  	"testing"
    12  )
    13  
    14  // String returns the block node as a human-readable name.
    15  func (node blockNode) String() string {
    16  	return fmt.Sprintf("%s(%d)", node.hash, node.height)
    17  }
    18  
    19  // zipLocators is a convenience function that returns a single block locator
    20  // given a variable number of them and is used in the tests.
    21  func zipLocators(locators ...BlockLocator) BlockLocator {
    22  	var hashes BlockLocator
    23  	for _, locator := range locators {
    24  		hashes = append(hashes, locator...)
    25  	}
    26  	return hashes
    27  }
    28  
    29  // TestChainView ensures all of the exported functionality of chain views works
    30  // as intended with the exception of some special cases which are handled in
    31  // other tests.
    32  func TestChainView(t *testing.T) {
    33  	// Construct a synthetic block index consisting of the following
    34  	// structure.
    35  	// 0 -> 1 -> 2  -> 3  -> 4
    36  	//       \-> 2a -> 3a -> 4a  -> 5a -> 6a -> 7a -> ... -> 26a
    37  	//             \-> 3a'-> 4a' -> 5a'
    38  	branch0Nodes := chainedFakeNodes(nil, 5)
    39  	branch1Nodes := chainedFakeNodes(branch0Nodes[1], 25)
    40  	branch2Nodes := chainedFakeNodes(branch1Nodes[0], 3)
    41  
    42  	tests := []struct {
    43  		name       string
    44  		view       *chainView   // active view
    45  		genesis    *blockNode   // expected genesis block of active view
    46  		tip        *blockNode   // expected tip of active view
    47  		side       *chainView   // side chain view
    48  		sideTip    *blockNode   // expected tip of side chain view
    49  		fork       *blockNode   // expected fork node
    50  		contains   []*blockNode // expected nodes in active view
    51  		noContains []*blockNode // expected nodes NOT in active view
    52  		equal      *chainView   // view expected equal to active view
    53  		unequal    *chainView   // view expected NOT equal to active
    54  		locator    BlockLocator // expected locator for active view tip
    55  	}{
    56  		{
    57  			// Create a view for branch 0 as the active chain and
    58  			// another view for branch 1 as the side chain.
    59  			name:       "chain0-chain1",
    60  			view:       newChainView(branchTip(branch0Nodes)),
    61  			genesis:    branch0Nodes[0],
    62  			tip:        branchTip(branch0Nodes),
    63  			side:       newChainView(branchTip(branch1Nodes)),
    64  			sideTip:    branchTip(branch1Nodes),
    65  			fork:       branch0Nodes[1],
    66  			contains:   branch0Nodes,
    67  			noContains: branch1Nodes,
    68  			equal:      newChainView(branchTip(branch0Nodes)),
    69  			unequal:    newChainView(branchTip(branch1Nodes)),
    70  			locator:    locatorHashes(branch0Nodes, 4, 3, 2, 1, 0),
    71  		},
    72  		{
    73  			// Create a view for branch 1 as the active chain and
    74  			// another view for branch 2 as the side chain.
    75  			name:       "chain1-chain2",
    76  			view:       newChainView(branchTip(branch1Nodes)),
    77  			genesis:    branch0Nodes[0],
    78  			tip:        branchTip(branch1Nodes),
    79  			side:       newChainView(branchTip(branch2Nodes)),
    80  			sideTip:    branchTip(branch2Nodes),
    81  			fork:       branch1Nodes[0],
    82  			contains:   branch1Nodes,
    83  			noContains: branch2Nodes,
    84  			equal:      newChainView(branchTip(branch1Nodes)),
    85  			unequal:    newChainView(branchTip(branch2Nodes)),
    86  			locator: zipLocators(
    87  				locatorHashes(branch1Nodes, 24, 23, 22, 21, 20,
    88  					19, 18, 17, 16, 15, 14, 13, 11, 7),
    89  				locatorHashes(branch0Nodes, 1, 0)),
    90  		},
    91  		{
    92  			// Create a view for branch 2 as the active chain and
    93  			// another view for branch 0 as the side chain.
    94  			name:       "chain2-chain0",
    95  			view:       newChainView(branchTip(branch2Nodes)),
    96  			genesis:    branch0Nodes[0],
    97  			tip:        branchTip(branch2Nodes),
    98  			side:       newChainView(branchTip(branch0Nodes)),
    99  			sideTip:    branchTip(branch0Nodes),
   100  			fork:       branch0Nodes[1],
   101  			contains:   branch2Nodes,
   102  			noContains: branch0Nodes[2:],
   103  			equal:      newChainView(branchTip(branch2Nodes)),
   104  			unequal:    newChainView(branchTip(branch0Nodes)),
   105  			locator: zipLocators(
   106  				locatorHashes(branch2Nodes, 2, 1, 0),
   107  				locatorHashes(branch1Nodes, 0),
   108  				locatorHashes(branch0Nodes, 1, 0)),
   109  		},
   110  	}
   111  testLoop:
   112  	for _, test := range tests {
   113  		// Ensure the active and side chain heights are the expected
   114  		// values.
   115  		if test.view.Height() != test.tip.height {
   116  			t.Errorf("%s: unexpected active view height -- got "+
   117  				"%d, want %d", test.name, test.view.Height(),
   118  				test.tip.height)
   119  			continue
   120  		}
   121  		if test.side.Height() != test.sideTip.height {
   122  			t.Errorf("%s: unexpected side view height -- got %d, "+
   123  				"want %d", test.name, test.side.Height(),
   124  				test.sideTip.height)
   125  			continue
   126  		}
   127  
   128  		// Ensure the active and side chain genesis block is the
   129  		// expected value.
   130  		if test.view.Genesis() != test.genesis {
   131  			t.Errorf("%s: unexpected active view genesis -- got "+
   132  				"%v, want %v", test.name, test.view.Genesis(),
   133  				test.genesis)
   134  			continue
   135  		}
   136  		if test.side.Genesis() != test.genesis {
   137  			t.Errorf("%s: unexpected side view genesis -- got %v, "+
   138  				"want %v", test.name, test.view.Genesis(),
   139  				test.genesis)
   140  			continue
   141  		}
   142  
   143  		// Ensure the active and side chain tips are the expected nodes.
   144  		if test.view.Tip() != test.tip {
   145  			t.Errorf("%s: unexpected active view tip -- got %v, "+
   146  				"want %v", test.name, test.view.Tip(), test.tip)
   147  			continue
   148  		}
   149  		if test.side.Tip() != test.sideTip {
   150  			t.Errorf("%s: unexpected active view tip -- got %v, "+
   151  				"want %v", test.name, test.side.Tip(),
   152  				test.sideTip)
   153  			continue
   154  		}
   155  
   156  		// Ensure that regardless of the order the two chains are
   157  		// compared they both return the expected fork point.
   158  		forkNode := test.view.FindFork(test.side.Tip())
   159  		if forkNode != test.fork {
   160  			t.Errorf("%s: unexpected fork node (view, side) -- "+
   161  				"got %v, want %v", test.name, forkNode,
   162  				test.fork)
   163  			continue
   164  		}
   165  		forkNode = test.side.FindFork(test.view.Tip())
   166  		if forkNode != test.fork {
   167  			t.Errorf("%s: unexpected fork node (side, view) -- "+
   168  				"got %v, want %v", test.name, forkNode,
   169  				test.fork)
   170  			continue
   171  		}
   172  
   173  		// Ensure that the fork point for a node that is already part
   174  		// of the chain view is the node itself.
   175  		forkNode = test.view.FindFork(test.view.Tip())
   176  		if forkNode != test.view.Tip() {
   177  			t.Errorf("%s: unexpected fork node (view, tip) -- "+
   178  				"got %v, want %v", test.name, forkNode,
   179  				test.view.Tip())
   180  			continue
   181  		}
   182  
   183  		// Ensure all expected nodes are contained in the active view.
   184  		for _, node := range test.contains {
   185  			if !test.view.Contains(node) {
   186  				t.Errorf("%s: expected %v in active view",
   187  					test.name, node)
   188  				continue testLoop
   189  			}
   190  		}
   191  
   192  		// Ensure all nodes from side chain view are NOT contained in
   193  		// the active view.
   194  		for _, node := range test.noContains {
   195  			if test.view.Contains(node) {
   196  				t.Errorf("%s: unexpected %v in active view",
   197  					test.name, node)
   198  				continue testLoop
   199  			}
   200  		}
   201  
   202  		// Ensure equality of different views into the same chain works
   203  		// as intended.
   204  		if !test.view.Equals(test.equal) {
   205  			t.Errorf("%s: unexpected unequal views", test.name)
   206  			continue
   207  		}
   208  		if test.view.Equals(test.unequal) {
   209  			t.Errorf("%s: unexpected equal views", test.name)
   210  			continue
   211  		}
   212  
   213  		// Ensure all nodes contained in the view return the expected
   214  		// next node.
   215  		for i, node := range test.contains {
   216  			// Final node expects nil for the next node.
   217  			var expected *blockNode
   218  			if i < len(test.contains)-1 {
   219  				expected = test.contains[i+1]
   220  			}
   221  			if next := test.view.Next(node); next != expected {
   222  				t.Errorf("%s: unexpected next node -- got %v, "+
   223  					"want %v", test.name, next, expected)
   224  				continue testLoop
   225  			}
   226  		}
   227  
   228  		// Ensure nodes that are not contained in the view do not
   229  		// produce a successor node.
   230  		for _, node := range test.noContains {
   231  			if next := test.view.Next(node); next != nil {
   232  				t.Errorf("%s: unexpected next node -- got %v, "+
   233  					"want nil", test.name, next)
   234  				continue testLoop
   235  			}
   236  		}
   237  
   238  		// Ensure all nodes contained in the view can be retrieved by
   239  		// height.
   240  		for _, wantNode := range test.contains {
   241  			node := test.view.NodeByHeight(wantNode.height)
   242  			if node != wantNode {
   243  				t.Errorf("%s: unexpected node for height %d -- "+
   244  					"got %v, want %v", test.name,
   245  					wantNode.height, node, wantNode)
   246  				continue testLoop
   247  			}
   248  		}
   249  
   250  		// Ensure the block locator for the tip of the active view
   251  		// consists of the expected hashes.
   252  		locator := test.view.BlockLocator(test.view.tip())
   253  		if !reflect.DeepEqual(locator, test.locator) {
   254  			t.Errorf("%s: unexpected locator -- got %v, want %v",
   255  				test.name, locator, test.locator)
   256  			continue
   257  		}
   258  	}
   259  }
   260  
   261  // TestChainViewForkCorners ensures that finding the fork between two chains
   262  // works in some corner cases such as when the two chains have completely
   263  // unrelated histories.
   264  func TestChainViewForkCorners(t *testing.T) {
   265  	// Construct two unrelated single branch synthetic block indexes.
   266  	branchNodes := chainedFakeNodes(nil, 5)
   267  	unrelatedBranchNodes := chainedFakeNodes(nil, 7)
   268  
   269  	// Create chain views for the two unrelated histories.
   270  	view1 := newChainView(branchTip(branchNodes))
   271  	view2 := newChainView(branchTip(unrelatedBranchNodes))
   272  
   273  	// Ensure attempting to find a fork point with a node that doesn't exist
   274  	// doesn't produce a node.
   275  	if fork := view1.FindFork(nil); fork != nil {
   276  		t.Fatalf("FindFork: unexpected fork -- got %v, want nil", fork)
   277  	}
   278  
   279  	// Ensure attempting to find a fork point in two chain views with
   280  	// totally unrelated histories doesn't produce a node.
   281  	for _, node := range branchNodes {
   282  		if fork := view2.FindFork(node); fork != nil {
   283  			t.Fatalf("FindFork: unexpected fork -- got %v, want nil",
   284  				fork)
   285  		}
   286  	}
   287  	for _, node := range unrelatedBranchNodes {
   288  		if fork := view1.FindFork(node); fork != nil {
   289  			t.Fatalf("FindFork: unexpected fork -- got %v, want nil",
   290  				fork)
   291  		}
   292  	}
   293  }
   294  
   295  // TestChainViewSetTip ensures changing the tip works as intended including
   296  // capacity changes.
   297  func TestChainViewSetTip(t *testing.T) {
   298  	// Construct a synthetic block index consisting of the following
   299  	// structure.
   300  	// 0 -> 1 -> 2  -> 3  -> 4
   301  	//       \-> 2a -> 3a -> 4a  -> 5a -> 6a -> 7a -> ... -> 26a
   302  	branch0Nodes := chainedFakeNodes(nil, 5)
   303  	branch1Nodes := chainedFakeNodes(branch0Nodes[1], 25)
   304  
   305  	tests := []struct {
   306  		name     string
   307  		view     *chainView     // active view
   308  		tips     []*blockNode   // tips to set
   309  		contains [][]*blockNode // expected nodes in view for each tip
   310  	}{
   311  		{
   312  			// Create an empty view and set the tip to increasingly
   313  			// longer chains.
   314  			name: "increasing",
   315  			view: newChainView(nil),
   316  			tips: []*blockNode{branchTip(branch0Nodes),
   317  				branchTip(branch1Nodes)},
   318  			contains: [][]*blockNode{branch0Nodes, branch1Nodes},
   319  		},
   320  		{
   321  			// Create a view with a longer chain and set the tip to
   322  			// increasingly shorter chains.
   323  			name:     "decreasing",
   324  			view:     newChainView(branchTip(branch1Nodes)),
   325  			tips:     []*blockNode{branchTip(branch0Nodes), nil},
   326  			contains: [][]*blockNode{branch0Nodes, nil},
   327  		},
   328  		{
   329  			// Create a view with a shorter chain and set the tip to
   330  			// a longer chain followed by setting it back to the
   331  			// shorter chain.
   332  			name: "small-large-small",
   333  			view: newChainView(branchTip(branch0Nodes)),
   334  			tips: []*blockNode{branchTip(branch1Nodes),
   335  				branchTip(branch0Nodes)},
   336  			contains: [][]*blockNode{branch1Nodes, branch0Nodes},
   337  		},
   338  		{
   339  			// Create a view with a longer chain and set the tip to
   340  			// a smaller chain followed by setting it back to the
   341  			// longer chain.
   342  			name: "large-small-large",
   343  			view: newChainView(branchTip(branch1Nodes)),
   344  			tips: []*blockNode{branchTip(branch0Nodes),
   345  				branchTip(branch1Nodes)},
   346  			contains: [][]*blockNode{branch0Nodes, branch1Nodes},
   347  		},
   348  	}
   349  
   350  testLoop:
   351  	for _, test := range tests {
   352  		for i, tip := range test.tips {
   353  			// Ensure the view tip is the expected node.
   354  			test.view.SetTip(tip)
   355  			if test.view.Tip() != tip {
   356  				t.Errorf("%s: unexpected view tip -- got %v, "+
   357  					"want %v", test.name, test.view.Tip(),
   358  					tip)
   359  				continue testLoop
   360  			}
   361  
   362  			// Ensure all expected nodes are contained in the view.
   363  			for _, node := range test.contains[i] {
   364  				if !test.view.Contains(node) {
   365  					t.Errorf("%s: expected %v in active view",
   366  						test.name, node)
   367  					continue testLoop
   368  				}
   369  			}
   370  
   371  		}
   372  	}
   373  }
   374  
   375  // TestChainViewNil ensures that creating and accessing a nil chain view behaves
   376  // as expected.
   377  func TestChainViewNil(t *testing.T) {
   378  	// Ensure two unininitialized views are considered equal.
   379  	view := newChainView(nil)
   380  	if !view.Equals(newChainView(nil)) {
   381  		t.Fatal("uninitialized nil views unequal")
   382  	}
   383  
   384  	// Ensure the genesis of an uninitialized view does not produce a node.
   385  	if genesis := view.Genesis(); genesis != nil {
   386  		t.Fatalf("Genesis: unexpected genesis -- got %v, want nil",
   387  			genesis)
   388  	}
   389  
   390  	// Ensure the tip of an uninitialized view does not produce a node.
   391  	if tip := view.Tip(); tip != nil {
   392  		t.Fatalf("Tip: unexpected tip -- got %v, want nil", tip)
   393  	}
   394  
   395  	// Ensure the height of an uninitialized view is the expected value.
   396  	if height := view.Height(); height != -1 {
   397  		t.Fatalf("Height: unexpected height -- got %d, want -1", height)
   398  	}
   399  
   400  	// Ensure attempting to get a node for a height that does not exist does
   401  	// not produce a node.
   402  	if node := view.NodeByHeight(10); node != nil {
   403  		t.Fatalf("NodeByHeight: unexpected node -- got %v, want nil", node)
   404  	}
   405  
   406  	// Ensure an uninitialized view does not report it contains nodes.
   407  	fakeNode := chainedFakeNodes(nil, 1)[0]
   408  	if view.Contains(fakeNode) {
   409  		t.Fatalf("Contains: view claims it contains node %v", fakeNode)
   410  	}
   411  
   412  	// Ensure the next node for a node that does not exist does not produce
   413  	// a node.
   414  	if next := view.Next(nil); next != nil {
   415  		t.Fatalf("Next: unexpected next node -- got %v, want nil", next)
   416  	}
   417  
   418  	// Ensure the next node for a node that exists does not produce a node.
   419  	if next := view.Next(fakeNode); next != nil {
   420  		t.Fatalf("Next: unexpected next node -- got %v, want nil", next)
   421  	}
   422  
   423  	// Ensure attempting to find a fork point with a node that doesn't exist
   424  	// doesn't produce a node.
   425  	if fork := view.FindFork(nil); fork != nil {
   426  		t.Fatalf("FindFork: unexpected fork -- got %v, want nil", fork)
   427  	}
   428  
   429  	// Ensure attempting to get a block locator for the tip doesn't produce
   430  	// one since the tip is nil.
   431  	if locator := view.BlockLocator(nil); locator != nil {
   432  		t.Fatalf("BlockLocator: unexpected locator -- got %v, want nil",
   433  			locator)
   434  	}
   435  
   436  	// Ensure attempting to get a block locator for a node that exists still
   437  	// works as intended.
   438  	branchNodes := chainedFakeNodes(nil, 50)
   439  	wantLocator := locatorHashes(branchNodes, 49, 48, 47, 46, 45, 44, 43,
   440  		42, 41, 40, 39, 38, 36, 32, 24, 8, 0)
   441  	locator := view.BlockLocator(branchTip(branchNodes))
   442  	if !reflect.DeepEqual(locator, wantLocator) {
   443  		t.Fatalf("BlockLocator: unexpected locator -- got %v, want %v",
   444  			locator, wantLocator)
   445  	}
   446  }