Skip to main content

reth_rpc_eth_api/helpers/
block.rs

1//! Database access for `eth_` block RPC methods. Loads block and receipt data w.r.t. network.
2
3use 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
23/// Result type of the fetched block receipts.
24pub type BlockReceiptsResult<N, E> = Result<Option<Vec<RpcReceipt<N>>>, E>;
25/// Result type of the fetched block and its receipts.
26pub 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
34/// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the
35/// `eth_` namespace.
36pub trait EthBlocks: LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives>> {
37    /// Returns the block header for the given block id.
38    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            // Header responses omit the block size: https://github.com/ethereum/execution-apis/pull/877
48            let header = self.converter().convert_header(block.clone_sealed_header(), None)?;
49            Ok(Some(header))
50        }
51    }
52
53    /// Returns the populated rpc block object for the given block id.
54    ///
55    /// If `full` is true, the block object will contain all transaction objects, otherwise it will
56    /// only contain the transaction hashes.
57    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    /// Returns the number transactions in the given block.
78    ///
79    /// Returns `None` if the block does not exist
80    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    /// Helper function for `eth_getBlockReceipts`.
88    ///
89    /// Returns all transaction receipts in block, or `None` if block wasn't found.
90    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    /// Helper method that loads a block and all its receipts.
151    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                // First, try to get the pending block from the provider, in case we already
167                // received the actual pending block from the CL.
168                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 no pending block from provider, build the pending block locally.
178                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    /// Returns uncle headers of given block.
199    ///
200    /// Returns an empty vec if there are none.
201    #[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    /// Returns uncle block at given index in given block.
217    ///
218    /// Returns `None` if index out of range.
219    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
254/// Loads a block from database.
255///
256/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
257pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
258    /// Returns the block object for the given block id.
259    #[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                // Pending block can be fetched directly without need for caching
276                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                // If no pending block from provider, try to get local pending block
283                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    /// Returns the block for the given block id, together with the block's cached block access
303    /// list, if any.
304    ///
305    /// The BAL is only returned if it is already cached, it is never fetched from the BAL store.
306    /// Pending blocks never have a BAL.
307    #[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}