reth_consensus_debug_client/providers/
rpc.rs1use crate::PayloadProvider;
2use alloy_consensus::BlockHeader;
3use alloy_eips::BlockId;
4use alloy_provider::{
5 network::{primitives::HeaderResponse, AnyNetwork, AnyRpcBlock, AnyRpcHeader, BlockResponse},
6 ConnectionConfig, Provider, ProviderBuilder, WebSocketConfig,
7};
8use alloy_rpc_types_engine::PayloadExtras;
9use alloy_transport::TransportResult;
10use futures::{future::BoxFuture, stream::BoxStream, StreamExt};
11use reth_node_api::ExecutionPayload;
12use std::sync::Arc;
13use tokio::sync::mpsc::Sender;
14use tracing::{debug, warn};
15
16#[derive(derive_more::Debug, Clone)]
19pub struct RpcBlockProvider<ExecutionData> {
20 client: AnyRpcClient,
21 #[debug(skip)]
22 convert: Arc<dyn Fn(AnyRpcBlock, PayloadExtras) -> ExecutionData + Send + Sync>,
23}
24
25impl<ExecutionData> RpcBlockProvider<ExecutionData> {
26 pub async fn new(
28 rpc_url: &str,
29 convert: impl Fn(AnyRpcBlock, PayloadExtras) -> ExecutionData + Send + Sync + 'static,
30 ) -> eyre::Result<Self> {
31 Ok(Self { client: AnyRpcClient::connect(rpc_url).await?, convert: Arc::new(convert) })
32 }
33
34 pub const fn without_block_access_lists(mut self) -> Self {
36 self.client.fetch_block_access_list = false;
37 self
38 }
39}
40
41impl<ExecutionData> PayloadProvider for RpcBlockProvider<ExecutionData>
42where
43 ExecutionData: ExecutionPayload,
44{
45 type ExecutionData = ExecutionData;
46
47 async fn subscribe_payloads(&self, tx: Sender<Self::ExecutionData>) {
48 loop {
49 let Ok(mut stream) = self.client.full_block_stream().await.inspect_err(|err| {
50 warn!(
51 target: "consensus::debug-client",
52 %err,
53 url=%self.client.url,
54 "Failed to subscribe to blocks, retrying in 5s",
55 );
56 }) else {
57 if tx.is_closed() {
60 return;
61 }
62 tokio::time::sleep(std::time::Duration::from_secs(5)).await;
63 continue
64 };
65
66 while let Some(res) = stream.next().await {
67 match res {
68 Ok(block) => {
69 let extras = self.client.payload_extras(block.header()).await;
70 let payload = (self.convert)(block, extras);
71 if tx.send(payload).await.is_err() {
72 return;
74 }
75 }
76 Err(err) => {
77 warn!(
78 target: "consensus::debug-client",
79 %err,
80 url=%self.client.url,
81 "Failed to fetch a block",
82 );
83 }
84 }
85 }
86 debug!(
88 target: "consensus::debug-client",
89 url=%self.client.url,
90 "Re-establishing block subscription",
91 );
92 }
93 }
94
95 async fn get_payload(&self, block_number: u64) -> eyre::Result<Self::ExecutionData> {
96 let (block, extras) = self.client.get_block(block_number).await?;
97 Ok((self.convert)(block, extras))
98 }
99}
100
101type BlockStream = BoxStream<'static, TransportResult<AnyRpcBlock>>;
102
103#[derive(derive_more::Debug, Clone)]
109struct AnyRpcClient {
110 #[debug(skip)]
111 provider: Arc<dyn Provider<AnyNetwork>>,
112 url: String,
113 fetch_block_access_list: bool,
114}
115
116impl AnyRpcClient {
117 fn connect(rpc_url: &str) -> BoxFuture<'_, eyre::Result<Self>> {
118 Box::pin(async move {
119 Ok(Self {
120 provider: Arc::new(
121 ProviderBuilder::<_, _, AnyNetwork>::default()
122 .connect_with_config(
123 rpc_url,
124 ConnectionConfig::default().with_max_retries(u32::MAX).with_ws_config(
125 WebSocketConfig::default()
126 .max_frame_size(Some(128 * 1024 * 1024))
128 .max_message_size(Some(128 * 1024 * 1024)),
129 ),
130 )
131 .await?,
132 ),
133 url: rpc_url.to_string(),
134 fetch_block_access_list: true,
135 })
136 })
137 }
138
139 fn full_block_stream(&self) -> BoxFuture<'_, TransportResult<BlockStream>> {
144 Box::pin(async move {
145 match self.provider.subscribe_full_blocks().full().into_stream().await {
147 Ok(sub) => Ok(sub.boxed()),
148 Err(err) => {
149 debug!(
150 target: "consensus::debug-client",
151 %err,
152 url=%self.url,
153 "Failed to establish block subscription",
154 );
155 Ok(self.provider.watch_full_blocks().await?.full().into_stream().boxed())
156 }
157 }
158 })
159 }
160
161 fn get_block(
162 &self,
163 block_number: u64,
164 ) -> BoxFuture<'_, eyre::Result<(AnyRpcBlock, PayloadExtras)>> {
165 Box::pin(async move {
166 let block = self
167 .provider
168 .get_block_by_number(block_number.into())
169 .full()
170 .await?
171 .ok_or_else(|| eyre::eyre!("block not found by number {}", block_number))?;
172 let extras = self.payload_extras(block.header()).await;
173 Ok((block, extras))
174 })
175 }
176
177 fn payload_extras<'a>(&'a self, header: &'a AnyRpcHeader) -> BoxFuture<'a, PayloadExtras> {
182 Box::pin(async move {
183 if !self.fetch_block_access_list {
184 return PayloadExtras::default()
185 }
186
187 let block_hash = header.hash();
188 if header.block_access_list_hash().is_none() {
189 return PayloadExtras::default()
190 };
191
192 let block_access_list = self
193 .provider
194 .get_block_access_list_raw(BlockId::from(block_hash))
195 .await
196 .inspect_err(|err| {
197 warn!(
198 target: "consensus::debug-client",
199 %err,
200 url=%self.url,
201 %block_hash,
202 "Failed to fetch block access list",
203 );
204 })
205 .ok()
206 .flatten();
207
208 PayloadExtras::from(block_access_list)
209 })
210 }
211}