From f97622b054d1d868792deb5585ddf16264c5ff88 Mon Sep 17 00:00:00 2001 From: james-prysm <90280386+james-prysm@users.noreply.github.com> Date: Wed, 26 Nov 2025 08:33:24 -0800 Subject: [PATCH] e2e support electra forkstart (#16048) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit **What type of PR is this?** Bug fix **What does this PR do? Why is it needed?** Allows for starting e2e tests from electra or a specific fork of interest again. doesn't fix missing execution requests tests, nishant reverted it. **Which issues(s) does this PR fix?** Fixes # **Other notes for review** **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: RadosÅ‚aw Kapka --- changelog/james-prysm_fix-fork-start-e2e.md | 3 ++ config/params/testnet_e2e_config.go | 8 +-- runtime/interop/premine-state.go | 8 ++- testing/endtoend/evaluators/fork.go | 49 ++++++++++++------- testing/endtoend/evaluators/operations.go | 47 +++++++++++++++--- testing/endtoend/fork.go | 1 - testing/endtoend/minimal_e2e_test.go | 2 +- testing/endtoend/minimal_scenario_e2e_test.go | 5 ++ testing/endtoend/types/fork.go | 27 ++++++++++ testing/endtoend/types/types.go | 7 +++ 10 files changed, 125 insertions(+), 32 deletions(-) create mode 100644 changelog/james-prysm_fix-fork-start-e2e.md delete mode 100644 testing/endtoend/fork.go diff --git a/changelog/james-prysm_fix-fork-start-e2e.md b/changelog/james-prysm_fix-fork-start-e2e.md new file mode 100644 index 0000000000..4d4eb6d8bd --- /dev/null +++ b/changelog/james-prysm_fix-fork-start-e2e.md @@ -0,0 +1,3 @@ +### Fixed + +- fixes E2E tests to be able to start from Electra genesis fork or future forks \ No newline at end of file diff --git a/config/params/testnet_e2e_config.go b/config/params/testnet_e2e_config.go index ad5ce74a76..da56197679 100644 --- a/config/params/testnet_e2e_config.go +++ b/config/params/testnet_e2e_config.go @@ -62,8 +62,8 @@ func E2ETestConfig() *BeaconChainConfig { e2eConfig.FuluForkVersion = []byte{6, 0, 0, 253} e2eConfig.BlobSchedule = []BlobScheduleEntry{ - {Epoch: 12, MaxBlobsPerBlock: 6}, - {Epoch: 14, MaxBlobsPerBlock: 9}, + {Epoch: e2eConfig.DenebForkEpoch, MaxBlobsPerBlock: uint64(e2eConfig.DeprecatedMaxBlobsPerBlock)}, + {Epoch: e2eConfig.ElectraForkEpoch, MaxBlobsPerBlock: uint64(e2eConfig.DeprecatedMaxBlobsPerBlockElectra)}, } e2eConfig.InitializeForkSchedule() @@ -117,8 +117,8 @@ func E2EMainnetTestConfig() *BeaconChainConfig { e2eConfig.MinPerEpochChurnLimit = 2 e2eConfig.BlobSchedule = []BlobScheduleEntry{ - {Epoch: 12, MaxBlobsPerBlock: 6}, - {Epoch: 14, MaxBlobsPerBlock: 9}, + {Epoch: e2eConfig.DenebForkEpoch, MaxBlobsPerBlock: uint64(e2eConfig.DeprecatedMaxBlobsPerBlock)}, + {Epoch: e2eConfig.ElectraForkEpoch, MaxBlobsPerBlock: uint64(e2eConfig.DeprecatedMaxBlobsPerBlockElectra)}, } e2eConfig.InitializeForkSchedule() diff --git a/runtime/interop/premine-state.go b/runtime/interop/premine-state.go index 292ef1a907..a97648844e 100644 --- a/runtime/interop/premine-state.go +++ b/runtime/interop/premine-state.go @@ -156,12 +156,16 @@ func (s *PremineGenesisConfig) empty() (state.BeaconState, error) { return nil, err } case version.Electra: - e, err = state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{}) + e, err = state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{ + DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex, + }) if err != nil { return nil, err } case version.Fulu: - e, err = state_native.InitializeFromProtoFulu(ðpb.BeaconStateFulu{}) + e, err = state_native.InitializeFromProtoFulu(ðpb.BeaconStateFulu{ + DepositRequestsStartIndex: params.BeaconConfig().UnsetDepositRequestsStartIndex, + }) if err != nil { return nil, err } diff --git a/testing/endtoend/evaluators/fork.go b/testing/endtoend/evaluators/fork.go index da167c86d6..00f8ab46fe 100644 --- a/testing/endtoend/evaluators/fork.go +++ b/testing/endtoend/evaluators/fork.go @@ -10,33 +10,36 @@ import ( ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v7/runtime/version" "github.com/OffchainLabs/prysm/v7/testing/endtoend/policies" - "github.com/OffchainLabs/prysm/v7/testing/endtoend/types" + e2etypes "github.com/OffchainLabs/prysm/v7/testing/endtoend/types" "github.com/OffchainLabs/prysm/v7/time/slots" "github.com/pkg/errors" "google.golang.org/grpc" ) var streamDeadline = 1 * time.Minute -var startingFork = version.Phase0 // AltairForkTransition ensures that the Altair hard fork has occurred successfully. -var AltairForkTransition = types.Evaluator{ +var AltairForkTransition = e2etypes.Evaluator{ Name: "altair_fork_transition_%d", Policy: func(e primitives.Epoch) bool { - altair := policies.OnEpoch(params.BeaconConfig().AltairForkEpoch) - // TODO (11750): modify policies to take an end to end config - if startingFork == version.Phase0 { - return altair(e) + // Only run if we started before Altair + if e2etypes.GenesisFork() >= version.Altair { + return false } - return false + altair := policies.OnEpoch(params.BeaconConfig().AltairForkEpoch) + return altair(e) }, Evaluation: altairForkOccurs, } // BellatrixForkTransition ensures that the Bellatrix hard fork has occurred successfully. -var BellatrixForkTransition = types.Evaluator{ +var BellatrixForkTransition = e2etypes.Evaluator{ Name: "bellatrix_fork_transition_%d", Policy: func(e primitives.Epoch) bool { + // Only run if we started before Bellatrix + if e2etypes.GenesisFork() >= version.Bellatrix { + return false + } fEpoch := params.BeaconConfig().BellatrixForkEpoch return policies.OnEpoch(fEpoch)(e) }, @@ -44,9 +47,13 @@ var BellatrixForkTransition = types.Evaluator{ } // CapellaForkTransition ensures that the Capella hard fork has occurred successfully. -var CapellaForkTransition = types.Evaluator{ +var CapellaForkTransition = e2etypes.Evaluator{ Name: "capella_fork_transition_%d", Policy: func(e primitives.Epoch) bool { + // Only run if we started before Capella + if e2etypes.GenesisFork() >= version.Capella { + return false + } fEpoch := params.BeaconConfig().CapellaForkEpoch return policies.OnEpoch(fEpoch)(e) }, @@ -54,9 +61,13 @@ var CapellaForkTransition = types.Evaluator{ } // DenebForkTransition ensures that the Deneb hard fork has occurred successfully -var DenebForkTransition = types.Evaluator{ +var DenebForkTransition = e2etypes.Evaluator{ Name: "deneb_fork_transition_%d", Policy: func(e primitives.Epoch) bool { + // Only run if we started before Deneb + if e2etypes.GenesisFork() >= version.Deneb { + return false + } fEpoch := params.BeaconConfig().DenebForkEpoch return policies.OnEpoch(fEpoch)(e) }, @@ -64,16 +75,20 @@ var DenebForkTransition = types.Evaluator{ } // ElectraForkTransition ensures that the electra hard fork has occurred successfully -var ElectraForkTransition = types.Evaluator{ +var ElectraForkTransition = e2etypes.Evaluator{ Name: "electra_fork_transition_%d", Policy: func(e primitives.Epoch) bool { + // Only run if we started before Electra + if e2etypes.GenesisFork() >= version.Electra { + return false + } fEpoch := params.BeaconConfig().ElectraForkEpoch return policies.OnEpoch(fEpoch)(e) }, Evaluation: electraForkOccurs, } -func altairForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error { +func altairForkOccurs(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error { conn := conns[0] client := ethpb.NewBeaconNodeValidatorClient(conn) ctx, cancel := context.WithTimeout(context.Background(), streamDeadline) @@ -115,7 +130,7 @@ func altairForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) err return nil } -func bellatrixForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error { +func bellatrixForkOccurs(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error { conn := conns[0] client := ethpb.NewBeaconNodeValidatorClient(conn) ctx, cancel := context.WithTimeout(context.Background(), streamDeadline) @@ -160,7 +175,7 @@ func bellatrixForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) return nil } -func capellaForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error { +func capellaForkOccurs(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error { conn := conns[0] client := ethpb.NewBeaconNodeValidatorClient(conn) ctx, cancel := context.WithTimeout(context.Background(), streamDeadline) @@ -203,7 +218,7 @@ func capellaForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) er return nil } -func denebForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error { +func denebForkOccurs(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error { conn := conns[0] client := ethpb.NewBeaconNodeValidatorClient(conn) ctx, cancel := context.WithTimeout(context.Background(), streamDeadline) @@ -246,7 +261,7 @@ func denebForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) erro return nil } -func electraForkOccurs(_ *types.EvaluationContext, conns ...*grpc.ClientConn) error { +func electraForkOccurs(_ *e2etypes.EvaluationContext, conns ...*grpc.ClientConn) error { conn := conns[0] client := ethpb.NewBeaconNodeValidatorClient(conn) ctx, cancel := context.WithTimeout(context.Background(), streamDeadline) diff --git a/testing/endtoend/evaluators/operations.go b/testing/endtoend/evaluators/operations.go index deb9a0570c..4372964673 100644 --- a/testing/endtoend/evaluators/operations.go +++ b/testing/endtoend/evaluators/operations.go @@ -18,6 +18,7 @@ import ( "github.com/OffchainLabs/prysm/v7/encoding/bytesutil" "github.com/OffchainLabs/prysm/v7/encoding/ssz/detect" ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" + "github.com/OffchainLabs/prysm/v7/runtime/version" "github.com/OffchainLabs/prysm/v7/testing/endtoend/helpers" e2e "github.com/OffchainLabs/prysm/v7/testing/endtoend/params" "github.com/OffchainLabs/prysm/v7/testing/endtoend/policies" @@ -42,9 +43,16 @@ var depositEndEpoch = depositActivationStartEpoch + primitives.Epoch(math.Ceil(f var exitSubmissionEpoch = primitives.Epoch(7) // ProcessesDepositsInBlocks ensures the expected amount of deposits are accepted into blocks. +// Note: This evaluator only works for pre-Electra genesis since Electra uses EIP-6110 deposit requests. var ProcessesDepositsInBlocks = e2etypes.Evaluator{ - Name: "processes_deposits_in_blocks_epoch_%d", - Policy: policies.OnEpoch(depositsInBlockStart), // We expect all deposits to enter in one epoch. + Name: "processes_deposits_in_blocks_epoch_%d", + Policy: func(e primitives.Epoch) bool { + // Skip if starting at Electra or later - deposits work differently with EIP-6110 + if e2etypes.GenesisFork() >= version.Electra { + return false + } + return policies.OnEpoch(depositsInBlockStart)(e) + }, Evaluation: processesDepositsInBlocks, } @@ -56,16 +64,30 @@ var VerifyBlockGraffiti = e2etypes.Evaluator{ } // ActivatesDepositedValidators ensures the expected amount of validator deposits are activated into the state. +// Note: This evaluator only works for pre-Electra genesis since Electra uses EIP-6110 deposit requests. var ActivatesDepositedValidators = e2etypes.Evaluator{ - Name: "processes_deposit_validators_epoch_%d", - Policy: policies.BetweenEpochs(depositActivationStartEpoch, depositEndEpoch), + Name: "processes_deposit_validators_epoch_%d", + Policy: func(e primitives.Epoch) bool { + // Skip if starting at Electra or later - deposits work differently with EIP-6110 + if e2etypes.GenesisFork() >= version.Electra { + return false + } + return policies.BetweenEpochs(depositActivationStartEpoch, depositEndEpoch)(e) + }, Evaluation: activatesDepositedValidators, } // DepositedValidatorsAreActive ensures the expected amount of validators are active after their deposits are processed. +// Note: This evaluator only works for pre-Electra genesis since Electra uses EIP-6110 deposit requests. var DepositedValidatorsAreActive = e2etypes.Evaluator{ - Name: "deposited_validators_are_active_epoch_%d", - Policy: policies.AfterNthEpoch(depositEndEpoch), + Name: "deposited_validators_are_active_epoch_%d", + Policy: func(e primitives.Epoch) bool { + // Skip if starting at Electra or later - deposits work differently with EIP-6110 + if e2etypes.GenesisFork() >= version.Electra { + return false + } + return policies.AfterNthEpoch(depositEndEpoch)(e) + }, Evaluation: depositedValidatorsAreActive, } @@ -88,6 +110,10 @@ var SubmitWithdrawal = e2etypes.Evaluator{ Name: "submit_withdrawal_epoch_%d", Policy: func(currentEpoch primitives.Epoch) bool { fEpoch := params.BeaconConfig().CapellaForkEpoch + // If Capella is disabled (starting at Deneb+), run after exit submission + if e2etypes.GenesisFork() >= version.Deneb { + return policies.OnEpoch(exitSubmissionEpoch + 1)(currentEpoch) + } return policies.BetweenEpochs(fEpoch-2, fEpoch+1)(currentEpoch) }, Evaluation: submitWithdrawal, @@ -102,7 +128,14 @@ var ValidatorsHaveWithdrawn = e2etypes.Evaluator{ return false } // Only run this for minimal setups after capella - validWithdrawnEpoch := params.BeaconConfig().CapellaForkEpoch + 1 + fEpoch := params.BeaconConfig().CapellaForkEpoch + // If Capella is disabled (starting at Deneb+), run after withdrawal submission + var validWithdrawnEpoch primitives.Epoch + if e2etypes.GenesisFork() >= version.Deneb { + validWithdrawnEpoch = exitSubmissionEpoch + 2 + } else { + validWithdrawnEpoch = fEpoch + 1 + } requiredPolicy := policies.OnEpoch(validWithdrawnEpoch) return requiredPolicy(currentEpoch) diff --git a/testing/endtoend/fork.go b/testing/endtoend/fork.go deleted file mode 100644 index 6ee3d8a562..0000000000 --- a/testing/endtoend/fork.go +++ /dev/null @@ -1 +0,0 @@ -package endtoend diff --git a/testing/endtoend/minimal_e2e_test.go b/testing/endtoend/minimal_e2e_test.go index f11f5970d2..9f4f588822 100644 --- a/testing/endtoend/minimal_e2e_test.go +++ b/testing/endtoend/minimal_e2e_test.go @@ -11,4 +11,4 @@ import ( func TestEndToEnd_MinimalConfig(t *testing.T) { r := e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Electra, params.E2ETestConfig()), types.WithCheckpointSync()) r.run() -} +} \ No newline at end of file diff --git a/testing/endtoend/minimal_scenario_e2e_test.go b/testing/endtoend/minimal_scenario_e2e_test.go index 7f6b4ec4c0..63f466a148 100644 --- a/testing/endtoend/minimal_scenario_e2e_test.go +++ b/testing/endtoend/minimal_scenario_e2e_test.go @@ -25,6 +25,11 @@ func TestEndToEnd_MinimalConfig_Web3Signer_PersistentKeys(t *testing.T) { e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Electra, params.E2ETestConfig()), types.WithRemoteSignerAndPersistentKeysFile()).run() } +func TestEndToEnd_MinimalConfig_CurrentFork(t *testing.T) { + r := e2eMinimal(t, types.InitForkCfg(version.Electra, version.Electra, params.E2ETestConfig()), types.WithCheckpointSync()) + r.run() +} + func TestEndToEnd_MinimalConfig_ValidatorRESTApi_SSZ(t *testing.T) { e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Electra, params.E2ETestConfig()), types.WithCheckpointSync(), types.WithValidatorRESTApi(), types.WithSSZOnly()).run() } diff --git a/testing/endtoend/types/fork.go b/testing/endtoend/types/fork.go index 56cbc3011b..5aa83d28ce 100644 --- a/testing/endtoend/types/fork.go +++ b/testing/endtoend/types/fork.go @@ -6,6 +6,7 @@ import ( "github.com/OffchainLabs/prysm/v7/config/params" "github.com/OffchainLabs/prysm/v7/runtime/version" + log "github.com/sirupsen/logrus" ) func InitForkCfg(start, end int, c *params.BeaconChainConfig) *params.BeaconChainConfig { @@ -13,6 +14,9 @@ func InitForkCfg(start, end int, c *params.BeaconChainConfig) *params.BeaconChai if end < start { panic("end fork is less than the start fork") // lint:nopanic -- test code. } + if start < version.Bellatrix { + log.Fatal("E2e tests require starting from Bellatrix or later (pre-merge forks are not supported)") // lint:nopanic -- test code. + } if start >= version.Altair { c.AltairForkEpoch = 0 } @@ -28,6 +32,13 @@ func InitForkCfg(start, end int, c *params.BeaconChainConfig) *params.BeaconChai if start >= version.Electra { c.ElectraForkEpoch = 0 } + if start >= version.Fulu { + c.FuluForkEpoch = 0 + } + + if end < version.Fulu { + c.FuluForkEpoch = math.MaxUint64 + } if end < version.Electra { c.ElectraForkEpoch = math.MaxUint64 } @@ -45,7 +56,23 @@ func InitForkCfg(start, end int, c *params.BeaconChainConfig) *params.BeaconChai } // Time TTD to line up roughly with the bellatrix fork epoch. // E2E sets EL block production rate equal to SecondsPerETH1Block to keep the math simple. + // the chain starts post-merge (AKA post bellatrix) so TTD should be 0. ttd := uint64(c.BellatrixForkEpoch) * uint64(c.SlotsPerEpoch) * c.SecondsPerSlot c.TerminalTotalDifficulty = fmt.Sprintf("%d", ttd) + + // Update blob schedule to use the modified fork epochs. + // Only include entries for forks that are enabled (not set to MaxUint64). + c.BlobSchedule = nil + if c.DenebForkEpoch != math.MaxUint64 { + c.BlobSchedule = append(c.BlobSchedule, params.BlobScheduleEntry{ + Epoch: c.DenebForkEpoch, MaxBlobsPerBlock: uint64(c.DeprecatedMaxBlobsPerBlock), + }) + } + if c.ElectraForkEpoch != math.MaxUint64 { + c.BlobSchedule = append(c.BlobSchedule, params.BlobScheduleEntry{ + Epoch: c.ElectraForkEpoch, MaxBlobsPerBlock: uint64(c.DeprecatedMaxBlobsPerBlockElectra), + }) + } + c.InitializeForkSchedule() return c } diff --git a/testing/endtoend/types/types.go b/testing/endtoend/types/types.go index d68494aeda..8de400bc58 100644 --- a/testing/endtoend/types/types.go +++ b/testing/endtoend/types/types.go @@ -94,6 +94,13 @@ type E2EConfig struct { func GenesisFork() int { cfg := params.BeaconConfig() + // Check from highest fork to lowest to find the genesis fork. + if cfg.ElectraForkEpoch == 0 { + return version.Electra + } + if cfg.DenebForkEpoch == 0 { + return version.Deneb + } if cfg.CapellaForkEpoch == 0 { return version.Capella }