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 BlockProvider, DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider,
9};
10use reth_engine_local::{LocalMiner, MiningMode};
11use reth_node_api::{
12 BlockTy, FullNodeComponents, FullNodeTypes, HeaderTy, PayloadAttrTy, PayloadAttributesBuilder,
13 PayloadTypes,
14};
15use std::{
16 future::{Future, IntoFuture},
17 pin::Pin,
18 sync::Arc,
19};
20use tracing::info;
21
22pub trait DebugNode<N: FullNodeComponents>: Node<N> {
56 type RpcBlock: Serialize + DeserializeOwned + 'static;
59
60 fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy<Self>;
70
71 fn local_payload_attributes_builder(
78 chain_spec: &Self::ChainSpec,
79 ) -> impl PayloadAttributesBuilder<<Self::Payload as PayloadTypes>::PayloadAttributes, HeaderTy<Self>>;
80}
81
82#[derive(Debug, Clone)]
104pub struct DebugNodeLauncher<L = EngineNodeLauncher> {
105 inner: L,
106}
107
108impl<L> DebugNodeLauncher<L> {
109 pub const fn new(inner: L) -> Self {
111 Self { inner }
112 }
113}
114
115pub type DefaultDebugBlockProvider<N> = EtherscanBlockProvider<
118 <<N as FullNodeTypes>::Types as DebugNode<N>>::RpcBlock,
119 BlockTy<<N as FullNodeTypes>::Types>,
120>;
121
122#[expect(missing_debug_implementations, clippy::type_complexity)]
124pub struct DebugNodeLauncherFuture<L, Target, N, B = DefaultDebugBlockProvider<N>>
125where
126 N: FullNodeComponents<Types: DebugNode<N>>,
127{
128 inner: L,
129 target: Target,
130 local_payload_attributes_builder:
131 Option<Box<dyn PayloadAttributesBuilder<PayloadAttrTy<N::Types>, HeaderTy<N::Types>>>>,
132 map_attributes:
133 Option<Box<dyn Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync>>,
134 debug_block_provider: Option<B>,
135 mining_mode: Option<MiningMode<N::Pool>>,
136}
137
138impl<L, Target, N, AddOns, B> DebugNodeLauncherFuture<L, Target, N, B>
139where
140 N: FullNodeComponents<Types: DebugNode<N>>,
141 AddOns: RethRpcAddOns<N>,
142 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>>,
143 B: BlockProvider<Block = BlockTy<N::Types>> + Clone,
144{
145 pub fn with_payload_attributes_builder(
147 self,
148 builder: impl PayloadAttributesBuilder<PayloadAttrTy<N::Types>, HeaderTy<N::Types>>,
149 ) -> Self {
150 Self {
151 inner: self.inner,
152 target: self.target,
153 local_payload_attributes_builder: Some(Box::new(builder)),
154 map_attributes: None,
155 debug_block_provider: self.debug_block_provider,
156 mining_mode: self.mining_mode,
157 }
158 }
159
160 pub fn map_debug_payload_attributes(
162 self,
163 f: impl Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync + 'static,
164 ) -> Self {
165 Self {
166 inner: self.inner,
167 target: self.target,
168 local_payload_attributes_builder: None,
169 map_attributes: Some(Box::new(f)),
170 debug_block_provider: self.debug_block_provider,
171 mining_mode: self.mining_mode,
172 }
173 }
174
175 pub fn with_mining_mode(mut self, mode: MiningMode<N::Pool>) -> Self {
180 self.mining_mode = Some(mode);
181 self
182 }
183
184 pub fn with_debug_block_provider<B2>(
189 self,
190 provider: B2,
191 ) -> DebugNodeLauncherFuture<L, Target, N, B2>
192 where
193 B2: BlockProvider<Block = BlockTy<N::Types>> + Clone,
194 {
195 DebugNodeLauncherFuture {
196 inner: self.inner,
197 target: self.target,
198 local_payload_attributes_builder: self.local_payload_attributes_builder,
199 map_attributes: self.map_attributes,
200 debug_block_provider: Some(provider),
201 mining_mode: self.mining_mode,
202 }
203 }
204
205 async fn launch_node(self) -> eyre::Result<NodeHandle<N, AddOns>> {
206 let Self {
207 inner,
208 target,
209 local_payload_attributes_builder,
210 map_attributes,
211 debug_block_provider,
212 mining_mode,
213 } = self;
214
215 let handle = inner.launch_node(target).await?;
216
217 let config = &handle.node.config;
218
219 if let Some(provider) = debug_block_provider {
220 info!(target: "reth::cli", "Using custom debug block provider");
221
222 let rpc_consensus_client = DebugConsensusClient::new(
223 handle.node.add_ons_handle.beacon_engine_handle.clone(),
224 Arc::new(provider),
225 );
226
227 handle
228 .node
229 .task_executor
230 .spawn_critical_task("custom debug block provider consensus client", async move {
231 rpc_consensus_client.run().await
232 });
233 } else if let Some(url) = config.debug.rpc_consensus_url.clone() {
234 info!(target: "reth::cli", "Using RPC consensus client: {}", url);
235
236 let block_provider =
237 RpcBlockProvider::<AnyNetwork, _>::new(url.as_str(), |block_response| {
238 let json = serde_json::to_value(block_response)
239 .expect("Block serialization cannot fail");
240 let rpc_block =
241 serde_json::from_value(json).expect("Block deserialization cannot fail");
242 N::Types::rpc_to_primitive_block(rpc_block)
243 })
244 .await?;
245
246 let rpc_consensus_client = DebugConsensusClient::new(
247 handle.node.add_ons_handle.beacon_engine_handle.clone(),
248 Arc::new(block_provider),
249 );
250
251 handle.node.task_executor.spawn_critical_task("rpc-ws consensus client", async move {
252 rpc_consensus_client.run().await
253 });
254 } else if let Some(maybe_custom_etherscan_url) = config.debug.etherscan.clone() {
255 info!(target: "reth::cli", "Using etherscan as consensus client");
256
257 let chain = config.chain.chain();
258 let etherscan_url = maybe_custom_etherscan_url.map(Ok).unwrap_or_else(|| {
259 chain
260 .etherscan_urls()
261 .map(|urls| urls.0.to_string())
262 .ok_or_else(|| eyre::eyre!("failed to get etherscan url for chain: {chain}"))
263 })?;
264
265 let block_provider = EtherscanBlockProvider::new(
266 etherscan_url,
267 chain.etherscan_api_key().ok_or_else(|| {
268 eyre::eyre!(
269 "etherscan api key not found for rpc consensus client for chain: {chain}"
270 )
271 })?,
272 chain.id(),
273 N::Types::rpc_to_primitive_block,
274 );
275 let rpc_consensus_client = DebugConsensusClient::new(
276 handle.node.add_ons_handle.beacon_engine_handle.clone(),
277 Arc::new(block_provider),
278 );
279 handle
280 .node
281 .task_executor
282 .spawn_critical_task("etherscan consensus client", async move {
283 rpc_consensus_client.run().await
284 });
285 }
286
287 if config.dev.dev {
288 info!(target: "reth::cli", "Using local payload attributes builder for dev mode");
289
290 let blockchain_db = handle.node.provider.clone();
291 let chain_spec = config.chain.clone();
292 let beacon_engine_handle = handle.node.add_ons_handle.beacon_engine_handle.clone();
293 let pool = handle.node.pool.clone();
294 let payload_builder_handle = handle.node.payload_builder_handle.clone();
295
296 let builder = if let Some(builder) = local_payload_attributes_builder {
297 Either::Left(builder)
298 } else {
299 let local = N::Types::local_payload_attributes_builder(&chain_spec);
300 let builder = if let Some(f) = map_attributes {
301 Either::Left(move |parent| f(local.build(&parent)))
302 } else {
303 Either::Right(local)
304 };
305 Either::Right(builder)
306 };
307
308 let dev_mining_mode =
309 mining_mode.unwrap_or_else(|| handle.node.config.dev_mining_mode(pool));
310 handle.node.task_executor.spawn_critical_task("local engine", async move {
311 LocalMiner::new(
312 blockchain_db,
313 builder,
314 beacon_engine_handle,
315 dev_mining_mode,
316 payload_builder_handle,
317 )
318 .run()
319 .await
320 });
321 }
322
323 Ok(handle)
324 }
325}
326
327impl<L, Target, N, AddOns, B> IntoFuture for DebugNodeLauncherFuture<L, Target, N, B>
328where
329 Target: Send + 'static,
330 N: FullNodeComponents<Types: DebugNode<N>>,
331 AddOns: RethRpcAddOns<N> + 'static,
332 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
333 B: BlockProvider<Block = BlockTy<N::Types>> + Clone + 'static,
334{
335 type Output = eyre::Result<NodeHandle<N, AddOns>>;
336 type IntoFuture = Pin<Box<dyn Future<Output = eyre::Result<NodeHandle<N, AddOns>>> + Send>>;
337
338 fn into_future(self) -> Self::IntoFuture {
339 Box::pin(self.launch_node())
340 }
341}
342
343impl<L, Target, N, AddOns> LaunchNode<Target> for DebugNodeLauncher<L>
344where
345 Target: Send + 'static,
346 N: FullNodeComponents<Types: DebugNode<N>>,
347 AddOns: RethRpcAddOns<N> + 'static,
348 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
349 DefaultDebugBlockProvider<N>: BlockProvider<Block = BlockTy<N::Types>> + Clone,
350{
351 type Node = NodeHandle<N, AddOns>;
352 type Future = DebugNodeLauncherFuture<L, Target, N>;
353
354 fn launch_node(self, target: Target) -> Self::Future {
355 DebugNodeLauncherFuture {
356 inner: self.inner,
357 target,
358 local_payload_attributes_builder: None,
359 map_attributes: None,
360 debug_block_provider: None,
361 mining_mode: None,
362 }
363 }
364}