diff --git a/beacon_node/execution_layer/src/engine_api.rs b/beacon_node/execution_layer/src/engine_api.rs index 048e232d567..52a58c88fec 100644 --- a/beacon_node/execution_layer/src/engine_api.rs +++ b/beacon_node/execution_layer/src/engine_api.rs @@ -2,10 +2,11 @@ use crate::engines::ForkchoiceState; use crate::http::{ ENGINE_FORKCHOICE_UPDATED_V1, ENGINE_FORKCHOICE_UPDATED_V2, ENGINE_FORKCHOICE_UPDATED_V3, ENGINE_FORKCHOICE_UPDATED_V4, ENGINE_GET_BLOBS_V2, ENGINE_GET_CLIENT_VERSION_V1, - ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, - ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, - ENGINE_GET_PAYLOAD_V5, ENGINE_GET_PAYLOAD_V6, ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, - ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, ENGINE_NEW_PAYLOAD_V5, + ENGINE_GET_INCLUSION_LIST_V1, ENGINE_GET_PAYLOAD_BODIES_BY_HASH_V1, + ENGINE_GET_PAYLOAD_BODIES_BY_RANGE_V1, ENGINE_GET_PAYLOAD_V1, ENGINE_GET_PAYLOAD_V2, + ENGINE_GET_PAYLOAD_V3, ENGINE_GET_PAYLOAD_V4, ENGINE_GET_PAYLOAD_V5, ENGINE_GET_PAYLOAD_V6, + ENGINE_NEW_PAYLOAD_V1, ENGINE_NEW_PAYLOAD_V2, ENGINE_NEW_PAYLOAD_V3, ENGINE_NEW_PAYLOAD_V4, + ENGINE_NEW_PAYLOAD_V5, }; use eth2::types::{ BlobsBundle, SsePayloadAttributes, SsePayloadAttributesV1, SsePayloadAttributesV2, @@ -614,6 +615,7 @@ pub struct EngineCapabilities { pub get_client_version_v1: bool, pub get_blobs_v2: bool, pub get_blobs_v3: bool, + pub get_inclusion_list_v1: bool, } impl EngineCapabilities { @@ -676,6 +678,9 @@ impl EngineCapabilities { if self.get_blobs_v2 { response.push(ENGINE_GET_BLOBS_V2); } + if self.get_inclusion_list_v1 { + response.push(ENGINE_GET_INCLUSION_LIST_V1); + } response } diff --git a/beacon_node/execution_layer/src/engine_api/http.rs b/beacon_node/execution_layer/src/engine_api/http.rs index 23510e1b0ee..4822d1c8fae 100644 --- a/beacon_node/execution_layer/src/engine_api/http.rs +++ b/beacon_node/execution_layer/src/engine_api/http.rs @@ -15,6 +15,7 @@ use std::sync::LazyLock; use tokio::sync::Mutex; use tracing::Span; use tracing_opentelemetry::OpenTelemetrySpanExt; +use types::ProgressiveTransactions; use std::time::{Duration, Instant}; @@ -67,6 +68,9 @@ pub const ENGINE_GET_BLOBS_V2: &str = "engine_getBlobsV2"; pub const ENGINE_GET_BLOBS_V3: &str = "engine_getBlobsV3"; pub const ENGINE_GET_BLOBS_TIMEOUT: Duration = Duration::from_secs(1); +pub const ENGINE_GET_INCLUSION_LIST_V1: &str = "engine_getInclusionListV1"; +pub const ENGINE_GET_INCLUSION_LIST_TIMEOUT: Duration = Duration::from_secs(1); + /// This error is returned during a `chainId` call by Geth. pub const EIP155_ERROR_STR: &str = "chain not synced beyond EIP-155 replay-protection fork block"; /// This code is returned by all clients when a method is not supported @@ -94,6 +98,7 @@ pub static LIGHTHOUSE_CAPABILITIES: &[&str] = &[ ENGINE_GET_CLIENT_VERSION_V1, ENGINE_GET_BLOBS_V2, ENGINE_GET_BLOBS_V3, + ENGINE_GET_INCLUSION_LIST_V1, ]; /// We opt to initialize the JsonClientVersionV1 rather than the ClientVersionV1 @@ -752,6 +757,21 @@ impl HttpJsonRpc { .await } + pub async fn get_inclusion_list_v1( + &self, + block_hash: ExecutionBlockHash, + ) -> Result { + let params = json!([block_hash]); + + self.rpc_request::( + ENGINE_GET_INCLUSION_LIST_V1, + params, + ENGINE_GET_INCLUSION_LIST_TIMEOUT * self.execution_timeout_multiplier, + ) + .await + .map(|response| response.0) + } + pub async fn get_block_by_number( &self, query: BlockByNumberQuery<'_>, @@ -1251,6 +1271,7 @@ impl HttpJsonRpc { get_client_version_v1: capabilities.contains(ENGINE_GET_CLIENT_VERSION_V1), get_blobs_v2: capabilities.contains(ENGINE_GET_BLOBS_V2), get_blobs_v3: capabilities.contains(ENGINE_GET_BLOBS_V3), + get_inclusion_list_v1: capabilities.contains(ENGINE_GET_INCLUSION_LIST_V1), }) } @@ -1529,7 +1550,7 @@ mod test { use super::*; use crate::test_utils::{DEFAULT_JWT_SECRET, MockServer}; use fixed_bytes::FixedBytesExtended; - use ssz_types::VariableList; + use ssz_types::{ProgressiveVariableList, VariableList}; use std::future::Future; use std::str::FromStr; use std::sync::Arc; @@ -1709,6 +1730,15 @@ mod test { txs } + fn generate_progressive_transactions(spec: &[usize]) -> ProgressiveTransactions { + let mut txs = ProgressiveTransactions::empty(); + for &num_bytes in spec { + txs.push(ProgressiveVariableList::new(vec![0; num_bytes])); + } + + txs + } + #[test] fn transaction_serde() { assert_transactions_serde::( @@ -1757,6 +1787,42 @@ mod test { ); } + fn assert_inclusion_list_serde( + name: &str, + as_obj: ProgressiveTransactions, + as_json: serde_json::Value, + ) { + assert_eq!( + serde_json::to_value(JsonInclusionListV1(as_obj.clone())).unwrap(), + as_json, + "encoding for {}", + name + ); + assert_eq!( + serde_json::from_value::(as_json) + .unwrap() + .0, + as_obj, + "decoding for {}", + name + ); + } + + #[test] + fn inclusion_list_serde() { + assert_inclusion_list_serde("empty", generate_progressive_transactions(&[]), json!([])); + assert_inclusion_list_serde( + "one empty tx", + generate_progressive_transactions(&[0]), + json!(["0x"]), + ); + assert_inclusion_list_serde( + "mixed bag", + generate_progressive_transactions(&[0, 1, 3, 0]), + json!(["0x", "0x00", "0x000000", "0x"]), + ); + } + #[tokio::test] async fn get_block_by_number_request() { Tester::new(true) @@ -1784,6 +1850,33 @@ mod test { .await; } + #[tokio::test] + async fn get_inclusion_list_v1_request() { + Tester::new(true) + .assert_request_equals( + |client| async move { + let _ = client + .get_inclusion_list_v1(ExecutionBlockHash::repeat_byte(1)) + .await; + }, + json!({ + "id": STATIC_ID, + "jsonrpc": JSONRPC_VERSION, + "method": ENGINE_GET_INCLUSION_LIST_V1, + "params": [HASH_01] + }), + ) + .await; + + Tester::new(false) + .assert_auth_failure(|client| async move { + client + .get_inclusion_list_v1(ExecutionBlockHash::repeat_byte(1)) + .await + }) + .await; + } + #[tokio::test] async fn forkchoice_updated_v1_with_payload_attributes_request() { Tester::new(true) diff --git a/beacon_node/execution_layer/src/engine_api/json_structures.rs b/beacon_node/execution_layer/src/engine_api/json_structures.rs index 34c8f72489f..b6f5f820034 100644 --- a/beacon_node/execution_layer/src/engine_api/json_structures.rs +++ b/beacon_node/execution_layer/src/engine_api/json_structures.rs @@ -1313,6 +1313,13 @@ impl TryFrom for ClientVersionV1 { } } +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] +#[serde(transparent)] +pub struct JsonInclusionListV1( + #[serde(with = "ssz_types::serde_utils::prog_list_of_hex_prog_var_list")] + pub ProgressiveTransactions, +); + #[cfg(test)] mod tests { use bls::{PublicKeyBytes, SignatureBytes}; diff --git a/beacon_node/execution_layer/src/lib.rs b/beacon_node/execution_layer/src/lib.rs index 5c94a5fd65a..944757809a0 100644 --- a/beacon_node/execution_layer/src/lib.rs +++ b/beacon_node/execution_layer/src/lib.rs @@ -48,7 +48,8 @@ use types::execution::BlockProductionVersion; use types::kzg_ext::{KzgCommitments, ProgressiveKzgCommitments}; use types::{ AbstractExecPayload, BlobsList, ExecutionPayloadDeneb, ExecutionRequests, - ExecutionRequestsElectra, ExecutionRequestsGloas, KzgProofs, SignedBlindedBeaconBlock, + ExecutionRequestsElectra, ExecutionRequestsGloas, KzgProofs, ProgressiveTransactions, + SignedBlindedBeaconBlock, }; use types::{ BeaconStateError, BlindedPayload, ChainSpec, Epoch, ExecPayload, ExecutionPayloadBellatrix, @@ -155,6 +156,7 @@ pub enum Error { ZeroLengthTransaction, PayloadBodiesByRangeNotSupported, GetBlobsNotSupported, + GetInclusionListNotSupported, InvalidJWTSecret(String), InvalidForkForPayload, InvalidPayloadBody(String), @@ -1776,6 +1778,23 @@ impl ExecutionLayer { } } + pub async fn get_inclusion_list_v1( + &self, + block_hash: ExecutionBlockHash, + ) -> Result { + let capabilities = self.get_engine_capabilities(None).await?; + + if capabilities.get_inclusion_list_v1 { + self.engine() + .request(|engine| async move { engine.api.get_inclusion_list_v1(block_hash).await }) + .await + .map_err(Box::new) + .map_err(Error::EngineError) + } else { + Err(Error::GetInclusionListNotSupported) + } + } + pub async fn get_block_by_number( &self, query: BlockByNumberQuery<'_>, diff --git a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs index 1fcff6806f2..e1dee7d85ed 100644 --- a/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs +++ b/beacon_node/execution_layer/src/test_utils/execution_block_generator.rs @@ -27,7 +27,7 @@ use types::{ Blob, ChainSpec, EthSpec, ExecutionBlockHash, ExecutionPayload, ExecutionPayloadBellatrix, ExecutionPayloadCapella, ExecutionPayloadDeneb, ExecutionPayloadElectra, ExecutionPayloadFulu, ExecutionPayloadGloas, ExecutionPayloadHeader, ExecutionPayloadHeze, ExecutionRequests, - ForkName, Hash256, KzgProofs, Transaction, Transactions, Uint256, + ForkName, Hash256, KzgProofs, ProgressiveTransactions, Transaction, Transactions, Uint256, }; const TEST_BLOB_BUNDLE: &[u8] = include_bytes!("fixtures/mainnet/test_blobs_bundle.ssz"); @@ -178,6 +178,11 @@ pub struct ExecutionBlockGenerator { /// execution requests with the generated payload ID. next_execution_requests: Option>, generate_blobs: bool, + /* + * Inclusion lists (heze+) + */ + /// The transactions returned by `getInclusionList` for any known block. + inclusion_list: ProgressiveTransactions, } fn make_rng() -> Arc> { @@ -221,6 +226,7 @@ impl ExecutionBlockGenerator { execution_requests: <_>::default(), next_execution_requests: None, generate_blobs: true, + inclusion_list: <_>::default(), }; generator.insert_pow_block(0).unwrap(); @@ -489,6 +495,21 @@ impl ExecutionBlockGenerator { self.next_execution_requests = Some(requests); } + /// Return the configured inclusion list transactions for the provided block. + pub fn get_inclusion_list( + &self, + block_hash: ExecutionBlockHash, + ) -> Option { + self.blocks + .contains_key(&block_hash) + .then(|| self.inclusion_list.clone()) + } + + /// Set the transactions returned by `getInclusionList`. + pub fn set_inclusion_list(&mut self, transactions: ProgressiveTransactions) { + self.inclusion_list = transactions; + } + /// Look up a blob and proof by versioned hash across all stored bundles. pub fn get_blob_and_proof(&self, versioned_hash: &Hash256) -> Option> { self.blobs_bundles diff --git a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs index 2f4ad9fdaa3..39ab39879a8 100644 --- a/beacon_node/execution_layer/src/test_utils/handle_rpc.rs +++ b/beacon_node/execution_layer/src/test_utils/handle_rpc.rs @@ -528,6 +528,22 @@ pub async fn handle_rpc( let response: Option>> = results.into_iter().collect(); Ok(serde_json::to_value(response).unwrap()) } + ENGINE_GET_INCLUSION_LIST_V1 => { + let block_hash = get_param::(params, 0) + .map_err(|s| (s, BAD_PARAMS_ERROR_CODE))?; + let transactions = ctx + .execution_block_generator + .read() + .get_inclusion_list(block_hash) + .ok_or_else(|| { + ( + format!("no block for hash {:?}", block_hash), + UNKNOWN_PAYLOAD_ERROR_CODE, + ) + })?; + + Ok(serde_json::to_value(JsonInclusionListV1(transactions)).unwrap()) + } ENGINE_FORKCHOICE_UPDATED_V1 | ENGINE_FORKCHOICE_UPDATED_V2 | ENGINE_FORKCHOICE_UPDATED_V3 diff --git a/beacon_node/execution_layer/src/test_utils/mod.rs b/beacon_node/execution_layer/src/test_utils/mod.rs index 88f46cf8e77..82b55551367 100644 --- a/beacon_node/execution_layer/src/test_utils/mod.rs +++ b/beacon_node/execution_layer/src/test_utils/mod.rs @@ -59,6 +59,7 @@ pub const DEFAULT_ENGINE_CAPABILITIES: EngineCapabilities = EngineCapabilities { get_client_version_v1: true, get_blobs_v2: true, get_blobs_v3: true, + get_inclusion_list_v1: true, }; pub static DEFAULT_CLIENT_VERSION: LazyLock =