github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/forkchoice/protoarray/store_test.go (about)

     1  package protoarray
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	types "github.com/prysmaticlabs/eth2-types"
     8  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
     9  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    10  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    11  )
    12  
    13  func TestStore_PruneThreshold(t *testing.T) {
    14  	s := &Store{
    15  		pruneThreshold: defaultPruneThreshold,
    16  	}
    17  	if got := s.PruneThreshold(); got != defaultPruneThreshold {
    18  		t.Errorf("PruneThreshold() = %v, want %v", got, defaultPruneThreshold)
    19  	}
    20  }
    21  
    22  func TestStore_JustifiedEpoch(t *testing.T) {
    23  	j := types.Epoch(100)
    24  	s := &Store{
    25  		justifiedEpoch: j,
    26  	}
    27  	if got := s.JustifiedEpoch(); got != j {
    28  		t.Errorf("JustifiedEpoch() = %v, want %v", got, j)
    29  	}
    30  }
    31  
    32  func TestStore_FinalizedEpoch(t *testing.T) {
    33  	f := types.Epoch(50)
    34  	s := &Store{
    35  		finalizedEpoch: f,
    36  	}
    37  	if got := s.FinalizedEpoch(); got != f {
    38  		t.Errorf("FinalizedEpoch() = %v, want %v", got, f)
    39  	}
    40  }
    41  
    42  func TestStore_Nodes(t *testing.T) {
    43  	nodes := []*Node{
    44  		{slot: 100},
    45  		{slot: 101},
    46  	}
    47  	s := &Store{
    48  		nodes: nodes,
    49  	}
    50  	require.DeepEqual(t, nodes, s.Nodes())
    51  }
    52  
    53  func TestStore_NodesIndices(t *testing.T) {
    54  	nodeIndices := map[[32]byte]uint64{
    55  		{'a'}: 1,
    56  		{'b'}: 2,
    57  	}
    58  	s := &Store{
    59  		nodesIndices: nodeIndices,
    60  	}
    61  	require.DeepEqual(t, nodeIndices, s.NodesIndices())
    62  }
    63  
    64  func TestForkChoice_HasNode(t *testing.T) {
    65  	nodeIndices := map[[32]byte]uint64{
    66  		{'a'}: 1,
    67  		{'b'}: 2,
    68  	}
    69  	s := &Store{
    70  		nodesIndices: nodeIndices,
    71  	}
    72  	f := &ForkChoice{store: s}
    73  	require.Equal(t, true, f.HasNode([32]byte{'a'}))
    74  }
    75  
    76  func TestForkChoice_Store(t *testing.T) {
    77  	nodeIndices := map[[32]byte]uint64{
    78  		{'a'}: 1,
    79  		{'b'}: 2,
    80  	}
    81  	s := &Store{
    82  		nodesIndices: nodeIndices,
    83  	}
    84  	f := &ForkChoice{store: s}
    85  	require.DeepEqual(t, s, f.Store())
    86  }
    87  
    88  func TestForkChoice_Nodes(t *testing.T) {
    89  	nodes := []*Node{
    90  		{slot: 100},
    91  		{slot: 101},
    92  	}
    93  	s := &Store{
    94  		nodes: nodes,
    95  	}
    96  	f := &ForkChoice{store: s}
    97  	require.DeepEqual(t, s.nodes, f.Nodes())
    98  }
    99  
   100  func TestStore_Head_UnknownJustifiedRoot(t *testing.T) {
   101  	s := &Store{nodesIndices: make(map[[32]byte]uint64)}
   102  
   103  	_, err := s.head(context.Background(), [32]byte{})
   104  	assert.ErrorContains(t, errUnknownJustifiedRoot.Error(), err)
   105  }
   106  
   107  func TestStore_Head_UnknownJustifiedIndex(t *testing.T) {
   108  	r := [32]byte{'A'}
   109  	indices := make(map[[32]byte]uint64)
   110  	indices[r] = 1
   111  	s := &Store{nodesIndices: indices}
   112  
   113  	_, err := s.head(context.Background(), r)
   114  	assert.ErrorContains(t, errInvalidJustifiedIndex.Error(), err)
   115  }
   116  
   117  func TestStore_Head_Itself(t *testing.T) {
   118  	r := [32]byte{'A'}
   119  	indices := make(map[[32]byte]uint64)
   120  	indices[r] = 0
   121  
   122  	// Since the justified node does not have a best descendant so the best node
   123  	// is itself.
   124  	s := &Store{nodesIndices: indices, nodes: []*Node{{root: r, bestDescendant: NonExistentNode}}, canonicalNodes: make(map[[32]byte]bool)}
   125  	h, err := s.head(context.Background(), r)
   126  	require.NoError(t, err)
   127  	assert.Equal(t, r, h)
   128  }
   129  
   130  func TestStore_Head_BestDescendant(t *testing.T) {
   131  	r := [32]byte{'A'}
   132  	best := [32]byte{'B'}
   133  	indices := make(map[[32]byte]uint64)
   134  	indices[r] = 0
   135  
   136  	// Since the justified node's best descendent is at index 1 and it's root is `best`,
   137  	// the head should be `best`.
   138  	s := &Store{nodesIndices: indices, nodes: []*Node{{root: r, bestDescendant: 1}, {root: best}}, canonicalNodes: make(map[[32]byte]bool)}
   139  	h, err := s.head(context.Background(), r)
   140  	require.NoError(t, err)
   141  	assert.Equal(t, best, h)
   142  }
   143  
   144  func TestStore_Head_ContextCancelled(t *testing.T) {
   145  	ctx, cancel := context.WithCancel(context.Background())
   146  	r := [32]byte{'A'}
   147  	best := [32]byte{'B'}
   148  	indices := make(map[[32]byte]uint64)
   149  	indices[r] = 0
   150  	s := &Store{nodesIndices: indices, nodes: []*Node{{root: r, bestDescendant: 1}, {root: best}}, canonicalNodes: make(map[[32]byte]bool)}
   151  	cancel()
   152  	_, err := s.head(ctx, r)
   153  	require.ErrorContains(t, "context canceled", err)
   154  }
   155  
   156  func TestStore_Insert_UnknownParent(t *testing.T) {
   157  	// The new node does not have a parent.
   158  	s := &Store{nodesIndices: make(map[[32]byte]uint64)}
   159  	require.NoError(t, s.insert(context.Background(), 100, [32]byte{'A'}, [32]byte{'B'}, [32]byte{}, 1, 1))
   160  	assert.Equal(t, 1, len(s.nodes), "Did not insert block")
   161  	assert.Equal(t, 1, len(s.nodesIndices), "Did not insert block")
   162  	assert.Equal(t, NonExistentNode, s.nodes[0].parent, "Incorrect parent")
   163  	assert.Equal(t, types.Epoch(1), s.nodes[0].justifiedEpoch, "Incorrect justification")
   164  	assert.Equal(t, types.Epoch(1), s.nodes[0].finalizedEpoch, "Incorrect finalization")
   165  	assert.Equal(t, [32]byte{'A'}, s.nodes[0].root, "Incorrect root")
   166  }
   167  
   168  func TestStore_Insert_KnownParent(t *testing.T) {
   169  	// Similar to UnknownParent test, but this time the new node has a valid parent already in store.
   170  	// The new node builds on top of the parent.
   171  	s := &Store{nodesIndices: make(map[[32]byte]uint64)}
   172  	s.nodes = []*Node{{}}
   173  	p := [32]byte{'B'}
   174  	s.nodesIndices[p] = 0
   175  	require.NoError(t, s.insert(context.Background(), 100, [32]byte{'A'}, p, [32]byte{}, 1, 1))
   176  	assert.Equal(t, 2, len(s.nodes), "Did not insert block")
   177  	assert.Equal(t, 2, len(s.nodesIndices), "Did not insert block")
   178  	assert.Equal(t, uint64(0), s.nodes[1].parent, "Incorrect parent")
   179  	assert.Equal(t, types.Epoch(1), s.nodes[1].justifiedEpoch, "Incorrect justification")
   180  	assert.Equal(t, types.Epoch(1), s.nodes[1].finalizedEpoch, "Incorrect finalization")
   181  	assert.Equal(t, [32]byte{'A'}, s.nodes[1].root, "Incorrect root")
   182  }
   183  
   184  func TestStore_ApplyScoreChanges_InvalidDeltaLength(t *testing.T) {
   185  	s := &Store{}
   186  
   187  	// This will fail because node indices has length of 0, and delta list has a length of 1.
   188  	err := s.applyWeightChanges(context.Background(), 0, 0, []int{1})
   189  	assert.ErrorContains(t, errInvalidDeltaLength.Error(), err)
   190  }
   191  
   192  func TestStore_ApplyScoreChanges_UpdateEpochs(t *testing.T) {
   193  	s := &Store{}
   194  
   195  	// The justified and finalized epochs in Store should be updated to 1 and 1 given the following input.
   196  	require.NoError(t, s.applyWeightChanges(context.Background(), 1, 1, []int{}))
   197  	assert.Equal(t, types.Epoch(1), s.justifiedEpoch, "Did not update justified epoch")
   198  	assert.Equal(t, types.Epoch(1), s.finalizedEpoch, "Did not update finalized epoch")
   199  }
   200  
   201  func TestStore_ApplyScoreChanges_UpdateWeightsPositiveDelta(t *testing.T) {
   202  	// Construct 3 nodes with weight 100 on each node. The 3 nodes linked to each other.
   203  	s := &Store{nodes: []*Node{
   204  		{root: [32]byte{'A'}, weight: 100},
   205  		{root: [32]byte{'A'}, weight: 100},
   206  		{parent: 1, root: [32]byte{'A'}, weight: 100}}}
   207  
   208  	// Each node gets one unique vote. The weight should look like 103 <- 102 <- 101 because
   209  	// they get propagated back.
   210  	require.NoError(t, s.applyWeightChanges(context.Background(), 0, 0, []int{1, 1, 1}))
   211  	assert.Equal(t, uint64(103), s.nodes[0].weight)
   212  	assert.Equal(t, uint64(102), s.nodes[1].weight)
   213  	assert.Equal(t, uint64(101), s.nodes[2].weight)
   214  }
   215  
   216  func TestStore_ApplyScoreChanges_UpdateWeightsNegativeDelta(t *testing.T) {
   217  	// Construct 3 nodes with weight 100 on each node. The 3 nodes linked to each other.
   218  	s := &Store{nodes: []*Node{
   219  		{root: [32]byte{'A'}, weight: 100},
   220  		{root: [32]byte{'A'}, weight: 100},
   221  		{parent: 1, root: [32]byte{'A'}, weight: 100}}}
   222  
   223  	// Each node gets one unique vote which contributes to negative delta.
   224  	// The weight should look like 97 <- 98 <- 99 because they get propagated back.
   225  	require.NoError(t, s.applyWeightChanges(context.Background(), 0, 0, []int{-1, -1, -1}))
   226  	assert.Equal(t, uint64(97), s.nodes[0].weight)
   227  	assert.Equal(t, uint64(98), s.nodes[1].weight)
   228  	assert.Equal(t, uint64(99), s.nodes[2].weight)
   229  }
   230  
   231  func TestStore_ApplyScoreChanges_UpdateWeightsMixedDelta(t *testing.T) {
   232  	// Construct 3 nodes with weight 100 on each node. The 3 nodes linked to each other.
   233  	s := &Store{nodes: []*Node{
   234  		{root: [32]byte{'A'}, weight: 100},
   235  		{root: [32]byte{'A'}, weight: 100},
   236  		{parent: 1, root: [32]byte{'A'}, weight: 100}}}
   237  
   238  	// Each node gets one mixed vote. The weight should look like 100 <- 200 <- 250.
   239  	require.NoError(t, s.applyWeightChanges(context.Background(), 0, 0, []int{-100, -50, 150}))
   240  	assert.Equal(t, uint64(100), s.nodes[0].weight)
   241  	assert.Equal(t, uint64(200), s.nodes[1].weight)
   242  	assert.Equal(t, uint64(250), s.nodes[2].weight)
   243  }
   244  
   245  func TestStore_UpdateBestChildAndDescendant_RemoveChild(t *testing.T) {
   246  	// Make parent's best child equal's to input child index and child is not viable.
   247  	s := &Store{nodes: []*Node{{bestChild: 1}, {}}, justifiedEpoch: 1, finalizedEpoch: 1}
   248  	require.NoError(t, s.updateBestChildAndDescendant(0, 1))
   249  
   250  	// Verify parent's best child and best descendant are `none`.
   251  	assert.Equal(t, NonExistentNode, s.nodes[0].bestChild, "Did not get correct best child index")
   252  	assert.Equal(t, NonExistentNode, s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   253  }
   254  
   255  func TestStore_UpdateBestChildAndDescendant_UpdateDescendant(t *testing.T) {
   256  	// Make parent's best child equal to child index and child is viable.
   257  	s := &Store{nodes: []*Node{{bestChild: 1}, {bestDescendant: NonExistentNode}}}
   258  	require.NoError(t, s.updateBestChildAndDescendant(0, 1))
   259  
   260  	// Verify parent's best child is the same and best descendant is not set to child index.
   261  	assert.Equal(t, uint64(1), s.nodes[0].bestChild, "Did not get correct best child index")
   262  	assert.Equal(t, uint64(1), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   263  }
   264  
   265  func TestStore_UpdateBestChildAndDescendant_ChangeChildByViability(t *testing.T) {
   266  	// Make parent's best child not equal to child index, child leads to viable index and
   267  	// parents best child doesnt lead to viable index.
   268  	s := &Store{
   269  		justifiedEpoch: 1,
   270  		finalizedEpoch: 1,
   271  		nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1},
   272  			{bestDescendant: NonExistentNode},
   273  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}}}
   274  	require.NoError(t, s.updateBestChildAndDescendant(0, 2))
   275  
   276  	// Verify parent's best child and best descendant are set to child index.
   277  	assert.Equal(t, uint64(2), s.nodes[0].bestChild, "Did not get correct best child index")
   278  	assert.Equal(t, uint64(2), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   279  }
   280  
   281  func TestStore_UpdateBestChildAndDescendant_ChangeChildByWeight(t *testing.T) {
   282  	// Make parent's best child not equal to child index, child leads to viable index and
   283  	// parents best child leads to viable index but child has more weight than parent's best child.
   284  	s := &Store{
   285  		justifiedEpoch: 1,
   286  		finalizedEpoch: 1,
   287  		nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1},
   288  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1},
   289  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1, weight: 1}}}
   290  	require.NoError(t, s.updateBestChildAndDescendant(0, 2))
   291  
   292  	// Verify parent's best child and best descendant are set to child index.
   293  	assert.Equal(t, uint64(2), s.nodes[0].bestChild, "Did not get correct best child index")
   294  	assert.Equal(t, uint64(2), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   295  }
   296  
   297  func TestStore_UpdateBestChildAndDescendant_ChangeChildAtLeaf(t *testing.T) {
   298  	// Make parent's best child to none and input child leads to viable index.
   299  	s := &Store{
   300  		justifiedEpoch: 1,
   301  		finalizedEpoch: 1,
   302  		nodes: []*Node{{bestChild: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1},
   303  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1},
   304  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}}}
   305  	require.NoError(t, s.updateBestChildAndDescendant(0, 2))
   306  
   307  	// Verify parent's best child and best descendant are set to child index.
   308  	assert.Equal(t, uint64(2), s.nodes[0].bestChild, "Did not get correct best child index")
   309  	assert.Equal(t, uint64(2), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   310  }
   311  
   312  func TestStore_UpdateBestChildAndDescendant_NoChangeByViability(t *testing.T) {
   313  	// Make parent's best child not equal to child index, child leads to not viable index and
   314  	// parents best child leads to viable index.
   315  	s := &Store{
   316  		justifiedEpoch: 1,
   317  		finalizedEpoch: 1,
   318  		nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1},
   319  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1},
   320  			{bestDescendant: NonExistentNode}}}
   321  	require.NoError(t, s.updateBestChildAndDescendant(0, 2))
   322  
   323  	// Verify parent's best child and best descendant are not changed.
   324  	assert.Equal(t, uint64(1), s.nodes[0].bestChild, "Did not get correct best child index")
   325  	assert.Equal(t, uint64(0), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   326  }
   327  
   328  func TestStore_UpdateBestChildAndDescendant_NoChangeByWeight(t *testing.T) {
   329  	// Make parent's best child not equal to child index, child leads to viable index and
   330  	// parents best child leads to viable index but parent's best child has more weight.
   331  	s := &Store{
   332  		justifiedEpoch: 1,
   333  		finalizedEpoch: 1,
   334  		nodes: []*Node{{bestChild: 1, justifiedEpoch: 1, finalizedEpoch: 1},
   335  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1, weight: 1},
   336  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1}}}
   337  	require.NoError(t, s.updateBestChildAndDescendant(0, 2))
   338  
   339  	// Verify parent's best child and best descendant are not changed.
   340  	assert.Equal(t, uint64(1), s.nodes[0].bestChild, "Did not get correct best child index")
   341  	assert.Equal(t, uint64(0), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   342  }
   343  
   344  func TestStore_UpdateBestChildAndDescendant_NoChangeAtLeaf(t *testing.T) {
   345  	// Make parent's best child to none and input child does not lead to viable index.
   346  	s := &Store{
   347  		justifiedEpoch: 1,
   348  		finalizedEpoch: 1,
   349  		nodes: []*Node{{bestChild: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1},
   350  			{bestDescendant: NonExistentNode, justifiedEpoch: 1, finalizedEpoch: 1},
   351  			{bestDescendant: NonExistentNode}}}
   352  	require.NoError(t, s.updateBestChildAndDescendant(0, 2))
   353  
   354  	// Verify parent's best child and best descendant are not changed.
   355  	assert.Equal(t, NonExistentNode, s.nodes[0].bestChild, "Did not get correct best child index")
   356  	assert.Equal(t, uint64(0), s.nodes[0].bestDescendant, "Did not get correct best descendant index")
   357  }
   358  
   359  func TestStore_Prune_LessThanThreshold(t *testing.T) {
   360  	// Define 100 nodes in store.
   361  	numOfNodes := 100
   362  	indices := make(map[[32]byte]uint64)
   363  	nodes := make([]*Node, 0)
   364  	for i := 0; i < numOfNodes; i++ {
   365  		indices[indexToHash(uint64(i))] = uint64(i)
   366  		nodes = append(nodes, &Node{slot: types.Slot(i)})
   367  	}
   368  
   369  	s := &Store{nodes: nodes, nodesIndices: indices, pruneThreshold: 100}
   370  
   371  	// Finalized root is at index 99 so everything before 99 should be pruned,
   372  	// but PruneThreshold is at 100 so nothing will be pruned.
   373  	require.NoError(t, s.prune(context.Background(), indexToHash(99)))
   374  	assert.Equal(t, 100, len(s.nodes), "Incorrect nodes count")
   375  	assert.Equal(t, 100, len(s.nodesIndices), "Incorrect node indices count")
   376  }
   377  
   378  func TestStore_Prune_MoreThanThreshold(t *testing.T) {
   379  	// Define 100 nodes in store.
   380  	numOfNodes := 100
   381  	indices := make(map[[32]byte]uint64)
   382  	nodes := make([]*Node, 0)
   383  	for i := 0; i < numOfNodes; i++ {
   384  		indices[indexToHash(uint64(i))] = uint64(i)
   385  		nodes = append(nodes, &Node{slot: types.Slot(i), root: indexToHash(uint64(i)),
   386  			bestDescendant: NonExistentNode, bestChild: NonExistentNode})
   387  	}
   388  
   389  	s := &Store{nodes: nodes, nodesIndices: indices}
   390  
   391  	// Finalized root is at index 99 so everything before 99 should be pruned.
   392  	require.NoError(t, s.prune(context.Background(), indexToHash(99)))
   393  	assert.Equal(t, 1, len(s.nodes), "Incorrect nodes count")
   394  	assert.Equal(t, 1, len(s.nodesIndices), "Incorrect node indices count")
   395  }
   396  
   397  func TestStore_Prune_MoreThanOnce(t *testing.T) {
   398  	// Define 100 nodes in store.
   399  	numOfNodes := 100
   400  	indices := make(map[[32]byte]uint64)
   401  	nodes := make([]*Node, 0)
   402  	for i := 0; i < numOfNodes; i++ {
   403  		indices[indexToHash(uint64(i))] = uint64(i)
   404  		nodes = append(nodes, &Node{slot: types.Slot(i), root: indexToHash(uint64(i)),
   405  			bestDescendant: NonExistentNode, bestChild: NonExistentNode})
   406  	}
   407  
   408  	s := &Store{nodes: nodes, nodesIndices: indices}
   409  
   410  	// Finalized root is at index 11 so everything before 11 should be pruned.
   411  	require.NoError(t, s.prune(context.Background(), indexToHash(10)))
   412  	assert.Equal(t, 90, len(s.nodes), "Incorrect nodes count")
   413  	assert.Equal(t, 90, len(s.nodesIndices), "Incorrect node indices count")
   414  
   415  	// One more time.
   416  	require.NoError(t, s.prune(context.Background(), indexToHash(20)))
   417  	assert.Equal(t, 80, len(s.nodes), "Incorrect nodes count")
   418  	assert.Equal(t, 80, len(s.nodesIndices), "Incorrect node indices count")
   419  }
   420  func TestStore_LeadsToViableHead(t *testing.T) {
   421  	tests := []struct {
   422  		n              *Node
   423  		justifiedEpoch types.Epoch
   424  		finalizedEpoch types.Epoch
   425  		want           bool
   426  	}{
   427  		{&Node{}, 0, 0, true},
   428  		{&Node{}, 1, 0, false},
   429  		{&Node{}, 0, 1, false},
   430  		{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, true},
   431  		{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, false},
   432  		{&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, true},
   433  	}
   434  	for _, tc := range tests {
   435  		s := &Store{
   436  			justifiedEpoch: tc.justifiedEpoch,
   437  			finalizedEpoch: tc.finalizedEpoch,
   438  			nodes:          []*Node{tc.n},
   439  		}
   440  		got, err := s.leadsToViableHead(tc.n)
   441  		require.NoError(t, err)
   442  		assert.Equal(t, tc.want, got)
   443  	}
   444  }
   445  
   446  func TestStore_ViableForHead(t *testing.T) {
   447  	tests := []struct {
   448  		n              *Node
   449  		justifiedEpoch types.Epoch
   450  		finalizedEpoch types.Epoch
   451  		want           bool
   452  	}{
   453  		{&Node{}, 0, 0, true},
   454  		{&Node{}, 1, 0, false},
   455  		{&Node{}, 0, 1, false},
   456  		{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, true},
   457  		{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, false},
   458  		{&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, true},
   459  	}
   460  	for _, tc := range tests {
   461  		s := &Store{
   462  			justifiedEpoch: tc.justifiedEpoch,
   463  			finalizedEpoch: tc.finalizedEpoch,
   464  		}
   465  		assert.Equal(t, tc.want, s.viableForHead(tc.n))
   466  	}
   467  }
   468  
   469  func TestStore_HasParent(t *testing.T) {
   470  	tests := []struct {
   471  		m    map[[32]byte]uint64
   472  		n    []*Node
   473  		r    [32]byte
   474  		want bool
   475  	}{
   476  		{r: [32]byte{'a'}, want: false},
   477  		{m: map[[32]byte]uint64{{'a'}: 0}, r: [32]byte{'a'}, want: false},
   478  		{m: map[[32]byte]uint64{{'a'}: 0}, r: [32]byte{'a'},
   479  			n: []*Node{{parent: NonExistentNode}}, want: false},
   480  		{m: map[[32]byte]uint64{{'a'}: 0},
   481  			n: []*Node{{parent: 0}}, r: [32]byte{'a'},
   482  			want: true},
   483  	}
   484  	for _, tc := range tests {
   485  		f := &ForkChoice{store: &Store{
   486  			nodesIndices: tc.m,
   487  			nodes:        tc.n,
   488  		}}
   489  		assert.Equal(t, tc.want, f.HasParent(tc.r))
   490  	}
   491  }
   492  
   493  func TestStore_AncestorRoot(t *testing.T) {
   494  	ctx := context.Background()
   495  	f := &ForkChoice{store: &Store{}}
   496  	f.store.nodesIndices = map[[32]byte]uint64{}
   497  	_, err := f.AncestorRoot(ctx, [32]byte{'a'}, 0)
   498  	assert.ErrorContains(t, "node does not exist", err)
   499  	f.store.nodesIndices[[32]byte{'a'}] = 0
   500  	_, err = f.AncestorRoot(ctx, [32]byte{'a'}, 0)
   501  	assert.ErrorContains(t, "node index out of range", err)
   502  	f.store.nodesIndices[[32]byte{'b'}] = 1
   503  	f.store.nodesIndices[[32]byte{'c'}] = 2
   504  	f.store.nodes = []*Node{
   505  		{slot: 1, root: [32]byte{'a'}, parent: NonExistentNode},
   506  		{slot: 2, root: [32]byte{'b'}, parent: 0},
   507  		{slot: 3, root: [32]byte{'c'}, parent: 1},
   508  	}
   509  
   510  	r, err := f.AncestorRoot(ctx, [32]byte{'c'}, 1)
   511  	require.NoError(t, err)
   512  	assert.Equal(t, bytesutil.ToBytes32(r), [32]byte{'a'})
   513  	r, err = f.AncestorRoot(ctx, [32]byte{'c'}, 2)
   514  	require.NoError(t, err)
   515  	assert.Equal(t, bytesutil.ToBytes32(r), [32]byte{'b'})
   516  }
   517  
   518  func TestStore_AncestorRootOutOfBound(t *testing.T) {
   519  	ctx := context.Background()
   520  	f := &ForkChoice{store: &Store{}}
   521  	f.store.nodesIndices = map[[32]byte]uint64{}
   522  	_, err := f.AncestorRoot(ctx, [32]byte{'a'}, 0)
   523  	assert.ErrorContains(t, "node does not exist", err)
   524  	f.store.nodesIndices[[32]byte{'a'}] = 0
   525  	_, err = f.AncestorRoot(ctx, [32]byte{'a'}, 0)
   526  	assert.ErrorContains(t, "node index out of range", err)
   527  	f.store.nodesIndices[[32]byte{'b'}] = 1
   528  	f.store.nodesIndices[[32]byte{'c'}] = 2
   529  	f.store.nodes = []*Node{
   530  		{slot: 1, root: [32]byte{'a'}, parent: NonExistentNode},
   531  		{slot: 2, root: [32]byte{'b'}, parent: 100}, // Out of bound parent.
   532  		{slot: 3, root: [32]byte{'c'}, parent: 1},
   533  	}
   534  
   535  	_, err = f.AncestorRoot(ctx, [32]byte{'c'}, 1)
   536  	require.ErrorContains(t, "node index out of range", err)
   537  }
   538  
   539  func TestStore_UpdateCanonicalNodes_WholeList(t *testing.T) {
   540  	ctx := context.Background()
   541  	f := &ForkChoice{store: &Store{}}
   542  	f.store.canonicalNodes = map[[32]byte]bool{}
   543  	f.store.nodesIndices = map[[32]byte]uint64{}
   544  	f.store.nodes = []*Node{
   545  		{slot: 1, root: [32]byte{'a'}, parent: NonExistentNode},
   546  		{slot: 2, root: [32]byte{'b'}, parent: 0},
   547  		{slot: 3, root: [32]byte{'c'}, parent: 1},
   548  	}
   549  	f.store.nodesIndices[[32]byte{'c'}] = 2
   550  	require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'c'}))
   551  	require.Equal(t, len(f.store.nodes), len(f.store.canonicalNodes))
   552  	require.Equal(t, true, f.IsCanonical([32]byte{'c'}))
   553  	require.Equal(t, true, f.IsCanonical([32]byte{'b'}))
   554  	require.Equal(t, true, f.IsCanonical([32]byte{'c'}))
   555  	require.DeepEqual(t, f.Node([32]byte{'c'}), f.store.nodes[2])
   556  	require.Equal(t, f.Node([32]byte{'d'}), (*Node)(nil))
   557  }
   558  
   559  func TestStore_UpdateCanonicalNodes_ParentAlreadyIn(t *testing.T) {
   560  	ctx := context.Background()
   561  	f := &ForkChoice{store: &Store{}}
   562  	f.store.canonicalNodes = map[[32]byte]bool{}
   563  	f.store.nodesIndices = map[[32]byte]uint64{}
   564  	f.store.nodes = []*Node{
   565  		{},
   566  		{slot: 2, root: [32]byte{'b'}, parent: 0},
   567  		{slot: 3, root: [32]byte{'c'}, parent: 1},
   568  	}
   569  	f.store.nodesIndices[[32]byte{'c'}] = 2
   570  	f.store.canonicalNodes[[32]byte{'b'}] = true
   571  	require.NoError(t, f.store.updateCanonicalNodes(ctx, [32]byte{'c'}))
   572  	require.Equal(t, len(f.store.nodes)-1, len(f.store.canonicalNodes))
   573  
   574  	require.Equal(t, true, f.IsCanonical([32]byte{'c'}))
   575  	require.Equal(t, true, f.IsCanonical([32]byte{'b'}))
   576  }
   577  
   578  func TestStore_UpdateCanonicalNodes_ContextCancelled(t *testing.T) {
   579  	ctx, cancel := context.WithCancel(context.Background())
   580  	f := &ForkChoice{store: &Store{}}
   581  	f.store.canonicalNodes = map[[32]byte]bool{}
   582  	f.store.nodesIndices = map[[32]byte]uint64{}
   583  	f.store.nodes = []*Node{
   584  		{slot: 1, root: [32]byte{'a'}, parent: NonExistentNode},
   585  		{slot: 2, root: [32]byte{'b'}, parent: 0},
   586  		{slot: 3, root: [32]byte{'c'}, parent: 1},
   587  	}
   588  	f.store.nodesIndices[[32]byte{'c'}] = 2
   589  	cancel()
   590  	require.ErrorContains(t, "context canceled", f.store.updateCanonicalNodes(ctx, [32]byte{'c'}))
   591  }