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_eips::BlockId;
10use alloy_rlp::Encodable;
11use alloy_rpc_types_eth::{Block, BlockTransactions, Index};
12use futures::Future;
13use reth_node_api::BlockBody;
14use reth_primitives_traits::{AlloyBlockHeader, RecoveredBlock, SealedHeader, TransactionMeta};
15use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcConvert, RpcHeader};
16use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx};
17use reth_transaction_pool::{PoolTransaction, TransactionPool};
18use std::sync::Arc;
19
20pub type BlockReceiptsResult<N, E> = Result<Option<Vec<RpcReceipt<N>>>, E>;
22pub type BlockAndReceiptsResult<Eth> = Result<
24 Option<(
25 Arc<RecoveredBlock<<<Eth as RpcNodeCore>::Provider as BlockReader>::Block>>,
26 Arc<Vec<ProviderReceipt<<Eth as RpcNodeCore>::Provider>>>,
27 )>,
28 <Eth as EthApiTypes>::Error,
29>;
30
31pub trait EthBlocks: LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives>> {
34 fn rpc_block_header(
36 &self,
37 block_id: BlockId,
38 ) -> impl Future<Output = Result<Option<RpcHeader<Self::NetworkTypes>>, Self::Error>> + Send
39 where
40 Self: FullEthApiTypes,
41 {
42 async move { Ok(self.rpc_block(block_id, false).await?.map(|block| block.header)) }
43 }
44
45 fn rpc_block(
50 &self,
51 block_id: BlockId,
52 full: bool,
53 ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
54 where
55 Self: FullEthApiTypes,
56 {
57 async move {
58 let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
59
60 let block = block.clone_into_rpc_block(
61 full.into(),
62 |tx, tx_info| self.converter().fill(tx, tx_info),
63 |header, size| self.converter().convert_header(header, size),
64 )?;
65 Ok(Some(block))
66 }
67 }
68
69 fn block_transaction_count(
73 &self,
74 block_id: BlockId,
75 ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + Send {
76 async move {
77 if block_id.is_pending() {
78 return Ok(self
80 .provider()
81 .pending_block()
82 .map_err(Self::Error::from_eth_err)?
83 .map(|block| block.body().transaction_count()));
84 }
85
86 let block_hash = match self
87 .provider()
88 .block_hash_for_id(block_id)
89 .map_err(Self::Error::from_eth_err)?
90 {
91 Some(block_hash) => block_hash,
92 None => return Ok(None),
93 };
94
95 Ok(self
96 .cache()
97 .get_recovered_block(block_hash)
98 .await
99 .map_err(Self::Error::from_eth_err)?
100 .map(|b| b.body().transaction_count()))
101 }
102 }
103
104 fn block_receipts(
108 &self,
109 block_id: BlockId,
110 ) -> impl Future<Output = BlockReceiptsResult<Self::NetworkTypes, Self::Error>> + Send
111 where
112 Self: LoadReceipt,
113 {
114 async move {
115 if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
116 let block_number = block.number();
117 let base_fee = block.base_fee_per_gas();
118 let block_hash = block.hash();
119 let excess_blob_gas = block.excess_blob_gas();
120 let timestamp = block.timestamp();
121 let mut gas_used = 0;
122 let mut next_log_index = 0;
123
124 let inputs = block
125 .transactions_recovered()
126 .zip(Arc::unwrap_or_clone(receipts))
127 .enumerate()
128 .map(|(idx, (tx, receipt))| {
129 let meta = TransactionMeta {
130 tx_hash: *tx.tx_hash(),
131 index: idx as u64,
132 block_hash,
133 block_number,
134 base_fee,
135 excess_blob_gas,
136 timestamp,
137 };
138
139 let cumulative_gas_used = receipt.cumulative_gas_used();
140 let logs_len = receipt.logs().len();
141
142 let input = ConvertReceiptInput {
143 tx,
144 gas_used: cumulative_gas_used - gas_used,
145 next_log_index,
146 meta,
147 receipt,
148 };
149
150 gas_used = cumulative_gas_used;
151 next_log_index += logs_len;
152
153 input
154 })
155 .collect::<Vec<_>>();
156
157 return Ok(self
158 .converter()
159 .convert_receipts_with_block(inputs, block.sealed_block())
160 .map(Some)?)
161 }
162
163 Ok(None)
164 }
165 }
166
167 fn load_block_and_receipts(
169 &self,
170 block_id: BlockId,
171 ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
172 where
173 Self: LoadReceipt,
174 Self::Pool:
175 TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
176 {
177 async move {
178 if block_id.is_pending() {
179 if let Some((block, receipts)) = self
182 .provider()
183 .pending_block_and_receipts()
184 .map_err(Self::Error::from_eth_err)?
185 {
186 return Ok(Some((Arc::new(block), Arc::new(receipts))));
187 }
188
189 if let Some(pending) = self.local_pending_block().await? {
191 return Ok(Some((pending.block, pending.receipts)));
192 }
193 }
194
195 if let Some(block_hash) =
196 self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? &&
197 let Some((block, receipts)) = self
198 .cache()
199 .get_block_and_receipts(block_hash)
200 .await
201 .map_err(Self::Error::from_eth_err)?
202 {
203 return Ok(Some((block, receipts)));
204 }
205
206 Ok(None)
207 }
208 }
209
210 #[expect(clippy::type_complexity)]
214 fn ommers(
215 &self,
216 block_id: BlockId,
217 ) -> impl Future<Output = Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
218 {
219 async move {
220 if let Some(block) = self.recovered_block(block_id).await? {
221 Ok(block.body().ommers().map(|o| o.to_vec()))
222 } else {
223 Ok(None)
224 }
225 }
226 }
227
228 fn ommer_by_block_and_index(
232 &self,
233 block_id: BlockId,
234 index: Index,
235 ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
236 {
237 async move {
238 let uncles = if block_id.is_pending() {
239 self.provider()
241 .pending_block()
242 .map_err(Self::Error::from_eth_err)?
243 .and_then(|block| block.body().ommers().map(|o| o.to_vec()))
244 } else {
245 self.recovered_block(block_id)
246 .await?
247 .map(|block| block.body().ommers().map(|o| o.to_vec()).unwrap_or_default())
248 }
249 .unwrap_or_default();
250
251 uncles
252 .into_iter()
253 .nth(index.into())
254 .map(|header| {
255 let block =
256 alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
257 let size = block.length();
258 let header = self
259 .converter()
260 .convert_header(SealedHeader::new_unhashed(block.header), size)?;
261 Ok(Block {
262 uncles: vec![],
263 header,
264 transactions: BlockTransactions::Uncle,
265 withdrawals: None,
266 })
267 })
268 .transpose()
269 }
270 }
271}
272
273pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
277 #[expect(clippy::type_complexity)]
279 fn recovered_block(
280 &self,
281 block_id: BlockId,
282 ) -> impl Future<
283 Output = Result<
284 Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
285 Self::Error,
286 >,
287 > + Send {
288 async move {
289 if block_id.is_pending() {
290 if let Some(pending_block) =
292 self.provider().pending_block().map_err(Self::Error::from_eth_err)?
293 {
294 return Ok(Some(Arc::new(pending_block)));
295 }
296
297 return match self.local_pending_block().await? {
299 Some(pending) => Ok(Some(pending.block)),
300 None => Ok(None),
301 };
302 }
303
304 let block_hash = match self
305 .provider()
306 .block_hash_for_id(block_id)
307 .map_err(Self::Error::from_eth_err)?
308 {
309 Some(block_hash) => block_hash,
310 None => return Ok(None),
311 };
312
313 self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
314 }
315 }
316}