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