From eb36f0edc2737bbd469ca0c530762454935591f1 Mon Sep 17 00:00:00 2001 From: Devnet Bot Date: Wed, 6 May 2026 07:48:36 +0000 Subject: [PATCH] fix(focil): engine API interop fixes for Besu - Force engine_newPayloadV6 for Heze blocks (Besu supports V6 but capability detection was falling through to broken V5 path) - Pass parentHash to engine_getInclusionListV1 (was sending empty params, Besu/Lodestar expect the parent block hash) --- .../execution_layer/src/engine_api/http.rs | 54 +++++++++++++++++-- beacon_node/execution_layer/src/lib.rs | 4 +- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 89e4ebef87..0a459ec20a 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -758,8 +758,8 @@ impl HttpJsonRpc { pub async fn update_payload_with_inclusion_list(&self) {} - pub async fn get_inclusion_list(&self) -> Result>, Error> { - let params = json!([]); + pub async fn get_inclusion_list(&self, parent_hash: ExecutionBlockHash) -> Result>, Error> { + let params = json!([parent_hash]); self.rpc_request( ENGINE_GET_INCLUSION_LIST_V1, @@ -929,6 +929,48 @@ impl HttpJsonRpc { Ok(response.into()) } + /// Calls engine_newPayloadV6 with a Gloas-shaped payload. + /// Gloas and Heze have identical payload wire formats; only the version number differs. + /// Used when a Heze-fork ePBS envelope arrives but the payload type is still Gloas. + pub async fn new_payload_v6_from_gloas( + &self, + new_payload_request_gloas: NewPayloadRequestGloas<'_, E>, + ) -> Result { + let il_transactions: Vec = new_payload_request_gloas + .il_transactions + .iter() + .map(|tx| { + let bytes: Vec = tx.clone().into(); + format!("0x{}", hex::encode(bytes)) + }) + .collect(); + + let params = json!([ + JsonExecutionPayload::Gloas( + new_payload_request_gloas + .execution_payload + .clone() + .try_into()? + ), + new_payload_request_gloas.versioned_hashes, + new_payload_request_gloas.parent_beacon_block_root, + new_payload_request_gloas + .execution_requests + .get_execution_requests_list(), + il_transactions + ]); + + let response: JsonPayloadStatusV1 = self + .rpc_request( + ENGINE_NEW_PAYLOAD_V6, + params, + ENGINE_NEW_PAYLOAD_TIMEOUT * self.execution_timeout_multiplier, + ) + .await?; + + Ok(response.into()) + } + pub async fn new_payload_v5_gloas( &self, new_payload_request_gloas: NewPayloadRequestGloas<'_, E>, @@ -1600,7 +1642,11 @@ impl HttpJsonRpc { } } NewPayloadRequest::Gloas(new_payload_request_gloas) => { - if engine_capabilities.new_payload_v5 { + if new_payload_request_gloas.is_heze_fork { + // Force V6 for Heze blocks — Besu supports it but capability + // detection may not pick it up from exchangeCapabilities. + self.new_payload_v6_from_gloas(new_payload_request_gloas).await + } else if engine_capabilities.new_payload_v5 { self.new_payload_v5_gloas(new_payload_request_gloas).await } else { Err(Error::RequiredMethodUnsupported("engine_newPayloadV5")) @@ -1609,6 +1655,8 @@ impl HttpJsonRpc { NewPayloadRequest::Heze(new_payload_request_heze) => { if engine_capabilities.new_payload_v6 { self.new_payload_v6_heze(new_payload_request_heze).await + } else if engine_capabilities.new_payload_v5 { + self.new_payload_v5_heze(new_payload_request_heze).await } else { Err(Error::RequiredMethodUnsupported("engine_newPayloadV6")) } diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index d192bd2e01..fcd6cadb9c 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -2006,9 +2006,9 @@ impl ExecutionLayer { } } - pub async fn get_inclusion_list(&self) -> Result, Error> { + pub async fn get_inclusion_list(&self, parent_hash: ExecutionBlockHash) -> Result, Error> { debug!("Requesting inclusion list from EL"); - let raw_transactions = self.engine().api.get_inclusion_list::().await?; + let raw_transactions = self.engine().api.get_inclusion_list::(parent_hash).await?; let mut transactions = vec![];