From 51bbe37cb547ca9c7a32f8cc4682822fe877d051 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Wed, 8 Jul 2026 10:08:57 +0300 Subject: [PATCH 01/10] impl gloas alpha spec 12 changes --- .../gossip_methods.rs | 2 +- .../network/src/sync/block_lookups/mod.rs | 23 +++++++++ beacon_node/network/src/sync/manager.rs | 51 ++++++++++++++++++- .../mainnet/config.yaml | 4 +- consensus/fork_choice/src/fork_choice.rs | 9 ++-- .../process_operations.rs | 23 +++++---- consensus/types/configs/mainnet.yaml | 4 +- consensus/types/presets/gnosis/gloas.yaml | 4 +- consensus/types/presets/mainnet/gloas.yaml | 4 +- consensus/types/presets/minimal/gloas.yaml | 4 +- .../src/builder/builder_deposit_request.rs | 4 -- consensus/types/src/core/chain_spec.rs | 16 +++--- consensus/types/src/core/eth_spec.rs | 4 +- testing/ef_tests/Makefile | 2 +- 14 files changed, 111 insertions(+), 43 deletions(-) diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index 7957b56cbf0..2f2bbaf9c64 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -4018,7 +4018,6 @@ impl NetworkBeaconProcessor { PayloadBidError::BadSignature | PayloadBidError::InvalidBuilder { .. } | PayloadBidError::InvalidBuilderVersion { .. } - | PayloadBidError::InvalidFeeRecipient | PayloadBidError::ExecutionPaymentNonZero { .. } | PayloadBidError::InvalidBlobKzgCommitments { .. } | PayloadBidError::BidNotDescendantOfParent { .. } @@ -4038,6 +4037,7 @@ impl NetworkBeaconProcessor { | PayloadBidError::ParentBlockRootUnknown { .. } | PayloadBidError::ParentBlockRootNotCanonical { .. } | PayloadBidError::BuilderCantCoverBid { .. } + | PayloadBidError::InvalidFeeRecipient | PayloadBidError::InvalidGasLimit | PayloadBidError::BeaconStateError(_) | PayloadBidError::InternalError(_) diff --git a/beacon_node/network/src/sync/block_lookups/mod.rs b/beacon_node/network/src/sync/block_lookups/mod.rs index b6d78e5e318..0d3e29ecc82 100644 --- a/beacon_node/network/src/sync/block_lookups/mod.rs +++ b/beacon_node/network/src/sync/block_lookups/mod.rs @@ -223,6 +223,29 @@ impl BlockLookups { self.new_current_lookup(block_root, None, None, peer_source, &PeerType::Block, cx) } + /// Search the payload envelope of a known block. `peer_source` peers claim the payload for + /// `bid_block_hash` has been imported (e.g. via an `index == 1` attestation), so they can + /// serve this block's payload envelope and data columns. + /// + /// Returns true if the lookup is created or already exists + #[must_use = "only reference the new lookup if returns true"] + pub fn search_payload_envelope( + &mut self, + block_root: Hash256, + bid_block_hash: ExecutionBlockHash, + peer_source: &[PeerId], + cx: &mut SyncNetworkContext, + ) -> bool { + self.new_current_lookup( + block_root, + None, + None, + peer_source, + &PeerType::PayloadEnvelope(bid_block_hash), + cx, + ) + } + /// A block or blob triggers the search of a parent. /// Check if this new lookup extends a bad chain: /// - Extending `child_block_root_trigger` would exceed the max depth diff --git a/beacon_node/network/src/sync/manager.rs b/beacon_node/network/src/sync/manager.rs index f2c01bb4dae..62da3189c32 100644 --- a/beacon_node/network/src/sync/manager.rs +++ b/beacon_node/network/src/sync/manager.rs @@ -898,13 +898,12 @@ impl SyncManager { { self.notified_unknown_payload_roots .insert((peer_id, block_root)); - // TODO(gloas): trigger a payload-envelope lookup for `block_root` via - // `ExecutionPayloadEnvelopesByRoot`. Wired up in the gloas lookup-sync PR (#9155). debug!( ?block_root, ?peer_id, "Received unknown payload envelope from attestation" ); + self.handle_unknown_payload_envelope(peer_id, block_root); } } SyncMessage::Disconnect(peer_id) => { @@ -1019,6 +1018,54 @@ impl SyncManager { } } + fn handle_unknown_payload_envelope(&mut self, peer_id: PeerId, block_root: Hash256) { + let Some(block) = self + .chain + .canonical_head + .fork_choice_read_lock() + .get_block(&block_root) + else { + debug!( + ?block_root, + "Ignoring payload envelope request for block not in fork choice" + ); + return; + }; + + if block.payload_received { + return; + } + + let Some(bid_block_hash) = block.execution_payload_block_hash else { + debug!( + ?block_root, + "Ignoring payload envelope request for block without a bid block hash" + ); + return; + }; + + match self.should_search_for_block(Some(block.slot), &peer_id) { + Ok(_) => { + if self.block_lookups.search_payload_envelope( + block_root, + bid_block_hash, + &[peer_id], + &mut self.network, + ) { + // Lookup created. No need to log here it's logged in `new_current_lookup` + } else { + debug!( + ?block_root, + "No lookup created for unknown payload envelope" + ); + } + } + Err(reason) => { + debug!(%block_root, reason, "Ignoring unknown payload envelope request"); + } + } + } + fn should_search_for_block( &mut self, block_slot: Option, diff --git a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml index ced96791425..02bf37cb551 100644 --- a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml @@ -93,8 +93,8 @@ SYNC_MESSAGE_DUE_BPS: 3333 CONTRIBUTION_DUE_BPS: 6667 # Gloas -# 2**13 (= 8192) epochs -MIN_BUILDER_WITHDRAWABILITY_DELAY: 8192 +# 2**6 (= 64) epochs +MIN_BUILDER_WITHDRAWABILITY_DELAY: 64 # 2500 basis points, 25% of SLOT_DURATION_MS ATTESTATION_DUE_BPS_GLOAS: 2500 # 5000 basis points, 50% of SLOT_DURATION_MS diff --git a/consensus/fork_choice/src/fork_choice.rs b/consensus/fork_choice/src/fork_choice.rs index bfb1f0b81e5..92c5f721285 100644 --- a/consensus/fork_choice/src/fork_choice.rs +++ b/consensus/fork_choice/src/fork_choice.rs @@ -541,8 +541,8 @@ where } } - /// Returns the dependent root for `block_root`, per the spec `get_dependent_root` helper. - fn get_dependent_root( + /// Returns the dependent root for `block_root`, per the spec `get_shuffling_dependent_root` helper. + fn get_shuffling_dependent_root( &self, block_root: Hash256, current_slot: Slot, @@ -882,8 +882,9 @@ where if is_timely && is_first_block { // The block isn't in fork choice so resolve its dependent root via its parent. let block_dependent_root = - self.get_dependent_root(block.parent_root(), current_slot, spec)?; - let head_dependent_root = self.get_dependent_root(head_root, current_slot, spec)?; + self.get_shuffling_dependent_root(block.parent_root(), current_slot, spec)?; + let head_dependent_root = + self.get_shuffling_dependent_root(head_root, current_slot, spec)?; // Add proposer score boost if the block is timely, not conflicting with an // existing block, with the same dependent root as the canonical chain head. diff --git a/consensus/state_processing/src/per_block_processing/process_operations.rs b/consensus/state_processing/src/per_block_processing/process_operations.rs index 8d9962c4783..54454bf5d2a 100644 --- a/consensus/state_processing/src/per_block_processing/process_operations.rs +++ b/consensus/state_processing/src/per_block_processing/process_operations.rs @@ -10,6 +10,8 @@ use bls::PublicKeyBytes; use ssz_types::FixedVector; use typenum::U33; use types::consts::altair::{PARTICIPATION_FLAG_WEIGHTS, PROPOSER_WEIGHT, WEIGHT_DENOMINATOR}; +use types::consts::gloas::PAYLOAD_BUILDER_VERSION; +use types::is_builder_withdrawal_credential; pub fn process_operations>( state: &mut BeaconState, @@ -858,6 +860,10 @@ fn process_builder_deposit_request( builder_deposit_request: &BuilderDepositRequest, spec: &ChainSpec, ) -> Result<(), BlockProcessingError> { + if !is_builder_withdrawal_credential(builder_deposit_request.withdrawal_credentials, spec) { + return Ok(()); + } + let builder_index = state .builders()? .iter() @@ -866,13 +872,10 @@ fn process_builder_deposit_request( match builder_index { None => { if builder_deposit_request.is_valid_builder_deposit_signature(spec) { - let version = builder_deposit_request - .version() - .ok_or(BeaconStateError::WithdrawalCredentialMissingVersion)?; let slot = state.slot(); state.add_builder_to_registry( builder_deposit_request.pubkey, - version, + PAYLOAD_BUILDER_VERSION, builder_deposit_request.withdrawal_credentials, builder_deposit_request.amount, slot, @@ -887,16 +890,14 @@ fn process_builder_deposit_request( .get_mut(builder_index) .ok_or(BeaconStateError::UnknownBuilder(builder_index as u64))?; - // TODO(gloas): this is already different in `master`, needs an update when we go - // to spec 1.7.0-alpha.12+ - builder - .balance - .safe_add_assign(builder_deposit_request.amount)?; - - if builder.withdrawable_epoch != spec.far_future_epoch { + if builder.withdrawable_epoch != spec.far_future_epoch && builder.balance == 0 { builder.withdrawable_epoch = current_epoch.safe_add(spec.min_builder_withdrawability_delay)?; } + + builder + .balance + .safe_add_assign(builder_deposit_request.amount)?; } } diff --git a/consensus/types/configs/mainnet.yaml b/consensus/types/configs/mainnet.yaml index 743384bcc90..25bf872a7a0 100644 --- a/consensus/types/configs/mainnet.yaml +++ b/consensus/types/configs/mainnet.yaml @@ -91,8 +91,8 @@ SYNC_MESSAGE_DUE_BPS: 3333 CONTRIBUTION_DUE_BPS: 6667 # Gloas -# 2**13 (= 8192) epochs -MIN_BUILDER_WITHDRAWABILITY_DELAY: 8192 +# 2**6 (= 64) epochs +MIN_BUILDER_WITHDRAWABILITY_DELAY: 64 # 2500 basis points, 25% of SLOT_DURATION_MS ATTESTATION_DUE_BPS_GLOAS: 2500 # 5000 basis points, 50% of SLOT_DURATION_MS diff --git a/consensus/types/presets/gnosis/gloas.yaml b/consensus/types/presets/gnosis/gloas.yaml index 1f290d70105..6b1e6625a59 100644 --- a/consensus/types/presets/gnosis/gloas.yaml +++ b/consensus/types/presets/gnosis/gloas.yaml @@ -24,7 +24,7 @@ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 # Execution # --------------------------------------------------------------- -# 2**8 (= 256) builder deposit requests -MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256 +# 2**6 (= 64) builder deposit requests +MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64 # 2**4 (= 16) builder exit requests MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 diff --git a/consensus/types/presets/mainnet/gloas.yaml b/consensus/types/presets/mainnet/gloas.yaml index 4bd7888c6c9..b50a86833d1 100644 --- a/consensus/types/presets/mainnet/gloas.yaml +++ b/consensus/types/presets/mainnet/gloas.yaml @@ -24,7 +24,7 @@ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 # Execution # --------------------------------------------------------------- -# 2**8 (= 256) builder deposit requests -MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256 +# 2**6 (= 64) builder deposit requests +MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64 # 2**4 (= 16) builder exit requests MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 \ No newline at end of file diff --git a/consensus/types/presets/minimal/gloas.yaml b/consensus/types/presets/minimal/gloas.yaml index 096afd53f41..bee0628e4ce 100644 --- a/consensus/types/presets/minimal/gloas.yaml +++ b/consensus/types/presets/minimal/gloas.yaml @@ -24,7 +24,7 @@ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16 # Execution # --------------------------------------------------------------- -# 2**8 (= 256) builder deposit requests -MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 256 +# 2**6 (= 64) builder deposit requests +MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64 # 2**4 (= 16) builder exit requests MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 diff --git a/consensus/types/src/builder/builder_deposit_request.rs b/consensus/types/src/builder/builder_deposit_request.rs index f57845750d1..7449892b874 100644 --- a/consensus/types/src/builder/builder_deposit_request.rs +++ b/consensus/types/src/builder/builder_deposit_request.rs @@ -27,10 +27,6 @@ impl BuilderDepositRequest { } } - pub fn version(&self) -> Option { - self.withdrawal_credentials.as_slice().first().cloned() - } - pub fn is_valid_builder_deposit_signature(&self, spec: &ChainSpec) -> bool { let Ok(pubkey) = self.pubkey.decompress() else { return false; diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index 73163060e3b..be674aa2cf4 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -1104,7 +1104,7 @@ impl ChainSpec { bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, compounding_withdrawal_prefix_byte: 0x02, - builder_withdrawal_prefix_byte: 0x03, + builder_withdrawal_prefix_byte: 0xB0, /* * Time parameters @@ -1121,7 +1121,7 @@ impl ChainSpec { proposer_reorg_cutoff_bps: 1667, attestation_due_bps: 3333, attestation_due_bps_gloas: 2500, - payload_due_bps: 7500, + payload_due_bps: 5000, payload_attestation_due_bps: 7500, aggregate_due_bps: 6667, sync_message_due_bps: 3333, @@ -1286,7 +1286,7 @@ impl ChainSpec { gloas_fork_epoch: None, builder_payment_threshold_numerator: 6, builder_payment_threshold_denominator: 10, - min_builder_withdrawability_delay: Epoch::new(8192), + min_builder_withdrawability_delay: Epoch::new(64), churn_limit_quotient_gloas: option_wrapper(|| u64::checked_pow(2, 15)) .expect("calculation does not overflow"), consolidation_churn_limit_quotient: option_wrapper(|| u64::checked_pow(2, 16)) @@ -1531,7 +1531,7 @@ impl ChainSpec { bls_withdrawal_prefix_byte: 0x00, eth1_address_withdrawal_prefix_byte: 0x01, compounding_withdrawal_prefix_byte: 0x02, - builder_withdrawal_prefix_byte: 0x03, + builder_withdrawal_prefix_byte: 0xB0, /* * Time parameters @@ -1548,7 +1548,7 @@ impl ChainSpec { proposer_reorg_cutoff_bps: 1667, attestation_due_bps: 3333, attestation_due_bps_gloas: 2500, - payload_due_bps: 7500, + payload_due_bps: 5000, payload_attestation_due_bps: 7500, aggregate_due_bps: 6667, @@ -1713,7 +1713,7 @@ impl ChainSpec { gloas_fork_epoch: None, builder_payment_threshold_numerator: 6, builder_payment_threshold_denominator: 10, - min_builder_withdrawability_delay: Epoch::new(8192), + min_builder_withdrawability_delay: Epoch::new(64), churn_limit_quotient_gloas: option_wrapper(|| u64::checked_pow(2, 15)) .expect("calculation does not overflow"), consolidation_churn_limit_quotient: option_wrapper(|| u64::checked_pow(2, 16)) @@ -2406,7 +2406,7 @@ const fn default_attestation_due_bps_gloas() -> u64 { } const fn default_payload_due_bps() -> u64 { - 7500 + 5000 } const fn default_payload_attestation_due_bps() -> u64 { @@ -2426,7 +2426,7 @@ const fn default_contribution_due_bps() -> u64 { } const fn default_min_builder_withdrawability_delay() -> u64 { - 8192 + 64 } const fn default_churn_limit_quotient_gloas() -> u64 { diff --git a/consensus/types/src/core/eth_spec.rs b/consensus/types/src/core/eth_spec.rs index 9d794706476..bff5ea78000 100644 --- a/consensus/types/src/core/eth_spec.rs +++ b/consensus/types/src/core/eth_spec.rs @@ -541,7 +541,7 @@ impl EthSpec for MainnetEthSpec { type PtcWindowLength = U96; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH type MaxPayloadAttestations = U4; type MaxBuildersPerWithdrawalsSweep = U16384; - type MaxBuilderDepositRequestsPerPayload = U256; + type MaxBuilderDepositRequestsPerPayload = U64; type MaxBuilderExitRequestsPerPayload = U16; fn default_spec() -> ChainSpec { @@ -700,7 +700,7 @@ impl EthSpec for GnosisEthSpec { type PtcWindowLength = U48; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH type MaxPayloadAttestations = U2; type MaxBuildersPerWithdrawalsSweep = U16384; - type MaxBuilderDepositRequestsPerPayload = U256; + type MaxBuilderDepositRequestsPerPayload = U64; type MaxBuilderExitRequestsPerPayload = U16; fn default_spec() -> ChainSpec { diff --git a/testing/ef_tests/Makefile b/testing/ef_tests/Makefile index 1af7ca64338..cbc6cf5ca0f 100644 --- a/testing/ef_tests/Makefile +++ b/testing/ef_tests/Makefile @@ -1,6 +1,6 @@ # To download/extract nightly tests, run: # CONSENSUS_SPECS_TEST_VERSION=nightly make -CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.11 +CONSENSUS_SPECS_TEST_VERSION ?= v1.7.0-alpha.12 REPO_NAME := consensus-spec-tests OUTPUT_DIR := ./$(REPO_NAME) From dd0c13dd3fdd30f495fc6341922ca9b7ba2c98b3 Mon Sep 17 00:00:00 2001 From: Eitan Seri-Levi Date: Wed, 8 Jul 2026 12:09:17 +0300 Subject: [PATCH 02/10] Fix tests --- consensus/types/src/core/chain_spec.rs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/consensus/types/src/core/chain_spec.rs b/consensus/types/src/core/chain_spec.rs index be674aa2cf4..7ebccc0092b 100644 --- a/consensus/types/src/core/chain_spec.rs +++ b/consensus/types/src/core/chain_spec.rs @@ -1132,7 +1132,7 @@ impl ChainSpec { */ unaggregated_attestation_due: Duration::from_millis(3999), unaggregated_attestation_due_gloas: Duration::from_millis(3000), - payload_due: Duration::from_millis(9000), + payload_due: Duration::from_millis(6000), payload_attestation_due: Duration::from_millis(9000), aggregate_attestation_due: Duration::from_millis(8000), sync_message_due: Duration::from_millis(3999), @@ -1456,7 +1456,7 @@ impl ChainSpec { */ unaggregated_attestation_due: Duration::from_millis(1999), unaggregated_attestation_due_gloas: Duration::from_millis(1500), - payload_due: Duration::from_millis(4500), + payload_due: Duration::from_millis(3000), payload_attestation_due: Duration::from_millis(4500), aggregate_attestation_due: Duration::from_millis(4000), sync_message_due: Duration::from_millis(1999), @@ -1558,7 +1558,7 @@ impl ChainSpec { */ unaggregated_attestation_due: Duration::from_millis(1666), unaggregated_attestation_due_gloas: Duration::from_millis(1250), - payload_due: Duration::from_millis(3750), + payload_due: Duration::from_millis(2500), payload_attestation_due: Duration::from_millis(3750), aggregate_attestation_due: Duration::from_millis(3333), sync_message_due: Duration::from_millis(1666), @@ -3743,10 +3743,10 @@ mod yaml_tests { let tiny_due = custom_spec.get_unaggregated_attestation_due(); assert_eq!(tiny_due, Duration::from_millis(1)); // 12000 * 1 / 10000 = 1.2 -> 1 - // Test payload due (7500 bps = 75% of 12s = 9s) + // Test payload due (5000 bps = 50% of 12s = 6s) let spec = ChainSpec::mainnet().compute_derived_values::(); let payload_due = spec.get_payload_due(); - assert_eq!(payload_due, Duration::from_millis(9000)); // 12000 * 7500 / 10000 + assert_eq!(payload_due, Duration::from_millis(6000)); // 12000 * 5000 / 10000 // Test payload attestation due (7500 bps = 75% of 12s = 9s) let payload_att_due = spec.get_payload_attestation_due(); @@ -3787,8 +3787,8 @@ mod yaml_tests { Duration::from_millis(8000) ); - // Mainnet payload due: 12000ms slots, 7500 bps = 9000ms - assert_eq!(mainnet.get_payload_due(), Duration::from_millis(9000)); + // Mainnet payload due: 12000ms slots, 5000 bps = 6000ms + assert_eq!(mainnet.get_payload_due(), Duration::from_millis(6000)); assert_eq!( mainnet.get_payload_attestation_due(), Duration::from_millis(9000) @@ -3815,8 +3815,8 @@ mod yaml_tests { minimal.get_contribution_message_due(), Duration::from_millis(4000) ); - // Minimal payload due: 6000ms slots, 7500 bps = 4500ms - assert_eq!(minimal.get_payload_due(), Duration::from_millis(4500)); + // Minimal payload due: 6000ms slots, 5000 bps = 3000ms + assert_eq!(minimal.get_payload_due(), Duration::from_millis(3000)); assert_eq!( minimal.get_payload_attestation_due(), Duration::from_millis(4500) @@ -3843,8 +3843,8 @@ mod yaml_tests { gnosis.get_contribution_message_due(), Duration::from_millis(3333) ); - // Gnosis payload due: 5000ms slots, 7500 bps = 3750ms - assert_eq!(gnosis.get_payload_due(), Duration::from_millis(3750)); + // Gnosis payload due: 5000ms slots, 5000 bps = 2500ms + assert_eq!(gnosis.get_payload_due(), Duration::from_millis(2500)); assert_eq!( gnosis.get_payload_attestation_due(), Duration::from_millis(3750) From d8aa246506845664bc3e35036d655ceeaeebfcea Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Tue, 4 Aug 2026 23:10:23 -0700 Subject: [PATCH 03/10] final changes --- .../beacon_chain/src/block_verification.rs | 20 ++++ .../gossip_verified_envelope.rs | 106 ++++++++++++++++- .../src/payload_envelope_verification/mod.rs | 7 ++ .../beacon_chain/tests/block_verification.rs | 42 +++++++ .../lighthouse_network/src/rpc/protocol.rs | 2 +- .../lighthouse_network/src/types/pubsub.rs | 108 ++++++++++++++++++ .../gossip_methods.rs | 1 + .../src/per_block_processing.rs | 70 ++++++------ consensus/types/presets/gnosis/gloas.yaml | 20 ++-- consensus/types/presets/mainnet/gloas.yaml | 22 ++-- consensus/types/presets/minimal/gloas.yaml | 20 ++-- consensus/types/src/core/eth_spec.rs | 93 ++++++++++++--- consensus/types/src/core/preset.rs | 22 +++- testing/ef_tests/check_all_files_accessed.py | 6 - testing/ef_tests/src/handler.rs | 6 - testing/ef_tests/tests/tests.rs | 10 -- 16 files changed, 450 insertions(+), 105 deletions(-) diff --git a/beacon_node/beacon_chain/src/block_verification.rs b/beacon_node/beacon_chain/src/block_verification.rs index abbcba94b95..4901412e9c2 100644 --- a/beacon_node/beacon_chain/src/block_verification.rs +++ b/beacon_node/beacon_chain/src/block_verification.rs @@ -81,6 +81,9 @@ use slot_clock::SlotClock; use ssz::Encode; use ssz_derive::{Decode, Encode}; use state_processing::per_block_processing::errors::IntoWithIndex; +use state_processing::per_block_processing::{ + process_operations::verify_operation_list_lengths, verify_execution_request_list_lengths, +}; use state_processing::{ AllCaches, BlockProcessingError, BlockSignatureStrategy, ConsensusContext, SlotProcessingError, VerifyBlockRoot, @@ -896,6 +899,23 @@ impl GossipVerifiedBlock { } } + if let Ok(parent_execution_requests) = block.message().body().parent_execution_requests() { + verify_operation_list_lengths(block.message().body()) + .map_err(BlockError::PerBlockProcessingError)?; + verify_execution_request_list_lengths(parent_execution_requests) + .map_err(BlockError::PerBlockProcessingError)?; + let deposits_len = block.message().body().deposits().len(); + if deposits_len > 0 { + return Err(BlockError::PerBlockProcessingError( + BlockProcessingError::OperationListTooLong { + kind: "deposits", + length: deposits_len, + max: 0, + }, + )); + } + } + let block_root = get_block_header_root(block_header); // Do not gossip a block from a finalized slot. diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index 46b30f5642d..9ef37318480 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -73,6 +73,40 @@ pub(crate) fn verify_envelope_consistency( }); } + let requests = &envelope.execution_requests; + let length_checks: [(&str, usize, usize); 5] = [ + ( + "withdrawal_requests", + requests.withdrawals.len(), + E::max_withdrawal_requests_per_payload(), + ), + ( + "consolidation_requests", + requests.consolidations.len(), + E::max_consolidation_requests_per_payload(), + ), + ( + "builder_deposit_requests", + requests.builder_deposits.len(), + E::max_builder_deposit_requests_per_payload(), + ), + ( + "builder_exit_requests", + requests.builder_exits.len(), + E::max_builder_exit_requests_per_payload(), + ), + ( + "withdrawals", + envelope.payload.withdrawals.len(), + E::max_withdrawals_per_payload(), + ), + ]; + for (kind, length, max) in length_checks { + if length > max { + return Err(EnvelopeError::OperationListTooLong { kind, length, max }); + } + } + Ok(()) } @@ -311,13 +345,13 @@ impl BeaconChain { mod tests { use std::marker::PhantomData; - use bls::Signature; + use bls::{PublicKeyBytes, Signature}; use ssz_types::ProgressiveVariableList; use types::{ - BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, Eth1Data, ExecutionBlockHash, - ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, - ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, - SignedExecutionPayloadBid, Slot, SyncAggregate, + Address, BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BuilderExitRequest, Eth1Data, + EthSpec, ExecutionBlockHash, ExecutionPayloadBid, ExecutionPayloadEnvelope, + ExecutionPayloadGloas, ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, + SignedBeaconBlock, SignedExecutionPayloadBid, Slot, SyncAggregate, Withdrawal, }; use super::verify_envelope_consistency; @@ -457,4 +491,66 @@ mod tests { Err(EnvelopeError::BlockHashMismatch { .. }) )); } + + #[test] + fn test_payload_withdrawals_over_limit() { + let slot = Slot::new(10); + let builder_index = 1; + let block_hash = ExecutionBlockHash::repeat_byte(0xaa); + + let mut envelope = make_envelope(slot, builder_index, block_hash); + let block = make_block(slot); + let bid = make_bid(builder_index, block_hash); + + let withdrawal = Withdrawal { + index: 0, + validator_index: 0, + address: Address::ZERO, + amount: 0, + }; + let max = E::max_withdrawals_per_payload(); + envelope.payload.withdrawals = ProgressiveVariableList::new(vec![withdrawal.clone(); max]); + assert!(verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)).is_ok()); + + envelope.payload.withdrawals = ProgressiveVariableList::new(vec![withdrawal; max + 1]); + let result = verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)); + assert!(matches!( + result, + Err(EnvelopeError::OperationListTooLong { + kind: "withdrawals", + .. + }) + )); + } + + #[test] + fn test_execution_requests_over_limit() { + let slot = Slot::new(10); + let builder_index = 1; + let block_hash = ExecutionBlockHash::repeat_byte(0xaa); + + let mut envelope = make_envelope(slot, builder_index, block_hash); + let block = make_block(slot); + let bid = make_bid(builder_index, block_hash); + + let exit = BuilderExitRequest { + source_address: Address::ZERO, + pubkey: PublicKeyBytes::empty(), + }; + let max = E::max_builder_exit_requests_per_payload(); + envelope.execution_requests.builder_exits = + ProgressiveVariableList::new(vec![exit.clone(); max]); + assert!(verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)).is_ok()); + + envelope.execution_requests.builder_exits = + ProgressiveVariableList::new(vec![exit; max + 1]); + let result = verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)); + assert!(matches!( + result, + Err(EnvelopeError::OperationListTooLong { + kind: "builder_exit_requests", + .. + }) + )); + } } diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs index 1dc84184203..634f5ab3b5d 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/mod.rs @@ -224,6 +224,12 @@ pub enum EnvelopeError { payload_slot: Slot, latest_finalized_slot: Slot, }, + /// An envelope list exceeds its spec limit + OperationListTooLong { + kind: &'static str, + length: usize, + max: usize, + }, /// Some Beacon Chain Error BeaconChainError(Box), /// Some Beacon State error @@ -259,6 +265,7 @@ impl EnvelopeError { | EnvelopeError::BlockHashMismatch { .. } | EnvelopeError::UnknownValidator { .. } | EnvelopeError::IncorrectBlockProposer { .. } + | EnvelopeError::OperationListTooLong { .. } | EnvelopeError::EnvelopeProcessingError(_) => true, EnvelopeError::ExecutionPayloadError(e) => e.penalize_peer(), EnvelopeError::BlockRootUnknown { .. } diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 8e1918b061c..9d99e7b8713 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -1546,6 +1546,48 @@ async fn block_gossip_verification() { "should not import a block with higher blob_kzg_commitment length than the max_blobs at epoch" ); } + + /* + * This test ensures that: + * + * [New in Gloas:EIP7688] We do not accept blocks whose progressive operation lists exceed + * their spec limits. + */ + let (mut block, signature) = chain_segment[block_index] + .beacon_block + .as_ref() + .clone() + .deconstruct(); + + if let BeaconBlock::Gloas(gloas_block) = &mut block { + let deposit = Deposit { + proof: ssz_types::FixedVector::default(), + data: DepositData { + pubkey: bls::PublicKeyBytes::empty(), + withdrawal_credentials: Hash256::ZERO, + amount: 0, + signature: bls::SignatureBytes::empty(), + }, + }; + gloas_block.body.deposits = ssz_types::ProgressiveVariableList::new(vec![deposit]); + assert!( + matches!( + unwrap_err( + harness + .chain + .verify_block_for_gossip(Arc::new(SignedBeaconBlock::from_block( + block, signature + ))) + .await + ), + BlockError::PerBlockProcessingError(BlockProcessingError::OperationListTooLong { + kind: "deposits", + .. + }) + ), + "should not accept a gloas block with a non-empty deposits list" + ); + } } async fn verify_and_process_gossip_data_sidecars( diff --git a/beacon_node/lighthouse_network/src/rpc/protocol.rs b/beacon_node/lighthouse_network/src/rpc/protocol.rs index da975195fe8..4ae69b5583c 100644 --- a/beacon_node/lighthouse_network/src/rpc/protocol.rs +++ b/beacon_node/lighthouse_network/src/rpc/protocol.rs @@ -734,7 +734,7 @@ pub fn rpc_data_column_limits( if fork_name.gloas_enabled() { RpcLimits::new( DataColumnSidecarGloas::::min_size(), - DataColumnSidecarFulu::::max_size(max_blobs), + E::max_data_column_sidecar_size(), ) } else { RpcLimits::new( diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index 274df91b139..5c65016a61a 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -205,6 +205,13 @@ impl PubsubMessage { // SSZ bytes but different hash tree roots, so the variant must be // chosen by fork. if fork_name.gloas_enabled() { + if data.len() > E::max_signed_aggregate_and_proof_size() { + return Err(format!( + "SignedAggregateAndProof size {} exceeds MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE {}", + data.len(), + E::max_signed_aggregate_and_proof_size() + )); + } SignedAggregateAndProof::Gloas( SignedAggregateAndProofGloas::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?, @@ -292,6 +299,15 @@ impl PubsubMessage { GossipKind::DataColumnSidecar(subnet_id) => { match fork_context.get_fork_from_context_bytes(gossip_topic.fork_digest) { Some(fork) if fork.fulu_enabled() => { + if fork.gloas_enabled() + && data.len() > E::max_data_column_sidecar_size() + { + return Err(format!( + "DataColumnSidecar size {} exceeds MAX_DATA_COLUMN_SIDECAR_SIZE {}", + data.len(), + E::max_data_column_sidecar_size() + )); + } let col_sidecar = Arc::new( DataColumnSidecar::from_ssz_bytes_for_fork(data, *fork) .map_err(|e| format!("{:?}", e))?, @@ -324,6 +340,13 @@ impl PubsubMessage { Some(&fork_name) => { // [Modified in Gloas:EIP7688] see `BeaconAggregateAndProof` above. if fork_name.gloas_enabled() { + if data.len() > E::max_attester_slashing_size() { + return Err(format!( + "AttesterSlashing size {} exceeds MAX_ATTESTER_SLASHING_SIZE {}", + data.len(), + E::max_attester_slashing_size() + )); + } AttesterSlashing::Gloas( AttesterSlashingGloas::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?, @@ -381,6 +404,13 @@ impl PubsubMessage { ))) } GossipKind::ExecutionPayloadBid => { + if data.len() > E::max_signed_execution_payload_bid_size() { + return Err(format!( + "SignedExecutionPayloadBid size {} exceeds MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE {}", + data.len(), + E::max_signed_execution_payload_bid_size() + )); + } let execution_payload_bid = SignedExecutionPayloadBid::from_ssz_bytes(data) .map_err(|e| format!("{:?}", e))?; Ok(PubsubMessage::ExecutionPayloadBid(Box::new( @@ -585,3 +615,81 @@ impl std::fmt::Display for PubsubMessage { } } } + +#[cfg(test)] +mod tests { + use super::*; + use types::{Epoch, EthSpec, MainnetEthSpec, Slot, data::DataColumnSubnetId}; + + type E = MainnetEthSpec; + + fn gloas_fork_context() -> ForkContext { + let mut spec = E::default_spec(); + spec.altair_fork_epoch = Some(Epoch::new(0)); + spec.bellatrix_fork_epoch = Some(Epoch::new(0)); + spec.capella_fork_epoch = Some(Epoch::new(0)); + spec.deneb_fork_epoch = Some(Epoch::new(0)); + spec.electra_fork_epoch = Some(Epoch::new(0)); + spec.fulu_fork_epoch = Some(Epoch::new(0)); + spec.gloas_fork_epoch = Some(Epoch::new(0)); + ForkContext::new::(Slot::new(0), Hash256::ZERO, &spec) + } + + fn decode_oversized(kind: GossipKind, size: usize) -> Result, String> { + let fork_context = gloas_fork_context(); + let topic = GossipTopic::new( + kind, + GossipEncoding::default(), + fork_context.current_fork_digest(), + ); + let topic_hash = TopicHash::from_raw(String::from(topic)); + let data = vec![0u8; size]; + PubsubMessage::decode(&topic_hash, &data, &fork_context) + } + + #[test] + fn gloas_aggregate_and_proof_size_bound() { + let max = E::max_signed_aggregate_and_proof_size(); + let err = decode_oversized(GossipKind::BeaconAggregateAndProof, max + 1).unwrap_err(); + assert!(err.contains("MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE"), "{err}"); + let err = decode_oversized(GossipKind::BeaconAggregateAndProof, max).unwrap_err(); + assert!( + !err.contains("MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE"), + "{err}" + ); + } + + #[test] + fn gloas_attester_slashing_size_bound() { + let max = E::max_attester_slashing_size(); + let err = decode_oversized(GossipKind::AttesterSlashing, max + 1).unwrap_err(); + assert!(err.contains("MAX_ATTESTER_SLASHING_SIZE"), "{err}"); + let err = decode_oversized(GossipKind::AttesterSlashing, max).unwrap_err(); + assert!(!err.contains("MAX_ATTESTER_SLASHING_SIZE"), "{err}"); + } + + #[test] + fn gloas_data_column_sidecar_size_bound() { + let max = E::max_data_column_sidecar_size(); + let kind = GossipKind::DataColumnSidecar(DataColumnSubnetId::new(0)); + let err = decode_oversized(kind.clone(), max + 1).unwrap_err(); + assert!(err.contains("MAX_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); + let err = decode_oversized(kind, max).unwrap_err(); + assert!(!err.contains("MAX_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); + } + + #[test] + fn gloas_execution_payload_bid_size_bound() { + let max = E::max_signed_execution_payload_bid_size(); + let err = decode_oversized(GossipKind::ExecutionPayloadBid, max + 1).unwrap_err(); + assert!( + err.contains("MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE"), + "{err}" + ); + let err = decode_oversized(GossipKind::ExecutionPayloadBid, max).unwrap_err(); + assert!( + !err.contains("MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE"), + "{err}" + ); + } +} diff --git a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs index b2c1d374d1b..64de339169e 100644 --- a/beacon_node/network/src/network_beacon_processor/gossip_methods.rs +++ b/beacon_node/network/src/network_beacon_processor/gossip_methods.rs @@ -3779,6 +3779,7 @@ impl NetworkBeaconProcessor { | EnvelopeError::UnknownValidator { .. } | EnvelopeError::IncorrectBlockProposer { .. } | EnvelopeError::ExecutionPayloadError(_) + | EnvelopeError::OperationListTooLong { .. } | EnvelopeError::EnvelopeProcessingError(_) => { self.propagate_validation_result( message_id, diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 3637ffbe564..80456de5e6f 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -599,38 +599,7 @@ pub fn apply_parent_execution_payload( let parent_slot = parent_bid.slot; let parent_epoch = parent_slot.epoch(E::slots_per_epoch()); - // [New in Gloas:EIP7688] These request lists have no type-level bound, so enforce the spec's - // per-payload limits here. Deposit requests are deliberately unbounded (see the - // `deposit_requests_greater_than_electra_max` spec test). - // [New in Gloas:EIP8282] The builder request lists are checked as well. - let request_checks: [(&str, usize, usize); 4] = [ - ( - "withdrawal_requests", - requests.withdrawals.len(), - E::MaxWithdrawalRequestsPerPayload::to_usize(), - ), - ( - "consolidation_requests", - requests.consolidations.len(), - E::MaxConsolidationRequestsPerPayload::to_usize(), - ), - ( - "builder_deposit_requests", - requests.builder_deposits.len(), - E::MaxBuilderDepositRequestsPerPayload::to_usize(), - ), - ( - "builder_exit_requests", - requests.builder_exits.len(), - E::MaxBuilderExitRequestsPerPayload::to_usize(), - ), - ]; - for (kind, length, max) in request_checks { - block_verify!( - length <= max, - BlockProcessingError::OperationListTooLong { kind, length, max } - ); - } + verify_execution_request_list_lengths(requests)?; // Process execution requests from the parent's payload process_operations::process_deposit_requests(state, &requests.deposits, spec)?; @@ -677,6 +646,43 @@ pub fn apply_parent_execution_payload( Ok(()) } +/// Deposit requests are deliberately unbounded (see the `deposit_requests_greater_than_electra_max` +/// spec test). +pub fn verify_execution_request_list_lengths( + requests: &ExecutionRequestsGloas, +) -> Result<(), BlockProcessingError> { + let request_checks: [(&str, usize, usize); 4] = [ + ( + "withdrawal_requests", + requests.withdrawals.len(), + E::MaxWithdrawalRequestsPerPayload::to_usize(), + ), + ( + "consolidation_requests", + requests.consolidations.len(), + E::MaxConsolidationRequestsPerPayload::to_usize(), + ), + ( + "builder_deposit_requests", + requests.builder_deposits.len(), + E::MaxBuilderDepositRequestsPerPayload::to_usize(), + ), + ( + "builder_exit_requests", + requests.builder_exits.len(), + E::MaxBuilderExitRequestsPerPayload::to_usize(), + ), + ]; + for (kind, length, max) in request_checks { + block_verify!( + length <= max, + BlockProcessingError::OperationListTooLong { kind, length, max } + ); + } + + Ok(()) +} + /// Spec: `settle_builder_payment`. /// /// Moves a pending payment from `builder_pending_payments[payment_index]` into diff --git a/consensus/types/presets/gnosis/gloas.yaml b/consensus/types/presets/gnosis/gloas.yaml index 6b1e6625a59..95d09f9185b 100644 --- a/consensus/types/presets/gnosis/gloas.yaml +++ b/consensus/types/presets/gnosis/gloas.yaml @@ -10,13 +10,6 @@ PTC_SIZE: 512 # 2**1 (= 2) attestations MAX_PAYLOAD_ATTESTATIONS: 2 -# State list lengths -# --------------------------------------------------------------- -# 2**40 (= 1,099,511,627,776) builder spots -BUILDER_REGISTRY_LIMIT: 1099511627776 -# 2**20 (= 1,048,576) builder pending withdrawals -BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 - # Withdrawals processing # --------------------------------------------------------------- # 2**14 (= 16,384) builders @@ -28,3 +21,16 @@ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64 # 2**4 (= 16) builder exit requests MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 + +# Type-specific SSZ bounds +# --------------------------------------------------------------- +# 16,829 bytes, ~16 KiB +MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE: 16829 +# 2,097,616 bytes, ~2 MiB +MAX_ATTESTER_SLASHING_SIZE: 2097616 +# 8,585,272 bytes, ~8 MiB +MAX_DATA_COLUMN_SIDECAR_SIZE: 8585272 +# 8,585,741 bytes, ~8 MiB +MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: 8585741 +# 196,932 bytes, ~192 KiB +MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: 196932 diff --git a/consensus/types/presets/mainnet/gloas.yaml b/consensus/types/presets/mainnet/gloas.yaml index b50a86833d1..c561e96b011 100644 --- a/consensus/types/presets/mainnet/gloas.yaml +++ b/consensus/types/presets/mainnet/gloas.yaml @@ -10,13 +10,6 @@ PTC_SIZE: 512 # 2**2 (= 4) attestations MAX_PAYLOAD_ATTESTATIONS: 4 -# State list lengths -# --------------------------------------------------------------- -# 2**40 (= 1,099,511,627,776) builder spots -BUILDER_REGISTRY_LIMIT: 1099511627776 -# 2**20 (= 1,048,576) builder pending withdrawals -BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 - # Withdrawals processing # --------------------------------------------------------------- # 2**14 (= 16,384) builders @@ -27,4 +20,17 @@ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16384 # 2**6 (= 64) builder deposit requests MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64 # 2**4 (= 16) builder exit requests -MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 \ No newline at end of file +MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 + +# Type-specific SSZ bounds +# --------------------------------------------------------------- +# 16,829 bytes, ~16 KiB +MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE: 16829 +# 2,097,616 bytes, ~2 MiB +MAX_ATTESTER_SLASHING_SIZE: 2097616 +# 8,585,272 bytes, ~8 MiB +MAX_DATA_COLUMN_SIDECAR_SIZE: 8585272 +# 8,585,741 bytes, ~8 MiB +MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: 8585741 +# 196,932 bytes, ~192 KiB +MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: 196932 \ No newline at end of file diff --git a/consensus/types/presets/minimal/gloas.yaml b/consensus/types/presets/minimal/gloas.yaml index bee0628e4ce..e3d9757c084 100644 --- a/consensus/types/presets/minimal/gloas.yaml +++ b/consensus/types/presets/minimal/gloas.yaml @@ -10,13 +10,6 @@ PTC_SIZE: 16 # 2**2 (= 4) attestations MAX_PAYLOAD_ATTESTATIONS: 4 -# State list lengths -# --------------------------------------------------------------- -# 2**40 (= 1,099,511,627,776) builder spots -BUILDER_REGISTRY_LIMIT: 1099511627776 -# 2**20 (= 1,048,576) builder pending withdrawals -BUILDER_PENDING_WITHDRAWALS_LIMIT: 1048576 - # Withdrawals processing # --------------------------------------------------------------- # [customized] 2**4 (= 16) builders @@ -28,3 +21,16 @@ MAX_BUILDERS_PER_WITHDRAWALS_SWEEP: 16 MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD: 64 # 2**4 (= 16) builder exit requests MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD: 16 + +# Type-specific SSZ bounds +# --------------------------------------------------------------- +# [customized] 1,462 bytes, ~1 KiB +MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE: 1462 +# [customized] 131,536 bytes, ~128 KiB +MAX_ATTESTER_SLASHING_SIZE: 131536 +# 8,585,272 bytes, ~8 MiB +MAX_DATA_COLUMN_SIDECAR_SIZE: 8585272 +# 8,585,741 bytes, ~8 MiB +MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE: 8585741 +# 196,932 bytes, ~192 KiB +MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE: 196932 diff --git a/consensus/types/src/core/eth_spec.rs b/consensus/types/src/core/eth_spec.rs index 1af5a1145b2..fd352df7c84 100644 --- a/consensus/types/src/core/eth_spec.rs +++ b/consensus/types/src/core/eth_spec.rs @@ -122,10 +122,6 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type CellsPerExtBlob: Unsigned + Clone + Sync + Send + Debug + PartialEq; type NumberOfColumns: Unsigned + Clone + Sync + Send + Debug + PartialEq; type ProposerLookaheadSlots: Unsigned + Clone + Sync + Send + Debug + PartialEq; - /* - * New in Gloas - */ - type BuilderRegistryLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; /* * Derived values (set these CAREFULLY) */ @@ -179,7 +175,6 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + type PtcWindowLength: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxPayloadAttestations: Unsigned + Clone + Sync + Send + Debug + PartialEq; type BuilderPendingPaymentsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; - type BuilderPendingWithdrawalsLimit: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxBuildersPerWithdrawalsSweep: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxBuilderDepositRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; type MaxBuilderExitRequestsPerPayload: Unsigned + Clone + Sync + Send + Debug + PartialEq; @@ -380,11 +375,6 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::BuilderPendingPaymentsLimit::to_usize() } - /// Returns the `BUILDER_PENDING_WITHDRAWALS_LIMIT` constant for this specification. - fn builder_pending_withdrawals_limit() -> usize { - Self::BuilderPendingWithdrawalsLimit::to_usize() - } - /// Returns the `MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD` constant for this specification. fn max_consolidation_requests_per_payload() -> usize { Self::MaxConsolidationRequestsPerPayload::to_usize() @@ -461,6 +451,21 @@ pub trait EthSpec: 'static + Default + Sync + Send + Clone + Debug + PartialEq + Self::MaxBuilderExitRequestsPerPayload::to_usize() } + /// Returns the `MAX_SIGNED_AGGREGATE_AND_PROOF_SIZE` constant for this specification. + fn max_signed_aggregate_and_proof_size() -> usize; + + /// Returns the `MAX_ATTESTER_SLASHING_SIZE` constant for this specification. + fn max_attester_slashing_size() -> usize; + + /// Returns the `MAX_DATA_COLUMN_SIDECAR_SIZE` constant for this specification. + fn max_data_column_sidecar_size() -> usize; + + /// Returns the `MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE` constant for this specification. + fn max_partial_data_column_sidecar_size() -> usize; + + /// Returns the `MAX_SIGNED_EXECUTION_PAYLOAD_BID_SIZE` constant for this specification. + fn max_signed_execution_payload_bid_size() -> usize; + /// Returns the `PAYLOAD_TIMELY_THRESHOLD` constant (PTC_SIZE / 2). fn payload_timely_threshold() -> usize { Self::PTCSize::to_usize() / 2 @@ -505,7 +510,6 @@ impl EthSpec for MainnetEthSpec { type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; type BuilderPendingPaymentsLimit = U64; // 2 * SLOTS_PER_EPOCH = 2 * 32 = 64 - type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -532,7 +536,6 @@ impl EthSpec for MainnetEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U64; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH - type BuilderRegistryLimit = U1099511627776; type SyncSubcommitteeSize = U128; // 512 committee size / 4 sync committee subnet count type MaxPendingAttestations = U4096; // 128 max attestations * 32 slots per epoch type SlotsPerEth1VotingPeriod = U2048; // 64 epochs * 32 slots per epoch @@ -562,6 +565,26 @@ impl EthSpec for MainnetEthSpec { fn spec_name() -> EthSpecId { EthSpecId::Mainnet } + + fn max_signed_aggregate_and_proof_size() -> usize { + 16829 + } + + fn max_attester_slashing_size() -> usize { + 2097616 + } + + fn max_data_column_sidecar_size() -> usize { + 8585272 + } + + fn max_partial_data_column_sidecar_size() -> usize { + 8585741 + } + + fn max_signed_execution_payload_bid_size() -> usize { + 196932 + } } /// Ethereum Foundation minimal spec, as defined in the eth2.0-specs repo. @@ -609,7 +632,6 @@ impl EthSpec for MinimalEthSpec { GenesisEpoch, HistoricalRootsLimit, ValidatorRegistryLimit, - BuilderPendingWithdrawalsLimit, MaxProposerSlashings, MaxAttesterSlashings, MaxAttestations, @@ -633,8 +655,7 @@ impl EthSpec for MinimalEthSpec { MaxPayloadAttestations, MaxBuilderDepositRequestsPerPayload, MaxBuilderExitRequestsPerPayload, - InclusionListCommitteeSize, - BuilderRegistryLimit + InclusionListCommitteeSize }); fn default_spec() -> ChainSpec { @@ -644,6 +665,26 @@ impl EthSpec for MinimalEthSpec { fn spec_name() -> EthSpecId { EthSpecId::Minimal } + + fn max_signed_aggregate_and_proof_size() -> usize { + 1462 + } + + fn max_attester_slashing_size() -> usize { + 131536 + } + + fn max_data_column_sidecar_size() -> usize { + 8585272 + } + + fn max_partial_data_column_sidecar_size() -> usize { + 8585741 + } + + fn max_signed_execution_payload_bid_size() -> usize { + 196932 + } } /// Gnosis Beacon Chain specifications. @@ -666,7 +707,6 @@ impl EthSpec for GnosisEthSpec { type HistoricalRootsLimit = U16777216; type ValidatorRegistryLimit = U1099511627776; type BuilderPendingPaymentsLimit = U32; // 2 * SLOTS_PER_EPOCH = 2 * 16 = 32 - type BuilderPendingWithdrawalsLimit = U1048576; type MaxProposerSlashings = U16; type MaxAttesterSlashings = U2; type MaxAttestations = U128; @@ -707,7 +747,6 @@ impl EthSpec for GnosisEthSpec { type CellsPerExtBlob = U128; type NumberOfColumns = U128; type ProposerLookaheadSlots = U32; // Derived from (MIN_SEED_LOOKAHEAD + 1) * SLOTS_PER_EPOCH - type BuilderRegistryLimit = U1099511627776; type PTCSize = U512; type PtcWindowLength = U48; // (2 + MIN_SEED_LOOKAHEAD) * SLOTS_PER_EPOCH type MaxPayloadAttestations = U2; @@ -723,6 +762,26 @@ impl EthSpec for GnosisEthSpec { fn spec_name() -> EthSpecId { EthSpecId::Gnosis } + + fn max_signed_aggregate_and_proof_size() -> usize { + 16829 + } + + fn max_attester_slashing_size() -> usize { + 2097616 + } + + fn max_data_column_sidecar_size() -> usize { + 8585272 + } + + fn max_partial_data_column_sidecar_size() -> usize { + 8585741 + } + + fn max_signed_execution_payload_bid_size() -> usize { + 196932 + } } #[cfg(test)] diff --git a/consensus/types/src/core/preset.rs b/consensus/types/src/core/preset.rs index 611c31d0a1f..43fa650e13c 100644 --- a/consensus/types/src/core/preset.rs +++ b/consensus/types/src/core/preset.rs @@ -337,15 +337,21 @@ pub struct GloasPreset { #[serde(with = "serde_utils::quoted_u64")] pub max_payload_attestations: u64, #[serde(with = "serde_utils::quoted_u64")] - pub builder_registry_limit: u64, - #[serde(with = "serde_utils::quoted_u64")] - pub builder_pending_withdrawals_limit: u64, - #[serde(with = "serde_utils::quoted_u64")] pub max_builders_per_withdrawals_sweep: u64, #[serde(with = "serde_utils::quoted_u64")] pub max_builder_deposit_requests_per_payload: u64, #[serde(with = "serde_utils::quoted_u64")] pub max_builder_exit_requests_per_payload: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_signed_aggregate_and_proof_size: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_attester_slashing_size: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_data_column_sidecar_size: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_partial_data_column_sidecar_size: u64, + #[serde(with = "serde_utils::quoted_u64")] + pub max_signed_execution_payload_bid_size: u64, } impl GloasPreset { @@ -353,13 +359,17 @@ impl GloasPreset { Self { ptc_size: E::ptc_size() as u64, max_payload_attestations: E::max_payload_attestations() as u64, - builder_registry_limit: E::BuilderRegistryLimit::to_u64(), - builder_pending_withdrawals_limit: E::builder_pending_withdrawals_limit() as u64, max_builders_per_withdrawals_sweep: E::max_builders_per_withdrawals_sweep() as u64, max_builder_deposit_requests_per_payload: E::max_builder_deposit_requests_per_payload() as u64, max_builder_exit_requests_per_payload: E::max_builder_exit_requests_per_payload() as u64, + max_signed_aggregate_and_proof_size: E::max_signed_aggregate_and_proof_size() as u64, + max_attester_slashing_size: E::max_attester_slashing_size() as u64, + max_data_column_sidecar_size: E::max_data_column_sidecar_size() as u64, + max_partial_data_column_sidecar_size: E::max_partial_data_column_sidecar_size() as u64, + max_signed_execution_payload_bid_size: E::max_signed_execution_payload_bid_size() + as u64, } } } diff --git a/testing/ef_tests/check_all_files_accessed.py b/testing/ef_tests/check_all_files_accessed.py index b1a402a07d4..29d504eb763 100755 --- a/testing/ef_tests/check_all_files_accessed.py +++ b/testing/ef_tests/check_all_files_accessed.py @@ -62,12 +62,6 @@ "tests/.*/gloas/ssz_static/ForkChoiceNode/.*", # TODO(gloas): the FCR handler disables Gloas until Gloas fast confirmation is supported. "tests/.*/gloas/fast_confirmation/.*", - # TODO(alpha.12): these tests are disabled until the v1.7.0-alpha.12 EIP-8282 changes are - # implemented. - "tests/.*/gloas/fork/.*", - "tests/.*/gloas/operations/parent_execution_payload/.*", - "tests/.*/gloas/operations/builder_deposit_request/.*", - "tests/.*/gloas/operations/builder_exit_request/.*", # Ignore full epoch tests for now (just test the sub-transitions). "tests/.*/.*/epoch_processing/.*/pre_epoch.ssz_snappy", "tests/.*/.*/epoch_processing/.*/post_epoch.ssz_snappy", diff --git a/testing/ef_tests/src/handler.rs b/testing/ef_tests/src/handler.rs index d0fc0149b4d..f6a5377a145 100644 --- a/testing/ef_tests/src/handler.rs +++ b/testing/ef_tests/src/handler.rs @@ -633,12 +633,6 @@ impl Handler for ForkHandler { fn handler_name(&self) -> String { "fork".into() } - - // TODO(alpha.12): enable Gloas once the fork-upgrade builder-deposit handling matches the - // v1.7.0-alpha.12 EIP-8282 behaviour. - fn is_enabled_for_fork(&self, fork_name: ForkName) -> bool { - Self::Case::is_enabled_for_fork(fork_name) && fork_name != ForkName::Gloas - } } #[derive(Educe)] diff --git a/testing/ef_tests/tests/tests.rs b/testing/ef_tests/tests/tests.rs index 0dc64cf7920..db6bdb5d2ce 100644 --- a/testing/ef_tests/tests/tests.rs +++ b/testing/ef_tests/tests/tests.rs @@ -99,11 +99,7 @@ fn operations_execution_payload_bid() { OperationsHandler::>::default().run(); } -// TODO(alpha.12): un-ignore once the remaining v1.7.0-alpha.12 EIP-8282 changes land (the -// minimal-preset `MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD` drop from 256 to 64, and the changed -// builder-slot reuse semantics), which are out of scope for the progressive SSZ PR. #[test] -#[ignore] fn operations_parent_execution_payload() { OperationsHandler::>::default().run(); OperationsHandler::>::default().run(); @@ -140,20 +136,14 @@ fn operations_consolidations() { OperationsHandler::::default().run(); } -// TODO(alpha.12): un-ignore once `process_builder_deposit_request` is updated to the -// v1.7.0-alpha.12 EIP-8282 behaviour, which is out of scope for the progressive SSZ PR. #[test] -#[ignore] #[cfg(not(feature = "fake_crypto"))] fn operations_builder_deposit_requests() { OperationsHandler::::default().run(); OperationsHandler::::default().run(); } -// TODO(alpha.12): un-ignore once `process_builder_exit_request` is updated to the -// v1.7.0-alpha.12 EIP-8282 behaviour, which is out of scope for the progressive SSZ PR. #[test] -#[ignore] fn operations_builder_exit_requests() { OperationsHandler::::default().run(); OperationsHandler::::default().run(); From 6b813a283300a5834594d9cf62d9ed752758a32d Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Tue, 4 Aug 2026 23:33:50 -0700 Subject: [PATCH 04/10] Clean up --- .../gossip_verified_envelope.rs | 65 ++++++++++--------- .../beacon_chain/tests/block_verification.rs | 3 +- .../src/per_block_processing.rs | 5 +- 3 files changed, 37 insertions(+), 36 deletions(-) diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index 9ef37318480..8456b1ec36a 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -74,37 +74,40 @@ pub(crate) fn verify_envelope_consistency( } let requests = &envelope.execution_requests; - let length_checks: [(&str, usize, usize); 5] = [ - ( - "withdrawal_requests", - requests.withdrawals.len(), - E::max_withdrawal_requests_per_payload(), - ), - ( - "consolidation_requests", - requests.consolidations.len(), - E::max_consolidation_requests_per_payload(), - ), - ( - "builder_deposit_requests", - requests.builder_deposits.len(), - E::max_builder_deposit_requests_per_payload(), - ), - ( - "builder_exit_requests", - requests.builder_exits.len(), - E::max_builder_exit_requests_per_payload(), - ), - ( - "withdrawals", - envelope.payload.withdrawals.len(), - E::max_withdrawals_per_payload(), - ), - ]; - for (kind, length, max) in length_checks { - if length > max { - return Err(EnvelopeError::OperationListTooLong { kind, length, max }); - } + if requests.withdrawals.len() > E::max_withdrawal_requests_per_payload() { + return Err(EnvelopeError::OperationListTooLong { + kind: "withdrawal_requests", + length: requests.withdrawals.len(), + max: E::max_withdrawal_requests_per_payload(), + }); + } + if requests.consolidations.len() > E::max_consolidation_requests_per_payload() { + return Err(EnvelopeError::OperationListTooLong { + kind: "consolidation_requests", + length: requests.consolidations.len(), + max: E::max_consolidation_requests_per_payload(), + }); + } + if requests.builder_deposits.len() > E::max_builder_deposit_requests_per_payload() { + return Err(EnvelopeError::OperationListTooLong { + kind: "builder_deposit_requests", + length: requests.builder_deposits.len(), + max: E::max_builder_deposit_requests_per_payload(), + }); + } + if requests.builder_exits.len() > E::max_builder_exit_requests_per_payload() { + return Err(EnvelopeError::OperationListTooLong { + kind: "builder_exit_requests", + length: requests.builder_exits.len(), + max: E::max_builder_exit_requests_per_payload(), + }); + } + if envelope.payload.withdrawals.len() > E::max_withdrawals_per_payload() { + return Err(EnvelopeError::OperationListTooLong { + kind: "withdrawals", + length: envelope.payload.withdrawals.len(), + max: E::max_withdrawals_per_payload(), + }); } Ok(()) diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 9d99e7b8713..1e1283c6223 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -1550,8 +1550,7 @@ async fn block_gossip_verification() { /* * This test ensures that: * - * [New in Gloas:EIP7688] We do not accept blocks whose progressive operation lists exceed - * their spec limits. + * We do not accept gloas blocks whose progressive operation lists exceed their spec limits. */ let (mut block, signature) = chain_segment[block_index] .beacon_block diff --git a/consensus/state_processing/src/per_block_processing.rs b/consensus/state_processing/src/per_block_processing.rs index 80456de5e6f..22ec9a9ed47 100644 --- a/consensus/state_processing/src/per_block_processing.rs +++ b/consensus/state_processing/src/per_block_processing.rs @@ -651,7 +651,7 @@ pub fn apply_parent_execution_payload( pub fn verify_execution_request_list_lengths( requests: &ExecutionRequestsGloas, ) -> Result<(), BlockProcessingError> { - let request_checks: [(&str, usize, usize); 4] = [ + let checks = [ ( "withdrawal_requests", requests.withdrawals.len(), @@ -673,13 +673,12 @@ pub fn verify_execution_request_list_lengths( E::MaxBuilderExitRequestsPerPayload::to_usize(), ), ]; - for (kind, length, max) in request_checks { + for (kind, length, max) in checks { block_verify!( length <= max, BlockProcessingError::OperationListTooLong { kind, length, max } ); } - Ok(()) } From 30ea172c2d8a97f3884ce68b6a04f315dc377ff2 Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Wed, 5 Aug 2026 05:33:35 -0700 Subject: [PATCH 05/10] Fix test --- .../gossip_verified_envelope.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index beb7aa543d3..0dc25924362 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -566,7 +566,6 @@ mod tests { let mut envelope = make_envelope(slot, builder_index, block_hash); let block = make_block(slot); - let bid = make_bid(builder_index, block_hash); let exit = BuilderExitRequest { source_address: Address::ZERO, @@ -575,6 +574,13 @@ mod tests { let max = E::max_builder_exit_requests_per_payload(); envelope.execution_requests.builder_exits = ProgressiveVariableList::new(vec![exit.clone(); max]); + + let bid = ExecutionPayloadBid { + builder_index, + block_hash, + execution_requests_root: envelope.execution_requests.tree_hash_root(), + ..ExecutionPayloadBid::default() + }; assert!(verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)).is_ok()); envelope.execution_requests.builder_exits = From dcd62c4e278634a46301bfcd69216214c6b950fe Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Wed, 5 Aug 2026 21:28:32 -0700 Subject: [PATCH 06/10] size bounds for partial data columns --- .../lighthouse_network/src/service/mod.rs | 2 +- .../lighthouse_network/src/types/pubsub.rs | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/beacon_node/lighthouse_network/src/service/mod.rs b/beacon_node/lighthouse_network/src/service/mod.rs index 5c4a97d820a..27a2b3a3787 100644 --- a/beacon_node/lighthouse_network/src/service/mod.rs +++ b/beacon_node/lighthouse_network/src/service/mod.rs @@ -1432,7 +1432,7 @@ impl Network { .ok()?; if let Some(message) = message { - match decode_partial::(&topic, &group_id, &message) { + match decode_partial::(&topic, &group_id, &message, &self.fork_context) { Err(error) => { debug!( topic = ?topic_hash, diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index 5c65016a61a..bd7640c252d 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -509,9 +509,29 @@ pub fn decode_partial( topic: &GossipTopic, group: &[u8], data: &[u8], + fork_context: &ForkContext, ) -> Result, String> { match topic.kind() { GossipKind::DataColumnSidecar(id) => { + match fork_context.get_fork_from_context_bytes(topic.fork_digest) { + Some(fork) if fork.fulu_enabled() => { + if fork.gloas_enabled() + && data.len() > E::max_partial_data_column_sidecar_size() + { + return Err(format!( + "PartialDataColumnSidecar size {} exceeds MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE {}", + data.len(), + E::max_partial_data_column_sidecar_size() + )); + } + } + Some(_) | None => { + return Err(format!( + "data_column_sidecar topic invalid for given fork digest {:?}", + topic.fork_digest + )); + } + } if group.first() != Some(&0) { return Err(format!("Unknown data column format: {:?}", group.first())); } @@ -678,6 +698,30 @@ mod tests { assert!(!err.contains("MAX_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); } + #[test] + fn gloas_partial_data_column_sidecar_size_bound() { + let fork_context = gloas_fork_context(); + let topic = GossipTopic::new( + GossipKind::DataColumnSidecar(DataColumnSubnetId::new(0)), + GossipEncoding::default(), + fork_context.current_fork_digest(), + ); + let group = { + let mut group = vec![0u8]; + group.extend_from_slice(Hash256::ZERO.as_slice()); + group + }; + let max = E::max_partial_data_column_sidecar_size(); + + let data = vec![0u8; max + 1]; + let err = decode_partial::(&topic, &group, &data, &fork_context).unwrap_err(); + assert!(err.contains("MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); + + let data = vec![0u8; max]; + let err = decode_partial::(&topic, &group, &data, &fork_context).unwrap_err(); + assert!(!err.contains("MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); + } + #[test] fn gloas_execution_payload_bid_size_bound() { let max = E::max_signed_execution_payload_bid_size(); From 31156a450c34181e24971ec4c27f07bd10be4fbf Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Wed, 5 Aug 2026 21:34:14 -0700 Subject: [PATCH 07/10] Add additional size limit test coverage --- .../gossip_verified_envelope.rs | 108 ++++++++++++++---- 1 file changed, 83 insertions(+), 25 deletions(-) diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index 0dc25924362..934b9302d99 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -359,13 +359,14 @@ impl BeaconChain { mod tests { use std::marker::PhantomData; - use bls::{PublicKeyBytes, Signature}; + use bls::{PublicKeyBytes, Signature, SignatureBytes}; use ssz_types::ProgressiveVariableList; use types::{ - Address, BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BuilderExitRequest, Eth1Data, - EthSpec, ExecutionBlockHash, ExecutionPayloadBid, ExecutionPayloadEnvelope, - ExecutionPayloadGloas, ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, - SignedBeaconBlock, SignedExecutionPayloadBid, Slot, SyncAggregate, Withdrawal, + Address, BeaconBlock, BeaconBlockBodyGloas, BeaconBlockGloas, BuilderDepositRequest, + BuilderExitRequest, ConsolidationRequest, Eth1Data, EthSpec, ExecutionBlockHash, + ExecutionPayloadBid, ExecutionPayloadEnvelope, ExecutionPayloadGloas, + ExecutionRequestsGloas, Graffiti, Hash256, MinimalEthSpec, SignedBeaconBlock, + SignedExecutionPayloadBid, Slot, SyncAggregate, Withdrawal, WithdrawalRequest, }; use super::verify_envelope_consistency; @@ -558,8 +559,11 @@ mod tests { )); } - #[test] - fn test_execution_requests_over_limit() { + fn assert_requests_list_bound( + kind: &'static str, + max: usize, + set_len: impl Fn(&mut ExecutionRequestsGloas, usize), + ) { let slot = Slot::new(10); let builder_index = 1; let block_hash = ExecutionBlockHash::repeat_byte(0xaa); @@ -567,31 +571,85 @@ mod tests { let mut envelope = make_envelope(slot, builder_index, block_hash); let block = make_block(slot); - let exit = BuilderExitRequest { - source_address: Address::ZERO, - pubkey: PublicKeyBytes::empty(), - }; - let max = E::max_builder_exit_requests_per_payload(); - envelope.execution_requests.builder_exits = - ProgressiveVariableList::new(vec![exit.clone(); max]); - + set_len(&mut envelope.execution_requests, max); let bid = ExecutionPayloadBid { builder_index, block_hash, execution_requests_root: envelope.execution_requests.tree_hash_root(), ..ExecutionPayloadBid::default() }; - assert!(verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)).is_ok()); + assert!( + verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)).is_ok(), + "{kind} at max should be accepted" + ); - envelope.execution_requests.builder_exits = - ProgressiveVariableList::new(vec![exit; max + 1]); + set_len(&mut envelope.execution_requests, max + 1); let result = verify_envelope_consistency::(&envelope, &block, &bid, Slot::new(0)); - assert!(matches!( - result, - Err(EnvelopeError::OperationListTooLong { - kind: "builder_exit_requests", - .. - }) - )); + assert!( + matches!( + result, + Err(EnvelopeError::OperationListTooLong { kind: k, .. }) if k == kind + ), + "{kind} over max should be rejected" + ); + } + + #[test] + fn test_execution_requests_over_limit() { + assert_requests_list_bound( + "withdrawal_requests", + E::max_withdrawal_requests_per_payload(), + |requests, len| { + let withdrawal_request = WithdrawalRequest { + source_address: Address::ZERO, + validator_pubkey: PublicKeyBytes::empty(), + amount: 0, + }; + requests.withdrawals = + ProgressiveVariableList::new(vec![withdrawal_request; len]); + }, + ); + + assert_requests_list_bound( + "consolidation_requests", + E::max_consolidation_requests_per_payload(), + |requests, len| { + let consolidation_request = ConsolidationRequest { + source_address: Address::ZERO, + source_pubkey: PublicKeyBytes::empty(), + target_pubkey: PublicKeyBytes::empty(), + }; + requests.consolidations = + ProgressiveVariableList::new(vec![consolidation_request; len]); + }, + ); + + assert_requests_list_bound( + "builder_deposit_requests", + E::max_builder_deposit_requests_per_payload(), + |requests, len| { + let builder_deposit_request = BuilderDepositRequest { + pubkey: PublicKeyBytes::empty(), + withdrawal_credentials: Hash256::ZERO, + amount: 0, + signature: SignatureBytes::empty(), + }; + requests.builder_deposits = + ProgressiveVariableList::new(vec![builder_deposit_request; len]); + }, + ); + + assert_requests_list_bound( + "builder_exit_requests", + E::max_builder_exit_requests_per_payload(), + |requests, len| { + let builder_exit_request = BuilderExitRequest { + source_address: Address::ZERO, + pubkey: PublicKeyBytes::empty(), + }; + requests.builder_exits = + ProgressiveVariableList::new(vec![builder_exit_request; len]); + }, + ); } } From 3468d5e072bae2c8f3c4bb17675ae8eec7d842c0 Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Wed, 5 Aug 2026 21:36:52 -0700 Subject: [PATCH 08/10] Update comment --- beacon_node/beacon_chain/tests/block_verification.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/beacon_node/beacon_chain/tests/block_verification.rs b/beacon_node/beacon_chain/tests/block_verification.rs index 4768433d2e9..495e1001f12 100644 --- a/beacon_node/beacon_chain/tests/block_verification.rs +++ b/beacon_node/beacon_chain/tests/block_verification.rs @@ -1551,7 +1551,8 @@ async fn block_gossip_verification() { /* * This test ensures that: * - * We do not accept gloas blocks whose progressive operation lists exceed their spec limits. + * We do not accept gloas blocks with a non-empty `deposits` list. Gloas removes legacy + * eth1 deposits, so the effective limit for this progressive list is zero. */ let (mut block, signature) = chain_segment[block_index] .beacon_block From 750400ca32dba7d6e8f32ad84dad83b4e23b3be4 Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Wed, 5 Aug 2026 21:45:11 -0700 Subject: [PATCH 09/10] Small spec update --- .../built_in_network_configs/mainnet/config.yaml | 6 ++++++ consensus/types/configs/mainnet.yaml | 6 ++++++ consensus/types/configs/minimal.yaml | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml index 02bf37cb551..8b08e96a0d4 100644 --- a/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml +++ b/common/eth2_network_config/built_in_network_configs/mainnet/config.yaml @@ -103,6 +103,8 @@ AGGREGATE_DUE_BPS_GLOAS: 5000 SYNC_MESSAGE_DUE_BPS_GLOAS: 2500 # 5000 basis points, 50% of SLOT_DURATION_MS CONTRIBUTION_DUE_BPS_GLOAS: 5000 +# 5000 basis points, 50% of SLOT_DURATION_MS +PAYLOAD_DUE_BPS: 5000 # 7500 basis points, 75% of SLOT_DURATION_MS PAYLOAD_ATTESTATION_DUE_BPS: 7500 @@ -227,3 +229,7 @@ BLOB_SCHEDULE: MAX_BLOBS_PER_BLOCK: 15 - EPOCH: 419072 # January 7, 2026, 01:01:11am UTC MAX_BLOBS_PER_BLOCK: 21 + +# Fast Confirmation Rule +# --------------------------------------------------------------- +CONFIRMATION_BYZANTINE_THRESHOLD: 25 diff --git a/consensus/types/configs/mainnet.yaml b/consensus/types/configs/mainnet.yaml index 8a31420b44b..e0c6a750506 100644 --- a/consensus/types/configs/mainnet.yaml +++ b/consensus/types/configs/mainnet.yaml @@ -101,6 +101,8 @@ AGGREGATE_DUE_BPS_GLOAS: 5000 SYNC_MESSAGE_DUE_BPS_GLOAS: 2500 # 5000 basis points, 50% of SLOT_DURATION_MS CONTRIBUTION_DUE_BPS_GLOAS: 5000 +# 5000 basis points, 50% of SLOT_DURATION_MS +PAYLOAD_DUE_BPS: 5000 # 7500 basis points, 75% of SLOT_DURATION_MS PAYLOAD_ATTESTATION_DUE_BPS: 7500 @@ -230,3 +232,7 @@ BLOB_SCHEDULE: MAX_BLOBS_PER_BLOCK: 15 - EPOCH: 419072 # January 7, 2026, 01:01:11am UTC MAX_BLOBS_PER_BLOCK: 21 + +# Fast Confirmation Rule +# --------------------------------------------------------------- +CONFIRMATION_BYZANTINE_THRESHOLD: 25 diff --git a/consensus/types/configs/minimal.yaml b/consensus/types/configs/minimal.yaml index fa7633fcceb..30575e8f50c 100644 --- a/consensus/types/configs/minimal.yaml +++ b/consensus/types/configs/minimal.yaml @@ -97,6 +97,8 @@ AGGREGATE_DUE_BPS_GLOAS: 5000 SYNC_MESSAGE_DUE_BPS_GLOAS: 2500 # 5000 basis points, 50% of SLOT_DURATION_MS CONTRIBUTION_DUE_BPS_GLOAS: 5000 +# 5000 basis points, 50% of SLOT_DURATION_MS +PAYLOAD_DUE_BPS: 5000 # 7500 basis points, 75% of SLOT_DURATION_MS PAYLOAD_ATTESTATION_DUE_BPS: 7500 @@ -223,3 +225,7 @@ MAX_BYTES_PER_INCLUSION_LIST: 8192 # --------------------------------------------------------------- BLOB_SCHEDULE: [] + +# Fast Confirmation Rule +# --------------------------------------------------------------- +CONFIRMATION_BYZANTINE_THRESHOLD: 25 From 0b9672d2cdf1b5fc68571745bcd244dbdf89fbf2 Mon Sep 17 00:00:00 2001 From: Eitan Seri- Levi Date: Wed, 5 Aug 2026 21:46:43 -0700 Subject: [PATCH 10/10] FMT --- .../gossip_verified_envelope.rs | 3 +-- beacon_node/lighthouse_network/src/types/pubsub.rs | 10 ++++++++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs index 934b9302d99..41254d1d5fe 100644 --- a/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs +++ b/beacon_node/beacon_chain/src/payload_envelope_verification/gossip_verified_envelope.rs @@ -605,8 +605,7 @@ mod tests { validator_pubkey: PublicKeyBytes::empty(), amount: 0, }; - requests.withdrawals = - ProgressiveVariableList::new(vec![withdrawal_request; len]); + requests.withdrawals = ProgressiveVariableList::new(vec![withdrawal_request; len]); }, ); diff --git a/beacon_node/lighthouse_network/src/types/pubsub.rs b/beacon_node/lighthouse_network/src/types/pubsub.rs index bd7640c252d..0f2c4b8c2e2 100644 --- a/beacon_node/lighthouse_network/src/types/pubsub.rs +++ b/beacon_node/lighthouse_network/src/types/pubsub.rs @@ -715,11 +715,17 @@ mod tests { let data = vec![0u8; max + 1]; let err = decode_partial::(&topic, &group, &data, &fork_context).unwrap_err(); - assert!(err.contains("MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); + assert!( + err.contains("MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE"), + "{err}" + ); let data = vec![0u8; max]; let err = decode_partial::(&topic, &group, &data, &fork_context).unwrap_err(); - assert!(!err.contains("MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE"), "{err}"); + assert!( + !err.contains("MAX_PARTIAL_DATA_COLUMN_SIDECAR_SIZE"), + "{err}" + ); } #[test]