diff --git a/crates/e2e-test-utils/src/node.rs b/crates/e2e-test-utils/src/node.rs index 4dd1ae63e1a..23990b63ab2 100644 --- a/crates/e2e-test-utils/src/node.rs +++ b/crates/e2e-test-utils/src/node.rs @@ -90,7 +90,11 @@ where where AddOns::EthApi: EthApiSpec>> + EthTransactions - + TraceExt, + + TraceExt + + reth_rpc_eth_api::RpcNodeCore< + Provider = Node::Provider, + Primitives = ::Primitives, + >, { let mut chain = Vec::with_capacity(length as usize); for i in 0..length { diff --git a/crates/e2e-test-utils/src/rpc.rs b/crates/e2e-test-utils/src/rpc.rs index ff030c390b9..8e939c1eac1 100644 --- a/crates/e2e-test-utils/src/rpc.rs +++ b/crates/e2e-test-utils/src/rpc.rs @@ -9,7 +9,7 @@ use reth_provider::BlockReader; use reth_rpc_api::DebugApiServer; use reth_rpc_eth_api::{ helpers::{EthApiSpec, EthTransactions, TraceExt}, - EthApiTypes, + EthApiTypes, RpcNodeCore, }; #[expect(missing_debug_implementations)] @@ -22,7 +22,8 @@ where Node: FullNodeComponents>, EthApi: EthApiSpec>> + EthTransactions - + TraceExt, + + TraceExt + + RpcNodeCore::Primitives>, { /// Injects a raw transaction into the node tx pool via RPC server pub async fn inject_tx(&self, raw_tx: Bytes) -> Result { diff --git a/crates/node/builder/src/rpc.rs b/crates/node/builder/src/rpc.rs index 7fb219ff7af..e8d37de7516 100644 --- a/crates/node/builder/src/rpc.rs +++ b/crates/node/builder/src/rpc.rs @@ -24,14 +24,15 @@ use reth_node_core::{ }; use reth_payload_builder::{PayloadBuilderHandle, PayloadStore}; use reth_rpc::{ - eth::{core::EthRpcConverterFor, DevSigner, EthApiTypes, FullEthApiServer}, + eth::{core::EthRpcConverterFor, DevSigner, EthApiTypes, FullEthApiServer, RpcNodeCore}, AdminApi, }; use reth_rpc_api::{eth::helpers::EthTransactions, IntoEngineApiRpcModule}; use reth_rpc_builder::{ auth::{AuthRpcModule, AuthServerHandle}, config::RethRpcServerConfig, - RpcModuleBuilder, RpcRegistryInner, RpcServerConfig, RpcServerHandle, TransportRpcModules, + RethRpcModule, RpcModuleBuilder, RpcRegistryInner, RpcServerConfig, RpcServerHandle, + TransportRpcModules, }; use reth_rpc_engine_api::{capabilities::EngineCapabilities, EngineApi}; use reth_rpc_eth_types::{cache::cache_new_blocks_task, EthConfig, EthStateCache}; @@ -1001,6 +1002,11 @@ where let auth_config = config.rpc.auth_server_config(jwt_secret)?; let module_config = config.rpc.transport_rpc_module_config(); + // Only start collecting bad blocks if the debug_ endpoint installed + let debug_enabled = + module_config.http().is_some_and(|sel| sel.contains(&RethRpcModule::Debug)) || + module_config.ws().is_some_and(|sel| sel.contains(&RethRpcModule::Debug)) || + module_config.ipc().is_some_and(|sel| sel.contains(&RethRpcModule::Debug)); debug!(target: "reth::cli", http=?module_config.http(), ws=?module_config.ws(), "Using RPC module config"); let (mut modules, mut auth_module, registry) = RpcModuleBuilder::default() @@ -1018,6 +1024,14 @@ where registry.eth_api().signers().write().extend(signers); } + // keep track of invalid blocks for `debug_getBadBlocks` only if debug RPC is enabled + if debug_enabled { + registry.debug_api().spawn_invalid_block_listener( + engine_events.new_listener(), + node.task_executor().clone(), + ); + } + let mut registry = RpcRegistry { registry }; let ctx = RpcContext { node: node.clone(), @@ -1189,6 +1203,7 @@ impl<'a, N: FullNodeComponents: Default + Send + 'static { /// The Ethapi implementation this builder will build. type EthApi: EthApiTypes + + RpcNodeCore> + FullEthApiServer + Unpin + 'static; diff --git a/crates/optimism/rpc/src/eth/mod.rs b/crates/optimism/rpc/src/eth/mod.rs index d5f42a5473d..d49ebc0e948 100644 --- a/crates/optimism/rpc/src/eth/mod.rs +++ b/crates/optimism/rpc/src/eth/mod.rs @@ -21,7 +21,7 @@ pub use receipt::{OpReceiptBuilder, OpReceiptFieldsBuilder}; use reqwest::Url; use reth_chainspec::{EthereumHardforks, Hardforks}; use reth_evm::ConfigureEvm; -use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy, NodeTypes}; +use reth_node_api::{FullNodeComponents, FullNodeTypes, HeaderTy, NodeTypes, PrimitivesTy}; use reth_node_builder::rpc::{EthApiBuilder, EthApiCtx}; use reth_optimism_flashblocks::{ FlashBlockBuildInfo, FlashBlockCompleteSequence, FlashBlockCompleteSequenceRx, @@ -487,8 +487,8 @@ where >, NetworkT: RpcTypes, OpRpcConvert: RpcConvert, - OpEthApi>: - FullEthApiServer, + OpEthApi>: FullEthApiServer + + RpcNodeCore>, { type EthApi = OpEthApi>; diff --git a/crates/rpc/rpc-api/src/debug.rs b/crates/rpc/rpc-api/src/debug.rs index 0fca5f18457..5eae8df091c 100644 --- a/crates/rpc/rpc-api/src/debug.rs +++ b/crates/rpc/rpc-api/src/debug.rs @@ -3,7 +3,7 @@ use alloy_genesis::ChainConfig; use alloy_json_rpc::RpcObject; use alloy_primitives::{Address, Bytes, B256}; use alloy_rpc_types_debug::ExecutionWitness; -use alloy_rpc_types_eth::{Block, Bundle, StateContext}; +use alloy_rpc_types_eth::{BadBlock, Bundle, StateContext}; use alloy_rpc_types_trace::geth::{ BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult, }; @@ -38,7 +38,7 @@ pub trait DebugApi { /// Returns an array of recent bad blocks that the client has seen on the network. #[method(name = "getBadBlocks")] - async fn bad_blocks(&self) -> RpcResult>; + async fn bad_blocks(&self) -> RpcResult>; /// Returns the structured logs created during the execution of EVM between two blocks /// (excluding start) as a JSON object. diff --git a/crates/rpc/rpc-builder/src/lib.rs b/crates/rpc/rpc-builder/src/lib.rs index 5348fedc3af..3c8bbc66e20 100644 --- a/crates/rpc/rpc-builder/src/lib.rs +++ b/crates/rpc/rpc-builder/src/lib.rs @@ -36,8 +36,8 @@ use reth_evm::ConfigureEvm; use reth_network_api::{noop::NoopNetwork, NetworkInfo, Peers}; use reth_primitives_traits::{NodePrimitives, TxTy}; use reth_rpc::{ - AdminApi, DebugApi, EngineEthApi, EthApi, EthApiBuilder, EthBundle, MinerApi, NetApi, - OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi, ValidationApiConfig, Web3Api, + AdminApi, BadBlockStore, DebugApi, EngineEthApi, EthApi, EthApiBuilder, EthBundle, MinerApi, + NetApi, OtterscanApi, RPCApi, RethApi, TraceApi, TxPoolApi, ValidationApiConfig, Web3Api, }; use reth_rpc_api::servers::*; use reth_rpc_eth_api::{ @@ -52,8 +52,7 @@ use reth_rpc_eth_api::{ use reth_rpc_eth_types::{receipt::EthReceiptConverter, EthConfig, EthSubscriptionIdProvider}; use reth_rpc_layer::{AuthLayer, Claims, CompressionLayer, JwtAuthValidator, JwtSecret}; use reth_storage_api::{ - AccountReader, BlockReader, ChangeSetReader, FullRpcProvider, ProviderBlock, - StateProviderFactory, + AccountReader, BlockReader, ChangeSetReader, FullRpcProvider, StateProviderFactory, }; use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner, TokioTaskExecutor}; use reth_transaction_pool::{noop::NoopTransactionPool, TransactionPool}; @@ -332,7 +331,8 @@ where RpcRegistryInner, ) where - EthApi: FullEthApiServer, + EthApi: FullEthApiServer + + RpcNodeCore, { let Self { provider, pool, network, executor, consensus, evm_config, .. } = self; @@ -359,7 +359,7 @@ where eth: EthApi, ) -> RpcRegistryInner where - EthApi: EthApiTypes + 'static, + EthApi: EthApiTypes + RpcNodeCore + 'static, { let Self { provider, pool, network, executor, consensus, evm_config, .. } = self; RpcRegistryInner::new(provider, pool, network, executor, consensus, config, evm_config, eth) @@ -373,7 +373,8 @@ where eth: EthApi, ) -> TransportRpcModules<()> where - EthApi: FullEthApiServer, + EthApi: FullEthApiServer + + RpcNodeCore, { let mut modules = TransportRpcModules::default(); @@ -511,6 +512,8 @@ pub struct RpcRegistryInner< modules: HashMap, /// eth config settings eth_config: EthConfig, + /// Recent bad blocks observed by the node. + bad_block_store: BadBlockStore, } // === impl RpcRegistryInner === @@ -527,7 +530,7 @@ where + 'static, Pool: Send + Sync + Clone + 'static, Network: Clone + 'static, - EthApi: EthApiTypes + 'static, + EthApi: EthApiTypes + RpcNodeCore + 'static, EvmConfig: ConfigureEvm, { /// Creates a new, empty instance. @@ -560,6 +563,7 @@ where blocking_pool_guard, eth_config: config.eth, evm_config, + bad_block_store: BadBlockStore::default(), } } } @@ -595,6 +599,11 @@ where &self.provider } + /// Returns the bad block store. + pub const fn bad_block_store(&self) -> &BadBlockStore { + &self.bad_block_store + } + /// Returns all installed methods pub fn methods(&self) -> Vec { self.modules.values().cloned().collect() @@ -706,8 +715,10 @@ where /// If called outside of the tokio runtime. See also [`Self::eth_api`] pub fn register_debug(&mut self) -> &mut Self where - EthApi: EthApiSpec + EthTransactions + TraceExt, - EvmConfig::Primitives: NodePrimitives>, + EthApi: EthApiSpec + + EthTransactions + + TraceExt + + RpcNodeCore, { let debug_api = self.debug_api(); self.modules.insert(RethRpcModule::Debug, debug_api.into_rpc().into()); @@ -814,8 +825,15 @@ where /// # Panics /// /// If called outside of the tokio runtime. See also [`Self::eth_api`] - pub fn debug_api(&self) -> DebugApi { - DebugApi::new(self.eth_api().clone(), self.blocking_pool_guard.clone()) + pub fn debug_api(&self) -> DebugApi + where + EthApi: EthApiTypes + RpcNodeCore, + { + DebugApi::new( + self.eth_api().clone(), + self.blocking_pool_guard.clone(), + self.bad_block_store.clone(), + ) } /// Instantiates `NetApi` @@ -847,7 +865,7 @@ where + ChangeSetReader, Pool: TransactionPool + Clone + 'static, Network: NetworkInfo + Peers + Clone + 'static, - EthApi: FullEthApiServer, + EthApi: FullEthApiServer + RpcNodeCore, EvmConfig: ConfigureEvm + 'static, Consensus: FullConsensus + Clone + 'static, { @@ -933,11 +951,13 @@ where ) .into_rpc() .into(), - RethRpcModule::Debug => { - DebugApi::new(eth_api.clone(), self.blocking_pool_guard.clone()) - .into_rpc() - .into() - } + RethRpcModule::Debug => DebugApi::new( + eth_api.clone(), + self.blocking_pool_guard.clone(), + self.bad_block_store.clone(), + ) + .into_rpc() + .into(), RethRpcModule::Eth => { // merge all eth handlers let mut module = eth_api.clone().into_rpc(); diff --git a/crates/rpc/rpc-eth-api/src/types.rs b/crates/rpc/rpc-eth-api/src/types.rs index b4b23e01680..c4f723e3eec 100644 --- a/crates/rpc/rpc-eth-api/src/types.rs +++ b/crates/rpc/rpc-eth-api/src/types.rs @@ -28,7 +28,6 @@ pub trait EthApiTypes: Send + Sync + Clone { type NetworkTypes: RpcTypes; /// Conversion methods for transaction RPC type. type RpcConvert: RpcConvert; - /// Returns reference to transaction response builder. fn converter(&self) -> &Self::RpcConvert; } diff --git a/crates/rpc/rpc/src/debug.rs b/crates/rpc/rpc/src/debug.rs index 9a53b2ad3aa..1fb7fe0d34a 100644 --- a/crates/rpc/rpc/src/debug.rs +++ b/crates/rpc/rpc/src/debug.rs @@ -9,50 +9,61 @@ use alloy_primitives::{hex::decode, uint, Address, Bytes, B256}; use alloy_rlp::{Decodable, Encodable}; use alloy_rpc_types_debug::ExecutionWitness; use alloy_rpc_types_eth::{ - state::EvmOverrides, Block as RpcBlock, BlockError, Bundle, StateContext, + state::EvmOverrides, BadBlock, BlockError, BlockTransactionsKind, Bundle, StateContext, }; use alloy_rpc_types_trace::geth::{ BlockTraceResult, GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult, }; use async_trait::async_trait; use jsonrpsee::core::RpcResult; +use parking_lot::RwLock; use reth_chainspec::{ChainSpecProvider, EthChainSpec, EthereumHardforks}; +use reth_engine_primitives::ConsensusEngineEvent; use reth_errors::RethError; use reth_evm::{execute::Executor, ConfigureEvm, EvmEnvFor}; -use reth_primitives_traits::{Block as _, BlockBody, ReceiptWithBloom, RecoveredBlock}; +use reth_primitives_traits::{ + Block as BlockTrait, BlockBody, BlockTy, ReceiptWithBloom, RecoveredBlock, +}; use reth_revm::{db::State, witness::ExecutionWitnessRecord}; use reth_rpc_api::DebugApiServer; use reth_rpc_convert::RpcTxReq; use reth_rpc_eth_api::{ helpers::{EthTransactions, TraceExt}, - EthApiTypes, FromEthApiError, RpcNodeCore, + EthApiTypes, FromEthApiError, RpcBlock, RpcConvert, RpcNodeCore, }; use reth_rpc_eth_types::EthApiError; use reth_rpc_server_types::{result::internal_rpc_err, ToRpcResult}; use reth_storage_api::{ - BlockIdReader, BlockReaderIdExt, HeaderProvider, ProviderBlock, ReceiptProviderIdExt, - StateProofProvider, StateProviderFactory, StateRootProvider, TransactionVariant, + BlockIdReader, BlockReaderIdExt, HeaderProvider, ReceiptProviderIdExt, StateProofProvider, + StateProviderFactory, StateRootProvider, TransactionVariant, }; -use reth_tasks::pool::BlockingTaskGuard; +use reth_tasks::{pool::BlockingTaskGuard, TaskSpawner}; use reth_trie_common::{updates::TrieUpdates, HashedPostState}; use revm::DatabaseCommit; use revm_inspectors::tracing::{DebugInspector, TransactionContext}; -use std::sync::Arc; +use serde::{Deserialize, Serialize}; +use std::{collections::VecDeque, sync::Arc}; use tokio::sync::{AcquireError, OwnedSemaphorePermit}; +use tokio_stream::StreamExt; /// `debug` API implementation. /// /// This type provides the functionality for handling `debug` related requests. -pub struct DebugApi { +pub struct DebugApi { inner: Arc>, } -// === impl DebugApi === - -impl DebugApi { +impl DebugApi +where + Eth: EthApiTypes + RpcNodeCore, +{ /// Create a new instance of the [`DebugApi`] - pub fn new(eth_api: Eth, blocking_task_guard: BlockingTaskGuard) -> Self { - let inner = Arc::new(DebugApiInner { eth_api, blocking_task_guard }); + pub fn new( + eth_api: Eth, + blocking_task_guard: BlockingTaskGuard, + bad_block_store: BadBlockStore>, + ) -> Self { + let inner = Arc::new(DebugApiInner { eth_api, blocking_task_guard, bad_block_store }); Self { inner } } @@ -60,20 +71,23 @@ impl DebugApi { pub fn eth_api(&self) -> &Eth { &self.inner.eth_api } -} -impl DebugApi { /// Access the underlying provider. pub fn provider(&self) -> &Eth::Provider { self.inner.eth_api.provider() } + + /// Access the bad block store. + pub fn bad_block_store(&self) -> &BadBlockStore> { + &self.inner.bad_block_store + } } // === impl DebugApi === impl DebugApi where - Eth: EthApiTypes + TraceExt + 'static, + Eth: EthApiTypes + RpcNodeCore + TraceExt + 'static, { /// Acquires a permit to execute a tracing call. async fn acquire_trace_permit(&self) -> Result { @@ -83,7 +97,7 @@ where /// Trace the entire block asynchronously async fn trace_block( &self, - block: Arc>>, + block: Arc>>, evm_env: EvmEnvFor, opts: GethDebugTracingOptions, ) -> Result, Eth::Error> { @@ -143,7 +157,7 @@ where rlp_block: Bytes, opts: GethDebugTracingOptions, ) -> Result, Eth::Error> { - let block: ProviderBlock = Decodable::decode(&mut rlp_block.as_ref()) + let block: BlockTy = Decodable::decode(&mut rlp_block.as_ref()) .map_err(BlockError::RlpDecodeRawBlock) .map_err(Eth::Error::from_eth_err)?; @@ -514,7 +528,7 @@ where /// Generates an execution witness, using the given recovered block. pub async fn debug_execution_witness_for_block( &self, - block: Arc>>, + block: Arc>>, ) -> Result { let block_number = block.header().number(); @@ -610,7 +624,7 @@ where #[async_trait] impl DebugApiServer> for DebugApi where - Eth: EthApiTypes + EthTransactions + TraceExt + 'static, + Eth: EthApiTypes + RpcNodeCore + EthTransactions + TraceExt + 'static, { /// Handler for `debug_getRawHeader` async fn raw_header(&self, block_id: BlockId) -> RpcResult { @@ -660,7 +674,7 @@ where /// Handler for `debug_getRawTransactions` /// Returns the bytes of the transaction for the given hash. async fn raw_transactions(&self, block_id: BlockId) -> RpcResult> { - let block = self + let block: RecoveredBlock> = self .provider() .block_with_senders_by_id(block_id, TransactionVariant::NoHash) .to_rpc_result()? @@ -681,8 +695,42 @@ where } /// Handler for `debug_getBadBlocks` - async fn bad_blocks(&self) -> RpcResult> { - Ok(vec![]) + async fn bad_blocks(&self) -> RpcResult> { + let blocks = self.bad_block_store().all(); + let mut bad_blocks = Vec::with_capacity(blocks.len()); + + #[derive(Serialize, Deserialize)] + struct BadBlockSerde { + block: T, + hash: B256, + rlp: Bytes, + } + + for block in blocks { + let rpc_block = block + .clone_into_rpc_block( + BlockTransactionsKind::Full, + |tx, tx_info| self.eth_api().converter().fill(tx, tx_info), + |header, size| self.eth_api().converter().convert_header(header, size), + ) + .map_err(|err| internal_rpc_err(err.to_string()))?; + + let mut rlp = Vec::new(); + block.clone_sealed_block().encode(&mut rlp); + + let wrapper = BadBlockSerde::> { + block: rpc_block, + hash: block.hash(), + rlp: rlp.into(), + }; + let bad_block: BadBlock = serde_json::to_value(wrapper) + .and_then(serde_json::from_value) + .map_err(|err| EthApiError::other(internal_rpc_err(err.to_string())))?; + + bad_blocks.push(bad_block); + } + + Ok(bad_blocks) } /// Handler for `debug_traceChain` @@ -1045,21 +1093,92 @@ where } } -impl std::fmt::Debug for DebugApi { +impl std::fmt::Debug for DebugApi { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DebugApi").finish_non_exhaustive() } } -impl Clone for DebugApi { +impl Clone for DebugApi { fn clone(&self) -> Self { Self { inner: Arc::clone(&self.inner) } } } -struct DebugApiInner { +impl DebugApi +where + Eth: EthApiTypes + RpcNodeCore, +{ + /// Listens for invalid blocks and records them in the bad block store. + pub fn spawn_invalid_block_listener(&self, mut stream: St, executor: S) + where + S: TaskSpawner + Clone + 'static, + St: tokio_stream::Stream> + + Send + + Unpin + + 'static, + { + let bad_block_store = self.bad_block_store().clone(); + executor.spawn(Box::pin(async move { + while let Some(event) = stream.next().await { + if let ConsensusEngineEvent::InvalidBlock(block) = event && + let Ok(recovered) = + RecoveredBlock::try_recover_sealed(block.as_ref().clone()) + { + bad_block_store.insert(recovered); + } + } + })); + } +} + +/// A bounded, deduplicating store of recently observed bad blocks. +#[derive(Clone, Debug)] +pub struct BadBlockStore { + inner: Arc>>>>, + limit: usize, +} + +impl BadBlockStore { + /// Creates a new store with the given capacity. + pub fn new(limit: usize) -> Self { + Self { inner: Arc::new(RwLock::new(VecDeque::with_capacity(limit))), limit } + } + + /// Inserts a recovered block, keeping only the most recent `limit` entries and deduplicating + /// by block hash. + pub fn insert(&self, block: RecoveredBlock) { + let hash = block.hash(); + let mut guard = self.inner.write(); + + // skip if we already recorded this bad block , and keep original ordering + if guard.iter().any(|b| b.hash() == hash) { + return; + } + guard.push_back(Arc::new(block)); + + while guard.len() > self.limit { + guard.pop_front(); + } + } + + /// Returns all cached bad blocks ordered from newest to oldest. + pub fn all(&self) -> Vec>> { + let guard = self.inner.read(); + guard.iter().rev().cloned().collect() + } +} + +impl Default for BadBlockStore { + fn default() -> Self { + Self::new(64) + } +} + +struct DebugApiInner { /// The implementation of `eth` API eth_api: Eth, // restrict the number of concurrent calls to blocking calls blocking_task_guard: BlockingTaskGuard, + bad_block_store: BadBlockStore>, } diff --git a/crates/rpc/rpc/src/lib.rs b/crates/rpc/rpc/src/lib.rs index b5a20c19cf6..9fc841dfd2c 100644 --- a/crates/rpc/rpc/src/lib.rs +++ b/crates/rpc/rpc/src/lib.rs @@ -49,7 +49,7 @@ mod web3; pub use admin::AdminApi; pub use aliases::*; -pub use debug::DebugApi; +pub use debug::{BadBlockStore, DebugApi}; pub use engine::{EngineApi, EngineEthApi}; pub use eth::{helpers::SyncListener, EthApi, EthApiBuilder, EthBundle, EthFilter, EthPubSub}; pub use miner::MinerApi; diff --git a/crates/stages/types/src/id.rs b/crates/stages/types/src/id.rs index 8c0a91c8731..bbf19a749b1 100644 --- a/crates/stages/types/src/id.rs +++ b/crates/stages/types/src/id.rs @@ -114,6 +114,7 @@ impl StageId { /// Get a pre-encoded raw Vec, for example, to be used as the DB key for /// `tables::StageCheckpoints` and `tables::StageCheckpointProgresses` + #[allow(clippy::missing_const_for_fn)] pub fn get_pre_encoded(&self) -> Option<&Vec> { #[cfg(not(feature = "std"))] { diff --git a/crates/trie/trie/src/node_iter.rs b/crates/trie/trie/src/node_iter.rs index b57fc2da707..425754f351b 100644 --- a/crates/trie/trie/src/node_iter.rs +++ b/crates/trie/trie/src/node_iter.rs @@ -94,6 +94,7 @@ where } /// Creates a new [`TrieNodeIter`]. + #[allow(clippy::missing_const_for_fn)] fn new(walker: TrieWalker, hashed_cursor: H, trie_type: TrieType) -> Self { Self { walker, diff --git a/examples/exex-hello-world/src/main.rs b/examples/exex-hello-world/src/main.rs index 2c89fb72627..97aa0b005b0 100644 --- a/examples/exex-hello-world/src/main.rs +++ b/examples/exex-hello-world/src/main.rs @@ -66,7 +66,11 @@ async fn ethapi_exex( ) -> eyre::Result<()> where Node: FullNodeComponents>, - EthApi: FullEthApi, + EthApi: FullEthApi + + reth_ethereum::rpc::eth::RpcNodeCore< + Provider = Node::Provider, + Primitives = ::Primitives, + >, { // Wait for the ethapi to be sent from the main function let rpc_handle = rpc_handle.await?;