From f372db40c5f6c85f3d1b468180a5233d6ce16e57 Mon Sep 17 00:00:00 2001 From: Luca Provini Date: Wed, 24 Apr 2024 11:36:31 +0200 Subject: [PATCH] feat: blob e2e test (#7823) --- Cargo.lock | 2 + crates/e2e-test-utils/Cargo.toml | 2 +- crates/e2e-test-utils/src/engine_api.rs | 16 ++-- crates/e2e-test-utils/src/lib.rs | 12 ++- crates/e2e-test-utils/src/network.rs | 4 +- crates/e2e-test-utils/src/node.rs | 106 +++++++++++++---------- crates/e2e-test-utils/src/payload.rs | 4 +- crates/e2e-test-utils/src/rpc.rs | 24 +++++ crates/e2e-test-utils/src/transaction.rs | 80 +++++++++++++++++ crates/e2e-test-utils/src/wallet.rs | 55 +++++------- crates/node-ethereum/tests/e2e/blobs.rs | 96 ++++++++++++++++++++ crates/node-ethereum/tests/e2e/eth.rs | 39 +++++++-- crates/node-ethereum/tests/e2e/main.rs | 1 + crates/node-ethereum/tests/e2e/p2p.rs | 19 ++-- crates/optimism/node/tests/e2e/p2p.rs | 25 ++++-- crates/optimism/node/tests/e2e/utils.rs | 17 +--- 16 files changed, 373 insertions(+), 129 deletions(-) create mode 100644 crates/e2e-test-utils/src/rpc.rs create mode 100644 crates/e2e-test-utils/src/transaction.rs create mode 100644 crates/node-ethereum/tests/e2e/blobs.rs diff --git a/Cargo.lock b/Cargo.lock index 9c450fe70..d96e0fe3b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,7 @@ dependencies = [ "c-kzg", "serde", "sha2 0.10.8", + "thiserror", ] [[package]] @@ -185,6 +186,7 @@ dependencies = [ "alloy-serde", "arbitrary", "c-kzg", + "derive_more", "ethereum_ssz", "ethereum_ssz_derive", "once_cell", diff --git a/crates/e2e-test-utils/Cargo.toml b/crates/e2e-test-utils/Cargo.toml index 96b4ca2e6..03e0edb91 100644 --- a/crates/e2e-test-utils/Cargo.toml +++ b/crates/e2e-test-utils/Cargo.toml @@ -33,5 +33,5 @@ alloy-signer.workspace = true alloy-signer-wallet = { workspace = true, features = ["mnemonic"] } alloy-rpc-types.workspace = true alloy-network.workspace = true -alloy-consensus.workspace = true +alloy-consensus = { workspace = true, features = ["kzg"] } tracing.workspace = true \ No newline at end of file diff --git a/crates/e2e-test-utils/src/engine_api.rs b/crates/e2e-test-utils/src/engine_api.rs index fe05b0b68..9ede69e67 100644 --- a/crates/e2e-test-utils/src/engine_api.rs +++ b/crates/e2e-test-utils/src/engine_api.rs @@ -13,13 +13,13 @@ use reth_primitives::B256; use std::marker::PhantomData; /// Helper for engine api operations -pub struct EngineApiHelper { +pub struct EngineApiTestContext { pub canonical_stream: CanonStateNotificationStream, pub engine_api_client: HttpClient, pub _marker: PhantomData, } -impl EngineApiHelper { +impl EngineApiTestContext { /// Retrieves a v3 payload from the engine api pub async fn get_payload_v3( &self, @@ -34,6 +34,7 @@ impl EngineApiHelper { payload: E::BuiltPayload, payload_builder_attributes: E::PayloadBuilderAttributes, expected_status: PayloadStatusEnum, + versioned_hashes: Vec, ) -> eyre::Result where E::ExecutionPayloadV3: From + PayloadEnvelopeExt, @@ -45,7 +46,7 @@ impl EngineApiHelper { let submission = EngineApiClient::::new_payload_v3( &self.engine_api_client, envelope_v3.execution_payload(), - vec![], + versioned_hashes, payload_builder_attributes.parent_beacon_block_root().unwrap(), ) .await?; @@ -56,18 +57,17 @@ impl EngineApiHelper { } /// Sends forkchoice update to the engine api - pub async fn update_forkchoice(&self, hash: B256) -> eyre::Result<()> { + pub async fn update_forkchoice(&self, current_head: B256, new_head: B256) -> eyre::Result<()> { EngineApiClient::::fork_choice_updated_v2( &self.engine_api_client, ForkchoiceState { - head_block_hash: hash, - safe_block_hash: hash, - finalized_block_hash: hash, + head_block_hash: new_head, + safe_block_hash: current_head, + finalized_block_hash: current_head, }, None, ) .await?; - Ok(()) } diff --git a/crates/e2e-test-utils/src/lib.rs b/crates/e2e-test-utils/src/lib.rs index 3c34f76e5..8e57eebed 100644 --- a/crates/e2e-test-utils/src/lib.rs +++ b/crates/e2e-test-utils/src/lib.rs @@ -1,4 +1,4 @@ -use node::NodeHelper; +use node::NodeTestContext; use reth::{ args::{DiscoveryArgs, NetworkArgs, RpcServerArgs}, builder::{NodeBuilder, NodeConfig, NodeHandle}, @@ -18,6 +18,9 @@ use wallet::Wallet; /// Wrapper type to create test nodes pub mod node; +/// Helper for transaction operations +pub mod transaction; + /// Helper type to yield accounts from mnemonic pub mod wallet; @@ -29,6 +32,8 @@ mod network; /// Helper for engine api operations mod engine_api; +/// Helper for rpc operations +mod rpc; /// Helper traits mod traits; @@ -75,7 +80,7 @@ where .launch() .await?; - let mut node = NodeHelper::new(node).await?; + let mut node = NodeTestContext::new(node).await?; // Connect each node in a chain. if let Some(previous_node) = nodes.last_mut() { @@ -104,4 +109,5 @@ type TmpPool = <>>::PoolBuilde type TmpNodeAdapter = FullNodeTypesAdapter>; /// Type alias for a type of NodeHelper -pub type NodeHelperType = NodeHelper, TmpPool>>; +pub type NodeHelperType = + NodeTestContext, TmpPool>>; diff --git a/crates/e2e-test-utils/src/network.rs b/crates/e2e-test-utils/src/network.rs index 341b0d7d0..92e9b316a 100644 --- a/crates/e2e-test-utils/src/network.rs +++ b/crates/e2e-test-utils/src/network.rs @@ -5,12 +5,12 @@ use reth_tracing::tracing::info; use tokio_stream::wrappers::UnboundedReceiverStream; /// Helper for network operations -pub struct NetworkHelper { +pub struct NetworkTestContext { network_events: UnboundedReceiverStream, network: NetworkHandle, } -impl NetworkHelper { +impl NetworkTestContext { /// Creates a new network helper pub fn new(network: NetworkHandle) -> Self { let network_events = network.event_listener(); diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index 18d147fd9..b2ccf899e 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -1,35 +1,36 @@ use crate::{ - engine_api::EngineApiHelper, network::NetworkHelper, payload::PayloadHelper, - traits::PayloadEnvelopeExt, + engine_api::EngineApiTestContext, network::NetworkTestContext, payload::PayloadTestContext, + rpc::RpcTestContext, traits::PayloadEnvelopeExt, }; + use alloy_rpc_types::BlockNumberOrTag; use eyre::Ok; + use futures_util::Future; use reth::{ api::{BuiltPayload, EngineTypes, FullNodeComponents, PayloadBuilderAttributes}, builder::FullNode, providers::{BlockReader, BlockReaderIdExt, CanonStateSubscriptions, StageCheckpointReader}, - rpc::{ - eth::{error::EthResult, EthTransactions}, - types::engine::PayloadStatusEnum, - }, + rpc::types::engine::PayloadStatusEnum, }; +use reth_node_builder::NodeTypes; use reth_primitives::{stage::StageId, BlockHash, BlockNumber, Bytes, B256}; use std::{marker::PhantomData, pin::Pin}; use tokio_stream::StreamExt; /// An helper struct to handle node actions -pub struct NodeHelper +pub struct NodeTestContext where Node: FullNodeComponents, { pub inner: FullNode, - pub payload: PayloadHelper, - pub network: NetworkHelper, - pub engine_api: EngineApiHelper, + pub payload: PayloadTestContext, + pub network: NetworkTestContext, + pub engine_api: EngineApiTestContext, + pub rpc: RpcTestContext, } -impl NodeHelper +impl NodeTestContext where Node: FullNodeComponents, { @@ -39,17 +40,18 @@ where Ok(Self { inner: node.clone(), - network: NetworkHelper::new(node.network.clone()), - payload: PayloadHelper::new(builder).await?, - engine_api: EngineApiHelper { + payload: PayloadTestContext::new(builder).await?, + network: NetworkTestContext::new(node.network.clone()), + engine_api: EngineApiTestContext { engine_api_client: node.auth_server_handle().http_client(), canonical_stream: node.provider.canonical_state_stream(), _marker: PhantomData::, }, + rpc: RpcTestContext { inner: node.rpc_registry }, }) } - pub async fn connect(&mut self, node: &mut NodeHelper) { + pub async fn connect(&mut self, node: &mut NodeTestContext) { self.network.add_peer(node.network.record()).await; node.network.add_peer(self.network.record()).await; node.network.expect_session().await; @@ -62,7 +64,7 @@ where pub async fn advance( &mut self, length: u64, - tx_generator: impl Fn() -> Pin>>, + tx_generator: impl Fn(u64) -> Pin>>, attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes + Copy, ) -> eyre::Result< @@ -76,60 +78,74 @@ where From<::BuiltPayload> + PayloadEnvelopeExt, { let mut chain = Vec::with_capacity(length as usize); - for _ in 0..length { - let (payload, _) = - self.advance_block(tx_generator().await, attributes_generator).await?; - chain.push(payload); + for i in 0..length { + let raw_tx = tx_generator(i).await; + let tx_hash = self.rpc.inject_tx(raw_tx).await?; + let (payload, eth_attr) = self.advance_block(vec![], attributes_generator).await?; + let block_hash = payload.block().hash(); + let block_number = payload.block().number; + self.assert_new_block(tx_hash, block_hash, block_number).await?; + chain.push((payload, eth_attr)); } Ok(chain) } - /// Advances the node forward one block - pub async fn advance_block( + /// Creates a new payload from given attributes generator + /// expects a payload attribute event and waits until the payload is built. + /// + /// It triggers the resolve payload via engine api and expects the built payload event. + pub async fn new_payload( &mut self, - raw_tx: Bytes, attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, ) -> eyre::Result<( - ( - ::BuiltPayload, - ::PayloadBuilderAttributes, - ), - B256, + <::Engine as EngineTypes>::BuiltPayload, + <::Engine as EngineTypes>::PayloadBuilderAttributes, )> where ::ExecutionPayloadV3: From<::BuiltPayload> + PayloadEnvelopeExt, { - // push tx into pool via RPC server - let tx_hash = self.inject_tx(raw_tx).await?; - // trigger new payload building draining the pool let eth_attr = self.payload.new_payload(attributes_generator).await.unwrap(); - // first event is the payload attributes self.payload.expect_attr_event(eth_attr.clone()).await?; - // wait for the payload builder to have finished building self.payload.wait_for_built_payload(eth_attr.payload_id()).await; - // trigger resolve payload via engine api self.engine_api.get_payload_v3(eth_attr.payload_id()).await?; - // ensure we're also receiving the built payload as event - let payload = self.payload.expect_built_payload().await?; + Ok((self.payload.expect_built_payload().await?, eth_attr)) + } + + /// Advances the node forward one block + pub async fn advance_block( + &mut self, + versioned_hashes: Vec, + attributes_generator: impl Fn(u64) -> ::PayloadBuilderAttributes, + ) -> eyre::Result<( + ::BuiltPayload, + <::Engine as EngineTypes>::PayloadBuilderAttributes, + )> + where + ::ExecutionPayloadV3: + From<::BuiltPayload> + PayloadEnvelopeExt, + { + let (payload, eth_attr) = self.new_payload(attributes_generator).await?; - // submit payload via engine api let block_hash = self .engine_api - .submit_payload(payload.clone(), eth_attr.clone(), PayloadStatusEnum::Valid) + .submit_payload( + payload.clone(), + eth_attr.clone(), + PayloadStatusEnum::Valid, + versioned_hashes, + ) .await?; // trigger forkchoice update via engine api to commit the block to the blockchain - self.engine_api.update_forkchoice(block_hash).await?; + self.engine_api.update_forkchoice(block_hash, block_hash).await?; - // assert the block has been committed to the blockchain - self.assert_new_block(tx_hash, block_hash, payload.block().number).await?; - Ok(((payload, eth_attr), tx_hash)) + Ok((payload, eth_attr)) } /// Waits for block to be available on node. @@ -169,12 +185,6 @@ where Ok(()) } - /// Injects a raw transaction into the node tx pool via RPC server - async fn inject_tx(&mut self, raw_tx: Bytes) -> EthResult { - let eth_api = self.inner.rpc_registry.eth_api(); - eth_api.send_raw_transaction(raw_tx).await - } - /// Asserts that a new block has been added to the blockchain /// and the tx has been included in the block pub async fn assert_new_block( diff --git a/crates/e2e-test-utils/src/payload.rs b/crates/e2e-test-utils/src/payload.rs index 2d349721b..47f4134d7 100644 --- a/crates/e2e-test-utils/src/payload.rs +++ b/crates/e2e-test-utils/src/payload.rs @@ -4,13 +4,13 @@ use reth_payload_builder::{Events, PayloadBuilderHandle, PayloadId}; use tokio_stream::wrappers::BroadcastStream; /// Helper for payload operations -pub struct PayloadHelper { +pub struct PayloadTestContext { pub payload_event_stream: BroadcastStream>, payload_builder: PayloadBuilderHandle, pub timestamp: u64, } -impl PayloadHelper { +impl PayloadTestContext { /// Creates a new payload helper pub async fn new(payload_builder: PayloadBuilderHandle) -> eyre::Result { let payload_events = payload_builder.subscribe().await?; diff --git a/crates/e2e-test-utils/src/rpc.rs b/crates/e2e-test-utils/src/rpc.rs new file mode 100644 index 000000000..09f161a91 --- /dev/null +++ b/crates/e2e-test-utils/src/rpc.rs @@ -0,0 +1,24 @@ +use alloy_consensus::TxEnvelope; +use alloy_network::eip2718::Decodable2718; +use reth::{api::FullNodeComponents, builder::rpc::RpcRegistry, rpc::api::DebugApiServer}; +use reth_primitives::{Bytes, B256}; +use reth_rpc::eth::{error::EthResult, EthTransactions}; + +pub struct RpcTestContext { + pub inner: RpcRegistry, +} + +impl RpcTestContext { + /// Injects a raw transaction into the node tx pool via RPC server + pub async fn inject_tx(&mut self, raw_tx: Bytes) -> EthResult { + let eth_api = self.inner.eth_api(); + eth_api.send_raw_transaction(raw_tx).await + } + + /// Retrieves a transaction envelope by its hash + pub async fn envelope_by_hash(&mut self, hash: B256) -> eyre::Result { + let tx = self.inner.debug_api().raw_transaction(hash).await?.unwrap(); + let tx = tx.to_vec(); + Ok(TxEnvelope::decode_2718(&mut tx.as_ref()).unwrap()) + } +} diff --git a/crates/e2e-test-utils/src/transaction.rs b/crates/e2e-test-utils/src/transaction.rs new file mode 100644 index 000000000..a2c40052c --- /dev/null +++ b/crates/e2e-test-utils/src/transaction.rs @@ -0,0 +1,80 @@ +use alloy_consensus::{ + BlobTransactionSidecar, SidecarBuilder, SimpleCoder, TxEip4844Variant, TxEnvelope, +}; +use alloy_network::{eip2718::Encodable2718, EthereumSigner, TransactionBuilder}; +use alloy_rpc_types::{TransactionInput, TransactionRequest}; +use alloy_signer_wallet::LocalWallet; +use eyre::Ok; +use reth_primitives::{hex, Address, Bytes, U256}; + +use reth_primitives::{constants::eip4844::MAINNET_KZG_TRUSTED_SETUP, B256}; + +pub struct TransactionTestContext; + +impl TransactionTestContext { + /// Creates a static transfer and signs it + pub async fn transfer_tx(chain_id: u64, wallet: LocalWallet) -> Bytes { + let tx = tx(chain_id, None, 0); + let signer = EthereumSigner::from(wallet); + tx.build(&signer).await.unwrap().encoded_2718().into() + } + + /// Creates a tx with blob sidecar and sign it + pub async fn tx_with_blobs(chain_id: u64, wallet: LocalWallet) -> eyre::Result { + let mut tx = tx(chain_id, None, 0); + + let mut builder = SidecarBuilder::::new(); + builder.ingest(b"dummy blob"); + let sidecar: BlobTransactionSidecar = builder.build()?; + + tx.set_blob_sidecar(sidecar); + tx.set_max_fee_per_blob_gas(15e9 as u128); + + let signer = EthereumSigner::from(wallet); + let signed = tx.clone().build(&signer).await.unwrap(); + + Ok(signed.encoded_2718().into()) + } + + pub async fn optimism_l1_block_info_tx( + chain_id: u64, + wallet: LocalWallet, + nonce: u64, + ) -> Bytes { + let l1_block_info = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240")); + let tx = tx(chain_id, Some(l1_block_info), nonce); + let signer = EthereumSigner::from(wallet); + tx.build(&signer).await.unwrap().encoded_2718().into() + } + + /// Validates the sidecar of a given tx envelope and returns the versioned hashes + pub fn validate_sidecar(tx: TxEnvelope) -> Vec { + let proof_setting = MAINNET_KZG_TRUSTED_SETUP.clone(); + + match tx { + TxEnvelope::Eip4844(signed) => match signed.tx() { + TxEip4844Variant::TxEip4844WithSidecar(tx) => { + tx.validate_blob(&proof_setting).unwrap(); + tx.sidecar.versioned_hashes().collect() + } + _ => panic!("Expected Eip4844 transaction with sidecar"), + }, + _ => panic!("Expected Eip4844 transaction"), + } + } +} + +/// Creates a type 2 transaction +fn tx(chain_id: u64, data: Option, nonce: u64) -> TransactionRequest { + TransactionRequest { + nonce: Some(nonce), + value: Some(U256::from(100)), + to: Some(Address::random()), + gas: Some(210000), + max_fee_per_gas: Some(20e9 as u128), + max_priority_fee_per_gas: Some(20e9 as u128), + chain_id: Some(chain_id), + input: TransactionInput { input: None, data }, + ..Default::default() + } +} diff --git a/crates/e2e-test-utils/src/wallet.rs b/crates/e2e-test-utils/src/wallet.rs index d064eede9..d94dec2a0 100644 --- a/crates/e2e-test-utils/src/wallet.rs +++ b/crates/e2e-test-utils/src/wallet.rs @@ -1,19 +1,19 @@ -use alloy_network::{eip2718::Encodable2718, EthereumSigner, TransactionBuilder}; -use alloy_rpc_types::{TransactionInput, TransactionRequest}; +use alloy_signer::Signer; use alloy_signer_wallet::{coins_bip39::English, LocalWallet, MnemonicBuilder}; -use reth_primitives::{hex, Address, Bytes, U256}; + /// One of the accounts of the genesis allocations. pub struct Wallet { - inner: LocalWallet, - pub nonce: u64, + pub inner: LocalWallet, chain_id: u64, + amount: usize, + derivation_path: Option, } impl Wallet { /// Creates a new account from one of the secret/pubkeys of the genesis allocations (test.json) - pub(crate) fn new(phrase: &str) -> Self { - let inner = MnemonicBuilder::::default().phrase(phrase).build().unwrap(); - Self { inner, chain_id: 1, nonce: 0 } + pub fn new(amount: usize) -> Self { + let inner = MnemonicBuilder::::default().phrase(TEST_MNEMONIC).build().unwrap(); + Self { inner, chain_id: 1, amount, derivation_path: None } } /// Sets chain id @@ -22,31 +22,24 @@ impl Wallet { self } - /// Creates a static transfer and signs it - pub async fn transfer_tx(&mut self) -> Bytes { - self.tx(None).await + fn get_derivation_path(&self) -> &str { + self.derivation_path.as_deref().unwrap_or("m/44'/60'/0'/0/") } - pub async fn optimism_l1_block_info_tx(&mut self) -> Bytes { - let l1_block_info = Bytes::from_static(&hex!("7ef9015aa044bae9d41b8380d781187b426c6fe43df5fb2fb57bd4466ef6a701e1f01e015694deaddeaddeaddeaddeaddeaddeaddeaddead000194420000000000000000000000000000000000001580808408f0d18001b90104015d8eb900000000000000000000000000000000000000000000000000000000008057650000000000000000000000000000000000000000000000000000000063d96d10000000000000000000000000000000000000000000000000000000000009f35273d89754a1e0387b89520d989d3be9c37c1f32495a88faf1ea05c61121ab0d1900000000000000000000000000000000000000000000000000000000000000010000000000000000000000002d679b567db6187c0c8323fa982cfb88b74dbcc7000000000000000000000000000000000000000000000000000000000000083400000000000000000000000000000000000000000000000000000000000f4240")); - self.tx(Some(l1_block_info)).await - } + pub fn gen(&self) -> Vec { + let builder = MnemonicBuilder::::default().phrase(TEST_MNEMONIC); - /// Creates a transaction with data and signs it - pub async fn tx(&mut self, data: Option) -> Bytes { - let tx = TransactionRequest { - nonce: Some(self.nonce), - value: Some(U256::from(100)), - to: Some(Address::random()), - gas_price: Some(20e9 as u128), - gas: Some(210000), - chain_id: Some(self.chain_id), - input: TransactionInput { input: None, data }, - ..Default::default() - }; - self.nonce += 1; - let signer = EthereumSigner::from(self.inner.clone()); - tx.build(&signer).await.unwrap().encoded_2718().into() + // use the derivation path + let derivation_path = self.get_derivation_path(); + + let mut wallets = Vec::with_capacity(self.amount); + for idx in 0..self.amount { + let builder = + builder.clone().derivation_path(&format!("{derivation_path}{idx}")).unwrap(); + let wallet = builder.build().unwrap().with_chain_id(Some(self.chain_id)); + wallets.push(wallet) + } + wallets } } @@ -54,6 +47,6 @@ const TEST_MNEMONIC: &str = "test test test test test test test test test test t impl Default for Wallet { fn default() -> Self { - Wallet::new(TEST_MNEMONIC) + Wallet::new(1) } } diff --git a/crates/node-ethereum/tests/e2e/blobs.rs b/crates/node-ethereum/tests/e2e/blobs.rs new file mode 100644 index 000000000..d8fca42d6 --- /dev/null +++ b/crates/node-ethereum/tests/e2e/blobs.rs @@ -0,0 +1,96 @@ +use std::sync::Arc; + +use reth::{ + args::RpcServerArgs, + builder::{NodeBuilder, NodeConfig, NodeHandle}, + rpc::types::engine::PayloadStatusEnum, + tasks::TaskManager, +}; +use reth_e2e_test_utils::{ + node::NodeTestContext, transaction::TransactionTestContext, wallet::Wallet, +}; +use reth_node_ethereum::EthereumNode; +use reth_primitives::{b256, ChainSpecBuilder, Genesis, MAINNET}; +use reth_transaction_pool::TransactionPool; + +use crate::utils::eth_payload_attributes; + +#[tokio::test] +async fn can_handle_blobs() -> 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) + .cancun_activated() + .build(), + ); + let node_config = NodeConfig::test() + .with_chain(chain_spec) + .with_unused_ports() + .with_rpc(RpcServerArgs::default().with_unused_ports().with_http()); + let NodeHandle { node, node_exit_future: _ } = NodeBuilder::new(node_config.clone()) + .testing_node(exec.clone()) + .node(EthereumNode::default()) + .launch() + .await?; + + let mut node = NodeTestContext::new(node).await?; + + let wallets = Wallet::new(2).gen(); + let blob_wallet = wallets.first().unwrap(); + let second_wallet = wallets.last().unwrap(); + + // inject normal tx + let raw_tx = TransactionTestContext::transfer_tx(1, second_wallet.clone()).await; + let tx_hash = node.rpc.inject_tx(raw_tx).await?; + // build payload with normal tx + let (payload, attributes) = node.new_payload(eth_payload_attributes).await?; + + // clean the pool + node.inner.pool.remove_transactions(vec![tx_hash]); + + // build blob tx + let blob_tx = TransactionTestContext::tx_with_blobs(1, blob_wallet.clone()).await?; + + // inject blob tx to the pool + let blob_tx_hash = node.rpc.inject_tx(blob_tx).await?; + // fetch it from rpc + let envelope = node.rpc.envelope_by_hash(blob_tx_hash).await?; + // validate sidecar + let versioned_hashes = TransactionTestContext::validate_sidecar(envelope); + + // build a payload + let (blob_payload, blob_attr) = node.new_payload(eth_payload_attributes).await?; + + // submit the blob payload + let blob_block_hash = node + .engine_api + .submit_payload(blob_payload, blob_attr, PayloadStatusEnum::Valid, versioned_hashes.clone()) + .await?; + + let genesis_hash = b256!("d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"); + + let (_, _) = tokio::join!( + // send fcu with blob hash + node.engine_api.update_forkchoice(genesis_hash, blob_block_hash), + // send fcu with normal hash + node.engine_api.update_forkchoice(genesis_hash, payload.block().hash()) + ); + + // submit normal payload + node.engine_api.submit_payload(payload, attributes, PayloadStatusEnum::Valid, vec![]).await?; + + tokio::time::sleep(std::time::Duration::from_secs(3)).await; + + // expects the blob tx to be back in the pool + let envelope = node.rpc.envelope_by_hash(blob_tx_hash).await?; + // make sure the sidecar is present + TransactionTestContext::validate_sidecar(envelope); + + Ok(()) +} diff --git a/crates/node-ethereum/tests/e2e/eth.rs b/crates/node-ethereum/tests/e2e/eth.rs index 39ba5e232..4f566e7c8 100644 --- a/crates/node-ethereum/tests/e2e/eth.rs +++ b/crates/node-ethereum/tests/e2e/eth.rs @@ -4,7 +4,9 @@ use reth::{ builder::{NodeBuilder, NodeConfig, NodeHandle}, tasks::TaskManager, }; -use reth_e2e_test_utils::{node::NodeHelper, setup, wallet::Wallet}; +use reth_e2e_test_utils::{ + node::NodeTestContext, setup, transaction::TransactionTestContext, wallet::Wallet, +}; use reth_node_ethereum::EthereumNode; use reth_primitives::{ChainSpecBuilder, Genesis, MAINNET}; use std::sync::Arc; @@ -13,7 +15,7 @@ use std::sync::Arc; async fn can_run_eth_node() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, mut wallet) = setup::( + let (mut nodes, _tasks, _wallet) = setup::( 1, Arc::new( ChainSpecBuilder::default() @@ -27,10 +29,20 @@ async fn can_run_eth_node() -> eyre::Result<()> { .await?; let mut node = nodes.pop().unwrap(); - let raw_tx = wallet.transfer_tx().await; + let wallet = Wallet::default(); + let raw_tx = TransactionTestContext::transfer_tx(1, wallet.inner).await; // make the node advance - node.advance_block(raw_tx, eth_payload_attributes).await?; + let tx_hash = node.rpc.inject_tx(raw_tx).await?; + + // make the node advance + let (payload, _) = node.advance_block(vec![], eth_payload_attributes).await?; + + let block_hash = payload.block().hash(); + let block_number = payload.block().number; + + // assert the block has been committed to the blockchain + node.assert_new_block(tx_hash, block_hash, block_number).await?; Ok(()) } @@ -62,14 +74,23 @@ async fn can_run_eth_node_with_auth_engine_api_over_ipc() -> eyre::Result<()> { .node(EthereumNode::default()) .launch() .await?; - let mut node = NodeHelper::new(node).await?; + let mut node = NodeTestContext::new(node).await?; // Configure wallet from test mnemonic and create dummy transfer tx - let mut wallet = Wallet::default(); - let raw_tx = wallet.transfer_tx().await; + let wallet = Wallet::default(); + let raw_tx = TransactionTestContext::transfer_tx(1, wallet.inner).await; // make the node advance - node.advance_block(raw_tx, crate::utils::eth_payload_attributes).await?; + let tx_hash = node.rpc.inject_tx(raw_tx).await?; + + // make the node advance + let (payload, _) = node.advance_block(vec![], eth_payload_attributes).await?; + + let block_hash = payload.block().hash(); + let block_number = payload.block().number; + + // assert the block has been committed to the blockchain + node.assert_new_block(tx_hash, block_hash, block_number).await?; Ok(()) } @@ -99,7 +120,7 @@ async fn test_failed_run_eth_node_with_no_auth_engine_api_over_ipc_opts() -> eyr .launch() .await?; - let node = NodeHelper::new(node).await?; + let node = NodeTestContext::new(node).await?; // Ensure that the engine api client is not available let client = node.inner.engine_ipc_client().await; diff --git a/crates/node-ethereum/tests/e2e/main.rs b/crates/node-ethereum/tests/e2e/main.rs index 6a8a01064..1d0d6db8c 100644 --- a/crates/node-ethereum/tests/e2e/main.rs +++ b/crates/node-ethereum/tests/e2e/main.rs @@ -1,3 +1,4 @@ +mod blobs; mod dev; mod eth; mod p2p; diff --git a/crates/node-ethereum/tests/e2e/p2p.rs b/crates/node-ethereum/tests/e2e/p2p.rs index c7ce2a7c1..768d1ac5a 100644 --- a/crates/node-ethereum/tests/e2e/p2p.rs +++ b/crates/node-ethereum/tests/e2e/p2p.rs @@ -1,5 +1,5 @@ use crate::utils::eth_payload_attributes; -use reth_e2e_test_utils::setup; +use reth_e2e_test_utils::{setup, transaction::TransactionTestContext}; use reth_node_ethereum::EthereumNode; use reth_primitives::{ChainSpecBuilder, MAINNET}; use std::sync::Arc; @@ -8,7 +8,7 @@ use std::sync::Arc; async fn can_sync() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, mut wallet) = setup::( + let (mut nodes, _tasks, wallet) = setup::( 2, Arc::new( ChainSpecBuilder::default() @@ -21,17 +21,24 @@ async fn can_sync() -> eyre::Result<()> { ) .await?; - let raw_tx = wallet.transfer_tx().await; + let raw_tx = TransactionTestContext::transfer_tx(1, wallet.inner).await; let mut second_node = nodes.pop().unwrap(); let mut first_node = nodes.pop().unwrap(); // Make the first node advance - let ((payload, _), tx_hash) = - first_node.advance_block(raw_tx.clone(), eth_payload_attributes).await?; + let tx_hash = first_node.rpc.inject_tx(raw_tx).await?; + + // make the node advance + let (payload, _) = first_node.advance_block(vec![], eth_payload_attributes).await?; + let block_hash = payload.block().hash(); + let block_number = payload.block().number; + + // assert the block has been committed to the blockchain + first_node.assert_new_block(tx_hash, block_hash, block_number).await?; // only send forkchoice update to second node - second_node.engine_api.update_forkchoice(block_hash).await?; + second_node.engine_api.update_forkchoice(block_hash, block_hash).await?; // expect second node advanced via p2p gossip second_node.assert_new_block(tx_hash, block_hash, 1).await?; diff --git a/crates/optimism/node/tests/e2e/p2p.rs b/crates/optimism/node/tests/e2e/p2p.rs index da6af2090..a38fadf67 100644 --- a/crates/optimism/node/tests/e2e/p2p.rs +++ b/crates/optimism/node/tests/e2e/p2p.rs @@ -1,13 +1,15 @@ use crate::utils::{advance_chain, setup}; -use std::sync::Arc; -use tokio::sync::Mutex; +use reth::primitives::BASE_MAINNET; +use reth_e2e_test_utils::{transaction::TransactionTestContext, wallet::Wallet}; +use reth_primitives::ChainId; #[tokio::test] async fn can_sync() -> eyre::Result<()> { reth_tracing::init_test_tracing(); - let (mut nodes, _tasks, wallet) = setup(2).await?; - let wallet = Arc::new(Mutex::new(wallet)); + let chain_id: ChainId = BASE_MAINNET.chain.into(); + + let (mut nodes, _tasks, _wallet) = setup(2).await?; let second_node = nodes.pop().unwrap(); let mut first_node = nodes.pop().unwrap(); @@ -15,13 +17,24 @@ async fn can_sync() -> eyre::Result<()> { let tip: usize = 300; let tip_index: usize = tip - 1; + let wallet = Wallet::default(); + // On first node, create a chain up to block number 300a - let canonical_payload_chain = advance_chain(tip, &mut first_node, wallet.clone()).await?; + let canonical_payload_chain = advance_chain(tip, &mut first_node, |nonce: u64| { + let wallet = wallet.inner.clone(); + Box::pin(async move { + TransactionTestContext::optimism_l1_block_info_tx(chain_id, wallet, nonce).await + }) + }) + .await?; let canonical_chain = canonical_payload_chain.iter().map(|p| p.0.block().hash()).collect::>(); // On second node, sync up to block number 300a - second_node.engine_api.update_forkchoice(canonical_chain[tip_index]).await?; + second_node + .engine_api + .update_forkchoice(canonical_chain[tip_index], canonical_chain[tip_index]) + .await?; second_node.wait_block(tip as u64, canonical_chain[tip_index], true).await?; Ok(()) diff --git a/crates/optimism/node/tests/e2e/utils.rs b/crates/optimism/node/tests/e2e/utils.rs index 5322cad9a..e86a7c654 100644 --- a/crates/optimism/node/tests/e2e/utils.rs +++ b/crates/optimism/node/tests/e2e/utils.rs @@ -1,10 +1,9 @@ -use reth::{rpc::types::engine::PayloadAttributes, tasks::TaskManager}; +use reth::{primitives::Bytes, rpc::types::engine::PayloadAttributes, tasks::TaskManager}; use reth_e2e_test_utils::{wallet::Wallet, NodeHelperType}; use reth_node_optimism::{OptimismBuiltPayload, OptimismNode, OptimismPayloadBuilderAttributes}; use reth_payload_builder::EthPayloadBuilderAttributes; use reth_primitives::{Address, ChainSpecBuilder, Genesis, B256, BASE_MAINNET}; -use std::sync::Arc; -use tokio::sync::Mutex; +use std::{future::Future, pin::Pin, sync::Arc}; /// Optimism Node Helper type pub(crate) type OpNode = NodeHelperType; @@ -28,17 +27,9 @@ pub(crate) async fn setup(num_nodes: usize) -> eyre::Result<(Vec, TaskMa pub(crate) async fn advance_chain( length: usize, node: &mut OpNode, - wallet: Arc>, + tx_generator: impl Fn(u64) -> Pin>>, ) -> eyre::Result> { - node.advance( - length as u64, - || { - let wallet = wallet.clone(); - Box::pin(async move { wallet.lock().await.optimism_l1_block_info_tx().await }) - }, - optimism_payload_attributes, - ) - .await + node.advance(length as u64, tx_generator, optimism_payload_attributes).await } /// Helper function to create a new eth payload attributes