Forkchoice helper for dependent root (#15136)

* Forkchoice helper for dependent root

* James' suggestion
This commit is contained in:
Potuz
2025-04-07 09:42:39 -05:00
committed by GitHub
parent a6052efefb
commit 0d7d9bd5fc
8 changed files with 111 additions and 2 deletions

View File

@@ -126,3 +126,10 @@ func (s *Service) hashForGenesisBlock(ctx context.Context, root [32]byte) ([]byt
}
return bytesutil.SafeCopyBytes(header.BlockHash()), nil
}
// DependentRoot wraps the corresponding method in forkchoice
func (s *Service) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
s.cfg.ForkChoiceStore.RLock()
defer s.cfg.ForkChoiceStore.RUnlock()
return s.cfg.ForkChoiceStore.DependentRoot(epoch)
}

View File

@@ -622,6 +622,25 @@ func (f *ForkChoice) Slot(root [32]byte) (primitives.Slot, error) {
return n.slot, nil
}
// DependentRoot returns the last root of the epoch prior to the requested ecoch in the canonical chain.
func (f *ForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
tr, err := f.TargetRootForEpoch(f.CachedHeadRoot(), epoch)
if err != nil {
return [32]byte{}, err
}
if tr == [32]byte{} {
return [32]byte{}, nil
}
n, ok := f.store.nodeByRoot[tr]
if !ok || n == nil {
return [32]byte{}, ErrNilNode
}
if slots.ToEpoch(n.slot) == epoch && n.parent != nil {
n = n.parent
}
return n.root, nil
}
// TargetRootForEpoch returns the root of the target block for a given epoch.
// The epoch parameter is crucial to identify the correct target root. For example:
// When inserting a block at slot 63 with block root 0xA and target root 0xB (pointing to the block at slot 32),

View File

@@ -30,7 +30,7 @@ func TestLastRoot(t *testing.T) {
st, root, err = prepareForkchoiceState(ctx, 34, [32]byte{'6'}, [32]byte{'5'}, [32]byte{'6'}, 0, 0)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, st, root))
headNode, _ := f.store.nodeByRoot[[32]byte{'6'}]
headNode := f.store.nodeByRoot[[32]byte{'6'}]
f.store.headNode = headNode
require.Equal(t, [32]byte{'6'}, f.store.headNode.root)
require.Equal(t, [32]byte{'2'}, f.LastRoot(0))

View File

@@ -471,53 +471,101 @@ func TestStore_TargetRootForEpoch(t *testing.T) {
target, err := f.TargetRootForEpoch(blk.Root(), 1)
require.NoError(t, err)
require.Equal(t, target, blk.Root())
dependent, err := f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
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))
headRoot, err := f.Head(ctx) // To cache the head root
require.NoError(t, err)
require.Equal(t, headRoot, blk1.Root())
target, err = f.TargetRootForEpoch(blk1.Root(), 1)
require.NoError(t, err)
require.Equal(t, target, blk.Root())
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
// Insert a block for the next epoch (missed slot 0)
state, blk2, err := prepareForkchoiceState(ctx, 2*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'c'}, blk1.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk2))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk2.Root())
target, err = f.TargetRootForEpoch(blk2.Root(), 2)
require.NoError(t, err)
require.Equal(t, target, blk1.Root())
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk2.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
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))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk3.Root())
target, err = f.TargetRootForEpoch(blk2.Root(), 2)
require.NoError(t, err)
require.Equal(t, target, blk1.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
// Prune finalization
s := f.store
s.finalizedCheckpoint.Root = blk1.Root()
s.justifiedCheckpoint.Root = blk1.Root()
require.NoError(t, s.prune(ctx))
target, err = f.TargetRootForEpoch(blk1.Root(), 1)
require.NoError(t, err)
require.Equal(t, [32]byte{}, target)
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, [32]byte{}, dependent)
// Insert a block for next epoch (slot 0 present)
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))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk4.Root())
target, err = f.TargetRootForEpoch(blk4.Root(), 3)
require.NoError(t, err)
require.Equal(t, target, blk4.Root())
dependent, err = f.DependentRoot(3)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
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))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk5.Root())
target, err = f.TargetRootForEpoch(blk5.Root(), 3)
require.NoError(t, err)
require.Equal(t, target, blk4.Root())
dependent, err = f.DependentRoot(3)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
dependent, err = f.DependentRoot(2)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
// Target root where the target epoch is same or ahead of the block slot
target, err = f.TargetRootForEpoch(blk5.Root(), 4)
@@ -533,9 +581,21 @@ func TestStore_TargetRootForEpoch(t *testing.T) {
state, blk6, err := prepareForkchoiceState(ctx, 4*params.BeaconConfig().SlotsPerEpoch+1, [32]byte{'g'}, blk5.Root(), params.BeaconConfig().ZeroHash, 1, 1)
require.NoError(t, err)
require.NoError(t, f.InsertNode(ctx, state, blk6))
headRoot, err = f.Head(ctx)
require.NoError(t, err)
require.Equal(t, headRoot, blk6.Root())
target, err = f.TargetRootForEpoch(blk6.Root(), 4)
require.NoError(t, err)
require.Equal(t, target, blk5.Root())
dependent, err = f.DependentRoot(4)
require.NoError(t, err)
require.Equal(t, dependent, blk5.Root())
dependent, err = f.DependentRoot(3)
require.NoError(t, err)
require.Equal(t, dependent, blk1.Root())
dependent, err = f.DependentRoot(1)
require.NoError(t, err)
require.Equal(t, dependent, [32]byte{})
target, err = f.TargetRootForEpoch(blk6.Root(), 2)
require.NoError(t, err)
require.Equal(t, target, blk1.Root())

View File

@@ -79,6 +79,7 @@ type FastGetter interface {
ReceivedBlocksLastEpoch() (uint64, error)
ShouldOverrideFCU() bool
Slot([32]byte) (primitives.Slot, error)
DependentRoot(primitives.Epoch) ([32]byte, error)
TargetRootForEpoch([32]byte, primitives.Epoch) ([32]byte, error)
UnrealizedJustifiedPayloadBlockHash() [32]byte
Weight(root [32]byte) (uint64, error)

View File

@@ -170,6 +170,13 @@ func (ro *ROForkChoice) LastRoot(e primitives.Epoch) [32]byte {
return ro.getter.LastRoot(e)
}
// DependentRoot delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) DependentRoot(epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock()
defer ro.l.RUnlock()
return ro.getter.DependentRoot(epoch)
}
// TargetRootForEpoch delegates to the underlying forkchoice call, under a lock.
func (ro *ROForkChoice) TargetRootForEpoch(root [32]byte, epoch primitives.Epoch) ([32]byte, error) {
ro.l.RLock()

View File

@@ -39,6 +39,7 @@ const (
lastRootCalled
targetRootForEpochCalled
parentRootCalled
dependentRootCalled
)
func _discard(t *testing.T, e error) {
@@ -156,6 +157,11 @@ func TestROLocking(t *testing.T) {
call: targetRootForEpochCalled,
cb: func(g FastGetter) { _, err := g.TargetRootForEpoch([32]byte{}, 0); _discard(t, err) },
},
{
name: "dependentRootCalled",
call: dependentRootCalled,
cb: func(g FastGetter) { _, err := g.DependentRoot(0); _discard(t, err) },
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
@@ -293,6 +299,12 @@ func (ro *mockROForkchoice) LastRoot(_ primitives.Epoch) [32]byte {
return [32]byte{}
}
// DependentRoot impoements FastGetter.
func (ro *mockROForkchoice) DependentRoot(_ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, dependentRootCalled)
return [32]byte{}, nil
}
// TargetRootForEpoch implements FastGetter.
func (ro *mockROForkchoice) TargetRootForEpoch(_ [32]byte, _ primitives.Epoch) ([32]byte, error) {
ro.calls = append(ro.calls, targetRootForEpochCalled)

View File

@@ -0,0 +1,3 @@
### Ignored
- Add helper for dependent root in forkchoice.