diff --git a/beacon-chain/blockchain/process_attestation_helpers.go b/beacon-chain/blockchain/process_attestation_helpers.go index 24185cc8db..ec9e86602c 100644 --- a/beacon-chain/blockchain/process_attestation_helpers.go +++ b/beacon-chain/blockchain/process_attestation_helpers.go @@ -22,7 +22,7 @@ import ( // The caller of this function must have a lock on forkchoice. func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) state.ReadOnlyBeaconState { headEpoch := slots.ToEpoch(s.HeadSlot()) - if c.Epoch < headEpoch || c.Epoch == 0 { + if c.Epoch+1 < headEpoch || c.Epoch == 0 { return nil } // Only use head state if the head state is compatible with the target checkpoint. @@ -30,11 +30,13 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st if err != nil { return nil } - headDependent, err := s.cfg.ForkChoiceStore.DependentRootForEpoch([32]byte(headRoot), c.Epoch-1) + // headEpoch - 1 equals c.Epoch if c is from the previous epoch and equals c.Epoch - 1 if c is from the current epoch. + // We don't use the smaller c.Epoch - 1 because forkchoice would not have the data to answer that. + headDependent, err := s.cfg.ForkChoiceStore.DependentRootForEpoch([32]byte(headRoot), headEpoch-1) if err != nil { return nil } - targetDependent, err := s.cfg.ForkChoiceStore.DependentRootForEpoch([32]byte(c.Root), c.Epoch-1) + targetDependent, err := s.cfg.ForkChoiceStore.DependentRootForEpoch([32]byte(c.Root), headEpoch-1) if err != nil { return nil } @@ -43,7 +45,7 @@ func (s *Service) getRecentPreState(ctx context.Context, c *ethpb.Checkpoint) st } // If the head state alone is enough, we can return it directly read only. - if c.Epoch == headEpoch { + if c.Epoch <= headEpoch { st, err := s.HeadStateReadOnly(ctx) if err != nil { return nil diff --git a/beacon-chain/blockchain/process_attestation_test.go b/beacon-chain/blockchain/process_attestation_test.go index 8874de9700..1a3b7e0f52 100644 --- a/beacon-chain/blockchain/process_attestation_test.go +++ b/beacon-chain/blockchain/process_attestation_test.go @@ -170,12 +170,13 @@ func TestService_GetRecentPreState(t *testing.T) { err = s.SetFinalizedCheckpoint(cp0) require.NoError(t, err) - st, root, err := prepareForkchoiceState(ctx, 31, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0) + st, blk, err := prepareForkchoiceState(ctx, 31, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0) require.NoError(t, err) - require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root)) + require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) service.head = &head{ root: [32]byte(ckRoot), state: s, + block: blk, slot: 31, } require.NotNil(t, service.getRecentPreState(ctx, ðpb.Checkpoint{Epoch: 1, Root: ckRoot})) @@ -197,12 +198,13 @@ func TestService_GetRecentPreState_Old_Checkpoint(t *testing.T) { err = s.SetFinalizedCheckpoint(cp0) require.NoError(t, err) - st, root, err := prepareForkchoiceState(ctx, 33, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0) + st, blk, err := prepareForkchoiceState(ctx, 33, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0) require.NoError(t, err) - require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root)) + require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) service.head = &head{ root: [32]byte(ckRoot), state: s, + block: blk, slot: 33, } require.IsNil(t, service.getRecentPreState(ctx, ðpb.Checkpoint{})) @@ -227,6 +229,7 @@ func TestService_GetRecentPreState_Same_DependentRoots(t *testing.T) { require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) st, blk, err = prepareForkchoiceState(ctx, 64, [32]byte{'T'}, blk.Root(), [32]byte{}, cp0, cp0) require.NoError(t, err) + headBlock := blk require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) st, blk, err = prepareForkchoiceState(ctx, 33, [32]byte{'U'}, [32]byte(ckRoot), [32]byte{}, cp0, cp0) require.NoError(t, err) @@ -235,8 +238,9 @@ func TestService_GetRecentPreState_Same_DependentRoots(t *testing.T) { service.head = &head{ root: [32]byte{'T'}, - state: s, + block: headBlock, slot: 64, + state: s, } require.NotNil(t, service.getRecentPreState(ctx, ðpb.Checkpoint{Epoch: 2, Root: cpRoot[:]})) } @@ -263,6 +267,7 @@ func TestService_GetRecentPreState_Different_DependentRoots(t *testing.T) { require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) st, blk, err = prepareForkchoiceState(ctx, 64, [32]byte{'U'}, blk.Root(), [32]byte{}, cp0, cp0) require.NoError(t, err) + headBlock := blk require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) st, blk, err = prepareForkchoiceState(ctx, 33, [32]byte{'V'}, [32]byte(ckRoot), [32]byte{}, cp0, cp0) require.NoError(t, err) @@ -270,7 +275,8 @@ func TestService_GetRecentPreState_Different_DependentRoots(t *testing.T) { cpRoot := blk.Root() service.head = &head{ - root: [32]byte{'T'}, + root: [32]byte{'U'}, + block: headBlock, state: s, slot: 64, } @@ -287,12 +293,13 @@ func TestService_GetRecentPreState_Different(t *testing.T) { err = s.SetFinalizedCheckpoint(cp0) require.NoError(t, err) - st, root, err := prepareForkchoiceState(ctx, 33, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0) + st, blk, err := prepareForkchoiceState(ctx, 33, [32]byte(ckRoot), [32]byte{}, [32]byte{'R'}, cp0, cp0) require.NoError(t, err) - require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, root)) + require.NoError(t, service.cfg.ForkChoiceStore.InsertNode(ctx, st, blk)) service.head = &head{ root: [32]byte(ckRoot), state: s, + block: blk, slot: 33, } require.IsNil(t, service.getRecentPreState(ctx, ðpb.Checkpoint{})) diff --git a/changelog/potuz_use_head_previous_epoch.md b/changelog/potuz_use_head_previous_epoch.md new file mode 100644 index 0000000000..278c652d3b --- /dev/null +++ b/changelog/potuz_use_head_previous_epoch.md @@ -0,0 +1,3 @@ +### Added + +- Use the head state to validate attestations for the previous epoch if head is compatible with the target checkpoint.