From 9b2934f1f6e6256cc161874cf8b315e9a78714aa Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Mon, 6 May 2024 13:04:33 -0500 Subject: [PATCH] Electra: BeaconState implementation (#13919) * Electra: Beacon State * Electra: Beacon state fixes from PR 13919 * Add missing tests - part 1 * Split eip_7251_root.go into different files and reuse/share code with historical state summaries root. It's identical! * Add missing tests - part 2 * deposit receipts start index getters and setters (#13947) * adding in getters and setters for deposit receipts start index * adding tests * gaz * Add missing tests - part 3 of 3 Update the electra withdrawal example with a ssz state containing pending partial withdrawals * add tests for beacon-chain/state/state-native/getters_balance_deposits.go * Add electra field to testing/util/block.go execution payload * godoc commentary on public methods * Fix failing test * Add balances index out of bounds check and relevant tests. * Revert switch case electra * Instead of copying spectest data into testdata, use the spectest dependency * Deepsource fixes * Address @rkapka PR feedback * s/MaxPendingPartialsPerWithdrawalSweep/MaxPendingPartialsPerWithdrawalsSweep/ * Use multivalue slice compatible accessors for validator and balance in ActiveBalanceAtIndex * More @rkapka feedback. What a great reviewer! * More tests for branching logic in ExitEpochAndUpdateChurn * fix build --------- Co-authored-by: james-prysm <90280386+james-prysm@users.noreply.github.com> --- beacon-chain/blockchain/execution_engine.go | 4 +- beacon-chain/core/blocks/exit_test.go | 2 - beacon-chain/core/blocks/withdrawals.go | 2 +- beacon-chain/core/helpers/BUILD.bazel | 1 + beacon-chain/core/helpers/validator_churn.go | 15 +- .../core/helpers/validator_churn_test.go | 27 +- beacon-chain/rpc/eth/builder/handlers.go | 2 +- beacon-chain/rpc/eth/config/handlers_test.go | 2 +- beacon-chain/rpc/eth/events/events.go | 4 +- .../validator/proposer_execution_payload.go | 4 +- beacon-chain/state/BUILD.bazel | 1 + beacon-chain/state/interfaces.go | 48 +- beacon-chain/state/state-native/BUILD.bazel | 35 +- .../state-native/beacon_state_mainnet.go | 35 + .../state-native/beacon_state_minimal.go | 35 + .../state-native/getters_balance_deposits.go | 39 + .../getters_balance_deposits_test.go | 50 ++ .../state-native/getters_consolidation.go | 64 ++ .../getters_consolidation_test.go | 118 +++ .../state-native/getters_deposit_receipts.go | 16 + .../getters_deposit_receipts_test.go | 26 + .../state-native/getters_payload_header.go | 20 +- .../state/state-native/getters_state.go | 92 +- .../state/state-native/getters_validator.go | 57 ++ .../state-native/getters_validator_test.go | 79 ++ .../state/state-native/getters_withdrawal.go | 139 ++- .../state-native/getters_withdrawal_test.go | 843 +++++++----------- beacon-chain/state/state-native/hasher.go | 61 ++ .../state-native/setters_balance_deposits.go | 67 ++ .../setters_balance_deposits_test.go | 61 ++ .../state/state-native/setters_churn.go | 80 ++ .../state/state-native/setters_churn_test.go | 200 +++++ .../state-native/setters_consolidation.go | 84 ++ .../setters_consolidation_test.go | 76 ++ .../state-native/setters_deposit_receipts.go | 21 + .../setters_deposit_receipts_test.go | 27 + .../state-native/setters_payload_header.go | 42 + .../setters_payload_header_test.go | 107 +++ .../state/state-native/setters_withdrawal.go | 57 ++ .../state-native/setters_withdrawal_test.go | 66 ++ .../state/state-native/spec_parameters.go | 4 +- beacon-chain/state/state-native/state_test.go | 10 + beacon-chain/state/state-native/state_trie.go | 199 ++++- .../state/state-native/state_trie_test.go | 96 ++ .../state/state-native/types/types.go | 64 +- beacon-chain/state/stateutil/BUILD.bazel | 4 + .../stateutil/historical_summaries_root.go | 34 +- .../pending_balance_deposits_root.go | 10 + .../stateutil/pending_consolidations_root.go | 10 + .../pending_partial_withdrawals_root.go | 10 + beacon-chain/state/stateutil/slice_root.go | 41 + beacon-chain/state/stateutil/trie_helpers.go | 4 +- config/params/config.go | 30 +- config/params/interop.go | 1 + config/params/loader.go | 2 + config/params/mainnet_config.go | 33 +- config/params/minimal_config.go | 2 +- config/params/testdata/e2e_config.yaml | 3 + config/params/testnet_e2e_config.go | 7 + config/params/testnet_holesky_config.go | 4 + config/params/testnet_sepolia_config.go | 4 + encoding/ssz/detect/configfork.go | 16 + encoding/ssz/detect/configfork_test.go | 69 +- math/BUILD.bazel | 5 +- math/math_helper.go | 46 + proto/prysm/v1alpha1/BUILD.bazel | 3 + proto/prysm/v1alpha1/beacon_state.pb.go | 178 ++-- proto/prysm/v1alpha1/beacon_state.proto | 6 +- proto/prysm/v1alpha1/generated.ssz.go | 21 +- .../shared/electra/ssz_static/BUILD.bazel | 2 + .../shared/electra/ssz_static/ssz_static.go | 28 +- testing/util/BUILD.bazel | 1 + testing/util/block.go | 143 +++ testing/util/deneb.go | 17 - testing/util/electra.go | 25 + testing/util/merge.go | 9 + testing/util/state.go | 70 ++ testing/util/state_test.go | 18 + 78 files changed, 3100 insertions(+), 838 deletions(-) create mode 100644 beacon-chain/state/state-native/getters_balance_deposits.go create mode 100644 beacon-chain/state/state-native/getters_balance_deposits_test.go create mode 100644 beacon-chain/state/state-native/getters_consolidation.go create mode 100644 beacon-chain/state/state-native/getters_consolidation_test.go create mode 100644 beacon-chain/state/state-native/getters_deposit_receipts.go create mode 100644 beacon-chain/state/state-native/getters_deposit_receipts_test.go create mode 100644 beacon-chain/state/state-native/setters_balance_deposits.go create mode 100644 beacon-chain/state/state-native/setters_balance_deposits_test.go create mode 100644 beacon-chain/state/state-native/setters_churn.go create mode 100644 beacon-chain/state/state-native/setters_churn_test.go create mode 100644 beacon-chain/state/state-native/setters_consolidation.go create mode 100644 beacon-chain/state/state-native/setters_consolidation_test.go create mode 100644 beacon-chain/state/state-native/setters_deposit_receipts.go create mode 100644 beacon-chain/state/state-native/setters_deposit_receipts_test.go create mode 100644 beacon-chain/state/state-native/setters_payload_header_test.go create mode 100644 beacon-chain/state/stateutil/pending_balance_deposits_root.go create mode 100644 beacon-chain/state/stateutil/pending_consolidations_root.go create mode 100644 beacon-chain/state/stateutil/pending_partial_withdrawals_root.go create mode 100644 beacon-chain/state/stateutil/slice_root.go create mode 100644 testing/util/electra.go diff --git a/beacon-chain/blockchain/execution_engine.go b/beacon-chain/blockchain/execution_engine.go index 3d4e72442c..3ef06e3f44 100644 --- a/beacon-chain/blockchain/execution_engine.go +++ b/beacon-chain/blockchain/execution_engine.go @@ -325,7 +325,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, var attr payloadattribute.Attributer switch st.Version() { case version.Deneb: - withdrawals, err := st.ExpectedWithdrawals() + withdrawals, _, err := st.ExpectedWithdrawals() if err != nil { log.WithError(err).Error("Could not get expected withdrawals to get payload attribute") return emptyAttri @@ -342,7 +342,7 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, return emptyAttri } case version.Capella: - withdrawals, err := st.ExpectedWithdrawals() + withdrawals, _, err := st.ExpectedWithdrawals() if err != nil { log.WithError(err).Error("Could not get expected withdrawals to get payload attribute") return emptyAttri diff --git a/beacon-chain/core/blocks/exit_test.go b/beacon-chain/core/blocks/exit_test.go index 7f589685fc..d40f35f7a2 100644 --- a/beacon-chain/core/blocks/exit_test.go +++ b/beacon-chain/core/blocks/exit_test.go @@ -135,8 +135,6 @@ func TestProcessVoluntaryExits_AppliesCorrectStatus(t *testing.T) { } func TestVerifyExitAndSignature(t *testing.T) { - undo := util.HackDenebMaxuint(t) - defer undo() denebSlot, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) require.NoError(t, err) tests := []struct { diff --git a/beacon-chain/core/blocks/withdrawals.go b/beacon-chain/core/blocks/withdrawals.go index a9672c1204..5b234d1614 100644 --- a/beacon-chain/core/blocks/withdrawals.go +++ b/beacon-chain/core/blocks/withdrawals.go @@ -145,7 +145,7 @@ func ValidateBLSToExecutionChange(st state.ReadOnlyBeaconState, signed *ethpb.Si // next_validator_index = ValidatorIndex(next_index % len(state.validators)) // state.next_withdrawal_validator_index = next_validator_index func ProcessWithdrawals(st state.BeaconState, executionData interfaces.ExecutionData) (state.BeaconState, error) { - expectedWithdrawals, err := st.ExpectedWithdrawals() + expectedWithdrawals, _, err := st.ExpectedWithdrawals() if err != nil { return nil, errors.Wrap(err, "could not get expected withdrawals") } diff --git a/beacon-chain/core/helpers/BUILD.bazel b/beacon-chain/core/helpers/BUILD.bazel index 1912d6c299..1b5709add1 100644 --- a/beacon-chain/core/helpers/BUILD.bazel +++ b/beacon-chain/core/helpers/BUILD.bazel @@ -78,6 +78,7 @@ go_test( "//container/slice:go_default_library", "//crypto/hash:go_default_library", "//encoding/bytesutil:go_default_library", + "//math:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//testing/assert:go_default_library", "//testing/require:go_default_library", diff --git a/beacon-chain/core/helpers/validator_churn.go b/beacon-chain/core/helpers/validator_churn.go index b36fd81569..04409f0811 100644 --- a/beacon-chain/core/helpers/validator_churn.go +++ b/beacon-chain/core/helpers/validator_churn.go @@ -2,6 +2,7 @@ package helpers import ( "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/math" ) // BalanceChurnLimit for the current active balance, in gwei. @@ -18,12 +19,12 @@ import ( // get_total_active_balance(state) // CHURN_LIMIT_QUOTIENT // ) // return churn - churn % EFFECTIVE_BALANCE_INCREMENT -func BalanceChurnLimit(activeBalanceGwei uint64) uint64 { +func BalanceChurnLimit(activeBalance math.Gwei) math.Gwei { churn := max( params.BeaconConfig().MinPerEpochChurnLimitElectra, - (activeBalanceGwei / params.BeaconConfig().ChurnLimitQuotient), + (uint64(activeBalance) / params.BeaconConfig().ChurnLimitQuotient), ) - return churn - churn%params.BeaconConfig().EffectiveBalanceIncrement + return math.Gwei(churn - churn%params.BeaconConfig().EffectiveBalanceIncrement) } // ActivationExitChurnLimit for the current active balance, in gwei. @@ -36,8 +37,8 @@ func BalanceChurnLimit(activeBalanceGwei uint64) uint64 { // Return the churn limit for the current epoch dedicated to activations and exits. // """ // return min(MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT, get_balance_churn_limit(state)) -func ActivationExitChurnLimit(activeBalanceGwei uint64) uint64 { - return min(params.BeaconConfig().MaxPerEpochActivationExitChurnLimit, BalanceChurnLimit(activeBalanceGwei)) +func ActivationExitChurnLimit(activeBalance math.Gwei) math.Gwei { + return min(math.Gwei(params.BeaconConfig().MaxPerEpochActivationExitChurnLimit), BalanceChurnLimit(activeBalance)) } // ConsolidationChurnLimit for the current active balance, in gwei. @@ -47,6 +48,6 @@ func ActivationExitChurnLimit(activeBalanceGwei uint64) uint64 { // // def get_consolidation_churn_limit(state: BeaconState) -> Gwei: // return get_balance_churn_limit(state) - get_activation_exit_churn_limit(state) -func ConsolidationChurnLimit(activeBalanceGwei uint64) uint64 { - return BalanceChurnLimit(activeBalanceGwei) - ActivationExitChurnLimit(activeBalanceGwei) +func ConsolidationChurnLimit(activeBalance math.Gwei) math.Gwei { + return BalanceChurnLimit(activeBalance) - ActivationExitChurnLimit(activeBalance) } diff --git a/beacon-chain/core/helpers/validator_churn_test.go b/beacon-chain/core/helpers/validator_churn_test.go index 556505291a..6a83b4f016 100644 --- a/beacon-chain/core/helpers/validator_churn_test.go +++ b/beacon-chain/core/helpers/validator_churn_test.go @@ -5,29 +5,30 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/math" "github.com/prysmaticlabs/prysm/v5/testing/assert" ) func TestBalanceChurnLimit(t *testing.T) { tests := []struct { name string - activeBalance uint64 - expected uint64 + activeBalance math.Gwei + expected math.Gwei }{ { name: "less than MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA", activeBalance: 111, - expected: params.BeaconConfig().MinPerEpochChurnLimitElectra, + expected: math.Gwei(params.BeaconConfig().MinPerEpochChurnLimitElectra), }, { name: "modulo EFFECTIVE_BALANCE_INCREMENT", - activeBalance: 111 + params.BeaconConfig().MinPerEpochChurnLimitElectra*params.BeaconConfig().ChurnLimitQuotient, - expected: params.BeaconConfig().MinPerEpochChurnLimitElectra, + activeBalance: math.Gwei(111 + params.BeaconConfig().MinPerEpochChurnLimitElectra*params.BeaconConfig().ChurnLimitQuotient), + expected: math.Gwei(params.BeaconConfig().MinPerEpochChurnLimitElectra), }, { name: "more than MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA", - activeBalance: 2000 * params.BeaconConfig().EffectiveBalanceIncrement * params.BeaconConfig().ChurnLimitQuotient, - expected: 2000 * params.BeaconConfig().EffectiveBalanceIncrement, + activeBalance: math.Gwei(2000 * params.BeaconConfig().EffectiveBalanceIncrement * params.BeaconConfig().ChurnLimitQuotient), + expected: math.Gwei(2000 * params.BeaconConfig().EffectiveBalanceIncrement), }, } @@ -41,18 +42,18 @@ func TestBalanceChurnLimit(t *testing.T) { func TestActivationExitChurnLimit(t *testing.T) { tests := []struct { name string - activeBalance uint64 - expected uint64 + activeBalance math.Gwei + expected math.Gwei }{ { name: "less than MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT", activeBalance: 1, - expected: params.BeaconConfig().MinPerEpochChurnLimitElectra, + expected: math.Gwei(params.BeaconConfig().MinPerEpochChurnLimitElectra), }, { name: "more than MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT", - activeBalance: 2000 * params.BeaconConfig().EffectiveBalanceIncrement * params.BeaconConfig().ChurnLimitQuotient, - expected: params.BeaconConfig().MaxPerEpochActivationExitChurnLimit, + activeBalance: math.Gwei(2000 * params.BeaconConfig().EffectiveBalanceIncrement * params.BeaconConfig().ChurnLimitQuotient), + expected: math.Gwei(params.BeaconConfig().MaxPerEpochActivationExitChurnLimit), }, } @@ -66,6 +67,6 @@ func TestActivationExitChurnLimit(t *testing.T) { // FuzzConsolidationChurnLimit exercises BalanceChurnLimit and ActivationExitChurnLimit func FuzzConsolidationChurnLimit(f *testing.F) { f.Fuzz(func(t *testing.T, activeBalance uint64) { - helpers.ConsolidationChurnLimit(activeBalance) + helpers.ConsolidationChurnLimit(math.Gwei(activeBalance)) }) } diff --git a/beacon-chain/rpc/eth/builder/handlers.go b/beacon-chain/rpc/eth/builder/handlers.go index 0d9fc4b136..d175d79852 100644 --- a/beacon-chain/rpc/eth/builder/handlers.go +++ b/beacon-chain/rpc/eth/builder/handlers.go @@ -96,7 +96,7 @@ func (s *Server) ExpectedWithdrawals(w http.ResponseWriter, r *http.Request) { }) return } - withdrawals, err := st.ExpectedWithdrawals() + withdrawals, _, err := st.ExpectedWithdrawals() if err != nil { httputil.WriteError(w, &httputil.DefaultJsonError{ Message: "could not get expected withdrawals", diff --git a/beacon-chain/rpc/eth/config/handlers_test.go b/beacon-chain/rpc/eth/config/handlers_test.go index 813cc7de5a..c2f123d4b9 100644 --- a/beacon-chain/rpc/eth/config/handlers_test.go +++ b/beacon-chain/rpc/eth/config/handlers_test.go @@ -140,7 +140,7 @@ func TestGetSpec(t *testing.T) { config.PendingPartialWithdrawalsLimit = 80 config.MinActivationBalance = 81 config.PendingBalanceDepositLimit = 82 - config.MaxPendingPartialsPerWithdrawalSweep = 83 + config.MaxPendingPartialsPerWithdrawalsSweep = 83 config.PendingConsolidationsLimit = 84 config.MaxPartialWithdrawalsPerPayload = 85 config.FullExitRequestAmount = 86 diff --git a/beacon-chain/rpc/eth/events/events.go b/beacon-chain/rpc/eth/events/events.go index 776dddf198..942b02545a 100644 --- a/beacon-chain/rpc/eth/events/events.go +++ b/beacon-chain/rpc/eth/events/events.go @@ -449,7 +449,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite SuggestedFeeRecipient: hexutil.Encode(headPayload.FeeRecipient()), } case version.Capella: - withdrawals, err := headState.ExpectedWithdrawals() + withdrawals, _, err := headState.ExpectedWithdrawals() if err != nil { return write(w, flusher, "Could not get head state expected withdrawals: "+err.Error()) } @@ -460,7 +460,7 @@ func (s *Server) sendPayloadAttributes(ctx context.Context, w http.ResponseWrite Withdrawals: structs.WithdrawalsFromConsensus(withdrawals), } case version.Deneb: - withdrawals, err := headState.ExpectedWithdrawals() + withdrawals, _, err := headState.ExpectedWithdrawals() if err != nil { return write(w, flusher, "Could not get head state expected withdrawals: "+err.Error()) } diff --git a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go index 70aa3831c2..4605901cff 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go @@ -128,7 +128,7 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe var attr payloadattribute.Attributer switch st.Version() { case version.Deneb: - withdrawals, err := st.ExpectedWithdrawals() + withdrawals, _, err := st.ExpectedWithdrawals() if err != nil { return nil, false, err } @@ -143,7 +143,7 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe return nil, false, err } case version.Capella: - withdrawals, err := st.ExpectedWithdrawals() + withdrawals, _, err := st.ExpectedWithdrawals() if err != nil { return nil, false, err } diff --git a/beacon-chain/state/BUILD.bazel b/beacon-chain/state/BUILD.bazel index fa1ee84cff..95af0712b8 100644 --- a/beacon-chain/state/BUILD.bazel +++ b/beacon-chain/state/BUILD.bazel @@ -13,6 +13,7 @@ go_library( "//config/fieldparams:go_default_library", "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", + "//math:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "@com_github_prometheus_client_golang//prometheus:go_default_library", diff --git a/beacon-chain/state/interfaces.go b/beacon-chain/state/interfaces.go index ff827758ae..8dc8876f2b 100644 --- a/beacon-chain/state/interfaces.go +++ b/beacon-chain/state/interfaces.go @@ -11,6 +11,7 @@ import ( fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ) @@ -56,6 +57,8 @@ type ReadOnlyBeaconState interface { ReadOnlyParticipation ReadOnlyInactivity ReadOnlySyncCommittee + ReadOnlyDeposits + ReadOnlyConsolidations ToProtoUnsafe() interface{} ToProto() interface{} GenesisTime() uint64 @@ -87,6 +90,9 @@ type WriteOnlyBeaconState interface { WriteOnlyParticipation WriteOnlyInactivity WriteOnlySyncCommittee + WriteOnlyConsolidations + WriteOnlyWithdrawals + WriteOnlyDeposits SetGenesisTime(val uint64) error SetGenesisValidatorsRoot(val []byte) error SetSlot(val primitives.Slot) error @@ -98,8 +104,6 @@ type WriteOnlyBeaconState interface { AppendHistoricalRoots(root [32]byte) error AppendHistoricalSummaries(*ethpb.HistoricalSummary) error SetLatestExecutionPayloadHeader(payload interfaces.ExecutionData) error - SetNextWithdrawalIndex(i uint64) error - SetNextWithdrawalValidatorIndex(i primitives.ValidatorIndex) error SaveValidatorIndices() } @@ -134,6 +138,7 @@ type ReadOnlyBalances interface { Balances() []uint64 BalanceAtIndex(idx primitives.ValidatorIndex) (uint64, error) BalancesLength() int + ActiveBalanceAtIndex(idx primitives.ValidatorIndex) (uint64, error) } // ReadOnlyCheckpoint defines a struct which only has read access to checkpoint methods. @@ -182,9 +187,11 @@ type ReadOnlyAttestations interface { // ReadOnlyWithdrawals defines a struct which only has read access to withdrawal methods. type ReadOnlyWithdrawals interface { - ExpectedWithdrawals() ([]*enginev1.Withdrawal, error) + ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, error) NextWithdrawalValidatorIndex() (primitives.ValidatorIndex, error) NextWithdrawalIndex() (uint64, error) + PendingBalanceToWithdraw(idx primitives.ValidatorIndex) (uint64, error) + NumPendingPartialWithdrawals() (uint64, error) } // ReadOnlyParticipation defines a struct which only has read access to participation methods. @@ -204,6 +211,19 @@ type ReadOnlySyncCommittee interface { NextSyncCommittee() (*ethpb.SyncCommittee, error) } +type ReadOnlyDeposits interface { + DepositBalanceToConsume() (math.Gwei, error) + DepositReceiptsStartIndex() (uint64, error) + PendingBalanceDeposits() ([]*ethpb.PendingBalanceDeposit, error) +} + +type ReadOnlyConsolidations interface { + ConsolidationBalanceToConsume() (math.Gwei, error) + EarliestConsolidationEpoch() (primitives.Epoch, error) + PendingConsolidations() ([]*ethpb.PendingConsolidation, error) + NumPendingConsolidations() (uint64, error) +} + // WriteOnlyBlockRoots defines a struct which only has write access to block roots methods. type WriteOnlyBlockRoots interface { SetBlockRoots(val [][]byte) error @@ -222,6 +242,7 @@ type WriteOnlyEth1Data interface { SetEth1DataVotes(val []*ethpb.Eth1Data) error AppendEth1DataVotes(val *ethpb.Eth1Data) error SetEth1DepositIndex(val uint64) error + ExitEpochAndUpdateChurn(exitBalance math.Gwei) (primitives.Epoch, error) } // WriteOnlyValidators defines a struct which only has write access to validators methods. @@ -283,3 +304,24 @@ type WriteOnlySyncCommittee interface { SetCurrentSyncCommittee(val *ethpb.SyncCommittee) error SetNextSyncCommittee(val *ethpb.SyncCommittee) error } + +type WriteOnlyWithdrawals interface { + AppendPendingPartialWithdrawal(ppw *ethpb.PendingPartialWithdrawal) error + DequeuePartialWithdrawals(num uint64) error + SetNextWithdrawalIndex(i uint64) error + SetNextWithdrawalValidatorIndex(i primitives.ValidatorIndex) error +} + +type WriteOnlyConsolidations interface { + AppendPendingConsolidation(val *ethpb.PendingConsolidation) error + SetConsolidationBalanceToConsume(math.Gwei) error + SetEarliestConsolidationEpoch(epoch primitives.Epoch) error + SetPendingConsolidations(val []*ethpb.PendingConsolidation) error +} + +type WriteOnlyDeposits interface { + AppendPendingBalanceDeposit(index primitives.ValidatorIndex, amount uint64) error + SetDepositReceiptsStartIndex(index uint64) error + SetPendingBalanceDeposits(val []*ethpb.PendingBalanceDeposit) error + SetDepositBalanceToConsume(math.Gwei) error +} diff --git a/beacon-chain/state/state-native/BUILD.bazel b/beacon-chain/state/state-native/BUILD.bazel index 4cfa724f4b..1d16f099ee 100644 --- a/beacon-chain/state/state-native/BUILD.bazel +++ b/beacon-chain/state/state-native/BUILD.bazel @@ -3,11 +3,16 @@ load("@prysm//tools/go:def.bzl", "go_library", "go_test") go_library( name = "go_default_library", srcs = [ + "beacon_state_mainnet.go", + "beacon_state_minimal.go", # keep "doc.go", "error.go", "getters_attestation.go", + "getters_balance_deposits.go", "getters_block.go", "getters_checkpoint.go", + "getters_consolidation.go", + "getters_deposit_receipts.go", "getters_eth1.go", "getters_misc.go", "getters_participation.go", @@ -22,8 +27,12 @@ go_library( "proofs.go", "readonly_validator.go", "setters_attestation.go", + "setters_balance_deposits.go", "setters_block.go", "setters_checkpoint.go", + "setters_churn.go", + "setters_consolidation.go", + "setters_deposit_receipts.go", "setters_eth1.go", "setters_misc.go", "setters_participation.go", @@ -38,13 +47,11 @@ go_library( "state_trie.go", "types.go", "validator_index_cache.go", - ] + select({ - "//config:mainnet": ["beacon_state_mainnet.go"], - "//config:minimal": ["beacon_state_minimal.go"], - }), + ], importpath = "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native", visibility = ["//visibility:public"], deps = [ + "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/time:go_default_library", "//beacon-chain/state:go_default_library", "//beacon-chain/state/fieldtrie:go_default_library", @@ -82,8 +89,11 @@ go_test( name = "go_default_test", srcs = [ "getters_attestation_test.go", + "getters_balance_deposits_test.go", "getters_block_test.go", "getters_checkpoint_test.go", + "getters_consolidation_test.go", + "getters_deposit_receipts_test.go", "getters_participation_test.go", "getters_test.go", "getters_validator_test.go", @@ -94,9 +104,14 @@ go_test( "readonly_validator_test.go", "references_test.go", "setters_attestation_test.go", + "setters_balance_deposits_test.go", + "setters_churn_test.go", + "setters_consolidation_test.go", + "setters_deposit_receipts_test.go", "setters_eth1_test.go", "setters_misc_test.go", "setters_participation_test.go", + "setters_payload_header_test.go", "setters_validator_test.go", "setters_withdrawal_test.go", "state_fuzz_test.go", @@ -105,9 +120,12 @@ go_test( "types_test.go", "validator_index_cache_test.go", ], - data = glob(["testdata/**"]), + data = glob(["testdata/**"]) + [ + "@consensus_spec_tests_mainnet//:test_data", + ], embed = [":go_default_library"], deps = [ + "//beacon-chain/core/helpers:go_default_library", "//beacon-chain/core/transition:go_default_library", "//beacon-chain/state:go_default_library", "//beacon-chain/state/state-native/types:go_default_library", @@ -116,11 +134,14 @@ go_test( "//config/features:go_default_library", "//config/fieldparams:go_default_library", "//config/params:go_default_library", + "//consensus-types:go_default_library", "//consensus-types/blocks:go_default_library", + "//consensus-types/interfaces:go_default_library", "//consensus-types/primitives:go_default_library", "//container/trie:go_default_library", "//crypto/rand:go_default_library", "//encoding/bytesutil:go_default_library", + "//math:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", "//runtime/interop:go_default_library", @@ -128,9 +149,13 @@ go_test( "//testing/assert:go_default_library", "//testing/require:go_default_library", "//testing/util:go_default_library", + "//time/slots:go_default_library", "@com_github_ethereum_go_ethereum//common/hexutil:go_default_library", + "@com_github_golang_snappy//:go_default_library", + "@com_github_google_go_cmp//cmp:go_default_library", "@com_github_prysmaticlabs_go_bitfield//:go_default_library", "@com_github_sirupsen_logrus//:go_default_library", "@org_golang_google_protobuf//proto:go_default_library", + "@org_golang_google_protobuf//testing/protocmp:go_default_library", ], ) diff --git a/beacon-chain/state/state-native/beacon_state_mainnet.go b/beacon-chain/state/state-native/beacon_state_mainnet.go index dc46a233d3..4ceb16a655 100644 --- a/beacon-chain/state/state-native/beacon_state_mainnet.go +++ b/beacon-chain/state/state-native/beacon_state_mainnet.go @@ -13,6 +13,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil" "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ) @@ -57,9 +58,21 @@ type BeaconState struct { latestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader latestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella latestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb + latestExecutionPayloadHeaderElectra *enginev1.ExecutionPayloadHeaderElectra nextWithdrawalIndex uint64 nextWithdrawalValidatorIndex primitives.ValidatorIndex + // Electra fields + depositReceiptsStartIndex uint64 + depositBalanceToConsume math.Gwei + exitBalanceToConsume math.Gwei + earliestExitEpoch primitives.Epoch + consolidationBalanceToConsume math.Gwei + earliestConsolidationEpoch primitives.Epoch + pendingBalanceDeposits []*ethpb.PendingBalanceDeposit // pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT] + pendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // pending_partial_withdrawals: List[PartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pendingConsolidations []*ethpb.PendingConsolidation // pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + id uint64 lock sync.RWMutex dirtyFields map[types.FieldIndex]bool @@ -103,8 +116,19 @@ type beaconStateMarshalable struct { NextSyncCommittee *ethpb.SyncCommittee `json:"next_sync_committee" yaml:"next_sync_committee"` LatestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader `json:"latest_execution_payload_header" yaml:"latest_execution_payload_header"` LatestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella `json:"latest_execution_payload_header_capella" yaml:"latest_execution_payload_header_capella"` + LatestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb `json:"latest_execution_payload_header_deneb" yaml:"latest_execution_payload_header_deneb"` + LatestExecutionPayloadHeaderElectra *enginev1.ExecutionPayloadHeaderElectra `json:"latest_execution_payload_header_electra" yaml:"latest_execution_payload_header_electra"` NextWithdrawalIndex uint64 `json:"next_withdrawal_index" yaml:"next_withdrawal_index"` NextWithdrawalValidatorIndex primitives.ValidatorIndex `json:"next_withdrawal_validator_index" yaml:"next_withdrawal_validator_index"` + DepositReceiptsStartIndex uint64 `json:"deposit_receipts_start_index" yaml:"deposit_receipts_start_index"` + DepositBalanceToConsume math.Gwei `json:"deposit_balance_to_consume" yaml:"deposit_balance_to_consume"` + ExitBalanceToConsume math.Gwei `json:"exit_balance_to_consume" yaml:"exit_balance_to_consume"` + EarliestExitEpoch primitives.Epoch `json:"earliest_exit_epoch" yaml:"earliest_exit_epoch"` + ConsolidationBalanceToConsume math.Gwei `json:"consolidation_balance_to_consume" yaml:"consolidation_balance_to_consume"` + EarliestConsolidationEpoch primitives.Epoch `json:"earliest_consolidation_epoch" yaml:"earliest_consolidation_epoch"` + PendingBalanceDeposits []*ethpb.PendingBalanceDeposit `json:"pending_balance_deposits" yaml:"pending_balance_deposits"` + PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal `json:"pending_partial_withdrawals" yaml:"pending_partial_withdrawals"` + PendingConsolidations []*ethpb.PendingConsolidation `json:"pending_consolidations" yaml:"pending_consolidations"` } func (b *BeaconState) MarshalJSON() ([]byte, error) { @@ -162,8 +186,19 @@ func (b *BeaconState) MarshalJSON() ([]byte, error) { NextSyncCommittee: b.nextSyncCommittee, LatestExecutionPayloadHeader: b.latestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapella, + LatestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDeneb, + LatestExecutionPayloadHeaderElectra: b.latestExecutionPayloadHeaderElectra, NextWithdrawalIndex: b.nextWithdrawalIndex, NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + DepositReceiptsStartIndex: b.depositReceiptsStartIndex, + DepositBalanceToConsume: b.depositBalanceToConsume, + ExitBalanceToConsume: b.exitBalanceToConsume, + EarliestExitEpoch: b.earliestExitEpoch, + ConsolidationBalanceToConsume: b.consolidationBalanceToConsume, + EarliestConsolidationEpoch: b.earliestConsolidationEpoch, + PendingBalanceDeposits: b.pendingBalanceDeposits, + PendingPartialWithdrawals: b.pendingPartialWithdrawals, + PendingConsolidations: b.pendingConsolidations, } return json.Marshal(marshalable) } diff --git a/beacon-chain/state/state-native/beacon_state_minimal.go b/beacon-chain/state/state-native/beacon_state_minimal.go index f7d7c6fe8f..1a5631f9cc 100644 --- a/beacon-chain/state/state-native/beacon_state_minimal.go +++ b/beacon-chain/state/state-native/beacon_state_minimal.go @@ -13,6 +13,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil" "github.com/prysmaticlabs/prysm/v5/config/features" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ) @@ -57,9 +58,21 @@ type BeaconState struct { latestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader latestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella latestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb + latestExecutionPayloadHeaderElectra *enginev1.ExecutionPayloadHeaderElectra nextWithdrawalIndex uint64 nextWithdrawalValidatorIndex primitives.ValidatorIndex + // Electra fields + depositReceiptsStartIndex uint64 + depositBalanceToConsume math.Gwei + exitBalanceToConsume math.Gwei + earliestExitEpoch primitives.Epoch + consolidationBalanceToConsume math.Gwei + earliestConsolidationEpoch primitives.Epoch + pendingBalanceDeposits []*ethpb.PendingBalanceDeposit // pending_balance_deposits: List[PendingBalanceDeposit, PENDING_BALANCE_DEPOSITS_LIMIT] + pendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal // pending_partial_withdrawals: List[PartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pendingConsolidations []*ethpb.PendingConsolidation // pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + id uint64 lock sync.RWMutex dirtyFields map[types.FieldIndex]bool @@ -103,8 +116,19 @@ type beaconStateMarshalable struct { NextSyncCommittee *ethpb.SyncCommittee `json:"next_sync_committee" yaml:"next_sync_committee"` LatestExecutionPayloadHeader *enginev1.ExecutionPayloadHeader `json:"latest_execution_payload_header" yaml:"latest_execution_payload_header"` LatestExecutionPayloadHeaderCapella *enginev1.ExecutionPayloadHeaderCapella `json:"latest_execution_payload_header_capella" yaml:"latest_execution_payload_header_capella"` + LatestExecutionPayloadHeaderDeneb *enginev1.ExecutionPayloadHeaderDeneb `json:"latest_execution_payload_header_deneb" yaml:"latest_execution_payload_header_deneb"` + LatestExecutionPayloadHeaderElectra *enginev1.ExecutionPayloadHeaderElectra `json:"latest_execution_payload_header_electra" yaml:"latest_execution_payload_header_electra"` NextWithdrawalIndex uint64 `json:"next_withdrawal_index" yaml:"next_withdrawal_index"` NextWithdrawalValidatorIndex primitives.ValidatorIndex `json:"next_withdrawal_validator_index" yaml:"next_withdrawal_validator_index"` + DepositReceiptsStartIndex uint64 `json:"deposit_receipts_start_index" yaml:"deposit_receipts_start_index"` + DepositBalanceToConsume math.Gwei `json:"deposit_balance_to_consume" yaml:"deposit_balance_to_consume"` + ExitBalanceToConsume math.Gwei `json:"exit_balance_to_consume" yaml:"exit_balance_to_consume"` + EarliestExitEpoch primitives.Epoch `json:"earliest_exit_epoch" yaml:"earliest_exit_epoch"` + ConsolidationBalanceToConsume math.Gwei `json:"consolidation_balance_to_consume" yaml:"consolidation_balance_to_consume"` + EarliestConsolidationEpoch primitives.Epoch `json:"earliest_consolidation_epoch" yaml:"earliest_consolidation_epoch"` + PendingBalanceDeposits []*ethpb.PendingBalanceDeposit `json:"pending_balance_deposits" yaml:"pending_balance_deposits"` + PendingPartialWithdrawals []*ethpb.PendingPartialWithdrawal `json:"pending_partial_withdrawals" yaml:"pending_partial_withdrawals"` + PendingConsolidations []*ethpb.PendingConsolidation `json:"pending_consolidations" yaml:"pending_consolidations"` } func (b *BeaconState) MarshalJSON() ([]byte, error) { @@ -162,8 +186,19 @@ func (b *BeaconState) MarshalJSON() ([]byte, error) { NextSyncCommittee: b.nextSyncCommittee, LatestExecutionPayloadHeader: b.latestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapella, + LatestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDeneb, + LatestExecutionPayloadHeaderElectra: b.latestExecutionPayloadHeaderElectra, NextWithdrawalIndex: b.nextWithdrawalIndex, NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + DepositReceiptsStartIndex: b.depositReceiptsStartIndex, + DepositBalanceToConsume: b.depositBalanceToConsume, + ExitBalanceToConsume: b.exitBalanceToConsume, + EarliestExitEpoch: b.earliestExitEpoch, + ConsolidationBalanceToConsume: b.consolidationBalanceToConsume, + EarliestConsolidationEpoch: b.earliestConsolidationEpoch, + PendingBalanceDeposits: b.pendingBalanceDeposits, + PendingPartialWithdrawals: b.pendingPartialWithdrawals, + PendingConsolidations: b.pendingConsolidations, } return json.Marshal(marshalable) } diff --git a/beacon-chain/state/state-native/getters_balance_deposits.go b/beacon-chain/state/state-native/getters_balance_deposits.go new file mode 100644 index 0000000000..900a5ece21 --- /dev/null +++ b/beacon-chain/state/state-native/getters_balance_deposits.go @@ -0,0 +1,39 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/math" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +// DepositBalanceToConsume is a non-mutating call to the beacon state which returns the value of the +// deposit balance to consume field. This method requires access to the RLock on the state and only +// applies in electra or later. +func (b *BeaconState) DepositBalanceToConsume() (math.Gwei, error) { + if b.version < version.Electra { + return 0, errNotSupported("DepositBalanceToConsume", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.depositBalanceToConsume, nil +} + +// PendingBalanceDeposits is a non-mutating call to the beacon state which returns a deep copy of +// the pending balance deposit slice. This method requires access to the RLock on the state and +// only applies in electra or later. +func (b *BeaconState) PendingBalanceDeposits() ([]*ethpb.PendingBalanceDeposit, error) { + if b.version < version.Electra { + return nil, errNotSupported("PendingBalanceDeposits", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.pendingBalanceDepositsVal(), nil +} + +func (b *BeaconState) pendingBalanceDepositsVal() []*ethpb.PendingBalanceDeposit { + if b.pendingBalanceDeposits == nil { + return nil + } + + return ethpb.CopyPendingBalanceDeposits(b.pendingBalanceDeposits) +} diff --git a/beacon-chain/state/state-native/getters_balance_deposits_test.go b/beacon-chain/state/state-native/getters_balance_deposits_test.go new file mode 100644 index 0000000000..358681e52b --- /dev/null +++ b/beacon-chain/state/state-native/getters_balance_deposits_test.go @@ -0,0 +1,50 @@ +package state_native_test + +import ( + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestDepositBalanceToConsume(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + DepositBalanceToConsume: 44, + }) + require.NoError(t, err) + dbtc, err := s.DepositBalanceToConsume() + require.NoError(t, err) + require.Equal(t, math.Gwei(44), dbtc) + + // Fails for older than electra state + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = s.DepositBalanceToConsume() + require.ErrorContains(t, "not supported", err) +} + +func TestPendingBalanceDeposits(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingBalanceDeposits: []*eth.PendingBalanceDeposit{ + {Index: 1, Amount: 2}, + {Index: 3, Amount: 4}, + }, + }) + require.NoError(t, err) + pbd, err := s.PendingBalanceDeposits() + require.NoError(t, err) + require.Equal(t, 2, len(pbd)) + require.Equal(t, primitives.ValidatorIndex(1), pbd[0].Index) + require.Equal(t, uint64(2), pbd[0].Amount) + require.Equal(t, primitives.ValidatorIndex(3), pbd[1].Index) + require.Equal(t, uint64(4), pbd[1].Amount) + + // Fails for older than electra state + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = s.DepositBalanceToConsume() + require.ErrorContains(t, "not supported", err) +} diff --git a/beacon-chain/state/state-native/getters_consolidation.go b/beacon-chain/state/state-native/getters_consolidation.go new file mode 100644 index 0000000000..017dde5112 --- /dev/null +++ b/beacon-chain/state/state-native/getters_consolidation.go @@ -0,0 +1,64 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +// EarliestConsolidationEpoch is a non-mutating call to the beacon state which returns the value of +// the earliest consolidation epoch field. This method requires access to the RLock on the state and +// only applies in electra or later. +func (b *BeaconState) EarliestConsolidationEpoch() (primitives.Epoch, error) { + if b.version < version.Electra { + return 0, errNotSupported("EarliestConsolidationEpoch", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.earliestConsolidationEpoch, nil +} + +// ConsolidationBalanceToConsume is a non-mutating call to the beacon state which returns the value +// of the consolidation balance to consume field. This method requires access to the RLock on the +// state and only applies in electra or later. +func (b *BeaconState) ConsolidationBalanceToConsume() (math.Gwei, error) { + if b.version < version.Electra { + return 0, errNotSupported("ConsolidationBalanceToConsume", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.consolidationBalanceToConsume, nil +} + +// PendingConsolidations is a non-mutating call to the beacon state which returns a deep copy of the +// pending consolidations slice. This method requires access to the RLock on the state and only +// applies in electra or later. +func (b *BeaconState) PendingConsolidations() ([]*ethpb.PendingConsolidation, error) { + if b.version < version.Electra { + return nil, errNotSupported("PendingConsolidations", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.pendingConsolidationsVal(), nil +} + +// NumPendingConsolidations is a non-mutating call to the beacon state which returns the number of +// pending consolidations in the beacon state. This method requires access to the RLock on the state +// and only applies in electra or later. +func (b *BeaconState) NumPendingConsolidations() (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("NumPendingConsolidations", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return uint64(len(b.pendingConsolidations)), nil +} + +func (b *BeaconState) pendingConsolidationsVal() []*ethpb.PendingConsolidation { + if b.pendingConsolidations == nil { + return nil + } + + return ethpb.CopyPendingConsolidations(b.pendingConsolidations) +} diff --git a/beacon-chain/state/state-native/getters_consolidation_test.go b/beacon-chain/state/state-native/getters_consolidation_test.go new file mode 100644 index 0000000000..6a4ec384a2 --- /dev/null +++ b/beacon-chain/state/state-native/getters_consolidation_test.go @@ -0,0 +1,118 @@ +package state_native_test + +import ( + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestEarliestConsolidationEpoch(t *testing.T) { + t.Run("electra returns expected value", func(t *testing.T) { + want := primitives.Epoch(10) + st, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{ + EarliestConsolidationEpoch: want, + }) + require.NoError(t, err) + got, err := st.EarliestConsolidationEpoch() + require.NoError(t, err) + require.Equal(t, want, got) + }) + + t.Run("earlier than electra returns error", func(t *testing.T) { + st, err := state_native.InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = st.EarliestConsolidationEpoch() + require.ErrorContains(t, "is not supported", err) + }) +} + +func TestConsolidationBalanceToConsume(t *testing.T) { + t.Run("electra returns expected value", func(t *testing.T) { + want := math.Gwei(10) + st, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{ + ConsolidationBalanceToConsume: want, + }) + require.NoError(t, err) + got, err := st.ConsolidationBalanceToConsume() + require.NoError(t, err) + require.Equal(t, want, got) + }) + + t.Run("earlier than electra returns error", func(t *testing.T) { + st, err := state_native.InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = st.ConsolidationBalanceToConsume() + require.ErrorContains(t, "is not supported", err) + }) +} + +func TestPendingConsolidations(t *testing.T) { + t.Run("electra returns expected value", func(t *testing.T) { + want := []*ethpb.PendingConsolidation{ + { + SourceIndex: 1, + TargetIndex: 2, + }, + { + SourceIndex: 3, + TargetIndex: 4, + }, + { + SourceIndex: 5, + TargetIndex: 6, + }, + { + SourceIndex: 7, + TargetIndex: 8, + }, + } + st, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{ + PendingConsolidations: want, + }) + require.NoError(t, err) + got, err := st.PendingConsolidations() + require.NoError(t, err) + require.DeepEqual(t, want, got) + }) + + t.Run("earlier than electra returns error", func(t *testing.T) { + st, err := state_native.InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = st.PendingConsolidations() + require.ErrorContains(t, "is not supported", err) + }) +} + +func TestNumPendingConsolidations(t *testing.T) { + t.Run("electra returns expected value", func(t *testing.T) { + want := uint64(4) + st, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{ + PendingConsolidations: []*ethpb.PendingConsolidation{ + { + SourceIndex: 1, + TargetIndex: 2, + }, + { + SourceIndex: 3, + TargetIndex: 4, + }, + { + SourceIndex: 5, + TargetIndex: 6, + }, + { + SourceIndex: 7, + TargetIndex: 8, + }, + }, + }) + require.NoError(t, err) + got, err := st.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, want, got) + }) +} diff --git a/beacon-chain/state/state-native/getters_deposit_receipts.go b/beacon-chain/state/state-native/getters_deposit_receipts.go new file mode 100644 index 0000000000..aa15a9a8a0 --- /dev/null +++ b/beacon-chain/state/state-native/getters_deposit_receipts.go @@ -0,0 +1,16 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +// DepositReceiptsStartIndex is used for returning the deposit receipts start index which is used for eip6110 +func (b *BeaconState) DepositReceiptsStartIndex() (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("DepositReceiptsStartIndex", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + + return b.depositReceiptsStartIndex, nil +} diff --git a/beacon-chain/state/state-native/getters_deposit_receipts_test.go b/beacon-chain/state/state-native/getters_deposit_receipts_test.go new file mode 100644 index 0000000000..de6ea94bb2 --- /dev/null +++ b/beacon-chain/state/state-native/getters_deposit_receipts_test.go @@ -0,0 +1,26 @@ +package state_native_test + +import ( + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" +) + +func TestDepositReceiptsStartIndex(t *testing.T) { + t.Run("previous fork returns expected error", func(t *testing.T) { + dState, _ := util.DeterministicGenesisState(t, 1) + _, err := dState.DepositReceiptsStartIndex() + require.ErrorContains(t, "is not supported", err) + }) + t.Run("electra returns expected value", func(t *testing.T) { + want := uint64(2) + dState, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{DepositReceiptsStartIndex: want}) + require.NoError(t, err) + got, err := dState.DepositReceiptsStartIndex() + require.NoError(t, err) + require.Equal(t, want, got) + }) +} diff --git a/beacon-chain/state/state-native/getters_payload_header.go b/beacon-chain/state/state-native/getters_payload_header.go index 74feadca17..ba2a204d84 100644 --- a/beacon-chain/state/state-native/getters_payload_header.go +++ b/beacon-chain/state/state-native/getters_payload_header.go @@ -1,6 +1,7 @@ package state_native import ( + "fmt" "math/big" "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" @@ -19,15 +20,18 @@ func (b *BeaconState) LatestExecutionPayloadHeader() (interfaces.ExecutionData, b.lock.RLock() defer b.lock.RUnlock() - if b.version == version.Bellatrix { + switch b.version { + case version.Bellatrix: return blocks.WrappedExecutionPayloadHeader(b.latestExecutionPayloadHeaderVal()) - } - - if b.version == version.Capella { + case version.Capella: return blocks.WrappedExecutionPayloadHeaderCapella(b.latestExecutionPayloadHeaderCapellaVal(), big.NewInt(0)) + case version.Deneb: + return blocks.WrappedExecutionPayloadHeaderDeneb(b.latestExecutionPayloadHeaderDenebVal(), big.NewInt(0)) + case version.Electra: + return blocks.WrappedExecutionPayloadHeaderElectra(b.latestExecutionPayloadHeaderElectraVal(), big.NewInt(0)) + default: + return nil, fmt.Errorf("unsupported version (%s) for latest execution payload header", version.String(b.version)) } - - return blocks.WrappedExecutionPayloadHeaderDeneb(b.latestExecutionPayloadHeaderDenebVal(), big.NewInt(0)) } // latestExecutionPayloadHeaderVal of the beacon state. @@ -45,3 +49,7 @@ func (b *BeaconState) latestExecutionPayloadHeaderCapellaVal() *enginev1.Executi func (b *BeaconState) latestExecutionPayloadHeaderDenebVal() *enginev1.ExecutionPayloadHeaderDeneb { return ethpb.CopyExecutionPayloadHeaderDeneb(b.latestExecutionPayloadHeaderDeneb) } + +func (b *BeaconState) latestExecutionPayloadHeaderElectraVal() *enginev1.ExecutionPayloadHeaderElectra { + return ethpb.CopyExecutionPayloadHeaderElectra(b.latestExecutionPayloadHeaderElectra) +} diff --git a/beacon-chain/state/state-native/getters_state.go b/beacon-chain/state/state-native/getters_state.go index c8f9c4aa7b..402586e633 100644 --- a/beacon-chain/state/state-native/getters_state.go +++ b/beacon-chain/state/state-native/getters_state.go @@ -172,6 +172,46 @@ func (b *BeaconState) ToProtoUnsafe() interface{} { NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, HistoricalSummaries: b.historicalSummaries, } + case version.Electra: + return ðpb.BeaconStateElectra{ + GenesisTime: b.genesisTime, + GenesisValidatorsRoot: gvrCopy[:], + Slot: b.slot, + Fork: b.fork, + LatestBlockHeader: b.latestBlockHeader, + BlockRoots: br, + StateRoots: sr, + HistoricalRoots: b.historicalRoots.Slice(), + Eth1Data: b.eth1Data, + Eth1DataVotes: b.eth1DataVotes, + Eth1DepositIndex: b.eth1DepositIndex, + Validators: vals, + Balances: bals, + RandaoMixes: rm, + Slashings: b.slashings, + PreviousEpochParticipation: b.previousEpochParticipation, + CurrentEpochParticipation: b.currentEpochParticipation, + JustificationBits: b.justificationBits, + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpoint, + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpoint, + FinalizedCheckpoint: b.finalizedCheckpoint, + InactivityScores: b.inactivityScoresVal(), + CurrentSyncCommittee: b.currentSyncCommittee, + NextSyncCommittee: b.nextSyncCommittee, + LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderElectra, + NextWithdrawalIndex: b.nextWithdrawalIndex, + NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + HistoricalSummaries: b.historicalSummaries, + DepositReceiptsStartIndex: b.depositReceiptsStartIndex, + DepositBalanceToConsume: b.depositBalanceToConsume, + ExitBalanceToConsume: b.exitBalanceToConsume, + EarliestExitEpoch: b.earliestExitEpoch, + ConsolidationBalanceToConsume: b.consolidationBalanceToConsume, + EarliestConsolidationEpoch: b.earliestConsolidationEpoch, + PendingBalanceDeposits: b.pendingBalanceDeposits, + PendingPartialWithdrawals: b.pendingPartialWithdrawals, + PendingConsolidations: b.pendingConsolidations, + } default: return nil } @@ -338,6 +378,46 @@ func (b *BeaconState) ToProto() interface{} { NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, HistoricalSummaries: b.historicalSummariesVal(), } + case version.Electra: + return ðpb.BeaconStateElectra{ + GenesisTime: b.genesisTime, + GenesisValidatorsRoot: gvrCopy[:], + Slot: b.slot, + Fork: b.forkVal(), + LatestBlockHeader: b.latestBlockHeaderVal(), + BlockRoots: br, + StateRoots: sr, + HistoricalRoots: b.historicalRoots.Slice(), + Eth1Data: b.eth1DataVal(), + Eth1DataVotes: b.eth1DataVotesVal(), + Eth1DepositIndex: b.eth1DepositIndex, + Validators: b.validatorsVal(), + Balances: b.balancesVal(), + RandaoMixes: rm, + Slashings: b.slashingsVal(), + PreviousEpochParticipation: b.previousEpochParticipationVal(), + CurrentEpochParticipation: b.currentEpochParticipationVal(), + JustificationBits: b.justificationBitsVal(), + PreviousJustifiedCheckpoint: b.previousJustifiedCheckpointVal(), + CurrentJustifiedCheckpoint: b.currentJustifiedCheckpointVal(), + FinalizedCheckpoint: b.finalizedCheckpointVal(), + InactivityScores: b.inactivityScoresVal(), + CurrentSyncCommittee: b.currentSyncCommitteeVal(), + NextSyncCommittee: b.nextSyncCommitteeVal(), + LatestExecutionPayloadHeader: b.latestExecutionPayloadHeaderElectraVal(), + NextWithdrawalIndex: b.nextWithdrawalIndex, + NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + HistoricalSummaries: b.historicalSummariesVal(), + DepositReceiptsStartIndex: b.depositReceiptsStartIndex, + DepositBalanceToConsume: b.depositBalanceToConsume, + ExitBalanceToConsume: b.exitBalanceToConsume, + EarliestExitEpoch: b.earliestExitEpoch, + ConsolidationBalanceToConsume: b.consolidationBalanceToConsume, + EarliestConsolidationEpoch: b.earliestConsolidationEpoch, + PendingBalanceDeposits: b.pendingBalanceDepositsVal(), + PendingPartialWithdrawals: b.pendingPartialWithdrawalsVal(), + PendingConsolidations: b.pendingConsolidationsVal(), + } default: return nil } @@ -449,7 +529,17 @@ func ProtobufBeaconStateCapella(s interface{}) (*ethpb.BeaconStateCapella, error func ProtobufBeaconStateDeneb(s interface{}) (*ethpb.BeaconStateDeneb, error) { pbState, ok := s.(*ethpb.BeaconStateDeneb) if !ok { - return nil, errors.New("input is not type pb.ProtobufBeaconStateDeneb") + return nil, errors.New("input is not type pb.BeaconStateDeneb") + } + return pbState, nil +} + +// ProtobufBeaconStateElectra transforms an input into beacon state Electra in the form of protobuf. +// Error is returned if the input is not type protobuf beacon state. +func ProtobufBeaconStateElectra(s interface{}) (*ethpb.BeaconStateElectra, error) { + pbState, ok := s.(*ethpb.BeaconStateElectra) + if !ok { + return nil, errors.New("input is not type pb.BeaconStateElectra") } return pbState, nil } diff --git a/beacon-chain/state/state-native/getters_validator.go b/beacon-chain/state/state-native/getters_validator.go index 940e85975a..0f1df58b9c 100644 --- a/beacon-chain/state/state-native/getters_validator.go +++ b/beacon-chain/state/state-native/getters_validator.go @@ -2,6 +2,7 @@ package state_native import ( "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" "github.com/prysmaticlabs/prysm/v5/config/features" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" @@ -413,3 +414,59 @@ func (b *BeaconState) inactivityScoresVal() []uint64 { copy(res, b.inactivityScores) return res } + +// ActiveBalanceAtIndex returns the active balance for the given validator. +// +// Spec definition: +// +// def get_active_balance(state: BeaconState, validator_index: ValidatorIndex) -> Gwei: +// max_effective_balance = get_validator_max_effective_balance(state.validators[validator_index]) +// return min(state.balances[validator_index], max_effective_balance) +func (b *BeaconState) ActiveBalanceAtIndex(i primitives.ValidatorIndex) (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("ActiveBalanceAtIndex", b.version) + } + + b.lock.RLock() + defer b.lock.RUnlock() + + v, err := b.validatorAtIndex(i) + if err != nil { + return 0, err + } + + bal, err := b.balanceAtIndex(i) + if err != nil { + return 0, err + } + + return min(bal, helpers.ValidatorMaxEffectiveBalance(v)), nil +} + +// PendingBalanceToWithdraw returns the sum of all pending withdrawals for the given validator. +// +// Spec definition: +// +// def get_pending_balance_to_withdraw(state: BeaconState, validator_index: ValidatorIndex) -> Gwei: +// return sum( +// withdrawal.amount for withdrawal in state.pending_partial_withdrawals if withdrawal.index == validator_index) +func (b *BeaconState) PendingBalanceToWithdraw(idx primitives.ValidatorIndex) (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("PendingBalanceToWithdraw", b.version) + } + + b.lock.RLock() + defer b.lock.RUnlock() + + // TODO: Consider maintaining this value in the state, if it's a potential bottleneck. + // This is n*m complexity, but this method can only be called + // MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD per slot. A more optimized storage indexing such as a + // lookup map could be used to reduce the complexity marginally. + var sum uint64 + for _, w := range b.pendingPartialWithdrawals { + if w.Index == idx { + sum += w.Amount + } + } + return sum, nil +} diff --git a/beacon-chain/state/state-native/getters_validator_test.go b/beacon-chain/state/state-native/getters_validator_test.go index 43df4d9d50..81e823c194 100644 --- a/beacon-chain/state/state-native/getters_validator_test.go +++ b/beacon-chain/state/state-native/getters_validator_test.go @@ -1,12 +1,15 @@ package state_native_test import ( + "math" "testing" "github.com/ethereum/go-ethereum/common/hexutil" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" statenative "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" testtmpl "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/testing" + "github.com/prysmaticlabs/prysm/v5/config/params" + consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" @@ -66,3 +69,79 @@ func TestValidatorIndexes(t *testing.T) { require.Equal(t, hexutil.Encode(readOnlyBytes[:]), hexutil.Encode(byteValue[:])) }) } + +func TestActiveBalanceAtIndex(t *testing.T) { + // Test setup with a state with 4 validators. + // Validators 0 & 1 have compounding withdrawal credentials while validators 2 & 3 have BLS withdrawal credentials. + pb := ðpb.BeaconStateElectra{ + Validators: []*ethpb.Validator{ + { + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte}, + }, + { + WithdrawalCredentials: []byte{params.BeaconConfig().CompoundingWithdrawalPrefixByte}, + }, + { + WithdrawalCredentials: []byte{params.BeaconConfig().BLSWithdrawalPrefixByte}, + }, + { + WithdrawalCredentials: []byte{params.BeaconConfig().BLSWithdrawalPrefixByte}, + }, + }, + Balances: []uint64{ + 55, + math.MaxUint64, + 55, + math.MaxUint64, + }, + } + state, err := statenative.InitializeFromProtoUnsafeElectra(pb) + require.NoError(t, err) + + ab, err := state.ActiveBalanceAtIndex(0) + require.NoError(t, err) + require.Equal(t, uint64(55), ab) + + ab, err = state.ActiveBalanceAtIndex(1) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxEffectiveBalanceElectra, ab) + + ab, err = state.ActiveBalanceAtIndex(2) + require.NoError(t, err) + require.Equal(t, uint64(55), ab) + + ab, err = state.ActiveBalanceAtIndex(3) + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MinActivationBalance, ab) + + // Accessing a validator index out of bounds should error. + _, err = state.ActiveBalanceAtIndex(4) + require.ErrorIs(t, err, consensus_types.ErrOutOfBounds) + + // Accessing a validator wwhere balance slice is out of bounds for some reason. + require.NoError(t, state.SetBalances([]uint64{})) + _, err = state.ActiveBalanceAtIndex(0) + require.ErrorIs(t, err, consensus_types.ErrOutOfBounds) +} + +func TestPendingBalanceToWithdraw(t *testing.T) { + pb := ðpb.BeaconStateElectra{ + PendingPartialWithdrawals: []*ethpb.PendingPartialWithdrawal{ + { + Amount: 100, + }, + { + Amount: 200, + }, + { + Amount: 300, + }, + }, + } + state, err := statenative.InitializeFromProtoUnsafeElectra(pb) + require.NoError(t, err) + + ab, err := state.PendingBalanceToWithdraw(0) + require.NoError(t, err) + require.Equal(t, uint64(600), ab) +} diff --git a/beacon-chain/state/state-native/getters_withdrawal.go b/beacon-chain/state/state-native/getters_withdrawal.go index b31eccefef..f5eb8bed97 100644 --- a/beacon-chain/state/state-native/getters_withdrawal.go +++ b/beacon-chain/state/state-native/getters_withdrawal.go @@ -1,7 +1,10 @@ package state_native import ( + "fmt" + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" "github.com/prysmaticlabs/prysm/v5/encoding/bytesutil" @@ -41,10 +44,64 @@ func (b *BeaconState) NextWithdrawalValidatorIndex() (primitives.ValidatorIndex, // ExpectedWithdrawals returns the withdrawals that a proposer will need to pack in the next block // applied to the current state. It is also used by validators to check that the execution payload carried -// the right number of withdrawals -func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, error) { +// the right number of withdrawals. Note: The number of partial withdrawals will be zero before EIP-7251. +// +// Spec definition: +// +// def get_expected_withdrawals(state: BeaconState) -> Tuple[Sequence[Withdrawal], uint64]: +// epoch = get_current_epoch(state) +// withdrawal_index = state.next_withdrawal_index +// validator_index = state.next_withdrawal_validator_index +// withdrawals: List[Withdrawal] = [] +// +// # [New in Electra:EIP7251] Consume pending partial withdrawals +// for withdrawal in state.pending_partial_withdrawals: +// if withdrawal.withdrawable_epoch > epoch or len(withdrawals) == MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP: +// break +// +// validator = state.validators[withdrawal.index] +// has_sufficient_effective_balance = validator.effective_balance >= MIN_ACTIVATION_BALANCE +// has_excess_balance = state.balances[withdrawal.index] > MIN_ACTIVATION_BALANCE +// if validator.exit_epoch == FAR_FUTURE_EPOCH and has_sufficient_effective_balance and has_excess_balance: +// withdrawable_balance = min(state.balances[withdrawal.index] - MIN_ACTIVATION_BALANCE, withdrawal.amount) +// withdrawals.append(Withdrawal( +// index=withdrawal_index, +// validator_index=withdrawal.index, +// address=ExecutionAddress(validator.withdrawal_credentials[12:]), +// amount=withdrawable_balance, +// )) +// withdrawal_index += WithdrawalIndex(1) +// +// partial_withdrawals_count = len(withdrawals) +// +// # Sweep for remaining. +// bound = min(len(state.validators), MAX_VALIDATORS_PER_WITHDRAWALS_SWEEP) +// for _ in range(bound): +// validator = state.validators[validator_index] +// balance = state.balances[validator_index] +// if is_fully_withdrawable_validator(validator, balance, epoch): +// withdrawals.append(Withdrawal( +// index=withdrawal_index, +// validator_index=validator_index, +// address=ExecutionAddress(validator.withdrawal_credentials[12:]), +// amount=balance, +// )) +// withdrawal_index += WithdrawalIndex(1) +// elif is_partially_withdrawable_validator(validator, balance): +// withdrawals.append(Withdrawal( +// index=withdrawal_index, +// validator_index=validator_index, +// address=ExecutionAddress(validator.withdrawal_credentials[12:]), +// amount=balance - get_validator_max_effective_balance(validator), # [Modified in Electra:EIP7251] +// )) +// withdrawal_index += WithdrawalIndex(1) +// if len(withdrawals) == MAX_WITHDRAWALS_PER_PAYLOAD: +// break +// validator_index = ValidatorIndex((validator_index + 1) % len(state.validators)) +// return withdrawals, partial_withdrawals_count +func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, uint64, error) { if b.version < version.Capella { - return nil, errNotSupported("ExpectedWithdrawals", b.version) + return nil, 0, errNotSupported("ExpectedWithdrawals", b.version) } b.lock.RLock() @@ -55,18 +112,49 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, error) { withdrawalIndex := b.nextWithdrawalIndex epoch := slots.ToEpoch(b.slot) + // Electra partial withdrawals functionality. + if b.version >= version.Electra { + for _, w := range b.pendingPartialWithdrawals { + if w.WithdrawableEpoch > epoch || len(withdrawals) >= int(params.BeaconConfig().MaxPendingPartialsPerWithdrawalsSweep) { + break + } + + v, err := b.validatorAtIndex(w.Index) + if err != nil { + return nil, 0, fmt.Errorf("failed to determine withdrawals at index %d: %w", w.Index, err) + } + vBal, err := b.balanceAtIndex(w.Index) + if err != nil { + return nil, 0, fmt.Errorf("could not retrieve balance at index %d: %w", w.Index, err) + } + hasSufficientEffectiveBalance := v.EffectiveBalance >= params.BeaconConfig().MinActivationBalance + hasExcessBalance := vBal > params.BeaconConfig().MinActivationBalance + if v.ExitEpoch == params.BeaconConfig().FarFutureEpoch && hasSufficientEffectiveBalance && hasExcessBalance { + amount := min(vBal-params.BeaconConfig().MinActivationBalance, w.Amount) + withdrawals = append(withdrawals, &enginev1.Withdrawal{ + Index: withdrawalIndex, + ValidatorIndex: w.Index, + Address: v.WithdrawalCredentials[12:], + Amount: amount, + }) + withdrawalIndex++ + } + } + } + partialWithdrawalsCount := uint64(len(withdrawals)) + validatorsLen := b.validatorsLen() bound := mathutil.Min(uint64(validatorsLen), params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep) for i := uint64(0); i < bound; i++ { val, err := b.validatorAtIndex(validatorIndex) if err != nil { - return nil, errors.Wrapf(err, "could not retrieve validator at index %d", validatorIndex) + return nil, 0, errors.Wrapf(err, "could not retrieve validator at index %d", validatorIndex) } balance, err := b.balanceAtIndex(validatorIndex) if err != nil { - return nil, errors.Wrapf(err, "could not retrieve balance at index %d", validatorIndex) + return nil, 0, errors.Wrapf(err, "could not retrieve balance at index %d", validatorIndex) } - if balance > 0 && isFullyWithdrawableValidator(val, epoch) { + if helpers.IsFullyWithdrawableValidator(val, balance, epoch) { withdrawals = append(withdrawals, &enginev1.Withdrawal{ Index: withdrawalIndex, ValidatorIndex: validatorIndex, @@ -74,12 +162,12 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, error) { Amount: balance, }) withdrawalIndex++ - } else if isPartiallyWithdrawableValidator(val, balance) { + } else if helpers.IsPartiallyWithdrawableValidator(val, balance, epoch) { withdrawals = append(withdrawals, &enginev1.Withdrawal{ Index: withdrawalIndex, ValidatorIndex: validatorIndex, Address: bytesutil.SafeCopyBytes(val.WithdrawalCredentials[ETH1AddressOffset:]), - Amount: balance - params.BeaconConfig().MaxEffectiveBalance, + Amount: balance - helpers.ValidatorMaxEffectiveBalance(val), }) withdrawalIndex++ } @@ -91,36 +179,17 @@ func (b *BeaconState) ExpectedWithdrawals() ([]*enginev1.Withdrawal, error) { validatorIndex = 0 } } - return withdrawals, nil + + return withdrawals, partialWithdrawalsCount, nil } -// hasETH1WithdrawalCredential returns whether the validator has an ETH1 -// Withdrawal prefix. It assumes that the caller has a lock on the state -func hasETH1WithdrawalCredential(val *ethpb.Validator) bool { - if val == nil { - return false - } - cred := val.WithdrawalCredentials - return len(cred) > 0 && cred[0] == params.BeaconConfig().ETH1AddressWithdrawalPrefixByte +func (b *BeaconState) pendingPartialWithdrawalsVal() []*ethpb.PendingPartialWithdrawal { + return ethpb.CopyPendingPartialWithdrawals(b.pendingPartialWithdrawals) } -// isFullyWithdrawableValidator returns whether the validator is able to perform a full -// withdrawal. This differ from the spec helper in that the balance > 0 is not -// checked. This function assumes that the caller holds a lock on the state -func isFullyWithdrawableValidator(val *ethpb.Validator, epoch primitives.Epoch) bool { - if val == nil { - return false +func (b *BeaconState) NumPendingPartialWithdrawals() (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("NumPendingPartialWithdrawals", b.version) } - return hasETH1WithdrawalCredential(val) && val.WithdrawableEpoch <= epoch -} - -// isPartiallyWithdrawable returns whether the validator is able to perform a -// partial withdrawal. This function assumes that the caller has a lock on the state -func isPartiallyWithdrawableValidator(val *ethpb.Validator, balance uint64) bool { - if val == nil { - return false - } - hasMaxBalance := val.EffectiveBalance == params.BeaconConfig().MaxEffectiveBalance - hasExcessBalance := balance > params.BeaconConfig().MaxEffectiveBalance - return hasETH1WithdrawalCredential(val) && hasExcessBalance && hasMaxBalance + return uint64(len(b.pendingPartialWithdrawals)), nil } diff --git a/beacon-chain/state/state-native/getters_withdrawal_test.go b/beacon-chain/state/state-native/getters_withdrawal_test.go index 4fa4e05a85..fc73894942 100644 --- a/beacon-chain/state/state-native/getters_withdrawal_test.go +++ b/beacon-chain/state/state-native/getters_withdrawal_test.go @@ -1,8 +1,10 @@ -package state_native +package state_native_test import ( "testing" + "github.com/golang/snappy" + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" "github.com/prysmaticlabs/prysm/v5/config/params" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" @@ -10,588 +12,341 @@ import ( "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" ) func TestNextWithdrawalIndex(t *testing.T) { t.Run("ok for deneb", func(t *testing.T) { - s := BeaconState{version: version.Deneb, nextWithdrawalIndex: 123} + s, err := state_native.InitializeFromProtoDeneb(ðpb.BeaconStateDeneb{NextWithdrawalIndex: 123}) + require.NoError(t, err) i, err := s.NextWithdrawalIndex() require.NoError(t, err) assert.Equal(t, uint64(123), i) }) t.Run("ok", func(t *testing.T) { - s := BeaconState{version: version.Capella, nextWithdrawalIndex: 123} + s, err := state_native.InitializeFromProtoCapella(ðpb.BeaconStateCapella{NextWithdrawalIndex: 123}) + require.NoError(t, err) i, err := s.NextWithdrawalIndex() require.NoError(t, err) assert.Equal(t, uint64(123), i) }) t.Run("version before Capella not supported", func(t *testing.T) { - s := BeaconState{version: version.Bellatrix} - _, err := s.NextWithdrawalIndex() + s, err := state_native.InitializeFromProtoBellatrix(ðpb.BeaconStateBellatrix{}) + require.NoError(t, err) + _, err = s.NextWithdrawalIndex() assert.ErrorContains(t, "NextWithdrawalIndex is not supported", err) }) } func TestNextWithdrawalValidatorIndex(t *testing.T) { t.Run("ok for deneb", func(t *testing.T) { - s := BeaconState{version: version.Deneb, nextWithdrawalValidatorIndex: 123} + pb := ðpb.BeaconStateDeneb{NextWithdrawalValidatorIndex: 123} + s, err := state_native.InitializeFromProtoDeneb(pb) + require.NoError(t, err) i, err := s.NextWithdrawalValidatorIndex() require.NoError(t, err) assert.Equal(t, primitives.ValidatorIndex(123), i) }) t.Run("ok", func(t *testing.T) { - s := BeaconState{version: version.Capella, nextWithdrawalValidatorIndex: 123} + pb := ðpb.BeaconStateCapella{NextWithdrawalValidatorIndex: 123} + s, err := state_native.InitializeFromProtoCapella(pb) + require.NoError(t, err) i, err := s.NextWithdrawalValidatorIndex() require.NoError(t, err) assert.Equal(t, primitives.ValidatorIndex(123), i) }) t.Run("version before Capella not supported", func(t *testing.T) { - s := BeaconState{version: version.Bellatrix} - _, err := s.NextWithdrawalValidatorIndex() + s, err := state_native.InitializeFromProtoBellatrix(ðpb.BeaconStateBellatrix{}) + require.NoError(t, err) + _, err = s.NextWithdrawalValidatorIndex() assert.ErrorContains(t, "NextWithdrawalValidatorIndex is not supported", err) }) } -func TestHasETH1WithdrawalCredentials(t *testing.T) { - creds := []byte{0xFA, 0xCC} - v := ðpb.Validator{WithdrawalCredentials: creds} - require.Equal(t, false, hasETH1WithdrawalCredential(v)) - creds = []byte{params.BeaconConfig().ETH1AddressWithdrawalPrefixByte, 0xCC} - v = ðpb.Validator{WithdrawalCredentials: creds} - require.Equal(t, true, hasETH1WithdrawalCredential(v)) - // No Withdrawal cred - v = ðpb.Validator{} - require.Equal(t, false, hasETH1WithdrawalCredential(v)) -} - -func TestIsFullyWithdrawableValidator(t *testing.T) { - // No ETH1 prefix - creds := []byte{0xFA, 0xCC} - v := ðpb.Validator{ - WithdrawalCredentials: creds, - WithdrawableEpoch: 2, - } - require.Equal(t, false, isFullyWithdrawableValidator(v, 3)) - // Wrong withdrawable epoch - creds = []byte{params.BeaconConfig().ETH1AddressWithdrawalPrefixByte, 0xCC} - v = ðpb.Validator{ - WithdrawalCredentials: creds, - WithdrawableEpoch: 2, - } - require.Equal(t, false, isFullyWithdrawableValidator(v, 1)) - // Fully withdrawable - creds = []byte{params.BeaconConfig().ETH1AddressWithdrawalPrefixByte, 0xCC} - v = ðpb.Validator{ - WithdrawalCredentials: creds, - WithdrawableEpoch: 2, - } - require.Equal(t, true, isFullyWithdrawableValidator(v, 3)) -} - func TestExpectedWithdrawals(t *testing.T) { - t.Run("no withdrawals", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 0, len(expected)) - }) - t.Run("one fully withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - nextWithdrawalValidatorIndex: 20, - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.validators[3].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 1, len(expected)) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: s.balances[3], - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("one partially withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.balances[3] += params.BeaconConfig().MinDepositAmount - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 1, len(expected)) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MinDepositAmount, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("one partially and one fully withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - val.WithdrawalCredentials[31] = byte(i) - s.validators[i] = val - } - s.balances[3] += params.BeaconConfig().MinDepositAmount - s.validators[7].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 2, len(expected)) + for _, stateVersion := range []int{version.Capella, version.Deneb, version.Electra} { + t.Run(version.String(stateVersion), func(t *testing.T) { + t.Run("no withdrawals", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + vals[i] = val + } + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(expected)) + }) + t.Run("one fully withdrawable", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + require.NoError(t, s.SetNextWithdrawalValidatorIndex(20)) - withdrawalFull := &enginev1.Withdrawal{ - Index: 1, - ValidatorIndex: 7, - Address: s.validators[7].WithdrawalCredentials[12:], - Amount: s.balances[7], - } - withdrawalPartial := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MinDepositAmount, - } - require.DeepEqual(t, withdrawalPartial, expected[0]) - require.DeepEqual(t, withdrawalFull, expected[1]) - }) - t.Run("all partially withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := 0; i < 100; i++ { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + + vals[i] = val + } + vals[3].WithdrawableEpoch = primitives.Epoch(0) + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 1, len(expected)) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: vals[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MaxEffectiveBalance, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("one partially withdrawable", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + require.NoError(t, s.SetNextWithdrawalValidatorIndex(20)) + + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := 0; i < 100; i++ { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + + vals[i] = val + } + balances[3] += params.BeaconConfig().MinDepositAmount + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 1, len(expected)) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: vals[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MinDepositAmount, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("one partially and one fully withdrawable", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + vals[i] = val + } + balances[3] += params.BeaconConfig().MinDepositAmount + vals[7].WithdrawableEpoch = primitives.Epoch(0) + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 2, len(expected)) + + withdrawalFull := &enginev1.Withdrawal{ + Index: 1, + ValidatorIndex: 7, + Address: vals[7].WithdrawalCredentials[12:], + Amount: balances[7], + } + withdrawalPartial := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: vals[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MinDepositAmount, + } + require.DeepEqual(t, withdrawalPartial, expected[0]) + require.DeepEqual(t, withdrawalFull, expected[1]) + }) + t.Run("all partially withdrawable", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + vals[i] = val + } + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 0, + Address: vals[0].WithdrawalCredentials[12:], + Amount: 1, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("all fully withdrawable", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(0), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + vals[i] = val + } + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 0, + Address: vals[0].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MaxEffectiveBalance, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("all fully and partially withdrawable", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(0), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + vals[i] = val + } + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 0, + Address: vals[0].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MaxEffectiveBalance + 1, + } + require.DeepEqual(t, withdrawal, expected[0]) + }) + t.Run("one fully withdrawable but zero balance", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + require.NoError(t, s.SetNextWithdrawalValidatorIndex(20)) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + vals[i] = val + } + vals[3].WithdrawableEpoch = primitives.Epoch(0) + balances[3] = 0 + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 0, len(expected)) + }) + t.Run("one partially withdrawable, one above sweep bound", func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, stateVersion) + vals := make([]*ethpb.Validator, 100) + balances := make([]uint64, 100) + for i := range vals { + balances[i] = params.BeaconConfig().MaxEffectiveBalance + val := ðpb.Validator{ + WithdrawalCredentials: make([]byte, 32), + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, + WithdrawableEpoch: primitives.Epoch(1), + } + val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte + val.WithdrawalCredentials[31] = byte(i) + vals[i] = val + } + balances[3] += params.BeaconConfig().MinDepositAmount + balances[10] += params.BeaconConfig().MinDepositAmount + require.NoError(t, s.SetValidators(vals)) + require.NoError(t, s.SetBalances(balances)) + saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep + params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = 10 + expected, _, err := s.ExpectedWithdrawals() + require.NoError(t, err) + require.Equal(t, 1, len(expected)) + withdrawal := &enginev1.Withdrawal{ + Index: 0, + ValidatorIndex: 3, + Address: vals[3].WithdrawalCredentials[12:], + Amount: params.BeaconConfig().MinDepositAmount, + } + require.DeepEqual(t, withdrawal, expected[0]) + params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved + }) + }) + } + + t.Run("electra all pending partial withdrawals", func(t *testing.T) { + // Load a serialized Electra state from disk. + // This spectest has a fully hydrated beacon state with partial pending withdrawals. + serializedBytes, err := util.BazelFileBytes("tests/mainnet/electra/operations/execution_layer_withdrawal_request/pyspec_tests/pending_withdrawals_consume_all_excess_balance/pre.ssz_snappy") require.NoError(t, err) - require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 0, - Address: s.validators[0].WithdrawalCredentials[12:], - Amount: 1, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("all fully withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(0), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() + serializedSSZ, err := snappy.Decode(nil /* dst */, serializedBytes) require.NoError(t, err) - require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 0, - Address: s.validators[0].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MaxEffectiveBalance, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("all fully and partially withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(0), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() + pb := ðpb.BeaconStateElectra{} + require.NoError(t, pb.UnmarshalSSZ(serializedSSZ)) + s, err := state_native.InitializeFromProtoElectra(pb) require.NoError(t, err) - require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 0, - Address: s.validators[0].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MaxEffectiveBalance + 1, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("one fully withdrawable but zero balance", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - nextWithdrawalValidatorIndex: 20, - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.validators[3].WithdrawableEpoch = primitives.Epoch(0) - s.balances[3] = 0 - expected, err := s.ExpectedWithdrawals() + t.Log(s.NumPendingPartialWithdrawals()) + expected, partialWithdrawalsCount, err := s.ExpectedWithdrawals() require.NoError(t, err) - require.Equal(t, 0, len(expected)) - }) - t.Run("one partially withdrawable, one above sweep bound", func(t *testing.T) { - s := BeaconState{ - version: version.Capella, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.balances[3] += params.BeaconConfig().MinDepositAmount - s.balances[10] += params.BeaconConfig().MinDepositAmount - saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep - params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = 10 - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 1, len(expected)) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MinDepositAmount, - } - require.DeepEqual(t, withdrawal, expected[0]) - params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved - }) -} - -func TestExpectedWithdrawals_Deneb(t *testing.T) { - t.Run("no withdrawals", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 0, len(expected)) - }) - t.Run("one fully withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - nextWithdrawalValidatorIndex: 20, - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.validators[3].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 1, len(expected)) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: s.balances[3], - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("one partially withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.balances[3] += params.BeaconConfig().MinDepositAmount - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 1, len(expected)) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MinDepositAmount, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("one partially and one fully withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - val.WithdrawalCredentials[31] = byte(i) - s.validators[i] = val - } - s.balances[3] += params.BeaconConfig().MinDepositAmount - s.validators[7].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 2, len(expected)) - - withdrawalFull := &enginev1.Withdrawal{ - Index: 1, - ValidatorIndex: 7, - Address: s.validators[7].WithdrawalCredentials[12:], - Amount: s.balances[7], - } - withdrawalPartial := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MinDepositAmount, - } - require.DeepEqual(t, withdrawalPartial, expected[0]) - require.DeepEqual(t, withdrawalFull, expected[1]) - }) - t.Run("all partially withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 0, - Address: s.validators[0].WithdrawalCredentials[12:], - Amount: 1, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("all fully withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(0), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 0, - Address: s.validators[0].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MaxEffectiveBalance, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("all fully and partially withdrawable", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance + 1 - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(0), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 0, - Address: s.validators[0].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MaxEffectiveBalance + 1, - } - require.DeepEqual(t, withdrawal, expected[0]) - }) - t.Run("one fully withdrawable but zero balance", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - nextWithdrawalValidatorIndex: 20, - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.validators[3].WithdrawableEpoch = primitives.Epoch(0) - s.balances[3] = 0 - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 0, len(expected)) - }) - t.Run("one partially withdrawable, one above sweep bound", func(t *testing.T) { - s := BeaconState{ - version: version.Deneb, - validators: make([]*ethpb.Validator, 100), - balances: make([]uint64, 100), - } - for i := range s.validators { - s.balances[i] = params.BeaconConfig().MaxEffectiveBalance - val := ðpb.Validator{ - WithdrawalCredentials: make([]byte, 32), - EffectiveBalance: params.BeaconConfig().MaxEffectiveBalance, - WithdrawableEpoch: primitives.Epoch(1), - } - val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte - s.validators[i] = val - } - s.balances[3] += params.BeaconConfig().MinDepositAmount - s.balances[10] += params.BeaconConfig().MinDepositAmount - saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep - params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = 10 - expected, err := s.ExpectedWithdrawals() - require.NoError(t, err) - require.Equal(t, 1, len(expected)) - withdrawal := &enginev1.Withdrawal{ - Index: 0, - ValidatorIndex: 3, - Address: s.validators[3].WithdrawalCredentials[12:], - Amount: params.BeaconConfig().MinDepositAmount, - } - require.DeepEqual(t, withdrawal, expected[0]) - params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = saved + require.Equal(t, 8, len(expected)) + require.Equal(t, uint64(8), partialWithdrawalsCount) }) } diff --git a/beacon-chain/state/state-native/hasher.go b/beacon-chain/state/state-native/hasher.go index eaf3235949..92014a0767 100644 --- a/beacon-chain/state/state-native/hasher.go +++ b/beacon-chain/state/state-native/hasher.go @@ -3,6 +3,7 @@ package state_native import ( "context" "encoding/binary" + "fmt" "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" @@ -38,6 +39,10 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateCapellaFieldCount) case version.Deneb: fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateDenebFieldCount) + case version.Electra: + fieldRoots = make([][]byte, params.BeaconConfig().BeaconStateElectraFieldCount) + default: + return nil, fmt.Errorf("unknown state version %s", version.String(state.version)) } // Genesis time root. @@ -247,6 +252,15 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b fieldRoots[types.LatestExecutionPayloadHeaderDeneb.RealPosition()] = executionPayloadRoot[:] } + if state.version == version.Electra { + // Execution payload root. + executionPayloadRoot, err := state.latestExecutionPayloadHeaderElectra.HashTreeRoot() + if err != nil { + return nil, err + } + fieldRoots[types.LatestExecutionPayloadHeaderElectra.RealPosition()] = executionPayloadRoot[:] + } + if state.version >= version.Capella { // Next withdrawal index root. nextWithdrawalIndexRoot := make([]byte, 32) @@ -266,5 +280,52 @@ func ComputeFieldRootsWithHasher(ctx context.Context, state *BeaconState) ([][]b fieldRoots[types.HistoricalSummaries.RealPosition()] = historicalSummaryRoot[:] } + if state.version >= version.Electra { + // DepositReceiptsStartIndex root. + drsiRoot := ssz.Uint64Root(state.depositReceiptsStartIndex) + fieldRoots[types.DepositReceiptsStartIndex.RealPosition()] = drsiRoot[:] + + // DepositBalanceToConsume root. + dbtcRoot := ssz.Uint64Root(uint64(state.depositBalanceToConsume)) + fieldRoots[types.DepositBalanceToConsume.RealPosition()] = dbtcRoot[:] + + // ExitBalanceToConsume root. + ebtcRoot := ssz.Uint64Root(uint64(state.exitBalanceToConsume)) + fieldRoots[types.ExitBalanceToConsume.RealPosition()] = ebtcRoot[:] + + // EarliestExitEpoch root. + eeeRoot := ssz.Uint64Root(uint64(state.earliestExitEpoch)) + fieldRoots[types.EarliestExitEpoch.RealPosition()] = eeeRoot[:] + + // ConsolidationBalanceToConsume root. + cbtcRoot := ssz.Uint64Root(uint64(state.consolidationBalanceToConsume)) + fieldRoots[types.ConsolidationBalanceToConsume.RealPosition()] = cbtcRoot[:] + + // EarliestConsolidationEpoch root. + eceRoot := ssz.Uint64Root(uint64(state.earliestConsolidationEpoch)) + fieldRoots[types.EarliestConsolidationEpoch.RealPosition()] = eceRoot[:] + + // PendingBalanceDeposits root. + pbdRoot, err := stateutil.PendingBalanceDepositsRoot(state.pendingBalanceDeposits) + if err != nil { + return nil, errors.Wrap(err, "could not compute pending balance deposits merkleization") + } + fieldRoots[types.PendingBalanceDeposits.RealPosition()] = pbdRoot[:] + + // PendingPartialWithdrawals root. + ppwRoot, err := stateutil.PendingPartialWithdrawalsRoot(state.pendingPartialWithdrawals) + if err != nil { + return nil, errors.Wrap(err, "could not compute pending partial withdrawals merkleization") + } + fieldRoots[types.PendingPartialWithdrawals.RealPosition()] = ppwRoot[:] + + // PendingConsolidations root. + pcRoot, err := stateutil.PendingConsolidationsRoot(state.pendingConsolidations) + if err != nil { + return nil, errors.Wrap(err, "could not compute pending consolidations merkleization") + } + fieldRoots[types.PendingConsolidations.RealPosition()] = pcRoot[:] + } + return fieldRoots, nil } diff --git a/beacon-chain/state/state-native/setters_balance_deposits.go b/beacon-chain/state/state-native/setters_balance_deposits.go new file mode 100644 index 0000000000..9bb398b13e --- /dev/null +++ b/beacon-chain/state/state-native/setters_balance_deposits.go @@ -0,0 +1,67 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +// AppendPendingBalanceDeposit is a mutating call to the beacon state to create and append a pending +// balance deposit object on to the state. This method requires access to the Lock on the state and +// only applies in electra or later. +func (b *BeaconState) AppendPendingBalanceDeposit(index primitives.ValidatorIndex, amount uint64) error { + if b.version < version.Electra { + return errNotSupported("AppendPendingBalanceDeposit", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[types.PendingBalanceDeposits].MinusRef() + b.sharedFieldReferences[types.PendingBalanceDeposits] = stateutil.NewRef(1) + + b.pendingBalanceDeposits = append(b.pendingBalanceDeposits, ðpb.PendingBalanceDeposit{Index: index, Amount: amount}) + + b.markFieldAsDirty(types.PendingBalanceDeposits) + b.rebuildTrie[types.PendingBalanceDeposits] = true + return nil +} + +// SetPendingBalanceDeposits is a mutating call to the beacon state which replaces the pending +// balance deposit slice with the provided value. This method requires access to the Lock on the +// state and only applies in electra or later. +func (b *BeaconState) SetPendingBalanceDeposits(val []*ethpb.PendingBalanceDeposit) error { + if b.version < version.Electra { + return errNotSupported("SetPendingBalanceDeposits", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[types.PendingBalanceDeposits].MinusRef() + b.sharedFieldReferences[types.PendingBalanceDeposits] = stateutil.NewRef(1) + + b.pendingBalanceDeposits = val + + b.markFieldAsDirty(types.PendingBalanceDeposits) + b.rebuildTrie[types.PendingBalanceDeposits] = true + return nil +} + +// SetDepositBalanceToConsume is a mutating call to the beacon state which sets the deposit balance +// to consume value to the given value. This method requires access to the Lock on the state and +// only applies in electra or later. +func (b *BeaconState) SetDepositBalanceToConsume(dbtc math.Gwei) error { + if b.version < version.Electra { + return errNotSupported("SetDepositBalanceToConsume", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.depositBalanceToConsume = dbtc + + b.markFieldAsDirty(types.DepositBalanceToConsume) + b.rebuildTrie[types.DepositBalanceToConsume] = true + return nil +} diff --git a/beacon-chain/state/state-native/setters_balance_deposits_test.go b/beacon-chain/state/state-native/setters_balance_deposits_test.go new file mode 100644 index 0000000000..7c2b201230 --- /dev/null +++ b/beacon-chain/state/state-native/setters_balance_deposits_test.go @@ -0,0 +1,61 @@ +package state_native_test + +import ( + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestAppendPendingBalanceDeposit(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + pbd, err := s.PendingBalanceDeposits() + require.NoError(t, err) + require.Equal(t, 0, len(pbd)) + require.NoError(t, s.AppendPendingBalanceDeposit(1, 10)) + pbd, err = s.PendingBalanceDeposits() + require.NoError(t, err) + require.Equal(t, 1, len(pbd)) + require.Equal(t, primitives.ValidatorIndex(1), pbd[0].Index) + require.Equal(t, uint64(10), pbd[0].Amount) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.AppendPendingBalanceDeposit(1, 1)) +} + +func TestSetPendingBalanceDeposits(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + pbd, err := s.PendingBalanceDeposits() + require.NoError(t, err) + require.Equal(t, 0, len(pbd)) + require.NoError(t, s.SetPendingBalanceDeposits([]*eth.PendingBalanceDeposit{{}, {}, {}})) + pbd, err = s.PendingBalanceDeposits() + require.NoError(t, err) + require.Equal(t, 3, len(pbd)) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.SetPendingBalanceDeposits([]*eth.PendingBalanceDeposit{{}, {}, {}})) +} + +func TestSetDepositBalanceToConsume(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + require.NoError(t, s.SetDepositBalanceToConsume(10)) + dbtc, err := s.DepositBalanceToConsume() + require.NoError(t, err) + require.Equal(t, math.Gwei(10), dbtc) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.SetDepositBalanceToConsume(10)) +} diff --git a/beacon-chain/state/state-native/setters_churn.go b/beacon-chain/state/state-native/setters_churn.go new file mode 100644 index 0000000000..bf7e67602a --- /dev/null +++ b/beacon-chain/state/state-native/setters_churn.go @@ -0,0 +1,80 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + "github.com/prysmaticlabs/prysm/v5/runtime/version" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +// ExitEpochAndUpdateChurn computes the exit epoch and updates the churn. This method mutates the state. +// +// Spec definition: +// +// def compute_exit_epoch_and_update_churn(state: BeaconState, exit_balance: Gwei) -> Epoch: +// earliest_exit_epoch = max(state.earliest_exit_epoch, compute_activation_exit_epoch(get_current_epoch(state))) +// per_epoch_churn = get_activation_exit_churn_limit(state) +// # New epoch for exits. +// if state.earliest_exit_epoch < earliest_exit_epoch: +// exit_balance_to_consume = per_epoch_churn +// else: +// exit_balance_to_consume = state.exit_balance_to_consume +// +// # Exit doesn't fit in the current earliest epoch. +// if exit_balance > exit_balance_to_consume: +// balance_to_process = exit_balance - exit_balance_to_consume +// additional_epochs = (balance_to_process - 1) // per_epoch_churn + 1 +// earliest_exit_epoch += additional_epochs +// exit_balance_to_consume += additional_epochs * per_epoch_churn +// +// # Consume the balance and update state variables. +// state.exit_balance_to_consume = exit_balance_to_consume - exit_balance +// state.earliest_exit_epoch = earliest_exit_epoch +// +// return state.earliest_exit_epoch +func (b *BeaconState) ExitEpochAndUpdateChurn(exitBalance math.Gwei) (primitives.Epoch, error) { + if b.version < version.Electra { + return 0, errNotSupported("ExitEpochAndUpdateChurn", b.version) + } + + // This helper requires access to the RLock and cannot be called from within the write Lock. + activeBal, err := helpers.TotalActiveBalance(b) + if err != nil { + return 0, err + } + + b.lock.Lock() + defer b.lock.Unlock() + + earliestExitEpoch := max(b.earliestExitEpoch, helpers.ActivationExitEpoch(slots.ToEpoch(b.slot))) + perEpochChurn := helpers.ActivationExitChurnLimit(math.Gwei(activeBal)) // Guaranteed to be non-zero. + + // New epoch for exits + var exitBalanceToConsume math.Gwei + if b.earliestExitEpoch < earliestExitEpoch { + exitBalanceToConsume = perEpochChurn + } else { + exitBalanceToConsume = b.exitBalanceToConsume + } + + // Exit doesn't fit in the current earliest epoch. + if exitBalance > exitBalanceToConsume { + balanceToProcess := exitBalance - exitBalanceToConsume + additionalEpochs := primitives.Epoch((balanceToProcess-1)/perEpochChurn + 1) + earliestExitEpoch += additionalEpochs + exitBalanceToConsume += math.Gwei(additionalEpochs) * perEpochChurn + } + + // Consume the balance and update state variables. + b.exitBalanceToConsume = exitBalanceToConsume - exitBalance + b.earliestExitEpoch = earliestExitEpoch + + b.markFieldAsDirty(types.ExitBalanceToConsume) + b.rebuildTrie[types.ExitBalanceToConsume] = true + b.markFieldAsDirty(types.EarliestExitEpoch) + b.rebuildTrie[types.EarliestExitEpoch] = true + + return b.earliestExitEpoch, nil +} diff --git a/beacon-chain/state/state-native/setters_churn_test.go b/beacon-chain/state/state-native/setters_churn_test.go new file mode 100644 index 0000000000..2aa98382c4 --- /dev/null +++ b/beacon-chain/state/state-native/setters_churn_test.go @@ -0,0 +1,200 @@ +package state_native_test + +import ( + "testing" + + "github.com/golang/snappy" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/core/helpers" + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" + "github.com/prysmaticlabs/prysm/v5/time/slots" +) + +func TestExitEpochAndUpdateChurn_SpectestCase(t *testing.T) { + // Load a serialized Electra state from disk. + // The spec tests shows that the exit epoch is 262 for validator 0 performing a voluntary exit. + serializedBytes, err := util.BazelFileBytes("tests/mainnet/electra/operations/voluntary_exit/pyspec_tests/exit_existing_churn_and_churn_limit_balance/pre.ssz_snappy") + require.NoError(t, err) + serializedSSZ, err := snappy.Decode(nil /* dst */, serializedBytes) + require.NoError(t, err) + pb := ð.BeaconStateElectra{} + require.NoError(t, pb.UnmarshalSSZ(serializedSSZ)) + s, err := state_native.InitializeFromProtoElectra(pb) + require.NoError(t, err) + + val, err := s.ValidatorAtIndex(0) + require.NoError(t, err) + + ee, err := s.ExitEpochAndUpdateChurn(math.Gwei(val.EffectiveBalance)) + require.NoError(t, err) + require.Equal(t, primitives.Epoch(262), ee) + + p := s.ToProto() + pb, ok := p.(*eth.BeaconStateElectra) + if !ok { + t.Fatal("wrong proto") + } + require.Equal(t, math.Gwei(127000000000), pb.ExitBalanceToConsume) + require.Equal(t, primitives.Epoch(262), pb.EarliestExitEpoch) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = s.ExitEpochAndUpdateChurn(10) + require.ErrorContains(t, "not supported", err) +} + +func TestExitEpochAndUpdateChurn(t *testing.T) { + slot := primitives.Slot(10_000_000) + epoch := slots.ToEpoch(slot) + t.Run("state earliest exit epoch is old", func(t *testing.T) { + st, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + Slot: slot, + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra, + }, + }, + Balances: []uint64{params.BeaconConfig().MaxEffectiveBalanceElectra}, + EarliestExitEpoch: epoch - params.BeaconConfig().MaxSeedLookahead*2, // Old, relative to slot. + ExitBalanceToConsume: math.Gwei(20_000_000), + }) + require.NoError(t, err) + activeBal, err := helpers.TotalActiveBalance(st) + require.NoError(t, err) + + exitBal := math.Gwei(10_000_000) + + wantExitBalToConsume := helpers.ActivationExitChurnLimit(math.Gwei(activeBal)) - exitBal + + ee, err := st.ExitEpochAndUpdateChurn(exitBal) + require.NoError(t, err) + + wantExitEpoch := helpers.ActivationExitEpoch(epoch) + require.Equal(t, wantExitEpoch, ee) + + p := st.ToProto() + pb, ok := p.(*eth.BeaconStateElectra) + if !ok { + t.Fatal("wrong proto") + } + require.Equal(t, wantExitBalToConsume, pb.ExitBalanceToConsume) + require.Equal(t, wantExitEpoch, pb.EarliestExitEpoch) + }) + + t.Run("state exit bal to consume is less than activation exit churn limit", func(t *testing.T) { + st, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + Slot: slot, + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra, + }, + }, + Balances: []uint64{params.BeaconConfig().MaxEffectiveBalanceElectra}, + EarliestExitEpoch: epoch, + ExitBalanceToConsume: math.Gwei(20_000_000), + }) + require.NoError(t, err) + activeBal, err := helpers.TotalActiveBalance(st) + require.NoError(t, err) + + activationExitChurnLimit := helpers.ActivationExitChurnLimit(math.Gwei(activeBal)) + exitBal := activationExitChurnLimit * 2 + + wantExitBalToConsume := math.Gwei(0) + + ee, err := st.ExitEpochAndUpdateChurn(exitBal) + require.NoError(t, err) + + wantExitEpoch := helpers.ActivationExitEpoch(epoch) + 1 + require.Equal(t, wantExitEpoch, ee) + + p := st.ToProto() + pb, ok := p.(*eth.BeaconStateElectra) + if !ok { + t.Fatal("wrong proto") + } + require.Equal(t, wantExitBalToConsume, pb.ExitBalanceToConsume) + require.Equal(t, wantExitEpoch, pb.EarliestExitEpoch) + }) + + t.Run("state earliest exit epoch is in the future and exit balance is less than state", func(t *testing.T) { + st, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + Slot: slot, + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra, + }, + }, + Balances: []uint64{params.BeaconConfig().MaxEffectiveBalanceElectra}, + EarliestExitEpoch: epoch + 10_000, + ExitBalanceToConsume: math.Gwei(20_000_000), + }) + require.NoError(t, err) + + exitBal := math.Gwei(10_000_000) + + wantExitBalToConsume := math.Gwei(20_000_000) - exitBal + + ee, err := st.ExitEpochAndUpdateChurn(exitBal) + require.NoError(t, err) + + wantExitEpoch := epoch + 10_000 + require.Equal(t, wantExitEpoch, ee) + + p := st.ToProto() + pb, ok := p.(*eth.BeaconStateElectra) + if !ok { + t.Fatal("wrong proto") + } + require.Equal(t, wantExitBalToConsume, pb.ExitBalanceToConsume) + require.Equal(t, wantExitEpoch, pb.EarliestExitEpoch) + }) + + t.Run("state earliest exit epoch is in the future and exit balance exceeds state", func(t *testing.T) { + st, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + Slot: slot, + Validators: []*eth.Validator{ + { + EffectiveBalance: params.BeaconConfig().MaxEffectiveBalanceElectra, + }, + }, + Balances: []uint64{params.BeaconConfig().MaxEffectiveBalanceElectra}, + EarliestExitEpoch: epoch + 10_000, + ExitBalanceToConsume: math.Gwei(20_000_000), + }) + require.NoError(t, err) + + exitBal := math.Gwei(40_000_000) + activeBal, err := helpers.TotalActiveBalance(st) + require.NoError(t, err) + activationExitChurnLimit := helpers.ActivationExitChurnLimit(math.Gwei(activeBal)) + wantExitBalToConsume := activationExitChurnLimit - 20_000_000 + + ee, err := st.ExitEpochAndUpdateChurn(exitBal) + require.NoError(t, err) + + wantExitEpoch := epoch + 10_000 + 1 + require.Equal(t, wantExitEpoch, ee) + + p := st.ToProto() + pb, ok := p.(*eth.BeaconStateElectra) + if !ok { + t.Fatal("wrong proto") + } + require.Equal(t, wantExitBalToConsume, pb.ExitBalanceToConsume) + require.Equal(t, wantExitEpoch, pb.EarliestExitEpoch) + }) + + t.Run("earlier than electra returns error", func(t *testing.T) { + st, err := state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = st.ExitEpochAndUpdateChurn(0) + require.ErrorContains(t, "is not supported", err) + }) +} diff --git a/beacon-chain/state/state-native/setters_consolidation.go b/beacon-chain/state/state-native/setters_consolidation.go new file mode 100644 index 0000000000..c76e1ae5c0 --- /dev/null +++ b/beacon-chain/state/state-native/setters_consolidation.go @@ -0,0 +1,84 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +// AppendPendingConsolidation is a mutating call to the beacon state which appends the provided +// pending consolidation to the end of the slice on the state. This method requires access to the +// Lock on the state and only applies in electra or later. +func (b *BeaconState) AppendPendingConsolidation(val *ethpb.PendingConsolidation) error { + if b.version < version.Electra { + return errNotSupported("AppendPendingConsolidation", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[types.PendingConsolidations].MinusRef() + b.sharedFieldReferences[types.PendingConsolidations] = stateutil.NewRef(1) + + b.pendingConsolidations = append(b.pendingConsolidations, val) + + b.markFieldAsDirty(types.PendingConsolidations) + b.rebuildTrie[types.PendingConsolidations] = true + return nil +} + +// SetPendingConsolidations is a mutating call to the beacon state which replaces the slice on the +// state with the given value. This method requires access to the Lock on the state and only applies +// in electra or later. +func (b *BeaconState) SetPendingConsolidations(val []*ethpb.PendingConsolidation) error { + if b.version < version.Electra { + return errNotSupported("SetPendingConsolidations", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[types.PendingConsolidations].MinusRef() + b.sharedFieldReferences[types.PendingConsolidations] = stateutil.NewRef(1) + + b.pendingConsolidations = val + + b.markFieldAsDirty(types.PendingConsolidations) + b.rebuildTrie[types.PendingConsolidations] = true + return nil +} + +// SetEarliestConsolidationEpoch is a mutating call to the beacon state which sets the earlest +// consolidation epoch value. This method requires access to the Lock on the state and only applies +// in electra or later. +func (b *BeaconState) SetEarliestConsolidationEpoch(epoch primitives.Epoch) error { + if b.version < version.Electra { + return errNotSupported("SetEarliestConsolidationEpoch", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.earliestConsolidationEpoch = epoch + + b.markFieldAsDirty(types.EarliestConsolidationEpoch) + b.rebuildTrie[types.EarliestConsolidationEpoch] = true + return nil +} + +// SetConsolidationBalanceToConsume is a mutating call to the beacon state which sets the value of +// the consolidation balance to consume to the provided value. This method requires access to the +// Lock on the state and only applies in electra or later. +func (b *BeaconState) SetConsolidationBalanceToConsume(balance math.Gwei) error { + if b.version < version.Electra { + return errNotSupported("SetConsolidationBalanceToConsume", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + b.consolidationBalanceToConsume = balance + + b.markFieldAsDirty(types.ConsolidationBalanceToConsume) + b.rebuildTrie[types.ConsolidationBalanceToConsume] = true + return nil +} diff --git a/beacon-chain/state/state-native/setters_consolidation_test.go b/beacon-chain/state/state-native/setters_consolidation_test.go new file mode 100644 index 0000000000..1a2ee153ce --- /dev/null +++ b/beacon-chain/state/state-native/setters_consolidation_test.go @@ -0,0 +1,76 @@ +package state_native_test + +import ( + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + "github.com/prysmaticlabs/prysm/v5/math" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +func TestAppendPendingConsolidation(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + num, err := s.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(0), num) + require.NoError(t, s.AppendPendingConsolidation(ð.PendingConsolidation{})) + num, err = s.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(1), num) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.AppendPendingConsolidation(ð.PendingConsolidation{})) +} + +func TestSetPendingConsolidations(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + num, err := s.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(0), num) + require.NoError(t, s.SetPendingConsolidations([]*eth.PendingConsolidation{{}, {}, {}})) + num, err = s.NumPendingConsolidations() + require.NoError(t, err) + require.Equal(t, uint64(3), num) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.SetPendingConsolidations([]*eth.PendingConsolidation{{}, {}, {}})) +} + +func TestSetEarliestConsolidationEpoch(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + ece, err := s.EarliestConsolidationEpoch() + require.NoError(t, err) + require.Equal(t, primitives.Epoch(0), ece) + require.NoError(t, s.SetEarliestConsolidationEpoch(10)) + ece, err = s.EarliestConsolidationEpoch() + require.NoError(t, err) + require.Equal(t, primitives.Epoch(10), ece) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.SetEarliestConsolidationEpoch(10)) +} + +func TestSetConsolidationBalanceToConsume(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{}) + require.NoError(t, err) + require.NoError(t, s.SetConsolidationBalanceToConsume(10)) + cbtc, err := s.ConsolidationBalanceToConsume() + require.NoError(t, err) + require.Equal(t, math.Gwei(10), cbtc) + + // Fails for versions older than electra + s, err = state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + require.ErrorContains(t, "not supported", s.SetConsolidationBalanceToConsume(10)) +} diff --git a/beacon-chain/state/state-native/setters_deposit_receipts.go b/beacon-chain/state/state-native/setters_deposit_receipts.go new file mode 100644 index 0000000000..bd24820a99 --- /dev/null +++ b/beacon-chain/state/state-native/setters_deposit_receipts.go @@ -0,0 +1,21 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +// SetDepositReceiptsStartIndex for the beacon state. Updates the DepositReceiptsStartIndex +func (b *BeaconState) SetDepositReceiptsStartIndex(index uint64) error { + if b.version < version.Electra { + return errNotSupported("SetDepositReceiptsStartIndex", b.version) + } + + b.lock.Lock() + defer b.lock.Unlock() + + b.depositReceiptsStartIndex = index + b.markFieldAsDirty(types.DepositReceiptsStartIndex) + b.rebuildTrie[types.DepositReceiptsStartIndex] = true + return nil +} diff --git a/beacon-chain/state/state-native/setters_deposit_receipts_test.go b/beacon-chain/state/state-native/setters_deposit_receipts_test.go new file mode 100644 index 0000000000..8c02bfd5b6 --- /dev/null +++ b/beacon-chain/state/state-native/setters_deposit_receipts_test.go @@ -0,0 +1,27 @@ +package state_native_test + +import ( + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" +) + +func TestSetDepositReceiptsStartIndex(t *testing.T) { + t.Run("previous fork returns expected error", func(t *testing.T) { + dState, _ := util.DeterministicGenesisState(t, 1) + require.ErrorContains(t, "is not supported", dState.SetDepositReceiptsStartIndex(1)) + }) + t.Run("electra sets expected value", func(t *testing.T) { + old := uint64(2) + dState, err := state_native.InitializeFromProtoElectra(ðpb.BeaconStateElectra{DepositReceiptsStartIndex: old}) + require.NoError(t, err) + want := uint64(3) + require.NoError(t, dState.SetDepositReceiptsStartIndex(want)) + got, err := dState.DepositReceiptsStartIndex() + require.NoError(t, err) + require.Equal(t, want, got) + }) +} diff --git a/beacon-chain/state/state-native/setters_payload_header.go b/beacon-chain/state/state-native/setters_payload_header.go index 3b36f7f5b6..9187aec5ba 100644 --- a/beacon-chain/state/state-native/setters_payload_header.go +++ b/beacon-chain/state/state-native/setters_payload_header.go @@ -1,6 +1,8 @@ package state_native import ( + "fmt" + "github.com/pkg/errors" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" consensusblocks "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" @@ -21,6 +23,9 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa switch header := val.Proto().(type) { case *enginev1.ExecutionPayload: + if b.version != version.Bellatrix { + return fmt.Errorf("wrong state version (%s) for bellatrix execution payload", version.String(b.version)) + } latest, err := consensusblocks.PayloadToHeader(val) if err != nil { return errors.Wrap(err, "could not convert payload to header") @@ -29,6 +34,9 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.markFieldAsDirty(types.LatestExecutionPayloadHeader) return nil case *enginev1.ExecutionPayloadCapella: + if b.version != version.Capella { + return fmt.Errorf("wrong state version (%s) for capella execution payload", version.String(b.version)) + } latest, err := consensusblocks.PayloadToHeaderCapella(val) if err != nil { return errors.Wrap(err, "could not convert payload to header") @@ -37,6 +45,9 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.markFieldAsDirty(types.LatestExecutionPayloadHeaderCapella) return nil case *enginev1.ExecutionPayloadDeneb: + if b.version != version.Deneb { + return fmt.Errorf("wrong state version (%s) for deneb execution payload", version.String(b.version)) + } latest, err := consensusblocks.PayloadToHeaderDeneb(val) if err != nil { return errors.Wrap(err, "could not convert payload to header") @@ -44,18 +55,49 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.latestExecutionPayloadHeaderDeneb = latest b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb) return nil + case *enginev1.ExecutionPayloadElectra: + if b.version != version.Electra { + return fmt.Errorf("wrong state version (%s) for electra execution payload", version.String(b.version)) + } + eVal, ok := val.(interfaces.ExecutionDataElectra) + if !ok { + return fmt.Errorf("could not cast %T to ExecutionDataElectra: %w", val, interfaces.ErrInvalidCast) + } + latest, err := consensusblocks.PayloadToHeaderElectra(eVal) + if err != nil { + return errors.Wrap(err, "could not convert payload to header") + } + b.latestExecutionPayloadHeaderElectra = latest + b.markFieldAsDirty(types.LatestExecutionPayloadHeaderElectra) + return nil case *enginev1.ExecutionPayloadHeader: + if b.version != version.Bellatrix { + return fmt.Errorf("wrong state version (%s) for bellatrix execution payload header", version.String(b.version)) + } b.latestExecutionPayloadHeader = header b.markFieldAsDirty(types.LatestExecutionPayloadHeader) return nil case *enginev1.ExecutionPayloadHeaderCapella: + if b.version != version.Capella { + return fmt.Errorf("wrong state version (%s) for capella execution payload header", version.String(b.version)) + } b.latestExecutionPayloadHeaderCapella = header b.markFieldAsDirty(types.LatestExecutionPayloadHeaderCapella) return nil case *enginev1.ExecutionPayloadHeaderDeneb: + if b.version != version.Deneb { + return fmt.Errorf("wrong state version (%s) for deneb execution payload header", version.String(b.version)) + } b.latestExecutionPayloadHeaderDeneb = header b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb) return nil + case *enginev1.ExecutionPayloadHeaderElectra: + if b.version != version.Electra { + return fmt.Errorf("wrong state version (%s) for electra execution payload header", version.String(b.version)) + } + b.latestExecutionPayloadHeaderElectra = header + b.markFieldAsDirty(types.LatestExecutionPayloadHeaderElectra) + return nil default: return errors.New("value must be an execution payload header") } diff --git a/beacon-chain/state/state-native/setters_payload_header_test.go b/beacon-chain/state/state-native/setters_payload_header_test.go new file mode 100644 index 0000000000..d8d9fb17cb --- /dev/null +++ b/beacon-chain/state/state-native/setters_payload_header_test.go @@ -0,0 +1,107 @@ +package state_native_test + +import ( + "fmt" + "testing" + + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" + "github.com/prysmaticlabs/prysm/v5/consensus-types/blocks" + "github.com/prysmaticlabs/prysm/v5/consensus-types/interfaces" + "github.com/prysmaticlabs/prysm/v5/runtime/version" + "github.com/prysmaticlabs/prysm/v5/testing/require" + "github.com/prysmaticlabs/prysm/v5/testing/util" +) + +func TestSetLatestExecutionPayloadHeader(t *testing.T) { + versionOffset := version.Bellatrix // PayloadHeader only applies in Bellatrix and beyond. + payloads := []interfaces.ExecutionData{ + func() interfaces.ExecutionData { + e := util.NewBeaconBlockBellatrix().Block.Body.ExecutionPayload + ee, err := blocks.WrappedExecutionPayload(e) + require.NoError(t, err) + return ee + }(), + func() interfaces.ExecutionData { + e := util.NewBeaconBlockCapella().Block.Body.ExecutionPayload + ee, err := blocks.WrappedExecutionPayloadCapella(e, nil) + require.NoError(t, err) + return ee + }(), + func() interfaces.ExecutionData { + e := util.NewBeaconBlockDeneb().Block.Body.ExecutionPayload + ee, err := blocks.WrappedExecutionPayloadDeneb(e, nil) + require.NoError(t, err) + return ee + }(), + func() interfaces.ExecutionData { + e := util.NewBeaconBlockElectra().Block.Body.ExecutionPayload + ee, err := blocks.WrappedExecutionPayloadElectra(e, nil) + require.NoError(t, err) + return ee + }(), + } + + payloadHeaders := []interfaces.ExecutionData{ + func() interfaces.ExecutionData { + e := util.NewBlindedBeaconBlockBellatrix().Block.Body.ExecutionPayloadHeader + ee, err := blocks.WrappedExecutionPayloadHeader(e) + require.NoError(t, err) + return ee + }(), + func() interfaces.ExecutionData { + e := util.NewBlindedBeaconBlockCapella().Block.Body.ExecutionPayloadHeader + ee, err := blocks.WrappedExecutionPayloadHeaderCapella(e, nil) + require.NoError(t, err) + return ee + }(), + func() interfaces.ExecutionData { + e := util.NewBlindedBeaconBlockDeneb().Message.Body.ExecutionPayloadHeader + ee, err := blocks.WrappedExecutionPayloadHeaderDeneb(e, nil) + require.NoError(t, err) + return ee + }(), + func() interfaces.ExecutionData { + e := util.NewBlindedBeaconBlockElectra().Message.Body.ExecutionPayloadHeader + ee, err := blocks.WrappedExecutionPayloadHeaderElectra(e, nil) + require.NoError(t, err) + return ee + }(), + } + + t.Run("can set payload", func(t *testing.T) { + for i, p := range payloads { + t.Run(version.String(i+versionOffset), func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, i+versionOffset) + require.NoError(t, s.SetLatestExecutionPayloadHeader(p)) + }) + } + }) + + t.Run("can set payload header", func(t *testing.T) { + for i, ph := range payloadHeaders { + t.Run(version.String(i+versionOffset), func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, i+versionOffset) + require.NoError(t, s.SetLatestExecutionPayloadHeader(ph)) + }) + } + }) + + t.Run("mismatched type version returns error", func(t *testing.T) { + require.Equal(t, len(payloads), len(payloadHeaders), "This test will fail if the payloads and payload headers are not same length") + for i := 0; i < len(payloads); i++ { + for j := 0; j < len(payloads); j++ { + if i == j { + continue + } + t.Run(fmt.Sprintf("%s state with %s payload", version.String(i+versionOffset), version.String(j+versionOffset)), func(t *testing.T) { + s := state_native.EmptyStateFromVersion(t, i+versionOffset) + p := payloads[j] + require.ErrorContains(t, "wrong state version", s.SetLatestExecutionPayloadHeader(p)) + ph := payloadHeaders[j] + require.ErrorContains(t, "wrong state version", s.SetLatestExecutionPayloadHeader(ph)) + }) + } + } + }) + +} diff --git a/beacon-chain/state/state-native/setters_withdrawal.go b/beacon-chain/state/state-native/setters_withdrawal.go index 5548fe0140..36b97a8267 100644 --- a/beacon-chain/state/state-native/setters_withdrawal.go +++ b/beacon-chain/state/state-native/setters_withdrawal.go @@ -1,8 +1,12 @@ package state_native import ( + "errors" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/stateutil" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" ) @@ -34,3 +38,56 @@ func (b *BeaconState) SetNextWithdrawalValidatorIndex(i primitives.ValidatorInde b.markFieldAsDirty(types.NextWithdrawalValidatorIndex) return nil } + +// AppendPendingPartialWithdrawal is a mutating call to the beacon state which appends the given +// value to the end of the pending partial withdrawals slice in the state. This method requires +// access to the Lock on the state and only applies in electra or later. +func (b *BeaconState) AppendPendingPartialWithdrawal(ppw *eth.PendingPartialWithdrawal) error { + if b.version < version.Electra { + return errNotSupported("AppendPendingPartialWithdrawal", b.version) + } + + if ppw == nil { + return errors.New("cannot append nil pending partial withdrawal") + } + + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[types.PendingPartialWithdrawals].MinusRef() + b.sharedFieldReferences[types.PendingPartialWithdrawals] = stateutil.NewRef(1) + + b.pendingPartialWithdrawals = append(b.pendingPartialWithdrawals, ppw) + + b.markFieldAsDirty(types.PendingPartialWithdrawals) + b.rebuildTrie[types.PendingPartialWithdrawals] = true + return nil +} + +// DequeuePartialWithdrawals removes the partial withdrawals from the beginning of the partial withdrawals list. +func (b *BeaconState) DequeuePartialWithdrawals(n uint64) error { + if b.version < version.Electra { + return errNotSupported("DequeuePartialWithdrawals", b.version) + } + + if n > uint64(len(b.pendingPartialWithdrawals)) { + return errors.New("cannot dequeue more withdrawals than are in the queue") + } + + if n == 0 { + return nil // Don't wait on a lock for no reason. + } + + b.lock.Lock() + defer b.lock.Unlock() + + b.sharedFieldReferences[types.PendingPartialWithdrawals].MinusRef() + b.sharedFieldReferences[types.PendingPartialWithdrawals] = stateutil.NewRef(1) + + b.pendingPartialWithdrawals = b.pendingPartialWithdrawals[n:] + + b.markFieldAsDirty(types.PendingPartialWithdrawals) + b.rebuildTrie[types.PendingPartialWithdrawals] = true + + return nil +} diff --git a/beacon-chain/state/state-native/setters_withdrawal_test.go b/beacon-chain/state/state-native/setters_withdrawal_test.go index 8cb3ec2639..d7627d3975 100644 --- a/beacon-chain/state/state-native/setters_withdrawal_test.go +++ b/beacon-chain/state/state-native/setters_withdrawal_test.go @@ -5,6 +5,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + eth "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" "github.com/prysmaticlabs/prysm/v5/runtime/version" "github.com/prysmaticlabs/prysm/v5/testing/require" ) @@ -52,3 +53,68 @@ func TestSetNextWithdrawalValidatorIndex_Deneb(t *testing.T) { require.Equal(t, primitives.ValidatorIndex(5), s.nextWithdrawalValidatorIndex) require.Equal(t, true, s.dirtyFields[types.NextWithdrawalValidatorIndex]) } + +func TestDequeuePendingWithdrawals(t *testing.T) { + s, err := InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingPartialWithdrawals: []*eth.PendingPartialWithdrawal{ + {}, + {}, + {}, + }, + }) + require.NoError(t, err) + + // 2 of 3 should be OK + num, err := s.NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(3), num) + require.NoError(t, s.DequeuePartialWithdrawals(2)) + num, err = s.NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(1), num) + + // 2 of 1 exceeds the limit and an error should be returned + num, err = s.NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(1), num) + require.ErrorContains(t, "cannot dequeue more withdrawals than are in the queue", s.DequeuePartialWithdrawals(2)) + + // Removing all pending partial withdrawals should be OK. + num, err = s.NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(1), num) + require.NoError(t, s.DequeuePartialWithdrawals(1)) + num, err = s.Copy().NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(0), num) + + s, err = InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + + require.ErrorContains(t, "is not supported", s.DequeuePartialWithdrawals(0)) +} + +func TestAppendPendingWithdrawals(t *testing.T) { + s, err := InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingPartialWithdrawals: []*eth.PendingPartialWithdrawal{ + {}, + {}, + {}, + }, + }) + require.NoError(t, err) + num, err := s.NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(3), num) + require.NoError(t, s.AppendPendingPartialWithdrawal(ð.PendingPartialWithdrawal{})) + num, err = s.NumPendingPartialWithdrawals() + require.NoError(t, err) + require.Equal(t, uint64(4), num) + + require.ErrorContains(t, "cannot append nil pending partial withdrawal", s.AppendPendingPartialWithdrawal(nil)) + + s, err = InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + + require.ErrorContains(t, "is not supported", s.AppendPendingPartialWithdrawal(nil)) +} diff --git a/beacon-chain/state/state-native/spec_parameters.go b/beacon-chain/state/state-native/spec_parameters.go index 697afd6e73..1612a71efb 100644 --- a/beacon-chain/state/state-native/spec_parameters.go +++ b/beacon-chain/state/state-native/spec_parameters.go @@ -7,7 +7,7 @@ import ( func (b *BeaconState) ProportionalSlashingMultiplier() (uint64, error) { switch b.version { - case version.Bellatrix, version.Capella, version.Deneb: + case version.Bellatrix, version.Capella, version.Deneb, version.Electra: return params.BeaconConfig().ProportionalSlashingMultiplierBellatrix, nil case version.Altair: return params.BeaconConfig().ProportionalSlashingMultiplierAltair, nil @@ -19,7 +19,7 @@ func (b *BeaconState) ProportionalSlashingMultiplier() (uint64, error) { func (b *BeaconState) InactivityPenaltyQuotient() (uint64, error) { switch b.version { - case version.Bellatrix, version.Capella, version.Deneb: + case version.Bellatrix, version.Capella, version.Deneb, version.Electra: return params.BeaconConfig().InactivityPenaltyQuotientBellatrix, nil case version.Altair: return params.BeaconConfig().InactivityPenaltyQuotientAltair, nil diff --git a/beacon-chain/state/state-native/state_test.go b/beacon-chain/state/state-native/state_test.go index faed7b7f6a..f5ddef9d32 100644 --- a/beacon-chain/state/state-native/state_test.go +++ b/beacon-chain/state/state-native/state_test.go @@ -502,3 +502,13 @@ func generateState(t *testing.T) state.BeaconState { assert.NoError(t, err) return newState } + +func EmptyStateFromVersion(t *testing.T, v int) state.BeaconState { + gen := generateState(t) + s, ok := gen.(*BeaconState) + if !ok { + t.Fatal("not a beacon state") + } + s.version = v + return s +} diff --git a/beacon-chain/state/state-native/state_trie.go b/beacon-chain/state/state-native/state_trie.go index 93fa41f311..60dc8acd60 100644 --- a/beacon-chain/state/state-native/state_trie.go +++ b/beacon-chain/state/state-native/state_trie.go @@ -2,6 +2,7 @@ package state_native import ( "context" + "fmt" "runtime" "sort" @@ -93,17 +94,36 @@ var denebFields = append( types.HistoricalSummaries, ) +var electraFields = append( + altairFields, + types.NextWithdrawalIndex, + types.NextWithdrawalValidatorIndex, + types.HistoricalSummaries, + types.LatestExecutionPayloadHeaderElectra, + types.DepositReceiptsStartIndex, + types.DepositBalanceToConsume, + types.ExitBalanceToConsume, + types.EarliestExitEpoch, + types.ConsolidationBalanceToConsume, + types.EarliestConsolidationEpoch, + types.PendingBalanceDeposits, + types.PendingPartialWithdrawals, + types.PendingConsolidations, +) + const ( phase0SharedFieldRefCount = 10 altairSharedFieldRefCount = 11 bellatrixSharedFieldRefCount = 12 capellaSharedFieldRefCount = 14 denebSharedFieldRefCount = 14 + electraSharedFieldRefCount = 17 experimentalStatePhase0SharedFieldRefCount = 5 experimentalStateAltairSharedFieldRefCount = 5 experimentalStateBellatrixSharedFieldRefCount = 6 experimentalStateCapellaSharedFieldRefCount = 8 experimentalStateDenebSharedFieldRefCount = 8 + experimentalStateElectraSharedFieldRefCount = 11 ) // InitializeFromProtoPhase0 the beacon state from a protobuf representation. @@ -131,6 +151,10 @@ func InitializeFromProtoDeneb(st *ethpb.BeaconStateDeneb) (state.BeaconState, er return InitializeFromProtoUnsafeDeneb(proto.Clone(st).(*ethpb.BeaconStateDeneb)) } +func InitializeFromProtoElectra(st *ethpb.BeaconStateElectra) (state.BeaconState, error) { + return InitializeFromProtoUnsafeElectra(proto.Clone(st).(*ethpb.BeaconStateElectra)) +} + // InitializeFromProtoUnsafePhase0 directly uses the beacon state protobuf fields // and sets them as fields of the BeaconState type. func InitializeFromProtoUnsafePhase0(st *ethpb.BeaconState) (state.BeaconState, error) { @@ -683,6 +707,131 @@ func InitializeFromProtoUnsafeDeneb(st *ethpb.BeaconStateDeneb) (state.BeaconSta return b, nil } +// InitializeFromProtoUnsafeElectra directly uses the beacon state protobuf fields +// and sets them as fields of the BeaconState type. +func InitializeFromProtoUnsafeElectra(st *ethpb.BeaconStateElectra) (state.BeaconState, error) { + if st == nil { + return nil, errors.New("received nil state") + } + + hRoots := customtypes.HistoricalRoots(make([][32]byte, len(st.HistoricalRoots))) + for i, r := range st.HistoricalRoots { + hRoots[i] = bytesutil.ToBytes32(r) + } + + fieldCount := params.BeaconConfig().BeaconStateElectraFieldCount + b := &BeaconState{ + version: version.Electra, + genesisTime: st.GenesisTime, + genesisValidatorsRoot: bytesutil.ToBytes32(st.GenesisValidatorsRoot), + slot: st.Slot, + fork: st.Fork, + latestBlockHeader: st.LatestBlockHeader, + historicalRoots: hRoots, + eth1Data: st.Eth1Data, + eth1DataVotes: st.Eth1DataVotes, + eth1DepositIndex: st.Eth1DepositIndex, + slashings: st.Slashings, + previousEpochParticipation: st.PreviousEpochParticipation, + currentEpochParticipation: st.CurrentEpochParticipation, + justificationBits: st.JustificationBits, + previousJustifiedCheckpoint: st.PreviousJustifiedCheckpoint, + currentJustifiedCheckpoint: st.CurrentJustifiedCheckpoint, + finalizedCheckpoint: st.FinalizedCheckpoint, + currentSyncCommittee: st.CurrentSyncCommittee, + nextSyncCommittee: st.NextSyncCommittee, + latestExecutionPayloadHeaderElectra: st.LatestExecutionPayloadHeader, + nextWithdrawalIndex: st.NextWithdrawalIndex, + nextWithdrawalValidatorIndex: st.NextWithdrawalValidatorIndex, + historicalSummaries: st.HistoricalSummaries, + depositReceiptsStartIndex: st.DepositReceiptsStartIndex, + depositBalanceToConsume: st.DepositBalanceToConsume, + exitBalanceToConsume: st.ExitBalanceToConsume, + earliestExitEpoch: st.EarliestExitEpoch, + consolidationBalanceToConsume: st.ConsolidationBalanceToConsume, + earliestConsolidationEpoch: st.EarliestConsolidationEpoch, + pendingBalanceDeposits: st.PendingBalanceDeposits, + pendingPartialWithdrawals: st.PendingPartialWithdrawals, + pendingConsolidations: st.PendingConsolidations, + + dirtyFields: make(map[types.FieldIndex]bool, fieldCount), + dirtyIndices: make(map[types.FieldIndex][]uint64, fieldCount), + stateFieldLeaves: make(map[types.FieldIndex]*fieldtrie.FieldTrie, fieldCount), + rebuildTrie: make(map[types.FieldIndex]bool, fieldCount), + valMapHandler: stateutil.NewValMapHandler(st.Validators), + } + + if features.Get().EnableExperimentalState { + b.blockRootsMultiValue = NewMultiValueBlockRoots(st.BlockRoots) + b.stateRootsMultiValue = NewMultiValueStateRoots(st.StateRoots) + b.randaoMixesMultiValue = NewMultiValueRandaoMixes(st.RandaoMixes) + b.balancesMultiValue = NewMultiValueBalances(st.Balances) + b.validatorsMultiValue = NewMultiValueValidators(st.Validators) + b.inactivityScoresMultiValue = NewMultiValueInactivityScores(st.InactivityScores) + b.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, experimentalStateElectraSharedFieldRefCount) + } else { + bRoots := make([][32]byte, fieldparams.BlockRootsLength) + for i, r := range st.BlockRoots { + bRoots[i] = bytesutil.ToBytes32(r) + } + b.blockRoots = bRoots + + sRoots := make([][32]byte, fieldparams.StateRootsLength) + for i, r := range st.StateRoots { + sRoots[i] = bytesutil.ToBytes32(r) + } + b.stateRoots = sRoots + + mixes := make([][32]byte, fieldparams.RandaoMixesLength) + for i, m := range st.RandaoMixes { + mixes[i] = bytesutil.ToBytes32(m) + } + b.randaoMixes = mixes + + b.balances = st.Balances + b.validators = st.Validators + b.inactivityScores = st.InactivityScores + + b.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, electraSharedFieldRefCount) + } + + for _, f := range electraFields { + b.dirtyFields[f] = true + b.rebuildTrie[f] = true + b.dirtyIndices[f] = []uint64{} + trie, err := fieldtrie.NewFieldTrie(f, types.BasicArray, nil, 0) + if err != nil { + return nil, err + } + b.stateFieldLeaves[f] = trie + } + + // Initialize field reference tracking for shared data. + b.sharedFieldReferences[types.HistoricalRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Eth1DataVotes] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Slashings] = stateutil.NewRef(1) + b.sharedFieldReferences[types.PreviousEpochParticipationBits] = stateutil.NewRef(1) + b.sharedFieldReferences[types.CurrentEpochParticipationBits] = stateutil.NewRef(1) + b.sharedFieldReferences[types.LatestExecutionPayloadHeaderElectra] = stateutil.NewRef(1) // New in Electra. + b.sharedFieldReferences[types.HistoricalSummaries] = stateutil.NewRef(1) // New in Capella. + b.sharedFieldReferences[types.PendingBalanceDeposits] = stateutil.NewRef(1) // New in Electra. + b.sharedFieldReferences[types.PendingPartialWithdrawals] = stateutil.NewRef(1) // New in Electra. + b.sharedFieldReferences[types.PendingConsolidations] = stateutil.NewRef(1) // New in Electra. + if !features.Get().EnableExperimentalState { + b.sharedFieldReferences[types.BlockRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[types.StateRoots] = stateutil.NewRef(1) + b.sharedFieldReferences[types.RandaoMixes] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Balances] = stateutil.NewRef(1) + b.sharedFieldReferences[types.Validators] = stateutil.NewRef(1) + b.sharedFieldReferences[types.InactivityScores] = stateutil.NewRef(1) + } + + state.Count.Inc() + // Finalizer runs when dst is being destroyed in garbage collection. + runtime.SetFinalizer(b, finalizerCleanup) + return b, nil +} + // Copy returns a deep copy of the beacon state. func (b *BeaconState) Copy() state.BeaconState { b.lock.RLock() @@ -700,17 +849,25 @@ func (b *BeaconState) Copy() state.BeaconState { fieldCount = params.BeaconConfig().BeaconStateCapellaFieldCount case version.Deneb: fieldCount = params.BeaconConfig().BeaconStateDenebFieldCount + case version.Electra: + fieldCount = params.BeaconConfig().BeaconStateElectraFieldCount } dst := &BeaconState{ version: b.version, // Primitive types, safe to copy. - genesisTime: b.genesisTime, - slot: b.slot, - eth1DepositIndex: b.eth1DepositIndex, - nextWithdrawalIndex: b.nextWithdrawalIndex, - nextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + genesisTime: b.genesisTime, + slot: b.slot, + eth1DepositIndex: b.eth1DepositIndex, + nextWithdrawalIndex: b.nextWithdrawalIndex, + nextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + depositReceiptsStartIndex: b.depositReceiptsStartIndex, + depositBalanceToConsume: b.depositBalanceToConsume, + exitBalanceToConsume: b.exitBalanceToConsume, + earliestExitEpoch: b.earliestExitEpoch, + consolidationBalanceToConsume: b.consolidationBalanceToConsume, + earliestConsolidationEpoch: b.earliestConsolidationEpoch, // Large arrays, infrequently changed, constant size. blockRoots: b.blockRoots, @@ -735,6 +892,9 @@ func (b *BeaconState) Copy() state.BeaconState { currentEpochParticipation: b.currentEpochParticipation, inactivityScores: b.inactivityScores, inactivityScoresMultiValue: b.inactivityScoresMultiValue, + pendingBalanceDeposits: b.pendingBalanceDeposits, + pendingPartialWithdrawals: b.pendingPartialWithdrawals, + pendingConsolidations: b.pendingConsolidations, // Everything else, too small to be concerned about, constant size. genesisValidatorsRoot: b.genesisValidatorsRoot, @@ -750,6 +910,7 @@ func (b *BeaconState) Copy() state.BeaconState { latestExecutionPayloadHeader: b.latestExecutionPayloadHeaderVal(), latestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapellaVal(), latestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDenebVal(), + latestExecutionPayloadHeaderElectra: b.latestExecutionPayloadHeaderElectraVal(), id: types.Enumerator.Inc(), @@ -786,6 +947,8 @@ func (b *BeaconState) Copy() state.BeaconState { dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, experimentalStateCapellaSharedFieldRefCount) case version.Deneb: dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, experimentalStateDenebSharedFieldRefCount) + case version.Electra: + dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, experimentalStateElectraSharedFieldRefCount) } } else { switch b.version { @@ -799,6 +962,8 @@ func (b *BeaconState) Copy() state.BeaconState { dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, capellaSharedFieldRefCount) case version.Deneb: dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, denebSharedFieldRefCount) + case version.Electra: + dst.sharedFieldReferences = make(map[types.FieldIndex]*stateutil.Reference, electraSharedFieldRefCount) } } @@ -891,6 +1056,10 @@ func (b *BeaconState) initializeMerkleLayers(ctx context.Context) error { b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateCapellaFieldCount) case version.Deneb: b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateDenebFieldCount) + case version.Electra: + b.dirtyFields = make(map[types.FieldIndex]bool, params.BeaconConfig().BeaconStateElectraFieldCount) + default: + return fmt.Errorf("unknown state version (%s) when computing dirty fields in merklization", version.String(b.version)) } return nil @@ -1109,12 +1278,32 @@ func (b *BeaconState) rootSelector(ctx context.Context, field types.FieldIndex) return b.latestExecutionPayloadHeaderCapella.HashTreeRoot() case types.LatestExecutionPayloadHeaderDeneb: return b.latestExecutionPayloadHeaderDeneb.HashTreeRoot() + case types.LatestExecutionPayloadHeaderElectra: + return b.latestExecutionPayloadHeaderElectra.HashTreeRoot() case types.NextWithdrawalIndex: return ssz.Uint64Root(b.nextWithdrawalIndex), nil case types.NextWithdrawalValidatorIndex: return ssz.Uint64Root(uint64(b.nextWithdrawalValidatorIndex)), nil case types.HistoricalSummaries: return stateutil.HistoricalSummariesRoot(b.historicalSummaries) + case types.DepositReceiptsStartIndex: + return ssz.Uint64Root(b.depositReceiptsStartIndex), nil + case types.DepositBalanceToConsume: + return ssz.Uint64Root(uint64(b.depositBalanceToConsume)), nil + case types.ExitBalanceToConsume: + return ssz.Uint64Root(uint64(b.exitBalanceToConsume)), nil + case types.EarliestExitEpoch: + return ssz.Uint64Root(uint64(b.earliestExitEpoch)), nil + case types.ConsolidationBalanceToConsume: + return ssz.Uint64Root(uint64(b.consolidationBalanceToConsume)), nil + case types.EarliestConsolidationEpoch: + return ssz.Uint64Root(uint64(b.earliestConsolidationEpoch)), nil + case types.PendingBalanceDeposits: + return stateutil.PendingBalanceDepositsRoot(b.pendingBalanceDeposits) + case types.PendingPartialWithdrawals: + return stateutil.PendingPartialWithdrawalsRoot(b.pendingPartialWithdrawals) + case types.PendingConsolidations: + return stateutil.PendingConsolidationsRoot(b.pendingConsolidations) } return [32]byte{}, errors.New("invalid field index provided") } diff --git a/beacon-chain/state/state-native/state_trie_test.go b/beacon-chain/state/state-native/state_trie_test.go index ed51dfbd23..2fa4858a9f 100644 --- a/beacon-chain/state/state-native/state_trie_test.go +++ b/beacon-chain/state/state-native/state_trie_test.go @@ -5,6 +5,8 @@ import ( "context" "testing" + "github.com/golang/snappy" + "github.com/google/go-cmp/cmp" "github.com/prysmaticlabs/prysm/v5/beacon-chain/state" statenative "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" "github.com/prysmaticlabs/prysm/v5/config/features" @@ -14,6 +16,7 @@ import ( "github.com/prysmaticlabs/prysm/v5/testing/assert" "github.com/prysmaticlabs/prysm/v5/testing/require" "github.com/prysmaticlabs/prysm/v5/testing/util" + "google.golang.org/protobuf/testing/protocmp" ) func TestInitializeFromProto_Phase0(t *testing.T) { @@ -203,6 +206,42 @@ func TestInitializeFromProto_Deneb(t *testing.T) { } } +func TestInitializeFromProto_Electra(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateElectra + error string + } + initTests := []test{ + { + name: "nil state", + state: nil, + error: "received nil state", + }, + { + name: "nil validators", + state: ðpb.BeaconStateElectra{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateElectra{}, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := statenative.InitializeFromProtoElectra(tt.state) + if tt.error != "" { + require.ErrorContains(t, tt.error, err) + } else { + require.NoError(t, err) + } + }) + } +} + func TestInitializeFromProtoUnsafe_Phase0(t *testing.T) { testState, _ := util.DeterministicGenesisState(t, 64) pbState, err := statenative.ProtobufBeaconStatePhase0(testState.ToProtoUnsafe()) @@ -365,6 +404,37 @@ func TestInitializeFromProtoUnsafe_Deneb(t *testing.T) { } } +func TestInitializeFromProtoUnsafe_Electra(t *testing.T) { + type test struct { + name string + state *ethpb.BeaconStateElectra + error string + } + initTests := []test{ + { + name: "nil validators", + state: ðpb.BeaconStateElectra{ + Slot: 4, + Validators: nil, + }, + }, + { + name: "empty state", + state: ðpb.BeaconStateElectra{}, + }, + } + for _, tt := range initTests { + t.Run(tt.name, func(t *testing.T) { + _, err := statenative.InitializeFromProtoUnsafeElectra(tt.state) + if tt.error != "" { + assert.ErrorContains(t, tt.error, err) + } else { + assert.NoError(t, err) + } + }) + } +} + func TestBeaconState_HashTreeRoot(t *testing.T) { testState, _ := util.DeterministicGenesisState(t, 64) @@ -772,3 +842,29 @@ func TestBeaconState_InitializeInactivityScoresCorrectly_Deneb(t *testing.T) { require.DeepSSZEqual(t, rt, newRt) } + +func TestBeaconChainCopy_Electra(t *testing.T) { + // Load a serialized Electra state from disk. + // This is a fully hydrated random test case from spectests. + serializedBytes, err := util.BazelFileBytes("tests/mainnet/electra/ssz_static/BeaconState/ssz_random/case_0/serialized.ssz_snappy") + require.NoError(t, err) + serializedSSZ, err := snappy.Decode(nil /* dst */, serializedBytes) + require.NoError(t, err) + pb := ðpb.BeaconStateElectra{} + require.NoError(t, pb.UnmarshalSSZ(serializedSSZ)) + st, err := statenative.InitializeFromProtoElectra(pb) + require.NoError(t, err) + + // Sanity check that InitializeFromProtoElectra and ToProto works + if !cmp.Equal(st.ToProto(), pb, protocmp.Transform()) { + t.Log(cmp.Diff(st.ToProto(), pb, protocmp.Transform())) + t.Fatal("InitializeFromProtoElectra does not match input proto") + } + + // Perform the copy and check that the copied state matches the original state. + st2 := st.Copy() + if !cmp.Equal(st.ToProto(), st2.ToProto(), protocmp.Transform()) { + t.Log(cmp.Diff(st.ToProto(), st2.ToProto(), protocmp.Transform())) + t.Fatal("Copied state does not match original state") + } +} diff --git a/beacon-chain/state/state-native/types/types.go b/beacon-chain/state/state-native/types/types.go index d2dd9cdbf1..0bcd77f9b0 100644 --- a/beacon-chain/state/state-native/types/types.go +++ b/beacon-chain/state/state-native/types/types.go @@ -1,6 +1,8 @@ package types import ( + "fmt" + "github.com/pkg/errors" consensus_types "github.com/prysmaticlabs/prysm/v5/consensus-types" ) @@ -83,15 +85,37 @@ func (f FieldIndex) String() string { case LatestExecutionPayloadHeader: return "latestExecutionPayloadHeader" case LatestExecutionPayloadHeaderCapella: - return "LatestExecutionPayloadHeaderCapella" + return "latestExecutionPayloadHeaderCapella" + case LatestExecutionPayloadHeaderDeneb: + return "latestExecutionPayloadHeaderDeneb" + case LatestExecutionPayloadHeaderElectra: + return "latestExecutionPayloadHeaderElectra" case NextWithdrawalIndex: - return "NextWithdrawalIndex" + return "nextWithdrawalIndex" case NextWithdrawalValidatorIndex: - return "NextWithdrawalValidatorIndex" + return "nextWithdrawalValidatorIndex" case HistoricalSummaries: - return "HistoricalSummaries" + return "historicalSummaries" + case DepositReceiptsStartIndex: + return "depositReceiptsStartIndex" + case DepositBalanceToConsume: + return "depositBalanceToConsume" + case ExitBalanceToConsume: + return "exitBalanceToConsume" + case EarliestExitEpoch: + return "earliestExitEpoch" + case ConsolidationBalanceToConsume: + return "consolidationBalanceToConsume" + case EarliestConsolidationEpoch: + return "earliestConsolidationEpoch" + case PendingBalanceDeposits: + return "pendingBalanceDeposits" + case PendingPartialWithdrawals: + return "pendingPartialWithdrawals" + case PendingConsolidations: + return "pendingConsolidations" default: - return "" + return fmt.Sprintf("unknown field index number: %d", f) } } @@ -147,7 +171,7 @@ func (f FieldIndex) RealPosition() int { return 22 case NextSyncCommittee: return 23 - case LatestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella, LatestExecutionPayloadHeaderDeneb: + case LatestExecutionPayloadHeader, LatestExecutionPayloadHeaderCapella, LatestExecutionPayloadHeaderDeneb, LatestExecutionPayloadHeaderElectra: return 24 case NextWithdrawalIndex: return 25 @@ -155,6 +179,24 @@ func (f FieldIndex) RealPosition() int { return 26 case HistoricalSummaries: return 27 + case DepositReceiptsStartIndex: + return 28 + case DepositBalanceToConsume: + return 29 + case ExitBalanceToConsume: + return 30 + case EarliestExitEpoch: + return 31 + case ConsolidationBalanceToConsume: + return 32 + case EarliestConsolidationEpoch: + return 33 + case PendingBalanceDeposits: + return 34 + case PendingPartialWithdrawals: + return 35 + case PendingConsolidations: + return 36 default: return -1 } @@ -207,9 +249,19 @@ const ( LatestExecutionPayloadHeader LatestExecutionPayloadHeaderCapella LatestExecutionPayloadHeaderDeneb + LatestExecutionPayloadHeaderElectra NextWithdrawalIndex NextWithdrawalValidatorIndex HistoricalSummaries + DepositReceiptsStartIndex // Electra: EIP-6110 + DepositBalanceToConsume // Electra: EIP-7251 + ExitBalanceToConsume // Electra: EIP-7251 + EarliestExitEpoch // Electra: EIP-7251 + ConsolidationBalanceToConsume // Electra: EIP-7251 + EarliestConsolidationEpoch // Electra: EIP-7251 + PendingBalanceDeposits // Electra: EIP-7251 + PendingPartialWithdrawals // Electra: EIP-7251 + PendingConsolidations // Electra: EIP-7251 ) // Enumerator keeps track of the number of states created since the node's start. diff --git a/beacon-chain/state/stateutil/BUILD.bazel b/beacon-chain/state/stateutil/BUILD.bazel index cc83efb14a..4480a56072 100644 --- a/beacon-chain/state/stateutil/BUILD.bazel +++ b/beacon-chain/state/stateutil/BUILD.bazel @@ -12,7 +12,11 @@ go_library( "historical_summaries_root.go", "participation_bit_root.go", "pending_attestation_root.go", + "pending_balance_deposits_root.go", + "pending_consolidations_root.go", + "pending_partial_withdrawals_root.go", "reference.go", + "slice_root.go", "sync_committee.root.go", "trie_helpers.go", "unrealized_justification.go", diff --git a/beacon-chain/state/stateutil/historical_summaries_root.go b/beacon-chain/state/stateutil/historical_summaries_root.go index 921d0c9f3d..54497a4dcb 100644 --- a/beacon-chain/state/stateutil/historical_summaries_root.go +++ b/beacon-chain/state/stateutil/historical_summaries_root.go @@ -1,42 +1,10 @@ package stateutil import ( - "bytes" - "encoding/binary" - "fmt" - - "github.com/pkg/errors" fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" - "github.com/prysmaticlabs/prysm/v5/encoding/ssz" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" ) func HistoricalSummariesRoot(summaries []*ethpb.HistoricalSummary) ([32]byte, error) { - max := uint64(fieldparams.HistoricalRootsLength) - if uint64(len(summaries)) > max { - return [32]byte{}, fmt.Errorf("historical summary exceeds max length %d", max) - } - - roots := make([][32]byte, len(summaries)) - for i := 0; i < len(summaries); i++ { - r, err := summaries[i].HashTreeRoot() - if err != nil { - return [32]byte{}, errors.Wrap(err, "could not merkleize historical summary") - } - roots[i] = r - } - - summariesRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), fieldparams.HistoricalRootsLength) - if err != nil { - return [32]byte{}, errors.Wrap(err, "could not compute historical summaries merkleization") - } - summariesLenBuf := new(bytes.Buffer) - if err := binary.Write(summariesLenBuf, binary.LittleEndian, uint64(len(summaries))); err != nil { - return [32]byte{}, errors.Wrap(err, "could not marshal historical summary length") - } - // We need to mix in the length of the slice. - summariesLenRoot := make([]byte, 32) - copy(summariesLenRoot, summariesLenBuf.Bytes()) - res := ssz.MixInLength(summariesRoot, summariesLenRoot) - return res, nil + return SliceRoot(summaries, fieldparams.HistoricalRootsLength) } diff --git a/beacon-chain/state/stateutil/pending_balance_deposits_root.go b/beacon-chain/state/stateutil/pending_balance_deposits_root.go new file mode 100644 index 0000000000..e228c993df --- /dev/null +++ b/beacon-chain/state/stateutil/pending_balance_deposits_root.go @@ -0,0 +1,10 @@ +package stateutil + +import ( + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) + +func PendingBalanceDepositsRoot(slice []*ethpb.PendingBalanceDeposit) ([32]byte, error) { + return SliceRoot(slice, fieldparams.PendingBalanceDepositsLimit) +} diff --git a/beacon-chain/state/stateutil/pending_consolidations_root.go b/beacon-chain/state/stateutil/pending_consolidations_root.go new file mode 100644 index 0000000000..1c4026d079 --- /dev/null +++ b/beacon-chain/state/stateutil/pending_consolidations_root.go @@ -0,0 +1,10 @@ +package stateutil + +import ( + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) + +func PendingConsolidationsRoot(slice []*ethpb.PendingConsolidation) ([32]byte, error) { + return SliceRoot(slice, fieldparams.PendingConsolidationsLimit) +} diff --git a/beacon-chain/state/stateutil/pending_partial_withdrawals_root.go b/beacon-chain/state/stateutil/pending_partial_withdrawals_root.go new file mode 100644 index 0000000000..e939b5c156 --- /dev/null +++ b/beacon-chain/state/stateutil/pending_partial_withdrawals_root.go @@ -0,0 +1,10 @@ +package stateutil + +import ( + fieldparams "github.com/prysmaticlabs/prysm/v5/config/fieldparams" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" +) + +func PendingPartialWithdrawalsRoot(slice []*ethpb.PendingPartialWithdrawal) ([32]byte, error) { + return SliceRoot(slice, fieldparams.PendingPartialWithdrawalsLimit) +} diff --git a/beacon-chain/state/stateutil/slice_root.go b/beacon-chain/state/stateutil/slice_root.go new file mode 100644 index 0000000000..236582296d --- /dev/null +++ b/beacon-chain/state/stateutil/slice_root.go @@ -0,0 +1,41 @@ +package stateutil + +import ( + "bytes" + "encoding/binary" + "fmt" + + "github.com/pkg/errors" + "github.com/prysmaticlabs/prysm/v5/encoding/ssz" +) + +// SliceRoot computes the root of a slice of hashable objects. +func SliceRoot[T ssz.Hashable](slice []T, limit uint64) ([32]byte, error) { + max := limit + if uint64(len(slice)) > max { + return [32]byte{}, fmt.Errorf("slice exceeds max length %d", max) + } + + roots := make([][32]byte, len(slice)) + for i := 0; i < len(slice); i++ { + r, err := slice[i].HashTreeRoot() + if err != nil { + return [32]byte{}, errors.Wrap(err, "could not merkleize object") + } + roots[i] = r + } + + sliceRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), limit) + if err != nil { + return [32]byte{}, errors.Wrap(err, "could not slice merkleization") + } + sliceLenBuf := new(bytes.Buffer) + if err := binary.Write(sliceLenBuf, binary.LittleEndian, uint64(len(slice))); err != nil { + return [32]byte{}, errors.Wrap(err, "could not marshal slice length") + } + // We need to mix in the length of the slice. + sliceLenRoot := make([]byte, 32) + copy(sliceLenRoot, sliceLenBuf.Bytes()) + res := ssz.MixInLength(sliceRoot, sliceLenRoot) + return res, nil +} diff --git a/beacon-chain/state/stateutil/trie_helpers.go b/beacon-chain/state/stateutil/trie_helpers.go index be4aabd0be..a0f630b26a 100644 --- a/beacon-chain/state/stateutil/trie_helpers.go +++ b/beacon-chain/state/stateutil/trie_helpers.go @@ -250,11 +250,11 @@ func AddInMixin(root [32]byte, length uint64) ([32]byte, error) { // Merkleize 32-byte leaves into a Merkle trie for its adequate depth, returning // the resulting layers of the trie based on the appropriate depth. This function -// pads the leaves to a length of 32. +// pads the leaves to a length of a multiple of 32. func Merkleize(leaves [][]byte) [][][]byte { hashFunc := hash.CustomSHA256Hasher() layers := make([][][]byte, ssz.Depth(uint64(len(leaves)))+1) - for len(leaves) != 32 { + for len(leaves)%32 != 0 { leaves = append(leaves, make([]byte, 32)) } currentLayer := leaves diff --git a/config/params/config.go b/config/params/config.go index 552ac2b9c6..f0624519c9 100644 --- a/config/params/config.go +++ b/config/params/config.go @@ -236,20 +236,20 @@ type BeaconChainConfig struct { MaxRequestBlocksDeneb uint64 `yaml:"MAX_REQUEST_BLOCKS_DENEB" spec:"true"` // MaxRequestBlocksDeneb is the maximum number of blocks in a single request after the deneb epoch. // Values introduce in Electra upgrade - DataColumnSidecarSubnetCount uint64 `yaml:"DATA_COLUMN_SIDECAR_SUBNET_COUNT" spec:"true"` // DataColumnSidecarSubnetCount is the number of data column sidecar subnets used in the gossipsub protocol - MaxPerEpochActivationExitChurnLimit uint64 `yaml:"MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT" spec:"true"` // MaxPerEpochActivationExitChurnLimit represents the maximum combined activation and exit churn. - MinPerEpochChurnLimitElectra uint64 `yaml:"MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA" spec:"true"` // MinPerEpochChurnLimitElectra is the minimum amount of churn allotted for validator rotations for electra. - MaxRequestDataColumnSidecars uint64 `yaml:"MAX_REQUEST_DATA_COLUMN_SIDECARS" spec:"true"` // MaxRequestDataColumnSidecars is the maximum number of data column sidecars in a single request - MaxEffectiveBalanceElectra uint64 `yaml:"MAX_EFFECTIVE_BALANCE_ELECTRA" spec:"true"` // MaxEffectiveBalanceElectra is the maximal amount of Gwei that is effective for staking, increased in electra. - MinSlashingPenaltyQuotientElectra uint64 `yaml:"MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA" spec:"true"` // MinSlashingPenaltyQuotientElectra is used to calculate the minimum penalty to prevent DoS attacks, modified for electra. - WhistleBlowerRewardQuotientElectra uint64 `yaml:"WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA" spec:"true"` // WhistleBlowerRewardQuotientElectra is used to calculate whistle blower reward, modified in electra. - PendingBalanceDepositLimit uint64 `yaml:"PENDING_BALANCE_DEPOSITS_LIMIT" spec:"true"` // PendingBalanceDepositLimit is the maximum number of pending balance deposits allowed in the beacon state. - PendingPartialWithdrawalsLimit uint64 `yaml:"PENDING_PARTIAL_WITHDRAWALS_LIMIT" spec:"true"` // PendingPartialWithdrawalsLimit is the maximum number of pending partial withdrawals allowed in the beacon state. - PendingConsolidationsLimit uint64 `yaml:"PENDING_CONSOLIDATIONS_LIMIT" spec:"true"` // PendingConsolidationsLimit is the maximum number of pending validator consolidations allowed in the beacon state. - MaxConsolidations uint64 `yaml:"MAX_CONSOLIDATIONS" spec:"true"` // MaxConsolidations is the maximum number of consolidations in a block. - MaxPendingPartialsPerWithdrawalSweep uint64 `yaml:"MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP" spec:"true"` // MaxPendingPartialsPerWithdrawalSweep is the maximum number of pending partial withdrawals to process per payload. - FullExitRequestAmount uint64 `yaml:"FULL_EXIT_REQUEST_AMOUNT" spec:"true"` // FullExitRequestAmount is the amount of Gwei required to request a full exit. - MaxWithdrawalRequestsPerPayload uint64 `yaml:"MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD" spec:"true"` // MaxWithdrawalRequestsPerPayload is the maximum number of execution layer withdrawal requests in each payload. + DataColumnSidecarSubnetCount uint64 `yaml:"DATA_COLUMN_SIDECAR_SUBNET_COUNT" spec:"true"` // DataColumnSidecarSubnetCount is the number of data column sidecar subnets used in the gossipsub protocol + MaxPerEpochActivationExitChurnLimit uint64 `yaml:"MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT" spec:"true"` // MaxPerEpochActivationExitChurnLimit represents the maximum combined activation and exit churn. + MinPerEpochChurnLimitElectra uint64 `yaml:"MIN_PER_EPOCH_CHURN_LIMIT_ELECTRA" spec:"true"` // MinPerEpochChurnLimitElectra is the minimum amount of churn allotted for validator rotations for electra. + MaxRequestDataColumnSidecars uint64 `yaml:"MAX_REQUEST_DATA_COLUMN_SIDECARS" spec:"true"` // MaxRequestDataColumnSidecars is the maximum number of data column sidecars in a single request + MaxEffectiveBalanceElectra uint64 `yaml:"MAX_EFFECTIVE_BALANCE_ELECTRA" spec:"true"` // MaxEffectiveBalanceElectra is the maximal amount of Gwei that is effective for staking, increased in electra. + MinSlashingPenaltyQuotientElectra uint64 `yaml:"MIN_SLASHING_PENALTY_QUOTIENT_ELECTRA" spec:"true"` // MinSlashingPenaltyQuotientElectra is used to calculate the minimum penalty to prevent DoS attacks, modified for electra. + WhistleBlowerRewardQuotientElectra uint64 `yaml:"WHISTLEBLOWER_REWARD_QUOTIENT_ELECTRA" spec:"true"` // WhistleBlowerRewardQuotientElectra is used to calculate whistle blower reward, modified in electra. + PendingBalanceDepositLimit uint64 `yaml:"PENDING_BALANCE_DEPOSITS_LIMIT" spec:"true"` // PendingBalanceDepositLimit is the maximum number of pending balance deposits allowed in the beacon state. + PendingPartialWithdrawalsLimit uint64 `yaml:"PENDING_PARTIAL_WITHDRAWALS_LIMIT" spec:"true"` // PendingPartialWithdrawalsLimit is the maximum number of pending partial withdrawals allowed in the beacon state. + PendingConsolidationsLimit uint64 `yaml:"PENDING_CONSOLIDATIONS_LIMIT" spec:"true"` // PendingConsolidationsLimit is the maximum number of pending validator consolidations allowed in the beacon state. + MaxConsolidations uint64 `yaml:"MAX_CONSOLIDATIONS" spec:"true"` // MaxConsolidations is the maximum number of consolidations in a block. + MaxPendingPartialsPerWithdrawalsSweep uint64 `yaml:"MAX_PENDING_PARTIALS_PER_WITHDRAWALS_SWEEP" spec:"true"` // MaxPendingPartialsPerWithdrawalsSweep is the maximum number of pending partial withdrawals to process per payload. + FullExitRequestAmount uint64 `yaml:"FULL_EXIT_REQUEST_AMOUNT" spec:"true"` // FullExitRequestAmount is the amount of Gwei required to request a full exit. + MaxWithdrawalRequestsPerPayload uint64 `yaml:"MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD" spec:"true"` // MaxWithdrawalRequestsPerPayload is the maximum number of execution layer withdrawal requests in each payload. // Networking Specific Parameters GossipMaxSize uint64 `yaml:"GOSSIP_MAX_SIZE" spec:"true"` // GossipMaxSize is the maximum allowed size of uncompressed gossip messages. @@ -288,6 +288,7 @@ func configForkSchedule(b *BeaconChainConfig) map[[fieldparams.VersionLength]byt fvs[bytesutil.ToBytes4(b.BellatrixForkVersion)] = b.BellatrixForkEpoch fvs[bytesutil.ToBytes4(b.CapellaForkVersion)] = b.CapellaForkEpoch fvs[bytesutil.ToBytes4(b.DenebForkVersion)] = b.DenebForkEpoch + fvs[bytesutil.ToBytes4(b.ElectraForkVersion)] = b.ElectraForkEpoch return fvs } @@ -309,6 +310,7 @@ func ConfigForkVersions(b *BeaconChainConfig) map[[fieldparams.VersionLength]byt bytesutil.ToBytes4(b.BellatrixForkVersion): version.Bellatrix, bytesutil.ToBytes4(b.CapellaForkVersion): version.Capella, bytesutil.ToBytes4(b.DenebForkVersion): version.Deneb, + bytesutil.ToBytes4(b.ElectraForkVersion): version.Electra, } } diff --git a/config/params/interop.go b/config/params/interop.go index f1a9e42452..44c2c048aa 100644 --- a/config/params/interop.go +++ b/config/params/interop.go @@ -11,6 +11,7 @@ func InteropConfig() *BeaconChainConfig { c.BellatrixForkVersion = []byte{2, 0, 0, 235} c.CapellaForkVersion = []byte{3, 0, 0, 235} c.DenebForkVersion = []byte{4, 0, 0, 235} + c.ElectraForkVersion = []byte{5, 0, 0, 235} c.InitializeForkSchedule() return c diff --git a/config/params/loader.go b/config/params/loader.go index c9a7a2562e..404ece6ef4 100644 --- a/config/params/loader.go +++ b/config/params/loader.go @@ -214,6 +214,8 @@ func ConfigToYaml(cfg *BeaconChainConfig) []byte { fmt.Sprintf("BLOB_SIDECAR_SUBNET_COUNT: %d", cfg.BlobsidecarSubnetCount), fmt.Sprintf("DENEB_FORK_EPOCH: %d", cfg.DenebForkEpoch), fmt.Sprintf("DENEB_FORK_VERSION: %#x", cfg.DenebForkVersion), + fmt.Sprintf("ELECTRA_FORK_EPOCH: %d", cfg.ElectraForkEpoch), + fmt.Sprintf("ELECTRA_FORK_VERSION: %#x", cfg.ElectraForkVersion), fmt.Sprintf("EPOCHS_PER_SUBNET_SUBSCRIPTION: %d", cfg.EpochsPerSubnetSubscription), fmt.Sprintf("ATTESTATION_SUBNET_EXTRA_BITS: %d", cfg.AttestationSubnetExtraBits), fmt.Sprintf("ATTESTATION_SUBNET_PREFIX_BITS: %d", cfg.AttestationSubnetPrefixBits), diff --git a/config/params/mainnet_config.go b/config/params/mainnet_config.go index 098ceb5c7d..ab18df129d 100644 --- a/config/params/mainnet_config.go +++ b/config/params/mainnet_config.go @@ -275,21 +275,21 @@ var mainnetBeaconConfig = &BeaconChainConfig{ MaxRequestBlocksDeneb: 128, // Values related to electra - MaxRequestDataColumnSidecars: 16384, - DataColumnSidecarSubnetCount: 32, - MinPerEpochChurnLimitElectra: 128_000_000_000, - MaxPerEpochActivationExitChurnLimit: 256_000_000_000, - MaxEffectiveBalanceElectra: 2048_000_000_000, - MinSlashingPenaltyQuotientElectra: 4096, - WhistleBlowerRewardQuotientElectra: 4096, - PendingBalanceDepositLimit: 134_217_728, - PendingPartialWithdrawalsLimit: 134_217_728, - PendingConsolidationsLimit: 262_144, - MinActivationBalance: 32_000_000_000, - MaxConsolidations: 1, - MaxPendingPartialsPerWithdrawalSweep: 8, - FullExitRequestAmount: 0, - MaxWithdrawalRequestsPerPayload: 16, + MaxRequestDataColumnSidecars: 16384, + DataColumnSidecarSubnetCount: 32, + MinPerEpochChurnLimitElectra: 128_000_000_000, + MaxPerEpochActivationExitChurnLimit: 256_000_000_000, + MaxEffectiveBalanceElectra: 2048_000_000_000, + MinSlashingPenaltyQuotientElectra: 4096, + WhistleBlowerRewardQuotientElectra: 4096, + PendingBalanceDepositLimit: 134_217_728, + PendingPartialWithdrawalsLimit: 134_217_728, + PendingConsolidationsLimit: 262_144, + MinActivationBalance: 32_000_000_000, + MaxConsolidations: 1, + MaxPendingPartialsPerWithdrawalsSweep: 8, + FullExitRequestAmount: 0, + MaxWithdrawalRequestsPerPayload: 16, // Values related to networking parameters. GossipMaxSize: 10 * 1 << 20, // 10 MiB @@ -332,16 +332,19 @@ func FillTestVersions(c *BeaconChainConfig, b byte) { c.BellatrixForkVersion = make([]byte, fieldparams.VersionLength) c.CapellaForkVersion = make([]byte, fieldparams.VersionLength) c.DenebForkVersion = make([]byte, fieldparams.VersionLength) + c.ElectraForkVersion = make([]byte, fieldparams.VersionLength) c.GenesisForkVersion[fieldparams.VersionLength-1] = b c.AltairForkVersion[fieldparams.VersionLength-1] = b c.BellatrixForkVersion[fieldparams.VersionLength-1] = b c.CapellaForkVersion[fieldparams.VersionLength-1] = b c.DenebForkVersion[fieldparams.VersionLength-1] = b + c.ElectraForkVersion[fieldparams.VersionLength-1] = b c.GenesisForkVersion[0] = 0 c.AltairForkVersion[0] = 1 c.BellatrixForkVersion[0] = 2 c.CapellaForkVersion[0] = 3 c.DenebForkVersion[0] = 4 + c.ElectraForkVersion[0] = 5 } diff --git a/config/params/minimal_config.go b/config/params/minimal_config.go index e008f6fd0e..adbdf250ec 100644 --- a/config/params/minimal_config.go +++ b/config/params/minimal_config.go @@ -108,7 +108,7 @@ func MinimalSpecConfig() *BeaconChainConfig { minimalConfig.MaxPartialWithdrawalsPerPayload = 1 minimalConfig.MaxWithdrawalRequestsPerPayload = 2 minimalConfig.PendingPartialWithdrawalsLimit = 64 - minimalConfig.MaxPendingPartialsPerWithdrawalSweep = 1 + minimalConfig.MaxPendingPartialsPerWithdrawalsSweep = 1 // Ethereum PoW parameters. minimalConfig.DepositChainID = 5 // Chain ID of eth1 goerli. diff --git a/config/params/testdata/e2e_config.yaml b/config/params/testdata/e2e_config.yaml index f81f949591..d264fbb618 100644 --- a/config/params/testdata/e2e_config.yaml +++ b/config/params/testdata/e2e_config.yaml @@ -44,6 +44,9 @@ CAPELLA_FORK_EPOCH: 10 # Deneb DENEB_FORK_VERSION: 0x040000fd DENEB_FORK_EPOCH: 12 +# Electra +ELECTRA_FORK_VERSION: 0x050000fd +ELECTRA_FORK_EPOCH: 18446744073709551615 # Time parameters diff --git a/config/params/testnet_e2e_config.go b/config/params/testnet_e2e_config.go index 65d6707840..a82c02ec16 100644 --- a/config/params/testnet_e2e_config.go +++ b/config/params/testnet_e2e_config.go @@ -1,10 +1,13 @@ package params +import "math" + const ( AltairE2EForkEpoch = 6 BellatrixE2EForkEpoch = 8 CapellaE2EForkEpoch = 10 DenebE2EForkEpoch = 12 + ElectraE2EForkEpoch = math.MaxUint64 ) // E2ETestConfig retrieves the configurations made specifically for E2E testing. @@ -40,6 +43,7 @@ func E2ETestConfig() *BeaconChainConfig { e2eConfig.BellatrixForkEpoch = BellatrixE2EForkEpoch e2eConfig.CapellaForkEpoch = CapellaE2EForkEpoch e2eConfig.DenebForkEpoch = DenebE2EForkEpoch + e2eConfig.ElectraForkEpoch = ElectraE2EForkEpoch // Terminal Total Difficulty. e2eConfig.TerminalTotalDifficulty = "480" @@ -51,6 +55,7 @@ func E2ETestConfig() *BeaconChainConfig { e2eConfig.BellatrixForkVersion = []byte{2, 0, 0, 253} e2eConfig.CapellaForkVersion = []byte{3, 0, 0, 253} e2eConfig.DenebForkVersion = []byte{4, 0, 0, 253} + e2eConfig.ElectraForkVersion = []byte{5, 0, 0, 253} e2eConfig.InitializeForkSchedule() return e2eConfig @@ -82,6 +87,7 @@ func E2EMainnetTestConfig() *BeaconChainConfig { e2eConfig.BellatrixForkEpoch = BellatrixE2EForkEpoch e2eConfig.CapellaForkEpoch = CapellaE2EForkEpoch e2eConfig.DenebForkEpoch = DenebE2EForkEpoch + e2eConfig.ElectraForkEpoch = ElectraE2EForkEpoch // Terminal Total Difficulty. e2eConfig.TerminalTotalDifficulty = "480" @@ -93,6 +99,7 @@ func E2EMainnetTestConfig() *BeaconChainConfig { e2eConfig.BellatrixForkVersion = []byte{2, 0, 0, 254} e2eConfig.CapellaForkVersion = []byte{3, 0, 0, 254} e2eConfig.DenebForkVersion = []byte{4, 0, 0, 254} + e2eConfig.ElectraForkVersion = []byte{5, 0, 0, 254} // Deneb changes. e2eConfig.MinPerEpochChurnLimit = 2 diff --git a/config/params/testnet_holesky_config.go b/config/params/testnet_holesky_config.go index a256d327a0..66bf9bd23f 100644 --- a/config/params/testnet_holesky_config.go +++ b/config/params/testnet_holesky_config.go @@ -1,5 +1,7 @@ package params +import "math" + // UseHoleskyNetworkConfig uses the Holesky beacon chain specific network config. func UseHoleskyNetworkConfig() { cfg := BeaconNetworkConfig().Copy() @@ -36,6 +38,8 @@ func HoleskyConfig() *BeaconChainConfig { cfg.CapellaForkVersion = []byte{0x4, 0x1, 0x70, 0x0} cfg.DenebForkEpoch = 29696 cfg.DenebForkVersion = []byte{0x05, 0x1, 0x70, 0x0} + cfg.ElectraForkEpoch = math.MaxUint64 + cfg.ElectraForkVersion = []byte{0x06, 0x1, 0x70, 0x0} // TODO: Define holesky fork version for electra. This is a placeholder value. cfg.TerminalTotalDifficulty = "0" cfg.DepositContractAddress = "0x4242424242424242424242424242424242424242" cfg.EjectionBalance = 28000000000 diff --git a/config/params/testnet_sepolia_config.go b/config/params/testnet_sepolia_config.go index 30c8228211..0df3e1e51d 100644 --- a/config/params/testnet_sepolia_config.go +++ b/config/params/testnet_sepolia_config.go @@ -1,6 +1,8 @@ package params import ( + "math" + eth1Params "github.com/ethereum/go-ethereum/params" ) @@ -37,6 +39,8 @@ func SepoliaConfig() *BeaconChainConfig { cfg.CapellaForkVersion = []byte{0x90, 0x00, 0x00, 0x72} cfg.DenebForkEpoch = 132608 cfg.DenebForkVersion = []byte{0x90, 0x00, 0x00, 0x73} + cfg.ElectraForkEpoch = math.MaxUint64 + cfg.ElectraForkVersion = []byte{0x90, 0x00, 0x00, 0x74} // TODO: Define sepolia fork version for electra. This is a placeholder value. cfg.TerminalTotalDifficulty = "17000000000000000" cfg.DepositContractAddress = "0x7f02C3E3c98b133055B8B348B2Ac625669Ed295D" cfg.InitializeForkSchedule() diff --git a/encoding/ssz/detect/configfork.go b/encoding/ssz/detect/configfork.go index db22858442..79099512cc 100644 --- a/encoding/ssz/detect/configfork.go +++ b/encoding/ssz/detect/configfork.go @@ -87,6 +87,8 @@ func FromForkVersion(cv [fieldparams.VersionLength]byte) (*VersionedUnmarshaler, fork = version.Capella case bytesutil.ToBytes4(cfg.DenebForkVersion): fork = version.Deneb + case bytesutil.ToBytes4(cfg.ElectraForkVersion): + fork = version.Electra default: return nil, errors.Wrapf(ErrForkNotFound, "version=%#x", cv) } @@ -152,6 +154,16 @@ func (cf *VersionedUnmarshaler) UnmarshalBeaconState(marshaled []byte) (s state. if err != nil { return nil, errors.Wrapf(err, "failed to init state trie from state, detected fork=%s", forkName) } + case version.Electra: + st := ðpb.BeaconStateElectra{} + err = st.UnmarshalSSZ(marshaled) + if err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal state, detected fork=%s", forkName) + } + s, err = state_native.InitializeFromProtoUnsafeElectra(st) + if err != nil { + return nil, errors.Wrapf(err, "failed to init state trie from state, detected fork=%s", forkName) + } default: return nil, fmt.Errorf("unable to initialize BeaconState for fork version=%s", forkName) } @@ -200,6 +212,8 @@ func (cf *VersionedUnmarshaler) UnmarshalBeaconBlock(marshaled []byte) (interfac blk = ðpb.SignedBeaconBlockCapella{} case version.Deneb: blk = ðpb.SignedBeaconBlockDeneb{} + case version.Electra: + blk = ðpb.SignedBeaconBlockElectra{} default: forkName := version.String(cf.Fork) return nil, fmt.Errorf("unable to initialize ReadOnlyBeaconBlock for fork version=%s at slot=%d", forkName, slot) @@ -235,6 +249,8 @@ func (cf *VersionedUnmarshaler) UnmarshalBlindedBeaconBlock(marshaled []byte) (i blk = ðpb.SignedBlindedBeaconBlockCapella{} case version.Deneb: blk = ðpb.SignedBlindedBeaconBlockDeneb{} + case version.Electra: + blk = ðpb.SignedBlindedBeaconBlockElectra{} default: forkName := version.String(cf.Fork) return nil, fmt.Errorf("unable to initialize ReadOnlyBeaconBlock for fork version=%s at slot=%d", forkName, slot) diff --git a/encoding/ssz/detect/configfork_test.go b/encoding/ssz/detect/configfork_test.go index 36d7b929b3..5a5e41a912 100644 --- a/encoding/ssz/detect/configfork_test.go +++ b/encoding/ssz/detect/configfork_test.go @@ -47,7 +47,7 @@ func TestSlotFromBlock(t *testing.T) { } func TestByState(t *testing.T) { - undo := util.HackDenebMaxuint(t) + undo := util.HackElectraMaxuint(t) defer undo() bc := params.BeaconConfig() altairSlot, err := slots.EpochStart(bc.AltairForkEpoch) @@ -58,6 +58,8 @@ func TestByState(t *testing.T) { require.NoError(t, err) denebSlot, err := slots.EpochStart(bc.DenebForkEpoch) require.NoError(t, err) + electraSlot, err := slots.EpochStart(bc.ElectraForkEpoch) + require.NoError(t, err) cases := []struct { name string version int @@ -94,6 +96,12 @@ func TestByState(t *testing.T) { slot: denebSlot, forkversion: bytesutil.ToBytes4(bc.DenebForkVersion), }, + { + name: "electra", + version: version.Electra, + slot: electraSlot, + forkversion: bytesutil.ToBytes4(bc.ElectraForkVersion), + }, } for _, c := range cases { st, err := stateForVersion(c.version) @@ -126,6 +134,8 @@ func stateForVersion(v int) (state.BeaconState, error) { return util.NewBeaconStateCapella() case version.Deneb: return util.NewBeaconStateDeneb() + case version.Electra: + return util.NewBeaconStateElectra() default: return nil, fmt.Errorf("unrecognized version %d", v) } @@ -133,7 +143,7 @@ func stateForVersion(v int) (state.BeaconState, error) { func TestUnmarshalState(t *testing.T) { ctx := context.Background() - undo := util.HackDenebMaxuint(t) + undo := util.HackElectraMaxuint(t) defer undo() bc := params.BeaconConfig() altairSlot, err := slots.EpochStart(bc.AltairForkEpoch) @@ -144,6 +154,8 @@ func TestUnmarshalState(t *testing.T) { require.NoError(t, err) denebSlot, err := slots.EpochStart(bc.DenebForkEpoch) require.NoError(t, err) + electraSlot, err := slots.EpochStart(bc.ElectraForkEpoch) + require.NoError(t, err) cases := []struct { name string version int @@ -180,6 +192,12 @@ func TestUnmarshalState(t *testing.T) { slot: denebSlot, forkversion: bytesutil.ToBytes4(bc.DenebForkVersion), }, + { + name: "electra", + version: version.Electra, + slot: electraSlot, + forkversion: bytesutil.ToBytes4(bc.ElectraForkVersion), + }, } for _, c := range cases { st, err := stateForVersion(c.version) @@ -205,7 +223,7 @@ func TestUnmarshalState(t *testing.T) { } func TestDetectAndUnmarshalBlock(t *testing.T) { - undo := util.HackDenebMaxuint(t) + undo := util.HackElectraMaxuint(t) defer undo() altairS, err := slots.EpochStart(params.BeaconConfig().AltairForkEpoch) require.NoError(t, err) @@ -215,6 +233,8 @@ func TestDetectAndUnmarshalBlock(t *testing.T) { require.NoError(t, err) denebS, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) require.NoError(t, err) + electraS, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch) + require.NoError(t, err) cases := []struct { b func(*testing.T, primitives.Slot) interfaces.ReadOnlySignedBeaconBlock name string @@ -260,6 +280,11 @@ func TestDetectAndUnmarshalBlock(t *testing.T) { b: signedTestBlockDeneb, slot: denebS, }, + { + name: "first slot of electra", + b: signedTestBlockElectra, + slot: electraS, + }, { name: "bellatrix block in altair slot", b: signedTestBlockBellatrix, @@ -296,13 +321,14 @@ func TestDetectAndUnmarshalBlock(t *testing.T) { } func TestUnmarshalBlock(t *testing.T) { - undo := util.HackDenebMaxuint(t) + undo := util.HackElectraMaxuint(t) defer undo() genv := bytesutil.ToBytes4(params.BeaconConfig().GenesisForkVersion) altairv := bytesutil.ToBytes4(params.BeaconConfig().AltairForkVersion) bellav := bytesutil.ToBytes4(params.BeaconConfig().BellatrixForkVersion) capellaV := bytesutil.ToBytes4(params.BeaconConfig().CapellaForkVersion) denebV := bytesutil.ToBytes4(params.BeaconConfig().DenebForkVersion) + electraV := bytesutil.ToBytes4(params.BeaconConfig().ElectraForkVersion) altairS, err := slots.EpochStart(params.BeaconConfig().AltairForkEpoch) require.NoError(t, err) bellaS, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch) @@ -311,6 +337,8 @@ func TestUnmarshalBlock(t *testing.T) { require.NoError(t, err) denebS, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) require.NoError(t, err) + electraS, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch) + require.NoError(t, err) cases := []struct { b func(*testing.T, primitives.Slot) interfaces.ReadOnlySignedBeaconBlock name string @@ -365,6 +393,12 @@ func TestUnmarshalBlock(t *testing.T) { version: denebV, slot: denebS, }, + { + name: "first slot of electra", + b: signedTestBlockElectra, + version: electraV, + slot: electraS, + }, { name: "bellatrix block in altair slot", b: signedTestBlockBellatrix, @@ -409,13 +443,14 @@ func TestUnmarshalBlock(t *testing.T) { } func TestUnmarshalBlindedBlock(t *testing.T) { - undo := util.HackDenebMaxuint(t) + undo := util.HackElectraMaxuint(t) defer undo() genv := bytesutil.ToBytes4(params.BeaconConfig().GenesisForkVersion) altairv := bytesutil.ToBytes4(params.BeaconConfig().AltairForkVersion) bellav := bytesutil.ToBytes4(params.BeaconConfig().BellatrixForkVersion) capellaV := bytesutil.ToBytes4(params.BeaconConfig().CapellaForkVersion) denebV := bytesutil.ToBytes4(params.BeaconConfig().DenebForkVersion) + electraV := bytesutil.ToBytes4(params.BeaconConfig().ElectraForkVersion) altairS, err := slots.EpochStart(params.BeaconConfig().AltairForkEpoch) require.NoError(t, err) bellaS, err := slots.EpochStart(params.BeaconConfig().BellatrixForkEpoch) @@ -424,6 +459,8 @@ func TestUnmarshalBlindedBlock(t *testing.T) { require.NoError(t, err) denebS, err := slots.EpochStart(params.BeaconConfig().DenebForkEpoch) require.NoError(t, err) + electraS, err := slots.EpochStart(params.BeaconConfig().ElectraForkEpoch) + require.NoError(t, err) cases := []struct { b func(*testing.T, primitives.Slot) interfaces.ReadOnlySignedBeaconBlock name string @@ -485,6 +522,12 @@ func TestUnmarshalBlindedBlock(t *testing.T) { version: denebV, slot: denebS, }, + { + name: "first slot of electra", + b: signedTestBlindedBlockElectra, + version: electraV, + slot: electraS, + }, { name: "genesis block in altair slot", b: signedTestBlockGenesis, @@ -577,6 +620,14 @@ func signedTestBlockDeneb(t *testing.T, slot primitives.Slot) interfaces.ReadOnl return s } +func signedTestBlockElectra(t *testing.T, slot primitives.Slot) interfaces.ReadOnlySignedBeaconBlock { + b := util.NewBeaconBlockElectra() + b.Block.Slot = slot + s, err := blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + return s +} + func signedTestBlindedBlockDeneb(t *testing.T, slot primitives.Slot) interfaces.ReadOnlySignedBeaconBlock { b := util.NewBlindedBeaconBlockDeneb() b.Message.Slot = slot @@ -584,3 +635,11 @@ func signedTestBlindedBlockDeneb(t *testing.T, slot primitives.Slot) interfaces. require.NoError(t, err) return s } + +func signedTestBlindedBlockElectra(t *testing.T, slot primitives.Slot) interfaces.ReadOnlySignedBeaconBlock { + b := util.NewBlindedBeaconBlockElectra() + b.Message.Slot = slot + s, err := blocks.NewSignedBeaconBlock(b) + require.NoError(t, err) + return s +} diff --git a/math/BUILD.bazel b/math/BUILD.bazel index ffd1f0ba76..39964b0712 100644 --- a/math/BUILD.bazel +++ b/math/BUILD.bazel @@ -5,7 +5,10 @@ go_library( srcs = ["math_helper.go"], importpath = "github.com/prysmaticlabs/prysm/v5/math", visibility = ["//visibility:public"], - deps = ["@com_github_thomaso_mirodin_intmath//u64:go_default_library"], + deps = [ + "@com_github_prysmaticlabs_fastssz//:go_default_library", + "@com_github_thomaso_mirodin_intmath//u64:go_default_library", + ], ) go_test( diff --git a/math/math_helper.go b/math/math_helper.go index bfca6d155d..d926a3db75 100644 --- a/math/math_helper.go +++ b/math/math_helper.go @@ -3,11 +3,13 @@ package math import ( "errors" + "fmt" stdmath "math" "math/big" "math/bits" "sync" + fssz "github.com/prysmaticlabs/fastssz" "github.com/thomaso-mirodin/intmath/u64" ) @@ -216,9 +218,53 @@ func AddInt(i ...int) (int, error) { // Wei is the smallest unit of Ether, represented as a pointer to a bigInt. type Wei *big.Int +var _ fssz.HashRoot = (Gwei)(0) +var _ fssz.Marshaler = (*Gwei)(nil) +var _ fssz.Unmarshaler = (*Gwei)(nil) + // Gwei is a denomination of 1e9 Wei represented as an uint64. type Gwei uint64 +// HashTreeRoot -- +func (g Gwei) HashTreeRoot() ([32]byte, error) { + return fssz.HashWithDefaultHasher(g) +} + +// HashTreeRootWith -- +func (g Gwei) HashTreeRootWith(hh *fssz.Hasher) error { + hh.PutUint64(uint64(g)) + return nil +} + +// UnmarshalSSZ -- +func (g *Gwei) UnmarshalSSZ(buf []byte) error { + if len(buf) != g.SizeSSZ() { + return fmt.Errorf("expected buffer of length %d received %d", g.SizeSSZ(), len(buf)) + } + *g = Gwei(fssz.UnmarshallUint64(buf)) + return nil +} + +// MarshalSSZTo -- +func (g *Gwei) MarshalSSZTo(dst []byte) ([]byte, error) { + marshalled, err := g.MarshalSSZ() + if err != nil { + return nil, err + } + return append(dst, marshalled...), nil +} + +// MarshalSSZ -- +func (g *Gwei) MarshalSSZ() ([]byte, error) { + marshalled := fssz.MarshalUint64([]byte{}, uint64(*g)) + return marshalled, nil +} + +// SizeSSZ -- +func (g *Gwei) SizeSSZ() int { + return 8 +} + // WeiToGwei converts big int wei to uint64 gwei. // The input `v` is copied before being modified. func WeiToGwei(v Wei) Gwei { diff --git a/proto/prysm/v1alpha1/BUILD.bazel b/proto/prysm/v1alpha1/BUILD.bazel index 3e8f57833c..02c496eabb 100644 --- a/proto/prysm/v1alpha1/BUILD.bazel +++ b/proto/prysm/v1alpha1/BUILD.bazel @@ -51,6 +51,7 @@ ssz_gen_marshal( includes = [ "//consensus-types/primitives:go_default_library", "//proto/engine/v1:go_default_library", + "//math:go_default_library", ], objs = [ "BeaconBlockAltair", @@ -162,6 +163,7 @@ go_proto_library( visibility = ["//visibility:public"], deps = [ "//consensus-types/primitives:go_default_library", + "//math:go_default_library", "//proto/engine/v1:go_default_library", "//proto/eth/ext:go_default_library", "@com_github_golang_protobuf//proto:go_default_library", @@ -219,6 +221,7 @@ go_library( visibility = ["//visibility:public"], deps = SSZ_DEPS + [ "//encoding/bytesutil:go_default_library", + "//math:go_default_library", "//proto/engine/v1:go_default_library", "//proto/eth/ext:go_default_library", "//runtime/version:go_default_library", diff --git a/proto/prysm/v1alpha1/beacon_state.pb.go b/proto/prysm/v1alpha1/beacon_state.pb.go index 71a2b06e9a..aac6093e1d 100755 --- a/proto/prysm/v1alpha1/beacon_state.pb.go +++ b/proto/prysm/v1alpha1/beacon_state.pb.go @@ -12,6 +12,7 @@ import ( github_com_prysmaticlabs_go_bitfield "github.com/prysmaticlabs/go-bitfield" github_com_prysmaticlabs_prysm_v5_consensus_types_primitives "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + github_com_prysmaticlabs_prysm_v5_math "github.com/prysmaticlabs/prysm/v5/math" v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" _ "github.com/prysmaticlabs/prysm/v5/proto/eth/ext" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -1868,10 +1869,10 @@ type BeaconStateElectra struct { NextWithdrawalValidatorIndex github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.ValidatorIndex `protobuf:"varint,11002,opt,name=next_withdrawal_validator_index,json=nextWithdrawalValidatorIndex,proto3" json:"next_withdrawal_validator_index,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.ValidatorIndex"` HistoricalSummaries []*HistoricalSummary `protobuf:"bytes,11003,rep,name=historical_summaries,json=historicalSummaries,proto3" json:"historical_summaries,omitempty" ssz-max:"16777216"` DepositReceiptsStartIndex uint64 `protobuf:"varint,12001,opt,name=deposit_receipts_start_index,json=depositReceiptsStartIndex,proto3" json:"deposit_receipts_start_index,omitempty"` - DepositBalanceToConsume uint64 `protobuf:"varint,12002,opt,name=deposit_balance_to_consume,json=depositBalanceToConsume,proto3" json:"deposit_balance_to_consume,omitempty"` - ExitBalanceToConsume uint64 `protobuf:"varint,12003,opt,name=exit_balance_to_consume,json=exitBalanceToConsume,proto3" json:"exit_balance_to_consume,omitempty"` + DepositBalanceToConsume github_com_prysmaticlabs_prysm_v5_math.Gwei `protobuf:"varint,12002,opt,name=deposit_balance_to_consume,json=depositBalanceToConsume,proto3" json:"deposit_balance_to_consume,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/math.Gwei"` + ExitBalanceToConsume github_com_prysmaticlabs_prysm_v5_math.Gwei `protobuf:"varint,12003,opt,name=exit_balance_to_consume,json=exitBalanceToConsume,proto3" json:"exit_balance_to_consume,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/math.Gwei"` EarliestExitEpoch github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch `protobuf:"varint,12004,opt,name=earliest_exit_epoch,json=earliestExitEpoch,proto3" json:"earliest_exit_epoch,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Epoch"` - ConsolidationBalanceToConsume uint64 `protobuf:"varint,12005,opt,name=consolidation_balance_to_consume,json=consolidationBalanceToConsume,proto3" json:"consolidation_balance_to_consume,omitempty"` + ConsolidationBalanceToConsume github_com_prysmaticlabs_prysm_v5_math.Gwei `protobuf:"varint,12005,opt,name=consolidation_balance_to_consume,json=consolidationBalanceToConsume,proto3" json:"consolidation_balance_to_consume,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/math.Gwei"` EarliestConsolidationEpoch github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch `protobuf:"varint,12006,opt,name=earliest_consolidation_epoch,json=earliestConsolidationEpoch,proto3" json:"earliest_consolidation_epoch,omitempty" cast-type:"github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Epoch"` PendingBalanceDeposits []*PendingBalanceDeposit `protobuf:"bytes,12007,rep,name=pending_balance_deposits,json=pendingBalanceDeposits,proto3" json:"pending_balance_deposits,omitempty" ssz-max:"134217728"` PendingPartialWithdrawals []*PendingPartialWithdrawal `protobuf:"bytes,12008,rep,name=pending_partial_withdrawals,json=pendingPartialWithdrawals,proto3" json:"pending_partial_withdrawals,omitempty" ssz-max:"134217728"` @@ -2113,18 +2114,18 @@ func (x *BeaconStateElectra) GetDepositReceiptsStartIndex() uint64 { return 0 } -func (x *BeaconStateElectra) GetDepositBalanceToConsume() uint64 { +func (x *BeaconStateElectra) GetDepositBalanceToConsume() github_com_prysmaticlabs_prysm_v5_math.Gwei { if x != nil { return x.DepositBalanceToConsume } - return 0 + return github_com_prysmaticlabs_prysm_v5_math.Gwei(0) } -func (x *BeaconStateElectra) GetExitBalanceToConsume() uint64 { +func (x *BeaconStateElectra) GetExitBalanceToConsume() github_com_prysmaticlabs_prysm_v5_math.Gwei { if x != nil { return x.ExitBalanceToConsume } - return 0 + return github_com_prysmaticlabs_prysm_v5_math.Gwei(0) } func (x *BeaconStateElectra) GetEarliestExitEpoch() github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch { @@ -2134,11 +2135,11 @@ func (x *BeaconStateElectra) GetEarliestExitEpoch() github_com_prysmaticlabs_pry return github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch(0) } -func (x *BeaconStateElectra) GetConsolidationBalanceToConsume() uint64 { +func (x *BeaconStateElectra) GetConsolidationBalanceToConsume() github_com_prysmaticlabs_prysm_v5_math.Gwei { if x != nil { return x.ConsolidationBalanceToConsume } - return 0 + return github_com_prysmaticlabs_prysm_v5_math.Gwei(0) } func (x *BeaconStateElectra) GetEarliestConsolidationEpoch() github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch { @@ -3010,7 +3011,7 @@ var file_proto_prysm_v1alpha1_beacon_state_proto_rawDesc = []byte{ 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x42, 0x0c, 0x92, 0xb5, 0x18, 0x08, 0x31, 0x36, 0x37, 0x37, 0x37, 0x32, 0x31, 0x36, 0x52, 0x13, 0x68, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x69, 0x65, 0x73, 0x22, 0xfa, 0x17, 0x0a, 0x12, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, + 0x72, 0x69, 0x65, 0x73, 0x22, 0x8d, 0x19, 0x0a, 0x12, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x45, 0x6c, 0x65, 0x63, 0x74, 0x72, 0x61, 0x12, 0x22, 0x0a, 0x0c, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0xe9, 0x07, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x67, 0x65, 0x6e, 0x65, 0x73, 0x69, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x12, @@ -3151,85 +3152,94 @@ var file_proto_prysm_v1alpha1_beacon_state_proto_rawDesc = []byte{ 0x69, 0x70, 0x74, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0xe1, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x19, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x70, 0x74, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x49, 0x6e, 0x64, - 0x65, 0x78, 0x12, 0x3c, 0x0a, 0x1a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x62, 0x61, + 0x65, 0x78, 0x12, 0x6d, 0x0a, 0x1a, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, - 0x18, 0xe2, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x17, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, - 0x12, 0x36, 0x0a, 0x17, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x18, 0xe3, 0x5d, 0x20, 0x01, - 0x28, 0x04, 0x52, 0x14, 0x65, 0x78, 0x69, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x54, - 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x77, 0x0a, 0x13, 0x65, 0x61, 0x72, 0x6c, - 0x69, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, - 0xe4, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, 0x18, 0x42, 0x67, 0x69, 0x74, 0x68, + 0x18, 0xe2, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x42, 0x2f, 0x82, 0xb5, 0x18, 0x2b, 0x67, 0x69, 0x74, + 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, + 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x6d, + 0x61, 0x74, 0x68, 0x2e, 0x47, 0x77, 0x65, 0x69, 0x52, 0x17, 0x64, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, + 0x65, 0x12, 0x67, 0x0a, 0x17, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x18, 0xe3, 0x5d, 0x20, + 0x01, 0x28, 0x04, 0x42, 0x2f, 0x82, 0xb5, 0x18, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, + 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, + 0x47, 0x77, 0x65, 0x69, 0x52, 0x14, 0x65, 0x78, 0x69, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x54, 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x77, 0x0a, 0x13, 0x65, 0x61, + 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x5f, 0x65, 0x70, 0x6f, 0x63, + 0x68, 0x18, 0xe4, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, 0x18, 0x42, 0x67, 0x69, + 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, + 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, + 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, + 0x70, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, + 0x52, 0x11, 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x45, 0x78, 0x69, 0x74, 0x45, 0x70, + 0x6f, 0x63, 0x68, 0x12, 0x79, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x6f, 0x5f, + 0x63, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x18, 0xe5, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x42, 0x2f, + 0x82, 0xb5, 0x18, 0x2b, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, + 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, + 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x47, 0x77, 0x65, 0x69, 0x52, + 0x1d, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, + 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x89, + 0x01, 0x0a, 0x1c, 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, + 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, + 0xe6, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, 0x18, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, - 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52, 0x11, - 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x45, 0x78, 0x69, 0x74, 0x45, 0x70, 0x6f, 0x63, - 0x68, 0x12, 0x48, 0x0a, 0x20, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, 0x6f, 0x5f, 0x63, 0x6f, - 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x18, 0xe5, 0x5d, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1d, 0x63, 0x6f, - 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x61, 0x6c, 0x61, 0x6e, - 0x63, 0x65, 0x54, 0x6f, 0x43, 0x6f, 0x6e, 0x73, 0x75, 0x6d, 0x65, 0x12, 0x89, 0x01, 0x0a, 0x1c, - 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x70, 0x6f, 0x63, 0x68, 0x18, 0xe6, 0x5d, 0x20, - 0x01, 0x28, 0x04, 0x42, 0x46, 0x82, 0xb5, 0x18, 0x42, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, - 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x63, 0x6f, 0x6e, 0x73, 0x65, - 0x6e, 0x73, 0x75, 0x73, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x70, 0x72, 0x69, 0x6d, 0x69, - 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52, 0x1a, 0x65, 0x61, 0x72, - 0x6c, 0x69, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x76, 0x0a, 0x18, 0x70, 0x65, 0x6e, 0x64, 0x69, - 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x65, 0x70, 0x6f, 0x73, - 0x69, 0x74, 0x73, 0x18, 0xe7, 0x5d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x65, 0x74, 0x68, - 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, - 0x61, 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, - 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, 0x33, - 0x34, 0x32, 0x31, 0x37, 0x37, 0x32, 0x38, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x12, - 0x7f, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x69, - 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x73, 0x18, 0xe8, - 0x5d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, - 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, - 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, 0x74, 0x68, - 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, 0x33, 0x34, 0x32, - 0x31, 0x37, 0x37, 0x32, 0x38, 0x52, 0x19, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, - 0x72, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x73, - 0x12, 0x6f, 0x0a, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, 0x6f, 0x6e, 0x73, - 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe9, 0x5d, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, - 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, - 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, - 0x92, 0xb5, 0x18, 0x06, 0x32, 0x36, 0x32, 0x31, 0x34, 0x34, 0x52, 0x15, 0x70, 0x65, 0x6e, 0x64, - 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x77, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x25, - 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x09, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, - 0x33, 0x32, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, 0x68, 0x12, 0x31, - 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, - 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, - 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, 0x75, 0x6c, 0x74, - 0x79, 0x22, 0x7f, 0x0a, 0x11, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, 0x61, 0x6c, 0x53, - 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x34, 0x0a, 0x12, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, - 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x10, 0x62, 0x6c, 0x6f, 0x63, - 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x12, 0x34, 0x0a, 0x12, - 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x72, 0x6f, - 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, - 0x52, 0x10, 0x73, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x6f, - 0x6f, 0x74, 0x42, 0x9b, 0x01, 0x0a, 0x19, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, + 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x73, 0x2e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x52, 0x1a, + 0x65, 0x61, 0x72, 0x6c, 0x69, 0x65, 0x73, 0x74, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x45, 0x70, 0x6f, 0x63, 0x68, 0x12, 0x76, 0x0a, 0x18, 0x70, 0x65, + 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x64, 0x65, + 0x70, 0x6f, 0x73, 0x69, 0x74, 0x73, 0x18, 0xe7, 0x5d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2c, 0x2e, + 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, + 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x42, 0x61, 0x6c, + 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x42, 0x0d, 0x92, 0xb5, 0x18, + 0x09, 0x31, 0x33, 0x34, 0x32, 0x31, 0x37, 0x37, 0x32, 0x38, 0x52, 0x16, 0x70, 0x65, 0x6e, 0x64, + 0x69, 0x6e, 0x67, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x44, 0x65, 0x70, 0x6f, 0x73, 0x69, + 0x74, 0x73, 0x12, 0x7f, 0x0a, 0x1b, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x61, + 0x72, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, + 0x73, 0x18, 0xe8, 0x5d, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2f, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x42, 0x10, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, - 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x70, - 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x72, - 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, 0x65, 0x74, 0x68, - 0xaa, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x45, 0x74, 0x68, 0x2e, - 0x56, 0x31, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, - 0x65, 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x2e, 0x50, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x57, + 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, 0x61, 0x6c, 0x42, 0x0d, 0x92, 0xb5, 0x18, 0x09, 0x31, + 0x33, 0x34, 0x32, 0x31, 0x37, 0x37, 0x32, 0x38, 0x52, 0x19, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, + 0x67, 0x50, 0x61, 0x72, 0x74, 0x69, 0x61, 0x6c, 0x57, 0x69, 0x74, 0x68, 0x64, 0x72, 0x61, 0x77, + 0x61, 0x6c, 0x73, 0x12, 0x6f, 0x0a, 0x16, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x5f, 0x63, + 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xe9, 0x5d, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x2b, 0x2e, 0x65, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, + 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x50, 0x65, 0x6e, + 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x42, 0x0a, 0x92, 0xb5, 0x18, 0x06, 0x32, 0x36, 0x32, 0x31, 0x34, 0x34, 0x52, 0x15, 0x70, + 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x69, 0x64, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x8d, 0x01, 0x0a, 0x08, 0x50, 0x6f, 0x77, 0x42, 0x6c, 0x6f, 0x63, + 0x6b, 0x12, 0x25, 0x0a, 0x0a, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x09, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x48, 0x61, 0x73, 0x68, 0x12, 0x27, 0x0a, 0x0b, 0x70, 0x61, 0x72, 0x65, + 0x6e, 0x74, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, + 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x0a, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x48, 0x61, 0x73, + 0x68, 0x12, 0x31, 0x0a, 0x10, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x69, + 0x63, 0x75, 0x6c, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, + 0x02, 0x33, 0x32, 0x52, 0x0f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x69, 0x66, 0x66, 0x69, 0x63, + 0x75, 0x6c, 0x74, 0x79, 0x22, 0x7f, 0x0a, 0x11, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x69, 0x63, + 0x61, 0x6c, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x34, 0x0a, 0x12, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, 0x02, 0x33, 0x32, 0x52, 0x10, 0x62, + 0x6c, 0x6f, 0x63, 0x6b, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x12, + 0x34, 0x0a, 0x12, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x73, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x5f, 0x72, 0x6f, 0x6f, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x42, 0x06, 0x8a, 0xb5, 0x18, + 0x02, 0x33, 0x32, 0x52, 0x10, 0x73, 0x74, 0x61, 0x74, 0x65, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, + 0x79, 0x52, 0x6f, 0x6f, 0x74, 0x42, 0x9b, 0x01, 0x0a, 0x19, 0x6f, 0x72, 0x67, 0x2e, 0x65, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x65, 0x74, 0x68, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x42, 0x10, 0x42, 0x65, 0x61, 0x63, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x3a, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, + 0x63, 0x6f, 0x6d, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x6c, 0x61, 0x62, + 0x73, 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x35, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x2f, 0x70, 0x72, 0x79, 0x73, 0x6d, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x3b, + 0x65, 0x74, 0x68, 0xaa, 0x02, 0x15, 0x45, 0x74, 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x2e, 0x45, + 0x74, 0x68, 0x2e, 0x56, 0x31, 0x41, 0x6c, 0x70, 0x68, 0x61, 0x31, 0xca, 0x02, 0x15, 0x45, 0x74, + 0x68, 0x65, 0x72, 0x65, 0x75, 0x6d, 0x5c, 0x45, 0x74, 0x68, 0x5c, 0x76, 0x31, 0x61, 0x6c, 0x70, + 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/prysm/v1alpha1/beacon_state.proto b/proto/prysm/v1alpha1/beacon_state.proto index 3fb67903b1..f4bc57e2ce 100644 --- a/proto/prysm/v1alpha1/beacon_state.proto +++ b/proto/prysm/v1alpha1/beacon_state.proto @@ -397,10 +397,10 @@ message BeaconStateElectra { // Fields introduced in EIP-7251 fork [12001-13000] uint64 deposit_receipts_start_index = 12001; - uint64 deposit_balance_to_consume = 12002; - uint64 exit_balance_to_consume = 12003; + uint64 deposit_balance_to_consume = 12002 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/math.Gwei"]; + uint64 exit_balance_to_consume = 12003 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/math.Gwei"]; uint64 earliest_exit_epoch = 12004 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Epoch"]; - uint64 consolidation_balance_to_consume = 12005; + uint64 consolidation_balance_to_consume = 12005 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/math.Gwei"]; uint64 earliest_consolidation_epoch = 12006 [(ethereum.eth.ext.cast_type) = "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives.Epoch"]; repeated PendingBalanceDeposit pending_balance_deposits = 12007 [(ethereum.eth.ext.ssz_max) = "pending_balance_deposits_limit"]; repeated PendingPartialWithdrawal pending_partial_withdrawals = 12008 [(ethereum.eth.ext.ssz_max) = "pending_partial_withdrawals_limit"]; diff --git a/proto/prysm/v1alpha1/generated.ssz.go b/proto/prysm/v1alpha1/generated.ssz.go index 707521ef83..d95f6eb989 100644 --- a/proto/prysm/v1alpha1/generated.ssz.go +++ b/proto/prysm/v1alpha1/generated.ssz.go @@ -1,10 +1,11 @@ // Code generated by fastssz. DO NOT EDIT. -// Hash: ae92821338b1fc2296cda7b35445bbd7005303c62eab0bcef3ef9c4c7cf0b9ef +// Hash: 4e7c07afe12b97ee034415333c33967cab8cf624f3fd43f37562cd6307760ec3 package eth import ( ssz "github.com/prysmaticlabs/fastssz" github_com_prysmaticlabs_prysm_v5_consensus_types_primitives "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + github_com_prysmaticlabs_prysm_v5_math "github.com/prysmaticlabs/prysm/v5/math" v1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ) @@ -17646,16 +17647,16 @@ func (b *BeaconStateElectra) MarshalSSZTo(buf []byte) (dst []byte, err error) { dst = ssz.MarshalUint64(dst, b.DepositReceiptsStartIndex) // Field (29) 'DepositBalanceToConsume' - dst = ssz.MarshalUint64(dst, b.DepositBalanceToConsume) + dst = ssz.MarshalUint64(dst, uint64(b.DepositBalanceToConsume)) // Field (30) 'ExitBalanceToConsume' - dst = ssz.MarshalUint64(dst, b.ExitBalanceToConsume) + dst = ssz.MarshalUint64(dst, uint64(b.ExitBalanceToConsume)) // Field (31) 'EarliestExitEpoch' dst = ssz.MarshalUint64(dst, uint64(b.EarliestExitEpoch)) // Field (32) 'ConsolidationBalanceToConsume' - dst = ssz.MarshalUint64(dst, b.ConsolidationBalanceToConsume) + dst = ssz.MarshalUint64(dst, uint64(b.ConsolidationBalanceToConsume)) // Field (33) 'EarliestConsolidationEpoch' dst = ssz.MarshalUint64(dst, uint64(b.EarliestConsolidationEpoch)) @@ -17979,16 +17980,16 @@ func (b *BeaconStateElectra) UnmarshalSSZ(buf []byte) error { b.DepositReceiptsStartIndex = ssz.UnmarshallUint64(buf[2736653:2736661]) // Field (29) 'DepositBalanceToConsume' - b.DepositBalanceToConsume = ssz.UnmarshallUint64(buf[2736661:2736669]) + b.DepositBalanceToConsume = github_com_prysmaticlabs_prysm_v5_math.Gwei(ssz.UnmarshallUint64(buf[2736661:2736669])) // Field (30) 'ExitBalanceToConsume' - b.ExitBalanceToConsume = ssz.UnmarshallUint64(buf[2736669:2736677]) + b.ExitBalanceToConsume = github_com_prysmaticlabs_prysm_v5_math.Gwei(ssz.UnmarshallUint64(buf[2736669:2736677])) // Field (31) 'EarliestExitEpoch' b.EarliestExitEpoch = github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch(ssz.UnmarshallUint64(buf[2736677:2736685])) // Field (32) 'ConsolidationBalanceToConsume' - b.ConsolidationBalanceToConsume = ssz.UnmarshallUint64(buf[2736685:2736693]) + b.ConsolidationBalanceToConsume = github_com_prysmaticlabs_prysm_v5_math.Gwei(ssz.UnmarshallUint64(buf[2736685:2736693])) // Field (33) 'EarliestConsolidationEpoch' b.EarliestConsolidationEpoch = github_com_prysmaticlabs_prysm_v5_consensus_types_primitives.Epoch(ssz.UnmarshallUint64(buf[2736693:2736701])) @@ -18567,16 +18568,16 @@ func (b *BeaconStateElectra) HashTreeRootWith(hh *ssz.Hasher) (err error) { hh.PutUint64(b.DepositReceiptsStartIndex) // Field (29) 'DepositBalanceToConsume' - hh.PutUint64(b.DepositBalanceToConsume) + hh.PutUint64(uint64(b.DepositBalanceToConsume)) // Field (30) 'ExitBalanceToConsume' - hh.PutUint64(b.ExitBalanceToConsume) + hh.PutUint64(uint64(b.ExitBalanceToConsume)) // Field (31) 'EarliestExitEpoch' hh.PutUint64(uint64(b.EarliestExitEpoch)) // Field (32) 'ConsolidationBalanceToConsume' - hh.PutUint64(b.ConsolidationBalanceToConsume) + hh.PutUint64(uint64(b.ConsolidationBalanceToConsume)) // Field (33) 'EarliestConsolidationEpoch' hh.PutUint64(uint64(b.EarliestConsolidationEpoch)) diff --git a/testing/spectest/shared/electra/ssz_static/BUILD.bazel b/testing/spectest/shared/electra/ssz_static/BUILD.bazel index 0c972544a4..b9b0431342 100644 --- a/testing/spectest/shared/electra/ssz_static/BUILD.bazel +++ b/testing/spectest/shared/electra/ssz_static/BUILD.bazel @@ -7,8 +7,10 @@ go_library( importpath = "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/electra/ssz_static", visibility = ["//testing/spectest:__subpackages__"], deps = [ + "//beacon-chain/state/state-native:go_default_library", "//proto/engine/v1:go_default_library", "//proto/prysm/v1alpha1:go_default_library", + "//testing/require:go_default_library", "//testing/spectest/shared/common/ssz_static:go_default_library", "@com_github_prysmaticlabs_fastssz//:go_default_library", ], diff --git a/testing/spectest/shared/electra/ssz_static/ssz_static.go b/testing/spectest/shared/electra/ssz_static/ssz_static.go index 9f9c6593bc..ae1ac0ddf2 100644 --- a/testing/spectest/shared/electra/ssz_static/ssz_static.go +++ b/testing/spectest/shared/electra/ssz_static/ssz_static.go @@ -1,12 +1,15 @@ package ssz_static import ( + "context" "errors" "testing" fssz "github.com/prysmaticlabs/fastssz" + state_native "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native" enginev1 "github.com/prysmaticlabs/prysm/v5/proto/engine/v1" ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/testing/require" common "github.com/prysmaticlabs/prysm/v5/testing/spectest/shared/common/ssz_static" ) @@ -16,26 +19,21 @@ func RunSSZStaticTests(t *testing.T, config string) { } func customHtr(t *testing.T, htrs []common.HTR, object interface{}) []common.HTR { - // TODO: Replace BeaconStateDeneb with BeaconStateElectra below and uncomment the code - //_, ok := object.(*ethpb.BeaconStateDeneb) - //if !ok { - // return htrs - //} - // - //htrs = append(htrs, func(s interface{}) ([32]byte, error) { - // beaconState, err := state_native.InitializeFromProtoDeneb(s.(*ethpb.BeaconStateDeneb)) - // require.NoError(t, err) - // return beaconState.HashTreeRoot(context.Background()) - //}) + _, ok := object.(*ethpb.BeaconStateElectra) + if !ok { + return htrs + } + + htrs = append(htrs, func(s interface{}) ([32]byte, error) { + beaconState, err := state_native.InitializeFromProtoElectra(s.(*ethpb.BeaconStateElectra)) + require.NoError(t, err) + return beaconState.HashTreeRoot(context.Background()) + }) return htrs } // UnmarshalledSSZ unmarshalls serialized input. func UnmarshalledSSZ(t *testing.T, serializedBytes []byte, folderName string) (interface{}, error) { - // TODO: Remove this check once BeaconState custom HTR function is ready - if folderName == "BeaconState" { - t.Skip("BeaconState is not ready") - } var obj interface{} switch folderName { case "ExecutionPayload": diff --git a/testing/util/BUILD.bazel b/testing/util/BUILD.bazel index 3f6c1e6121..7871191c5a 100644 --- a/testing/util/BUILD.bazel +++ b/testing/util/BUILD.bazel @@ -16,6 +16,7 @@ go_library( "deneb.go", "deneb_state.go", "deposits.go", + "electra.go", "helpers.go", "merge.go", "state.go", diff --git a/testing/util/block.go b/testing/util/block.go index bc7485a4b6..41c7812b1f 100644 --- a/testing/util/block.go +++ b/testing/util/block.go @@ -1140,6 +1140,19 @@ func HydrateSignedBeaconBlockDeneb(b *ethpb.SignedBeaconBlockDeneb) *ethpb.Signe return b } +// HydrateSignedBeaconBlockElectra hydrates a signed beacon block with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateSignedBeaconBlockElectra(b *ethpb.SignedBeaconBlockElectra) *ethpb.SignedBeaconBlockElectra { + if b == nil { + b = ðpb.SignedBeaconBlockElectra{} + } + if b.Signature == nil { + b.Signature = make([]byte, fieldparams.BLSSignatureLength) + } + b.Block = HydrateBeaconBlockElectra(b.Block) + return b +} + // HydrateSignedBeaconBlockContentsDeneb hydrates a signed beacon block with correct field length sizes // to comply with fssz marshalling and unmarshalling rules. func HydrateSignedBeaconBlockContentsDeneb(b *ethpb.SignedBeaconBlockContentsDeneb) *ethpb.SignedBeaconBlockContentsDeneb { @@ -1173,6 +1186,22 @@ func HydrateBeaconBlockDeneb(b *ethpb.BeaconBlockDeneb) *ethpb.BeaconBlockDeneb return b } +// HydrateBeaconBlockElectra hydrates a beacon block with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateBeaconBlockElectra(b *ethpb.BeaconBlockElectra) *ethpb.BeaconBlockElectra { + if b == nil { + b = ðpb.BeaconBlockElectra{} + } + if b.ParentRoot == nil { + b.ParentRoot = make([]byte, fieldparams.RootLength) + } + if b.StateRoot == nil { + b.StateRoot = make([]byte, fieldparams.RootLength) + } + b.Body = HydrateBeaconBlockBodyElectra(b.Body) + return b +} + // HydrateV2BeaconBlockDeneb hydrates a v2 beacon block with correct field length sizes // to comply with fssz marshalling and unmarshalling rules. func HydrateV2BeaconBlockDeneb(b *v2.BeaconBlockDeneb) *v2.BeaconBlockDeneb { @@ -1231,6 +1260,50 @@ func HydrateBeaconBlockBodyDeneb(b *ethpb.BeaconBlockBodyDeneb) *ethpb.BeaconBlo return b } +// HydrateBeaconBlockBodyElectra hydrates a beacon block body with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateBeaconBlockBodyElectra(b *ethpb.BeaconBlockBodyElectra) *ethpb.BeaconBlockBodyElectra { + if b == nil { + b = ðpb.BeaconBlockBodyElectra{} + } + if b.RandaoReveal == nil { + b.RandaoReveal = make([]byte, fieldparams.BLSSignatureLength) + } + if b.Graffiti == nil { + b.Graffiti = make([]byte, fieldparams.RootLength) + } + if b.Eth1Data == nil { + b.Eth1Data = ðpb.Eth1Data{ + DepositRoot: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + } + } + if b.SyncAggregate == nil { + b.SyncAggregate = ðpb.SyncAggregate{ + SyncCommitteeBits: make([]byte, fieldparams.SyncAggregateSyncCommitteeBytesLength), + SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength), + } + } + if b.ExecutionPayload == nil { + b.ExecutionPayload = &enginev1.ExecutionPayloadElectra{ + ParentHash: make([]byte, fieldparams.RootLength), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, fieldparams.RootLength), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, fieldparams.RootLength), + Transactions: make([][]byte, 0), + Withdrawals: make([]*enginev1.Withdrawal, 0), + DepositReceipts: make([]*enginev1.DepositReceipt, 0), + WithdrawalRequests: make([]*enginev1.ExecutionLayerWithdrawalRequest, 0), + } + } + return b +} + // HydrateV2BeaconBlockBodyDeneb hydrates a v2 beacon block body with correct field length sizes // to comply with fssz marshalling and unmarshalling rules. func HydrateV2BeaconBlockBodyDeneb(b *v2.BeaconBlockBodyDeneb) *v2.BeaconBlockBodyDeneb { @@ -1283,6 +1356,16 @@ func HydrateSignedBlindedBeaconBlockDeneb(b *ethpb.SignedBlindedBeaconBlockDeneb return b } +// HydrateSignedBlindedBeaconBlockElectra hydrates a signed blinded beacon block with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateSignedBlindedBeaconBlockElectra(b *ethpb.SignedBlindedBeaconBlockElectra) *ethpb.SignedBlindedBeaconBlockElectra { + if b.Signature == nil { + b.Signature = make([]byte, fieldparams.BLSSignatureLength) + } + b.Message = HydrateBlindedBeaconBlockElectra(b.Message) + return b +} + // HydrateV2SignedBlindedBeaconBlockDeneb hydrates a signed v2 blinded beacon block with correct field length sizes // to comply with fssz marshalling and unmarshalling rules. func HydrateV2SignedBlindedBeaconBlockDeneb(b *v2.SignedBlindedBeaconBlockDeneb) *v2.SignedBlindedBeaconBlockDeneb { @@ -1309,6 +1392,22 @@ func HydrateBlindedBeaconBlockDeneb(b *ethpb.BlindedBeaconBlockDeneb) *ethpb.Bli return b } +// HydrateBlindedBeaconBlockElectra hydrates a blinded beacon block with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateBlindedBeaconBlockElectra(b *ethpb.BlindedBeaconBlockElectra) *ethpb.BlindedBeaconBlockElectra { + if b == nil { + b = ðpb.BlindedBeaconBlockElectra{} + } + if b.ParentRoot == nil { + b.ParentRoot = make([]byte, fieldparams.RootLength) + } + if b.StateRoot == nil { + b.StateRoot = make([]byte, fieldparams.RootLength) + } + b.Body = HydrateBlindedBeaconBlockBodyElectra(b.Body) + return b +} + // HydrateV2BlindedBeaconBlockDeneb hydrates a v2 blinded beacon block with correct field length sizes // to comply with fssz marshalling and unmarshalling rules. func HydrateV2BlindedBeaconBlockDeneb(b *v2.BlindedBeaconBlockDeneb) *v2.BlindedBeaconBlockDeneb { @@ -1367,6 +1466,50 @@ func HydrateBlindedBeaconBlockBodyDeneb(b *ethpb.BlindedBeaconBlockBodyDeneb) *e return b } +// HydrateBlindedBeaconBlockBodyElectra hydrates a blinded beacon block body with correct field length sizes +// to comply with fssz marshalling and unmarshalling rules. +func HydrateBlindedBeaconBlockBodyElectra(b *ethpb.BlindedBeaconBlockBodyElectra) *ethpb.BlindedBeaconBlockBodyElectra { + if b == nil { + b = ðpb.BlindedBeaconBlockBodyElectra{} + } + if b.RandaoReveal == nil { + b.RandaoReveal = make([]byte, fieldparams.BLSSignatureLength) + } + if b.Graffiti == nil { + b.Graffiti = make([]byte, 32) + } + if b.Eth1Data == nil { + b.Eth1Data = ðpb.Eth1Data{ + DepositRoot: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, 32), + } + } + if b.SyncAggregate == nil { + b.SyncAggregate = ðpb.SyncAggregate{ + SyncCommitteeBits: make([]byte, fieldparams.SyncAggregateSyncCommitteeBytesLength), + SyncCommitteeSignature: make([]byte, fieldparams.BLSSignatureLength), + } + } + if b.ExecutionPayloadHeader == nil { + b.ExecutionPayloadHeader = &enginev1.ExecutionPayloadHeaderElectra{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, fieldparams.RootLength), + ReceiptsRoot: make([]byte, fieldparams.RootLength), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, 32), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + TransactionsRoot: make([]byte, fieldparams.RootLength), + WithdrawalsRoot: make([]byte, fieldparams.RootLength), + WithdrawalRequestsRoot: make([]byte, fieldparams.RootLength), + DepositReceiptsRoot: make([]byte, fieldparams.RootLength), + } + } + return b +} + // HydrateV2BlindedBeaconBlockBodyDeneb hydrates a blinded v2 beacon block body with correct field length sizes // to comply with fssz marshalling and unmarshalling rules. func HydrateV2BlindedBeaconBlockBodyDeneb(b *v2.BlindedBeaconBlockBodyDeneb) *v2.BlindedBeaconBlockBodyDeneb { diff --git a/testing/util/deneb.go b/testing/util/deneb.go index 83db5a2a5e..a847fa5023 100644 --- a/testing/util/deneb.go +++ b/testing/util/deneb.go @@ -2,7 +2,6 @@ package util import ( "encoding/binary" - "math" "math/big" "testing" @@ -187,19 +186,3 @@ func ExtendBlocksPlusBlobs(t *testing.T, blks []blocks.ROBlock, size int) ([]blo return blks, blobs } - -// HackDenebMaxuint is helpful for tests that need to set up cases where the deneb fork has passed. -// We have unit tests that assert our config matches the upstream config, where the next fork is always -// set to MaxUint64 until the fork epoch is formally set. This creates an issue for tests that want to -// work with slots that are defined to be after deneb because converting the max epoch to a slot leads -// to multiplication overflow. -// Monkey patching tests with this function is the simplest workaround in these cases. -func HackDenebMaxuint(t *testing.T) func() { - bc := params.MainnetConfig().Copy() - bc.DenebForkEpoch = math.MaxUint32 - undo, err := params.SetActiveWithUndo(bc) - require.NoError(t, err) - return func() { - require.NoError(t, undo()) - } -} diff --git a/testing/util/electra.go b/testing/util/electra.go new file mode 100644 index 0000000000..81a1279237 --- /dev/null +++ b/testing/util/electra.go @@ -0,0 +1,25 @@ +package util + +import ( + "math" + "testing" + + "github.com/prysmaticlabs/prysm/v5/config/params" + "github.com/prysmaticlabs/prysm/v5/testing/require" +) + +// HackElectraMaxuint is helpful for tests that need to set up cases where the electra fork has passed. +// We have unit tests that assert our config matches the upstream config, where the next fork is always +// set to MaxUint64 until the fork epoch is formally set. This creates an issue for tests that want to +// work with slots that are defined to be after electra because converting the max epoch to a slot leads +// to multiplication overflow. +// Monkey patching tests with this function is the simplest workaround in these cases. +func HackElectraMaxuint(t *testing.T) func() { + bc := params.MainnetConfig().Copy() + bc.ElectraForkEpoch = math.MaxUint32 + undo, err := params.SetActiveWithUndo(bc) + require.NoError(t, err) + return func() { + require.NoError(t, undo()) + } +} diff --git a/testing/util/merge.go b/testing/util/merge.go index 83217e85c4..c82c42106a 100644 --- a/testing/util/merge.go +++ b/testing/util/merge.go @@ -35,6 +35,10 @@ func NewBeaconBlockDeneb() *ethpb.SignedBeaconBlockDeneb { return HydrateSignedBeaconBlockDeneb(ðpb.SignedBeaconBlockDeneb{}) } +func NewBeaconBlockElectra() *ethpb.SignedBeaconBlockElectra { + return HydrateSignedBeaconBlockElectra(ðpb.SignedBeaconBlockElectra{}) +} + // NewBeaconBlockContentsDeneb creates a beacon block with minimum marshalable fields. func NewBeaconBlockContentsDeneb() *ethpb.SignedBeaconBlockContentsDeneb { return HydrateSignedBeaconBlockContentsDeneb(ðpb.SignedBeaconBlockContentsDeneb{}) @@ -45,6 +49,11 @@ func NewBlindedBeaconBlockDeneb() *ethpb.SignedBlindedBeaconBlockDeneb { return HydrateSignedBlindedBeaconBlockDeneb(ðpb.SignedBlindedBeaconBlockDeneb{}) } +// NewBlindedBeaconBlockElectra creates a blinded beacon block with minimum marshalable fields. +func NewBlindedBeaconBlockElectra() *ethpb.SignedBlindedBeaconBlockElectra { + return HydrateSignedBlindedBeaconBlockElectra(ðpb.SignedBlindedBeaconBlockElectra{}) +} + // NewBlindedBeaconBlockCapellaV2 creates a blinded beacon block with minimum marshalable fields. func NewBlindedBeaconBlockCapellaV2() *v2.SignedBlindedBeaconBlockCapella { return HydrateV2SignedBlindedBeaconBlockCapella(&v2.SignedBlindedBeaconBlockCapella{}) diff --git a/testing/util/state.go b/testing/util/state.go index 5d96ba9a32..332918e125 100644 --- a/testing/util/state.go +++ b/testing/util/state.go @@ -386,6 +386,76 @@ func NewBeaconStateDeneb(options ...func(state *ethpb.BeaconStateDeneb) error) ( return st.Copy(), nil } +// NewBeaconStateElectra creates a beacon state with minimum marshalable fields. +func NewBeaconStateElectra(options ...func(state *ethpb.BeaconStateElectra) error) (state.BeaconState, error) { + pubkeys := make([][]byte, 512) + for i := range pubkeys { + pubkeys[i] = make([]byte, 48) + } + + seed := ðpb.BeaconStateElectra{ + BlockRoots: filledByteSlice2D(uint64(params.BeaconConfig().SlotsPerHistoricalRoot), 32), + StateRoots: filledByteSlice2D(uint64(params.BeaconConfig().SlotsPerHistoricalRoot), 32), + Slashings: make([]uint64, params.BeaconConfig().EpochsPerSlashingsVector), + RandaoMixes: filledByteSlice2D(uint64(params.BeaconConfig().EpochsPerHistoricalVector), 32), + Validators: make([]*ethpb.Validator, 0), + CurrentJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + Eth1Data: ðpb.Eth1Data{ + DepositRoot: make([]byte, fieldparams.RootLength), + BlockHash: make([]byte, 32), + }, + Fork: ðpb.Fork{ + PreviousVersion: make([]byte, 4), + CurrentVersion: make([]byte, 4), + }, + Eth1DataVotes: make([]*ethpb.Eth1Data, 0), + HistoricalRoots: make([][]byte, 0), + JustificationBits: bitfield.Bitvector4{0x0}, + FinalizedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + LatestBlockHeader: HydrateBeaconHeader(ðpb.BeaconBlockHeader{}), + PreviousJustifiedCheckpoint: ðpb.Checkpoint{Root: make([]byte, fieldparams.RootLength)}, + PreviousEpochParticipation: make([]byte, 0), + CurrentEpochParticipation: make([]byte, 0), + CurrentSyncCommittee: ðpb.SyncCommittee{ + Pubkeys: pubkeys, + AggregatePubkey: make([]byte, 48), + }, + NextSyncCommittee: ðpb.SyncCommittee{ + Pubkeys: pubkeys, + AggregatePubkey: make([]byte, 48), + }, + LatestExecutionPayloadHeader: &enginev1.ExecutionPayloadHeaderElectra{ + ParentHash: make([]byte, 32), + FeeRecipient: make([]byte, 20), + StateRoot: make([]byte, 32), + ReceiptsRoot: make([]byte, 32), + LogsBloom: make([]byte, 256), + PrevRandao: make([]byte, 32), + ExtraData: make([]byte, 0), + BaseFeePerGas: make([]byte, 32), + BlockHash: make([]byte, 32), + TransactionsRoot: make([]byte, 32), + WithdrawalsRoot: make([]byte, 32), + DepositReceiptsRoot: make([]byte, 32), + WithdrawalRequestsRoot: make([]byte, 32), + }, + } + + for _, opt := range options { + err := opt(seed) + if err != nil { + return nil, err + } + } + + var st, err = state_native.InitializeFromProtoUnsafeElectra(seed) + if err != nil { + return nil, err + } + + return st.Copy(), nil +} + // SSZ will fill 2D byte slices with their respective values, so we must fill these in too for round // trip testing. func filledByteSlice2D(length, innerLen uint64) [][]byte { diff --git a/testing/util/state_test.go b/testing/util/state_test.go index d99e2c342b..57362f7072 100644 --- a/testing/util/state_test.go +++ b/testing/util/state_test.go @@ -59,6 +59,16 @@ func TestNewBeaconStateDeneb(t *testing.T) { assert.DeepEqual(t, st.ToProtoUnsafe(), got) } +func TestNewBeaconStateElectra(t *testing.T) { + st, err := NewBeaconStateElectra() + require.NoError(t, err) + b, err := st.MarshalSSZ() + require.NoError(t, err) + got := ðpb.BeaconStateElectra{} + require.NoError(t, got.UnmarshalSSZ(b)) + assert.DeepEqual(t, st.ToProtoUnsafe(), got) +} + func TestNewBeaconState_HashTreeRoot(t *testing.T) { st, err := NewBeaconState() require.NoError(t, err) @@ -76,4 +86,12 @@ func TestNewBeaconState_HashTreeRoot(t *testing.T) { require.NoError(t, err) _, err = st.HashTreeRoot(context.Background()) require.NoError(t, err) + st, err = NewBeaconStateDeneb() + require.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) + st, err = NewBeaconStateElectra() + require.NoError(t, err) + _, err = st.HashTreeRoot(context.Background()) + require.NoError(t, err) }