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