diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index 4dd1ae63e1..90877a5809 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -2,11 +2,11 @@ use crate::{network::NetworkTestContext, payload::PayloadTestContext, rpc::RpcTe use alloy_consensus::{transaction::TxHashRef, BlockHeader}; use alloy_eips::BlockId; use alloy_primitives::{BlockHash, BlockNumber, Bytes, Sealable, B256}; -use alloy_rpc_types_engine::ForkchoiceState; +use alloy_rpc_types_engine::{ExecutionPayloadEnvelopeV5, ForkchoiceState}; use alloy_rpc_types_eth::BlockNumberOrTag; use eyre::Ok; use futures_util::Future; -use jsonrpsee::http_client::HttpClient; +use jsonrpsee::{core::client::ClientT, http_client::HttpClient}; use reth_chainspec::EthereumHardforks; use reth_network_api::test_utils::PeersHandleProvider; use reth_node_api::{ @@ -20,6 +20,7 @@ use reth_provider::{ BlockReader, BlockReaderIdExt, CanonStateNotificationStream, CanonStateSubscriptions, HeaderProvider, StageCheckpointReader, }; +use reth_rpc_api::TestingBuildBlockRequestV1; use reth_rpc_builder::auth::AuthServerHandle; use reth_rpc_eth_api::helpers::{EthApiSpec, EthTransactions, TraceExt}; use reth_stages_types::StageId; @@ -319,4 +320,20 @@ where Ok(crate::testsuite::NodeClient::new_with_beacon_engine(rpc, auth, url, beacon_handle)) } + + /// Calls the `testing_buildBlockV1` RPC on this node. + /// + /// This endpoint builds a block using the provided parent, payload attributes, and + /// transactions. Requires the `Testing` RPC module to be enabled. + pub async fn testing_build_block_v1( + &self, + request: TestingBuildBlockRequestV1, + ) -> eyre::Result { + let client = + self.rpc_client().ok_or_else(|| eyre::eyre!("HTTP RPC client not available"))?; + + let res: ExecutionPayloadEnvelopeV5 = + client.request("testing_buildBlockV1", [request]).await?; + eyre::Ok(res) + } } diff --git a/crates/ethereum/node/tests/e2e/eth.rs b/crates/ethereum/node/tests/e2e/eth.rs index 1865890482..2fcfa26802 100644 --- a/crates/ethereum/node/tests/e2e/eth.rs +++ b/crates/ethereum/node/tests/e2e/eth.rs @@ -1,6 +1,10 @@ use crate::utils::eth_payload_attributes; +use alloy_eips::eip7685::RequestsOrHash; use alloy_genesis::Genesis; -use reth_chainspec::{ChainSpecBuilder, MAINNET}; +use alloy_primitives::{Address, B256}; +use alloy_rpc_types_engine::{PayloadAttributes, PayloadStatusEnum}; +use jsonrpsee_core::client::ClientT; +use reth_chainspec::{ChainSpecBuilder, EthChainSpec, MAINNET}; use reth_e2e_test_utils::{ node::NodeTestContext, setup, transaction::TransactionTestContext, wallet::Wallet, }; @@ -8,6 +12,7 @@ use reth_node_builder::{NodeBuilder, NodeHandle}; use reth_node_core::{args::RpcServerArgs, node_config::NodeConfig}; use reth_node_ethereum::EthereumNode; use reth_provider::BlockNumReader; +use reth_rpc_api::TestingBuildBlockRequestV1; use reth_tasks::TaskManager; use std::sync::Arc; @@ -180,3 +185,74 @@ async fn test_engine_graceful_shutdown() -> eyre::Result<()> { Ok(()) } + +#[tokio::test] +async fn test_testing_build_block_v1_osaka() -> eyre::Result<()> { + reth_tracing::init_test_tracing(); + let tasks = TaskManager::current(); + let exec = tasks.executor(); + + let genesis: Genesis = serde_json::from_str(include_str!("../assets/genesis.json")).unwrap(); + let chain_spec = Arc::new( + ChainSpecBuilder::default().chain(MAINNET.chain).genesis(genesis).osaka_activated().build(), + ); + let genesis_hash = chain_spec.genesis_hash(); + + let node_config = + NodeConfig::test().with_chain(chain_spec.clone()).with_unused_ports().with_rpc( + RpcServerArgs::default() + .with_unused_ports() + .with_http() + .with_http_api(reth_rpc_server_types::RpcModuleSelection::All), + ); + + let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config) + .testing_node(exec) + .node(EthereumNode::default()) + .launch() + .await?; + + let node = NodeTestContext::new(node, eth_payload_attributes).await?; + + let wallet = Wallet::default(); + let raw_tx = TransactionTestContext::transfer_tx_bytes(1, wallet.inner).await; + + let payload_attributes = PayloadAttributes { + timestamp: chain_spec.genesis().timestamp + 1, + prev_randao: B256::ZERO, + suggested_fee_recipient: Address::ZERO, + withdrawals: Some(vec![]), + parent_beacon_block_root: Some(B256::ZERO), + }; + + let request = TestingBuildBlockRequestV1 { + parent_block_hash: genesis_hash, + payload_attributes, + transactions: vec![raw_tx], + extra_data: None, + }; + + let envelope = node.testing_build_block_v1(request).await?; + + let engine_client = node.auth_server_handle().http_client(); + let payload = envelope.execution_payload.clone(); + let block_hash = payload.payload_inner.payload_inner.block_hash; + + let versioned_hashes: Vec = Vec::new(); + let parent_beacon_block_root = B256::ZERO; + let execution_requests = RequestsOrHash::Requests(envelope.execution_requests); + + let status: alloy_rpc_types_engine::PayloadStatus = engine_client + .request( + "engine_newPayloadV4", + (payload, versioned_hashes, parent_beacon_block_root, execution_requests), + ) + .await?; + assert_eq!(status.status, PayloadStatusEnum::Valid); + + node.update_forkchoice(genesis_hash, block_hash).await?; + + node.wait_block(1, block_hash, false).await?; + + Ok(()) +}