reth_node_builder/launch/
invalid_block_hook.rs1use 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
19pub trait InvalidBlockHookExt {
21 type Primitives: NodePrimitives;
23
24 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
53pub 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
117async 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 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}