From f6671105ef1f122a12972b67c1208b724e247915 Mon Sep 17 00:00:00 2001 From: Preston Van Loon Date: Thu, 25 Apr 2024 20:35:13 -0500 Subject: [PATCH] Electra: Beacon State --- beacon-chain/blockchain/execution_engine.go | 6 +- beacon-chain/core/blocks/withdrawals.go | 2 +- beacon-chain/rpc/eth/builder/handlers.go | 2 +- beacon-chain/rpc/eth/events/events.go | 4 +- .../validator/proposer_execution_payload.go | 6 +- beacon-chain/state/interfaces.go | 32 ++- beacon-chain/state/state-native/BUILD.bazel | 13 +- .../state-native/beacon_state_mainnet.go | 13 ++ .../state-native/beacon_state_minimal.go | 13 ++ .../state-native/getters_balance_deposits.go | 32 +++ .../state-native/getters_consolidation.go | 48 +++++ .../state-native/getters_payload_header.go | 20 +- .../state/state-native/getters_state.go | 90 ++++++++ .../state/state-native/getters_validator.go | 52 +++++ .../state/state-native/getters_withdrawal.go | 150 +++++++++++--- .../state-native/getters_withdrawal_test.go | 72 ++----- beacon-chain/state/state-native/hasher.go | 60 ++++++ .../state-native/setters_balance_deposits.go | 53 +++++ .../state/state-native/setters_churn.go | 79 +++++++ .../state-native/setters_consolidation.go | 66 ++++++ .../state-native/setters_payload_header.go | 12 ++ .../state/state-native/setters_withdrawal.go | 15 ++ .../state/state-native/spec_parameters.go | 4 +- beacon-chain/state/state-native/state_trie.go | 194 +++++++++++++++++- .../state/state-native/types/types.go | 48 ++++- beacon-chain/state/stateutil/BUILD.bazel | 1 + beacon-chain/state/stateutil/eip_7251_root.go | 102 +++++++++ beacon-chain/state/stateutil/trie_helpers.go | 4 +- encoding/ssz/detect/configfork.go | 14 ++ encoding/ssz/detect/configfork_test.go | 2 + .../shared/electra/ssz_static/BUILD.bazel | 2 + .../shared/electra/ssz_static/ssz_static.go | 28 ++- testing/util/state.go | 68 ++++++ 33 files changed, 1174 insertions(+), 133 deletions(-) create mode 100644 beacon-chain/state/state-native/getters_balance_deposits.go create mode 100644 beacon-chain/state/state-native/getters_consolidation.go create mode 100644 beacon-chain/state/state-native/setters_balance_deposits.go create mode 100644 beacon-chain/state/state-native/setters_churn.go create mode 100644 beacon-chain/state/state-native/setters_consolidation.go create mode 100644 beacon-chain/state/stateutil/eip_7251_root.go diff --git a/beacon-chain/blockchain/execution_engine.go b/beacon-chain/blockchain/execution_engine.go index 3d4e72442c03..0de3a4234b6d 100644 --- a/beacon-chain/blockchain/execution_engine.go +++ b/beacon-chain/blockchain/execution_engine.go @@ -324,8 +324,8 @@ func (s *Service) getPayloadAttribute(ctx context.Context, st state.BeaconState, var attr payloadattribute.Attributer switch st.Version() { - case version.Deneb: - withdrawals, err := st.ExpectedWithdrawals() + case version.Deneb, version.Electra: + 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/withdrawals.go b/beacon-chain/core/blocks/withdrawals.go index a9672c12046d..5b234d1614a8 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/rpc/eth/builder/handlers.go b/beacon-chain/rpc/eth/builder/handlers.go index 0d9fc4b136fc..d175d798522b 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/events/events.go b/beacon-chain/rpc/eth/events/events.go index 9382766d44ad..f3275b3dd213 100644 --- a/beacon-chain/rpc/eth/events/events.go +++ b/beacon-chain/rpc/eth/events/events.go @@ -440,7 +440,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()) } @@ -451,7 +451,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 70aa3831c245..8f75edef34b3 100644 --- a/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go +++ b/beacon-chain/rpc/prysm/v1alpha1/validator/proposer_execution_payload.go @@ -127,8 +127,8 @@ func (vs *Server) getLocalPayload(ctx context.Context, blk interfaces.ReadOnlyBe } var attr payloadattribute.Attributer switch st.Version() { - case version.Deneb: - withdrawals, err := st.ExpectedWithdrawals() + case version.Deneb, version.Electra: + 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/interfaces.go b/beacon-chain/state/interfaces.go index 0a6a92a27772..73139d2e9596 100644 --- a/beacon-chain/state/interfaces.go +++ b/beacon-chain/state/interfaces.go @@ -56,6 +56,7 @@ type ReadOnlyBeaconState interface { ReadOnlyParticipation ReadOnlyInactivity ReadOnlySyncCommittee + ReadOnlyElectra ToProtoUnsafe() interface{} ToProto() interface{} GenesisTime() uint64 @@ -87,6 +88,8 @@ type WriteOnlyBeaconState interface { WriteOnlyParticipation WriteOnlyInactivity WriteOnlySyncCommittee + WriteOnlyPendingBalanceDeposits + WriteOnlyElectra SetGenesisTime(val uint64) error SetGenesisValidatorsRoot(val []byte) error SetSlot(val primitives.Slot) error @@ -181,7 +184,7 @@ 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) } @@ -203,6 +206,18 @@ type ReadOnlySyncCommittee interface { NextSyncCommittee() (*ethpb.SyncCommittee, error) } +type ReadOnlyElectra interface { + ConsolidationBalanceToConsume() (uint64, error) + DepositBalanceToConsume() (uint64, error) + EarliestConsolidationEpoch() (primitives.Epoch, error) + PendingBalanceDeposits() ([]*ethpb.PendingBalanceDeposit, error) + PendingConsolidations() ([]*ethpb.PendingConsolidation, error) + NumPendingConsolidations() uint64 + NumPendingPartialWithdrawals() uint64 + ExitEpochAndUpdateChurn(exitBalance uint64) (primitives.Epoch, error) + PendingBalanceToWithdraw(idx primitives.ValidatorIndex) (uint64, error) +} + // WriteOnlyBlockRoots defines a struct which only has write access to block roots methods. type WriteOnlyBlockRoots interface { SetBlockRoots(val [][]byte) error @@ -282,3 +297,18 @@ type WriteOnlySyncCommittee interface { SetCurrentSyncCommittee(val *ethpb.SyncCommittee) error SetNextSyncCommittee(val *ethpb.SyncCommittee) error } + +type WriteOnlyPendingBalanceDeposits interface { + AppendPendingBalanceDeposit(index primitives.ValidatorIndex, amount uint64) error +} + +type WriteOnlyElectra interface { + SetConsolidationBalanceToConsume(gwei uint64) error + SetEarliestConsolidationEpoch(epoch primitives.Epoch) error + SetPendingBalanceDeposits(val []*ethpb.PendingBalanceDeposit) error + SetDepositBalanceToConsume(gwei uint64) error + SetPendingConsolidations(val []*ethpb.PendingConsolidation) error + DequeuePartialWithdrawals(idx uint64) error + AppendPendingConsolidation(val *ethpb.PendingConsolidation) error + AppendPendingPartialWithdrawal(ppw *ethpb.PendingPartialWithdrawal) error +} diff --git a/beacon-chain/state/state-native/BUILD.bazel b/beacon-chain/state/state-native/BUILD.bazel index 41f9f60c6441..e1652f3bb0c2 100644 --- a/beacon-chain/state/state-native/BUILD.bazel +++ b/beacon-chain/state/state-native/BUILD.bazel @@ -3,11 +3,15 @@ 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_eth1.go", "getters_misc.go", "getters_participation.go", @@ -22,8 +26,11 @@ 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_eth1.go", "setters_misc.go", "setters_participation.go", @@ -37,13 +44,11 @@ go_library( "ssz.go", "state_trie.go", "types.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", diff --git a/beacon-chain/state/state-native/beacon_state_mainnet.go b/beacon-chain/state/state-native/beacon_state_mainnet.go index 95eff14bf1fc..93cf2b80bda7 100644 --- a/beacon-chain/state/state-native/beacon_state_mainnet.go +++ b/beacon-chain/state/state-native/beacon_state_mainnet.go @@ -57,9 +57,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 uint64 // Gwei + exitBalanceToConsume uint64 // Gwei + earliestExitEpoch primitives.Epoch + consolidationBalanceToConsume uint64 // 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 @@ -163,6 +175,7 @@ func (b *BeaconState) MarshalJSON() ([]byte, error) { LatestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapella, NextWithdrawalIndex: b.nextWithdrawalIndex, NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + // TODO: Electra fields! } 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 efddb8d5688e..709429e57cf6 100644 --- a/beacon-chain/state/state-native/beacon_state_minimal.go +++ b/beacon-chain/state/state-native/beacon_state_minimal.go @@ -57,9 +57,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 uint64 // Gwei + exitBalanceToConsume uint64 // Gwei + earliestExitEpoch primitives.Epoch + consolidationBalanceToConsume uint64 // 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 @@ -163,6 +175,7 @@ func (b *BeaconState) MarshalJSON() ([]byte, error) { LatestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapella, NextWithdrawalIndex: b.nextWithdrawalIndex, NextWithdrawalValidatorIndex: b.nextWithdrawalValidatorIndex, + // TODO: Electra fields! } 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 000000000000..4fe0fd5e7087 --- /dev/null +++ b/beacon-chain/state/state-native/getters_balance_deposits.go @@ -0,0 +1,32 @@ +package state_native + +import ( + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +func (b *BeaconState) DepositBalanceToConsume() (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("DepositBalanceToConsume", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.depositBalanceToConsume, nil +} + +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.pendingBalanceDeposits, 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_consolidation.go b/beacon-chain/state/state-native/getters_consolidation.go new file mode 100644 index 000000000000..3c47e2a1db53 --- /dev/null +++ b/beacon-chain/state/state-native/getters_consolidation.go @@ -0,0 +1,48 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +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 +} + +func (b *BeaconState) ConsolidationBalanceToConsume() (uint64, error) { + if b.version < version.Electra { + return 0, errNotSupported("ConsolidationBalanceToConsume", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + return b.consolidationBalanceToConsume, nil +} + +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.pendingConsolidations, nil +} + +func (b *BeaconState) NumPendingConsolidations() uint64 { + b.lock.RLock() + defer b.lock.RUnlock() + return uint64(len(b.pendingConsolidations)) +} + +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_payload_header.go b/beacon-chain/state/state-native/getters_payload_header.go index 74feadca17df..b382ccb88337 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 ( + "errors" "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, errors.New("unsupported version for latest execution payload header") } - - 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 c8f9c4aa7b16..fd572dd2ca12 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 } @@ -453,3 +533,13 @@ func ProtobufBeaconStateDeneb(s interface{}) (*ethpb.BeaconStateDeneb, error) { } 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.ProtobufBeaconStateElectra") + } + return pbState, nil +} diff --git a/beacon-chain/state/state-native/getters_validator.go b/beacon-chain/state/state-native/getters_validator.go index 979a8d9163c6..6da79d4c737f 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" @@ -409,3 +410,54 @@ 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() + + if i >= primitives.ValidatorIndex(len(b.validators)) { + return 0, errors.Wrapf(consensus_types.ErrOutOfBounds, "validator index %d does not exist", i) + } + v := b.validators[i] + + return min(b.balances[i], 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_withdrawal.go b/beacon-chain/state/state-native/getters_withdrawal.go index b31eccefefa3..913bc94bc1df 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 epoch >= params.BeaconConfig().ElectraForkEpoch { + for _, w := range b.pendingPartialWithdrawals { + if primitives.Epoch(w.WithdrawableEpoch) > epoch || len(withdrawals) >= int(params.BeaconConfig().MaxPendingPartialsPerWithdrawalSweep) { + break + } + + v, err := b.validatorAtIndex(w.Index) + if err != nil { + return nil, 0, fmt.Errorf("failed to determine withdrawals: %w", 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,32 @@ 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 +// 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) } - 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 + if n >= uint64(len(b.pendingPartialWithdrawals)) { + return errors.New("cannot dequeue more withdrawals than are in the queue") } - hasMaxBalance := val.EffectiveBalance == params.BeaconConfig().MaxEffectiveBalance - hasExcessBalance := balance > params.BeaconConfig().MaxEffectiveBalance - return hasETH1WithdrawalCredential(val) && hasExcessBalance && hasMaxBalance + + b.lock.Lock() + defer b.lock.Unlock() + + b.pendingPartialWithdrawals = b.pendingPartialWithdrawals[n:] + + return nil +} + +func (b *BeaconState) NumPendingPartialWithdrawals() uint64 { + return uint64(len(b.pendingPartialWithdrawals)) } diff --git a/beacon-chain/state/state-native/getters_withdrawal_test.go b/beacon-chain/state/state-native/getters_withdrawal_test.go index 4fa4e05a85c7..ff00272b612e 100644 --- a/beacon-chain/state/state-native/getters_withdrawal_test.go +++ b/beacon-chain/state/state-native/getters_withdrawal_test.go @@ -52,42 +52,6 @@ func TestNextWithdrawalValidatorIndex(t *testing.T) { }) } -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{ @@ -105,7 +69,7 @@ func TestExpectedWithdrawals(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 0, len(expected)) }) @@ -127,7 +91,7 @@ func TestExpectedWithdrawals(t *testing.T) { s.validators[i] = val } s.validators[3].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 1, len(expected)) withdrawal := &enginev1.Withdrawal{ @@ -155,7 +119,7 @@ func TestExpectedWithdrawals(t *testing.T) { s.validators[i] = val } s.balances[3] += params.BeaconConfig().MinDepositAmount - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 1, len(expected)) withdrawal := &enginev1.Withdrawal{ @@ -185,7 +149,7 @@ func TestExpectedWithdrawals(t *testing.T) { } s.balances[3] += params.BeaconConfig().MinDepositAmount s.validators[7].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 2, len(expected)) @@ -220,7 +184,7 @@ func TestExpectedWithdrawals(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) withdrawal := &enginev1.Withdrawal{ @@ -247,7 +211,7 @@ func TestExpectedWithdrawals(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) withdrawal := &enginev1.Withdrawal{ @@ -274,7 +238,7 @@ func TestExpectedWithdrawals(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) withdrawal := &enginev1.Withdrawal{ @@ -304,7 +268,7 @@ func TestExpectedWithdrawals(t *testing.T) { } s.validators[3].WithdrawableEpoch = primitives.Epoch(0) s.balances[3] = 0 - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 0, len(expected)) }) @@ -328,7 +292,7 @@ func TestExpectedWithdrawals(t *testing.T) { s.balances[10] += params.BeaconConfig().MinDepositAmount saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = 10 - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 1, len(expected)) withdrawal := &enginev1.Withdrawal{ @@ -359,7 +323,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 0, len(expected)) }) @@ -381,7 +345,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { s.validators[i] = val } s.validators[3].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 1, len(expected)) withdrawal := &enginev1.Withdrawal{ @@ -409,7 +373,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { s.validators[i] = val } s.balances[3] += params.BeaconConfig().MinDepositAmount - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 1, len(expected)) withdrawal := &enginev1.Withdrawal{ @@ -439,7 +403,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { } s.balances[3] += params.BeaconConfig().MinDepositAmount s.validators[7].WithdrawableEpoch = primitives.Epoch(0) - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 2, len(expected)) @@ -474,7 +438,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) withdrawal := &enginev1.Withdrawal{ @@ -501,7 +465,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) withdrawal := &enginev1.Withdrawal{ @@ -528,7 +492,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { val.WithdrawalCredentials[0] = params.BeaconConfig().ETH1AddressWithdrawalPrefixByte s.validators[i] = val } - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, params.BeaconConfig().MaxWithdrawalsPerPayload, uint64(len(expected))) withdrawal := &enginev1.Withdrawal{ @@ -558,7 +522,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { } s.validators[3].WithdrawableEpoch = primitives.Epoch(0) s.balances[3] = 0 - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 0, len(expected)) }) @@ -582,7 +546,7 @@ func TestExpectedWithdrawals_Deneb(t *testing.T) { s.balances[10] += params.BeaconConfig().MinDepositAmount saved := params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep params.BeaconConfig().MaxValidatorsPerWithdrawalsSweep = 10 - expected, err := s.ExpectedWithdrawals() + expected, _, err := s.ExpectedWithdrawals() require.NoError(t, err) require.Equal(t, 1, len(expected)) withdrawal := &enginev1.Withdrawal{ diff --git a/beacon-chain/state/state-native/hasher.go b/beacon-chain/state/state-native/hasher.go index eaf3235949f0..4028e2b844b6 100644 --- a/beacon-chain/state/state-native/hasher.go +++ b/beacon-chain/state/state-native/hasher.go @@ -38,6 +38,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, errors.New("unknown state version") } // Genesis time root. @@ -247,6 +251,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 +279,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(state.depositBalanceToConsume) + fieldRoots[types.DepositBalanceToConsume.RealPosition()] = dbtcRoot[:] + + // ExitBalanceToConsume root. + ebtcRoot := ssz.Uint64Root(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(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 000000000000..9f092bbc55d2 --- /dev/null +++ b/beacon-chain/state/state-native/setters_balance_deposits.go @@ -0,0 +1,53 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +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() + + // TODO: Shared field references + b.pendingBalanceDeposits = append(b.pendingBalanceDeposits, ðpb.PendingBalanceDeposit{Index: index, Amount: amount}) + + b.markFieldAsDirty(types.PendingBalanceDeposits) + b.rebuildTrie[types.PendingBalanceDeposits] = true + return nil +} + +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() + + // TODO: Shared field references + b.pendingBalanceDeposits = val + + b.markFieldAsDirty(types.PendingBalanceDeposits) + b.rebuildTrie[types.PendingBalanceDeposits] = true + return nil +} + +func (b *BeaconState) SetDepositBalanceToConsume(gwei uint64) error { + if b.version < version.Electra { + return errNotSupported("SetDepositBalanceToConsume", b.version) + } + b.lock.Lock() + defer b.lock.Unlock() + + // TODO: Shared field references + b.depositBalanceToConsume = gwei + + b.markFieldAsDirty(types.DepositBalanceToConsume) + b.rebuildTrie[types.DepositBalanceToConsume] = true + return nil +} 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 000000000000..d554464b2362 --- /dev/null +++ b/beacon-chain/state/state-native/setters_churn.go @@ -0,0 +1,79 @@ +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/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 uint64) (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(activeBal) // Guaranteed to be non-zero. + + // New epoch for exits + var exitBalanceToConsume uint64 + 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 += uint64(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_consolidation.go b/beacon-chain/state/state-native/setters_consolidation.go new file mode 100644 index 000000000000..037cde19e473 --- /dev/null +++ b/beacon-chain/state/state-native/setters_consolidation.go @@ -0,0 +1,66 @@ +package state_native + +import ( + "github.com/prysmaticlabs/prysm/v5/beacon-chain/state/state-native/types" + "github.com/prysmaticlabs/prysm/v5/consensus-types/primitives" + ethpb "github.com/prysmaticlabs/prysm/v5/proto/prysm/v1alpha1" + "github.com/prysmaticlabs/prysm/v5/runtime/version" +) + +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() + + // TODO: Shared field references + b.pendingConsolidations = append(b.pendingConsolidations, val) + + b.markFieldAsDirty(types.PendingConsolidations) + b.rebuildTrie[types.PendingConsolidations] = true + return nil +} + +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() + + // TODO: Shared field references + b.pendingConsolidations = val + + b.markFieldAsDirty(types.PendingConsolidations) + b.rebuildTrie[types.PendingConsolidations] = true + return nil +} + +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 +} + +func (b *BeaconState) SetConsolidationBalanceToConsume(balance uint64) 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_payload_header.go b/beacon-chain/state/state-native/setters_payload_header.go index 3b36f7f5b658..336e48895c31 100644 --- a/beacon-chain/state/state-native/setters_payload_header.go +++ b/beacon-chain/state/state-native/setters_payload_header.go @@ -44,6 +44,14 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.latestExecutionPayloadHeaderDeneb = latest b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb) return nil + case *enginev1.ExecutionPayloadElectra: + latest, err := consensusblocks.PayloadToHeaderElectra(val) + 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: b.latestExecutionPayloadHeader = header b.markFieldAsDirty(types.LatestExecutionPayloadHeader) @@ -56,6 +64,10 @@ func (b *BeaconState) SetLatestExecutionPayloadHeader(val interfaces.ExecutionDa b.latestExecutionPayloadHeaderDeneb = header b.markFieldAsDirty(types.LatestExecutionPayloadHeaderDeneb) return nil + case *enginev1.ExecutionPayloadHeaderElectra: + 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_withdrawal.go b/beacon-chain/state/state-native/setters_withdrawal.go index 5548fe0140e4..88a081dda97b 100644 --- a/beacon-chain/state/state-native/setters_withdrawal.go +++ b/beacon-chain/state/state-native/setters_withdrawal.go @@ -3,6 +3,7 @@ package state_native 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" ) @@ -34,3 +35,17 @@ func (b *BeaconState) SetNextWithdrawalValidatorIndex(i primitives.ValidatorInde b.markFieldAsDirty(types.NextWithdrawalValidatorIndex) return nil } + +func (b *BeaconState) AppendPendingPartialWithdrawal(ppw *eth.PendingPartialWithdrawal) error { + if b.version < version.Electra { + return errNotSupported("AppendPendingPartialWithdrawal", b.version) + } + + b.lock.Lock() + defer b.lock.Unlock() + + // TODO: Shared field references + b.pendingPartialWithdrawals = append(b.pendingPartialWithdrawals, ppw) + b.markFieldAsDirty(types.PendingPartialWithdrawals) + return nil +} diff --git a/beacon-chain/state/state-native/spec_parameters.go b/beacon-chain/state/state-native/spec_parameters.go index 697afd6e73e2..1612a71efbdf 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_trie.go b/beacon-chain/state/state-native/state_trie.go index 03ec6d77079b..b5f58f2e9fd7 100644 --- a/beacon-chain/state/state-native/state_trie.go +++ b/beacon-chain/state/state-native/state_trie.go @@ -93,17 +93,32 @@ var denebFields = append( types.HistoricalSummaries, ) +var electraFields = append( + denebFields, + 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 = 8 // TODO: Is this correct? ) // InitializeFromProtoPhase0 the beacon state from a protobuf representation. @@ -131,6 +146,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 +702,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, experimentalStateDenebSharedFieldRefCount) + } 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.LatestExecutionPayloadHeaderDeneb] = stateutil.NewRef(1) // New in Deneb. + 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 +844,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 +887,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 +905,7 @@ func (b *BeaconState) Copy() state.BeaconState { latestExecutionPayloadHeader: b.latestExecutionPayloadHeaderVal(), latestExecutionPayloadHeaderCapella: b.latestExecutionPayloadHeaderCapellaVal(), latestExecutionPayloadHeaderDeneb: b.latestExecutionPayloadHeaderDenebVal(), + latestExecutionPayloadHeaderElectra: b.latestExecutionPayloadHeaderElectraVal(), id: types.Enumerator.Inc(), @@ -785,6 +941,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 { @@ -798,6 +956,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) } } @@ -890,6 +1050,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 errors.New("unknown state version when computing dirty fields in merklization") } return nil @@ -1108,12 +1272,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(b.depositBalanceToConsume), nil + case types.ExitBalanceToConsume: + return ssz.Uint64Root(b.exitBalanceToConsume), nil + case types.EarliestExitEpoch: + return ssz.Uint64Root(uint64(b.earliestExitEpoch)), nil + case types.ConsolidationBalanceToConsume: + return ssz.Uint64Root(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/types/types.go b/beacon-chain/state/state-native/types/types.go index d2dd9cdbf190..6f22fff2ca43 100644 --- a/beacon-chain/state/state-native/types/types.go +++ b/beacon-chain/state/state-native/types/types.go @@ -90,6 +90,24 @@ func (f FieldIndex) String() string { return "NextWithdrawalValidatorIndex" case 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 "" } @@ -147,7 +165,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 +173,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 +243,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 cc83efb14a4c..9e97d2b49db5 100644 --- a/beacon-chain/state/stateutil/BUILD.bazel +++ b/beacon-chain/state/stateutil/BUILD.bazel @@ -4,6 +4,7 @@ go_library( name = "go_default_library", srcs = [ "block_header_root.go", + "eip_7251_root.go", "eth1_root.go", "field_root_attestation.go", "field_root_eth1.go", diff --git a/beacon-chain/state/stateutil/eip_7251_root.go b/beacon-chain/state/stateutil/eip_7251_root.go new file mode 100644 index 000000000000..bbbc2bcb01e1 --- /dev/null +++ b/beacon-chain/state/stateutil/eip_7251_root.go @@ -0,0 +1,102 @@ +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 PendingBalanceDepositsRoot(slice []*ethpb.PendingBalanceDeposit) ([32]byte, error) { + max := uint64(fieldparams.PendingBalanceDepositsLimit) + if uint64(len(slice)) > max { + return [32]byte{}, fmt.Errorf("pending balance deposits 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 pending balance deposits") + } + roots[i] = r + } + + sliceRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), fieldparams.PendingBalanceDepositsLimit) + if err != nil { + return [32]byte{}, errors.Wrap(err, "could not compute pending balance deposits 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 pending balance deposits 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 +} + +func PendingPartialWithdrawalsRoot(slice []*ethpb.PendingPartialWithdrawal) ([32]byte, error) { + max := uint64(fieldparams.PendingPartialWithdrawalsLimit) + if uint64(len(slice)) > max { + return [32]byte{}, fmt.Errorf("pending partial withdrawals 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 pending partial withdrawals") + } + roots[i] = r + } + + sliceRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), fieldparams.PendingPartialWithdrawalsLimit) + if err != nil { + return [32]byte{}, errors.Wrap(err, "could not compute pending partial withdrawals 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 pending partial withdrawals 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 +} + +func PendingConsolidationsRoot(slice []*ethpb.PendingConsolidation) ([32]byte, error) { + max := uint64(fieldparams.PendingConsolidationsLimit) + if uint64(len(slice)) > max { + return [32]byte{}, fmt.Errorf("pending consolidations 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 pending consolidations") + } + roots[i] = r + } + + sliceRoot, err := ssz.BitwiseMerkleize(roots, uint64(len(roots)), fieldparams.PendingConsolidationsLimit) + if err != nil { + return [32]byte{}, errors.Wrap(err, "could not compute pending consolidations 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 pending consolidations 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 be4aabd0be2b..a0f630b26a8c 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/encoding/ssz/detect/configfork.go b/encoding/ssz/detect/configfork.go index db2285844296..4f870f6ee218 100644 --- a/encoding/ssz/detect/configfork.go +++ b/encoding/ssz/detect/configfork.go @@ -152,6 +152,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 +210,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 +247,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 36d7b929b3d3..369bd4778aef 100644 --- a/encoding/ssz/detect/configfork_test.go +++ b/encoding/ssz/detect/configfork_test.go @@ -126,6 +126,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) } diff --git a/testing/spectest/shared/electra/ssz_static/BUILD.bazel b/testing/spectest/shared/electra/ssz_static/BUILD.bazel index 0c972544a411..b9b04313420a 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 9f9c6593bce9..ae1ac0ddf274 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/state.go b/testing/util/state.go index 5d96ba9a32b7..2c1f08c756b8 100644 --- a/testing/util/state.go +++ b/testing/util/state.go @@ -386,6 +386,74 @@ 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), + }, + } + + 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 {