reth_rpc_eth_api/helpers/
block.rs1use super::{LoadPendingBlock, LoadReceipt, SpawnBlocking};
4use crate::{
5 node::RpcNodeCoreExt, EthApiTypes, FromEthApiError, FullEthApiTypes, RpcBlock, RpcNodeCore,
6 RpcReceipt,
7};
8use alloy_consensus::{transaction::TxHashRef, TxReceipt};
9use alloy_eip7928::bal::DecodedBal;
10use alloy_eips::BlockId;
11use alloy_rlp::Encodable;
12use alloy_rpc_types_eth::{Block, BlockTransactions, Index};
13use evm2::evm::Bal as EvmBal;
14use futures::Future;
15use reth_primitives_traits::{
16 AlloyBlockHeader, BlockBody, RecoveredBlock, SealedHeader, TransactionMeta,
17};
18use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcConvert, RpcHeader};
19use reth_rpc_eth_types::block::SharedReceipts;
20use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx};
21use reth_transaction_pool::{PoolTransaction, TransactionPool};
22use std::sync::Arc;
23
24pub type BlockReceiptsResult<N, E> = Result<Option<Vec<RpcReceipt<N>>>, E>;
26pub type BlockAndReceiptsResult<Eth> = Result<
28 Option<(
29 Arc<RecoveredBlock<<<Eth as RpcNodeCore>::Provider as BlockReader>::Block>>,
30 SharedReceipts<ProviderReceipt<<Eth as RpcNodeCore>::Provider>>,
31 )>,
32 <Eth as EthApiTypes>::Error,
33>;
34
35pub trait EthBlocks: LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives>> {
38 fn rpc_block_header(
40 &self,
41 block_id: BlockId,
42 ) -> impl Future<Output = Result<Option<RpcHeader<Self::NetworkTypes>>, Self::Error>> + Send
43 where
44 Self: FullEthApiTypes,
45 {
46 async move {
47 let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
48 let header = self.converter().convert_header(block.clone_sealed_header(), None)?;
50 Ok(Some(header))
51 }
52 }
53
54 fn rpc_block(
59 &self,
60 block_id: BlockId,
61 full: bool,
62 ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
63 where
64 Self: FullEthApiTypes,
65 {
66 async move {
67 let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
68
69 let block = block.clone_into_rpc_block(
70 full.into(),
71 |tx, tx_info| self.converter().fill(tx, tx_info),
72 |header, block_size| self.converter().convert_header(header, Some(block_size)),
73 )?;
74 Ok(Some(block))
75 }
76 }
77
78 fn block_transaction_count(
82 &self,
83 block_id: BlockId,
84 ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + Send {
85 async move { Ok(self.recovered_block(block_id).await?.map(|b| b.body().transaction_count())) }
86 }
87
88 fn block_receipts(
92 &self,
93 block_id: BlockId,
94 ) -> impl Future<Output = BlockReceiptsResult<Self::NetworkTypes, Self::Error>> + Send
95 where
96 Self: LoadReceipt,
97 {
98 async move {
99 if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
100 let block_number = block.number();
101 let base_fee = block.base_fee_per_gas();
102 let block_hash = block.hash();
103 let excess_blob_gas = block.excess_blob_gas();
104 let timestamp = block.timestamp();
105 let mut gas_used = 0;
106 let mut next_log_index = 0;
107
108 let inputs = block
109 .transactions_recovered()
110 .zip(receipts.into_vec())
111 .enumerate()
112 .map(|(idx, (tx, receipt))| {
113 let meta = TransactionMeta {
114 tx_hash: *tx.tx_hash(),
115 index: idx as u64,
116 block_hash,
117 block_number,
118 base_fee,
119 excess_blob_gas,
120 timestamp,
121 };
122
123 let cumulative_gas_used = receipt.cumulative_gas_used();
124 let logs_len = receipt.logs().len();
125
126 let input = ConvertReceiptInput {
127 tx,
128 gas_used: cumulative_gas_used - gas_used,
129 next_log_index,
130 meta,
131 receipt,
132 };
133
134 gas_used = cumulative_gas_used;
135 next_log_index += logs_len;
136
137 input
138 })
139 .collect::<Vec<_>>();
140
141 return Ok(self
142 .converter()
143 .convert_receipts_with_block(inputs, block.sealed_block())
144 .map(Some)?)
145 }
146
147 Ok(None)
148 }
149 }
150
151 fn load_block_and_receipts(
153 &self,
154 block_id: BlockId,
155 ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
156 where
157 Self: LoadReceipt,
158 Self::Pool:
159 TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
160 {
161 async move {
162 if block_id.is_pending() {
163 if self.pending_block_kind().is_none() {
164 return Ok(None);
165 }
166
167 if let Some(pending) = self
170 .provider()
171 .pending_block_and_receipts()
172 .map_err(Self::Error::from_eth_err)?
173 {
174 let (block, output) = pending.into_parts();
175 return Ok(Some((block, output.into())));
176 }
177
178 if let Some(pending) = self.local_pending_block().await? {
180 return Ok(Some((pending.block, pending.receipts)));
181 }
182 }
183
184 if let Some(block_hash) =
185 self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? &&
186 let Some((block, receipts)) = self
187 .cache()
188 .get_block_and_receipts(block_hash)
189 .await
190 .map_err(Self::Error::from_eth_err)?
191 {
192 return Ok(Some((block, receipts.into())));
193 }
194
195 Ok(None)
196 }
197 }
198
199 #[expect(clippy::type_complexity)]
203 fn ommers(
204 &self,
205 block_id: BlockId,
206 ) -> impl Future<Output = Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
207 {
208 async move {
209 if let Some(block) = self.recovered_block(block_id).await? {
210 Ok(block.body().ommers().map(|o| o.to_vec()))
211 } else {
212 Ok(None)
213 }
214 }
215 }
216
217 fn ommer_by_block_and_index(
221 &self,
222 block_id: BlockId,
223 index: Index,
224 ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
225 {
226 async move {
227 let uncles = self
228 .recovered_block(block_id)
229 .await?
230 .map(|block| block.body().ommers().map(|o| o.to_vec()).unwrap_or_default())
231 .unwrap_or_default();
232
233 uncles
234 .into_iter()
235 .nth(index.into())
236 .map(|header| {
237 let block =
238 alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
239 let size = block.length();
240 let header = self
241 .converter()
242 .convert_header(SealedHeader::new_unhashed(block.header), Some(size))?;
243 Ok(Block {
244 uncles: vec![],
245 header,
246 transactions: BlockTransactions::Uncle,
247 withdrawals: None,
248 })
249 })
250 .transpose()
251 }
252 }
253}
254
255pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
259 #[expect(clippy::type_complexity)]
261 fn recovered_block(
262 &self,
263 block_id: BlockId,
264 ) -> impl Future<
265 Output = Result<
266 Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
267 Self::Error,
268 >,
269 > + Send {
270 async move {
271 if block_id.is_pending() {
272 if self.pending_block_kind().is_none() {
273 return Ok(None);
274 }
275
276 if let Some(pending_block) =
278 self.provider().pending_block().map_err(Self::Error::from_eth_err)?
279 {
280 return Ok(Some(pending_block));
281 }
282
283 return match self.local_pending_block().await? {
285 Some(pending) => Ok(Some(pending.block)),
286 None => Ok(None),
287 };
288 }
289
290 let block_hash = match self
291 .provider()
292 .block_hash_for_id(block_id)
293 .map_err(Self::Error::from_eth_err)?
294 {
295 Some(block_hash) => block_hash,
296 None => return Ok(None),
297 };
298
299 self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
300 }
301 }
302
303 #[expect(clippy::type_complexity)]
309 fn recovered_block_and_maybe_bal(
310 &self,
311 block_id: BlockId,
312 ) -> impl Future<
313 Output = Result<
314 Option<(
315 Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>,
316 Option<Arc<DecodedBal<Arc<EvmBal>>>>,
317 )>,
318 Self::Error,
319 >,
320 > + Send {
321 async move {
322 if block_id.is_pending() {
323 return Ok(self.recovered_block(block_id).await?.map(|block| (block, None)));
324 }
325
326 let block_hash = match self
327 .provider()
328 .block_hash_for_id(block_id)
329 .map_err(Self::Error::from_eth_err)?
330 {
331 Some(block_hash) => block_hash,
332 None => return Ok(None),
333 };
334
335 self.cache()
336 .get_recovered_block_and_maybe_bal(block_hash)
337 .await
338 .map_err(Self::Error::from_eth_err)
339 }
340 }
341}