reth_node_builder/launch/
invalid_block_hook.rs1use 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
18pub trait InvalidBlockHookExt {
20 type Primitives: NodePrimitives;
22
23 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
52pub 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
116async 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 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}