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 evm2::evm::Bal as EvmBal;
14use futures::Future;
15use reth_primitives_traits::{
16    AlloyBlockHeader, BlockBody, RecoveredBlock, SealedHeader, TransactionMeta,
17};
18use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcConvert, RpcHeader};
19use reth_rpc_eth_types::block::SharedReceipts;
20use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx};
21use reth_transaction_pool::{PoolTransaction, TransactionPool};
22use std::sync::Arc;
23
24/// Result type of the fetched block receipts.
25pub type BlockReceiptsResult<N, E> = Result<Option<Vec<RpcReceipt<N>>>, E>;
26/// Result type of the fetched block and its receipts.
27pub type BlockAndReceiptsResult<Eth> = Result<
28    Option<(
29        Arc<RecoveredBlock<<<Eth as RpcNodeCore>::Provider as BlockReader>::Block>>,
30        SharedReceipts<ProviderReceipt<<Eth as RpcNodeCore>::Provider>>,
31    )>,
32    <Eth as EthApiTypes>::Error,
33>;
34
35/// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the
36/// `eth_` namespace.
37pub trait EthBlocks: LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives>> {
38    /// Returns the block header for the given block id.
39    fn rpc_block_header(
40        &self,
41        block_id: BlockId,
42    ) -> impl Future<Output = Result<Option<RpcHeader<Self::NetworkTypes>>, Self::Error>> + Send
43    where
44        Self: FullEthApiTypes,
45    {
46        async move {
47            let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
48            // Header responses omit the block size: https://github.com/ethereum/execution-apis/pull/877
49            let header = self.converter().convert_header(block.clone_sealed_header(), None)?;
50            Ok(Some(header))
51        }
52    }
53
54    /// Returns the populated rpc block object for the given block id.
55    ///
56    /// If `full` is true, the block object will contain all transaction objects, otherwise it will
57    /// only contain the transaction hashes.
58    fn rpc_block(
59        &self,
60        block_id: BlockId,
61        full: bool,
62    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
63    where
64        Self: FullEthApiTypes,
65    {
66        async move {
67            let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
68
69            let block = block.clone_into_rpc_block(
70                full.into(),
71                |tx, tx_info| self.converter().fill(tx, tx_info),
72                |header, block_size| self.converter().convert_header(header, Some(block_size)),
73            )?;
74            Ok(Some(block))
75        }
76    }
77
78    /// Returns the number transactions in the given block.
79    ///
80    /// Returns `None` if the block does not exist
81    fn block_transaction_count(
82        &self,
83        block_id: BlockId,
84    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + Send {
85        async move { Ok(self.recovered_block(block_id).await?.map(|b| b.body().transaction_count())) }
86    }
87
88    /// Helper function for `eth_getBlockReceipts`.
89    ///
90    /// Returns all transaction receipts in block, or `None` if block wasn't found.
91    fn block_receipts(
92        &self,
93        block_id: BlockId,
94    ) -> impl Future<Output = BlockReceiptsResult<Self::NetworkTypes, Self::Error>> + Send
95    where
96        Self: LoadReceipt,
97    {
98        async move {
99            if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
100                let block_number = block.number();
101                let base_fee = block.base_fee_per_gas();
102                let block_hash = block.hash();
103                let excess_blob_gas = block.excess_blob_gas();
104                let timestamp = block.timestamp();
105                let mut gas_used = 0;
106                let mut next_log_index = 0;
107
108                let inputs = block
109                    .transactions_recovered()
110                    .zip(receipts.into_vec())
111                    .enumerate()
112                    .map(|(idx, (tx, receipt))| {
113                        let meta = TransactionMeta {
114                            tx_hash: *tx.tx_hash(),
115                            index: idx as u64,
116                            block_hash,
117                            block_number,
118                            base_fee,
119                            excess_blob_gas,
120                            timestamp,
121                        };
122
123                        let cumulative_gas_used = receipt.cumulative_gas_used();
124                        let logs_len = receipt.logs().len();
125
126                        let input = ConvertReceiptInput {
127                            tx,
128                            gas_used: cumulative_gas_used - gas_used,
129                            next_log_index,
130                            meta,
131                            receipt,
132                        };
133
134                        gas_used = cumulative_gas_used;
135                        next_log_index += logs_len;
136
137                        input
138                    })
139                    .collect::<Vec<_>>();
140
141                return Ok(self
142                    .converter()
143                    .convert_receipts_with_block(inputs, block.sealed_block())
144                    .map(Some)?)
145            }
146
147            Ok(None)
148        }
149    }
150
151    /// Helper method that loads a block and all its receipts.
152    fn load_block_and_receipts(
153        &self,
154        block_id: BlockId,
155    ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
156    where
157        Self: LoadReceipt,
158        Self::Pool:
159            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
160    {
161        async move {
162            if block_id.is_pending() {
163                if self.pending_block_kind().is_none() {
164                    return Ok(None);
165                }
166
167                // First, try to get the pending block from the provider, in case we already
168                // received the actual pending block from the CL.
169                if let Some(pending) = self
170                    .provider()
171                    .pending_block_and_receipts()
172                    .map_err(Self::Error::from_eth_err)?
173                {
174                    let (block, output) = pending.into_parts();
175                    return Ok(Some((block, output.into())));
176                }
177
178                // If no pending block from provider, build the pending block locally.
179                if let Some(pending) = self.local_pending_block().await? {
180                    return Ok(Some((pending.block, pending.receipts)));
181                }
182            }
183
184            if let Some(block_hash) =
185                self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? &&
186                let Some((block, receipts)) = self
187                    .cache()
188                    .get_block_and_receipts(block_hash)
189                    .await
190                    .map_err(Self::Error::from_eth_err)?
191            {
192                return Ok(Some((block, receipts.into())));
193            }
194
195            Ok(None)
196        }
197    }
198
199    /// Returns uncle headers of given block.
200    ///
201    /// Returns an empty vec if there are none.
202    #[expect(clippy::type_complexity)]
203    fn ommers(
204        &self,
205        block_id: BlockId,
206    ) -> impl Future<Output = Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
207    {
208        async move {
209            if let Some(block) = self.recovered_block(block_id).await? {
210                Ok(block.body().ommers().map(|o| o.to_vec()))
211            } else {
212                Ok(None)
213            }
214        }
215    }
216
217    /// Returns uncle block at given index in given block.
218    ///
219    /// Returns `None` if index out of range.
220    fn ommer_by_block_and_index(
221        &self,
222        block_id: BlockId,
223        index: Index,
224    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
225    {
226        async move {
227            let uncles = self
228                .recovered_block(block_id)
229                .await?
230                .map(|block| block.body().ommers().map(|o| o.to_vec()).unwrap_or_default())
231                .unwrap_or_default();
232
233            uncles
234                .into_iter()
235                .nth(index.into())
236                .map(|header| {
237                    let block =
238                        alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
239                    let size = block.length();
240                    let header = self
241                        .converter()
242                        .convert_header(SealedHeader::new_unhashed(block.header), Some(size))?;
243                    Ok(Block {
244                        uncles: vec![],
245                        header,
246                        transactions: BlockTransactions::Uncle,
247                        withdrawals: None,
248                    })
249                })
250                .transpose()
251        }
252    }
253}
254
255/// Loads a block from database.
256///
257/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
258pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
259    /// Returns the block object for the given block id.
260    #[expect(clippy::type_complexity)]
261    fn recovered_block(
262        &self,
263        block_id: BlockId,
264    ) -> impl Future<
265        Output = Result<
266            Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
267            Self::Error,
268        >,
269    > + Send {
270        async move {
271            if block_id.is_pending() {
272                if self.pending_block_kind().is_none() {
273                    return Ok(None);
274                }
275
276                // Pending block can be fetched directly without need for caching
277                if let Some(pending_block) =
278                    self.provider().pending_block().map_err(Self::Error::from_eth_err)?
279                {
280                    return Ok(Some(pending_block));
281                }
282
283                // If no pending block from provider, try to get local pending block
284                return match self.local_pending_block().await? {
285                    Some(pending) => Ok(Some(pending.block)),
286                    None => Ok(None),
287                };
288            }
289
290            let block_hash = match self
291                .provider()
292                .block_hash_for_id(block_id)
293                .map_err(Self::Error::from_eth_err)?
294            {
295                Some(block_hash) => block_hash,
296                None => return Ok(None),
297            };
298
299            self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
300        }
301    }
302
303    /// Returns the block for the given block id, together with the block's cached block access
304    /// list, if any.
305    ///
306    /// The BAL is only returned if it is already cached, it is never fetched from the BAL store.
307    /// Pending blocks never have a BAL.
308    #[expect(clippy::type_complexity)]
309    fn recovered_block_and_maybe_bal(
310        &self,
311        block_id: BlockId,
312    ) -> impl Future<
313        Output = Result<
314            Option<(
315                Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>,
316                Option<Arc<DecodedBal<Arc<EvmBal>>>>,
317            )>,
318            Self::Error,
319        >,
320    > + Send {
321        async move {
322            if block_id.is_pending() {
323                return Ok(self.recovered_block(block_id).await?.map(|block| (block, None)));
324            }
325
326            let block_hash = match self
327                .provider()
328                .block_hash_for_id(block_id)
329                .map_err(Self::Error::from_eth_err)?
330            {
331                Some(block_hash) => block_hash,
332                None => return Ok(None),
333            };
334
335            self.cache()
336                .get_recovered_block_and_maybe_bal(block_hash)
337                .await
338                .map_err(Self::Error::from_eth_err)
339        }
340    }
341}