reth icon indicating copy to clipboard operation
reth copied to clipboard

EthApi Access or Instantiation Example for ExEx

Open binarynightowl opened this issue 7 months ago • 4 comments
trafficstars

Describe the change

There is examples for accessing the installed EthApi and TraceApi from the node outside of an exex as I am showing in the main function, which successfully allows me to simulate transactions for the mempool as shown. For example if an Exex wanted to to access this api, I created a helper function to help instantiate this since the ExEx ctx doesnt seem to have a rpc_registry to access the installed ones.

@mattsse wanted me to make an issue here to get a proper example made, If anyone can show me what I may be doing wrong instantiating this EthApi (or if theres a way to access the built in one) I would love to update the examples and make a PR.

use clap::Parser;
use eyre::Result;
use futures_util::{StreamExt, TryStreamExt};
use reth::api::{FullNodeComponents, NodeTypes};
use reth::chainspec::EthChainSpec;
use reth::primitives::EthereumHardforks;
use reth::rpc::api::eth::helpers::Call;
use reth::rpc::types::state::EvmOverrides;
use reth::rpc::types::BlockNumberOrTag;
use reth::{
    builder::NodeHandle, chainspec::EthereumChainSpecParser, cli::Cli,
    rpc::types::TransactionRequest, transaction_pool::TransactionPool,
};
use reth_evm::{ConfigureEvm, Evm};
use reth_exex::{ExExContext, ExExEvent};
use reth_node_ethereum::node::EthereumNode;
use reth_rpc::EthApi;
use reth_rpc_eth_types::{
    EthStateCache, EthStateCacheConfig, FeeHistoryCache,
    FeeHistoryCacheConfig, GasPriceOracle,
};
use reth_rpc_server_types::constants::{
    DEFAULT_ETH_PROOF_WINDOW, DEFAULT_MAX_SIMULATE_BLOCKS, DEFAULT_PROOF_PERMITS,
};
use reth_tasks::pool::BlockingTaskPool;
use std::future::Future;

pub(crate) fn create_eth_api<Node>(ctx: &ExExContext<Node>) -> reth_rpc::eth::core::EthApiFor<Node>
where
    Node: FullNodeComponents,
{
    let provider = ctx.components.provider().clone();
    let cache = EthStateCache::spawn_with(
        provider.clone(),
        EthStateCacheConfig::default(),
        ctx.task_executor().clone(),
    );
    let fee_history_cache = FeeHistoryCache::new(FeeHistoryCacheConfig::default());
    let gas_oracle = GasPriceOracle::new(provider.clone(), Default::default(), cache.clone());

    EthApi::new(
        provider.clone(),
        ctx.pool().clone(),
        ctx.network().clone(),
        cache.clone(),
        gas_oracle,
        ctx.config.chain.genesis().gas_limit,
        DEFAULT_MAX_SIMULATE_BLOCKS,
        DEFAULT_ETH_PROOF_WINDOW,
        BlockingTaskPool::build().expect("failed to build tracing pool"),
        fee_history_cache,
        ctx.components.evm_config().clone(),
        DEFAULT_PROOF_PERMITS,
    )
}

async fn init<Node: FullNodeComponents>(
    ctx: ExExContext<Node>,
) -> Result<impl Future<Output = Result<()>>>
where
    Node::Types: NodeTypes,
    <Node::Types as NodeTypes>::ChainSpec: EthereumHardforks,
{
    let eth_api = create_eth_api(&ctx);

    let mut pending_transactions = ctx.pool().new_pending_pool_transactions_listener();
    let evm_config = ctx.evm_config().clone();
    tokio::spawn(async move {
        // Waiting for new transactions
        while let Some(event) = pending_transactions.next().await {
            let tx = event.transaction;
            println!("Transaction received: {tx:?}");

            if let Some(_recipient) = tx.to() {
                let transaction_request =
                    TransactionRequest::from_recovered_transaction(tx.to_consensus());
                let sim = eth_api.spawn_with_call_at(
                    transaction_request,
                    BlockNumberOrTag::Latest.into(),
                    EvmOverrides::default(),
                    move |db, evm_env, tx_env| {
                        let mut evm = evm_config.evm_with_env(db, evm_env);
                        let result = evm.transact(tx_env)?;
                        Ok(result)
                    },
                );
                if let Ok(sim_result) = sim.await {
                    println!("sim result for transaction: {sim_result:?}");
                }
            }
        }
    });
    Ok(my_exex(ctx))
}

async fn my_exex<Node: FullNodeComponents>(mut ctx: ExExContext<Node>) -> Result<()>
where
    Node::Types: NodeTypes,
    <Node::Types as NodeTypes>::ChainSpec: EthereumHardforks,
{
    while let Some(notification) = ctx.notifications.try_next().await? {
        // match &notification {
        //     ExExNotification::ChainCommitted { new } => {
        //         info!(committed_chain = ?new.range(), "Received commit");
        //     }
        //     ExExNotification::ChainReorged { old, new } => {
        //         info!(from_chain = ?old.range(), to_chain = ?new.range(), "Received reorg");
        //     }
        //     ExExNotification::ChainReverted { old } => {
        //         info!(reverted_chain = ?old.range(), "Received revert");
        //     }
        // };

        if let Some(committed_chain) = notification.committed_chain() {
            ctx.events
                .send(ExExEvent::FinishedHeight(committed_chain.tip().num_hash()))?;
        }
    }

    Ok(())
}

fn print_type_of<T>(_: &T) {
    println!("{}", std::any::type_name::<T>());
}

fn main() {
    Cli::<EthereumChainSpecParser>::parse()
        .run(|builder, args| async move {
            // launch the node
            let NodeHandle {
                node,
                node_exit_future,
            } = builder
                .node(EthereumNode::default())
                .install_exex("my-exex", init)
                .launch()
                .await?;

            let eth_api = node.rpc_registry.eth_api().clone();

            let mut pending_transactions = node.pool.new_pending_pool_transactions_listener();
            node.task_executor.spawn(Box::pin(async move {
                // Waiting for new transactions
                while let Some(event) = pending_transactions.next().await {
                    let tx = event.transaction;
                    println!("Transaction received: {tx:?}");

                    if let Some(_recipient) = tx.to() {
                        let transaction_request =
                            TransactionRequest::from_recovered_transaction(tx.to_consensus());

                        let evm_config = node.evm_config.clone();
                        let sim = eth_api.spawn_with_call_at(
                            transaction_request,
                            BlockNumberOrTag::Latest.into(),
                            EvmOverrides::default(),
                            move |db, evm_env, tx_env| {
                                let mut evm = evm_config.evm_with_env(db, evm_env);
                                let result = evm.transact(tx_env)?;
                                Ok(result)
                            },
                        );
                        if let Ok(sim_result) = sim.await {
                            println!("sim result for transaction: {sim_result:?}");
                        }
                    }
                }
            }));

            node_exit_future.await
        })
        .unwrap();
}

The code in the main loop works well, but when I call any useful methods on the EthApi I create in the ExEx like .spawn_with_call_at give me an error:

error[E0599]: the method `spawn_with_call_at` exists for struct `EthApi<<Node as FullNodeTypes>::Provider, ..., ..., ...>`, but its trait bounds were not satisfied
  --> mainnet-observer/src/main.rs:77:35
   |
77 |                 let sim = eth_api.spawn_with_call_at(
   |                           --------^^^^^^^^^^^^^^^^^^ method cannot be called due to unsatisfied trait bounds
   |
  ::: /Users/tdettling/.cargo/git/checkouts/reth-e231042ee7db3fb7/ed7da87/crates/rpc/rpc-eth-types/src/error/mod.rs:43:1
   |
43 | pub enum EthApiError {
   | -------------------- doesn't satisfy `_: FromEvmError<<Node as FullNodeComponents>::Evm>`
   |
  ::: /Users/tdettling/.cargo/git/checkouts/reth-e231042ee7db3fb7/ed7da87/crates/rpc/rpc/src/eth/core.rs:63:1
   |
63 | pub struct EthApi<Provider: BlockReader, Pool, Network, EvmConfig> {
   | ------------------------------------------------------------------ doesn't satisfy `_: Call`
   |
   = note: the following trait bounds were not satisfied:
           `<<<<Node as FullNodeComponents>::Evm as ConfigureEvm>::BlockExecutorFactory as BlockExecutorFactory>::EvmFactory as EvmFactory>::Tx = TxEnv`
           which is required by `EthApi<<Node as FullNodeTypes>::Provider, <Node as FullNodeComponents>::Pool, <Node as FullNodeComponents>::Network, <Node as FullNodeComponents>::Evm>: reth::rpc::api::reth_rpc_eth_api::helpers::Call`
           `EthApiError: FromEvmError<<Node as FullNodeComponents>::Evm>`
           which is required by `EthApi<<Node as FullNodeTypes>::Provider, <Node as FullNodeComponents>::Pool, <Node as FullNodeComponents>::Network, <Node as FullNodeComponents>::Evm>: reth::rpc::api::reth_rpc_eth_api::helpers::Call`

Additional context

No response

binarynightowl avatar Apr 07 '25 14:04 binarynightowl