#![doc(
html_logo_url = "https://raw.githubusercontent.com/paradigmxyz/reth/main/assets/reth-docs.png",
html_favicon_url = "https://avatars0.githubusercontent.com/u/97369466?s=256",
issue_tracker_base_url = "https://github.com/paradigmxyz/reth/issues/"
)]
#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use std::{
fmt::Debug,
future::{poll_fn, Future},
sync::Arc,
task::Poll,
};
use alloy_eips::BlockNumHash;
use futures_util::FutureExt;
use reth_blockchain_tree::noop::NoopBlockchainTree;
use reth_chainspec::{ChainSpec, MAINNET};
use reth_consensus::test_utils::TestConsensus;
use reth_db::{
test_utils::{create_test_rw_db, create_test_static_files_dir, TempDatabase},
DatabaseEnv,
};
use reth_db_common::init::init_genesis;
use reth_evm::test_utils::MockExecutorProvider;
use reth_execution_types::Chain;
use reth_exex::{ExExContext, ExExEvent, ExExNotification, ExExNotifications, Wal};
use reth_network::{config::SecretKey, NetworkConfigBuilder, NetworkManager};
use reth_node_api::{
FullNodeTypes, FullNodeTypesAdapter, NodePrimitives, NodeTypes, NodeTypesWithDBAdapter,
NodeTypesWithEngine,
};
use reth_node_builder::{
components::{
Components, ComponentsBuilder, ConsensusBuilder, ExecutorBuilder, NodeComponentsBuilder,
PoolBuilder,
},
BuilderContext, Node, NodeAdapter, RethFullAdapter,
};
use reth_node_core::node_config::NodeConfig;
use reth_node_ethereum::{
node::{EthereumAddOns, EthereumNetworkBuilder, EthereumPayloadBuilder},
EthEngineTypes, EthEvmConfig,
};
use reth_payload_builder::noop::NoopPayloadBuilderService;
use reth_primitives::{BlockExt, EthPrimitives, Head, SealedBlockWithSenders, TransactionSigned};
use reth_provider::{
providers::{BlockchainProvider, StaticFileProvider},
BlockReader, EthStorage, ProviderFactory,
};
use reth_tasks::TaskManager;
use reth_transaction_pool::test_utils::{testing_pool, TestPool};
use tempfile::TempDir;
use thiserror::Error;
use tokio::sync::mpsc::{Sender, UnboundedReceiver};
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct TestPoolBuilder;
impl<Node> PoolBuilder<Node> for TestPoolBuilder
where
Node: FullNodeTypes<Types: NodeTypes<Primitives: NodePrimitives<SignedTx = TransactionSigned>>>,
{
type Pool = TestPool;
async fn build_pool(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Pool> {
Ok(testing_pool())
}
}
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct TestExecutorBuilder;
impl<Node> ExecutorBuilder<Node> for TestExecutorBuilder
where
Node: FullNodeTypes<Types: NodeTypes<ChainSpec = ChainSpec, Primitives = EthPrimitives>>,
{
type EVM = EthEvmConfig;
type Executor = MockExecutorProvider;
async fn build_evm(
self,
ctx: &BuilderContext<Node>,
) -> eyre::Result<(Self::EVM, Self::Executor)> {
let evm_config = EthEvmConfig::new(ctx.chain_spec());
let executor = MockExecutorProvider::default();
Ok((evm_config, executor))
}
}
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct TestConsensusBuilder;
impl<Node> ConsensusBuilder<Node> for TestConsensusBuilder
where
Node: FullNodeTypes,
{
type Consensus = Arc<TestConsensus>;
async fn build_consensus(self, _ctx: &BuilderContext<Node>) -> eyre::Result<Self::Consensus> {
Ok(Arc::new(TestConsensus::default()))
}
}
#[derive(Debug, Default, Clone, Copy)]
#[non_exhaustive]
pub struct TestNode;
impl NodeTypes for TestNode {
type Primitives = EthPrimitives;
type ChainSpec = ChainSpec;
type StateCommitment = reth_trie_db::MerklePatriciaTrie;
type Storage = EthStorage;
}
impl NodeTypesWithEngine for TestNode {
type Engine = EthEngineTypes;
}
impl<N> Node<N> for TestNode
where
N: FullNodeTypes<
Types: NodeTypesWithEngine<
Engine = EthEngineTypes,
ChainSpec = ChainSpec,
Primitives = EthPrimitives,
Storage = EthStorage,
>,
>,
{
type ComponentsBuilder = ComponentsBuilder<
N,
TestPoolBuilder,
EthereumPayloadBuilder,
EthereumNetworkBuilder,
TestExecutorBuilder,
TestConsensusBuilder,
>;
type AddOns = EthereumAddOns<
NodeAdapter<N, <Self::ComponentsBuilder as NodeComponentsBuilder<N>>::Components>,
>;
fn components_builder(&self) -> Self::ComponentsBuilder {
ComponentsBuilder::default()
.node_types::<N>()
.pool(TestPoolBuilder::default())
.payload(EthereumPayloadBuilder::default())
.network(EthereumNetworkBuilder::default())
.executor(TestExecutorBuilder::default())
.consensus(TestConsensusBuilder::default())
}
fn add_ons(&self) -> Self::AddOns {
EthereumAddOns::default()
}
}
pub type TmpDB = Arc<TempDatabase<DatabaseEnv>>;
pub type Adapter = NodeAdapter<
RethFullAdapter<TmpDB, TestNode>,
<<TestNode as Node<
FullNodeTypesAdapter<
TestNode,
TmpDB,
BlockchainProvider<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
>,
>>::ComponentsBuilder as NodeComponentsBuilder<RethFullAdapter<TmpDB, TestNode>>>::Components,
>;
pub type TestExExContext = ExExContext<Adapter>;
#[derive(Debug)]
pub struct TestExExHandle {
pub genesis: SealedBlockWithSenders,
pub provider_factory: ProviderFactory<NodeTypesWithDBAdapter<TestNode, TmpDB>>,
pub events_rx: UnboundedReceiver<ExExEvent>,
pub notifications_tx: Sender<ExExNotification>,
pub tasks: TaskManager,
_wal_directory: TempDir,
}
impl TestExExHandle {
pub async fn send_notification_chain_committed(&self, chain: Chain) -> eyre::Result<()> {
self.notifications_tx
.send(ExExNotification::ChainCommitted { new: Arc::new(chain) })
.await?;
Ok(())
}
pub async fn send_notification_chain_reorged(
&self,
old: Chain,
new: Chain,
) -> eyre::Result<()> {
self.notifications_tx
.send(ExExNotification::ChainReorged { old: Arc::new(old), new: Arc::new(new) })
.await?;
Ok(())
}
pub async fn send_notification_chain_reverted(&self, chain: Chain) -> eyre::Result<()> {
self.notifications_tx
.send(ExExNotification::ChainReverted { old: Arc::new(chain) })
.await?;
Ok(())
}
#[track_caller]
pub fn assert_events_empty(&self) {
assert!(self.events_rx.is_empty());
}
#[track_caller]
pub fn assert_event_finished_height(&mut self, height: BlockNumHash) -> eyre::Result<()> {
let event = self.events_rx.try_recv()?;
assert_eq!(event, ExExEvent::FinishedHeight(height));
Ok(())
}
}
pub async fn test_exex_context_with_chain_spec(
chain_spec: Arc<ChainSpec>,
) -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
let transaction_pool = testing_pool();
let evm_config = EthEvmConfig::new(chain_spec.clone());
let executor = MockExecutorProvider::default();
let consensus = Arc::new(TestConsensus::default());
let (static_dir, _) = create_test_static_files_dir();
let db = create_test_rw_db();
let provider_factory = ProviderFactory::<NodeTypesWithDBAdapter<TestNode, _>>::new(
db,
chain_spec.clone(),
StaticFileProvider::read_write(static_dir.into_path()).expect("static file provider"),
);
let genesis_hash = init_genesis(&provider_factory)?;
let provider =
BlockchainProvider::new(provider_factory.clone(), Arc::new(NoopBlockchainTree::default()))?;
let network_manager = NetworkManager::new(
NetworkConfigBuilder::new(SecretKey::new(&mut rand::thread_rng()))
.with_unused_discovery_port()
.with_unused_listener_port()
.build(provider_factory.clone()),
)
.await?;
let network = network_manager.handle().clone();
let tasks = TaskManager::current();
let task_executor = tasks.executor();
tasks.executor().spawn(network_manager);
let (_, payload_builder) = NoopPayloadBuilderService::<EthEngineTypes>::new();
let components = NodeAdapter::<FullNodeTypesAdapter<_, _, _>, _> {
components: Components {
transaction_pool,
evm_config,
executor,
consensus,
network,
payload_builder,
},
task_executor,
provider,
};
let genesis = provider_factory
.block_by_hash(genesis_hash)?
.ok_or_else(|| eyre::eyre!("genesis block not found"))?
.seal_slow()
.seal_with_senders::<reth_primitives::Block>()
.ok_or_else(|| eyre::eyre!("failed to recover senders"))?;
let head = Head {
number: genesis.number,
hash: genesis_hash,
difficulty: genesis.difficulty,
timestamp: genesis.timestamp,
total_difficulty: Default::default(),
};
let wal_directory = tempfile::tempdir()?;
let wal = Wal::new(wal_directory.path())?;
let (events_tx, events_rx) = tokio::sync::mpsc::unbounded_channel();
let (notifications_tx, notifications_rx) = tokio::sync::mpsc::channel(1);
let notifications = ExExNotifications::new(
head,
components.provider.clone(),
components.components.executor.clone(),
notifications_rx,
wal.handle(),
);
let ctx = ExExContext {
head,
config: NodeConfig::test(),
reth_config: reth_config::Config::default(),
events: events_tx,
notifications,
components,
};
Ok((
ctx,
TestExExHandle {
genesis,
provider_factory,
events_rx,
notifications_tx,
tasks,
_wal_directory: wal_directory,
},
))
}
pub async fn test_exex_context() -> eyre::Result<(ExExContext<Adapter>, TestExExHandle)> {
test_exex_context_with_chain_spec(MAINNET.clone()).await
}
pub trait PollOnce {
fn poll_once(&mut self) -> impl Future<Output = Result<(), PollOnceError>> + Send;
}
#[derive(Error, Debug)]
pub enum PollOnceError {
#[error("Execution Extension future returned Ready, but it should never resolve")]
FutureIsReady,
#[error(transparent)]
FutureError(#[from] eyre::Error),
}
impl<F: Future<Output = eyre::Result<()>> + Unpin + Send> PollOnce for F {
async fn poll_once(&mut self) -> Result<(), PollOnceError> {
poll_fn(|cx| match self.poll_unpin(cx) {
Poll::Ready(Ok(())) => Poll::Ready(Err(PollOnceError::FutureIsReady)),
Poll::Ready(Err(err)) => Poll::Ready(Err(PollOnceError::FutureError(err))),
Poll::Pending => Poll::Ready(Ok(())),
})
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn check_test_context_creation() {
let _ = test_exex_context().await.unwrap();
}
}