From 8dbfd6a15de31bf98b18dfc2cb7159dd8c2eacfa Mon Sep 17 00:00:00 2001 From: Barnabas Busa Date: Tue, 25 Aug 2026 16:08:14 +0200 Subject: [PATCH] Validate bid gas limit against the payload at bid.parent_block_hash validate_execution_payload_bid derived parent_gas_limit from the parent block's post-state latest_execution_payload_bid, i.e. the parent block's own bid, regardless of which payload the bid builds on. The spec keys parent_gas_limit on bid.parent_block_hash: a bid that builds on the parent's parent payload (parent payload withheld) must be checked against that payload's gas limit, which differs from the parent bid's gas limit whenever the gas limit is moving. Such bids were dropped with "bid gas_limit is not compatible with target_gas_limit" on gossip and via the publish REST endpoint (200 + Ignore, never broadcast). Resolve the chain link carrying the payload identified by bid.parent_block_hash (the existing execution_payload_locations index) and use the gas limit committed by that block: its bid post-Gloas, the payload itself pre-Gloas. Adds ExecutionPayload::gas_limit for the latter. --- fork_choice_store/src/store.rs | 30 ++++++++++++++++++++++++------ types/src/traits.rs | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/fork_choice_store/src/store.rs b/fork_choice_store/src/store.rs index 4547c19fc..4ffcc7ca3 100644 --- a/fork_choice_store/src/store.rs +++ b/fork_choice_store/src/store.rs @@ -2336,17 +2336,35 @@ impl> Store { }; // > the `bid.parent_block_hash` is the block hash of a known execution payload in fork choice - if !self - .execution_payload_locations - .contains_key(&bid.parent_block_hash) - { + let Some(parent_payload_chain_link) = + self.unfinalized_chain_link_by_execution_block_hash(bid.parent_block_hash) + else { return Ok(ExecutionPayloadBidAction::Ignore( "the `bid.parent_block_hash` is the block hash of a known execution payload in fork choice", )); - } + }; // > Check `is_gas_limit_target_compatible(parent_gas_limit, bid.gas_limit, target_gas_limit)` is True. - let parent_gas_limit = post_gloas_state.latest_execution_payload_bid().gas_limit; + // + // `parent_gas_limit` is the `gas_limit` of the execution payload identified by + // `bid.parent_block_hash`, which is committed to by the bid of the block that carries it + // (envelopes must match their bid's `gas_limit`). This is not necessarily the payload of + // `bid.parent_block_root`: a bid may build on the parent's parent payload when the parent + // block's payload was withheld. Pre-Gloas blocks carry the payload itself. + let parent_payload_block_body = parent_payload_chain_link.block.message().body(); + let Some(parent_gas_limit) = parent_payload_block_body + .with_payload_bid() + .map(|body| body.signed_execution_payload_bid().message.gas_limit) + .or_else(|| { + parent_payload_block_body + .with_execution_payload() + .map(|body| body.execution_payload().gas_limit()) + }) + else { + return Ok(ExecutionPayloadBidAction::Ignore( + "the `bid.parent_block_hash` is the block hash of a known execution payload in fork choice", + )); + }; if !predicates::is_gas_limit_target_compatible( parent_gas_limit, bid.gas_limit, diff --git a/types/src/traits.rs b/types/src/traits.rs index 3b8680871..fa2141acd 100644 --- a/types/src/traits.rs +++ b/types/src/traits.rs @@ -40,6 +40,7 @@ use crate::{ ExecutionPayload as BellatrixExecutionPayload, ExecutionPayloadHeader as BellatrixExecutionPayloadHeader, }, + primitives::Gas, }, cache::Cache, capella::{ @@ -1816,6 +1817,7 @@ pub trait ExecutionPayload: SszHash { fn block_hash(&self) -> ExecutionBlockHash; fn block_number(&self) -> Option; fn parent_hash(&self) -> ExecutionBlockHash; + fn gas_limit(&self) -> Gas; fn is_default_payload(&self) -> bool; fn to_header(&self) -> CombinedExecutionPayloadHeader

; @@ -1834,6 +1836,10 @@ impl ExecutionPayload

for BellatrixExecutionPayload

{ self.parent_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() } @@ -1856,6 +1862,10 @@ impl ExecutionPayload

for BellatrixExecutionPayloadHeader

{ self.parent_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() } @@ -1878,6 +1888,10 @@ impl ExecutionPayload

for CapellaExecutionPayload

{ self.parent_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() } @@ -1900,6 +1914,10 @@ impl ExecutionPayload

for CapellaExecutionPayloadHeader

{ self.parent_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() } @@ -1922,6 +1940,10 @@ impl ExecutionPayload

for DenebExecutionPayload

{ self.parent_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() } @@ -1944,6 +1966,10 @@ impl ExecutionPayload

for DenebExecutionPayloadHeader

{ self.parent_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() } @@ -1966,6 +1992,10 @@ impl ExecutionPayload

for ExecutionPayloadBid

{ self.parent_block_hash } + fn gas_limit(&self) -> Gas { + self.gas_limit + } + fn is_default_payload(&self) -> bool { self.is_default() }