reth_node_builder/launch/
debug.rs1use super::LaunchNode;
2use crate::{rpc::RethRpcAddOns, EngineNodeLauncher, Node, NodeHandle};
3use alloy_consensus::transaction::Either;
4use alloy_provider::network::AnyNetwork;
5use jsonrpsee::core::{DeserializeOwned, Serialize};
6use reth_chainspec::EthChainSpec;
7use reth_consensus_debug_client::{
8 DebugConsensusClient, EtherscanBlockProvider, PayloadProvider, RpcBlockProvider,
9};
10use reth_engine_local::{LocalMiner, MiningMode};
11use reth_node_api::{
12 BlockTy, FullNodeComponents, FullNodeTypes, HeaderTy, NodeTypes, PayloadAttrTy,
13 PayloadAttributesBuilder, PayloadTypes,
14};
15use reth_primitives_traits::SealedBlock;
16use std::{
17 future::{Future, IntoFuture},
18 pin::Pin,
19 sync::Arc,
20};
21use tracing::info;
22
23pub(crate) type PayloadDataTy<N> = <<N as NodeTypes>::Payload as PayloadTypes>::ExecutionData;
25
26pub trait DebugNode<N: FullNodeComponents>: Node<N> {
62 type RpcBlock: Serialize + DeserializeOwned + 'static;
65
66 fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy<Self>;
76
77 fn local_payload_attributes_builder(
84 chain_spec: &Self::ChainSpec,
85 ) -> impl PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes, HeaderTy<Self>>;
86}
87
88#[derive(Debug, Clone)]
110pub struct DebugNodeLauncher<L = EngineNodeLauncher> {
111 inner: L,
112}
113
114impl<L> DebugNodeLauncher<L> {
115 pub const fn new(inner: L) -> Self {
117 Self { inner }
118 }
119}
120
121pub type DefaultDebugBlockProvider<N> = EtherscanBlockProvider<
124 <<N as FullNodeTypes>::Types as DebugNode<N>>::RpcBlock,
125 PayloadDataTy<<N as FullNodeTypes>::Types>,
126>;
127
128#[expect(missing_debug_implementations, clippy::type_complexity)]
130pub struct DebugNodeLauncherFuture<L, Target, N, B = DefaultDebugBlockProvider<N>>
131where
132 N: FullNodeComponents<Types: DebugNode<N>>,
133{
134 inner: L,
135 target: Target,
136 local_payload_attributes_builder:
137 Option<Box<dyn PayloadAttributesBuilder<PayloadAttrTy<N::Types>, HeaderTy<N::Types>>>>,
138 map_attributes:
139 Option<Box<dyn Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync>>,
140 debug_block_provider: Option<B>,
141 mining_mode: Option<MiningMode<N::Pool>>,
142}
143
144impl<L, Target, N, AddOns, B> DebugNodeLauncherFuture<L, Target, N, B>
145where
146 N: FullNodeComponents<Types: DebugNode<N>>,
147 AddOns: RethRpcAddOns<N>,
148 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>>,
149 B: PayloadProvider<ExecutionData = PayloadDataTy<N::Types>> + Clone,
150{
151 pub fn with_payload_attributes_builder(
153 self,
154 builder: impl PayloadAttributesBuilder<PayloadAttrTy<N::Types>, HeaderTy<N::Types>>,
155 ) -> Self {
156 Self {
157 inner: self.inner,
158 target: self.target,
159 local_payload_attributes_builder: Some(Box::new(builder)),
160 map_attributes: None,
161 debug_block_provider: self.debug_block_provider,
162 mining_mode: self.mining_mode,
163 }
164 }
165
166 pub fn map_debug_payload_attributes(
168 self,
169 f: impl Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync + 'static,
170 ) -> Self {
171 Self {
172 inner: self.inner,
173 target: self.target,
174 local_payload_attributes_builder: None,
175 map_attributes: Some(Box::new(f)),
176 debug_block_provider: self.debug_block_provider,
177 mining_mode: self.mining_mode,
178 }
179 }
180
181 pub fn with_mining_mode(mut self, mode: MiningMode<N::Pool>) -> Self {
186 self.mining_mode = Some(mode);
187 self
188 }
189
190 pub fn with_debug_block_provider<B2>(
195 self,
196 provider: B2,
197 ) -> DebugNodeLauncherFuture<L, Target, N, B2>
198 where
199 B2: PayloadProvider<ExecutionData = PayloadDataTy<N::Types>> + Clone,
200 {
201 DebugNodeLauncherFuture {
202 inner: self.inner,
203 target: self.target,
204 local_payload_attributes_builder: self.local_payload_attributes_builder,
205 map_attributes: self.map_attributes,
206 debug_block_provider: Some(provider),
207 mining_mode: self.mining_mode,
208 }
209 }
210
211 async fn launch_node(self) -> eyre::Result<NodeHandle<N, AddOns>> {
212 let Self {
213 inner,
214 target,
215 local_payload_attributes_builder,
216 map_attributes,
217 debug_block_provider,
218 mining_mode,
219 } = self;
220
221 let handle = inner.launch_node(target).await?;
222
223 let config = &handle.node.config;
224
225 if let Some(provider) = debug_block_provider {
226 info!(target: "reth::cli", "Using custom debug block provider");
227
228 let rpc_consensus_client = DebugConsensusClient::new(
229 handle.node.add_ons_handle.beacon_engine_handle.clone(),
230 Arc::new(provider),
231 );
232
233 handle
234 .node
235 .task_executor
236 .spawn_critical_task("custom debug block provider consensus client", async move {
237 rpc_consensus_client.run().await
238 });
239 } else if let Some(url) = config.debug.rpc_consensus_url.clone() {
240 info!(target: "reth::cli", "Using RPC consensus client: {}", url);
241
242 let block_provider =
243 RpcBlockProvider::<AnyNetwork, _>::new(url.as_str(), |block_response, extras| {
244 let json = serde_json::to_value(block_response)
245 .expect("Block serialization cannot fail");
246 let rpc_block =
247 serde_json::from_value(json).expect("Block deserialization cannot fail");
248 let primitive_block = N::Types::rpc_to_primitive_block(rpc_block);
249 <N::Types as NodeTypes>::Payload::block_to_payload(
250 SealedBlock::new_unhashed(primitive_block),
251 extras.bal,
252 )
253 })
254 .await?;
255
256 let rpc_consensus_client = DebugConsensusClient::new(
257 handle.node.add_ons_handle.beacon_engine_handle.clone(),
258 Arc::new(block_provider),
259 );
260
261 handle.node.task_executor.spawn_critical_task("rpc-ws consensus client", async move {
262 rpc_consensus_client.run().await
263 });
264 } else if let Some(maybe_custom_etherscan_url) = config.debug.etherscan.clone() {
265 info!(target: "reth::cli", "Using etherscan as consensus client");
266
267 let chain = config.chain.chain();
268 let etherscan_url = maybe_custom_etherscan_url.map(Ok).unwrap_or_else(|| {
269 chain
270 .etherscan_urls()
271 .map(|urls| urls.0.to_string())
272 .ok_or_else(|| eyre::eyre!("failed to get etherscan url for chain: {chain}"))
273 })?;
274
275 let block_provider = EtherscanBlockProvider::new(
276 etherscan_url,
277 chain.etherscan_api_key().ok_or_else(|| {
278 eyre::eyre!(
279 "etherscan api key not found for rpc consensus client for chain: {chain}"
280 )
281 })?,
282 chain.id(),
283 |rpc_block| {
284 let primitive_block = N::Types::rpc_to_primitive_block(rpc_block);
285 <N::Types as NodeTypes>::Payload::block_to_payload(
286 SealedBlock::new_unhashed(primitive_block),
287 None,
288 )
289 },
290 );
291 let rpc_consensus_client = DebugConsensusClient::new(
292 handle.node.add_ons_handle.beacon_engine_handle.clone(),
293 Arc::new(block_provider),
294 );
295 handle
296 .node
297 .task_executor
298 .spawn_critical_task("etherscan consensus client", async move {
299 rpc_consensus_client.run().await
300 });
301 }
302
303 if config.dev.dev {
304 info!(target: "reth::cli", "Using local payload attributes builder for dev mode");
305
306 let blockchain_db = handle.node.provider.clone();
307 let chain_spec = config.chain.clone();
308 let beacon_engine_handle = handle.node.add_ons_handle.beacon_engine_handle.clone();
309 let pool = handle.node.pool.clone();
310 let payload_builder_handle = handle.node.payload_builder_handle.clone();
311
312 let builder = if let Some(builder) = local_payload_attributes_builder {
313 Either::Left(builder)
314 } else {
315 let local = N::Types::local_payload_attributes_builder(&chain_spec);
316 let builder = if let Some(f) = map_attributes {
317 Either::Left(move |parent| f(local.build(&parent)))
318 } else {
319 Either::Right(local)
320 };
321 Either::Right(builder)
322 };
323
324 let dev_mining_mode =
325 mining_mode.unwrap_or_else(|| handle.node.config.dev_mining_mode(pool));
326 let payload_wait_time = config.dev.payload_wait_time;
327 if let (Some(wait_time), Some(block_time)) = (payload_wait_time, config.dev.block_time)
328 {
329 eyre::ensure!(
330 wait_time <= block_time,
331 "--dev.payload-wait-time ({wait_time:?}) must be <= --dev.block-time ({block_time:?})"
332 );
333 }
334 handle.node.task_executor.spawn_critical_task("local engine", async move {
335 LocalMiner::new(
336 blockchain_db,
337 builder,
338 beacon_engine_handle,
339 dev_mining_mode,
340 payload_builder_handle,
341 )
342 .with_payload_wait_time_opt(payload_wait_time)
343 .run()
344 .await
345 });
346 }
347
348 Ok(handle)
349 }
350}
351
352impl<L, Target, N, AddOns, B> IntoFuture for DebugNodeLauncherFuture<L, Target, N, B>
353where
354 Target: Send + 'static,
355 N: FullNodeComponents<Types: DebugNode<N>>,
356 AddOns: RethRpcAddOns<N> + 'static,
357 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
358 B: PayloadProvider<ExecutionData = PayloadDataTy<N::Types>> + Clone + 'static,
359{
360 type Output = eyre::Result<NodeHandle<N, AddOns>>;
361 type IntoFuture = Pin<Box<dyn Future<Output = eyre::Result<NodeHandle<N, AddOns>>> + Send>>;
362
363 fn into_future(self) -> Self::IntoFuture {
364 Box::pin(self.launch_node())
365 }
366}
367
368impl<L, Target, N, AddOns> LaunchNode<Target> for DebugNodeLauncher<L>
369where
370 Target: Send + 'static,
371 N: FullNodeComponents<Types: DebugNode<N>>,
372 AddOns: RethRpcAddOns<N> + 'static,
373 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
374 DefaultDebugBlockProvider<N>: PayloadProvider<ExecutionData = PayloadDataTy<N::Types>> + Clone,
375{
376 type Node = NodeHandle<N, AddOns>;
377 type Future = DebugNodeLauncherFuture<L, Target, N>;
378
379 fn launch_node(self, target: Target) -> Self::Future {
380 DebugNodeLauncherFuture {
381 inner: self.inner,
382 target,
383 local_payload_attributes_builder: None,
384 map_attributes: None,
385 debug_block_provider: None,
386 mining_mode: None,
387 }
388 }
389}