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