diff --git a/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go b/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go index 04c6794b52f8..9c948d52ccea 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/forkchoice.go @@ -642,8 +642,12 @@ func (f *ForkChoice) DependentRootForEpoch(root [32]byte, epoch primitives.Epoch if !ok || node == nil { return [32]byte{}, ErrNilNode } - if slots.ToEpoch(node.slot) >= epoch && node.parent != nil { - node = node.parent + if slots.ToEpoch(node.slot) >= epoch { + if node.parent != nil { + node = node.parent + } else { + return f.store.finalizedDependentRoot, nil + } } return node.root, nil } diff --git a/beacon-chain/forkchoice/doubly-linked-tree/store.go b/beacon-chain/forkchoice/doubly-linked-tree/store.go index d645ab9f3567..ac163d71df29 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/store.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/store.go @@ -212,6 +212,9 @@ func (s *Store) prune(ctx context.Context) error { return nil } + // Save the new finalized dependent root because it will be pruned + s.finalizedDependentRoot = finalizedNode.parent.root + // Prune nodeByRoot starting from root if err := s.pruneFinalizedNodeByRootMap(ctx, s.treeRootNode, finalizedNode); err != nil { return err diff --git a/beacon-chain/forkchoice/doubly-linked-tree/store_test.go b/beacon-chain/forkchoice/doubly-linked-tree/store_test.go index ec1f8404fadc..eb5515f2e232 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/store_test.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/store_test.go @@ -465,6 +465,7 @@ func TestStore_TargetRootForEpoch(t *testing.T) { ctx := t.Context() f := setup(1, 1) + // Insert a block in slot 32 state, blk, err := prepareForkchoiceState(ctx, params.BeaconConfig().SlotsPerEpoch, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, state, blk)) @@ -475,6 +476,7 @@ func TestStore_TargetRootForEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, dependent, [32]byte{}) + // Insert a block in slot 33 state, blk1, err := prepareForkchoiceState(ctx, params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'b'}, blk.Root(), params.BeaconConfig().ZeroHash, 1, 1) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, state, blk1)) @@ -488,7 +490,7 @@ func TestStore_TargetRootForEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, dependent, [32]byte{}) - // Insert a block for the next epoch (missed slot 0) + // Insert a block for the next epoch (missed slot 0), slot 65 state, blk2, err := prepareForkchoiceState(ctx, 2*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'c'}, blk1.Root(), params.BeaconConfig().ZeroHash, 1, 1) require.NoError(t, err) @@ -509,6 +511,7 @@ func TestStore_TargetRootForEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, dependent, blk1.Root()) + // Insert a block at slot 66 state, blk3, err := prepareForkchoiceState(ctx, 2*params.BeaconConfig().SlotsPerEpoch+2, [32]byte{'d'}, blk2.Root(), params.BeaconConfig().ZeroHash, 1, 1) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, state, blk3)) @@ -533,8 +536,11 @@ func TestStore_TargetRootForEpoch(t *testing.T) { dependent, err = f.DependentRoot(1) require.NoError(t, err) require.Equal(t, [32]byte{}, dependent) + dependent, err = f.DependentRoot(2) + require.NoError(t, err) + require.Equal(t, blk1.Root(), dependent) - // Insert a block for next epoch (slot 0 present) + // Insert a block for the next epoch, slot 96 (descends from finalized at slot 33) state, blk4, err := prepareForkchoiceState(ctx, 3*params.BeaconConfig().SlotsPerEpoch, [32]byte{'e'}, blk1.Root(), params.BeaconConfig().ZeroHash, 1, 1) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, state, blk4)) @@ -551,6 +557,7 @@ func TestStore_TargetRootForEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, dependent, blk1.Root()) + // Insert a block at slot 97 state, blk5, err := prepareForkchoiceState(ctx, 3*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'f'}, blk4.Root(), params.BeaconConfig().ZeroHash, 1, 1) require.NoError(t, err) require.NoError(t, f.InsertNode(ctx, state, blk5)) @@ -600,12 +607,16 @@ func TestStore_TargetRootForEpoch(t *testing.T) { require.NoError(t, err) require.Equal(t, target, blk1.Root()) - // Prune finalization + // Prune finalization, finalize the block at slot 96 s.finalizedCheckpoint.Root = blk4.Root() require.NoError(t, s.prune(ctx)) target, err = f.TargetRootForEpoch(blk4.Root(), 3) require.NoError(t, err) require.Equal(t, blk4.Root(), target) + // Dependent root for the finalized block should be the root of the pruned block at slot 33 + dependent, err = f.DependentRootForEpoch(blk4.Root(), 3) + require.NoError(t, err) + require.Equal(t, blk1.Root(), dependent) } func TestStore_DependentRootForEpoch(t *testing.T) { diff --git a/beacon-chain/forkchoice/doubly-linked-tree/types.go b/beacon-chain/forkchoice/doubly-linked-tree/types.go index 3db93944af80..094c47be1657 100644 --- a/beacon-chain/forkchoice/doubly-linked-tree/types.go +++ b/beacon-chain/forkchoice/doubly-linked-tree/types.go @@ -31,6 +31,7 @@ type Store struct { proposerBoostRoot [fieldparams.RootLength]byte // latest block root that was boosted after being received in a timely manner. previousProposerBoostRoot [fieldparams.RootLength]byte // previous block root that was boosted after being received in a timely manner. previousProposerBoostScore uint64 // previous proposer boosted root score. + finalizedDependentRoot [fieldparams.RootLength]byte // dependent root at finalized checkpoint. committeeWeight uint64 // tracks the total active validator balance divided by the number of slots per Epoch. treeRootNode *Node // the root node of the store tree. headNode *Node // last head Node diff --git a/changelog/potuz_finalized_deproot.md b/changelog/potuz_finalized_deproot.md new file mode 100644 index 000000000000..bbf33bbb58a7 --- /dev/null +++ b/changelog/potuz_finalized_deproot.md @@ -0,0 +1,3 @@ +### Added + +- Track the dependent root of the latest finalized checkpoint in forkchoice.