From 11d3adc79adbd7c065c387f1060175cb758ce634 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 08:24:30 +1000 Subject: [PATCH 01/21] fix issue --- state-transition/core/state/statedb.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 7a81137932..202ddd01ae 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -145,6 +145,10 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // Iterate through indices to find the next validators to withdraw. for range bound { + // Cap the number of withdrawals to the maximum allowed per payload. + if uint64(len(withdrawals)) == maxWithdrawals { + break + } validator, err = s.ValidatorByIndex(validatorIndex) if err != nil { return nil, 0, err @@ -199,11 +203,6 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With withdrawalIndex++ } - // Cap the number of withdrawals to the maximum allowed per payload. - if uint64(len(withdrawals)) == maxWithdrawals { - break - } - // Increment the validator index to process the next validator. validatorIndex = (validatorIndex + 1) % totalValidators } From e14196795df63c98a0f05b5fa026bce2f6608600 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 09:20:55 +1000 Subject: [PATCH 02/21] add withdrawals_enabled --- chain/data.go | 2 ++ chain/helpers.go | 3 +++ chain/spec.go | 24 ++++++++++++++++++++++++ state-transition/core/state/statedb.go | 4 ++++ testing/files/spec.toml | 1 + testing/networks/80069/spec.toml | 1 + testing/networks/80094/spec.toml | 1 + 7 files changed, 36 insertions(+) diff --git a/chain/data.go b/chain/data.go index 6595bc8fad..d64b690f3d 100644 --- a/chain/data.go +++ b/chain/data.go @@ -95,6 +95,8 @@ type SpecData struct { Deneb1ForkTime uint64 `mapstructure:"deneb-one-fork-time"` // ElectraForkTime is the time at which the Electra fork is activated. ElectraForkTime uint64 `mapstructure:"electra-fork-time"` + // Electra1ForkTime is the time at which the Electra1 fork is activated. + Electra1ForkTime uint64 `mapstructure:"electra-one-fork-time"` // State list lengths // diff --git a/chain/helpers.go b/chain/helpers.go index 8925bd0815..7cbfe4d8fd 100644 --- a/chain/helpers.go +++ b/chain/helpers.go @@ -29,6 +29,9 @@ import ( // ActiveForkVersionForTimestamp returns the active fork version for a given timestamp. func (s spec) ActiveForkVersionForTimestamp(timestamp math.U64) common.Version { time := timestamp.Unwrap() + if time >= s.Electra1ForkTime() { + return version.Electra1() + } if time >= s.ElectraForkTime() { return version.Electra() } diff --git a/chain/spec.go b/chain/spec.go index a09485cd5d..280ca781cf 100644 --- a/chain/spec.go +++ b/chain/spec.go @@ -164,6 +164,10 @@ type WithdrawalsSpec interface { // which is set to MIN_VALIDATOR_WITHDRAWABILITY_DELAY epochs after its exit_epoch. // This is to allow some extra time for any slashable offences by the validator to be detected and reported. MinValidatorWithdrawabilityDelay() math.Epoch + + // WithdrawalsEnabled is a switch that can be used to freeze withdrawals in an emergency scenario. + // An exception is made for the EVM inflation withdrawal which is always active. + WithdrawalsEnabled(timestamp math.U64) bool } // Spec defines an interface for accessing chain-specific parameters. @@ -375,6 +379,11 @@ func (s spec) ElectraForkTime() uint64 { return s.Data.ElectraForkTime } +// Electra1ForkTime returns the epoch of the Electra fork. +func (s spec) Electra1ForkTime() uint64 { + return s.Data.Electra1ForkTime +} + // EpochsPerHistoricalVector returns the number of epochs per historical vector. func (s spec) EpochsPerHistoricalVector() uint64 { return s.Data.EpochsPerHistoricalVector @@ -469,3 +478,18 @@ func (s spec) EVMInflationPerBlock(timestamp math.U64) math.Gwei { panic(fmt.Sprintf("EVMInflationPerBlock not supported for this fork version: %d", fv)) } } + +// WithdrawalsEnabled is a switch that can be used to freeze withdrawals in an emergency scenario. +// An exception is made for the EVM inflation withdrawal which is always active. +func (s spec) WithdrawalsEnabled(_ math.U64) bool { + return true + /* + Example: + fv := s.ActiveForkVersionForTimestamp(timestamp) + switch fv { + case version.Electra1(): + return false + default: + return true + */ +} diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 202ddd01ae..666ec5ad48 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -116,6 +116,10 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // The first withdrawal is fixed to be the EVM inflation withdrawal. withdrawals = append(withdrawals, s.EVMInflationWithdrawal(timestamp)) + if !s.cs.WithdrawalsEnabled(timestamp) { + return withdrawals, processedPartialWithdrawals, nil + } + withdrawalIndex, err := s.GetNextWithdrawalIndex() if err != nil { return nil, 0, err diff --git a/testing/files/spec.toml b/testing/files/spec.toml index 9b392b8b1a..5ba0eaf039 100644 --- a/testing/files/spec.toml +++ b/testing/files/spec.toml @@ -35,6 +35,7 @@ target-seconds-per-eth1-block = 2 genesis-time = 0 deneb-one-fork-time = 0 electra-fork-time = 0 +electra-one-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/networks/80069/spec.toml b/testing/networks/80069/spec.toml index 89a7f58d32..37d5003c84 100644 --- a/testing/networks/80069/spec.toml +++ b/testing/networks/80069/spec.toml @@ -35,6 +35,7 @@ target-seconds-per-eth1-block = 2 genesis-time = 1_739_976_735 deneb-one-fork-time = 1_740_090_694 electra-fork-time = 1_746_633_600 +electra-one-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/networks/80094/spec.toml b/testing/networks/80094/spec.toml index 7830dac2e5..fe67106ffc 100644 --- a/testing/networks/80094/spec.toml +++ b/testing/networks/80094/spec.toml @@ -35,6 +35,7 @@ target-seconds-per-eth1-block = 2 genesis-time = 1_737_381_600 deneb-one-fork-time = 1_738_415_507 electra-fork-time = 9_999_999_999_999_999 +electra-one-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 From c416f127324a9519b012d6bf4a188caa9b8455d0 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 09:43:29 +1000 Subject: [PATCH 03/21] Added check --- state-transition/core/state/statedb.go | 6 ++++++ .../core/state_processor_staking.go | 20 +++++++++++-------- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 666ec5ad48..a776d76d92 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -116,6 +116,12 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // The first withdrawal is fixed to be the EVM inflation withdrawal. withdrawals = append(withdrawals, s.EVMInflationWithdrawal(timestamp)) + // If withdrawals are not enabled, return only the inflation withdrawal. + // Once withdrawals are re-enabled, all pending withdrawals will be processed. + // 1. Partial Withdrawal Requests will remain untouched and will be handled after re-enabling. + // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn. + // 3. Validators who have initiated an exit and have reached the withdrawable epoch will not be withdrawn. + if !s.cs.WithdrawalsEnabled(timestamp) { return withdrawals, processedPartialWithdrawals, nil } diff --git a/state-transition/core/state_processor_staking.go b/state-transition/core/state_processor_staking.go index 41cc4437d8..7c12af680d 100644 --- a/state-transition/core/state_processor_staking.go +++ b/state-transition/core/state_processor_staking.go @@ -67,15 +67,19 @@ func (sp *StateProcessor) processOperations( } } + // After Electra, validators can request withdrawals through execution requests which must be handled. if version.EqualsOrIsAfter(blk.GetForkVersion(), version.Electra()) { - // After Electra, validators can request withdrawals through execution requests which must be handled. - requests, err := blk.GetBody().GetExecutionRequests() - if err != nil { - return err - } - for _, withdrawal := range requests.Withdrawals { - if withdrawErr := sp.processWithdrawalRequest(st, withdrawal); withdrawErr != nil { - return withdrawErr + // If withdrawals are enabled, process the withdrawals. Otherwise, the withdrawal requests are ignored + // to prevent withdrawals from excessively queuing up. + if sp.cs.WithdrawalsEnabled(blk.GetTimestamp()) { + requests, err := blk.GetBody().GetExecutionRequests() + if err != nil { + return err + } + for _, withdrawal := range requests.Withdrawals { + if withdrawErr := sp.processWithdrawalRequest(st, withdrawal); withdrawErr != nil { + return withdrawErr + } } } } From 8f81a38511b706bf0246c3a8d0e5a5f578d50007 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 09:52:37 +1000 Subject: [PATCH 04/21] extending withdrawals disabling --- config/spec/defaults.go | 3 ++- config/spec/mainnet.go | 7 +++--- config/spec/testnet.go | 3 +++ .../core/state_processor_staking.go | 22 +++++++++---------- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/config/spec/defaults.go b/config/spec/defaults.go index af78281a4b..16df25b12b 100644 --- a/config/spec/defaults.go +++ b/config/spec/defaults.go @@ -56,7 +56,8 @@ const ( defaultTargetSecondsPerEth1Block = 2 // Berachain specific. // Fork-related values. - defaultElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. + defaultElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. + default1ElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. // State list length constants. defaultEpochsPerHistoricalVector = 8 diff --git a/config/spec/mainnet.go b/config/spec/mainnet.go index a2af82d5fb..d90547c67c 100644 --- a/config/spec/mainnet.go +++ b/config/spec/mainnet.go @@ -127,9 +127,10 @@ func MainnetChainSpecData() *chain.SpecData { TargetSecondsPerEth1Block: defaultTargetSecondsPerEth1Block, // Fork-related values. - GenesisTime: mainnetGenesisTime, - Deneb1ForkTime: mainnetDeneb1ForkTime, - ElectraForkTime: defaultElectraForkTime, + GenesisTime: mainnetGenesisTime, + Deneb1ForkTime: mainnetDeneb1ForkTime, + ElectraForkTime: defaultElectraForkTime, + Electra1ForkTime: default1ElectraForkTime, // State list length constants. EpochsPerHistoricalVector: defaultEpochsPerHistoricalVector, diff --git a/config/spec/testnet.go b/config/spec/testnet.go index 22b80a4d3f..c1438309cd 100644 --- a/config/spec/testnet.go +++ b/config/spec/testnet.go @@ -40,6 +40,9 @@ func TestnetChainSpecData() *chain.SpecData { // Timestamp of the Electra fork on Bepolia. specData.ElectraForkTime = 1746633600 + // Far future timestamp not yet determined. + specData.Electra1ForkTime = default1ElectraForkTime + return specData } diff --git a/state-transition/core/state_processor_staking.go b/state-transition/core/state_processor_staking.go index 7c12af680d..e9a91074e3 100644 --- a/state-transition/core/state_processor_staking.go +++ b/state-transition/core/state_processor_staking.go @@ -68,18 +68,16 @@ func (sp *StateProcessor) processOperations( } // After Electra, validators can request withdrawals through execution requests which must be handled. - if version.EqualsOrIsAfter(blk.GetForkVersion(), version.Electra()) { - // If withdrawals are enabled, process the withdrawals. Otherwise, the withdrawal requests are ignored - // to prevent withdrawals from excessively queuing up. - if sp.cs.WithdrawalsEnabled(blk.GetTimestamp()) { - requests, err := blk.GetBody().GetExecutionRequests() - if err != nil { - return err - } - for _, withdrawal := range requests.Withdrawals { - if withdrawErr := sp.processWithdrawalRequest(st, withdrawal); withdrawErr != nil { - return withdrawErr - } + // If withdrawals are enabled, process the withdrawals. Otherwise, the withdrawal requests are ignored + // to prevent withdrawals from excessively queuing up. + if version.EqualsOrIsAfter(blk.GetForkVersion(), version.Electra()) && sp.cs.WithdrawalsEnabled(blk.GetTimestamp()) { + requests, err := blk.GetBody().GetExecutionRequests() + if err != nil { + return err + } + for _, withdrawal := range requests.Withdrawals { + if withdrawErr := sp.processWithdrawalRequest(st, withdrawal); withdrawErr != nil { + return withdrawErr } } } From 1f9b9c93710a7028bb661da96d1c81eb006fdda1 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 09:57:44 +1000 Subject: [PATCH 05/21] Remove electra changes --- chain/data.go | 2 -- chain/helpers.go | 3 --- chain/spec.go | 5 ----- config/spec/defaults.go | 3 +-- state-transition/core/state/statedb.go | 9 +++++---- testing/files/spec.toml | 1 - testing/networks/80069/spec.toml | 1 - testing/networks/80094/spec.toml | 1 - 8 files changed, 6 insertions(+), 19 deletions(-) diff --git a/chain/data.go b/chain/data.go index d64b690f3d..6595bc8fad 100644 --- a/chain/data.go +++ b/chain/data.go @@ -95,8 +95,6 @@ type SpecData struct { Deneb1ForkTime uint64 `mapstructure:"deneb-one-fork-time"` // ElectraForkTime is the time at which the Electra fork is activated. ElectraForkTime uint64 `mapstructure:"electra-fork-time"` - // Electra1ForkTime is the time at which the Electra1 fork is activated. - Electra1ForkTime uint64 `mapstructure:"electra-one-fork-time"` // State list lengths // diff --git a/chain/helpers.go b/chain/helpers.go index 7cbfe4d8fd..8925bd0815 100644 --- a/chain/helpers.go +++ b/chain/helpers.go @@ -29,9 +29,6 @@ import ( // ActiveForkVersionForTimestamp returns the active fork version for a given timestamp. func (s spec) ActiveForkVersionForTimestamp(timestamp math.U64) common.Version { time := timestamp.Unwrap() - if time >= s.Electra1ForkTime() { - return version.Electra1() - } if time >= s.ElectraForkTime() { return version.Electra() } diff --git a/chain/spec.go b/chain/spec.go index 280ca781cf..2f2eea1dfa 100644 --- a/chain/spec.go +++ b/chain/spec.go @@ -379,11 +379,6 @@ func (s spec) ElectraForkTime() uint64 { return s.Data.ElectraForkTime } -// Electra1ForkTime returns the epoch of the Electra fork. -func (s spec) Electra1ForkTime() uint64 { - return s.Data.Electra1ForkTime -} - // EpochsPerHistoricalVector returns the number of epochs per historical vector. func (s spec) EpochsPerHistoricalVector() uint64 { return s.Data.EpochsPerHistoricalVector diff --git a/config/spec/defaults.go b/config/spec/defaults.go index 16df25b12b..af78281a4b 100644 --- a/config/spec/defaults.go +++ b/config/spec/defaults.go @@ -56,8 +56,7 @@ const ( defaultTargetSecondsPerEth1Block = 2 // Berachain specific. // Fork-related values. - defaultElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. - default1ElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. + defaultElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. // State list length constants. defaultEpochsPerHistoricalVector = 8 diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index a776d76d92..01a4210ec4 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -155,10 +155,6 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // Iterate through indices to find the next validators to withdraw. for range bound { - // Cap the number of withdrawals to the maximum allowed per payload. - if uint64(len(withdrawals)) == maxWithdrawals { - break - } validator, err = s.ValidatorByIndex(validatorIndex) if err != nil { return nil, 0, err @@ -213,6 +209,11 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With withdrawalIndex++ } + // Cap the number of withdrawals to the maximum allowed per payload. + if uint64(len(withdrawals)) == maxWithdrawals { + break + } + // Increment the validator index to process the next validator. validatorIndex = (validatorIndex + 1) % totalValidators } diff --git a/testing/files/spec.toml b/testing/files/spec.toml index 5ba0eaf039..9b392b8b1a 100644 --- a/testing/files/spec.toml +++ b/testing/files/spec.toml @@ -35,7 +35,6 @@ target-seconds-per-eth1-block = 2 genesis-time = 0 deneb-one-fork-time = 0 electra-fork-time = 0 -electra-one-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/networks/80069/spec.toml b/testing/networks/80069/spec.toml index 37d5003c84..89a7f58d32 100644 --- a/testing/networks/80069/spec.toml +++ b/testing/networks/80069/spec.toml @@ -35,7 +35,6 @@ target-seconds-per-eth1-block = 2 genesis-time = 1_739_976_735 deneb-one-fork-time = 1_740_090_694 electra-fork-time = 1_746_633_600 -electra-one-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/networks/80094/spec.toml b/testing/networks/80094/spec.toml index fe67106ffc..7830dac2e5 100644 --- a/testing/networks/80094/spec.toml +++ b/testing/networks/80094/spec.toml @@ -35,7 +35,6 @@ target-seconds-per-eth1-block = 2 genesis-time = 1_737_381_600 deneb-one-fork-time = 1_738_415_507 electra-fork-time = 9_999_999_999_999_999 -electra-one-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 From eb5df2cf7a0053dde434cb8c6faaa1d28035deb2 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 09:58:31 +1000 Subject: [PATCH 06/21] fix --- config/spec/mainnet.go | 7 +++---- config/spec/testnet.go | 3 --- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/config/spec/mainnet.go b/config/spec/mainnet.go index d90547c67c..a2af82d5fb 100644 --- a/config/spec/mainnet.go +++ b/config/spec/mainnet.go @@ -127,10 +127,9 @@ func MainnetChainSpecData() *chain.SpecData { TargetSecondsPerEth1Block: defaultTargetSecondsPerEth1Block, // Fork-related values. - GenesisTime: mainnetGenesisTime, - Deneb1ForkTime: mainnetDeneb1ForkTime, - ElectraForkTime: defaultElectraForkTime, - Electra1ForkTime: default1ElectraForkTime, + GenesisTime: mainnetGenesisTime, + Deneb1ForkTime: mainnetDeneb1ForkTime, + ElectraForkTime: defaultElectraForkTime, // State list length constants. EpochsPerHistoricalVector: defaultEpochsPerHistoricalVector, diff --git a/config/spec/testnet.go b/config/spec/testnet.go index c1438309cd..22b80a4d3f 100644 --- a/config/spec/testnet.go +++ b/config/spec/testnet.go @@ -40,9 +40,6 @@ func TestnetChainSpecData() *chain.SpecData { // Timestamp of the Electra fork on Bepolia. specData.ElectraForkTime = 1746633600 - // Far future timestamp not yet determined. - specData.Electra1ForkTime = default1ElectraForkTime - return specData } From 368fd7e70d779c7b565441051a63371471a562c2 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 10:21:37 +1000 Subject: [PATCH 07/21] Update statedb.go --- state-transition/core/state/statedb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 01a4210ec4..3cb967e47f 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -119,8 +119,8 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // If withdrawals are not enabled, return only the inflation withdrawal. // Once withdrawals are re-enabled, all pending withdrawals will be processed. // 1. Partial Withdrawal Requests will remain untouched and will be handled after re-enabling. - // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn. - // 3. Validators who have initiated an exit and have reached the withdrawable epoch will not be withdrawn. + // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn till re-enabled. + // 3. Validators who have initiated an exit and have reached the withdrawable epoch will not be withdrawn till re-enabled. if !s.cs.WithdrawalsEnabled(timestamp) { return withdrawals, processedPartialWithdrawals, nil From f8b20162205a40634e30bbfe2bdadc02a2bf776d Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 10:41:18 +1000 Subject: [PATCH 08/21] Update statedb.go --- state-transition/core/state/statedb.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 3cb967e47f..931bdef61b 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -120,7 +120,8 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // Once withdrawals are re-enabled, all pending withdrawals will be processed. // 1. Partial Withdrawal Requests will remain untouched and will be handled after re-enabling. // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn till re-enabled. - // 3. Validators who have initiated an exit and have reached the withdrawable epoch will not be withdrawn till re-enabled. + // 3. Validators who have initiated an exit via full withdrawal and have reached the withdrawable epoch will not be withdrawn till re-enabled. + // 4. Validators who have been kicked out due to validator set cap, i.e., initiated an exit, will not be withdrawn till re-enabled. if !s.cs.WithdrawalsEnabled(timestamp) { return withdrawals, processedPartialWithdrawals, nil From b84c9ea3a74eefd4f081a7033986a36441d25bef Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 10:43:35 +1000 Subject: [PATCH 09/21] Update statedb.go --- state-transition/core/state/statedb.go | 1 - 1 file changed, 1 deletion(-) diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index 931bdef61b..d635cb5d79 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -122,7 +122,6 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn till re-enabled. // 3. Validators who have initiated an exit via full withdrawal and have reached the withdrawable epoch will not be withdrawn till re-enabled. // 4. Validators who have been kicked out due to validator set cap, i.e., initiated an exit, will not be withdrawn till re-enabled. - if !s.cs.WithdrawalsEnabled(timestamp) { return withdrawals, processedPartialWithdrawals, nil } From 8c10052f3ed556b47a175602a7fa139ee46dedbe Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 10:55:30 +1000 Subject: [PATCH 10/21] comments linting --- state-transition/core/state/statedb.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index d635cb5d79..ee50e7e365 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -120,8 +120,8 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // Once withdrawals are re-enabled, all pending withdrawals will be processed. // 1. Partial Withdrawal Requests will remain untouched and will be handled after re-enabling. // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn till re-enabled. - // 3. Validators who have initiated an exit via full withdrawal and have reached the withdrawable epoch will not be withdrawn till re-enabled. - // 4. Validators who have been kicked out due to validator set cap, i.e., initiated an exit, will not be withdrawn till re-enabled. + // 3. Validators who have initiated a full withdrawal will not be withdrawn till re-enabled. + // 4. Validators who have been kicked out due to validator set cap will not be withdrawn till re-enabled. if !s.cs.WithdrawalsEnabled(timestamp) { return withdrawals, processedPartialWithdrawals, nil } From 7f0ae9b48c76ed89906882200646c6cb95fafc59 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 12:05:37 +1000 Subject: [PATCH 11/21] Added test for freezing withdrawals --- testing/simulated/components.go | 38 +++ .../pectra_freeze_withdrawals_test.go | 288 ++++++++++++++++++ 2 files changed, 326 insertions(+) create mode 100644 testing/simulated/pectra_freeze_withdrawals_test.go diff --git a/testing/simulated/components.go b/testing/simulated/components.go index ae1903f4f3..344dfb4645 100644 --- a/testing/simulated/components.go +++ b/testing/simulated/components.go @@ -28,6 +28,7 @@ import ( "github.com/berachain/beacon-kit/chain" "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/node-core/components" + "github.com/berachain/beacon-kit/primitives/math" ) func FixedComponents(t *testing.T) []any { @@ -144,3 +145,40 @@ func ProvidePectraWithdrawalTestChainSpec() (chain.Spec, error) { } return chainSpec, nil } + +// frozenWithdrawalsSpec is used in ProvideFreezeWithdrawalsChainSpec as a chain spec with withdrawals disabled +// after a certain time, then renabled +type frozenWithdrawalsSpec struct { + chain.Spec +} + +func (s frozenWithdrawalsSpec) WithdrawalsEnabled(timestamp math.U64) bool { + // withdrawals disabled from timestamp 10 to 30 + if timestamp >= 10 && timestamp < 30 { + return false + } + return true +} + +// ProvideFreezeWithdrawalsChainSpec provides a chain spec with pectra as the genesis, but with the +// withdrawals disabled and then re-enabled. +func ProvideFreezeWithdrawalsChainSpec() (chain.Spec, error) { + specData := spec.TestnetChainSpecData() + // Both Deneb1 and Electra happen in genesis. + specData.GenesisTime = 0 + specData.Deneb1ForkTime = 0 + specData.ElectraForkTime = 0 + // We set slots per epoch to 1 for faster observation of withdrawal behaviour + specData.SlotsPerEpoch = 1 + // We set this to 4 so tests are faster + specData.MinValidatorWithdrawabilityDelay = 4 + // Reduced validator set cap so eviction withdrawals are easier to trigger + specData.ValidatorSetCap = 1 + + chainSpec, err := chain.NewSpec(specData) + if err != nil { + return nil, err + } + + return frozenWithdrawalsSpec{chainSpec}, nil +} diff --git a/testing/simulated/pectra_freeze_withdrawals_test.go b/testing/simulated/pectra_freeze_withdrawals_test.go new file mode 100644 index 0000000000..919cbb7573 --- /dev/null +++ b/testing/simulated/pectra_freeze_withdrawals_test.go @@ -0,0 +1,288 @@ +//go:build simulated + +// SPDX-License-Identifier: BUSL-1.1 +// +// Copyright (C) 2025, Berachain Foundation. All rights reserved. +// Use of this software is governed by the Business Source License included +// in the LICENSE file of this repository and at www.mariadb.com/bsl11. +// +// ANY USE OF THE LICENSED WORK IN VIOLATION OF THIS LICENSE WILL AUTOMATICALLY +// TERMINATE YOUR RIGHTS UNDER THIS LICENSE FOR THE CURRENT AND ALL OTHER +// VERSIONS OF THE LICENSED WORK. +// +// THIS LICENSE DOES NOT GRANT YOU ANY RIGHT IN ANY TRADEMARK OR LOGO OF +// LICENSOR OR ITS AFFILIATES (PROVIDED THAT YOU MAY USE A TRADEMARK OR LOGO OF +// LICENSOR AS EXPRESSLY REQUIRED BY THIS LICENSE). +// +// TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON +// AN “AS IS” BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS, +// EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND +// TITLE. + +package simulated_test + +import ( + "bytes" + "context" + "math/big" + "path" + "testing" + "time" + + depositcli "github.com/berachain/beacon-kit/cli/commands/deposit" + consensustypes "github.com/berachain/beacon-kit/consensus-types/types" + "github.com/berachain/beacon-kit/execution/requests/eip7002" + "github.com/berachain/beacon-kit/geth-primitives/bind" + "github.com/berachain/beacon-kit/geth-primitives/deposit" + "github.com/berachain/beacon-kit/log/phuslu" + "github.com/berachain/beacon-kit/node-core/components/signer" + "github.com/berachain/beacon-kit/primitives/common" + "github.com/berachain/beacon-kit/primitives/constants" + beaconmath "github.com/berachain/beacon-kit/primitives/math" + "github.com/berachain/beacon-kit/testing/simulated" + "github.com/berachain/beacon-kit/testing/simulated/execution" + gethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + gethcore "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/params" + "github.com/spf13/viper" + "github.com/stretchr/testify/suite" +) + +// PectraFreezeWithdrawalsSuite defines our test suite for Pectra with withdrawals frozen, e.g. in emergency scenario. +type PectraFreezeWithdrawalsSuite struct { + suite.Suite + // Embedded shared accessors for convenience. + simulated.SharedAccessors +} + +// TestSimulatedCometComponent runs the test suite. +func TestPectraFreezeWithdrawalsSuite(t *testing.T) { + suite.Run(t, new(PectraFreezeWithdrawalsSuite)) +} + +// SetupTest initializes the test environment. +func (s *PectraFreezeWithdrawalsSuite) SetupTest() { + // Create a cancellable context for the duration of the test. + s.CtxApp, s.CtxAppCancelFn = context.WithCancel(context.Background()) + + // CometBFT uses context.TODO() for all ABCI calls, so we replicate that. + s.CtxComet = context.TODO() + + s.HomeDir = s.T().TempDir() + + // Initialize the home directory, Comet configuration, and genesis info. + const elGenesisPath = "./el-genesis-files/pectra-eth-genesis.json" + chainSpecFunc := simulated.ProvideFreezeWithdrawalsChainSpec + // Create the chainSpec. + chainSpec, err := chainSpecFunc() + s.Require().NoError(err) + cometConfig, genesisValidatorsRoot := simulated.InitializeHomeDir(s.T(), chainSpec, s.HomeDir, elGenesisPath) + s.GenesisValidatorsRoot = genesisValidatorsRoot + + // Start the EL (execution layer) Geth node. + elNode := execution.NewGethNode(s.HomeDir, execution.ValidGethImage()) + elHandle, authRPC, elRPC := elNode.Start(s.T(), path.Base(elGenesisPath)) + s.ElHandle = elHandle + + // Prepare a logger backed by a buffer to capture logs for assertions. + s.LogBuffer = new(bytes.Buffer) + logger := phuslu.NewLogger(s.LogBuffer, nil) + + // Build the Beacon node with the simulated Comet component and electra genesis chain spec + components := simulated.FixedComponents(s.T()) + components = append(components, simulated.ProvideSimComet) + components = append(components, chainSpecFunc) + + s.TestNode = simulated.NewTestNode(s.T(), simulated.TestNodeInput{ + TempHomeDir: s.HomeDir, + CometConfig: cometConfig, + AuthRPC: authRPC, + ClientRPC: elRPC, + Logger: logger, + AppOpts: viper.New(), + Components: components, + }) + + s.SimComet = s.TestNode.SimComet + + // Start the Beacon node in a separate goroutine. + go func() { + _ = s.TestNode.Start(s.CtxApp) + }() + + s.SimulationClient = execution.NewSimulationClient(s.TestNode.EngineClient) + timeOut := 10 * time.Second + interval := 50 * time.Millisecond + err = simulated.WaitTillServicesStarted(s.LogBuffer, timeOut, interval) + s.Require().NoError(err) +} + +// TearDownTest cleans up the test environment. +func (s *PectraFreezeWithdrawalsSuite) TearDownTest() { + // If the test has failed, log additional information. + if s.T().Failed() { + s.T().Log(s.LogBuffer.String()) + } + if err := s.ElHandle.Close(); err != nil { + s.T().Error("Error closing EL handle:", err) + } + // mimics the behaviour of shutdown func + s.CtxAppCancelFn() + s.TestNode.ServiceRegistry.StopAll() +} + +func (s *PectraFreezeWithdrawalsSuite) TestWithdrawals_FullWithdrawal_WhileWithdrawalsFrozen() { + // Initialize the chain and BLS signer + s.InitializeChain(s.T()) + blsSigner := simulated.GetBlsSigner(s.HomeDir) + + // Derive the execution address for the withdrawal + execAddr := simulated.WithdrawalExecutionAddress + senderAddress := gethcommon.HexToAddress(common.NewExecutionAddressFromHex(execAddr).String()) + + var nextBlockHeight int64 = 1 + + // 1. Advance one block to update the withdrawal contract's EXCESS_INHIBITOR + proposals, _, _ := s.MoveChainToHeight(s.T(), nextBlockHeight, 1, blsSigner, time.Unix(nextBlockHeight*2, 0)) + s.Require().Len(proposals, 1) + nextBlockHeight++ + + // 2. Send a full withdrawal request, i.e. 0 value + totalWithdrawalGwei := beaconmath.Gwei(0) + { + senderKey := simulated.GetTestKey(s.T()) + chainID := big.NewInt(int64(s.TestNode.ChainSpec.DepositEth1ChainID())) + signer := gethcore.NewPragueSigner(chainID) + + // Fetch the protocol fee + fee, err := eip7002.GetWithdrawalFee(s.CtxApp, s.TestNode.EngineClient) + s.Require().NoError(err) + + // Build and sign the withdrawal transaction + txData, err := eip7002.CreateWithdrawalRequestData(blsSigner.PublicKey(), totalWithdrawalGwei) + s.Require().NoError(err) + + tx := gethcore.MustSignNewTx(senderKey, signer, &gethcore.DynamicFeeTx{ + ChainID: chainID, + Nonce: 0, + To: ¶ms.WithdrawalQueueAddress, + Gas: 500_000, + GasFeeCap: big.NewInt(1e9), + GasTipCap: big.NewInt(1e9), + Value: fee, + Data: txData, + }) + + // Submit the raw transaction + txBytes, err := tx.MarshalBinary() + s.Require().NoError(err) + + var result interface{} + err = s.TestNode.EngineClient.Call(s.CtxApp, &result, "eth_sendRawTransaction", hexutil.Encode(txBytes)) + s.Require().NoError(err) + } + + // 3. Mine 2 blocks to include the withdrawal in the chain + { + iterations := int64(2) + s.LogBuffer.Reset() + s.MoveChainToHeight(s.T(), nextBlockHeight, iterations, blsSigner, time.Unix(nextBlockHeight*2, 0)) + nextBlockHeight += iterations + } + + // 4. Advance passed MinValidatorWithdrawabilityDelay so validator can be withdrawn but before withdrawals are re-activated. + { + delay := int64(s.TestNode.ChainSpec.MinValidatorWithdrawabilityDelay().Unwrap()) + 2 + s.LogBuffer.Reset() + s.MoveChainToHeight(s.T(), nextBlockHeight, delay, blsSigner, time.Unix(nextBlockHeight*2, 0)) + nextBlockHeight += delay + + validators, apiErr := s.TestNode.APIBackend.FilteredValidators(beaconmath.Slot(nextBlockHeight-1), nil, nil) + s.Require().NoError(apiErr) + s.Require().Len(validators, 1) + s.Require().Equal(constants.ValidatorStatusWithdrawalPossible, validators[0].Status) + } + + // 5. Capture balance before withdrawals re-activate + var beforeWithdrawBalance *big.Int + { + var err error + beforeWithdrawBalance, err = s.TestNode.ContractBackend.BalanceAt( + s.CtxApp, + senderAddress, + big.NewInt(nextBlockHeight-1), + ) + s.Require().NoError(err) + s.T().Logf("Balance before re-activation: %s wei", beforeWithdrawBalance) + } + + // 6. Mine one block at timestamp=30s to re-activate withdrawals + { + s.LogBuffer.Reset() + s.MoveChainToHeight(s.T(), nextBlockHeight, 1, blsSigner, time.Unix(30, 0)) + nextBlockHeight++ + } + + // 7. Capture balance after withdrawal executes + var afterWithdrawBalance *big.Int + { + var err error + afterWithdrawBalance, err = s.TestNode.ContractBackend.BalanceAt( + s.CtxApp, + senderAddress, + big.NewInt(nextBlockHeight-1), + ) + s.Require().NoError(err) + s.T().Logf("Balance after withdrawal: %s wei", afterWithdrawBalance) + } + + // 8. Ensure the withdrawal amount equals 10_000_000 BERA + delta := new(big.Int).Sub(afterWithdrawBalance, beforeWithdrawBalance) + expected := big.NewInt(0).Mul(big.NewInt(10_000_000), big.NewInt(1e18)) // 10 million BERA + s.Require().Equal(expected, delta, "expected withdrawal of 10_000_000 BERA, got %s", delta) +} + +func (s *PectraFreezeWithdrawalsSuite) defaultDeposit(blsSigner *signer.BLSSigner, creds consensustypes.WithdrawalCredentials, depositAmount beaconmath.Gwei, setOperator bool) { + depositContractAddress := gethcommon.Address(s.TestNode.ChainSpec.DepositContractAddress()) + depositClient, err := deposit.NewDepositContract(depositContractAddress, s.TestNode.ContractBackend) + s.Require().NoError(err) + + depositMsg, blsSig, err := depositcli.CreateDepositMessage( + s.TestNode.ChainSpec, + blsSigner, + s.GenesisValidatorsRoot, + creds, + depositAmount, + ) + s.Require().NoError(err) + err = depositcli.ValidateDeposit( + s.TestNode.ChainSpec, + depositMsg.Pubkey, + depositMsg.Credentials, + depositMsg.Amount, + s.GenesisValidatorsRoot, + blsSig, + ) + s.Require().NoError(err) + + elChainID := big.NewInt(int64(s.TestNode.ChainSpec.DepositEth1ChainID())) + senderKey := simulated.GetTestKey(s.T()) + senderAddress := gethcommon.HexToAddress(creds.String()) + s.Require().NoError(err) + operator := senderAddress + if !setOperator { + operator = gethcommon.HexToAddress("0x0000000000000000000000000000000000000000") + } + _, err = depositClient.Deposit(&bind.TransactOpts{ + From: senderAddress, + Signer: func(_ gethcommon.Address, tx *gethcore.Transaction) (*gethcore.Transaction, error) { + return gethcore.SignTx( + tx, gethcore.LatestSignerForChainID(elChainID), senderKey, + ) + }, + Value: big.NewInt(0).Mul(big.NewInt(int64(depositAmount)), big.NewInt(1e9)), + }, depositMsg.Pubkey[:], depositMsg.Credentials[:], blsSig[:], operator) + s.Require().NoError(err) +} From cbffee5f42c30b7bacd76448d46091cad1cc1163 Mon Sep 17 00:00:00 2001 From: Rez Date: Tue, 20 May 2025 12:30:58 +1000 Subject: [PATCH 12/21] Added test --- .../pectra_freeze_withdrawals_test.go | 143 ++++++++++++------ 1 file changed, 100 insertions(+), 43 deletions(-) diff --git a/testing/simulated/pectra_freeze_withdrawals_test.go b/testing/simulated/pectra_freeze_withdrawals_test.go index 919cbb7573..0207618b74 100644 --- a/testing/simulated/pectra_freeze_withdrawals_test.go +++ b/testing/simulated/pectra_freeze_withdrawals_test.go @@ -30,13 +30,8 @@ import ( "testing" "time" - depositcli "github.com/berachain/beacon-kit/cli/commands/deposit" - consensustypes "github.com/berachain/beacon-kit/consensus-types/types" "github.com/berachain/beacon-kit/execution/requests/eip7002" - "github.com/berachain/beacon-kit/geth-primitives/bind" - "github.com/berachain/beacon-kit/geth-primitives/deposit" "github.com/berachain/beacon-kit/log/phuslu" - "github.com/berachain/beacon-kit/node-core/components/signer" "github.com/berachain/beacon-kit/primitives/common" "github.com/berachain/beacon-kit/primitives/constants" beaconmath "github.com/berachain/beacon-kit/primitives/math" @@ -244,45 +239,107 @@ func (s *PectraFreezeWithdrawalsSuite) TestWithdrawals_FullWithdrawal_WhileWithd s.Require().Equal(expected, delta, "expected withdrawal of 10_000_000 BERA, got %s", delta) } -func (s *PectraFreezeWithdrawalsSuite) defaultDeposit(blsSigner *signer.BLSSigner, creds consensustypes.WithdrawalCredentials, depositAmount beaconmath.Gwei, setOperator bool) { - depositContractAddress := gethcommon.Address(s.TestNode.ChainSpec.DepositContractAddress()) - depositClient, err := deposit.NewDepositContract(depositContractAddress, s.TestNode.ContractBackend) - s.Require().NoError(err) +// Test partial withdrawal while withdrawals are frozen +func (s *PectraFreezeWithdrawalsSuite) TestWithdrawals_PartialWithdrawal_WhileWithdrawalsFrozen() { + // Initialize chain and signer + s.InitializeChain(s.T()) + blsSigner := simulated.GetBlsSigner(s.HomeDir) - depositMsg, blsSig, err := depositcli.CreateDepositMessage( - s.TestNode.ChainSpec, - blsSigner, - s.GenesisValidatorsRoot, - creds, - depositAmount, - ) - s.Require().NoError(err) - err = depositcli.ValidateDeposit( - s.TestNode.ChainSpec, - depositMsg.Pubkey, - depositMsg.Credentials, - depositMsg.Amount, - s.GenesisValidatorsRoot, - blsSig, - ) - s.Require().NoError(err) + // Execution address and sender + execAddr := simulated.WithdrawalExecutionAddress + senderAddress := gethcommon.HexToAddress(common.NewExecutionAddressFromHex(execAddr).String()) - elChainID := big.NewInt(int64(s.TestNode.ChainSpec.DepositEth1ChainID())) - senderKey := simulated.GetTestKey(s.T()) - senderAddress := gethcommon.HexToAddress(creds.String()) - s.Require().NoError(err) - operator := senderAddress - if !setOperator { - operator = gethcommon.HexToAddress("0x0000000000000000000000000000000000000000") + var nextBlockHeight int64 = 1 + + // 1. Advance one block to bump EXCESS_INHIBITOR + proposals, _, _ := s.MoveChainToHeight(s.T(), nextBlockHeight, 1, blsSigner, time.Unix(nextBlockHeight*2, 0)) + s.Require().Len(proposals, 1) + nextBlockHeight++ + + // 2. Submit a partial withdrawal request (e.g. 5 M BERA) + totalWithdrawalGwei := beaconmath.Gwei(5_000_000 * 1e9) + { + senderKey := simulated.GetTestKey(s.T()) + chainID := big.NewInt(int64(s.TestNode.ChainSpec.DepositEth1ChainID())) + signer := gethcore.NewPragueSigner(chainID) + + // Fetch withdrawal fee + fee, err := eip7002.GetWithdrawalFee(s.CtxApp, s.TestNode.EngineClient) + s.Require().NoError(err) + + // Build request data + txData, err := eip7002.CreateWithdrawalRequestData(blsSigner.PublicKey(), totalWithdrawalGwei) + s.Require().NoError(err) + + tx := gethcore.MustSignNewTx(senderKey, signer, &gethcore.DynamicFeeTx{ + ChainID: chainID, + Nonce: 0, + To: ¶ms.WithdrawalQueueAddress, + Gas: 500_000, + GasFeeCap: big.NewInt(1e9), + GasTipCap: big.NewInt(1e9), + Value: fee, + Data: txData, + }) + + // Send tx + txBytes, err := tx.MarshalBinary() + s.Require().NoError(err) + + var result interface{} + err = s.TestNode.EngineClient.Call(s.CtxApp, &result, "eth_sendRawTransaction", hexutil.Encode(txBytes)) + s.Require().NoError(err) } - _, err = depositClient.Deposit(&bind.TransactOpts{ - From: senderAddress, - Signer: func(_ gethcommon.Address, tx *gethcore.Transaction) (*gethcore.Transaction, error) { - return gethcore.SignTx( - tx, gethcore.LatestSignerForChainID(elChainID), senderKey, - ) - }, - Value: big.NewInt(0).Mul(big.NewInt(int64(depositAmount)), big.NewInt(1e9)), - }, depositMsg.Pubkey[:], depositMsg.Credentials[:], blsSig[:], operator) - s.Require().NoError(err) + + // 3. Mine 2 blocks to include the request + { + iters := int64(2) + s.LogBuffer.Reset() + s.MoveChainToHeight(s.T(), nextBlockHeight, iters, blsSigner, time.Unix(nextBlockHeight*2, 0)) + nextBlockHeight += iters + } + + // 4. Advance past withdrawability delay but before re-activation + { + delay := int64(s.TestNode.ChainSpec.MinValidatorWithdrawabilityDelay().Unwrap()) + 2 + s.LogBuffer.Reset() + s.MoveChainToHeight(s.T(), nextBlockHeight, delay, blsSigner, time.Unix(nextBlockHeight*2, 0)) + nextBlockHeight += delay + + // Validator should be withdrawable + validators, apiErr := s.TestNode.APIBackend.FilteredValidators(beaconmath.Slot(nextBlockHeight-1), nil, nil) + s.Require().NoError(apiErr) + s.Require().Len(validators, 1) + //s.Require().Equal(constants.ValidatorStatusWithdrawalPossible, validators[0].Status) + } + + // 5. Get balance before re-activation + var beforeBalance *big.Int + { + var err error + beforeBalance, err = s.TestNode.ContractBackend.BalanceAt(s.CtxApp, senderAddress, big.NewInt(nextBlockHeight-1)) + s.Require().NoError(err) + s.T().Logf("Balance before re-activation: %s wei", beforeBalance) + } + + // 6. Activate withdrawals at 30s + { + s.LogBuffer.Reset() + s.MoveChainToHeight(s.T(), nextBlockHeight, 1, blsSigner, time.Unix(30, 0)) + nextBlockHeight++ + } + + // 7. Get balance after partial withdrawal + var afterBalance *big.Int + { + var err error + afterBalance, err = s.TestNode.ContractBackend.BalanceAt(s.CtxApp, senderAddress, big.NewInt(nextBlockHeight-1)) + s.Require().NoError(err) + s.T().Logf("Balance after withdrawal: %s wei", afterBalance) + } + + // 8. Verify withdrawal equals 5M BERA + delta := new(big.Int).Sub(afterBalance, beforeBalance) + expected := new(big.Int).Mul(big.NewInt(5_000_000), big.NewInt(1e18)) + s.Require().Equal(expected.String(), delta.String(), "expected withdrawal of %d wei, got %s", expected.String(), delta) } From 6f76b581351f71ae62bcc789871311107b22b331 Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 05:30:24 +1000 Subject: [PATCH 13/21] Add Disable and Enable Fork --- chain/data.go | 4 ++++ chain/helpers.go | 16 ++++++++++++++++ chain/spec.go | 29 ++++++++++++----------------- config/spec/defaults.go | 5 ++++- config/spec/mainnet.go | 8 +++++--- config/spec/testnet.go | 4 ++++ testing/files/spec.toml | 2 ++ testing/networks/80069/spec.toml | 2 ++ testing/networks/80094/spec.toml | 2 ++ testing/simulated/components.go | 21 +++++---------------- 10 files changed, 56 insertions(+), 37 deletions(-) diff --git a/chain/data.go b/chain/data.go index 6595bc8fad..08e1867d41 100644 --- a/chain/data.go +++ b/chain/data.go @@ -95,6 +95,10 @@ type SpecData struct { Deneb1ForkTime uint64 `mapstructure:"deneb-one-fork-time"` // ElectraForkTime is the time at which the Electra fork is activated. ElectraForkTime uint64 `mapstructure:"electra-fork-time"` + // ElectraDisableWithdrawalsForkTime is the time at which withdrawals were first disabled (if disabled). + ElectraDisableWithdrawalsForkTime uint64 `mapstructure:"electra-disable-withdrawals-fork-time"` + // ElectraEnableWithdrawalsForkTime is the time at which withdrawals were enabled after disabling + ElectraEnableWithdrawalsForkTime uint64 `mapstructure:"electra-enable-withdrawals-fork-time"` // State list lengths // diff --git a/chain/helpers.go b/chain/helpers.go index 8925bd0815..0d08fd55a0 100644 --- a/chain/helpers.go +++ b/chain/helpers.go @@ -27,6 +27,12 @@ import ( ) // ActiveForkVersionForTimestamp returns the active fork version for a given timestamp. +// Note that ActiveForkVersionForTimestamp will NOT check for +// - ElectraDisableWithdrawalsForkTime +// - ElectraEnableWithdrawalsForkTime +// +// As there is no logic that relies on the above forks that goes through ActiveForkVersionForTimestamp. +// It also gives flexibility for these forks to occur at any point after Electra, e.g. after Electra1. func (s spec) ActiveForkVersionForTimestamp(timestamp math.U64) common.Version { time := timestamp.Unwrap() if time >= s.ElectraForkTime() { @@ -38,6 +44,16 @@ func (s spec) ActiveForkVersionForTimestamp(timestamp math.U64) common.Version { return version.Deneb() } +// WithdrawalsEnabled is a switch that can be used to freeze withdrawals in an emergency scenario. +// An exception is made for the EVM inflation withdrawal which is always active. +func (s spec) WithdrawalsEnabled(timestamp math.U64) bool { + time := timestamp.Unwrap() + if time >= s.ElectraDisableWithdrawalsForkTime() && time < s.ElectraEnableWithdrawalsForkTime() { + return false + } + return true +} + // GenesisForkVersion returns the fork version at genesis. func (s spec) GenesisForkVersion() common.Version { return s.ActiveForkVersionForTimestamp(math.U64(s.GenesisTime())) diff --git a/chain/spec.go b/chain/spec.go index 2f2eea1dfa..14e3aa39d8 100644 --- a/chain/spec.go +++ b/chain/spec.go @@ -369,16 +369,26 @@ func (s spec) GenesisTime() uint64 { return s.Data.GenesisTime } -// Deneb1ForkTime returns the epoch of the Deneb1 fork. +// Deneb1ForkTime returns the timestamp of the Deneb1 fork. func (s spec) Deneb1ForkTime() uint64 { return s.Data.Deneb1ForkTime } -// ElectraForkTime returns the epoch of the Electra fork. +// ElectraForkTime returns the timestamp of the Electra fork. func (s spec) ElectraForkTime() uint64 { return s.Data.ElectraForkTime } +// ElectraDisableWithdrawalsForkTime returns the timestamps of the ElectraDisableWithdrawalsForkTime fork. +func (s spec) ElectraDisableWithdrawalsForkTime() uint64 { + return s.Data.ElectraDisableWithdrawalsForkTime +} + +// ElectraEnableWithdrawalsForkTime returns the timestamps of the ElectraEnableWithdrawalsForkTime fork. +func (s spec) ElectraEnableWithdrawalsForkTime() uint64 { + return s.Data.ElectraEnableWithdrawalsForkTime +} + // EpochsPerHistoricalVector returns the number of epochs per historical vector. func (s spec) EpochsPerHistoricalVector() uint64 { return s.Data.EpochsPerHistoricalVector @@ -473,18 +483,3 @@ func (s spec) EVMInflationPerBlock(timestamp math.U64) math.Gwei { panic(fmt.Sprintf("EVMInflationPerBlock not supported for this fork version: %d", fv)) } } - -// WithdrawalsEnabled is a switch that can be used to freeze withdrawals in an emergency scenario. -// An exception is made for the EVM inflation withdrawal which is always active. -func (s spec) WithdrawalsEnabled(_ math.U64) bool { - return true - /* - Example: - fv := s.ActiveForkVersionForTimestamp(timestamp) - switch fv { - case version.Electra1(): - return false - default: - return true - */ -} diff --git a/config/spec/defaults.go b/config/spec/defaults.go index af78281a4b..48b022c3ae 100644 --- a/config/spec/defaults.go +++ b/config/spec/defaults.go @@ -56,7 +56,10 @@ const ( defaultTargetSecondsPerEth1Block = 2 // Berachain specific. // Fork-related values. - defaultElectraForkTime = 9999999999999999 // Set as a future timestamp as not yet determined. + defaultFarFutureTimestamp = 9999999999999999 + defaultElectraForkTime = defaultFarFutureTimestamp // Set as a future timestamp as not yet determined. + defaultElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp + defaultElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp // State list length constants. defaultEpochsPerHistoricalVector = 8 diff --git a/config/spec/mainnet.go b/config/spec/mainnet.go index a2af82d5fb..bf6b94ffc0 100644 --- a/config/spec/mainnet.go +++ b/config/spec/mainnet.go @@ -127,9 +127,11 @@ func MainnetChainSpecData() *chain.SpecData { TargetSecondsPerEth1Block: defaultTargetSecondsPerEth1Block, // Fork-related values. - GenesisTime: mainnetGenesisTime, - Deneb1ForkTime: mainnetDeneb1ForkTime, - ElectraForkTime: defaultElectraForkTime, + GenesisTime: mainnetGenesisTime, + Deneb1ForkTime: mainnetDeneb1ForkTime, + ElectraForkTime: defaultElectraForkTime, + ElectraDisableWithdrawalsForkTime: defaultElectraDisableWithdrawalsForkTime, + ElectraEnableWithdrawalsForkTime: defaultElectraEnableWithdrawalsForkTime, // State list length constants. EpochsPerHistoricalVector: defaultEpochsPerHistoricalVector, diff --git a/config/spec/testnet.go b/config/spec/testnet.go index 22b80a4d3f..7d6543fb1f 100644 --- a/config/spec/testnet.go +++ b/config/spec/testnet.go @@ -40,6 +40,10 @@ func TestnetChainSpecData() *chain.SpecData { // Timestamp of the Electra fork on Bepolia. specData.ElectraForkTime = 1746633600 + // Override the mainnet configuration for timestamps so it is not accidentally set on testnet a mainnet update. + specData.ElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp + specData.ElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp + return specData } diff --git a/testing/files/spec.toml b/testing/files/spec.toml index 9b392b8b1a..df7e0805f5 100644 --- a/testing/files/spec.toml +++ b/testing/files/spec.toml @@ -35,6 +35,8 @@ target-seconds-per-eth1-block = 2 genesis-time = 0 deneb-one-fork-time = 0 electra-fork-time = 0 +electra-disable-withdrawals-fork-time = 9_999_999_999_999_999 +electra-enable-withdrawals-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/networks/80069/spec.toml b/testing/networks/80069/spec.toml index 89a7f58d32..d982dd448a 100644 --- a/testing/networks/80069/spec.toml +++ b/testing/networks/80069/spec.toml @@ -35,6 +35,8 @@ target-seconds-per-eth1-block = 2 genesis-time = 1_739_976_735 deneb-one-fork-time = 1_740_090_694 electra-fork-time = 1_746_633_600 +electra-disable-withdrawals-fork-time = 9_999_999_999_999_999 +electra-enable-withdrawals-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/networks/80094/spec.toml b/testing/networks/80094/spec.toml index 7830dac2e5..3ca7f5964e 100644 --- a/testing/networks/80094/spec.toml +++ b/testing/networks/80094/spec.toml @@ -35,6 +35,8 @@ target-seconds-per-eth1-block = 2 genesis-time = 1_737_381_600 deneb-one-fork-time = 1_738_415_507 electra-fork-time = 9_999_999_999_999_999 +electra-disable-withdrawals-fork-time = 9_999_999_999_999_999 +electra-enable-withdrawals-fork-time = 9_999_999_999_999_999 # State list lengths epochs-per-historical-vector = 8 diff --git a/testing/simulated/components.go b/testing/simulated/components.go index 344dfb4645..ab5070762e 100644 --- a/testing/simulated/components.go +++ b/testing/simulated/components.go @@ -28,7 +28,6 @@ import ( "github.com/berachain/beacon-kit/chain" "github.com/berachain/beacon-kit/config/spec" "github.com/berachain/beacon-kit/node-core/components" - "github.com/berachain/beacon-kit/primitives/math" ) func FixedComponents(t *testing.T) []any { @@ -146,20 +145,6 @@ func ProvidePectraWithdrawalTestChainSpec() (chain.Spec, error) { return chainSpec, nil } -// frozenWithdrawalsSpec is used in ProvideFreezeWithdrawalsChainSpec as a chain spec with withdrawals disabled -// after a certain time, then renabled -type frozenWithdrawalsSpec struct { - chain.Spec -} - -func (s frozenWithdrawalsSpec) WithdrawalsEnabled(timestamp math.U64) bool { - // withdrawals disabled from timestamp 10 to 30 - if timestamp >= 10 && timestamp < 30 { - return false - } - return true -} - // ProvideFreezeWithdrawalsChainSpec provides a chain spec with pectra as the genesis, but with the // withdrawals disabled and then re-enabled. func ProvideFreezeWithdrawalsChainSpec() (chain.Spec, error) { @@ -175,10 +160,14 @@ func ProvideFreezeWithdrawalsChainSpec() (chain.Spec, error) { // Reduced validator set cap so eviction withdrawals are easier to trigger specData.ValidatorSetCap = 1 + // Disable and Enable withdrawals. + specData.ElectraDisableWithdrawalsForkTime = 10 + specData.ElectraEnableWithdrawalsForkTime = 30 + chainSpec, err := chain.NewSpec(specData) if err != nil { return nil, err } - return frozenWithdrawalsSpec{chainSpec}, nil + return chainSpec, nil } From 0485ae05582a3687fc62ae70e636d9567b79b95f Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 05:31:49 +1000 Subject: [PATCH 14/21] Update defaults.go --- config/spec/defaults.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/spec/defaults.go b/config/spec/defaults.go index 48b022c3ae..0c85595dc9 100644 --- a/config/spec/defaults.go +++ b/config/spec/defaults.go @@ -56,8 +56,8 @@ const ( defaultTargetSecondsPerEth1Block = 2 // Berachain specific. // Fork-related values. - defaultFarFutureTimestamp = 9999999999999999 - defaultElectraForkTime = defaultFarFutureTimestamp // Set as a future timestamp as not yet determined. + defaultFarFutureTimestamp = 9999999999999999 // a future timestamp as not yet determined. + defaultElectraForkTime = defaultFarFutureTimestamp defaultElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp defaultElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp From 23bd6d5047f6e516afe2fce6d5bc9b623f7dd3d9 Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 05:33:17 +1000 Subject: [PATCH 15/21] Update devnet.go --- config/spec/devnet.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/spec/devnet.go b/config/spec/devnet.go index 0049d4bc5e..9dbd90d809 100644 --- a/config/spec/devnet.go +++ b/config/spec/devnet.go @@ -72,6 +72,8 @@ func DevnetChainSpecData() *chain.SpecData { specData.GenesisTime = devnetGenesisTime specData.Deneb1ForkTime = devnetDeneb1ForkTime specData.ElectraForkTime = devnetElectraForkTime + specData.ElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp + specData.ElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp // EVM inflation is different from mainnet to test. specData.EVMInflationAddressGenesis = common.NewExecutionAddressFromHex(devnetEVMInflationAddress) From c018962e3e2fbf939b6231224fe5ba3adbff453c Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 05:35:14 +1000 Subject: [PATCH 16/21] Update devnet.go --- config/spec/devnet.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/config/spec/devnet.go b/config/spec/devnet.go index 9dbd90d809..6518c07c4a 100644 --- a/config/spec/devnet.go +++ b/config/spec/devnet.go @@ -48,6 +48,11 @@ const ( // devnet is configured to start on electra. devnetElectraForkTime = 0 + // devnetElectraDisableWithdrawalsForkTime is the start time at which withdrawals are disabled + devnetElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp + // devnetElectraEnableWithdrawalsForkTime is the start time at which withdrawals are re-enabled + devnetElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp + // devnetEVMInflationAddressDeneb1 is the address of the EVM inflation contract // after the Deneb1 fork. devnetEVMInflationAddressDeneb1 = "0x4206942069420694206942069420694206942069" @@ -72,8 +77,8 @@ func DevnetChainSpecData() *chain.SpecData { specData.GenesisTime = devnetGenesisTime specData.Deneb1ForkTime = devnetDeneb1ForkTime specData.ElectraForkTime = devnetElectraForkTime - specData.ElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp - specData.ElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp + specData.ElectraDisableWithdrawalsForkTime = devnetElectraDisableWithdrawalsForkTime + specData.ElectraEnableWithdrawalsForkTime = devnetElectraEnableWithdrawalsForkTime // EVM inflation is different from mainnet to test. specData.EVMInflationAddressGenesis = common.NewExecutionAddressFromHex(devnetEVMInflationAddress) From 31f1bcbd070c9e6a3e3d7869205ef369e1240f82 Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 15:50:12 +1000 Subject: [PATCH 17/21] Process Withdrawal Requests even if withdrawals disabled --- state-transition/core/state_processor_staking.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/state-transition/core/state_processor_staking.go b/state-transition/core/state_processor_staking.go index e9a91074e3..639132f1a5 100644 --- a/state-transition/core/state_processor_staking.go +++ b/state-transition/core/state_processor_staking.go @@ -70,7 +70,7 @@ func (sp *StateProcessor) processOperations( // After Electra, validators can request withdrawals through execution requests which must be handled. // If withdrawals are enabled, process the withdrawals. Otherwise, the withdrawal requests are ignored // to prevent withdrawals from excessively queuing up. - if version.EqualsOrIsAfter(blk.GetForkVersion(), version.Electra()) && sp.cs.WithdrawalsEnabled(blk.GetTimestamp()) { + if version.EqualsOrIsAfter(blk.GetForkVersion(), version.Electra()) { requests, err := blk.GetBody().GetExecutionRequests() if err != nil { return err From 0cca53eea5fcc1a5b41afbd1f4e64a7ec8359667 Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 19:36:14 +1000 Subject: [PATCH 18/21] Fix comment based on updated logic --- state-transition/core/state_processor_staking.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/state-transition/core/state_processor_staking.go b/state-transition/core/state_processor_staking.go index 639132f1a5..210afbb53f 100644 --- a/state-transition/core/state_processor_staking.go +++ b/state-transition/core/state_processor_staking.go @@ -68,8 +68,6 @@ func (sp *StateProcessor) processOperations( } // After Electra, validators can request withdrawals through execution requests which must be handled. - // If withdrawals are enabled, process the withdrawals. Otherwise, the withdrawal requests are ignored - // to prevent withdrawals from excessively queuing up. if version.EqualsOrIsAfter(blk.GetForkVersion(), version.Electra()) { requests, err := blk.GetBody().GetExecutionRequests() if err != nil { From f366782bfc643927fe0adc4b1ae3b6afb85736a5 Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 21 May 2025 20:12:48 +1000 Subject: [PATCH 19/21] Flip to withdrawals disabled --- chain/helpers.go | 9 +++------ chain/spec.go | 4 ++-- state-transition/core/state/statedb.go | 2 +- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/chain/helpers.go b/chain/helpers.go index 0d08fd55a0..3d137d985f 100644 --- a/chain/helpers.go +++ b/chain/helpers.go @@ -44,14 +44,11 @@ func (s spec) ActiveForkVersionForTimestamp(timestamp math.U64) common.Version { return version.Deneb() } -// WithdrawalsEnabled is a switch that can be used to freeze withdrawals in an emergency scenario. +// WithdrawalsDisabled is a switch that can be used to freeze withdrawals in an emergency scenario. // An exception is made for the EVM inflation withdrawal which is always active. -func (s spec) WithdrawalsEnabled(timestamp math.U64) bool { +func (s spec) WithdrawalsDisabled(timestamp math.U64) bool { time := timestamp.Unwrap() - if time >= s.ElectraDisableWithdrawalsForkTime() && time < s.ElectraEnableWithdrawalsForkTime() { - return false - } - return true + return time >= s.ElectraDisableWithdrawalsForkTime() && time < s.ElectraEnableWithdrawalsForkTime() } // GenesisForkVersion returns the fork version at genesis. diff --git a/chain/spec.go b/chain/spec.go index 14e3aa39d8..54f745e920 100644 --- a/chain/spec.go +++ b/chain/spec.go @@ -165,9 +165,9 @@ type WithdrawalsSpec interface { // This is to allow some extra time for any slashable offences by the validator to be detected and reported. MinValidatorWithdrawabilityDelay() math.Epoch - // WithdrawalsEnabled is a switch that can be used to freeze withdrawals in an emergency scenario. + // WithdrawalsDisabled is a switch that can be used to freeze withdrawals in an emergency scenario. // An exception is made for the EVM inflation withdrawal which is always active. - WithdrawalsEnabled(timestamp math.U64) bool + WithdrawalsDisabled(timestamp math.U64) bool } // Spec defines an interface for accessing chain-specific parameters. diff --git a/state-transition/core/state/statedb.go b/state-transition/core/state/statedb.go index ee50e7e365..e506a3aba9 100644 --- a/state-transition/core/state/statedb.go +++ b/state-transition/core/state/statedb.go @@ -122,7 +122,7 @@ func (s *StateDB) ExpectedWithdrawals(timestamp math.U64) (engineprimitives.With // 2. Validators whose balance is above MAX_EFFECTIVE_BALANCE will not be withdrawn till re-enabled. // 3. Validators who have initiated a full withdrawal will not be withdrawn till re-enabled. // 4. Validators who have been kicked out due to validator set cap will not be withdrawn till re-enabled. - if !s.cs.WithdrawalsEnabled(timestamp) { + if s.cs.WithdrawalsDisabled(timestamp) { return withdrawals, processedPartialWithdrawals, nil } From 32200e04b5346ce5c3075277871dcec72e19a1de Mon Sep 17 00:00:00 2001 From: Rez Date: Thu, 22 May 2025 06:10:37 +1000 Subject: [PATCH 20/21] merge conflict fix --- config/spec/defaults.go | 1 - 1 file changed, 1 deletion(-) diff --git a/config/spec/defaults.go b/config/spec/defaults.go index 0c85595dc9..8713b4c883 100644 --- a/config/spec/defaults.go +++ b/config/spec/defaults.go @@ -57,7 +57,6 @@ const ( // Fork-related values. defaultFarFutureTimestamp = 9999999999999999 // a future timestamp as not yet determined. - defaultElectraForkTime = defaultFarFutureTimestamp defaultElectraDisableWithdrawalsForkTime = defaultFarFutureTimestamp defaultElectraEnableWithdrawalsForkTime = defaultFarFutureTimestamp From 23daa35d79cff2909c846177c5febae46a71d815 Mon Sep 17 00:00:00 2001 From: Rez Date: Wed, 28 May 2025 10:30:36 +1000 Subject: [PATCH 21/21] Add spec enforcement of ordering --- chain/spec.go | 2 ++ chain/spec_test.go | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/chain/spec.go b/chain/spec.go index c0a0ffecc7..046341f29f 100644 --- a/chain/spec.go +++ b/chain/spec.go @@ -256,6 +256,8 @@ func (s spec) validate() error { s.Data.GenesisTime, s.Data.Deneb1ForkTime, s.Data.ElectraForkTime, + s.Data.ElectraDisableWithdrawalsForkTime, + s.Data.ElectraEnableWithdrawalsForkTime, } for i := 1; i < len(orderedForkTimes); i++ { prev, cur := orderedForkTimes[i-1], orderedForkTimes[i] diff --git a/chain/spec_test.go b/chain/spec_test.go index 4ceae5a42e..a16b08e454 100644 --- a/chain/spec_test.go +++ b/chain/spec_test.go @@ -42,6 +42,8 @@ func TestValidate_ForkOrder_Success(t *testing.T) { data.GenesisTime = 10 data.Deneb1ForkTime = 20 data.ElectraForkTime = 30 + data.ElectraDisableWithdrawalsForkTime = 40 + data.ElectraEnableWithdrawalsForkTime = 50 _, err := chain.NewSpec(data) require.NoError(t, err) @@ -53,6 +55,8 @@ func TestValidate_ForkOrder_GenesisAfterDeneb(t *testing.T) { data.GenesisTime = 50 data.Deneb1ForkTime = 20 data.ElectraForkTime = 60 + data.ElectraDisableWithdrawalsForkTime = 70 + data.ElectraEnableWithdrawalsForkTime = 80 _, err := chain.NewSpec(data) require.Error(t, err) @@ -65,6 +69,8 @@ func TestValidate_ForkOrder_DenebAfterElectra(t *testing.T) { data.GenesisTime = 10 data.Deneb1ForkTime = 80 data.ElectraForkTime = 40 + data.ElectraDisableWithdrawalsForkTime = 70 + data.ElectraEnableWithdrawalsForkTime = 80 _, err := chain.NewSpec(data) require.Error(t, err) @@ -77,6 +83,8 @@ func TestValidate_ForkOrder_AllForksAtGenesis(t *testing.T) { data.GenesisTime = 0 data.Deneb1ForkTime = 0 data.ElectraForkTime = 0 + data.ElectraDisableWithdrawalsForkTime = 0 + data.ElectraEnableWithdrawalsForkTime = 0 _, err := chain.NewSpec(data) require.NoError(t, err)