mirror of
https://github.com/paradigmxyz/reth.git
synced 2026-01-09 23:38:10 -05:00
test: complete mine block test in e2e testsuite (#14849)
Co-authored-by: Arsenii Kulikov <klkvrr@gmail.com>
This commit is contained in:
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -7348,12 +7348,15 @@ dependencies = [
|
||||
"reth-node-api",
|
||||
"reth-node-builder",
|
||||
"reth-node-core",
|
||||
"reth-node-ethereum",
|
||||
"reth-payload-builder",
|
||||
"reth-payload-builder-primitives",
|
||||
"reth-payload-primitives",
|
||||
"reth-primitives",
|
||||
"reth-primitives-traits",
|
||||
"reth-provider",
|
||||
"reth-rpc-api",
|
||||
"reth-rpc-builder",
|
||||
"reth-rpc-eth-api",
|
||||
"reth-rpc-layer",
|
||||
"reth-rpc-server-types",
|
||||
|
||||
@@ -33,6 +33,9 @@ reth-network-peers.workspace = true
|
||||
reth-engine-local.workspace = true
|
||||
reth-tasks.workspace = true
|
||||
reth-primitives.workspace = true
|
||||
reth-node-ethereum.workspace = true
|
||||
reth-primitives-traits.workspace = true
|
||||
reth-rpc-builder.workspace = true
|
||||
|
||||
revm.workspace = true
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Utilities for end-to-end tests.
|
||||
|
||||
use node::NodeTestContext;
|
||||
use reth_chainspec::EthChainSpec;
|
||||
use reth_chainspec::{ChainSpec, EthChainSpec};
|
||||
use reth_db::{test_utils::TempDatabase, DatabaseEnv};
|
||||
use reth_engine_local::LocalPayloadAttributesBuilder;
|
||||
use reth_network_api::test_utils::PeersHandleProvider;
|
||||
@@ -9,8 +9,8 @@ use reth_node_builder::{
|
||||
components::NodeComponentsBuilder,
|
||||
rpc::{EngineValidatorAddOn, RethRpcAddOns},
|
||||
EngineNodeLauncher, FullNodeTypesAdapter, Node, NodeAdapter, NodeBuilder, NodeComponents,
|
||||
NodeConfig, NodeHandle, NodeTypesWithDBAdapter, NodeTypesWithEngine, PayloadAttributesBuilder,
|
||||
PayloadTypes,
|
||||
NodeConfig, NodeHandle, NodePrimitives, NodeTypesWithDBAdapter, NodeTypesWithEngine,
|
||||
PayloadAttributesBuilder, PayloadTypes,
|
||||
};
|
||||
use reth_node_core::args::{DiscoveryArgs, NetworkArgs, RpcServerArgs};
|
||||
use reth_provider::providers::{BlockchainProvider, NodeTypesForProvider};
|
||||
@@ -44,7 +44,7 @@ pub async fn setup<N>(
|
||||
num_nodes: usize,
|
||||
chain_spec: Arc<N::ChainSpec>,
|
||||
is_dev: bool,
|
||||
attributes_generator: impl Fn(u64) -> <<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadBuilderAttributes + Copy + 'static,
|
||||
attributes_generator: impl Fn(u64) -> <<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadBuilderAttributes + Send + Sync + Copy + 'static,
|
||||
) -> eyre::Result<(Vec<NodeHelperType<N>>, TaskManager, Wallet)>
|
||||
where
|
||||
N: Default + Node<TmpNodeAdapter<N>> + NodeTypesForProvider + NodeTypesWithEngine,
|
||||
@@ -108,29 +108,16 @@ pub async fn setup_engine<N>(
|
||||
num_nodes: usize,
|
||||
chain_spec: Arc<N::ChainSpec>,
|
||||
is_dev: bool,
|
||||
attributes_generator: impl Fn(u64) -> <<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadBuilderAttributes + Copy + 'static,
|
||||
attributes_generator: impl Fn(u64) -> <<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadBuilderAttributes + Send + Sync + Copy + 'static,
|
||||
) -> eyre::Result<(
|
||||
Vec<NodeHelperType<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>>,
|
||||
TaskManager,
|
||||
Wallet,
|
||||
)>
|
||||
where
|
||||
N: Default
|
||||
+ Node<TmpNodeAdapter<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>>
|
||||
+ NodeTypesWithEngine
|
||||
+ NodeTypesForProvider,
|
||||
N::ComponentsBuilder: NodeComponentsBuilder<
|
||||
TmpNodeAdapter<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>,
|
||||
Components: NodeComponents<
|
||||
TmpNodeAdapter<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>,
|
||||
Network: PeersHandleProvider,
|
||||
>,
|
||||
>,
|
||||
N::AddOns: RethRpcAddOns<Adapter<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>>
|
||||
+ EngineValidatorAddOn<Adapter<N, BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>>>,
|
||||
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
|
||||
<<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadAttributes,
|
||||
>,
|
||||
N: NodeBuilderHelper,
|
||||
LocalPayloadAttributesBuilder<N::ChainSpec>:
|
||||
PayloadAttributesBuilder<<N::Engine as PayloadTypes>::PayloadAttributes>,
|
||||
{
|
||||
let tasks = TaskManager::current();
|
||||
let exec = tasks.executor();
|
||||
@@ -214,3 +201,74 @@ pub type Adapter<N, Provider = BlockchainProvider<NodeTypesWithDBAdapter<N, TmpD
|
||||
/// Type alias for a type of `NodeHelper`
|
||||
pub type NodeHelperType<N, Provider = BlockchainProvider<NodeTypesWithDBAdapter<N, TmpDB>>> =
|
||||
NodeTestContext<Adapter<N, Provider>, <N as Node<TmpNodeAdapter<N, Provider>>>::AddOns>;
|
||||
|
||||
/// Helper trait to simplify bounds when calling setup functions.
|
||||
pub trait NodeBuilderHelper
|
||||
where
|
||||
Self: Default
|
||||
+ NodeTypesForProvider
|
||||
+ NodeTypesWithEngine<
|
||||
Engine: PayloadTypes<
|
||||
PayloadBuilderAttributes: From<reth_payload_builder::EthPayloadBuilderAttributes>,
|
||||
>,
|
||||
> + Node<
|
||||
TmpNodeAdapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
Primitives: NodePrimitives<
|
||||
BlockHeader = alloy_consensus::Header,
|
||||
BlockBody = alloy_consensus::BlockBody<
|
||||
<Self::Primitives as NodePrimitives>::SignedTx,
|
||||
>,
|
||||
>,
|
||||
ComponentsBuilder: NodeComponentsBuilder<
|
||||
TmpNodeAdapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
Components: NodeComponents<
|
||||
TmpNodeAdapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
Network: PeersHandleProvider,
|
||||
>,
|
||||
>,
|
||||
AddOns: RethRpcAddOns<
|
||||
Adapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
> + EngineValidatorAddOn<
|
||||
Adapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
>,
|
||||
ChainSpec: From<ChainSpec> + Clone,
|
||||
>,
|
||||
LocalPayloadAttributesBuilder<Self::ChainSpec>:
|
||||
PayloadAttributesBuilder<<Self::Engine as PayloadTypes>::PayloadAttributes>,
|
||||
{
|
||||
}
|
||||
|
||||
impl<T> NodeBuilderHelper for T
|
||||
where
|
||||
Self: Default
|
||||
+ NodeTypesForProvider
|
||||
+ NodeTypesWithEngine<
|
||||
Engine: PayloadTypes<
|
||||
PayloadBuilderAttributes: From<reth_payload_builder::EthPayloadBuilderAttributes>,
|
||||
>,
|
||||
> + Node<
|
||||
TmpNodeAdapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
Primitives: NodePrimitives<
|
||||
BlockHeader = alloy_consensus::Header,
|
||||
BlockBody = alloy_consensus::BlockBody<
|
||||
<Self::Primitives as NodePrimitives>::SignedTx,
|
||||
>,
|
||||
>,
|
||||
ComponentsBuilder: NodeComponentsBuilder<
|
||||
TmpNodeAdapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
Components: NodeComponents<
|
||||
TmpNodeAdapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
Network: PeersHandleProvider,
|
||||
>,
|
||||
>,
|
||||
AddOns: RethRpcAddOns<
|
||||
Adapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
> + EngineValidatorAddOn<
|
||||
Adapter<Self, BlockchainProvider<NodeTypesWithDBAdapter<Self, TmpDB>>>,
|
||||
>,
|
||||
ChainSpec: From<ChainSpec> + Clone,
|
||||
>,
|
||||
LocalPayloadAttributesBuilder<Self::ChainSpec>:
|
||||
PayloadAttributesBuilder<<Self::Engine as PayloadTypes>::PayloadAttributes>,
|
||||
{
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use alloy_rpc_types_engine::ForkchoiceState;
|
||||
use alloy_rpc_types_eth::BlockNumberOrTag;
|
||||
use eyre::Ok;
|
||||
use futures_util::Future;
|
||||
use jsonrpsee::http_client::{transport::HttpBackend, HttpClient};
|
||||
use reth_chainspec::EthereumHardforks;
|
||||
use reth_network_api::test_utils::PeersHandleProvider;
|
||||
use reth_node_api::{
|
||||
@@ -20,6 +21,7 @@ use reth_provider::{
|
||||
StageCheckpointReader,
|
||||
};
|
||||
use reth_rpc_eth_api::helpers::{EthApiSpec, EthTransactions, TraceExt};
|
||||
use reth_rpc_layer::AuthClientService;
|
||||
use reth_stages_types::StageId;
|
||||
use std::pin::Pin;
|
||||
use tokio_stream::StreamExt;
|
||||
@@ -55,7 +57,7 @@ where
|
||||
/// Creates a new test node
|
||||
pub async fn new(
|
||||
node: FullNode<Node, AddOns>,
|
||||
attributes_generator: impl Fn(u64) -> Engine::PayloadBuilderAttributes + 'static,
|
||||
attributes_generator: impl Fn(u64) -> Engine::PayloadBuilderAttributes + Send + Sync + 'static,
|
||||
) -> eyre::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: node.clone(),
|
||||
@@ -293,4 +295,14 @@ where
|
||||
let addr = self.inner.rpc_server_handle().http_local_addr().unwrap();
|
||||
format!("http://{}", addr).parse().unwrap()
|
||||
}
|
||||
|
||||
/// Returns an RPC client.
|
||||
pub fn rpc_client(&self) -> Option<HttpClient> {
|
||||
self.inner.rpc_server_handle().http_client()
|
||||
}
|
||||
|
||||
/// Returns an Engine API client.
|
||||
pub fn engine_api_client(&self) -> HttpClient<AuthClientService<HttpBackend>> {
|
||||
self.inner.auth_server_handle().http_client()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,14 +12,14 @@ pub struct PayloadTestContext<T: PayloadTypes> {
|
||||
payload_builder: PayloadBuilderHandle<T>,
|
||||
pub timestamp: u64,
|
||||
#[debug(skip)]
|
||||
attributes_generator: Box<dyn Fn(u64) -> T::PayloadBuilderAttributes>,
|
||||
attributes_generator: Box<dyn Fn(u64) -> T::PayloadBuilderAttributes + Send + Sync>,
|
||||
}
|
||||
|
||||
impl<T: PayloadTypes> PayloadTestContext<T> {
|
||||
/// Creates a new payload helper
|
||||
pub async fn new(
|
||||
payload_builder: PayloadBuilderHandle<T>,
|
||||
attributes_generator: impl Fn(u64) -> T::PayloadBuilderAttributes + 'static,
|
||||
attributes_generator: impl Fn(u64) -> T::PayloadBuilderAttributes + Send + Sync + 'static,
|
||||
) -> eyre::Result<Self> {
|
||||
let payload_events = payload_builder.subscribe().await?;
|
||||
let payload_event_stream = payload_events.into_stream();
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
|
||||
use crate::testsuite::Environment;
|
||||
use alloy_eips::BlockId;
|
||||
use alloy_primitives::{BlockNumber, B256};
|
||||
use alloy_rpc_types_engine::{ExecutionPayload, PayloadAttributes};
|
||||
use alloy_primitives::{Address, BlockNumber, Bytes, B256};
|
||||
use alloy_rpc_types_engine::{
|
||||
ExecutionPayload, ForkchoiceState, PayloadAttributes, PayloadStatusEnum,
|
||||
};
|
||||
use alloy_rpc_types_eth::{Block, Header, Receipt, Transaction};
|
||||
use eyre::Result;
|
||||
use futures_util::future::BoxFuture;
|
||||
use std::future::Future;
|
||||
use reth_node_api::EngineTypes;
|
||||
use reth_rpc_api::clients::{EngineApiClient, EthApiClient};
|
||||
use std::{future::Future, marker::PhantomData};
|
||||
use tracing::debug;
|
||||
|
||||
/// An action that can be performed on an instance.
|
||||
///
|
||||
@@ -40,7 +46,6 @@ impl<I: 'static> ActionBox<I> {
|
||||
/// This allows using closures directly as actions with `.with_action(async move |env| {...})`.
|
||||
impl<I, F, Fut> Action<I> for F
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
F: FnMut(&Environment<I>) -> Fut + Send + 'static,
|
||||
Fut: Future<Output = Result<()>> + Send + 'static,
|
||||
{
|
||||
@@ -58,10 +63,7 @@ pub struct MineBlock {
|
||||
pub transactions: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for MineBlock
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for MineBlock {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move {
|
||||
// 1. Create a new payload with the given transactions
|
||||
@@ -76,41 +78,102 @@ where
|
||||
/// Mine a single block with the given transactions and verify the block was created
|
||||
/// successfully.
|
||||
#[derive(Debug)]
|
||||
pub struct AssertMineBlock {
|
||||
pub struct AssertMineBlock<Engine> {
|
||||
/// The node index to mine
|
||||
pub node_idx: usize,
|
||||
/// Transactions to include in the block
|
||||
pub transactions: Vec<Vec<u8>>,
|
||||
pub transactions: Vec<Bytes>,
|
||||
/// Expected block hash (optional)
|
||||
pub expected_hash: Option<B256>,
|
||||
/// Tracks engine type
|
||||
_phantom: PhantomData<Engine>,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for AssertMineBlock
|
||||
impl<Engine> AssertMineBlock<Engine> {
|
||||
/// Create a new `AssertMineBlock` action
|
||||
pub fn new(node_idx: usize, transactions: Vec<Bytes>, expected_hash: Option<B256>) -> Self {
|
||||
Self { node_idx, transactions, expected_hash, _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Engine> Action<Engine> for AssertMineBlock<Engine>
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
Engine: EngineTypes,
|
||||
Engine::PayloadAttributes: From<PayloadAttributes>,
|
||||
{
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
fn execute<'a>(&'a mut self, env: &'a Environment<Engine>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move {
|
||||
// 1. Create a new payload with the given transactions
|
||||
// 2. Execute forkchoiceUpdated with the new payload
|
||||
// 3. Verify the block was created successfully
|
||||
// 4. If expected_hash is provided, verify the block hash matches
|
||||
|
||||
/*
|
||||
* Example assertion code (would actually fetch the real hash):
|
||||
if let Some(expected_hash) = self.expected_hash {
|
||||
let actual_hash = B256::ZERO;
|
||||
if actual_hash != expected_hash {
|
||||
return Err(eyre!(
|
||||
"Block hash mismatch: expected {}, got {}",
|
||||
expected_hash,
|
||||
actual_hash
|
||||
));
|
||||
}
|
||||
if self.node_idx >= env.node_clients.len() {
|
||||
return Err(eyre::eyre!("Node index out of bounds: {}", self.node_idx));
|
||||
}
|
||||
*/
|
||||
|
||||
Ok(())
|
||||
let node_client = &env.node_clients[self.node_idx];
|
||||
let rpc_client = &node_client.rpc;
|
||||
let engine_client = &node_client.engine;
|
||||
|
||||
// get the latest block to use as parent
|
||||
let latest_block =
|
||||
EthApiClient::<Transaction, Block, Receipt, Header>::block_by_number(
|
||||
rpc_client,
|
||||
alloy_eips::BlockNumberOrTag::Latest,
|
||||
false,
|
||||
)
|
||||
.await?;
|
||||
|
||||
let latest_block = latest_block.ok_or_else(|| eyre::eyre!("Latest block not found"))?;
|
||||
let parent_hash = latest_block.header.hash;
|
||||
|
||||
debug!("Latest block hash: {parent_hash}");
|
||||
|
||||
// create a simple forkchoice state with the latest block as head
|
||||
let fork_choice_state = ForkchoiceState {
|
||||
head_block_hash: parent_hash,
|
||||
safe_block_hash: parent_hash,
|
||||
finalized_block_hash: parent_hash,
|
||||
};
|
||||
|
||||
let timestamp = std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.as_secs();
|
||||
|
||||
// create payload attributes for the new block
|
||||
let payload_attributes = PayloadAttributes {
|
||||
timestamp,
|
||||
prev_randao: B256::random(),
|
||||
suggested_fee_recipient: Address::random(),
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
};
|
||||
|
||||
let engine_payload_attributes: Engine::PayloadAttributes = payload_attributes.into();
|
||||
|
||||
let fcu_result = EngineApiClient::<Engine>::fork_choice_updated_v3(
|
||||
engine_client,
|
||||
fork_choice_state,
|
||||
Some(engine_payload_attributes),
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug!("FCU result: {:?}", fcu_result);
|
||||
|
||||
// check if we got a valid payload ID
|
||||
match fcu_result.payload_status.status {
|
||||
PayloadStatusEnum::Valid => {
|
||||
if let Some(payload_id) = fcu_result.payload_id {
|
||||
debug!("Got payload ID: {payload_id}");
|
||||
|
||||
// get the payload that was built
|
||||
let _engine_payload =
|
||||
EngineApiClient::<Engine>::get_payload_v3(engine_client, payload_id)
|
||||
.await?;
|
||||
Ok(())
|
||||
} else {
|
||||
Err(eyre::eyre!("No payload ID returned from forkchoiceUpdated"))
|
||||
}
|
||||
}
|
||||
_ => Err(eyre::eyre!("Payload status not valid: {:?}", fcu_result.payload_status)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -121,15 +184,30 @@ pub struct SubmitTransaction {
|
||||
/// The node index to submit to
|
||||
pub node_idx: usize,
|
||||
/// The raw transaction bytes
|
||||
pub raw_tx: Vec<u8>,
|
||||
pub raw_tx: Bytes,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for SubmitTransaction
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
impl<I: Sync> Action<I> for SubmitTransaction {
|
||||
fn execute<'a>(&'a mut self, env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move {
|
||||
if self.node_idx >= env.node_clients.len() {
|
||||
return Err(eyre::eyre!("Node index out of bounds: {}", self.node_idx));
|
||||
}
|
||||
|
||||
let node_client = &env.node_clients[self.node_idx];
|
||||
let rpc_client = &node_client.rpc;
|
||||
|
||||
let tx_hash =
|
||||
EthApiClient::<Transaction, Block, Receipt, Header>::send_raw_transaction(
|
||||
rpc_client,
|
||||
self.raw_tx.clone(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
debug!("Transaction submitted with hash: {}", tx_hash);
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,30 +220,7 @@ pub struct CreatePayload {
|
||||
pub attributes: PayloadAttributes,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for CreatePayload
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute forkchoiceUpdated with the given state.
|
||||
#[derive(Debug)]
|
||||
pub struct ForkchoiceUpdated {
|
||||
/// The node index to use
|
||||
pub node_idx: usize,
|
||||
/// Forkchoice state (head, safe, finalized block hashes)
|
||||
pub state: (B256, B256, B256),
|
||||
/// Payload attributes (optional)
|
||||
pub attributes: Option<PayloadAttributes>,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for ForkchoiceUpdated
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for CreatePayload {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -180,10 +235,7 @@ pub struct NewPayload {
|
||||
pub payload: ExecutionPayload,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for NewPayload
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for NewPayload {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -200,10 +252,7 @@ pub struct GetBlock {
|
||||
pub block_hash: Option<B256>,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for GetBlock
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for GetBlock {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -220,10 +269,7 @@ pub struct GetTransactionCount {
|
||||
pub block_id: BlockId,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for GetTransactionCount
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for GetTransactionCount {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -242,10 +288,7 @@ pub struct GetStorageAt {
|
||||
pub block_id: BlockId,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for GetStorageAt
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for GetStorageAt {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -262,10 +305,7 @@ pub struct Call {
|
||||
pub block_id: BlockId,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for Call
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for Call {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -280,10 +320,7 @@ pub struct WaitForBlocks {
|
||||
pub blocks: u64,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for WaitForBlocks
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for WaitForBlocks {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move { Ok(()) })
|
||||
}
|
||||
@@ -300,10 +337,7 @@ pub struct ChainReorg {
|
||||
pub new_blocks: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<I> Action<I> for ChainReorg
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I> Action<I> for ChainReorg {
|
||||
fn execute<'a>(&'a mut self, _env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move {
|
||||
// 1. Reorg the chain to the specified depth
|
||||
@@ -328,16 +362,14 @@ impl<I> Sequence<I> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Action<I> for Sequence<I>
|
||||
where
|
||||
I: Send + Sync + 'static,
|
||||
{
|
||||
impl<I: Sync + 'static> Action<I> for Sequence<I> {
|
||||
fn execute<'a>(&'a mut self, env: &'a Environment<I>) -> BoxFuture<'a, Result<()>> {
|
||||
Box::pin(async move {
|
||||
// Execute each action in sequence
|
||||
for action in &mut self.actions {
|
||||
action.execute(env).await?;
|
||||
}
|
||||
futures_util::future::try_join_all(
|
||||
self.actions.iter_mut().map(|action| action.execute(env)),
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
|
||||
1
crates/e2e-test-utils/src/testsuite/assets/genesis.json
Normal file
1
crates/e2e-test-utils/src/testsuite/assets/genesis.json
Normal file
@@ -0,0 +1 @@
|
||||
{"config":{"chainId":1,"homesteadBlock":0,"daoForkSupport":true,"eip150Block":0,"eip155Block":0,"eip158Block":0,"byzantiumBlock":0,"constantinopleBlock":0,"petersburgBlock":0,"istanbulBlock":0,"muirGlacierBlock":0,"berlinBlock":0,"londonBlock":0,"arrowGlacierBlock":0,"grayGlacierBlock":0,"shanghaiTime":0,"cancunTime":0,"terminalTotalDifficulty":"0x0","terminalTotalDifficultyPassed":true},"nonce":"0x0","timestamp":"0x0","extraData":"0x00","gasLimit":"0x1c9c380","difficulty":"0x0","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","coinbase":"0x0000000000000000000000000000000000000000","alloc":{"0x14dc79964da2c08b23698b3d3cc7ca32193d9955":{"balance":"0xd3c21bcecceda1000000"},"0x15d34aaf54267db7d7c367839aaf71a00a2c6a65":{"balance":"0xd3c21bcecceda1000000"},"0x1cbd3b2770909d4e10f157cabc84c7264073c9ec":{"balance":"0xd3c21bcecceda1000000"},"0x23618e81e3f5cdf7f54c3d65f7fbc0abf5b21e8f":{"balance":"0xd3c21bcecceda1000000"},"0x2546bcd3c84621e976d8185a91a922ae77ecec30":{"balance":"0xd3c21bcecceda1000000"},"0x3c44cdddb6a900fa2b585dd299e03d12fa4293bc":{"balance":"0xd3c21bcecceda1000000"},"0x70997970c51812dc3a010c7d01b50e0d17dc79c8":{"balance":"0xd3c21bcecceda1000000"},"0x71be63f3384f5fb98995898a86b02fb2426c5788":{"balance":"0xd3c21bcecceda1000000"},"0x8626f6940e2eb28930efb4cef49b2d1f2c9c1199":{"balance":"0xd3c21bcecceda1000000"},"0x90f79bf6eb2c4f870365e785982e1f101e93b906":{"balance":"0xd3c21bcecceda1000000"},"0x976ea74026e726554db657fa54763abd0c3a0aa9":{"balance":"0xd3c21bcecceda1000000"},"0x9965507d1a55bcc2695c58ba16fb37d819b0a4dc":{"balance":"0xd3c21bcecceda1000000"},"0x9c41de96b2088cdc640c6182dfcf5491dc574a57":{"balance":"0xd3c21bcecceda1000000"},"0xa0ee7a142d267c1f36714e4a8f75612f20a79720":{"balance":"0xd3c21bcecceda1000000"},"0xbcd4042de499d14e55001ccbb24a551f3b954096":{"balance":"0xd3c21bcecceda1000000"},"0xbda5747bfd65f08deb54cb465eb87d40e51b197e":{"balance":"0xd3c21bcecceda1000000"},"0xcd3b766ccdd6ae721141f452c550ca635964ce71":{"balance":"0xd3c21bcecceda1000000"},"0xdd2fd4581271e230360230f9337d5c0430bf44c0":{"balance":"0xd3c21bcecceda1000000"},"0xdf3e18d64bc6a983f673ab319ccae4f1a57c7097":{"balance":"0xd3c21bcecceda1000000"},"0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266":{"balance":"0xd3c21bcecceda1000000"},"0xfabb0ac9d68b0b445fb7357272ff202c5651694a":{"balance":"0xd3c21bcecceda1000000"}},"number":"0x0"}
|
||||
@@ -1,87 +1,35 @@
|
||||
//! Example tests using the test suite framework.
|
||||
|
||||
use crate::testsuite::{
|
||||
actions::{
|
||||
AssertMineBlock, Call, ForkchoiceUpdated, GetTransactionCount, MineBlock, SubmitTransaction,
|
||||
},
|
||||
actions::AssertMineBlock,
|
||||
setup::{NetworkSetup, Setup},
|
||||
TestBuilder,
|
||||
};
|
||||
use alloy_eips::{BlockId, BlockNumberOrTag};
|
||||
use alloy_primitives::B256;
|
||||
use eyre::Result;
|
||||
use reth_chainspec::{ChainSpecBuilder, MAINNET};
|
||||
use reth_node_ethereum::{EthEngineTypes, EthereumNode};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "empty testsuite impls"]
|
||||
async fn test_testsuite_submit_transaction_and_advance_block() -> Result<()> {
|
||||
async fn test_testsuite_assert_mine_block() -> Result<()> {
|
||||
reth_tracing::init_test_tracing();
|
||||
|
||||
let setup = Setup::default()
|
||||
.with_chain_spec(Arc::new(
|
||||
ChainSpecBuilder::default().chain(MAINNET.chain).cancun_activated().build(),
|
||||
ChainSpecBuilder::default()
|
||||
.chain(MAINNET.chain)
|
||||
.genesis(serde_json::from_str(include_str!("assets/genesis.json")).unwrap())
|
||||
.cancun_activated()
|
||||
.build(),
|
||||
))
|
||||
.with_network(NetworkSetup::single_node());
|
||||
|
||||
let test = TestBuilder::new(())
|
||||
let test = TestBuilder::new()
|
||||
.with_setup(setup)
|
||||
.with_action(SubmitTransaction { node_idx: 0, raw_tx: vec![] })
|
||||
.with_action(AssertMineBlock {
|
||||
node_idx: 0,
|
||||
transactions: vec![],
|
||||
expected_hash: Some(B256::ZERO),
|
||||
});
|
||||
.with_action(AssertMineBlock::<EthEngineTypes>::new(0, vec![], Some(B256::ZERO)));
|
||||
|
||||
test.run().await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "empty testsuite impls"]
|
||||
async fn test_testsuite_chain_reorg() -> Result<()> {
|
||||
let setup = Setup::default()
|
||||
.with_chain_spec(Arc::new(
|
||||
ChainSpecBuilder::default().chain(MAINNET.chain).cancun_activated().build(),
|
||||
))
|
||||
.with_network(NetworkSetup::multi_node(2));
|
||||
|
||||
let test = TestBuilder::new(())
|
||||
.with_setup(setup)
|
||||
.with_action(MineBlock { node_idx: 0, transactions: vec![] })
|
||||
.with_action(ForkchoiceUpdated {
|
||||
node_idx: 1,
|
||||
state: (
|
||||
B256::ZERO, // head block hash
|
||||
B256::ZERO, // safe block hash
|
||||
B256::ZERO, // finalized block hash
|
||||
),
|
||||
attributes: None,
|
||||
})
|
||||
.with_action(GetTransactionCount {
|
||||
node_idx: 1,
|
||||
address: B256::ZERO,
|
||||
block_id: BlockId::Number(BlockNumberOrTag::Latest),
|
||||
});
|
||||
|
||||
test.run().await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore = "empty testsuite impls"]
|
||||
async fn test_testsuite_complex_scenario() -> Result<()> {
|
||||
let setup = Setup::default()
|
||||
.with_chain_spec(Arc::new(
|
||||
ChainSpecBuilder::default().chain(MAINNET.chain).cancun_activated().build(),
|
||||
))
|
||||
.with_network(NetworkSetup::multi_node(3));
|
||||
|
||||
let test = TestBuilder::new(())
|
||||
.with_setup(setup)
|
||||
.with_action(SubmitTransaction { node_idx: 0, raw_tx: vec![] })
|
||||
.with_action(MineBlock { node_idx: 0, transactions: vec![] })
|
||||
.with_action(Call {
|
||||
node_idx: 1,
|
||||
request: Default::default(),
|
||||
block_id: BlockId::Number(BlockNumberOrTag::Latest),
|
||||
});
|
||||
|
||||
test.run().await
|
||||
test.run::<EthereumNode>().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
//! Utilities for running e2e tests against a node or a network of nodes.
|
||||
|
||||
use actions::{Action, ActionBox};
|
||||
use crate::{
|
||||
testsuite::actions::{Action, ActionBox},
|
||||
NodeBuilderHelper, PayloadAttributesBuilder,
|
||||
};
|
||||
use eyre::Result;
|
||||
use jsonrpsee::http_client::{transport::HttpBackend, HttpClient};
|
||||
use reth_engine_local::LocalPayloadAttributesBuilder;
|
||||
use reth_node_api::{NodeTypesWithEngine, PayloadTypes};
|
||||
use reth_rpc_layer::AuthClientService;
|
||||
use setup::Setup;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub mod actions;
|
||||
pub mod setup;
|
||||
@@ -10,71 +18,47 @@ pub mod setup;
|
||||
#[cfg(test)]
|
||||
mod examples;
|
||||
|
||||
/// A runner performs operations on an environment.
|
||||
/// Client handles for both regular RPC and Engine API endpoints
|
||||
#[derive(Debug)]
|
||||
pub struct Runner<I> {
|
||||
/// The environment containing the node(s) to test
|
||||
env: Environment<I>,
|
||||
}
|
||||
|
||||
impl<I: 'static> Runner<I> {
|
||||
/// Create a new test runner with the given environment
|
||||
pub fn new(instance: I) -> Self {
|
||||
Self { env: Environment { instance, ctx: () } }
|
||||
}
|
||||
|
||||
/// Execute an action
|
||||
pub async fn execute(&mut self, action: ActionBox<I>) -> Result<()> {
|
||||
action.execute(&self.env).await
|
||||
}
|
||||
|
||||
/// Execute a sequence of actions
|
||||
pub async fn run_actions(&mut self, actions: Vec<ActionBox<I>>) -> Result<()> {
|
||||
for action in actions {
|
||||
self.execute(action).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Run a complete test scenario with setup and actions
|
||||
pub async fn run_scenario(
|
||||
&mut self,
|
||||
setup: Option<Setup>,
|
||||
actions: Vec<ActionBox<I>>,
|
||||
) -> Result<()> {
|
||||
if let Some(setup) = setup {
|
||||
setup.apply(&mut self.env).await?;
|
||||
}
|
||||
|
||||
self.run_actions(actions).await
|
||||
}
|
||||
pub struct NodeClient {
|
||||
/// Regular JSON-RPC client
|
||||
pub rpc: HttpClient,
|
||||
/// Engine API client
|
||||
pub engine: HttpClient<AuthClientService<HttpBackend>>,
|
||||
}
|
||||
|
||||
/// Represents a test environment.
|
||||
#[derive(Debug)]
|
||||
pub struct Environment<I> {
|
||||
/// The instance against which we can run tests.
|
||||
pub instance: I,
|
||||
/// Context.
|
||||
pub ctx: (),
|
||||
/// Combined clients with both RPC and Engine API endpoints
|
||||
pub node_clients: Vec<NodeClient>,
|
||||
/// Tracks instance generic.
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl<I> Default for Environment<I> {
|
||||
fn default() -> Self {
|
||||
Self { node_clients: vec![], _phantom: Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Builder for creating test scenarios
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Default)]
|
||||
pub struct TestBuilder<I> {
|
||||
instance: I,
|
||||
setup: Option<Setup>,
|
||||
setup: Option<Setup<I>>,
|
||||
actions: Vec<ActionBox<I>>,
|
||||
env: Environment<I>,
|
||||
}
|
||||
|
||||
impl<I: 'static> TestBuilder<I> {
|
||||
/// Create a new test builder
|
||||
pub fn new(instance: I) -> Self {
|
||||
Self { instance, setup: None, actions: Vec::new() }
|
||||
pub fn new() -> Self {
|
||||
Self { setup: None, actions: Vec::new(), env: Default::default() }
|
||||
}
|
||||
|
||||
/// Set the test setup
|
||||
pub fn with_setup(mut self, setup: Setup) -> Self {
|
||||
pub fn with_setup(mut self, setup: Setup<I>) -> Self {
|
||||
self.setup = Some(setup);
|
||||
self
|
||||
}
|
||||
@@ -84,13 +68,44 @@ impl<I: 'static> TestBuilder<I> {
|
||||
where
|
||||
A: Action<I>,
|
||||
{
|
||||
self.actions.push(ActionBox::new(action));
|
||||
self.actions.push(ActionBox::<I>::new(action));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple actions to the test
|
||||
pub fn with_actions<II, A>(mut self, actions: II) -> Self
|
||||
where
|
||||
II: IntoIterator<Item = A>,
|
||||
A: Action<I>,
|
||||
{
|
||||
self.actions.extend(actions.into_iter().map(ActionBox::new));
|
||||
self
|
||||
}
|
||||
|
||||
/// Run the test scenario
|
||||
pub async fn run(self) -> Result<()> {
|
||||
let mut runner = Runner::new(self.instance);
|
||||
runner.run_scenario(self.setup, self.actions).await
|
||||
pub async fn run<N>(mut self) -> Result<()>
|
||||
where
|
||||
N: NodeBuilderHelper,
|
||||
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
|
||||
<<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadAttributes,
|
||||
>,
|
||||
{
|
||||
let mut setup = self.setup.take();
|
||||
|
||||
if let Some(ref mut s) = setup {
|
||||
s.apply::<N>(&mut self.env).await?;
|
||||
}
|
||||
|
||||
let actions = std::mem::take(&mut self.actions);
|
||||
|
||||
for action in actions {
|
||||
action.execute(&self.env).await?;
|
||||
}
|
||||
|
||||
// explicitly drop the setup to shutdown the nodes
|
||||
// after all actions have completed
|
||||
drop(setup);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,29 @@
|
||||
//! Test setup utilities for configuring the initial state.
|
||||
|
||||
use crate::testsuite::Environment;
|
||||
use eyre::Result;
|
||||
use crate::{setup_engine, testsuite::Environment, NodeBuilderHelper, PayloadAttributesBuilder};
|
||||
use alloy_eips::BlockNumberOrTag;
|
||||
use alloy_primitives::B256;
|
||||
use alloy_rpc_types_engine::PayloadAttributes;
|
||||
use alloy_rpc_types_eth::{Block as RpcBlock, Header, Receipt, Transaction};
|
||||
use eyre::{eyre, Result};
|
||||
use reth_chainspec::ChainSpec;
|
||||
use reth_engine_local::LocalPayloadAttributesBuilder;
|
||||
use reth_node_api::{NodeTypesWithEngine, PayloadTypes};
|
||||
use reth_node_core::primitives::RecoveredBlock;
|
||||
use reth_payload_builder::EthPayloadBuilderAttributes;
|
||||
use reth_primitives::Block;
|
||||
use reth_rpc_api::clients::EthApiClient;
|
||||
use revm::state::EvmState;
|
||||
use std::sync::Arc;
|
||||
use std::{marker::PhantomData, sync::Arc};
|
||||
use tokio::{
|
||||
sync::mpsc,
|
||||
time::{sleep, Duration},
|
||||
};
|
||||
use tracing::{debug, error};
|
||||
|
||||
/// Configuration for setting up a test environment
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Setup {
|
||||
/// Configuration for setting upa test environment
|
||||
#[derive(Debug)]
|
||||
pub struct Setup<I> {
|
||||
/// Chain specification to use
|
||||
pub chain_spec: Option<Arc<ChainSpec>>,
|
||||
/// Genesis block to use
|
||||
@@ -21,9 +34,39 @@ pub struct Setup {
|
||||
pub state: Option<EvmState>,
|
||||
/// Network configuration
|
||||
pub network: NetworkSetup,
|
||||
/// Shutdown channel to stop nodes when setup is dropped
|
||||
shutdown_tx: Option<mpsc::Sender<()>>,
|
||||
/// Is this setup in dev mode
|
||||
pub is_dev: bool,
|
||||
/// Tracks instance generic.
|
||||
_phantom: PhantomData<I>,
|
||||
}
|
||||
|
||||
impl Setup {
|
||||
impl<I> Default for Setup<I> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
chain_spec: None,
|
||||
genesis: None,
|
||||
blocks: Vec::new(),
|
||||
state: None,
|
||||
network: NetworkSetup::default(),
|
||||
shutdown_tx: None,
|
||||
is_dev: true,
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Drop for Setup<I> {
|
||||
fn drop(&mut self) {
|
||||
// Send shutdown signal if the channel exists
|
||||
if let Some(tx) = self.shutdown_tx.take() {
|
||||
let _ = tx.try_send(());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> Setup<I> {
|
||||
/// Create a new setup with default values
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@@ -65,15 +108,120 @@ impl Setup {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set dev mode
|
||||
pub fn with_dev_mode(mut self, is_dev: bool) -> Self {
|
||||
self.is_dev = is_dev;
|
||||
self
|
||||
}
|
||||
|
||||
/// Apply the setup to the environment
|
||||
pub async fn apply<I>(&self, _env: &mut Environment<I>) -> Result<()> {
|
||||
// Apply chain spec, genesis, blocks, state, and network configuration
|
||||
// This would involve setting up the node(s) with the specified configuration
|
||||
// and ensuring it's in the desired state before running tests
|
||||
pub async fn apply<N>(&mut self, env: &mut Environment<I>) -> Result<()>
|
||||
where
|
||||
N: NodeBuilderHelper,
|
||||
LocalPayloadAttributesBuilder<N::ChainSpec>: PayloadAttributesBuilder<
|
||||
<<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadAttributes,
|
||||
>,
|
||||
{
|
||||
let chain_spec =
|
||||
self.chain_spec.clone().ok_or_else(|| eyre!("Chain specification is required"))?;
|
||||
|
||||
// For each block in self.blocks, replay it on the node
|
||||
let (shutdown_tx, mut shutdown_rx) = mpsc::channel(1);
|
||||
|
||||
// Set up the network connections if multiple nodes
|
||||
self.shutdown_tx = Some(shutdown_tx);
|
||||
|
||||
let is_dev = self.is_dev;
|
||||
let node_count = self.network.node_count;
|
||||
|
||||
let attributes_generator = move |timestamp| {
|
||||
let attributes = PayloadAttributes {
|
||||
timestamp,
|
||||
prev_randao: B256::ZERO,
|
||||
suggested_fee_recipient: alloy_primitives::Address::ZERO,
|
||||
withdrawals: Some(vec![]),
|
||||
parent_beacon_block_root: Some(B256::ZERO),
|
||||
};
|
||||
<<N as NodeTypesWithEngine>::Engine as PayloadTypes>::PayloadBuilderAttributes::from(
|
||||
EthPayloadBuilderAttributes::new(B256::ZERO, attributes),
|
||||
)
|
||||
};
|
||||
|
||||
let result = setup_engine::<N>(
|
||||
node_count,
|
||||
Arc::<N::ChainSpec>::new((*chain_spec).clone().into()),
|
||||
is_dev,
|
||||
attributes_generator,
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut node_clients = Vec::new();
|
||||
match result {
|
||||
Ok((nodes, executor, _wallet)) => {
|
||||
// create HTTP clients for each node's RPC and Engine API endpoints
|
||||
for node in &nodes {
|
||||
let rpc = node
|
||||
.rpc_client()
|
||||
.ok_or_else(|| eyre!("Failed to create HTTP RPC client for node"))?;
|
||||
let engine = node.engine_api_client();
|
||||
|
||||
node_clients.push(crate::testsuite::NodeClient { rpc, engine });
|
||||
}
|
||||
|
||||
// spawn a separate task just to handle the shutdown
|
||||
tokio::spawn(async move {
|
||||
// keep nodes and executor in scope to ensure they're not dropped
|
||||
let _nodes = nodes;
|
||||
let _executor = executor;
|
||||
// Wait for shutdown signal
|
||||
let _ = shutdown_rx.recv().await;
|
||||
// nodes and executor will be dropped here when the test completes
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to setup nodes: {}", e);
|
||||
return Err(eyre!("Failed to setup nodes: {}", e));
|
||||
}
|
||||
}
|
||||
|
||||
if node_clients.is_empty() {
|
||||
return Err(eyre!("No nodes were created"));
|
||||
}
|
||||
|
||||
// wait for all nodes to be ready to accept RPC requests before proceeding
|
||||
for (idx, client) in node_clients.iter().enumerate() {
|
||||
let mut retry_count = 0;
|
||||
const MAX_RETRIES: usize = 5;
|
||||
let mut last_error = None;
|
||||
|
||||
while retry_count < MAX_RETRIES {
|
||||
match EthApiClient::<Transaction, RpcBlock, Receipt, Header>::block_by_number(
|
||||
&client.rpc,
|
||||
BlockNumberOrTag::Latest,
|
||||
false,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(_) => {
|
||||
debug!("Node {idx} RPC endpoint is ready");
|
||||
break;
|
||||
}
|
||||
Err(e) => {
|
||||
last_error = Some(e);
|
||||
retry_count += 1;
|
||||
debug!(
|
||||
"Node {idx} RPC endpoint not ready, retry {retry_count}/{MAX_RETRIES}"
|
||||
);
|
||||
sleep(Duration::from_millis(500)).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
if retry_count == MAX_RETRIES {
|
||||
return Err(eyre!("Failed to connect to node {idx} RPC endpoint after {MAX_RETRIES} retries: {:?}", last_error));
|
||||
}
|
||||
}
|
||||
|
||||
env.node_clients = node_clients;
|
||||
|
||||
// TODO: For each block in self.blocks, replay it on the node
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -88,18 +236,16 @@ pub struct Genesis {}
|
||||
pub struct NetworkSetup {
|
||||
/// Number of nodes to create
|
||||
pub node_count: usize,
|
||||
/// Whether to disable discovery
|
||||
pub disable_discovery: bool,
|
||||
}
|
||||
|
||||
impl NetworkSetup {
|
||||
/// Create a new network setup with a single node
|
||||
pub fn single_node() -> Self {
|
||||
Self { node_count: 1, disable_discovery: true }
|
||||
Self { node_count: 1 }
|
||||
}
|
||||
|
||||
/// Create a new network setup with multiple nodes
|
||||
pub fn multi_node(count: usize) -> Self {
|
||||
Self { node_count: count, disable_discovery: true }
|
||||
Self { node_count: count }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +381,12 @@ impl From<Genesis> for OpChainSpec {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChainSpec> for OpChainSpec {
|
||||
fn from(value: ChainSpec) -> Self {
|
||||
Self { inner: value }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct OpGenesisInfo {
|
||||
optimism_chain_info: op_alloy_rpc_types::OpChainInfo,
|
||||
|
||||
@@ -142,6 +142,14 @@ impl<T: Decodable2718 + Send + Sync + Debug> PayloadBuilderAttributes
|
||||
}
|
||||
}
|
||||
|
||||
impl<OpTransactionSigned> From<EthPayloadBuilderAttributes>
|
||||
for OpPayloadBuilderAttributes<OpTransactionSigned>
|
||||
{
|
||||
fn from(value: EthPayloadBuilderAttributes) -> Self {
|
||||
Self { payload_attributes: value, ..Default::default() }
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains the built payload.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct OpBuiltPayload<N: NodePrimitives = OpPrimitives> {
|
||||
|
||||
Reference in New Issue
Block a user