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_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
22/// Result type of the fetched block receipts.
23pub type BlockReceiptsResult<N, E> = Result<Option<Vec<RpcReceipt<N>>>, E>;
24/// Result type of the fetched block and its receipts.
25pub 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
33/// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the
34/// `eth_` namespace.
35pub trait EthBlocks: LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives>> {
36    /// Returns the block header for the given block id.
37    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    /// Returns the populated rpc block object for the given block id.
53    ///
54    /// If `full` is true, the block object will contain all transaction objects, otherwise it will
55    /// only contain the transaction hashes.
56    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    /// Returns the number transactions in the given block.
77    ///
78    /// Returns `None` if the block does not exist
79    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    /// Helper function for `eth_getBlockReceipts`.
87    ///
88    /// Returns all transaction receipts in block, or `None` if block wasn't found.
89    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    /// Helper method that loads a block and all its receipts.
150    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                // First, try to get the pending block from the provider, in case we already
166                // received the actual pending block from the CL.
167                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 no pending block from provider, build the pending block locally.
176                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    /// Returns uncle headers of given block.
197    ///
198    /// Returns an empty vec if there are none.
199    #[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    /// Returns uncle block at given index in given block.
215    ///
216    /// Returns `None` if index out of range.
217    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
252/// Loads a block from database.
253///
254/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
255pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
256    /// Returns the block object for the given block id.
257    #[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                // Pending block can be fetched directly without need for caching
274                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                // If no pending block from provider, try to get local pending block
281                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    /// Returns the block for the given block id, together with the block's cached block access
301    /// list, if any.
302    ///
303    /// The BAL is only returned if it is already cached, it is never fetched from the BAL store.
304    /// Pending blocks never have a BAL.
305    #[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}