From 9b3d1865617ca5a2ae190e8ac445bae519762c42 Mon Sep 17 00:00:00 2001 From: terence Date: Fri, 13 Mar 2026 14:12:42 -0700 Subject: [PATCH 1/5] Check pending deposits before applying builder deposits --- beacon-chain/core/gloas/deposit_request.go | 16 ++++- .../core/gloas/deposit_request_test.go | 27 ++++++++ beacon-chain/state/interfaces.go | 1 + .../state/state-native/getters_deposits.go | 55 ++++++++++++++++ .../state-native/getters_deposits_test.go | 65 +++++++++++++++++++ ...in_check-pending-deposit-before-builder.md | 3 + 6 files changed, 164 insertions(+), 3 deletions(-) create mode 100644 changelog/terencechain_check-pending-deposit-before-builder.md diff --git a/beacon-chain/core/gloas/deposit_request.go b/beacon-chain/core/gloas/deposit_request.go index 26420a4f5975..d323e694efc8 100644 --- a/beacon-chain/core/gloas/deposit_request.go +++ b/beacon-chain/core/gloas/deposit_request.go @@ -39,9 +39,10 @@ func processDepositRequests(ctx context.Context, beaconState state.BeaconState, // # Regardless of the withdrawal credentials prefix, if a builder/validator // # already exists with this pubkey, apply the deposit to their balance // is_builder = deposit_request.pubkey in builder_pubkeys -// is_validator = deposit_request.pubkey in validator_pubkeys -// is_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) -// if is_builder or (is_builder_prefix and not is_validator): +// has_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) +// is_existing_validator = deposit_request.pubkey in validator_pubkeys +// is_validator = is_existing_validator or is_pending_validator(state, deposit_request.pubkey) +// if is_builder or (has_builder_prefix and not is_validator): // # Apply builder deposits immediately // apply_deposit_for_builder( // state, @@ -132,6 +133,15 @@ func applyBuilderDepositRequest(beaconState state.BeaconState, request *enginev1 _, isValidator := beaconState.ValidatorIndexByPubkey(pubkey) idx, isBuilder := beaconState.BuilderIndexByPubkey(pubkey) isBuilderPrefix := helpers.IsBuilderWithdrawalCredential(request.WithdrawalCredentials) + if !isBuilder { + isPending, err := beaconState.IsPendingValidator(request.Pubkey) + if err != nil { + return false, err + } + if isPending { + return false, nil + } + } if !isBuilder && (!isBuilderPrefix || isValidator) { return false, nil } diff --git a/beacon-chain/core/gloas/deposit_request_test.go b/beacon-chain/core/gloas/deposit_request_test.go index 02fcf3de3c05..91b0e0f98986 100644 --- a/beacon-chain/core/gloas/deposit_request_test.go +++ b/beacon-chain/core/gloas/deposit_request_test.go @@ -91,6 +91,33 @@ func TestProcessDepositRequest_ExistingBuilderIncreasesBalance(t *testing.T) { require.Equal(t, 0, len(pending)) } +func TestProcessDepositRequest_BuilderDepositWithExistingPendingDepositStaysPending(t *testing.T) { + sk, err := bls.RandKey() + require.NoError(t, err) + + validatorCred := validatorWithdrawalCredentials() + builderCred := builderWithdrawalCredentials() + existingPending := stateTesting.GeneratePendingDeposit(t, sk, 1234, validatorCred, 0) + req := depositRequestFromPending(stateTesting.GeneratePendingDeposit(t, sk, 200, builderCred, 1), 9) + + st := newGloasState(t, nil, nil) + require.NoError(t, st.SetPendingDeposits([]*ethpb.PendingDeposit{existingPending})) + + err = processDepositRequest(st, req) + require.NoError(t, err) + + _, ok := st.BuilderIndexByPubkey(toBytes48(req.Pubkey)) + require.Equal(t, false, ok) + + pending, err := st.PendingDeposits() + require.NoError(t, err) + require.Equal(t, 2, len(pending)) + require.DeepEqual(t, existingPending.PublicKey, pending[0].PublicKey) + require.DeepEqual(t, req.Pubkey, pending[1].PublicKey) + require.DeepEqual(t, req.WithdrawalCredentials, pending[1].WithdrawalCredentials) + require.Equal(t, req.Amount, pending[1].Amount) +} + func TestApplyDepositForBuilder_InvalidSignatureIgnoresDeposit(t *testing.T) { sk, err := bls.RandKey() require.NoError(t, err) diff --git a/beacon-chain/state/interfaces.go b/beacon-chain/state/interfaces.go index 9a0321592076..8e458faaa6a9 100644 --- a/beacon-chain/state/interfaces.go +++ b/beacon-chain/state/interfaces.go @@ -239,6 +239,7 @@ type ReadOnlyDeposits interface { DepositBalanceToConsume() (primitives.Gwei, error) DepositRequestsStartIndex() (uint64, error) PendingDeposits() ([]*ethpb.PendingDeposit, error) + IsPendingValidator(pubkey []byte) (bool, error) } type ReadOnlyConsolidations interface { diff --git a/beacon-chain/state/state-native/getters_deposits.go b/beacon-chain/state/state-native/getters_deposits.go index cca78de8e7d4..d32a2a968e1e 100644 --- a/beacon-chain/state/state-native/getters_deposits.go +++ b/beacon-chain/state/state-native/getters_deposits.go @@ -1,6 +1,9 @@ package state_native import ( + "bytes" + + "github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" ethpb "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v7/runtime/version" @@ -30,6 +33,58 @@ func (b *BeaconState) PendingDeposits() ([]*ethpb.PendingDeposit, error) { return b.pendingDepositsVal(), nil } +// IsPendingValidator checks whether a pending deposit with a valid signature exists for the +// given pubkey. This method requires access to the RLock on the state and only applies in +// electra or later. +// +// +// def is_pending_validator(state: BeaconState, pubkey: BLSPubkey) -> bool: +// +// """ +// Check if a pending deposit with a valid signature is in the queue for the given pubkey. +// """ +// for pending_deposit in state.pending_deposits: +// if pending_deposit.pubkey != pubkey: +// continue +// if is_valid_deposit_signature( +// pending_deposit.pubkey, +// pending_deposit.withdrawal_credentials, +// pending_deposit.amount, +// pending_deposit.signature, +// ): +// return True +// return False +// +// +func (b *BeaconState) IsPendingValidator(pubkey []byte) (bool, error) { + if b.version < version.Electra { + return false, errNotSupported("IsPendingValidator", b.version) + } + b.lock.RLock() + defer b.lock.RUnlock() + for _, deposit := range b.pendingDeposits { + if deposit == nil { + continue + } + if !bytes.Equal(deposit.PublicKey, pubkey) { + continue + } + valid, err := helpers.IsValidDepositSignature(ðpb.Deposit_Data{ + PublicKey: deposit.PublicKey, + WithdrawalCredentials: deposit.WithdrawalCredentials, + Amount: deposit.Amount, + Signature: deposit.Signature, + }) + if err != nil { + continue + } + if valid { + return true, nil + } + } + return false, nil +} + func (b *BeaconState) pendingDepositsVal() []*ethpb.PendingDeposit { if b.pendingDeposits == nil { return nil diff --git a/beacon-chain/state/state-native/getters_deposits_test.go b/beacon-chain/state/state-native/getters_deposits_test.go index b7047aae98fa..e36818e206bb 100644 --- a/beacon-chain/state/state-native/getters_deposits_test.go +++ b/beacon-chain/state/state-native/getters_deposits_test.go @@ -4,7 +4,9 @@ import ( "testing" state_native "github.com/OffchainLabs/prysm/v7/beacon-chain/state/state-native" + stateTesting "github.com/OffchainLabs/prysm/v7/beacon-chain/state/testing" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" + "github.com/OffchainLabs/prysm/v7/crypto/bls" eth "github.com/OffchainLabs/prysm/v7/proto/prysm/v1alpha1" "github.com/OffchainLabs/prysm/v7/testing/require" ) @@ -66,3 +68,66 @@ func TestPendingDeposits(t *testing.T) { _, err = s.DepositBalanceToConsume() require.ErrorContains(t, "not supported", err) } + +func TestIsPendingValidator(t *testing.T) { + sk, err := bls.RandKey() + require.NoError(t, err) + validDeposit := stateTesting.GeneratePendingDeposit(t, sk, 1000, [32]byte{0x01}, 0) + + t.Run("valid signature returns true", func(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingDeposits: []*eth.PendingDeposit{validDeposit}, + }) + require.NoError(t, err) + + ok, err := s.IsPendingValidator(validDeposit.PublicKey) + require.NoError(t, err) + require.Equal(t, true, ok) + }) + + t.Run("invalid signature returns false", func(t *testing.T) { + invalidDeposit := ð.PendingDeposit{ + PublicKey: validDeposit.PublicKey, + WithdrawalCredentials: validDeposit.WithdrawalCredentials, + Amount: validDeposit.Amount, + Signature: make([]byte, 96), // invalid empty signature + } + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingDeposits: []*eth.PendingDeposit{invalidDeposit}, + }) + require.NoError(t, err) + + ok, err := s.IsPendingValidator(validDeposit.PublicKey) + require.NoError(t, err) + require.Equal(t, false, ok) + }) + + t.Run("unknown pubkey returns false", func(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingDeposits: []*eth.PendingDeposit{validDeposit}, + }) + require.NoError(t, err) + + ok, err := s.IsPendingValidator([]byte{9, 9, 9}) + require.NoError(t, err) + require.Equal(t, false, ok) + }) + + t.Run("nil deposit skipped", func(t *testing.T) { + s, err := state_native.InitializeFromProtoElectra(ð.BeaconStateElectra{ + PendingDeposits: []*eth.PendingDeposit{nil, validDeposit}, + }) + require.NoError(t, err) + + ok, err := s.IsPendingValidator(validDeposit.PublicKey) + require.NoError(t, err) + require.Equal(t, true, ok) + }) + + t.Run("pre-electra not supported", func(t *testing.T) { + s, err := state_native.InitializeFromProtoDeneb(ð.BeaconStateDeneb{}) + require.NoError(t, err) + _, err = s.IsPendingValidator([]byte{1, 2, 3}) + require.ErrorContains(t, "not supported", err) + }) +} diff --git a/changelog/terencechain_check-pending-deposit-before-builder.md b/changelog/terencechain_check-pending-deposit-before-builder.md new file mode 100644 index 000000000000..c114f8813c78 --- /dev/null +++ b/changelog/terencechain_check-pending-deposit-before-builder.md @@ -0,0 +1,3 @@ +### Fixed + +- Check for pending validator deposits with valid signatures before applying builder deposits, preventing pubkey hijacking into the builder registry. From f93de935f1e63af64ffbe6e1a34c65d8d3324ac9 Mon Sep 17 00:00:00 2001 From: terence Date: Sun, 15 Mar 2026 14:19:21 -0700 Subject: [PATCH 2/5] Bump consensus spec version to alpha 3 --- .ethspecify.yml | 55 +- WORKSPACE | 10 +- beacon-chain/core/gloas/deposit_request.go | 12 +- .../state/state-native/setters_gloas.go | 2 +- changelog/t_bump-consensus-spec-alpha3.md | 3 + config/params/loader_test.go | 4 + specrefs/configs.yml | 103 ++- specrefs/constants.yml | 19 +- specrefs/containers.yml | 103 +++ specrefs/dataclasses.yml | 75 +- specrefs/functions.yml | 705 ++++++++++++++++-- specrefs/presets.yml | 7 + 12 files changed, 977 insertions(+), 121 deletions(-) create mode 100644 changelog/t_bump-consensus-spec-alpha3.md diff --git a/.ethspecify.yml b/.ethspecify.yml index a33e7b576f6f..44356a1e2e63 100644 --- a/.ethspecify.yml +++ b/.ethspecify.yml @@ -1,4 +1,4 @@ -version: v1.7.0-alpha.2 +version: v1.7.0-alpha.3 style: full specrefs: @@ -49,6 +49,8 @@ exceptions: - RANDOM_CHALLENGE_KZG_CELL_BATCH_DOMAIN#fulu # fulu - UINT256_MAX#fulu + # heze + - DOMAIN_INCLUSION_LIST_COMMITTEE#heze # gloas - BUILDER_PAYMENT_THRESHOLD_DENOMINATOR#gloas - BUILDER_PAYMENT_THRESHOLD_NUMERATOR#gloas @@ -74,6 +76,14 @@ exceptions: - GLOAS_FORK_VERSION#gloas - MIN_BUILDER_WITHDRAWABILITY_DELAY#gloas - SYNC_MESSAGE_DUE_BPS_GLOAS#gloas + # heze + - HEZE_FORK_EPOCH#heze + - HEZE_FORK_VERSION#heze + - INCLUSION_LIST_SUBMISSION_DUE_BPS#heze + - MAX_BYTES_PER_INCLUSION_LIST#heze + - MAX_REQUEST_INCLUSION_LIST#heze + - PROPOSER_INCLUSION_LIST_CUTOFF_BPS#heze + - VIEW_FREEZE_CUTOFF_BPS#heze ssz_objects: # phase0 @@ -103,6 +113,12 @@ exceptions: - SignedExecutionPayloadBid#gloas - SignedExecutionPayloadEnvelope#gloas - SignedProposerPreferences#gloas + # heze + - BeaconState#heze + - ExecutionPayloadBid#heze + - InclusionList#heze + - SignedExecutionPayloadBid#heze + - SignedInclusionList#heze dataclasses: # phase0 @@ -123,6 +139,11 @@ exceptions: - ExpectedWithdrawals#gloas - LatestMessage#gloas - Store#gloas + # heze + - GetInclusionListResponse#heze + - InclusionListStore#heze + - PayloadAttributes#heze + - Store#heze functions: # Functions implemented by KZG library for EIP-4844 @@ -411,6 +432,36 @@ exceptions: - update_next_withdrawal_builder_index#gloas - update_payload_expected_withdrawals#gloas - update_proposer_boost_root#gloas + # new in alpha.3 (not yet implemented) + - compute_attestation_subnet_prefix_bits#phase0 + - compute_max_request_blob_sidecars#deneb + - compute_max_request_blob_sidecars#electra + - compute_max_request_data_column_sidecars#fulu + - compute_min_epochs_for_block_requests#phase0 + - is_payload_data_available#gloas + - is_pending_validator#gloas + # heze + - compute_fork_version#heze + - get_forkchoice_store#heze + - get_inclusion_list_bits#heze + - get_inclusion_list_committee#heze + - get_inclusion_list_committee_assignment#heze + - get_inclusion_list_signature#heze + - get_inclusion_list_store#heze + - get_inclusion_list_submission_due_ms#heze + - get_inclusion_list_transactions#heze + - get_proposer_inclusion_list_cutoff_ms#heze + - get_view_freeze_cutoff_ms#heze + - is_inclusion_list_bits_inclusive#heze + - is_payload_inclusion_list_satisfied#heze + - is_valid_inclusion_list_signature#heze + - on_execution_payload#heze + - on_inclusion_list#heze + - prepare_execution_payload#heze + - process_inclusion_list#heze + - record_payload_inclusion_list_satisfaction#heze + - should_extend_payload#heze + - upgrade_to_heze#heze presets: # gloas @@ -419,3 +470,5 @@ exceptions: - MAX_BUILDERS_PER_WITHDRAWALS_SWEEP#gloas - MAX_PAYLOAD_ATTESTATIONS#gloas - PTC_SIZE#gloas + # heze + - INCLUSION_LIST_COMMITTEE_SIZE#heze diff --git a/WORKSPACE b/WORKSPACE index e79919e0003c..8e45112bed9e 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -273,16 +273,16 @@ filegroup( url = "https://github.com/ethereum/EIPs/archive/5480440fe51742ed23342b68cf106cefd427e39d.tar.gz", ) -consensus_spec_version = "v1.7.0-alpha.2" +consensus_spec_version = "v1.7.0-alpha.3" load("@prysm//tools:download_spectests.bzl", "consensus_spec_tests") consensus_spec_tests( name = "consensus_spec_tests", flavors = { - "general": "sha256-iGQsGZ1cHah+2CSod9jC3kN8Ku4n6KO0hIwfINrn/po=", - "minimal": "sha256-TgcYt8N8sXSttdHTGvOa+exUZ1zn1UzlAMz0V7i37xc=", - "mainnet": "sha256-LnXyiLoJtrvEvbqLDSAAqpLMdN/lXv92SAgYG8fNjCs=", + "general": "sha256-mqREVsPGiGCR7Nn/YBraat52bbpZykPEgy89+WSDAzU=", + "minimal": "sha256-3xxzrQckowGY1AqgnscR/6Q9I/zQELs1rxOI2+Kp8S4=", + "mainnet": "sha256-rWCoQce5aqoMZGgQ5wQCTRJ5vojzBxZs7J+hw90Qyb8=", }, version = consensus_spec_version, ) @@ -298,7 +298,7 @@ filegroup( visibility = ["//visibility:public"], ) """, - integrity = "sha256-Y/67Dg393PksZj5rTFNLntiJ6hNdB7Rxbu5gZE2gebY=", + integrity = "sha256-uSX7ssSb3OkK3DPslI/9j8DNQTPB8oj3W2gwQUZV8zc=", strip_prefix = "consensus-specs-" + consensus_spec_version[1:], url = "https://github.com/ethereum/consensus-specs/archive/refs/tags/%s.tar.gz" % consensus_spec_version, ) diff --git a/beacon-chain/core/gloas/deposit_request.go b/beacon-chain/core/gloas/deposit_request.go index d323e694efc8..97907310960d 100644 --- a/beacon-chain/core/gloas/deposit_request.go +++ b/beacon-chain/core/gloas/deposit_request.go @@ -29,7 +29,7 @@ func processDepositRequests(ctx context.Context, beaconState state.BeaconState, // processDepositRequest processes the specific deposit request // -// +// // def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: // # [New in Gloas:EIP7732] // builder_pubkeys = [b.pubkey for b in state.builders] @@ -39,10 +39,12 @@ func processDepositRequests(ctx context.Context, beaconState state.BeaconState, // # Regardless of the withdrawal credentials prefix, if a builder/validator // # already exists with this pubkey, apply the deposit to their balance // is_builder = deposit_request.pubkey in builder_pubkeys -// has_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) -// is_existing_validator = deposit_request.pubkey in validator_pubkeys -// is_validator = is_existing_validator or is_pending_validator(state, deposit_request.pubkey) -// if is_builder or (has_builder_prefix and not is_validator): +// is_validator = deposit_request.pubkey in validator_pubkeys +// if is_builder or ( +// is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) +// and not is_validator +// and not is_pending_validator(state, deposit_request.pubkey) +// ): // # Apply builder deposits immediately // apply_deposit_for_builder( // state, diff --git a/beacon-chain/state/state-native/setters_gloas.go b/beacon-chain/state/state-native/setters_gloas.go index f841b8ea43dc..6b2655a85c0c 100644 --- a/beacon-chain/state/state-native/setters_gloas.go +++ b/beacon-chain/state/state-native/setters_gloas.go @@ -613,7 +613,7 @@ func decreaseBalanceWithVal(currBalance, delta primitives.Gwei) primitives.Gwei // OnboardBuildersFromPendingDeposits applies any pending builder deposits at the fork. // It mutates the state and prunes pending deposits accordingly. // -// +// // def onboard_builders_from_pending_deposits(state: BeaconState) -> None: // """ // Applies any pending deposit for builders, effectively diff --git a/changelog/t_bump-consensus-spec-alpha3.md b/changelog/t_bump-consensus-spec-alpha3.md new file mode 100644 index 000000000000..05887484cade --- /dev/null +++ b/changelog/t_bump-consensus-spec-alpha3.md @@ -0,0 +1,3 @@ +### Changed + +- Bump consensus spec version from v1.7.0-alpha.2 to v1.7.0-alpha.3 diff --git a/config/params/loader_test.go b/config/params/loader_test.go index 5ce2612b6049..e24ba81b5c6f 100644 --- a/config/params/loader_test.go +++ b/config/params/loader_test.go @@ -39,9 +39,13 @@ var placeholderFields = []string{ "EIP7805_FORK_VERSION", "EIP7928_FORK_EPOCH", "EIP7928_FORK_VERSION", + "EIP8025_FORK_EPOCH", + "EIP8025_FORK_VERSION", "EPOCHS_PER_SHUFFLING_PHASE", "FIELD_ELEMENTS_PER_CELL", // Configured as a constant in config/fieldparams/mainnet.go "FIELD_ELEMENTS_PER_EXT_BLOB", // Configured in proto/ssz_proto_library.bzl + "HEZE_FORK_EPOCH", + "HEZE_FORK_VERSION", "INCLUSION_LIST_SUBMISSION_DEADLINE", "INCLUSION_LIST_SUBMISSION_DUE_BPS", "KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH", // Configured in proto/ssz_proto_library.bzl diff --git a/specrefs/configs.yml b/specrefs/configs.yml index 44477f4bef57..6357beed511b 100644 --- a/specrefs/configs.yml +++ b/specrefs/configs.yml @@ -82,16 +82,6 @@ ATTESTATION_SUBNET_EXTRA_BITS = 0 -- name: ATTESTATION_SUBNET_PREFIX_BITS#phase0 - sources: - - file: config/params/config.go - search: AttestationSubnetPrefixBits\s+uint64 - regex: true - spec: | - - ATTESTATION_SUBNET_PREFIX_BITS: int = 6 - - - name: BALANCE_PER_ADDITIONAL_CUSTODY_GROUP#fulu sources: - file: config/params/config.go @@ -352,6 +342,20 @@ GLOAS_FORK_VERSION: Version = '0x07000000' +- name: HEZE_FORK_EPOCH#heze + sources: [] + spec: | + + HEZE_FORK_EPOCH: Epoch = 18446744073709551615 + + +- name: HEZE_FORK_VERSION#heze + sources: [] + spec: | + + HEZE_FORK_VERSION: Version = '0x08000000' + + - name: INACTIVITY_SCORE_BIAS#altair sources: - file: config/params/config.go @@ -372,6 +376,13 @@ INACTIVITY_SCORE_RECOVERY_RATE: uint64 = 16 +- name: INCLUSION_LIST_SUBMISSION_DUE_BPS#heze + sources: [] + spec: | + + INCLUSION_LIST_SUBMISSION_DUE_BPS: uint64 = 6667 + + - name: MAXIMUM_GOSSIP_CLOCK_DISPARITY#phase0 sources: - file: config/params/config.go @@ -402,6 +413,13 @@ MAX_BLOBS_PER_BLOCK_ELECTRA: uint64 = 9 +- name: MAX_BYTES_PER_INCLUSION_LIST#heze + sources: [] + spec: | + + MAX_BYTES_PER_INCLUSION_LIST = 8192 + + - name: MAX_PAYLOAD_SIZE#phase0 sources: - file: config/params/config.go @@ -432,26 +450,6 @@ MAX_PER_EPOCH_ACTIVATION_EXIT_CHURN_LIMIT: Gwei = 256000000000 -- name: MAX_REQUEST_BLOB_SIDECARS#deneb - sources: - - file: config/params/config.go - search: MaxRequestBlobSidecars\s+uint64 - regex: true - spec: | - - MAX_REQUEST_BLOB_SIDECARS = 768 - - -- name: MAX_REQUEST_BLOB_SIDECARS_ELECTRA#electra - sources: - - file: config/params/config.go - search: MaxRequestBlobSidecarsElectra\s+uint64 - regex: true - spec: | - - MAX_REQUEST_BLOB_SIDECARS_ELECTRA = 1152 - - - name: MAX_REQUEST_BLOCKS#phase0 sources: - file: config/params/config.go @@ -472,14 +470,11 @@ MAX_REQUEST_BLOCKS_DENEB = 128 -- name: MAX_REQUEST_DATA_COLUMN_SIDECARS#fulu - sources: - - file: config/params/config.go - search: MaxRequestDataColumnSidecars\s+uint64 - regex: true +- name: MAX_REQUEST_INCLUSION_LIST#heze + sources: [] spec: | - - MAX_REQUEST_DATA_COLUMN_SIDECARS = 16384 + + MAX_REQUEST_INCLUSION_LIST = 16 - name: MAX_REQUEST_PAYLOADS#gloas @@ -529,16 +524,6 @@ MIN_EPOCHS_FOR_BLOB_SIDECARS_REQUESTS = 4096 -- name: MIN_EPOCHS_FOR_BLOCK_REQUESTS#phase0 - sources: - - file: config/params/config.go - search: MinEpochsForBlockRequests\s+uint64 - regex: true - spec: | - - MIN_EPOCHS_FOR_BLOCK_REQUESTS = 33024 - - - name: MIN_EPOCHS_FOR_DATA_COLUMN_SIDECARS_REQUESTS#fulu sources: - file: config/params/config.go @@ -619,6 +604,13 @@ PAYLOAD_ATTESTATION_DUE_BPS: uint64 = 7500 +- name: PROPOSER_INCLUSION_LIST_CUTOFF_BPS#heze + sources: [] + spec: | + + PROPOSER_INCLUSION_LIST_CUTOFF_BPS: uint64 = 9167 + + - name: PROPOSER_REORG_CUTOFF_BPS#phase0 sources: - file: config/params/config.go @@ -689,16 +681,6 @@ SECONDS_PER_ETH1_BLOCK: uint64 = 14 -- name: SECONDS_PER_SLOT#phase0 - sources: - - file: config/params/config.go - search: SecondsPerSlot\s+uint64 - regex: true - spec: | - - SECONDS_PER_SLOT: uint64 = 12 - - - name: SHARD_COMMITTEE_PERIOD#phase0 sources: - file: config/params/config.go @@ -785,3 +767,10 @@ VALIDATOR_CUSTODY_REQUIREMENT = 8 + +- name: VIEW_FREEZE_CUTOFF_BPS#heze + sources: [] + spec: | + + VIEW_FREEZE_CUTOFF_BPS: uint64 = 7500 + diff --git a/specrefs/constants.yml b/specrefs/constants.yml index 464ddd4255be..e41dcc1a6098 100644 --- a/specrefs/constants.yml +++ b/specrefs/constants.yml @@ -212,6 +212,13 @@ DOMAIN_DEPOSIT: DomainType = '0x03000000' +- name: DOMAIN_INCLUSION_LIST_COMMITTEE#heze + sources: [] + spec: | + + DOMAIN_INCLUSION_LIST_COMMITTEE: DomainType = '0x0E000000' + + - name: DOMAIN_PROPOSER_PREFERENCES#gloas sources: [] spec: | @@ -442,22 +449,22 @@ - name: PAYLOAD_STATUS_EMPTY#gloas sources: [] spec: | - - PAYLOAD_STATUS_EMPTY: PayloadStatus = 1 + + PAYLOAD_STATUS_EMPTY: PayloadStatus = 0 - name: PAYLOAD_STATUS_FULL#gloas sources: [] spec: | - - PAYLOAD_STATUS_FULL: PayloadStatus = 2 + + PAYLOAD_STATUS_FULL: PayloadStatus = 1 - name: PAYLOAD_STATUS_PENDING#gloas sources: [] spec: | - - PAYLOAD_STATUS_PENDING: PayloadStatus = 0 + + PAYLOAD_STATUS_PENDING: PayloadStatus = 2 - name: PRIMITIVE_ROOT_OF_UNITY#deneb diff --git a/specrefs/containers.yml b/specrefs/containers.yml index 29d8da7a3244..48eb3ac8cdc6 100644 --- a/specrefs/containers.yml +++ b/specrefs/containers.yml @@ -627,6 +627,59 @@ payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] +- name: BeaconState#heze + sources: [] + spec: | + + class BeaconState(Container): + genesis_time: uint64 + genesis_validators_root: Root + slot: Slot + fork: Fork + latest_block_header: BeaconBlockHeader + block_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + state_roots: Vector[Root, SLOTS_PER_HISTORICAL_ROOT] + historical_roots: List[Root, HISTORICAL_ROOTS_LIMIT] + eth1_data: Eth1Data + eth1_data_votes: List[Eth1Data, EPOCHS_PER_ETH1_VOTING_PERIOD * SLOTS_PER_EPOCH] + eth1_deposit_index: uint64 + validators: List[Validator, VALIDATOR_REGISTRY_LIMIT] + balances: List[Gwei, VALIDATOR_REGISTRY_LIMIT] + randao_mixes: Vector[Bytes32, EPOCHS_PER_HISTORICAL_VECTOR] + slashings: Vector[Gwei, EPOCHS_PER_SLASHINGS_VECTOR] + previous_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + current_epoch_participation: List[ParticipationFlags, VALIDATOR_REGISTRY_LIMIT] + justification_bits: Bitvector[JUSTIFICATION_BITS_LENGTH] + previous_justified_checkpoint: Checkpoint + current_justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + inactivity_scores: List[uint64, VALIDATOR_REGISTRY_LIMIT] + current_sync_committee: SyncCommittee + next_sync_committee: SyncCommittee + # [Modified in Heze:EIP7805] + latest_execution_payload_bid: ExecutionPayloadBid + next_withdrawal_index: WithdrawalIndex + next_withdrawal_validator_index: ValidatorIndex + historical_summaries: List[HistoricalSummary, HISTORICAL_ROOTS_LIMIT] + deposit_requests_start_index: uint64 + deposit_balance_to_consume: Gwei + exit_balance_to_consume: Gwei + earliest_exit_epoch: Epoch + consolidation_balance_to_consume: Gwei + earliest_consolidation_epoch: Epoch + pending_deposits: List[PendingDeposit, PENDING_DEPOSITS_LIMIT] + pending_partial_withdrawals: List[PendingPartialWithdrawal, PENDING_PARTIAL_WITHDRAWALS_LIMIT] + pending_consolidations: List[PendingConsolidation, PENDING_CONSOLIDATIONS_LIMIT] + proposer_lookahead: Vector[ValidatorIndex, (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH] + builders: List[Builder, BUILDER_REGISTRY_LIMIT] + next_withdrawal_builder_index: BuilderIndex + execution_payload_availability: Bitvector[SLOTS_PER_HISTORICAL_ROOT] + builder_pending_payments: Vector[BuilderPendingPayment, 2 * SLOTS_PER_EPOCH] + builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] + latest_block_hash: Hash32 + payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + + - name: BlobIdentifier#deneb sources: - file: proto/prysm/v1alpha1/blobs.proto @@ -932,6 +985,26 @@ blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] +- name: ExecutionPayloadBid#heze + sources: [] + spec: | + + class ExecutionPayloadBid(Container): + parent_block_hash: Hash32 + parent_block_root: Root + block_hash: Hash32 + prev_randao: Bytes32 + fee_recipient: ExecutionAddress + gas_limit: uint64 + builder_index: BuilderIndex + slot: Slot + value: Gwei + execution_payment: Gwei + blob_kzg_commitments: List[KZGCommitment, MAX_BLOB_COMMITMENTS_PER_BLOCK] + # [New in Heze:EIP7805] + inclusion_list_bits: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE] + + - name: ExecutionPayloadEnvelope#gloas sources: [] spec: | @@ -1090,6 +1163,17 @@ state_summary_root: Root +- name: InclusionList#heze + sources: [] + spec: | + + class InclusionList(Container): + slot: Slot + validator_index: ValidatorIndex + inclusion_list_committee_root: Root + transactions: List[Transaction, MAX_TRANSACTIONS_PER_PAYLOAD] + + - name: IndexedAttestation#phase0 sources: - file: proto/prysm/v1alpha1/beacon_block.proto @@ -1471,6 +1555,16 @@ signature: BLSSignature +- name: SignedExecutionPayloadBid#heze + sources: [] + spec: | + + class SignedExecutionPayloadBid(Container): + # [Modified in Heze:EIP7805] + message: ExecutionPayloadBid + signature: BLSSignature + + - name: SignedExecutionPayloadEnvelope#gloas sources: [] spec: | @@ -1480,6 +1574,15 @@ signature: BLSSignature +- name: SignedInclusionList#heze + sources: [] + spec: | + + class SignedInclusionList(Container): + message: InclusionList + signature: BLSSignature + + - name: SignedProposerPreferences#gloas sources: [] spec: | diff --git a/specrefs/dataclasses.yml b/specrefs/dataclasses.yml index 7d02bfc4ddcd..f925ea9d4532 100644 --- a/specrefs/dataclasses.yml +++ b/specrefs/dataclasses.yml @@ -68,6 +68,14 @@ processed_sweep_withdrawals_count: uint64 +- name: GetInclusionListResponse#heze + sources: [] + spec: | + + class GetInclusionListResponse(object): + inclusion_list_transactions: Sequence[Transaction] + + - name: GetPayloadResponse#bellatrix sources: - file: consensus-types/blocks/get_payload.go @@ -140,6 +148,19 @@ execution_requests: Sequence[bytes] +- name: InclusionListStore#heze + sources: [] + spec: | + + class InclusionListStore(object): + inclusion_lists: DefaultDict[Tuple[Slot, Root], Set[InclusionList]] = field( + default_factory=lambda: defaultdict(set) + ) + equivocators: DefaultDict[Tuple[Slot, Root], Set[ValidatorIndex]] = field( + default_factory=lambda: defaultdict(set) + ) + + - name: LatestMessage#phase0 sources: [] spec: | @@ -288,6 +309,20 @@ parent_beacon_block_root: Root +- name: PayloadAttributes#heze + sources: [] + spec: | + + class PayloadAttributes(object): + timestamp: uint64 + prev_randao: Bytes32 + suggested_fee_recipient: ExecutionAddress + withdrawals: Sequence[Withdrawal] + parent_beacon_block_root: Root + # [New in Heze:EIP7805] + inclusion_list_transactions: Sequence[Transaction] + + - name: Store#phase0 sources: [] spec: | @@ -312,7 +347,7 @@ - name: Store#gloas sources: [] spec: | - + class Store(object): time: uint64 genesis_time: uint64 @@ -331,7 +366,41 @@ latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) # [New in Gloas:EIP7732] - execution_payload_states: Dict[Root, BeaconState] = field(default_factory=dict) + payload_states: Dict[Root, BeaconState] = field(default_factory=dict) + # [New in Gloas:EIP7732] + payload_timeliness_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict) # [New in Gloas:EIP7732] - ptc_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict) + payload_data_availability_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field( + default_factory=dict + ) + + +- name: Store#heze + sources: [] + spec: | + + class Store(object): + time: uint64 + genesis_time: uint64 + justified_checkpoint: Checkpoint + finalized_checkpoint: Checkpoint + unrealized_justified_checkpoint: Checkpoint + unrealized_finalized_checkpoint: Checkpoint + proposer_boost_root: Root + equivocating_indices: Set[ValidatorIndex] + blocks: Dict[Root, BeaconBlock] = field(default_factory=dict) + block_states: Dict[Root, BeaconState] = field(default_factory=dict) + block_timeliness: Dict[Root, Vector[boolean, NUM_BLOCK_TIMELINESS_DEADLINES]] = field( + default_factory=dict + ) + checkpoint_states: Dict[Checkpoint, BeaconState] = field(default_factory=dict) + latest_messages: Dict[ValidatorIndex, LatestMessage] = field(default_factory=dict) + unrealized_justifications: Dict[Root, Checkpoint] = field(default_factory=dict) + payload_states: Dict[Root, BeaconState] = field(default_factory=dict) + payload_timeliness_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field(default_factory=dict) + payload_data_availability_vote: Dict[Root, Vector[boolean, PTC_SIZE]] = field( + default_factory=dict + ) + # [New in Heze:EIP7805] + payload_inclusion_list_satisfaction: Dict[Root, boolean] = field(default_factory=dict) diff --git a/specrefs/functions.yml b/specrefs/functions.yml index 187b8ed355bb..66d700af7983 100644 --- a/specrefs/functions.yml +++ b/specrefs/functions.yml @@ -440,6 +440,17 @@ return Epoch(epoch + 1 + MAX_SEED_LOOKAHEAD) +- name: compute_attestation_subnet_prefix_bits#phase0 + sources: [] + spec: | + + def compute_attestation_subnet_prefix_bits() -> uint64: + """ + Return the number of NodeId bits to use when mapping to a subscribed subnet. + """ + return uint64(ceillog2(ATTESTATION_SUBNET_COUNT) + ATTESTATION_SUBNET_EXTRA_BITS) + + - name: compute_balance_weighted_acceptance#gloas sources: [] spec: | @@ -871,6 +882,33 @@ return GENESIS_FORK_VERSION +- name: compute_fork_version#heze + sources: [] + spec: | + + def compute_fork_version(epoch: Epoch) -> Version: + """ + Return the fork version at the given ``epoch``. + """ + if epoch >= HEZE_FORK_EPOCH: + return HEZE_FORK_VERSION + if epoch >= GLOAS_FORK_EPOCH: + return GLOAS_FORK_VERSION + if epoch >= FULU_FORK_EPOCH: + return FULU_FORK_VERSION + if epoch >= ELECTRA_FORK_EPOCH: + return ELECTRA_FORK_VERSION + if epoch >= DENEB_FORK_EPOCH: + return DENEB_FORK_VERSION + if epoch >= CAPELLA_FORK_EPOCH: + return CAPELLA_FORK_VERSION + if epoch >= BELLATRIX_FORK_EPOCH: + return BELLATRIX_FORK_VERSION + if epoch >= ALTAIR_FORK_EPOCH: + return ALTAIR_FORK_VERSION + return GENESIS_FORK_VERSION + + - name: compute_matrix#fulu sources: [] spec: | @@ -897,6 +935,40 @@ return matrix +- name: compute_max_request_blob_sidecars#deneb + sources: [] + spec: | + + def compute_max_request_blob_sidecars() -> uint64: + """ + Return the maximum number of blob sidecars in a single request. + """ + return uint64(MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK) + + +- name: compute_max_request_blob_sidecars#electra + sources: [] + spec: | + + def compute_max_request_blob_sidecars() -> uint64: + """ + Return the maximum number of blob sidecars in a single request. + """ + # [Modified in Electra:EIP7691] + return uint64(MAX_REQUEST_BLOCKS_DENEB * MAX_BLOBS_PER_BLOCK_ELECTRA) + + +- name: compute_max_request_data_column_sidecars#fulu + sources: [] + spec: | + + def compute_max_request_data_column_sidecars() -> uint64: + """ + Return the maximum number of data column sidecars in a single request. + """ + return uint64(MAX_REQUEST_BLOCKS_DENEB * NUMBER_OF_COLUMNS) + + - name: compute_merkle_proof#altair sources: [] spec: | @@ -904,6 +976,17 @@ def compute_merkle_proof(object: SSZObject, index: GeneralizedIndex) -> Sequence[Bytes32]: ... +- name: compute_min_epochs_for_block_requests#phase0 + sources: [] + spec: | + + def compute_min_epochs_for_block_requests() -> uint64: + """ + Return the minimum epoch range over which a node must serve blocks. + """ + return uint64(MIN_VALIDATOR_WITHDRAWABILITY_DELAY + CHURN_LIMIT_QUOTIENT // 2) + + - name: compute_new_state_root#phase0 sources: - file: beacon-chain/rpc/prysm/v1alpha1/validator/proposer.go @@ -1243,16 +1326,17 @@ - file: beacon-chain/p2p/subnets.go search: func computeSubscribedSubnet( spec: | - + def compute_subscribed_subnet(node_id: NodeID, epoch: Epoch, index: int) -> SubnetID: - node_id_prefix = node_id >> (NODE_ID_BITS - ATTESTATION_SUBNET_PREFIX_BITS) + prefix_bits = int(compute_attestation_subnet_prefix_bits()) + node_id_prefix = node_id >> (NODE_ID_BITS - prefix_bits) node_offset = node_id % EPOCHS_PER_SUBNET_SUBSCRIPTION permutation_seed = hash( uint_to_bytes(uint64((epoch + node_offset) // EPOCHS_PER_SUBNET_SUBSCRIPTION)) ) permutated_prefix = compute_shuffled_index( node_id_prefix, - 1 << ATTESTATION_SUBNET_PREFIX_BITS, + 1 << prefix_bits, permutation_seed, ) return SubnetID((permutated_prefix + index) % ATTESTATION_SUBNET_COUNT) @@ -1291,10 +1375,10 @@ - file: time/slots/slottime.go search: func StartTime( spec: | - + def compute_time_at_slot(state: BeaconState, slot: Slot) -> uint64: slots_since_genesis = slot - GENESIS_SLOT - return uint64(state.genesis_time + slots_since_genesis * SECONDS_PER_SLOT) + return uint64(state.genesis_time + slots_since_genesis * SLOT_DURATION_MS // 1000) - name: compute_weak_subjectivity_period#phase0 @@ -3220,7 +3304,7 @@ - name: get_forkchoice_store#phase0 sources: [] spec: | - + def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: assert anchor_block.state_root == hash_tree_root(anchor_state) anchor_root = hash_tree_root(anchor_block) @@ -3229,7 +3313,7 @@ finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) proposer_boost_root = Root() return Store( - time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), + time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, @@ -3247,7 +3331,7 @@ - name: get_forkchoice_store#gloas sources: [] spec: | - + def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: assert anchor_block.state_root == hash_tree_root(anchor_state) anchor_root = hash_tree_root(anchor_block) @@ -3256,7 +3340,7 @@ finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) proposer_boost_root = Root() return Store( - time=uint64(anchor_state.genesis_time + SECONDS_PER_SLOT * anchor_state.slot), + time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000), genesis_time=anchor_state.genesis_time, justified_checkpoint=justified_checkpoint, finalized_checkpoint=finalized_checkpoint, @@ -3266,12 +3350,57 @@ equivocating_indices=set(), blocks={anchor_root: copy(anchor_block)}, block_states={anchor_root: copy(anchor_state)}, + # [New in Gloas:EIP7732] + block_timeliness={anchor_root: [True, True]}, checkpoint_states={justified_checkpoint: copy(anchor_state)}, unrealized_justifications={anchor_root: justified_checkpoint}, # [New in Gloas:EIP7732] - execution_payload_states={anchor_root: copy(anchor_state)}, - ptc_vote={anchor_root: Vector[boolean, PTC_SIZE]()}, + payload_states={anchor_root: copy(anchor_state)}, + # [New in Gloas:EIP7732] + payload_timeliness_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + # [New in Gloas:EIP7732] + payload_data_availability_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + ) + + +- name: get_forkchoice_store#heze + sources: [] + spec: | + + def get_forkchoice_store(anchor_state: BeaconState, anchor_block: BeaconBlock) -> Store: + assert anchor_block.state_root == hash_tree_root(anchor_state) + anchor_root = hash_tree_root(anchor_block) + anchor_epoch = get_current_epoch(anchor_state) + justified_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + finalized_checkpoint = Checkpoint(epoch=anchor_epoch, root=anchor_root) + proposer_boost_root = Root() + return Store( + time=uint64(anchor_state.genesis_time + SLOT_DURATION_MS * anchor_state.slot // 1000), + genesis_time=anchor_state.genesis_time, + justified_checkpoint=justified_checkpoint, + finalized_checkpoint=finalized_checkpoint, + unrealized_justified_checkpoint=justified_checkpoint, + unrealized_finalized_checkpoint=finalized_checkpoint, + proposer_boost_root=proposer_boost_root, + equivocating_indices=set(), + blocks={anchor_root: copy(anchor_block)}, + block_states={anchor_root: copy(anchor_state)}, block_timeliness={anchor_root: [True, True]}, + checkpoint_states={justified_checkpoint: copy(anchor_state)}, + unrealized_justifications={anchor_root: justified_checkpoint}, + payload_states={anchor_root: copy(anchor_state)}, + payload_timeliness_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + payload_data_availability_vote={ + anchor_root: Vector[boolean, PTC_SIZE](True for _ in range(PTC_SIZE)) + }, + # [New in Heze:EIP7805] + payload_inclusion_list_satisfaction={anchor_root: True}, ) @@ -3455,6 +3584,133 @@ return rewards, penalties +- name: get_inclusion_list_bits#heze + sources: [] + spec: | + + def get_inclusion_list_bits( + store: InclusionListStore, state: BeaconState, slot: Slot + ) -> Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]: + """ + Return a ``Bitvector`` over inclusion list committee indices with bits set + for valid, non-equivocating inclusion list submissions for the given ``slot``. + """ + inclusion_list_committee = get_inclusion_list_committee(state, slot) + inclusion_list_committee_root = hash_tree_root(inclusion_list_committee) + key = (slot, inclusion_list_committee_root) + + validator_indices = [ + inclusion_list.validator_index + for inclusion_list in store.inclusion_lists[key] + if inclusion_list.validator_index not in store.equivocators[key] + ] + + return Bitvector[INCLUSION_LIST_COMMITTEE_SIZE]( + validator_index in validator_indices for validator_index in inclusion_list_committee + ) + + +- name: get_inclusion_list_committee#heze + sources: [] + spec: | + + def get_inclusion_list_committee( + state: BeaconState, slot: Slot + ) -> Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]: + """ + Get the inclusion list committee for the given ``slot``. + """ + epoch = compute_epoch_at_slot(slot) + indices: List[ValidatorIndex] = [] + # Concatenate all committees for this slot in order + committees_per_slot = get_committee_count_per_slot(state, epoch) + for i in range(committees_per_slot): + committee = get_beacon_committee(state, slot, CommitteeIndex(i)) + indices.extend(committee) + return Vector[ValidatorIndex, INCLUSION_LIST_COMMITTEE_SIZE]( + [indices[i % len(indices)] for i in range(INCLUSION_LIST_COMMITTEE_SIZE)] + ) + + +- name: get_inclusion_list_committee_assignment#heze + sources: [] + spec: | + + def get_inclusion_list_committee_assignment( + state: BeaconState, epoch: Epoch, validator_index: ValidatorIndex + ) -> Optional[Slot]: + """ + Returns the slot during the requested epoch in which the validator with + index ``validator_index`` is a member of the inclusion list committee. + Returns None if no assignment is found. + """ + next_epoch = Epoch(get_current_epoch(state) + 1) + assert epoch <= next_epoch + + start_slot = compute_start_slot_at_epoch(epoch) + for slot in range(start_slot, start_slot + SLOTS_PER_EPOCH): + if validator_index in get_inclusion_list_committee(state, Slot(slot)): + return Slot(slot) + return None + + +- name: get_inclusion_list_signature#heze + sources: [] + spec: | + + def get_inclusion_list_signature( + state: BeaconState, inclusion_list: InclusionList, privkey: int + ) -> BLSSignature: + domain = get_domain( + state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(inclusion_list.slot) + ) + signing_root = compute_signing_root(inclusion_list, domain) + return bls.Sign(privkey, signing_root) + + +- name: get_inclusion_list_store#heze + sources: [] + spec: | + + def get_inclusion_list_store() -> InclusionListStore: + # `cached_or_new_inclusion_list_store` is implementation and context dependent. + # It returns the cached `InclusionListStore`; if none exists, + # it initializes a new instance, caches it and returns it. + inclusion_list_store = cached_or_new_inclusion_list_store() + + return inclusion_list_store + + +- name: get_inclusion_list_submission_due_ms#heze + sources: [] + spec: | + + def get_inclusion_list_submission_due_ms(epoch: Epoch) -> uint64: + return get_slot_component_duration_ms(INCLUSION_LIST_SUBMISSION_DUE_BPS) + + +- name: get_inclusion_list_transactions#heze + sources: [] + spec: | + + def get_inclusion_list_transactions( + store: InclusionListStore, state: BeaconState, slot: Slot + ) -> Sequence[Transaction]: + inclusion_list_committee = get_inclusion_list_committee(state, slot) + inclusion_list_committee_root = hash_tree_root(inclusion_list_committee) + key = (slot, inclusion_list_committee_root) + + inclusion_list_transactions = [ + transaction + for inclusion_list in store.inclusion_lists[key] + if inclusion_list.validator_index not in store.equivocators[key] + for transaction in inclusion_list.transactions + ] + + # Deduplicate inclusion list transactions. Order does not need to be preserved. + return list(set(inclusion_list_transactions)) + + - name: get_index_for_new_builder#gloas sources: [] spec: | @@ -3783,13 +4039,13 @@ - name: get_node_children#gloas sources: [] spec: | - + def get_node_children( store: Store, blocks: Dict[Root, BeaconBlock], node: ForkChoiceNode ) -> Sequence[ForkChoiceNode]: if node.payload_status == PAYLOAD_STATUS_PENDING: children = [ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_EMPTY)] - if node.root in store.execution_payload_states: + if node.root in store.payload_states: children.append(ForkChoiceNode(root=node.root, payload_status=PAYLOAD_STATUS_FULL)) return children else: @@ -4026,6 +4282,14 @@ return head_root +- name: get_proposer_inclusion_list_cutoff_ms#heze + sources: [] + spec: | + + def get_proposer_inclusion_list_cutoff_ms(epoch: Epoch) -> uint64: + return get_slot_component_duration_ms(PROPOSER_INCLUSION_LIST_CUTOFF_BPS) + + - name: get_proposer_preferences_signature#gloas sources: [] spec: | @@ -4179,9 +4443,9 @@ - file: time/slots/slottime.go search: func CurrentSlot( spec: | - + def get_slots_since_genesis(store: Store) -> int: - return (store.time - store.genesis_time) // SECONDS_PER_SLOT + return (store.time - store.genesis_time) * 1000 // SLOT_DURATION_MS - name: get_source_deltas#phase0 @@ -4613,6 +4877,14 @@ return withdrawals, withdrawal_index, processed_count +- name: get_view_freeze_cutoff_ms#heze + sources: [] + spec: | + + def get_view_freeze_cutoff_ms(epoch: Epoch) -> uint64: + return get_slot_component_duration_ms(VIEW_FREEZE_CUTOFF_BPS) + + - name: get_voting_source#phase0 sources: [] spec: | @@ -5379,6 +5651,30 @@ return get_finality_delay(state) > MIN_EPOCHS_TO_INACTIVITY_PENALTY +- name: is_inclusion_list_bits_inclusive#heze + sources: [] + spec: | + + def is_inclusion_list_bits_inclusive( + store: InclusionListStore, + state: BeaconState, + slot: Slot, + inclusion_list_bits: Bitvector[INCLUSION_LIST_COMMITTEE_SIZE], + ) -> bool: + """ + Return ``True`` if and only if ``inclusion_list_bits`` is a superset of + the locally observed inclusion list bits for the given ``slot``. + """ + local_inclusion_list_bits = get_inclusion_list_bits(store, state, slot) + + return all( + inclusion_bit or not local_inclusion_bit + for inclusion_bit, local_inclusion_bit in zip( + inclusion_list_bits, local_inclusion_list_bits + ) + ) + + - name: is_merge_transition_block#bellatrix sources: [] spec: | @@ -5519,24 +5815,85 @@ ) +- name: is_payload_data_available#gloas + sources: [] + spec: | + + def is_payload_data_available(store: Store, root: Root) -> bool: + """ + Return whether the blob data for the beacon block with root ``root`` + was voted as present by the PTC, and was locally determined to be available. + """ + # The beacon block root must be known + assert root in store.payload_data_availability_vote + + # If the payload is not locally available, the blob data + # is not considered available regardless of the PTC vote + if root not in store.payload_states: + return False + + return sum(store.payload_data_availability_vote[root]) > DATA_AVAILABILITY_TIMELY_THRESHOLD + + +- name: is_payload_inclusion_list_satisfied#heze + sources: [] + spec: | + + def is_payload_inclusion_list_satisfied(store: Store, root: Root) -> bool: + """ + Return whether the execution payload for the beacon block with root ``root`` + satisfied the inclusion list constraints, and was locally determined to be available. + """ + # The beacon block root must be known + assert root in store.payload_inclusion_list_satisfaction + + # If the payload is not locally available, the payload + # is not considered to satisfy the inclusion list constraints + if root not in store.payload_states: + return False + + return store.payload_inclusion_list_satisfaction[root] + + - name: is_payload_timely#gloas sources: [] spec: | - + def is_payload_timely(store: Store, root: Root) -> bool: """ Return whether the execution payload for the beacon block with root ``root`` was voted as present by the PTC, and was locally determined to be available. """ # The beacon block root must be known - assert root in store.ptc_vote + assert root in store.payload_timeliness_vote # If the payload is not locally available, the payload # is not considered available regardless of the PTC vote - if root not in store.execution_payload_states: + if root not in store.payload_states: return False - return sum(store.ptc_vote[root]) > PAYLOAD_TIMELY_THRESHOLD + return sum(store.payload_timeliness_vote[root]) > PAYLOAD_TIMELY_THRESHOLD + + +- name: is_pending_validator#gloas + sources: [] + spec: | + + def is_pending_validator(state: BeaconState, pubkey: BLSPubkey) -> bool: + """ + Check if a pending deposit with a valid signature is in the queue for the given pubkey. + """ + for pending_deposit in state.pending_deposits: + if pending_deposit.pubkey != pubkey: + continue + if is_valid_deposit_signature( + pending_deposit.pubkey, + pending_deposit.withdrawal_credentials, + pending_deposit.amount, + pending_deposit.signature, + ): + return True + return False - name: is_proposer#phase0 @@ -5621,11 +5978,11 @@ - name: is_supporting_vote#gloas sources: [] spec: | - + def is_supporting_vote(store: Store, node: ForkChoiceNode, message: LatestMessage) -> bool: """ - Returns whether a vote for ``message.root`` supports the chain containing the beacon block ``node.root`` with the - payload contents indicated by ``node.payload_status`` as head during slot ``node.slot``. + Returns whether the vote ``message`` supports the chain containing the + forkchoice node ``node``. """ block = store.blocks[node.root] if node.root == message.root: @@ -5709,6 +6066,24 @@ return True +- name: is_valid_inclusion_list_signature#heze + sources: [] + spec: | + + def is_valid_inclusion_list_signature( + state: BeaconState, signed_inclusion_list: SignedInclusionList + ) -> bool: + """ + Check if ``signed_inclusion_list`` has a valid signature. + """ + message = signed_inclusion_list.message + index = message.validator_index + pubkey = state.validators[index].pubkey + domain = get_domain(state, DOMAIN_INCLUSION_LIST_COMMITTEE, compute_epoch_at_slot(message.slot)) + signing_root = compute_signing_root(message, domain) + return bls.Verify(pubkey, signing_root, signed_inclusion_list.signature) + + - name: is_valid_indexed_attestation#phase0 sources: - file: beacon-chain/core/blocks/attestation.go @@ -6365,7 +6740,7 @@ - name: on_block#gloas sources: [] spec: | - + def on_block(store: Store, signed_block: SignedBeaconBlock) -> None: """ Run ``on_block`` upon receiving a new block. @@ -6380,8 +6755,8 @@ parent_bid = parent_block.body.signed_execution_payload_bid.message # Make a copy of the state to avoid mutability issues if is_parent_node_full(store, block): - assert block.parent_root in store.execution_payload_states - state = copy(store.execution_payload_states[block.parent_root]) + assert block.parent_root in store.payload_states + state = copy(store.payload_states[block.parent_root]) else: assert bid.parent_block_hash == parent_bid.parent_block_hash state = copy(store.block_states[block.parent_root]) @@ -6410,7 +6785,8 @@ # Add new state for this block to the store store.block_states[block_root] = state # Add a new PTC voting for this block to the store - store.ptc_vote[block_root] = [False] * PTC_SIZE + store.payload_timeliness_vote[block_root] = [False] * PTC_SIZE + store.payload_data_availability_vote[block_root] = [False] * PTC_SIZE # Notify the store about the payload_attestations in the block notify_ptc_messages(store, state, block.body.payload_attestations) @@ -6428,7 +6804,33 @@ - name: on_execution_payload#gloas sources: [] spec: | - + + def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None: + """ + Run ``on_execution_payload`` upon receiving a new execution payload. + """ + envelope = signed_envelope.message + # The corresponding beacon block root needs to be known + assert envelope.beacon_block_root in store.block_states + + # Check if blob data is available + # If not, this payload MAY be queued and subsequently considered when blob data becomes available + assert is_data_available(envelope.beacon_block_root) + + # Make a copy of the state to avoid mutability issues + state = copy(store.block_states[envelope.beacon_block_root]) + + # Process the execution payload + process_execution_payload(state, signed_envelope, EXECUTION_ENGINE) + + # Add new state for this payload to the store + store.payload_states[envelope.beacon_block_root] = state + + +- name: on_execution_payload#heze + sources: [] + spec: | + def on_execution_payload(store: Store, signed_envelope: SignedExecutionPayloadEnvelope) -> None: """ Run ``on_execution_payload`` upon receiving a new execution payload. @@ -6447,14 +6849,40 @@ # Process the execution payload process_execution_payload(state, signed_envelope, EXECUTION_ENGINE) + # [New in Heze:EIP7805] + # Check if this payload satisfies the inclusion list constraints + # If not, add this payload to the store as inclusion list constraints unsatisfied + record_payload_inclusion_list_satisfaction( + store, state, envelope.beacon_block_root, envelope.payload, EXECUTION_ENGINE + ) + # Add new state for this payload to the store - store.execution_payload_states[envelope.beacon_block_root] = state + store.payload_states[envelope.beacon_block_root] = state + + +- name: on_inclusion_list#heze + sources: [] + spec: | + + def on_inclusion_list(store: Store, signed_inclusion_list: SignedInclusionList) -> None: + """ + Run ``on_inclusion_list`` upon receiving a new inclusion list. + """ + inclusion_list = signed_inclusion_list.message + + seconds_since_genesis = store.time - store.genesis_time + time_into_slot_ms = seconds_to_milliseconds(seconds_since_genesis) % SLOT_DURATION_MS + epoch = get_current_store_epoch(store) + view_freeze_cutoff_ms = get_view_freeze_cutoff_ms(epoch) + is_before_view_freeze_cutoff = time_into_slot_ms < view_freeze_cutoff_ms + + process_inclusion_list(get_inclusion_list_store(), inclusion_list, is_before_view_freeze_cutoff) - name: on_payload_attestation_message#gloas sources: [] spec: | - + def on_payload_attestation_message( store: Store, ptc_message: PayloadAttestationMessage, is_from_block: bool = False ) -> None: @@ -6486,22 +6914,26 @@ signature=ptc_message.signature, ), ) - # Update the ptc vote for the block + # Update the votes for the block ptc_index = ptc.index(ptc_message.validator_index) - ptc_vote = store.ptc_vote[data.beacon_block_root] - ptc_vote[ptc_index] = data.payload_present + payload_timeliness_vote = store.payload_timeliness_vote[data.beacon_block_root] + payload_timeliness_vote[ptc_index] = data.payload_present + payload_data_availability_vote = store.payload_data_availability_vote[data.beacon_block_root] + payload_data_availability_vote[ptc_index] = data.blob_data_available - name: on_tick#phase0 sources: [] spec: | - + def on_tick(store: Store, time: uint64) -> None: # If the ``store.time`` falls behind, while loop catches up slot by slot # to ensure that every previous slot is processed with ``on_tick_per_slot`` - tick_slot = (time - store.genesis_time) // SECONDS_PER_SLOT + tick_slot = (time - store.genesis_time) * 1000 // SLOT_DURATION_MS while get_current_slot(store) < tick_slot: - previous_time = store.genesis_time + (get_current_slot(store) + 1) * SECONDS_PER_SLOT + previous_time = ( + store.genesis_time + (get_current_slot(store) + 1) * SLOT_DURATION_MS // 1000 + ) on_tick_per_slot(store, previous_time) on_tick_per_slot(store, time) @@ -6670,6 +7102,37 @@ ) +- name: prepare_execution_payload#heze + sources: [] + spec: | + + def prepare_execution_payload( + state: BeaconState, + safe_block_hash: Hash32, + finalized_block_hash: Hash32, + suggested_fee_recipient: ExecutionAddress, + execution_engine: ExecutionEngine, + ) -> Optional[PayloadId]: + # Set the forkchoice head and initiate the payload build process + payload_attributes = PayloadAttributes( + timestamp=compute_time_at_slot(state, state.slot), + prev_randao=get_randao_mix(state, get_current_epoch(state)), + suggested_fee_recipient=suggested_fee_recipient, + withdrawals=get_expected_withdrawals(state).withdrawals, + parent_beacon_block_root=hash_tree_root(state.latest_block_header), + # [New in Heze:EIP7805] + inclusion_list_transactions=get_inclusion_list_transactions( + get_inclusion_list_store(), state, Slot(state.slot - 1) + ), + ) + return execution_engine.notify_forkchoice_updated( + head_block_hash=state.latest_block_hash, + safe_block_hash=safe_block_hash, + finalized_block_hash=finalized_block_hash, + payload_attributes=payload_attributes, + ) + + - name: process_attestation#phase0 sources: - file: beacon-chain/core/blocks/attestation.go @@ -7317,7 +7780,7 @@ - name: process_deposit_request#gloas sources: [] spec: | - + def process_deposit_request(state: BeaconState, deposit_request: DepositRequest) -> None: # [New in Gloas:EIP7732] builder_pubkeys = [b.pubkey for b in state.builders] @@ -7328,8 +7791,11 @@ # already exists with this pubkey, apply the deposit to their balance is_builder = deposit_request.pubkey in builder_pubkeys is_validator = deposit_request.pubkey in validator_pubkeys - is_builder_prefix = is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) - if is_builder or (is_builder_prefix and not is_validator): + if is_builder or ( + is_builder_withdrawal_credential(deposit_request.withdrawal_credentials) + and not is_validator + and not is_pending_validator(state, deposit_request.pubkey) + ): # Apply builder deposits immediately apply_deposit_for_builder( state, @@ -8045,6 +8511,35 @@ ) +- name: process_inclusion_list#heze + sources: [] + spec: | + + def process_inclusion_list( + store: InclusionListStore, inclusion_list: InclusionList, is_before_view_freeze_cutoff: bool + ) -> None: + key = (inclusion_list.slot, inclusion_list.inclusion_list_committee_root) + + # Ignore `inclusion_list` from equivocators. + if inclusion_list.validator_index in store.equivocators[key]: + return + + for stored_inclusion_list in store.inclusion_lists[key]: + if stored_inclusion_list.validator_index != inclusion_list.validator_index: + continue + + if stored_inclusion_list != inclusion_list: + store.equivocators[key].add(inclusion_list.validator_index) + store.inclusion_lists[key].remove(stored_inclusion_list) + + # Whether it was an equivocation or not, we have processed this `inclusion_list`. + return + + # Only store `inclusion_list` if it arrived before the view freeze cutoff. + if is_before_view_freeze_cutoff: + store.inclusion_lists[key].add(inclusion_list) + + - name: process_justification_and_finalization#phase0 sources: - file: beacon-chain/core/epoch/precompute/justification_finalization.go @@ -9346,6 +9841,26 @@ ] +- name: record_payload_inclusion_list_satisfaction#heze + sources: [] + spec: | + + def record_payload_inclusion_list_satisfaction( + store: Store, + state: BeaconState, + root: Root, + payload: ExecutionPayload, + execution_engine: ExecutionEngine, + ) -> None: + inclusion_list_transactions = get_inclusion_list_transactions( + get_inclusion_list_store(), state, Slot(state.slot - 1) + ) + is_inclusion_list_satisfied = execution_engine.is_inclusion_list_satisfied( + payload, inclusion_list_transactions + ) + store.payload_inclusion_list_satisfaction[root] = is_inclusion_list_satisfied + + - name: recover_matrix#fulu sources: [] spec: | @@ -9454,11 +9969,29 @@ - name: should_extend_payload#gloas sources: [] spec: | - + + def should_extend_payload(store: Store, root: Root) -> bool: + proposer_root = store.proposer_boost_root + return ( + (is_payload_timely(store, root) and is_payload_data_available(store, root)) + or proposer_root == Root() + or store.blocks[proposer_root].parent_root != root + or is_parent_node_full(store, store.blocks[proposer_root]) + ) + + +- name: should_extend_payload#heze + sources: [] + spec: | + def should_extend_payload(store: Store, root: Root) -> bool: + # [New in Heze:EIP7805] + if not is_payload_inclusion_list_satisfied(store, root): + return False + proposer_root = store.proposer_boost_root return ( - is_payload_timely(store, root) + (is_payload_timely(store, root) and is_payload_data_available(store, root)) or proposer_root == Root() or store.blocks[proposer_root].parent_root != root or is_parent_node_full(store, store.blocks[proposer_root]) @@ -9803,7 +10336,7 @@ - name: update_latest_messages#gloas sources: [] spec: | - + def update_latest_messages( store: Store, attesting_indices: Sequence[ValidatorIndex], attestation: Attestation ) -> None: @@ -9815,8 +10348,11 @@ ] for i in non_equivocating_attesting_indices: if i not in store.latest_messages or slot > store.latest_messages[i].slot: + # [Modified in Gloas:EIP7732] store.latest_messages[i] = LatestMessage( - slot=slot, root=beacon_block_root, payload_present=payload_present + slot=slot, + root=beacon_block_root, + payload_present=payload_present, ) @@ -10725,6 +11261,85 @@ return post +- name: upgrade_to_heze#heze + sources: [] + spec: | + + def upgrade_to_heze(pre: gloas.BeaconState) -> BeaconState: + epoch = gloas.get_current_epoch(pre) + latest_execution_payload_bid = ExecutionPayloadBid( + parent_block_hash=pre.latest_execution_payload_bid.parent_block_hash, + parent_block_root=pre.latest_execution_payload_bid.parent_block_root, + block_hash=pre.latest_execution_payload_bid.block_hash, + prev_randao=pre.latest_execution_payload_bid.prev_randao, + fee_recipient=pre.latest_execution_payload_bid.fee_recipient, + gas_limit=pre.latest_execution_payload_bid.gas_limit, + builder_index=pre.latest_execution_payload_bid.builder_index, + slot=pre.latest_execution_payload_bid.slot, + value=pre.latest_execution_payload_bid.value, + execution_payment=pre.latest_execution_payload_bid.execution_payment, + blob_kzg_commitments=pre.latest_execution_payload_bid.blob_kzg_commitments, + # [New in Heze:EIP7805] + inclusion_list_bits=Bitvector[INCLUSION_LIST_COMMITTEE_SIZE](), + ) + + post = BeaconState( + genesis_time=pre.genesis_time, + genesis_validators_root=pre.genesis_validators_root, + slot=pre.slot, + fork=Fork( + previous_version=pre.fork.current_version, + # [Modified in Heze:EIP7805] + current_version=HEZE_FORK_VERSION, + epoch=epoch, + ), + latest_block_header=pre.latest_block_header, + block_roots=pre.block_roots, + state_roots=pre.state_roots, + historical_roots=pre.historical_roots, + eth1_data=pre.eth1_data, + eth1_data_votes=pre.eth1_data_votes, + eth1_deposit_index=pre.eth1_deposit_index, + validators=pre.validators, + balances=pre.balances, + randao_mixes=pre.randao_mixes, + slashings=pre.slashings, + previous_epoch_participation=pre.previous_epoch_participation, + current_epoch_participation=pre.current_epoch_participation, + justification_bits=pre.justification_bits, + previous_justified_checkpoint=pre.previous_justified_checkpoint, + current_justified_checkpoint=pre.current_justified_checkpoint, + finalized_checkpoint=pre.finalized_checkpoint, + inactivity_scores=pre.inactivity_scores, + current_sync_committee=pre.current_sync_committee, + next_sync_committee=pre.next_sync_committee, + # [Modified in Heze:EIP7805] + latest_execution_payload_bid=latest_execution_payload_bid, + next_withdrawal_index=pre.next_withdrawal_index, + next_withdrawal_validator_index=pre.next_withdrawal_validator_index, + historical_summaries=pre.historical_summaries, + deposit_requests_start_index=pre.deposit_requests_start_index, + deposit_balance_to_consume=pre.deposit_balance_to_consume, + exit_balance_to_consume=pre.exit_balance_to_consume, + earliest_exit_epoch=pre.earliest_exit_epoch, + consolidation_balance_to_consume=pre.consolidation_balance_to_consume, + earliest_consolidation_epoch=pre.earliest_consolidation_epoch, + pending_deposits=pre.pending_deposits, + pending_partial_withdrawals=pre.pending_partial_withdrawals, + pending_consolidations=pre.pending_consolidations, + proposer_lookahead=pre.proposer_lookahead, + builders=pre.builders, + next_withdrawal_builder_index=pre.next_withdrawal_builder_index, + execution_payload_availability=pre.execution_payload_availability, + builder_pending_payments=pre.builder_pending_payments, + builder_pending_withdrawals=pre.builder_pending_withdrawals, + latest_block_hash=pre.latest_block_hash, + payload_expected_withdrawals=pre.payload_expected_withdrawals, + ) + + return post + + - name: validate_light_client_update#altair sources: [] spec: | @@ -10877,7 +11492,7 @@ - name: validate_on_attestation#gloas sources: [] spec: | - + def validate_on_attestation(store: Store, attestation: Attestation, is_from_block: bool) -> None: target = attestation.data.target @@ -10905,6 +11520,10 @@ assert attestation.data.index in [0, 1] if block_slot == attestation.data.slot: assert attestation.data.index == 0 + # [New in Gloas:EIP7732] + # If attesting for a full node, the payload must be known + if attestation.data.index == 1: + assert attestation.data.beacon_block_root in store.payload_states # LMD vote must be consistent with FFG vote target assert target.root == get_checkpoint_block( diff --git a/specrefs/presets.yml b/specrefs/presets.yml index 9e7938edfd5b..896f79deee6b 100644 --- a/specrefs/presets.yml +++ b/specrefs/presets.yml @@ -192,6 +192,13 @@ INACTIVITY_PENALTY_QUOTIENT_BELLATRIX: uint64 = 16777216 +- name: INCLUSION_LIST_COMMITTEE_SIZE#heze + sources: [] + spec: | + + INCLUSION_LIST_COMMITTEE_SIZE: uint64 = 16 + + - name: KZG_COMMITMENTS_INCLUSION_PROOF_DEPTH#fulu sources: - file: proto/ssz_proto_library.bzl From 829388a8967292070fed8fbdbef0b37a49f24922 Mon Sep 17 00:00:00 2001 From: terence Date: Wed, 18 Mar 2026 06:58:57 -0700 Subject: [PATCH 3/5] Manu's feedback --- beacon-chain/core/gloas/deposit_request.go | 22 +++++-------------- .../state/state-native/getters_deposits.go | 2 ++ changelog/t_bump-consensus-spec-alpha3.md | 4 ++++ ...in_check-pending-deposit-before-builder.md | 3 --- 4 files changed, 12 insertions(+), 19 deletions(-) delete mode 100644 changelog/terencechain_check-pending-deposit-before-builder.md diff --git a/beacon-chain/core/gloas/deposit_request.go b/beacon-chain/core/gloas/deposit_request.go index 97907310960d..7c11045482cd 100644 --- a/beacon-chain/core/gloas/deposit_request.go +++ b/beacon-chain/core/gloas/deposit_request.go @@ -68,37 +68,27 @@ func processDepositRequests(ctx context.Context, beaconState state.BeaconState, // ) // func processDepositRequest(beaconState state.BeaconState, request *enginev1.DepositRequest) error { - var err error - defer func() { - if err == nil { - builderDepositsProcessedTotal.Inc() - } - }() - if request == nil { - err = errors.New("nil deposit request") - return err + return errors.New("nil deposit request") } - var applied bool - applied, err = applyBuilderDepositRequest(beaconState, request) + applied, err := applyBuilderDepositRequest(beaconState, request) if err != nil { - err = errors.Wrap(err, "could not apply builder deposit") - return err + return errors.Wrap(err, "could not apply builder deposit") } if applied { + builderDepositsProcessedTotal.Inc() return nil } - if err = beaconState.AppendPendingDeposit(ðpb.PendingDeposit{ + if err := beaconState.AppendPendingDeposit(ðpb.PendingDeposit{ PublicKey: request.Pubkey, WithdrawalCredentials: request.WithdrawalCredentials, Amount: request.Amount, Signature: request.Signature, Slot: beaconState.Slot(), }); err != nil { - err = errors.Wrap(err, "could not append deposit request") - return err + return errors.Wrap(err, "could not append deposit request") } return nil } diff --git a/beacon-chain/state/state-native/getters_deposits.go b/beacon-chain/state/state-native/getters_deposits.go index d32a2a968e1e..22a9a5688dd5 100644 --- a/beacon-chain/state/state-native/getters_deposits.go +++ b/beacon-chain/state/state-native/getters_deposits.go @@ -2,6 +2,7 @@ package state_native import ( "bytes" + "fmt" "github.com/OffchainLabs/prysm/v7/beacon-chain/core/helpers" "github.com/OffchainLabs/prysm/v7/consensus-types/primitives" @@ -76,6 +77,7 @@ func (b *BeaconState) IsPendingValidator(pubkey []byte) (bool, error) { Signature: deposit.Signature, }) if err != nil { + log.WithField("pubkey", fmt.Sprintf("%x", deposit.PublicKey)).WithError(err).Warn("Could not verify pending deposit signature") continue } if valid { diff --git a/changelog/t_bump-consensus-spec-alpha3.md b/changelog/t_bump-consensus-spec-alpha3.md index 05887484cade..0f59cd50e8eb 100644 --- a/changelog/t_bump-consensus-spec-alpha3.md +++ b/changelog/t_bump-consensus-spec-alpha3.md @@ -1,3 +1,7 @@ ### Changed - Bump consensus spec version from v1.7.0-alpha.2 to v1.7.0-alpha.3 + +### Fixed + +- Check for pending validator deposits with valid signatures before applying builder deposits, preventing pubkey hijacking into the builder registry. diff --git a/changelog/terencechain_check-pending-deposit-before-builder.md b/changelog/terencechain_check-pending-deposit-before-builder.md deleted file mode 100644 index c114f8813c78..000000000000 --- a/changelog/terencechain_check-pending-deposit-before-builder.md +++ /dev/null @@ -1,3 +0,0 @@ -### Fixed - -- Check for pending validator deposits with valid signatures before applying builder deposits, preventing pubkey hijacking into the builder registry. From d5bdd0b6a6d0e4c7cf9467892ada4f4c7ec47a23 Mon Sep 17 00:00:00 2001 From: terence Date: Wed, 18 Mar 2026 07:08:52 -0700 Subject: [PATCH 4/5] refactor applybuilderDepositReq --- beacon-chain/core/gloas/deposit_request.go | 27 +++++++++++----------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/beacon-chain/core/gloas/deposit_request.go b/beacon-chain/core/gloas/deposit_request.go index 7c11045482cd..ee96eb08ead1 100644 --- a/beacon-chain/core/gloas/deposit_request.go +++ b/beacon-chain/core/gloas/deposit_request.go @@ -122,27 +122,26 @@ func applyBuilderDepositRequest(beaconState state.BeaconState, request *enginev1 } pubkey := bytesutil.ToBytes48(request.Pubkey) - _, isValidator := beaconState.ValidatorIndexByPubkey(pubkey) idx, isBuilder := beaconState.BuilderIndexByPubkey(pubkey) - isBuilderPrefix := helpers.IsBuilderWithdrawalCredential(request.WithdrawalCredentials) - if !isBuilder { - isPending, err := beaconState.IsPendingValidator(request.Pubkey) - if err != nil { + if isBuilder { + if err := beaconState.IncreaseBuilderBalance(idx, request.Amount); err != nil { return false, err } - if isPending { - return false, nil - } + return true, nil } - if !isBuilder && (!isBuilderPrefix || isValidator) { + + isBuilderPrefix := helpers.IsBuilderWithdrawalCredential(request.WithdrawalCredentials) + _, isValidator := beaconState.ValidatorIndexByPubkey(pubkey) + if !isBuilderPrefix || isValidator { return false, nil } - if isBuilder { - if err := beaconState.IncreaseBuilderBalance(idx, request.Amount); err != nil { - return false, err - } - return true, nil + isPending, err := beaconState.IsPendingValidator(request.Pubkey) + if err != nil { + return false, err + } + if isPending { + return false, nil } if err := applyDepositForNewBuilder( From fa7773c9837fb81e0fd4e5d39741f6a7221a8f3f Mon Sep 17 00:00:00 2001 From: terence Date: Thu, 2 Apr 2026 22:37:11 +0200 Subject: [PATCH 5/5] Fix spec ref --- specrefs/containers.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/specrefs/containers.yml b/specrefs/containers.yml index a4a5bb10ccc2..b2ed9a1ea8c3 100644 --- a/specrefs/containers.yml +++ b/specrefs/containers.yml @@ -686,7 +686,7 @@ - name: BeaconState#heze sources: [] spec: | - + class BeaconState(Container): genesis_time: uint64 genesis_validators_root: Root @@ -734,6 +734,7 @@ builder_pending_withdrawals: List[BuilderPendingWithdrawal, BUILDER_PENDING_WITHDRAWALS_LIMIT] latest_block_hash: Hash32 payload_expected_withdrawals: List[Withdrawal, MAX_WITHDRAWALS_PER_PAYLOAD] + ptc_window: Vector[Vector[ValidatorIndex, PTC_SIZE], (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH] - name: BlobIdentifier#deneb