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