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 {
43 let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
44 let header =
45 self.converter().convert_header(block.clone_sealed_header(), block.rlp_length())?;
46 Ok(Some(header))
47 }
48 }
49
50 fn rpc_block(
55 &self,
56 block_id: BlockId,
57 full: bool,
58 ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
59 where
60 Self: FullEthApiTypes,
61 {
62 async move {
63 let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
64
65 let block = block.clone_into_rpc_block(
66 full.into(),
67 |tx, tx_info| self.converter().fill(tx, tx_info),
68 |header, size| self.converter().convert_header(header, size),
69 )?;
70 Ok(Some(block))
71 }
72 }
73
74 fn block_transaction_count(
78 &self,
79 block_id: BlockId,
80 ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + Send {
81 async move { Ok(self.recovered_block(block_id).await?.map(|b| b.body().transaction_count())) }
82 }
83
84 fn block_receipts(
88 &self,
89 block_id: BlockId,
90 ) -> impl Future<Output = BlockReceiptsResult<Self::NetworkTypes, Self::Error>> + Send
91 where
92 Self: LoadReceipt,
93 {
94 async move {
95 if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
96 let block_number = block.number();
97 let base_fee = block.base_fee_per_gas();
98 let block_hash = block.hash();
99 let excess_blob_gas = block.excess_blob_gas();
100 let timestamp = block.timestamp();
101 let mut gas_used = 0;
102 let mut next_log_index = 0;
103
104 let inputs = block
105 .transactions_recovered()
106 .zip(Arc::unwrap_or_clone(receipts))
107 .enumerate()
108 .map(|(idx, (tx, receipt))| {
109 let meta = TransactionMeta {
110 tx_hash: *tx.tx_hash(),
111 index: idx as u64,
112 block_hash,
113 block_number,
114 base_fee,
115 excess_blob_gas,
116 timestamp,
117 };
118
119 let cumulative_gas_used = receipt.cumulative_gas_used();
120 let logs_len = receipt.logs().len();
121
122 let input = ConvertReceiptInput {
123 tx,
124 gas_used: cumulative_gas_used - gas_used,
125 next_log_index,
126 meta,
127 receipt,
128 };
129
130 gas_used = cumulative_gas_used;
131 next_log_index += logs_len;
132
133 input
134 })
135 .collect::<Vec<_>>();
136
137 return Ok(self
138 .converter()
139 .convert_receipts_with_block(inputs, block.sealed_block())
140 .map(Some)?)
141 }
142
143 Ok(None)
144 }
145 }
146
147 fn load_block_and_receipts(
149 &self,
150 block_id: BlockId,
151 ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
152 where
153 Self: LoadReceipt,
154 Self::Pool:
155 TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
156 {
157 async move {
158 if block_id.is_pending() {
159 if self.pending_block_kind().is_none() {
160 return Ok(None);
161 }
162
163 if let Some((block, receipts)) = self
166 .provider()
167 .pending_block_and_receipts()
168 .map_err(Self::Error::from_eth_err)?
169 {
170 return Ok(Some((Arc::new(block), Arc::new(receipts))));
171 }
172
173 if let Some(pending) = self.local_pending_block().await? {
175 return Ok(Some((pending.block, pending.receipts)));
176 }
177 }
178
179 if let Some(block_hash) =
180 self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? &&
181 let Some((block, receipts)) = self
182 .cache()
183 .get_block_and_receipts(block_hash)
184 .await
185 .map_err(Self::Error::from_eth_err)?
186 {
187 return Ok(Some((block, receipts)));
188 }
189
190 Ok(None)
191 }
192 }
193
194 #[expect(clippy::type_complexity)]
198 fn ommers(
199 &self,
200 block_id: BlockId,
201 ) -> impl Future<Output = Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
202 {
203 async move {
204 if let Some(block) = self.recovered_block(block_id).await? {
205 Ok(block.body().ommers().map(|o| o.to_vec()))
206 } else {
207 Ok(None)
208 }
209 }
210 }
211
212 fn ommer_by_block_and_index(
216 &self,
217 block_id: BlockId,
218 index: Index,
219 ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
220 {
221 async move {
222 let uncles = self
223 .recovered_block(block_id)
224 .await?
225 .map(|block| block.body().ommers().map(|o| o.to_vec()).unwrap_or_default())
226 .unwrap_or_default();
227
228 uncles
229 .into_iter()
230 .nth(index.into())
231 .map(|header| {
232 let block =
233 alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
234 let size = block.length();
235 let header = self
236 .converter()
237 .convert_header(SealedHeader::new_unhashed(block.header), size)?;
238 Ok(Block {
239 uncles: vec![],
240 header,
241 transactions: BlockTransactions::Uncle,
242 withdrawals: None,
243 })
244 })
245 .transpose()
246 }
247 }
248}
249
250pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
254 #[expect(clippy::type_complexity)]
256 fn recovered_block(
257 &self,
258 block_id: BlockId,
259 ) -> impl Future<
260 Output = Result<
261 Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
262 Self::Error,
263 >,
264 > + Send {
265 async move {
266 if block_id.is_pending() {
267 if self.pending_block_kind().is_none() {
268 return Ok(None);
269 }
270
271 if let Some(pending_block) =
273 self.provider().pending_block().map_err(Self::Error::from_eth_err)?
274 {
275 return Ok(Some(Arc::new(pending_block)));
276 }
277
278 return match self.local_pending_block().await? {
280 Some(pending) => Ok(Some(pending.block)),
281 None => Ok(None),
282 };
283 }
284
285 let block_hash = match self
286 .provider()
287 .block_hash_for_id(block_id)
288 .map_err(Self::Error::from_eth_err)?
289 {
290 Some(block_hash) => block_hash,
291 None => return Ok(None),
292 };
293
294 self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
295 }
296 }
297}