reth_node_builder/launch/
invalid_block_hook.rs

1//! Invalid block hook helpers for the node builder.
2
3use crate::AddOnsContext;
4use alloy_consensus::TxEnvelope;
5use alloy_rpc_types::{Block, Header, Receipt, Transaction, TransactionRequest};
6use eyre::OptionExt;
7use reth_chainspec::EthChainSpec;
8use reth_engine_primitives::InvalidBlockHook;
9use reth_node_api::{FullNodeComponents, NodeTypes};
10use reth_node_core::{
11    args::InvalidBlockHookType,
12    dirs::{ChainPath, DataDirPath},
13    node_config::NodeConfig,
14};
15use reth_primitives_traits::NodePrimitives;
16use reth_provider::ChainSpecProvider;
17use reth_rpc_api::EthApiClient;
18
19/// Extension trait for [`AddOnsContext`] to create invalid block hooks.
20pub trait InvalidBlockHookExt {
21    /// Node primitives type.
22    type Primitives: NodePrimitives;
23
24    /// Creates an invalid block hook based on the node configuration.
25    fn create_invalid_block_hook(
26        &self,
27        data_dir: &ChainPath<DataDirPath>,
28    ) -> impl std::future::Future<Output = eyre::Result<Box<dyn InvalidBlockHook<Self::Primitives>>>>
29           + Send;
30}
31
32impl<N> InvalidBlockHookExt for AddOnsContext<'_, N>
33where
34    N: FullNodeComponents,
35{
36    type Primitives = <N::Types as NodeTypes>::Primitives;
37
38    async fn create_invalid_block_hook(
39        &self,
40        data_dir: &ChainPath<DataDirPath>,
41    ) -> eyre::Result<Box<dyn InvalidBlockHook<Self::Primitives>>> {
42        create_invalid_block_hook(
43            self.config,
44            data_dir,
45            self.node.provider().clone(),
46            self.node.evm_config().clone(),
47            self.node.provider().chain_spec().chain().id(),
48        )
49        .await
50    }
51}
52
53/// Creates an invalid block hook based on the node configuration.
54///
55/// This function constructs the appropriate [`InvalidBlockHook`] based on the debug
56/// configuration in the node config. It supports:
57/// - Witness hooks for capturing block witness data
58/// - Healthy node verification via RPC
59///
60/// # Arguments
61/// * `config` - The node configuration containing debug settings
62/// * `data_dir` - The data directory for storing hook outputs
63/// * `provider` - The blockchain database provider
64/// * `evm_config` - The EVM configuration
65/// * `chain_id` - The chain ID for verification
66pub async fn create_invalid_block_hook<N, P, E>(
67    config: &NodeConfig<P::ChainSpec>,
68    data_dir: &ChainPath<DataDirPath>,
69    provider: P,
70    evm_config: E,
71    chain_id: u64,
72) -> eyre::Result<Box<dyn InvalidBlockHook<N>>>
73where
74    N: NodePrimitives,
75    P: reth_provider::StateProviderFactory
76        + reth_provider::ChainSpecProvider
77        + Clone
78        + Send
79        + Sync
80        + 'static,
81    E: reth_evm::ConfigureEvm<Primitives = N> + Clone + 'static,
82{
83    use reth_engine_primitives::{InvalidBlockHooks, NoopInvalidBlockHook};
84    use reth_invalid_block_hooks::InvalidBlockWitnessHook;
85
86    let Some(ref hook) = config.debug.invalid_block_hook else {
87        return Ok(Box::new(NoopInvalidBlockHook::default()))
88    };
89
90    let healthy_node_rpc_client = get_healthy_node_client(config, chain_id).await?;
91
92    let output_directory = data_dir.invalid_block_hooks();
93    let hooks = hook
94        .iter()
95        .copied()
96        .map(|hook| {
97            let output_directory = output_directory.join(hook.to_string());
98            std::fs::create_dir_all(&output_directory)?;
99
100            Ok(match hook {
101                InvalidBlockHookType::Witness => Box::new(InvalidBlockWitnessHook::new(
102                    provider.clone(),
103                    evm_config.clone(),
104                    output_directory,
105                    healthy_node_rpc_client.clone(),
106                )),
107                InvalidBlockHookType::PreState | InvalidBlockHookType::Opcode => {
108                    eyre::bail!("invalid block hook {hook:?} is not implemented yet")
109                }
110            } as Box<dyn InvalidBlockHook<_>>)
111        })
112        .collect::<Result<_, _>>()?;
113
114    Ok(Box::new(InvalidBlockHooks(hooks)))
115}
116
117/// Returns an RPC client for the healthy node, if configured in the node config.
118async fn get_healthy_node_client<C>(
119    config: &NodeConfig<C>,
120    chain_id: u64,
121) -> eyre::Result<Option<jsonrpsee::http_client::HttpClient>>
122where
123    C: EthChainSpec,
124{
125    let Some(url) = config.debug.healthy_node_rpc_url.as_ref() else {
126        return Ok(None);
127    };
128
129    let client = jsonrpsee::http_client::HttpClientBuilder::default().build(url)?;
130
131    // Verify that the healthy node is running the same chain as the current node.
132    let healthy_chain_id = EthApiClient::<
133        TransactionRequest,
134        Transaction,
135        Block,
136        Receipt,
137        Header,
138        TxEnvelope,
139    >::chain_id(&client)
140    .await?
141    .ok_or_eyre("healthy node rpc client didn't return a chain id")?;
142
143    if healthy_chain_id.to::<u64>() != chain_id {
144        eyre::bail!("Invalid chain ID. Expected {}, got {}", chain_id, healthy_chain_id);
145    }
146
147    Ok(Some(client))
148}