diff --git a/beacon-chain/core/blocks/exit.go b/beacon-chain/core/blocks/exit.go index 046b9bbc1a49..ad948f9c5278 100644 --- a/beacon-chain/core/blocks/exit.go +++ b/beacon-chain/core/blocks/exit.go @@ -40,6 +40,8 @@ var ValidatorCannotExitYetMsg = "validator has not been active long enough to ex // assert get_current_epoch(state) >= voluntary_exit.epoch // # Verify the validator has been active long enough // assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD +// # Only exit validator if it has no pending withdrawals in the queue +// assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in EIP7251] // # Verify signature // domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) // signing_root = compute_signing_root(voluntary_exit, domain) @@ -98,6 +100,8 @@ func ProcessVoluntaryExits( // assert get_current_epoch(state) >= voluntary_exit.epoch // # Verify the validator has been active long enough // assert get_current_epoch(state) >= validator.activation_epoch + SHARD_COMMITTEE_PERIOD +// # Only exit validator if it has no pending withdrawals in the queue +// assert get_pending_balance_to_withdraw(state, voluntary_exit.validator_index) == 0 # [New in EIP7251] // # Verify signature // domain = get_domain(state, DOMAIN_VOLUNTARY_EXIT, voluntary_exit.epoch) // signing_root = compute_signing_root(voluntary_exit, domain) @@ -128,7 +132,7 @@ func VerifyExitAndSignature( } exit := signed.Exit - if err := verifyExitConditions(validator, currentSlot, exit); err != nil { + if err := verifyExitConditions(state, validator, currentSlot, exit); err != nil { return err } domain, err := signing.Domain(fork, exit.Epoch, params.BeaconConfig().DomainVoluntaryExit, genesisRoot) @@ -163,7 +167,7 @@ func VerifyExitAndSignature( // assert bls.Verify(validator.pubkey, signing_root, signed_voluntary_exit.signature) // # Initiate exit // initiate_validator_exit(state, voluntary_exit.validator_index) -func verifyExitConditions(validator state.ReadOnlyValidator, currentSlot primitives.Slot, exit *ethpb.VoluntaryExit) error { +func verifyExitConditions(st state.ReadOnlyBeaconState, validator state.ReadOnlyValidator, currentSlot primitives.Slot, exit *ethpb.VoluntaryExit) error { currentEpoch := slots.ToEpoch(currentSlot) // Verify the validator is active. if !helpers.IsActiveValidatorUsingTrie(validator, currentEpoch) { @@ -187,5 +191,8 @@ func verifyExitConditions(validator state.ReadOnlyValidator, currentSlot primiti validator.ActivationEpoch()+params.BeaconConfig().ShardCommitteePeriod, ) } + if st.Version() >= version.EIP7251 { + panic("implement me") + } return nil } diff --git a/beacon-chain/core/eip7251/BUILD.bazel b/beacon-chain/core/eip7251/BUILD.bazel index b34c89cfe232..b7f6734f3f14 100644 --- a/beacon-chain/core/eip7251/BUILD.bazel +++ b/beacon-chain/core/eip7251/BUILD.bazel @@ -15,11 +15,13 @@ go_library( "//beacon-chain/core/epoch:go_default_library", "//beacon-chain/core/epoch/precompute:go_default_library", "//beacon-chain/core/helpers:go_default_library", + "//beacon-chain/core/signing:go_default_library", "//beacon-chain/core/time:go_default_library", "//beacon-chain/core/validators:go_default_library", "//beacon-chain/state:go_default_library", "//config/params:go_default_library", "//consensus-types/primitives:go_default_library", + "//crypto/bls:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//time/slots:go_default_library", "@com_github_pkg_errors//:go_default_library", diff --git a/beacon-chain/core/eip7251/transition.go b/beacon-chain/core/eip7251/transition.go index ffbce27feaab..56f0609546c1 100644 --- a/beacon-chain/core/eip7251/transition.go +++ b/beacon-chain/core/eip7251/transition.go @@ -9,8 +9,10 @@ import ( e "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epoch" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/epoch/precompute" "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/signing" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/crypto/bls" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/time/slots" "go.opencensus.io/trace" @@ -316,6 +318,11 @@ func ProcessConsolidations(ctx context.Context, st state.BeaconState, cs []*ethp return nil, errors.New("nil consolidations") } + domain, err := signing.ComputeDomain(params.BeaconConfig().DomainConsolidation, st.Fork().CurrentVersion, st.GenesisValidatorsRoot()) + if err != nil { + return nil, err + } + for _, c := range cs { if c == nil || c.Message == nil { return nil, errors.New("nil consolidation") @@ -362,7 +369,55 @@ func ProcessConsolidations(ctx context.Context, st state.BeaconState, cs []*ethp if currentEpoch < c.Message.Epoch { return nil, errors.New("consolidation is not valid yet") } + + if !helpers.HasExecutionWithdrawalCredentials(source) { + return nil, errors.New("source does not have execution withdrawal credentials") + } + if !helpers.HasExecutionWithdrawalCredentials(target) { + return nil, errors.New("target does not have execution withdrawal credentials") + } + if !helpers.IsSameWithdrawalCredentials(source, target) { + return nil, errors.New("source and target have different withdrawal credentials") + } + + sr, err := signing.ComputeSigningRoot(c.Message, domain) + if err != nil { + return nil, err + } + sourcePk, err := bls.PublicKeyFromBytes(source.PublicKey) + if err != nil { + return nil, errors.Wrap(err, "could not convert bytes to public key") + } + targetPk, err := bls.PublicKeyFromBytes(target.PublicKey) + if err != nil { + return nil, errors.Wrap(err, "could not convert bytes to public key") + } + sig, err := bls.SignatureFromBytes(c.Signature) + if err != nil { + return nil, errors.Wrap(err, "could not convert bytes to signature") + } + if !sig.FastAggregateVerify([]bls.PublicKey{sourcePk, targetPk}, sr) { + return nil, errors.New("consolidation signature verification failed") + } + + sEE, err := ComputeConsolidationEpochAndUpdateChurn(ctx, st, source.EffectiveBalance) + if err != nil { + return nil, err + } + source.ExitEpoch = sEE + source.WithdrawableEpoch = sEE + params.BeaconConfig().MinValidatorWithdrawabilityDelay + if err := st.UpdateValidatorAtIndex(c.Message.SourceIndex, source); err != nil { + return nil, err + } + if err := st.AppendPendingConsolidation(c.Message.ToPendingConsolidation()); err != nil { + return nil, err + } } - return nil, errors.New("not implemented") + return st, nil +} + +func ProcessExecutionLayerWithdrawRequests(ctx context.Context, st state.BeaconState, wds []*ethpb.ExecutionLayerWithdrawalRequest) (state.BeaconState, error) { + // TODO: Need to align with James He. This is a placeholder implementation. + panic("implement me") } diff --git a/beacon-chain/core/helpers/validators.go b/beacon-chain/core/helpers/validators.go index 18777cf92e88..eb2fe67dded2 100644 --- a/beacon-chain/core/helpers/validators.go +++ b/beacon-chain/core/helpers/validators.go @@ -534,3 +534,30 @@ func HasCompoundingWithdrawalCredential(v *ethpb.Validator) bool { func isCompoundingWithdrawalCredential(creds []byte) bool { return bytes.HasPrefix(creds, []byte{params.BeaconConfig().CompoundingWithdrawalPrefix}) } + +// HasExecutionWithdrawalCredentials checks if the validator has an execution withdrawal credential or compounding credential. +// New in EIP-7251: https://eips.ethereum.org/EIPS/eip-7251 +// +// Spec definition: +// +// def has_execution_withdrawal_credential(validator: Validator) -> bool: +// """ +// Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential. +// """ +// return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator) +func HasExecutionWithdrawalCredentials(v *ethpb.Validator) bool { + if v == nil { + return false + } + return HasCompoundingWithdrawalCredential(v) || HasETH1WithdrawalCredential(v) +} + +// IsSameWithdrawalCredentials returns true if both validators have the same withdrawal credentials. +// +// return a.withdrawal_credentials[12:] == b.withdrawal_credentials[12:] +func IsSameWithdrawalCredentials(a, b *ethpb.Validator) bool { + if len(a.WithdrawalCredentials) <= 12 || len(b.WithdrawalCredentials) <= 12 { + return false + } + return bytes.Equal(a.WithdrawalCredentials[12:], b.WithdrawalCredentials[12:]) +} diff --git a/beacon-chain/core/transition/transition_no_verify_sig.go b/beacon-chain/core/transition/transition_no_verify_sig.go index ed61dfea9464..fbf614025271 100644 --- a/beacon-chain/core/transition/transition_no_verify_sig.go +++ b/beacon-chain/core/transition/transition_no_verify_sig.go @@ -405,7 +405,7 @@ func VerifyBlobCommitmentCount(blk interfaces.ReadOnlyBeaconBlock) error { // for_ops(body.deposits, process_deposit) # [Modified in EIP7251] // for_ops(body.voluntary_exits, process_voluntary_exit) # [Modified in EIP7251] // for_ops(body.bls_to_execution_changes, process_bls_to_execution_change) -// for_ops(body.execution_payload.withdraw_requests, process_execution_layer_withdraw_request) # [New in EIP7251] +// for_ops(body.execution_payload.withdraw_requests, process_execution_layer_withdraw_request) # [New in EIP7002/EIP7251] // for_ops(body.consolidations, process_consolidation) # [New in EIP7251] func eip7251Operations( ctx context.Context, @@ -418,6 +418,11 @@ func eip7251Operations( return nil, err } + st, err = eip7251.ProcessExecutionLayerWithdrawRequests(ctx, st, nil) + if err != nil { + return nil, errors.Wrap(err, "could not process execution layer withdrawal requests") + } + cs, err := block.Body().Consolidations() if err != nil { return nil, errors.Wrap(err, "could not get consolidations") @@ -427,7 +432,6 @@ func eip7251Operations( return nil, errors.Wrap(err, "could not process consolidations") } - // TODO return st, nil } diff --git a/beacon-chain/state/interfaces.go b/beacon-chain/state/interfaces.go index d129df86d69d..b91cc1f2dbec 100644 --- a/beacon-chain/state/interfaces.go +++ b/beacon-chain/state/interfaces.go @@ -306,4 +306,5 @@ type WriteOnlyEIP7241 interface { SetDepositBalanceToConsume(gwei uint64) error SetPendingConsolidations(val []*ethpb.PendingConsolidation) error DequeuePartialWithdrawals(idx uint64) error + AppendPendingConsolidation(val *ethpb.PendingConsolidation) error } diff --git a/beacon-chain/state/state-native/getters_eip7251.go b/beacon-chain/state/state-native/getters_eip7251.go index 9b524fbd5876..6618d82f16b9 100644 --- a/beacon-chain/state/state-native/getters_eip7251.go +++ b/beacon-chain/state/state-native/getters_eip7251.go @@ -6,6 +6,8 @@ import ( "github.com/prysmaticlabs/prysm/v5/runtime/version" ) +// TODO: This file has getters and setters related to EIP-7251. Split it up! + func (b *BeaconState) EarliestConsolidationEpoch() (primitives.Epoch, error) { if b.version < version.EIP7251 { return 0, errNotSupported("EarliestConsolidationEpoch", b.version) @@ -111,3 +113,14 @@ func (b *BeaconState) SetPendingConsolidations(val []*ethpb.PendingConsolidation b.pendingConsolidations = val return nil } + +func (b *BeaconState) AppendPendingConsolidation(val *ethpb.PendingConsolidation) error { + if b.version < version.EIP7251 { + return errNotSupported("AppendPendingConsolidation", b.version) + } + b.lock.Lock() // TODO: Is this necessary? + defer b.lock.Unlock() + + b.pendingConsolidations = append(b.pendingConsolidations, val) + return nil +} diff --git a/beacon-chain/state/state-native/getters_withdrawal.go b/beacon-chain/state/state-native/getters_withdrawal.go index f953eaff2eb6..b1af5d21cd2e 100644 --- a/beacon-chain/state/state-native/getters_withdrawal.go +++ b/beacon-chain/state/state-native/getters_withdrawal.go @@ -205,7 +205,7 @@ func isFullyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoch pr // EIP-7251 logic if epoch >= params.BeaconConfig().EIP7251ForkEpoch { - return HasExecutionWithdrawalCredentials(val) && val.WithdrawableEpoch <= epoch + return helpers.HasExecutionWithdrawalCredentials(val) && val.WithdrawableEpoch <= epoch } return helpers.HasETH1WithdrawalCredential(val) && val.WithdrawableEpoch <= epoch @@ -238,7 +238,7 @@ func isPartiallyWithdrawableValidator(val *ethpb.Validator, balance uint64, epoc hasMaxBalance := val.EffectiveBalance == maxEB hasExcessBalance := balance > maxEB - return HasExecutionWithdrawalCredentials(val) && + return helpers.HasExecutionWithdrawalCredentials(val) && hasMaxBalance && hasExcessBalance } diff --git a/beacon-chain/state/state-native/validator_withdrawal_credentials.go b/beacon-chain/state/state-native/validator_withdrawal_credentials.go index 43333fd724e2..36654dbb5d68 100644 --- a/beacon-chain/state/state-native/validator_withdrawal_credentials.go +++ b/beacon-chain/state/state-native/validator_withdrawal_credentials.go @@ -3,10 +3,8 @@ package state_native import ( "bytes" - "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/params" - ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ) // TODO: Move all of this to core/helpers. @@ -37,20 +35,3 @@ func HasCompoundingWithdrawalCredentialUsingTrie(v state.ReadOnlyValidator) bool func isCompoundingWithdrawalCredential(creds []byte) bool { return bytes.HasPrefix(creds, []byte{params.BeaconConfig().CompoundingWithdrawalPrefix}) } - -// HasExecutionWithdrawalCredentials checks if the validator has an execution withdrawal credential or compounding credential. -// New in EIP-7251: https://eips.ethereum.org/EIPS/eip-7251 -// -// Spec definition: -// -// def has_execution_withdrawal_credential(validator: Validator) -> bool: -// """ -// Check if ``validator`` has a 0x01 or 0x02 prefixed withdrawal credential. -// """ -// return has_compounding_withdrawal_credential(validator) or has_eth1_withdrawal_credential(validator) -func HasExecutionWithdrawalCredentials(v *ethpb.Validator) bool { - if v == nil { - return false - } - return helpers.HasCompoundingWithdrawalCredential(v) || helpers.HasETH1WithdrawalCredential(v) -} diff --git a/beacon-chain/state/state-native/validator_withdrawal_credentials_test.go b/beacon-chain/state/state-native/validator_withdrawal_credentials_test.go index 9729ab64af3c..0c6bee65306e 100644 --- a/beacon-chain/state/state-native/validator_withdrawal_credentials_test.go +++ b/beacon-chain/state/state-native/validator_withdrawal_credentials_test.go @@ -3,7 +3,7 @@ package state_native_test import ( "testing" - state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" @@ -11,6 +11,7 @@ import ( ) // TODO: Test WithTrie version. +// TODO: Move this to helpers. func TestHasExecutionWithdrawalCredentials(t *testing.T) { tests := []struct { name string @@ -30,7 +31,7 @@ func TestHasExecutionWithdrawalCredentials(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - assert.Equal(t, tt.want, state_native.HasExecutionWithdrawalCredentials(tt.validator)) + assert.Equal(t, tt.want, helpers.HasExecutionWithdrawalCredentials(tt.validator)) }) } } diff --git a/proto/prysm/v1alpha1/BUILD.bazel b/proto/prysm/v1alpha1/BUILD.bazel index f916baffad02..f6daece62cef 100644 --- a/proto/prysm/v1alpha1/BUILD.bazel +++ b/proto/prysm/v1alpha1/BUILD.bazel @@ -195,6 +195,7 @@ go_library( name = "go_default_library", srcs = [ "cloners.go", + "eip_7251.go", "sync_committee_mainnet.go", "sync_committee_minimal.go", # keep ":ssz_generated_files", # keep diff --git a/proto/prysm/v1alpha1/eip_7251.go b/proto/prysm/v1alpha1/eip_7251.go new file mode 100644 index 000000000000..cfaec4267490 --- /dev/null +++ b/proto/prysm/v1alpha1/eip_7251.go @@ -0,0 +1,12 @@ +package eth + +func (c *Consolidation) ToPendingConsolidation() *PendingConsolidation { + if c == nil { + return nil + } + p := &PendingConsolidation{ + SourceIndex: c.SourceIndex, + TargetIndex: c.TargetIndex, + } + return p +} diff --git a/testing/spectest/shared/deneb/operations/withdrawals.go b/testing/spectest/shared/deneb/operations/withdrawals.go index d5637e8069b9..d31f3da0aea8 100644 --- a/testing/spectest/shared/deneb/operations/withdrawals.go +++ b/testing/spectest/shared/deneb/operations/withdrawals.go @@ -23,10 +23,6 @@ func RunWithdrawalsTest(t *testing.T, config string) { testFolders, testsFolderPath := utils.TestFolders(t, config, "deneb", "operations/withdrawals/pyspec_tests") for _, folder := range testFolders { t.Run(folder.Name(), func(t *testing.T) { - if folder.Name() == "success_excess_balance_but_no_max_effective_balance" { - t.Skip("Skipping broken spec test for EIP-7251") // TODO: Unskip - // This spectest is broken by changes in is_partially_withdrawable_validator. - } folderPath := path.Join(testsFolderPath, folder.Name()) payloadFile, err := util.BazelFileBytes(folderPath, "execution_payload.ssz_snappy") require.NoError(t, err)