changing minimal run to current-1 to current with a shorter total epoch run and moving full post merge run to post submit

This commit is contained in:
james-prysm
2025-12-16 22:55:10 -06:00
parent 9706eadea9
commit 9f2f70a231
8 changed files with 195 additions and 62 deletions

View File

@@ -5,6 +5,7 @@ load("@prysm//tools/go:def.bzl", "go_test")
# gazelle:exclude mainnet_scenario_e2e_test.go
# gazelle:exclude minimal_scenario_e2e_test.go
# gazelle:exclude minimal_builder_e2e_test.go
# gazelle:exclude minimal_postmerge_e2e_test.go
# Presubmit tests represent the group of endtoend tests that are run on pull
# requests and must be passing before a pull request can merge.
@@ -27,6 +28,7 @@ test_suite(
],
tests = [
":go_builder_test",
":go_minimal_postmerge_test",
":go_mainnet_test",
],
)
@@ -153,6 +155,39 @@ go_test(
deps = common_deps,
)
# Full e2e test from post-merge (Bellatrix) through all forks (post-submit only, takes longer)
go_test(
name = "go_minimal_postmerge_test",
size = "enormous",
testonly = True,
srcs = [
"component_handler_test.go",
"endtoend_setup_test.go",
"endtoend_test.go",
"minimal_postmerge_e2e_test.go",
],
args = ["-test.v"],
data = [
"//:prysm_sh",
"//cmd/beacon-chain",
"//cmd/validator",
"//config/params:custom_configs",
"//tools/bootnode",
"@com_github_ethereum_go_ethereum//cmd/geth",
"@web3signer",
],
eth_network = "minimal",
flaky = True,
shard_count = 2,
tags = [
"e2e",
"manual",
"minimal",
"requires-network",
],
deps = common_deps,
)
go_test(
name = "go_mainnet_test",
size = "large",

View File

@@ -35,6 +35,10 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
}
tracingPort := e2eParams.TestParams.Ports.JaegerTracingPort
tracingEndpoint := fmt.Sprintf("127.0.0.1:%d", tracingPort)
// Default exit epoch used for voluntary exit tests.
// Can be overridden via WithExitEpoch option for shorter test runs.
exitEpoch := primitives.Epoch(7)
evals := []types.Evaluator{
ev.PeersConnect,
ev.HealthzCheck,
@@ -44,10 +48,7 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
ev.FinalizationOccurs(3),
ev.VerifyBlockGraffiti,
ev.PeersCheck,
ev.ProposeVoluntaryExit,
ev.ValidatorsHaveExited,
ev.SubmitWithdrawal,
ev.ValidatorsHaveWithdrawn,
// Exit-related evaluators are added after processing options to allow custom exit epoch.
ev.ProcessesDepositsInBlocks,
ev.ActivatesDepositedValidators,
ev.DepositedValidatorsAreActive,
@@ -88,6 +89,18 @@ func e2eMinimal(t *testing.T, cfg *params.BeaconChainConfig, cfgo ...types.E2ECo
for _, o := range cfgo {
o(testConfig)
}
// Add exit-related evaluators using custom exit epoch if configured, otherwise use default.
if testConfig.ExitEpoch > 0 {
exitEpoch = testConfig.ExitEpoch
}
testConfig.Evaluators = append(testConfig.Evaluators,
ev.ProposeVoluntaryExitAtEpoch(exitEpoch),
ev.ValidatorsHaveExitedAtEpoch(exitEpoch+1),
ev.SubmitWithdrawalAtEpoch(exitEpoch+1),
ev.ValidatorsHaveWithdrawnAfterExitAtEpoch(exitEpoch),
)
if testConfig.UseBuilder {
testConfig.Evaluators = append(testConfig.Evaluators, ev.BuilderIsActive)
}

View File

@@ -40,7 +40,9 @@ var depositsInBlockStart = params.E2ETestConfig().EpochsPerEth1VotingPeriod * 2
// deposits included + finalization + MaxSeedLookahead for activation.
var depositActivationStartEpoch = depositsInBlockStart + 2 + params.E2ETestConfig().MaxSeedLookahead
var depositEndEpoch = depositActivationStartEpoch + primitives.Epoch(math.Ceil(float64(depositValCount)/float64(params.E2ETestConfig().MinPerEpochChurnLimit)))
var exitSubmissionEpoch = primitives.Epoch(7)
// Default exit submission epoch for standard tests.
var defaultExitSubmissionEpoch = 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.
@@ -92,61 +94,87 @@ var DepositedValidatorsAreActive = e2etypes.Evaluator{
}
// ProposeVoluntaryExit sends a voluntary exit from randomly selected validator in the genesis set.
var ProposeVoluntaryExit = e2etypes.Evaluator{
Name: "propose_voluntary_exit_epoch_%d",
Policy: policies.OnEpoch(exitSubmissionEpoch),
Evaluation: proposeVoluntaryExit,
// Uses the default exit submission epoch (7).
var ProposeVoluntaryExit = ProposeVoluntaryExitAtEpoch(defaultExitSubmissionEpoch)
// ProposeVoluntaryExitAtEpoch sends a voluntary exit at the specified epoch.
var ProposeVoluntaryExitAtEpoch = func(epoch primitives.Epoch) e2etypes.Evaluator {
return e2etypes.Evaluator{
Name: "propose_voluntary_exit_epoch_%d",
Policy: policies.OnEpoch(epoch),
Evaluation: proposeVoluntaryExit,
}
}
// ValidatorsHaveExited checks the beacon state for the exited validator and ensures its marked as exited.
var ValidatorsHaveExited = e2etypes.Evaluator{
Name: "voluntary_has_exited_%d",
Policy: policies.OnEpoch(8),
Evaluation: validatorsHaveExited,
// Uses the default exit submission epoch + 1 (epoch 8).
var ValidatorsHaveExited = ValidatorsHaveExitedAtEpoch(defaultExitSubmissionEpoch + 1)
// ValidatorsHaveExitedAtEpoch checks validators have exited at the specified epoch.
var ValidatorsHaveExitedAtEpoch = func(epoch primitives.Epoch) e2etypes.Evaluator {
return e2etypes.Evaluator{
Name: "voluntary_has_exited_%d",
Policy: policies.OnEpoch(epoch),
Evaluation: validatorsHaveExited,
}
}
// SubmitWithdrawal sends a withdrawal from a previously exited validator.
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,
// Uses default timing based on exit submission epoch.
var SubmitWithdrawal = SubmitWithdrawalAtEpoch(defaultExitSubmissionEpoch + 1)
// SubmitWithdrawalAtEpoch sends a withdrawal at the specified epoch.
// For pre-Deneb genesis, it runs around the Capella fork epoch instead.
var SubmitWithdrawalAtEpoch = func(epoch primitives.Epoch) e2etypes.Evaluator {
return 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 at the specified epoch
if e2etypes.GenesisFork() >= version.Deneb {
return policies.OnEpoch(epoch)(currentEpoch)
}
return policies.BetweenEpochs(fEpoch-2, fEpoch+1)(currentEpoch)
},
Evaluation: submitWithdrawal,
}
}
// ValidatorsHaveWithdrawn checks the beacon state for the withdrawn validator and ensures it has been withdrawn.
var ValidatorsHaveWithdrawn = e2etypes.Evaluator{
Name: "validator_has_withdrawn_%d",
Policy: func(currentEpoch primitives.Epoch) bool {
// TODO: Fix this for mainnet configs.
if params.BeaconConfig().ConfigName != params.EndToEndName {
return false
}
// Only run this for minimal setups after capella
fEpoch := params.BeaconConfig().CapellaForkEpoch
// If Capella is disabled (starting at Deneb+), run after withdrawal submission
var validWithdrawnEpoch primitives.Epoch
if e2etypes.GenesisFork() >= version.Deneb {
// Exit submitted at exitSubmissionEpoch (7)
// Exit epoch = exitSubmissionEpoch + 1 + MAX_SEED_LOOKAHEAD
// Withdrawable epoch = exit epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
// Add 1 more epoch for the sweep to process
exitEpoch := exitSubmissionEpoch + 1 + primitives.Epoch(params.BeaconConfig().MaxSeedLookahead)
withdrawableEpoch := exitEpoch + primitives.Epoch(params.BeaconConfig().MinValidatorWithdrawabilityDelay)
validWithdrawnEpoch = withdrawableEpoch + 1
} else {
validWithdrawnEpoch = fEpoch + 1
}
// Uses default timing based on exit submission epoch.
var ValidatorsHaveWithdrawn = ValidatorsHaveWithdrawnAfterExitAtEpoch(defaultExitSubmissionEpoch)
requiredPolicy := policies.OnEpoch(validWithdrawnEpoch)
return requiredPolicy(currentEpoch)
},
Evaluation: validatorsAreWithdrawn,
// ValidatorsHaveWithdrawnAfterExitAtEpoch checks validators have withdrawn after exiting at the specified epoch.
// For pre-Deneb genesis, it runs at CapellaForkEpoch + 1 instead.
var ValidatorsHaveWithdrawnAfterExitAtEpoch = func(exitSubmitEpoch primitives.Epoch) e2etypes.Evaluator {
return e2etypes.Evaluator{
Name: "validator_has_withdrawn_%d",
Policy: func(currentEpoch primitives.Epoch) bool {
// TODO: Fix this for mainnet configs.
if params.BeaconConfig().ConfigName != params.EndToEndName {
return false
}
// Only run this for minimal setups after capella
fEpoch := params.BeaconConfig().CapellaForkEpoch
// If Capella is disabled (starting at Deneb+), run after withdrawal submission
var validWithdrawnEpoch primitives.Epoch
if e2etypes.GenesisFork() >= version.Deneb {
// Exit submitted at exitSubmitEpoch
// Exit epoch = exitSubmitEpoch + 1 + MAX_SEED_LOOKAHEAD
// Withdrawable epoch = exit epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
// Add 1 more epoch for the sweep to process
exitEpoch := exitSubmitEpoch + 1 + primitives.Epoch(params.BeaconConfig().MaxSeedLookahead)
withdrawableEpoch := exitEpoch + primitives.Epoch(params.BeaconConfig().MinValidatorWithdrawabilityDelay)
validWithdrawnEpoch = withdrawableEpoch + 1
} else {
validWithdrawnEpoch = fEpoch + 1
}
requiredPolicy := policies.OnEpoch(validWithdrawnEpoch)
return requiredPolicy(currentEpoch)
},
Evaluation: validatorsAreWithdrawn,
}
}
// ValidatorsVoteWithTheMajority verifies whether validator vote for eth1data using the majority algorithm.
@@ -357,8 +385,7 @@ func depositedValidatorsAreActive(ec *e2etypes.EvaluationContext, conns ...*grpc
continue // we aren't checking for this validator
}
// ignore voluntary exits when checking balance and active status
exited := ec.ExitedVals[key]
if exited {
if _, exited := ec.ExitedVals[key]; exited {
nexits++
delete(expected, key)
continue
@@ -463,7 +490,7 @@ func proposeVoluntaryExit(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientC
return errors.Wrap(err, "could not propose exit")
}
pubk := bytesutil.ToBytes48(deposits[exitedIndex].Data.PublicKey)
ec.ExitedVals[pubk] = true
ec.ExitedVals[pubk] = chainHead.HeadEpoch // Store submission epoch
return nil
}
@@ -477,7 +504,7 @@ func proposeVoluntaryExit(ec *e2etypes.EvaluationContext, conns ...*grpc.ClientC
// Send an exit for a non-exited validator.
for i := 0; i < numOfExits; {
randIndex := primitives.ValidatorIndex(rand.Uint64() % params.BeaconConfig().MinGenesisActiveValidatorCount)
if ec.ExitedVals[bytesutil.ToBytes48(privKeys[randIndex].PublicKey().Marshal())] {
if _, alreadyExited := ec.ExitedVals[bytesutil.ToBytes48(privKeys[randIndex].PublicKey().Marshal())]; alreadyExited {
continue
}
if err := sendExit(randIndex); err != nil {

View File

@@ -62,6 +62,7 @@ var ValidatorSyncParticipation = types.Evaluator{
func validatorsAreActive(ec *types.EvaluationContext, conns ...*grpc.ClientConn) error {
conn := conns[0]
client := ethpb.NewBeaconChainClient(conn)
// Balances actually fluctuate but we just want to check initial balance.
validatorRequest := &ethpb.ListValidatorsRequest{
PageSize: int32(params.BeaconConfig().MinGenesisActiveValidatorCount),
@@ -72,17 +73,26 @@ func validatorsAreActive(ec *types.EvaluationContext, conns ...*grpc.ClientConn)
return errors.Wrap(err, "failed to get validators")
}
expectedCount := params.BeaconConfig().MinGenesisActiveValidatorCount
// Count should be MinGenesisActiveValidatorCount minus any validators that have exited.
// We determine actual exited count from the difference, as exits may be submitted but
// not yet processed, or affected by churn limits.
receivedCount := uint64(len(validators.ValidatorList))
if expectedCount != receivedCount {
return fmt.Errorf("expected validator count to be %d, received %d", expectedCount, receivedCount)
maxExpected := params.BeaconConfig().MinGenesisActiveValidatorCount
minExpected := maxExpected - uint64(len(ec.ExitedVals))
if receivedCount > maxExpected {
return fmt.Errorf("validator count %d exceeds genesis count %d", receivedCount, maxExpected)
}
if receivedCount < minExpected {
return fmt.Errorf("validator count %d is less than expected minimum %d (genesis %d - %d submitted exits)",
receivedCount, minExpected, maxExpected, len(ec.ExitedVals))
}
effBalanceLowCount := 0
exitEpochWrongCount := 0
withdrawEpochWrongCount := 0
for _, item := range validators.ValidatorList {
if ec.ExitedVals[bytesutil.ToBytes48(item.Validator.PublicKey)] {
if _, exited := ec.ExitedVals[bytesutil.ToBytes48(item.Validator.PublicKey)]; exited {
continue
}
if item.Validator.EffectiveBalance < params.BeaconConfig().MaxEffectiveBalance {

View File

@@ -10,7 +10,7 @@ import (
// Run mainnet e2e config with the current release validator against latest beacon node.
func TestEndToEnd_MainnetConfig_ValidatorAtCurrentRelease(t *testing.T) {
r := e2eMainnet(t, true, false, types.InitForkCfg(version.Bellatrix, version.Electra, params.E2EMainnetTestConfig()))
r := e2eMainnet(t, true, false, types.InitForkCfg(version.Bellatrix, version.Fulu, params.E2EMainnetTestConfig()))
r.run()
}

View File

@@ -8,7 +8,27 @@ import (
"github.com/OffchainLabs/prysm/v7/testing/endtoend/types"
)
// TestEndToEnd_MinimalConfig is the pre-submit e2e test from Electra to Fulu
// with compressed epochs. It runs 10 epochs with exit at epoch 4 (the earliest
// possible due to ShardCommitteePeriod=4), allowing all evaluators to complete:
// - Participation at epoch 2
// - Finalization at epoch 3
// - Fulu fork transition at epoch 2
// - Exit proposed at epoch 4
// - Exit confirmed at epoch 5
// - Withdrawal submitted at epoch 5
// - Withdrawal verified at epoch 8 (exit epoch 4 + 1 + MaxSeedLookahead + MinValidatorWithdrawabilityDelay + 1)
func TestEndToEnd_MinimalConfig(t *testing.T) {
r := e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Fulu, params.E2ETestConfig()), types.WithCheckpointSync())
cfg := params.E2ETestConfig()
cfg = types.InitForkCfg(version.Electra, version.Fulu, cfg)
// Set Fulu fork at epoch 2 for a quick fork transition test
cfg.FuluForkEpoch = 2
cfg.InitializeForkSchedule()
r := e2eMinimal(t, cfg,
types.WithCheckpointSync(),
types.WithEpochs(10),
types.WithExitEpoch(4), // Minimum due to ShardCommitteePeriod=4
)
r.run()
}

View File

@@ -0,0 +1,17 @@
package endtoend
import (
"testing"
"github.com/OffchainLabs/prysm/v7/config/params"
"github.com/OffchainLabs/prysm/v7/runtime/version"
"github.com/OffchainLabs/prysm/v7/testing/endtoend/types"
)
// TestEndToEnd_MinimalConfig_PostMerge is a post-submit test that runs the full
// e2e test from Bellatrix (post-merge) through all fork transitions to the latest fork.
// This test takes longer but provides comprehensive coverage of all forks.
func TestEndToEnd_MinimalConfig_PostMerge(t *testing.T) {
r := e2eMinimal(t, types.InitForkCfg(version.Bellatrix, version.Fulu, params.E2ETestConfig()), types.WithCheckpointSync())
r.run()
}

View File

@@ -67,6 +67,14 @@ func WithSSZOnly() E2EConfigOpt {
}
}
// WithExitEpoch sets a custom epoch for voluntary exit submission.
// This affects ProposeVoluntaryExit, ValidatorsHaveExited, SubmitWithdrawal, and ValidatorsHaveWithdrawn evaluators.
func WithExitEpoch(e primitives.Epoch) E2EConfigOpt {
return func(cfg *E2EConfig) {
cfg.ExitEpoch = e
}
}
// E2EConfig defines the struct for all configurations needed for E2E testing.
type E2EConfig struct {
TestCheckpointSync bool
@@ -82,6 +90,7 @@ type E2EConfig struct {
UseBeaconRestApi bool
UseBuilder bool
EpochsToRun uint64
ExitEpoch primitives.Epoch // Custom epoch for voluntary exit submission (0 means use default)
Seed int64
TracingSinkEndpoint string
Evaluators []Evaluator
@@ -149,7 +158,9 @@ type DepositBalancer interface {
// EvaluationContext allows for additional data to be provided to evaluators that need extra state.
type EvaluationContext struct {
DepositBalancer
ExitedVals map[[48]byte]bool
// ExitedVals maps validator pubkey to the epoch when their exit was submitted.
// The actual exit takes effect at: submission_epoch + 1 + MaxSeedLookahead
ExitedVals map[[48]byte]primitives.Epoch
SeenVotes map[primitives.Slot][]byte
ExpectedEth1DataVote []byte
}
@@ -158,7 +169,7 @@ type EvaluationContext struct {
func NewEvaluationContext(d DepositBalancer) *EvaluationContext {
return &EvaluationContext{
DepositBalancer: d,
ExitedVals: make(map[[48]byte]bool),
ExitedVals: make(map[[48]byte]primitives.Epoch),
SeenVotes: make(map[primitives.Slot][]byte),
}
}