From 271ee2ed328821bb1592674945030ae843fc91a8 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 7 Oct 2021 12:15:35 -0700 Subject: [PATCH] Cleanup slot helpers into intended locations (#9752) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Refactor slot time helpers to intended locations * Gazelle * Update beacon-chain/core/time/slot_epoch.go Co-authored-by: Radosław Kapka * Update beacon-chain/core/time/slot_epoch_test.go Co-authored-by: Radosław Kapka * Go fmt * Update transition_fuzz_test.go Co-authored-by: Radosław Kapka --- beacon-chain/core/time/BUILD.bazel | 1 - beacon-chain/core/time/slot_epoch.go | 9 + beacon-chain/core/time/slot_epoch_test.go | 367 ++---------------- beacon-chain/core/transition/BUILD.bazel | 1 - beacon-chain/core/transition/transition.go | 23 +- .../core/transition/transition_fuzz_test.go | 3 +- .../core/transition/transition_test.go | 66 ---- beacon-chain/state/stategen/replay.go | 2 +- time/slots/BUILD.bazel | 1 + time/slots/slottime_test.go | 335 ++++++++++++++++ 10 files changed, 382 insertions(+), 426 deletions(-) diff --git a/beacon-chain/core/time/BUILD.bazel b/beacon-chain/core/time/BUILD.bazel index 9b8ddc7c5..4826b559f 100644 --- a/beacon-chain/core/time/BUILD.bazel +++ b/beacon-chain/core/time/BUILD.bazel @@ -24,7 +24,6 @@ go_test( "//proto/prysm/v1alpha1:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", - "//time:go_default_library", "//time/slots:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", ], diff --git a/beacon-chain/core/time/slot_epoch.go b/beacon-chain/core/time/slot_epoch.go index f140271d2..ad762db13 100644 --- a/beacon-chain/core/time/slot_epoch.go +++ b/beacon-chain/core/time/slot_epoch.go @@ -59,3 +59,12 @@ func CanUpgradeToAltair(slot types.Slot) bool { altairEpoch := slots.ToEpoch(slot) == params.BeaconConfig().AltairForkEpoch return epochStart && altairEpoch } + +// CanProcessEpoch checks the eligibility to process epoch. +// The epoch can be processed at the end of the last slot of every epoch. +// +// Spec pseudocode definition: +// If (state.slot + 1) % SLOTS_PER_EPOCH == 0: +func CanProcessEpoch(state state.ReadOnlyBeaconState) bool { + return (state.Slot()+1)%params.BeaconConfig().SlotsPerEpoch == 0 +} diff --git a/beacon-chain/core/time/slot_epoch_test.go b/beacon-chain/core/time/slot_epoch_test.go index 7ee2bda48..d6aacb653 100644 --- a/beacon-chain/core/time/slot_epoch_test.go +++ b/beacon-chain/core/time/slot_epoch_test.go @@ -1,9 +1,7 @@ package time import ( - "math" "testing" - "time" types "github.com/prysmaticlabs/eth2-types" v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" @@ -11,7 +9,6 @@ import ( eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/testing/assert" "github.com/prysmaticlabs/prysm/testing/require" - prysmTime "github.com/prysmaticlabs/prysm/time" "github.com/prysmaticlabs/prysm/time/slots" ) @@ -83,338 +80,6 @@ func TestNextEpoch_OK(t *testing.T) { } } -func TestEpochStartSlot_OK(t *testing.T) { - tests := []struct { - epoch types.Epoch - startSlot types.Slot - error bool - }{ - {epoch: 0, startSlot: 0 * params.BeaconConfig().SlotsPerEpoch, error: false}, - {epoch: 1, startSlot: 1 * params.BeaconConfig().SlotsPerEpoch, error: false}, - {epoch: 10, startSlot: 10 * params.BeaconConfig().SlotsPerEpoch, error: false}, - {epoch: 1 << 58, startSlot: 1 << 63, error: false}, - {epoch: 1 << 59, startSlot: 1 << 63, error: true}, - {epoch: 1 << 60, startSlot: 1 << 63, error: true}, - } - for _, tt := range tests { - ss, err := slots.EpochStart(tt.epoch) - if !tt.error { - require.NoError(t, err) - assert.Equal(t, tt.startSlot, ss, "EpochStart(%d)", tt.epoch) - } else { - require.ErrorContains(t, "start slot calculation overflow", err) - } - } -} - -func TestEpochEndSlot_OK(t *testing.T) { - tests := []struct { - epoch types.Epoch - startSlot types.Slot - error bool - }{ - {epoch: 0, startSlot: 1*params.BeaconConfig().SlotsPerEpoch - 1, error: false}, - {epoch: 1, startSlot: 2*params.BeaconConfig().SlotsPerEpoch - 1, error: false}, - {epoch: 10, startSlot: 11*params.BeaconConfig().SlotsPerEpoch - 1, error: false}, - {epoch: 1 << 59, startSlot: 1 << 63, error: true}, - {epoch: 1 << 60, startSlot: 1 << 63, error: true}, - {epoch: math.MaxUint64, startSlot: 0, error: true}, - } - for _, tt := range tests { - ss, err := slots.EpochEnd(tt.epoch) - if !tt.error { - require.NoError(t, err) - assert.Equal(t, tt.startSlot, ss, "EpochStart(%d)", tt.epoch) - } else { - require.ErrorContains(t, "start slot calculation overflow", err) - } - } -} - -func TestIsEpochStart(t *testing.T) { - epochLength := params.BeaconConfig().SlotsPerEpoch - - tests := []struct { - slot types.Slot - result bool - }{ - { - slot: epochLength + 1, - result: false, - }, - { - slot: epochLength - 1, - result: false, - }, - { - slot: epochLength, - result: true, - }, - { - slot: epochLength * 2, - result: true, - }, - } - - for _, tt := range tests { - assert.Equal(t, tt.result, slots.IsEpochStart(tt.slot), "IsEpochStart(%d)", tt.slot) - } -} - -func TestIsEpochEnd(t *testing.T) { - epochLength := params.BeaconConfig().SlotsPerEpoch - - tests := []struct { - slot types.Slot - result bool - }{ - { - slot: epochLength + 1, - result: false, - }, - { - slot: epochLength, - result: false, - }, - { - slot: epochLength - 1, - result: true, - }, - } - - for _, tt := range tests { - assert.Equal(t, tt.result, slots.IsEpochEnd(tt.slot), "IsEpochEnd(%d)", tt.slot) - } -} - -func TestSlotsSinceEpochStarts(t *testing.T) { - tests := []struct { - slots types.Slot - wantedSlots types.Slot - }{ - {slots: 0, wantedSlots: 0}, - {slots: 1, wantedSlots: 1}, - {slots: params.BeaconConfig().SlotsPerEpoch - 1, wantedSlots: params.BeaconConfig().SlotsPerEpoch - 1}, - {slots: params.BeaconConfig().SlotsPerEpoch + 1, wantedSlots: 1}, - {slots: 10*params.BeaconConfig().SlotsPerEpoch + 2, wantedSlots: 2}, - } - for _, tt := range tests { - assert.Equal(t, tt.wantedSlots, slots.SinceEpochStarts(tt.slots)) - } -} - -func TestRoundUpToNearestEpoch_OK(t *testing.T) { - tests := []struct { - startSlot types.Slot - roundedUpSlot types.Slot - }{ - {startSlot: 0 * params.BeaconConfig().SlotsPerEpoch, roundedUpSlot: 0}, - {startSlot: 1*params.BeaconConfig().SlotsPerEpoch - 10, roundedUpSlot: 1 * params.BeaconConfig().SlotsPerEpoch}, - {startSlot: 10*params.BeaconConfig().SlotsPerEpoch - (params.BeaconConfig().SlotsPerEpoch - 1), roundedUpSlot: 10 * params.BeaconConfig().SlotsPerEpoch}, - } - for _, tt := range tests { - assert.Equal(t, tt.roundedUpSlot, slots.RoundUpToNearestEpoch(tt.startSlot), "RoundUpToNearestEpoch(%d)", tt.startSlot) - } -} - -func TestSlotToTime(t *testing.T) { - type args struct { - genesisTimeSec uint64 - slot types.Slot - } - tests := []struct { - name string - args args - want time.Time - wantedErr string - }{ - { - name: "slot_0", - args: args{ - genesisTimeSec: 0, - slot: 0, - }, - want: time.Unix(0, 0), - }, - { - name: "slot_1", - args: args{ - genesisTimeSec: 0, - slot: 1, - }, - want: time.Unix(int64(1*params.BeaconConfig().SecondsPerSlot), 0), - }, - { - name: "slot_12", - args: args{ - genesisTimeSec: 500, - slot: 12, - }, - want: time.Unix(500+int64(12*params.BeaconConfig().SecondsPerSlot), 0), - }, - { - name: "overflow", - args: args{ - genesisTimeSec: 500, - slot: math.MaxUint64, - }, - wantedErr: "is in the far distant future", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := slots.ToTime(tt.args.genesisTimeSec, tt.args.slot) - if tt.wantedErr != "" { - assert.ErrorContains(t, tt.wantedErr, err) - } else { - assert.NoError(t, err) - assert.DeepEqual(t, tt.want, got) - } - }) - } -} - -func TestVerifySlotTime(t *testing.T) { - type args struct { - genesisTime int64 - slot types.Slot - timeTolerance time.Duration - } - tests := []struct { - name string - args args - wantedErr string - }{ - { - name: "Past slot", - args: args{ - genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), - slot: 3, - }, - }, - { - name: "within tolerance", - args: args{ - genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Add(20 * time.Millisecond).Unix(), - slot: 5, - }, - }, - { - name: "future slot", - args: args{ - genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), - slot: 6, - }, - wantedErr: "could not process slot from the future", - }, - { - name: "max future slot", - args: args{ - genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), - slot: types.Slot(slots.MaxSlotBuffer + 6), - }, - wantedErr: "exceeds max allowed value relative to the local clock", - }, - { - name: "evil future slot", - args: args{ - genesisTime: prysmTime.Now().Add(-1 * 24 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), // 24 slots in the past - // Gets multiplied with slot duration, and results in an overflow. Wraps around to a valid time. - // Lower than max signed int. And chosen specifically to wrap to a valid slot 24 - slot: types.Slot((^uint64(0))/params.BeaconConfig().SecondsPerSlot) + 24, - }, - wantedErr: "is in the far distant future", - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := slots.VerifyTime(uint64(tt.args.genesisTime), tt.args.slot, tt.args.timeTolerance) - if tt.wantedErr != "" { - assert.ErrorContains(t, tt.wantedErr, err) - } else { - assert.NoError(t, err) - } - }) - } -} - -func TestValidateSlotClock_HandlesBadSlot(t *testing.T) { - genTime := prysmTime.Now().Add(-1 * time.Duration(slots.MaxSlotBuffer) * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix() - - assert.NoError(t, slots.ValidateClock(types.Slot(slots.MaxSlotBuffer), uint64(genTime)), "unexpected error validating slot") - assert.NoError(t, slots.ValidateClock(types.Slot(2*slots.MaxSlotBuffer), uint64(genTime)), "unexpected error validating slot") - assert.ErrorContains(t, "which exceeds max allowed value relative to the local clock", slots.ValidateClock(types.Slot(2*slots.MaxSlotBuffer+1), uint64(genTime)), "no error from bad slot") - assert.ErrorContains(t, "which exceeds max allowed value relative to the local clock", slots.ValidateClock(1<<63, uint64(genTime)), "no error from bad slot") -} - -func TestPrevSlot(t *testing.T) { - tests := []struct { - name string - slot types.Slot - want types.Slot - }{ - { - name: "no underflow", - slot: 0, - want: 0, - }, - { - name: "slot 1", - slot: 1, - want: 0, - }, - { - name: "slot 2", - slot: 2, - want: 1, - }, - { - name: "max", - slot: 1<<64 - 1, - want: 1<<64 - 1 - 1, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := slots.PrevSlot(tt.slot); got != tt.want { - t.Errorf("PrevSlot() = %v, want %v", got, tt.want) - } - }) - } -} - -func TestSyncCommitteePeriod(t *testing.T) { - tests := []struct { - epoch types.Epoch - wanted uint64 - }{ - {epoch: 0, wanted: 0}, - {epoch: 0, wanted: 0 / uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)}, - {epoch: 1, wanted: 1 / uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)}, - {epoch: 1000, wanted: 1000 / uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)}, - } - for _, test := range tests { - require.Equal(t, test.wanted, slots.SyncCommitteePeriod(test.epoch)) - } -} - -func TestSyncCommitteePeriodStartEpoch(t *testing.T) { - tests := []struct { - epoch types.Epoch - wanted types.Epoch - }{ - {epoch: 0, wanted: 0}, - {epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod + 1, wanted: params.BeaconConfig().EpochsPerSyncCommitteePeriod}, - {epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod*2 + 100, wanted: params.BeaconConfig().EpochsPerSyncCommitteePeriod * 2}, - {epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod*params.BeaconConfig().EpochsPerSyncCommitteePeriod + 1, wanted: params.BeaconConfig().EpochsPerSyncCommitteePeriod * params.BeaconConfig().EpochsPerSyncCommitteePeriod}, - } - for _, test := range tests { - e, err := slots.SyncCommitteePeriodStartEpoch(test.epoch) - require.NoError(t, err) - require.Equal(t, test.wanted, e) - } -} - func TestCanUpgradeToAltair(t *testing.T) { bc := params.BeaconConfig() bc.AltairForkEpoch = 5 @@ -448,3 +113,35 @@ func TestCanUpgradeToAltair(t *testing.T) { }) } } + +func TestCanProcessEpoch_TrueOnEpochsLastSlot(t *testing.T) { + tests := []struct { + slot types.Slot + canProcessEpoch bool + }{ + { + slot: 1, + canProcessEpoch: false, + }, { + slot: 63, + canProcessEpoch: true, + }, + { + slot: 64, + canProcessEpoch: false, + }, { + slot: 127, + canProcessEpoch: true, + }, { + slot: 1000000000, + canProcessEpoch: false, + }, + } + + for _, tt := range tests { + b := ð.BeaconState{Slot: tt.slot} + s, err := v1.InitializeFromProto(b) + require.NoError(t, err) + assert.Equal(t, tt.canProcessEpoch, CanProcessEpoch(s), "CanProcessEpoch(%d)", tt.slot) + } +} diff --git a/beacon-chain/core/transition/BUILD.bazel b/beacon-chain/core/transition/BUILD.bazel index 590d75dd0..1da96a65c 100644 --- a/beacon-chain/core/transition/BUILD.bazel +++ b/beacon-chain/core/transition/BUILD.bazel @@ -46,7 +46,6 @@ go_library( "//proto/prysm/v1alpha1:go_default_library", "//proto/prysm/v1alpha1/block:go_default_library", "//runtime/version:go_default_library", - "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", "@com_github_prometheus_client_golang//prometheus/promauto:go_default_library", diff --git a/beacon-chain/core/transition/transition.go b/beacon-chain/core/transition/transition.go index b8c4d2789..91411ea45 100644 --- a/beacon-chain/core/transition/transition.go +++ b/beacon-chain/core/transition/transition.go @@ -22,7 +22,6 @@ import ( "github.com/prysmaticlabs/prysm/monitoring/tracing" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/block" "github.com/prysmaticlabs/prysm/runtime/version" - "github.com/prysmaticlabs/prysm/time/slots" "go.opencensus.io/trace" ) @@ -237,7 +236,7 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot types.Slot) tracing.AnnotateError(span, err) return nil, errors.Wrap(err, "could not process slot") } - if CanProcessEpoch(state) { + if time.CanProcessEpoch(state) { switch state.Version() { case version.Phase0: state, err = ProcessEpochPrecompute(ctx, state) @@ -260,7 +259,7 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot types.Slot) return nil, errors.Wrap(err, "failed to increment state slot") } - if CanUpgradeToAltair(state.Slot()) { + if time.CanUpgradeToAltair(state.Slot()) { state, err = altair.UpgradeToAltair(ctx, state) if err != nil { tracing.AnnotateError(span, err) @@ -279,15 +278,6 @@ func ProcessSlots(ctx context.Context, state state.BeaconState, slot types.Slot) return state, nil } -// CanUpgradeToAltair returns true if the input `slot` can upgrade to Altair. -// Spec code: -// If state.slot % SLOTS_PER_EPOCH == 0 and compute_epoch_at_slot(state.slot) == ALTAIR_FORK_EPOCH -func CanUpgradeToAltair(slot types.Slot) bool { - epochStart := slots.IsEpochStart(slot) - altairEpoch := slots.ToEpoch(slot) == params.BeaconConfig().AltairForkEpoch - return epochStart && altairEpoch -} - // VerifyOperationLengths verifies that block operation lengths are valid. func VerifyOperationLengths(_ context.Context, state state.BeaconState, b block.SignedBeaconBlock) (state.BeaconState, error) { if err := helpers.VerifyNilBeaconBlock(b); err != nil { @@ -343,15 +333,6 @@ func VerifyOperationLengths(_ context.Context, state state.BeaconState, b block. return state, nil } -// CanProcessEpoch checks the eligibility to process epoch. -// The epoch can be processed at the end of the last slot of every epoch -// -// Spec pseudocode definition: -// If (state.slot + 1) % SLOTS_PER_EPOCH == 0: -func CanProcessEpoch(state state.ReadOnlyBeaconState) bool { - return (state.Slot()+1)%params.BeaconConfig().SlotsPerEpoch == 0 -} - // ProcessEpochPrecompute describes the per epoch operations that are performed on the beacon state. // It's optimized by pre computing validator attested info and epoch total/attested balances upfront. func ProcessEpochPrecompute(ctx context.Context, state state.BeaconState) (state.BeaconState, error) { diff --git a/beacon-chain/core/transition/transition_fuzz_test.go b/beacon-chain/core/transition/transition_fuzz_test.go index e597577fb..f53dc6ee8 100644 --- a/beacon-chain/core/transition/transition_fuzz_test.go +++ b/beacon-chain/core/transition/transition_fuzz_test.go @@ -6,6 +6,7 @@ import ( fuzz "github.com/google/gofuzz" types "github.com/prysmaticlabs/eth2-types" + "github.com/prysmaticlabs/prysm/beacon-chain/core/time" v1 "github.com/prysmaticlabs/prysm/beacon-chain/state/v1" ethpb "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper" @@ -122,7 +123,7 @@ func TestFuzzCanProcessEpoch_10000(_ *testing.T) { fuzzer.NilChance(0.1) for i := 0; i < 10000; i++ { fuzzer.Fuzz(state) - CanProcessEpoch(state) + time.CanProcessEpoch(state) } } diff --git a/beacon-chain/core/transition/transition_test.go b/beacon-chain/core/transition/transition_test.go index fce57a7ae..63d00e320 100644 --- a/beacon-chain/core/transition/transition_test.go +++ b/beacon-chain/core/transition/transition_test.go @@ -382,38 +382,6 @@ func TestProcessEpochPrecompute_CanProcess(t *testing.T) { assert.Equal(t, uint64(0), newState.Slashings()[2], "Unexpected slashed balance") } -func TestCanProcessEpoch_TrueOnEpochs(t *testing.T) { - tests := []struct { - slot types.Slot - canProcessEpoch bool - }{ - { - slot: 1, - canProcessEpoch: false, - }, { - slot: 63, - canProcessEpoch: true, - }, - { - slot: 64, - canProcessEpoch: false, - }, { - slot: 127, - canProcessEpoch: true, - }, { - slot: 1000000000, - canProcessEpoch: false, - }, - } - - for _, tt := range tests { - b := ðpb.BeaconState{Slot: tt.slot} - s, err := v1.InitializeFromProto(b) - require.NoError(t, err) - assert.Equal(t, tt.canProcessEpoch, transition.CanProcessEpoch(s), "CanProcessEpoch(%d)", tt.slot) - } -} - func TestProcessBlock_OverMaxProposerSlashings(t *testing.T) { maxSlashings := params.BeaconConfig().MaxProposerSlashings b := ðpb.SignedBeaconBlock{ @@ -589,37 +557,3 @@ func TestProcessSlotsUsingNextSlotCache(t *testing.T) { require.NoError(t, err) require.Equal(t, types.Slot(5), s.Slot()) } - -func TestCanUpgradeToAltair(t *testing.T) { - bc := params.BeaconConfig() - bc.AltairForkEpoch = 5 - params.OverrideBeaconConfig(bc) - tests := []struct { - name string - slot types.Slot - want bool - }{ - { - name: "not epoch start", - slot: 1, - want: false, - }, - { - name: "not altair epoch", - slot: params.BeaconConfig().SlotsPerEpoch, - want: false, - }, - { - name: "altair epoch", - slot: types.Slot(params.BeaconConfig().AltairForkEpoch) * params.BeaconConfig().SlotsPerEpoch, - want: true, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := transition.CanUpgradeToAltair(tt.slot); got != tt.want { - t.Errorf("canUpgradeToAltair() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/beacon-chain/state/stategen/replay.go b/beacon-chain/state/stategen/replay.go index 114d5de2f..6ca0dad22 100644 --- a/beacon-chain/state/stategen/replay.go +++ b/beacon-chain/state/stategen/replay.go @@ -188,7 +188,7 @@ func processSlotsStateGen(ctx context.Context, state state.BeaconState, slot typ if err != nil { return nil, errors.Wrap(err, "could not process slot") } - if transition.CanProcessEpoch(state) { + if time.CanProcessEpoch(state) { switch state.Version() { case version.Phase0: state, err = transition.ProcessEpochPrecompute(ctx, state) diff --git a/time/slots/BUILD.bazel b/time/slots/BUILD.bazel index fe8cf0524..9d77102ce 100644 --- a/time/slots/BUILD.bazel +++ b/time/slots/BUILD.bazel @@ -31,6 +31,7 @@ go_test( embed = [":go_default_library"], deps = [ "//config/params:go_default_library", + "//testing/assert:go_default_library", "//testing/require:go_default_library", "//time:go_default_library", "@com_github_prysmaticlabs_eth2_types//:go_default_library", diff --git a/time/slots/slottime_test.go b/time/slots/slottime_test.go index ec1534995..303431970 100644 --- a/time/slots/slottime_test.go +++ b/time/slots/slottime_test.go @@ -1,11 +1,14 @@ package slots import ( + "math" "testing" "time" types "github.com/prysmaticlabs/eth2-types" "github.com/prysmaticlabs/prysm/config/params" + "github.com/prysmaticlabs/prysm/testing/assert" + "github.com/prysmaticlabs/prysm/testing/require" prysmTime "github.com/prysmaticlabs/prysm/time" ) @@ -125,3 +128,335 @@ func TestMultiplySlotBy(t *testing.T) { }) } } + +func TestEpochStartSlot_OK(t *testing.T) { + tests := []struct { + epoch types.Epoch + startSlot types.Slot + error bool + }{ + {epoch: 0, startSlot: 0 * params.BeaconConfig().SlotsPerEpoch, error: false}, + {epoch: 1, startSlot: 1 * params.BeaconConfig().SlotsPerEpoch, error: false}, + {epoch: 10, startSlot: 10 * params.BeaconConfig().SlotsPerEpoch, error: false}, + {epoch: 1 << 58, startSlot: 1 << 63, error: false}, + {epoch: 1 << 59, startSlot: 1 << 63, error: true}, + {epoch: 1 << 60, startSlot: 1 << 63, error: true}, + } + for _, tt := range tests { + ss, err := EpochStart(tt.epoch) + if !tt.error { + require.NoError(t, err) + assert.Equal(t, tt.startSlot, ss, "EpochStart(%d)", tt.epoch) + } else { + require.ErrorContains(t, "start slot calculation overflow", err) + } + } +} + +func TestEpochEndSlot_OK(t *testing.T) { + tests := []struct { + epoch types.Epoch + startSlot types.Slot + error bool + }{ + {epoch: 0, startSlot: 1*params.BeaconConfig().SlotsPerEpoch - 1, error: false}, + {epoch: 1, startSlot: 2*params.BeaconConfig().SlotsPerEpoch - 1, error: false}, + {epoch: 10, startSlot: 11*params.BeaconConfig().SlotsPerEpoch - 1, error: false}, + {epoch: 1 << 59, startSlot: 1 << 63, error: true}, + {epoch: 1 << 60, startSlot: 1 << 63, error: true}, + {epoch: math.MaxUint64, startSlot: 0, error: true}, + } + for _, tt := range tests { + ss, err := EpochEnd(tt.epoch) + if !tt.error { + require.NoError(t, err) + assert.Equal(t, tt.startSlot, ss, "EpochStart(%d)", tt.epoch) + } else { + require.ErrorContains(t, "start slot calculation overflow", err) + } + } +} + +func TestIsEpochStart(t *testing.T) { + epochLength := params.BeaconConfig().SlotsPerEpoch + + tests := []struct { + slot types.Slot + result bool + }{ + { + slot: epochLength + 1, + result: false, + }, + { + slot: epochLength - 1, + result: false, + }, + { + slot: epochLength, + result: true, + }, + { + slot: epochLength * 2, + result: true, + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.result, IsEpochStart(tt.slot), "IsEpochStart(%d)", tt.slot) + } +} + +func TestIsEpochEnd(t *testing.T) { + epochLength := params.BeaconConfig().SlotsPerEpoch + + tests := []struct { + slot types.Slot + result bool + }{ + { + slot: epochLength + 1, + result: false, + }, + { + slot: epochLength, + result: false, + }, + { + slot: epochLength - 1, + result: true, + }, + } + + for _, tt := range tests { + assert.Equal(t, tt.result, IsEpochEnd(tt.slot), "IsEpochEnd(%d)", tt.slot) + } +} + +func TestSlotsSinceEpochStarts(t *testing.T) { + tests := []struct { + slots types.Slot + wantedSlots types.Slot + }{ + {slots: 0, wantedSlots: 0}, + {slots: 1, wantedSlots: 1}, + {slots: params.BeaconConfig().SlotsPerEpoch - 1, wantedSlots: params.BeaconConfig().SlotsPerEpoch - 1}, + {slots: params.BeaconConfig().SlotsPerEpoch + 1, wantedSlots: 1}, + {slots: 10*params.BeaconConfig().SlotsPerEpoch + 2, wantedSlots: 2}, + } + for _, tt := range tests { + assert.Equal(t, tt.wantedSlots, SinceEpochStarts(tt.slots)) + } +} + +func TestRoundUpToNearestEpoch_OK(t *testing.T) { + tests := []struct { + startSlot types.Slot + roundedUpSlot types.Slot + }{ + {startSlot: 0 * params.BeaconConfig().SlotsPerEpoch, roundedUpSlot: 0}, + {startSlot: 1*params.BeaconConfig().SlotsPerEpoch - 10, roundedUpSlot: 1 * params.BeaconConfig().SlotsPerEpoch}, + {startSlot: 10*params.BeaconConfig().SlotsPerEpoch - (params.BeaconConfig().SlotsPerEpoch - 1), roundedUpSlot: 10 * params.BeaconConfig().SlotsPerEpoch}, + } + for _, tt := range tests { + assert.Equal(t, tt.roundedUpSlot, RoundUpToNearestEpoch(tt.startSlot), "RoundUpToNearestEpoch(%d)", tt.startSlot) + } +} + +func TestSlotToTime(t *testing.T) { + type args struct { + genesisTimeSec uint64 + slot types.Slot + } + tests := []struct { + name string + args args + want time.Time + wantedErr string + }{ + { + name: "slot_0", + args: args{ + genesisTimeSec: 0, + slot: 0, + }, + want: time.Unix(0, 0), + }, + { + name: "slot_1", + args: args{ + genesisTimeSec: 0, + slot: 1, + }, + want: time.Unix(int64(1*params.BeaconConfig().SecondsPerSlot), 0), + }, + { + name: "slot_12", + args: args{ + genesisTimeSec: 500, + slot: 12, + }, + want: time.Unix(500+int64(12*params.BeaconConfig().SecondsPerSlot), 0), + }, + { + name: "overflow", + args: args{ + genesisTimeSec: 500, + slot: math.MaxUint64, + }, + wantedErr: "is in the far distant future", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ToTime(tt.args.genesisTimeSec, tt.args.slot) + if tt.wantedErr != "" { + assert.ErrorContains(t, tt.wantedErr, err) + } else { + assert.NoError(t, err) + assert.DeepEqual(t, tt.want, got) + } + }) + } +} + +func TestVerifySlotTime(t *testing.T) { + type args struct { + genesisTime int64 + slot types.Slot + timeTolerance time.Duration + } + tests := []struct { + name string + args args + wantedErr string + }{ + { + name: "Past slot", + args: args{ + genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), + slot: 3, + }, + }, + { + name: "within tolerance", + args: args{ + genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Add(20 * time.Millisecond).Unix(), + slot: 5, + }, + }, + { + name: "future slot", + args: args{ + genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), + slot: 6, + }, + wantedErr: "could not process slot from the future", + }, + { + name: "max future slot", + args: args{ + genesisTime: prysmTime.Now().Add(-1 * 5 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), + slot: types.Slot(MaxSlotBuffer + 6), + }, + wantedErr: "exceeds max allowed value relative to the local clock", + }, + { + name: "evil future slot", + args: args{ + genesisTime: prysmTime.Now().Add(-1 * 24 * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix(), // 24 slots in the past + // Gets multiplied with slot duration, and results in an overflow. Wraps around to a valid time. + // Lower than max signed int. And chosen specifically to wrap to a valid slot 24 + slot: types.Slot((^uint64(0))/params.BeaconConfig().SecondsPerSlot) + 24, + }, + wantedErr: "is in the far distant future", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := VerifyTime(uint64(tt.args.genesisTime), tt.args.slot, tt.args.timeTolerance) + if tt.wantedErr != "" { + assert.ErrorContains(t, tt.wantedErr, err) + } else { + assert.NoError(t, err) + } + }) + } +} + +func TestValidateSlotClock_HandlesBadSlot(t *testing.T) { + genTime := prysmTime.Now().Add(-1 * time.Duration(MaxSlotBuffer) * time.Duration(params.BeaconConfig().SecondsPerSlot) * time.Second).Unix() + + assert.NoError(t, ValidateClock(types.Slot(MaxSlotBuffer), uint64(genTime)), "unexpected error validating slot") + assert.NoError(t, ValidateClock(types.Slot(2*MaxSlotBuffer), uint64(genTime)), "unexpected error validating slot") + assert.ErrorContains(t, "which exceeds max allowed value relative to the local clock", ValidateClock(types.Slot(2*MaxSlotBuffer+1), uint64(genTime)), "no error from bad slot") + assert.ErrorContains(t, "which exceeds max allowed value relative to the local clock", ValidateClock(1<<63, uint64(genTime)), "no error from bad slot") +} + +func TestPrevSlot(t *testing.T) { + tests := []struct { + name string + slot types.Slot + want types.Slot + }{ + { + name: "no underflow", + slot: 0, + want: 0, + }, + { + name: "slot 1", + slot: 1, + want: 0, + }, + { + name: "slot 2", + slot: 2, + want: 1, + }, + { + name: "max", + slot: 1<<64 - 1, + want: 1<<64 - 1 - 1, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := PrevSlot(tt.slot); got != tt.want { + t.Errorf("PrevSlot() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestSyncCommitteePeriod(t *testing.T) { + tests := []struct { + epoch types.Epoch + wanted uint64 + }{ + {epoch: 0, wanted: 0}, + {epoch: 0, wanted: 0 / uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)}, + {epoch: 1, wanted: 1 / uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)}, + {epoch: 1000, wanted: 1000 / uint64(params.BeaconConfig().EpochsPerSyncCommitteePeriod)}, + } + for _, test := range tests { + require.Equal(t, test.wanted, SyncCommitteePeriod(test.epoch)) + } +} + +func TestSyncCommitteePeriodStartEpoch(t *testing.T) { + tests := []struct { + epoch types.Epoch + wanted types.Epoch + }{ + {epoch: 0, wanted: 0}, + {epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod + 1, wanted: params.BeaconConfig().EpochsPerSyncCommitteePeriod}, + {epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod*2 + 100, wanted: params.BeaconConfig().EpochsPerSyncCommitteePeriod * 2}, + {epoch: params.BeaconConfig().EpochsPerSyncCommitteePeriod*params.BeaconConfig().EpochsPerSyncCommitteePeriod + 1, wanted: params.BeaconConfig().EpochsPerSyncCommitteePeriod * params.BeaconConfig().EpochsPerSyncCommitteePeriod}, + } + for _, test := range tests { + e, err := SyncCommitteePeriodStartEpoch(test.epoch) + require.NoError(t, err) + require.Equal(t, test.wanted, e) + } +}