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::{DebugConsensusClient, EtherscanBlockProvider, RpcBlockProvider};
8use reth_engine_local::LocalMiner;
9use reth_node_api::{
10 BlockTy, FullNodeComponents, PayloadAttrTy, PayloadAttributesBuilder, PayloadTypes,
11};
12use std::{
13 future::{Future, IntoFuture},
14 pin::Pin,
15 sync::Arc,
16};
17use tracing::info;
18
19pub trait DebugNode<N: FullNodeComponents>: Node<N> {
53 type RpcBlock: Serialize + DeserializeOwned + 'static;
56
57 fn rpc_to_primitive_block(rpc_block: Self::RpcBlock) -> BlockTy<Self>;
67
68 fn local_payload_attributes_builder(
75 chain_spec: &Self::ChainSpec,
76 ) -> impl PayloadAttributesBuilder<
77 <<Self as reth_node_api::NodeTypes>::Payload as PayloadTypes>::PayloadAttributes,
78 >;
79}
80
81#[derive(Debug, Clone)]
103pub struct DebugNodeLauncher<L = EngineNodeLauncher> {
104 inner: L,
105}
106
107impl<L> DebugNodeLauncher<L> {
108 pub const fn new(inner: L) -> Self {
110 Self { inner }
111 }
112}
113
114#[expect(missing_debug_implementations, clippy::type_complexity)]
116pub struct DebugNodeLauncherFuture<L, Target, N>
117where
118 N: FullNodeComponents<Types: DebugNode<N>>,
119{
120 inner: L,
121 target: Target,
122 local_payload_attributes_builder:
123 Option<Box<dyn PayloadAttributesBuilder<PayloadAttrTy<N::Types>>>>,
124 map_attributes:
125 Option<Box<dyn Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync>>,
126}
127
128impl<L, Target, N, AddOns> DebugNodeLauncherFuture<L, Target, N>
129where
130 N: FullNodeComponents<Types: DebugNode<N>>,
131 AddOns: RethRpcAddOns<N>,
132 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>>,
133{
134 pub fn with_payload_attributes_builder(
135 self,
136 builder: impl PayloadAttributesBuilder<PayloadAttrTy<N::Types>>,
137 ) -> Self {
138 Self {
139 inner: self.inner,
140 target: self.target,
141 local_payload_attributes_builder: Some(Box::new(builder)),
142 map_attributes: None,
143 }
144 }
145
146 pub fn map_debug_payload_attributes(
147 self,
148 f: impl Fn(PayloadAttrTy<N::Types>) -> PayloadAttrTy<N::Types> + Send + Sync + 'static,
149 ) -> Self {
150 Self {
151 inner: self.inner,
152 target: self.target,
153 local_payload_attributes_builder: None,
154 map_attributes: Some(Box::new(f)),
155 }
156 }
157
158 async fn launch_node(self) -> eyre::Result<NodeHandle<N, AddOns>> {
159 let Self { inner, target, local_payload_attributes_builder, map_attributes } = self;
160
161 let handle = inner.launch_node(target).await?;
162
163 let config = &handle.node.config;
164 if let Some(url) = config.debug.rpc_consensus_url.clone() {
165 info!(target: "reth::cli", "Using RPC consensus client: {}", url);
166
167 let block_provider =
168 RpcBlockProvider::<AnyNetwork, _>::new(url.as_str(), |block_response| {
169 let json = serde_json::to_value(block_response)
170 .expect("Block serialization cannot fail");
171 let rpc_block =
172 serde_json::from_value(json).expect("Block deserialization cannot fail");
173 N::Types::rpc_to_primitive_block(rpc_block)
174 })
175 .await?;
176
177 let rpc_consensus_client = DebugConsensusClient::new(
178 handle.node.add_ons_handle.beacon_engine_handle.clone(),
179 Arc::new(block_provider),
180 );
181
182 handle.node.task_executor.spawn_critical("rpc-ws consensus client", async move {
183 rpc_consensus_client.run().await
184 });
185 }
186
187 if let Some(maybe_custom_etherscan_url) = config.debug.etherscan.clone() {
188 info!(target: "reth::cli", "Using etherscan as consensus client");
189
190 let chain = config.chain.chain();
191 let etherscan_url = maybe_custom_etherscan_url.map(Ok).unwrap_or_else(|| {
192 chain
194 .etherscan_urls()
195 .map(|urls| urls.0.to_string())
196 .ok_or_else(|| eyre::eyre!("failed to get etherscan url for chain: {chain}"))
197 })?;
198
199 let block_provider = EtherscanBlockProvider::new(
200 etherscan_url,
201 chain.etherscan_api_key().ok_or_else(|| {
202 eyre::eyre!(
203 "etherscan api key not found for rpc consensus client for chain: {chain}"
204 )
205 })?,
206 chain.id(),
207 N::Types::rpc_to_primitive_block,
208 );
209 let rpc_consensus_client = DebugConsensusClient::new(
210 handle.node.add_ons_handle.beacon_engine_handle.clone(),
211 Arc::new(block_provider),
212 );
213 handle.node.task_executor.spawn_critical("etherscan consensus client", async move {
214 rpc_consensus_client.run().await
215 });
216 }
217
218 if config.dev.dev {
219 info!(target: "reth::cli", "Using local payload attributes builder for dev mode");
220
221 let blockchain_db = handle.node.provider.clone();
222 let chain_spec = config.chain.clone();
223 let beacon_engine_handle = handle.node.add_ons_handle.beacon_engine_handle.clone();
224 let pool = handle.node.pool.clone();
225 let payload_builder_handle = handle.node.payload_builder_handle.clone();
226
227 let builder = if let Some(builder) = local_payload_attributes_builder {
228 Either::Left(builder)
229 } else {
230 let local = N::Types::local_payload_attributes_builder(&chain_spec);
231 let builder = if let Some(f) = map_attributes {
232 Either::Left(move |block_number| f(local.build(block_number)))
233 } else {
234 Either::Right(local)
235 };
236 Either::Right(builder)
237 };
238
239 let dev_mining_mode = handle.node.config.dev_mining_mode(pool);
240 handle.node.task_executor.spawn_critical("local engine", async move {
241 LocalMiner::new(
242 blockchain_db,
243 builder,
244 beacon_engine_handle,
245 dev_mining_mode,
246 payload_builder_handle,
247 )
248 .run()
249 .await
250 });
251 }
252
253 Ok(handle)
254 }
255}
256
257impl<L, Target, N, AddOns> IntoFuture for DebugNodeLauncherFuture<L, Target, N>
258where
259 Target: Send + 'static,
260 N: FullNodeComponents<Types: DebugNode<N>>,
261 AddOns: RethRpcAddOns<N> + 'static,
262 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
263{
264 type Output = eyre::Result<NodeHandle<N, AddOns>>;
265 type IntoFuture = Pin<Box<dyn Future<Output = eyre::Result<NodeHandle<N, AddOns>>> + Send>>;
266
267 fn into_future(self) -> Self::IntoFuture {
268 Box::pin(self.launch_node())
269 }
270}
271
272impl<L, Target, N, AddOns> LaunchNode<Target> for DebugNodeLauncher<L>
273where
274 Target: Send + 'static,
275 N: FullNodeComponents<Types: DebugNode<N>>,
276 AddOns: RethRpcAddOns<N> + 'static,
277 L: LaunchNode<Target, Node = NodeHandle<N, AddOns>> + 'static,
278{
279 type Node = NodeHandle<N, AddOns>;
280 type Future = DebugNodeLauncherFuture<L, Target, N>;
281
282 fn launch_node(self, target: Target) -> Self::Future {
283 DebugNodeLauncherFuture {
284 inner: self.inner,
285 target,
286 local_payload_attributes_builder: None,
287 map_attributes: None,
288 }
289 }
290}