Backfill data columns (#15580)

**What type of PR is this?**

Feature

**What does this PR do? Why is it needed?**

Adds data column support to backfill.

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description to this PR with sufficient context for
reviewers to understand this PR.

---------

Co-authored-by: Kasey <kasey@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Preston Van Loon <preston@pvl.dev>
This commit is contained in:
kasey
2025-12-02 10:19:32 -05:00
committed by GitHub
parent 2773bdef89
commit 61de11e2c4
84 changed files with 10944 additions and 964 deletions

View File

@@ -14,6 +14,9 @@ import (
"github.com/sirupsen/logrus"
)
// errOverflow is returned when a slot calculation would overflow.
var errOverflow = errors.New("slot calculation overflows")
// MaxSlotBuffer specifies the max buffer given to slots from
// incoming objects. (24 mins with mainnet spec)
const MaxSlotBuffer = uint64(1 << 7)
@@ -108,7 +111,7 @@ func ToForkVersion(slot primitives.Slot) int {
func EpochStart(epoch primitives.Epoch) (primitives.Slot, error) {
slot, err := params.BeaconConfig().SlotsPerEpoch.SafeMul(uint64(epoch))
if err != nil {
return slot, errors.Errorf("start slot calculation overflows: %v", err)
return slot, errors.Wrap(errOverflow, "epoch start")
}
return slot, nil
}
@@ -127,7 +130,7 @@ func UnsafeEpochStart(epoch primitives.Epoch) primitives.Slot {
// current epoch.
func EpochEnd(epoch primitives.Epoch) (primitives.Slot, error) {
if epoch == math.MaxUint64 {
return 0, errors.New("start slot calculation overflows")
return 0, errors.Wrap(errOverflow, "epoch end")
}
slot, err := EpochStart(epoch + 1)
if err != nil {
@@ -284,8 +287,26 @@ func WithinVotingWindow(genesis time.Time, slot primitives.Slot) bool {
}
// MaxSafeEpoch gives the largest epoch value that can be safely converted to a slot.
// Note that just dividing max uint64 by slots per epoch is not sufficient,
// because the resulting slot could still be the start of an epoch that would overflow
// in the end slot computation. So we subtract 1 to ensure that the final epoch can always
// have 32 slots.
func MaxSafeEpoch() primitives.Epoch {
return primitives.Epoch(math.MaxUint64 / uint64(params.BeaconConfig().SlotsPerEpoch))
return primitives.Epoch(math.MaxUint64/uint64(params.BeaconConfig().SlotsPerEpoch)) - 1
}
// SafeEpochStartOrMax returns the start slot of the given epoch if it will not overflow,
// otherwise it takes the highest epoch that won't overflow,
// and to introduce a little margin for error, returns the slot beginning the prior epoch.
func SafeEpochStartOrMax(e primitives.Epoch) primitives.Slot {
// The max value converted to a slot can't be the start of a conceptual epoch,
// because the first slot of that epoch would be overflow
// so use the start slot of the epoch right before that value.
me := MaxSafeEpoch()
if e > me {
return UnsafeEpochStart(me)
}
return UnsafeEpochStart(e)
}
// SecondsUntilNextEpochStart returns how many seconds until the next Epoch start from the current time and slot

View File

@@ -117,7 +117,7 @@ func TestEpochStartSlot_OK(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, tt.startSlot, ss, "EpochStart(%d)", tt.epoch)
} else {
require.ErrorContains(t, "start slot calculation overflow", err)
require.ErrorIs(t, err, errOverflow)
}
}
}
@@ -141,7 +141,7 @@ func TestEpochEndSlot_OK(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, tt.startSlot, ss, "EpochStart(%d)", tt.epoch)
} else {
require.ErrorContains(t, "start slot calculation overflow", err)
require.ErrorIs(t, err, errOverflow)
}
}
}
@@ -700,3 +700,75 @@ func TestSlotTickerReplayBehaviour(t *testing.T) {
require.Equal(t, ticks, counter)
}
func TestSafeEpochStartOrMax(t *testing.T) {
farFuture := params.BeaconConfig().FarFutureEpoch
// ensure FAR_FUTURE_EPOCH is indeed larger than MaxSafeEpoch
require.Equal(t, true, farFuture > MaxSafeEpoch())
// demonstrate overflow in naive usage of FAR_FUTURE_EPOCH
require.Equal(t, true, farFuture*primitives.Epoch(params.BeaconConfig().SlotsPerEpoch) < farFuture)
// sanity check that example "ordinary" epoch does not overflow
fulu, err := EpochStart(params.BeaconConfig().FuluForkEpoch)
require.NoError(t, err)
require.Equal(t, primitives.Slot(params.BeaconConfig().FuluForkEpoch)*params.BeaconConfig().SlotsPerEpoch, fulu)
maxEpochStart := primitives.Slot(math.MaxUint64) - 63
tests := []struct {
name string
epoch primitives.Epoch
want primitives.Slot
err error
}{
{
name: "genesis",
epoch: 0,
want: 0,
},
{
name: "ordinary epoch",
epoch: params.BeaconConfig().FuluForkEpoch,
want: primitives.Slot(params.BeaconConfig().FuluForkEpoch) * params.BeaconConfig().SlotsPerEpoch,
},
{
name: "max epoch without overflow",
epoch: MaxSafeEpoch(),
want: maxEpochStart,
},
{
name: "max epoch causing overflow",
epoch: primitives.Epoch(math.MaxUint64),
want: maxEpochStart,
err: errOverflow,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := SafeEpochStartOrMax(tt.epoch)
require.Equal(t, tt.want, got)
// If we expect an error, it should be present for both start and end calculations
_, startErr := EpochStart(tt.epoch)
_, endErr := EpochEnd(tt.epoch)
if tt.err != nil {
require.ErrorIs(t, startErr, tt.err)
require.ErrorIs(t, endErr, tt.err)
} else {
require.NoError(t, startErr)
require.NoError(t, endErr)
}
})
}
}
func TestMaxEpoch(t *testing.T) {
maxEpoch := MaxSafeEpoch()
_, err := EpochStart(maxEpoch + 2)
require.ErrorIs(t, err, errOverflow)
_, err = EpochEnd(maxEpoch + 1)
require.ErrorIs(t, err, errOverflow)
require.Equal(t, primitives.Slot(math.MaxUint64)-63, primitives.Slot(maxEpoch)*params.BeaconConfig().SlotsPerEpoch)
_, err = EpochEnd(maxEpoch)
require.NoError(t, err)
_, err = EpochStart(maxEpoch)
require.NoError(t, err)
}