reth_node_builder/launch/
invalid_block_hook.rs

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