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_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
20/// Result type of the fetched block receipts.
21pub type BlockReceiptsResult<N, E> = Result<Option<Vec<RpcReceipt<N>>>, E>;
22/// Result type of the fetched block and its receipts.
23pub 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
31/// Block related functions for the [`EthApiServer`](crate::EthApiServer) trait in the
32/// `eth_` namespace.
33pub trait EthBlocks: LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives>> {
34    /// Returns the block header for the given block id.
35    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    /// Returns the populated rpc block object for the given block id.
46    ///
47    /// If `full` is true, the block object will contain all transaction objects, otherwise it will
48    /// only contain the transaction hashes.
49    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    /// Returns the number transactions in the given block.
70    ///
71    /// Returns `None` if the block does not exist
72    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 no pending block from provider, build the pending block locally.
78            if block_id.is_pending() {
79                if let Some(pending) = self.local_pending_block().await? {
80                    return Ok(Some(pending.block.body().transaction_count()));
81                }
82                // Pending block can be fetched directly without need for caching
83                return Ok(self
84                    .provider()
85                    .pending_block()
86                    .map_err(Self::Error::from_eth_err)?
87                    .map(|block| block.body().transaction_count()));
88            }
89
90            let block_hash = match self
91                .provider()
92                .block_hash_for_id(block_id)
93                .map_err(Self::Error::from_eth_err)?
94            {
95                Some(block_hash) => block_hash,
96                None => return Ok(None),
97            };
98
99            Ok(self
100                .cache()
101                .get_recovered_block(block_hash)
102                .await
103                .map_err(Self::Error::from_eth_err)?
104                .map(|b| b.body().transaction_count()))
105        }
106    }
107
108    /// Helper function for `eth_getBlockReceipts`.
109    ///
110    /// Returns all transaction receipts in block, or `None` if block wasn't found.
111    fn block_receipts(
112        &self,
113        block_id: BlockId,
114    ) -> impl Future<Output = BlockReceiptsResult<Self::NetworkTypes, Self::Error>> + Send
115    where
116        Self: LoadReceipt,
117    {
118        async move {
119            if let Some((block, receipts)) = self.load_block_and_receipts(block_id).await? {
120                let block_number = block.number();
121                let base_fee = block.base_fee_per_gas();
122                let block_hash = block.hash();
123                let excess_blob_gas = block.excess_blob_gas();
124                let timestamp = block.timestamp();
125                let mut gas_used = 0;
126                let mut next_log_index = 0;
127
128                let inputs = block
129                    .transactions_recovered()
130                    .zip(Arc::unwrap_or_clone(receipts))
131                    .enumerate()
132                    .map(|(idx, (tx, receipt))| {
133                        let meta = TransactionMeta {
134                            tx_hash: *tx.tx_hash(),
135                            index: idx as u64,
136                            block_hash,
137                            block_number,
138                            base_fee,
139                            excess_blob_gas,
140                            timestamp,
141                        };
142
143                        let cumulative_gas_used = receipt.cumulative_gas_used();
144                        let logs_len = receipt.logs().len();
145
146                        let input = ConvertReceiptInput {
147                            tx,
148                            gas_used: cumulative_gas_used - gas_used,
149                            next_log_index,
150                            meta,
151                            receipt,
152                        };
153
154                        gas_used = cumulative_gas_used;
155                        next_log_index += logs_len;
156
157                        input
158                    })
159                    .collect::<Vec<_>>();
160
161                return Ok(self
162                    .converter()
163                    .convert_receipts_with_block(inputs, block.sealed_block())
164                    .map(Some)?)
165            }
166
167            Ok(None)
168        }
169    }
170
171    /// Helper method that loads a block and all its receipts.
172    fn load_block_and_receipts(
173        &self,
174        block_id: BlockId,
175    ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
176    where
177        Self: LoadReceipt,
178        Self::Pool:
179            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
180    {
181        async move {
182            if block_id.is_pending() {
183                // First, try to get the pending block from the provider, in case we already
184                // received the actual pending block from the CL.
185                if let Some((block, receipts)) = self
186                    .provider()
187                    .pending_block_and_receipts()
188                    .map_err(Self::Error::from_eth_err)?
189                {
190                    return Ok(Some((Arc::new(block), Arc::new(receipts))));
191                }
192
193                // If no pending block from provider, build the pending block locally.
194                if let Some(pending) = self.local_pending_block().await? {
195                    return Ok(Some((pending.block, pending.receipts)));
196                }
197            }
198
199            if let Some(block_hash) =
200                self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)? &&
201                let Some((block, receipts)) = self
202                    .cache()
203                    .get_block_and_receipts(block_hash)
204                    .await
205                    .map_err(Self::Error::from_eth_err)?
206            {
207                return Ok(Some((block, receipts)));
208            }
209
210            Ok(None)
211        }
212    }
213
214    /// Returns uncle headers of given block.
215    ///
216    /// Returns an empty vec if there are none.
217    #[expect(clippy::type_complexity)]
218    fn ommers(
219        &self,
220        block_id: BlockId,
221    ) -> impl Future<Output = Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
222    {
223        async move {
224            if let Some(block) = self.recovered_block(block_id).await? {
225                Ok(block.body().ommers().map(|o| o.to_vec()))
226            } else {
227                Ok(None)
228            }
229        }
230    }
231
232    /// Returns uncle block at given index in given block.
233    ///
234    /// Returns `None` if index out of range.
235    fn ommer_by_block_and_index(
236        &self,
237        block_id: BlockId,
238        index: Index,
239    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
240    {
241        async move {
242            let uncles = if block_id.is_pending() {
243                // Pending block can be fetched directly without need for caching
244                self.provider()
245                    .pending_block()
246                    .map_err(Self::Error::from_eth_err)?
247                    .and_then(|block| block.body().ommers().map(|o| o.to_vec()))
248            } else {
249                self.recovered_block(block_id)
250                    .await?
251                    .map(|block| block.body().ommers().map(|o| o.to_vec()).unwrap_or_default())
252            }
253            .unwrap_or_default();
254
255            uncles
256                .into_iter()
257                .nth(index.into())
258                .map(|header| {
259                    let block =
260                        alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
261                    let size = block.length();
262                    let header = self
263                        .converter()
264                        .convert_header(SealedHeader::new_unhashed(block.header), size)?;
265                    Ok(Block {
266                        uncles: vec![],
267                        header,
268                        transactions: BlockTransactions::Uncle,
269                        withdrawals: None,
270                    })
271                })
272                .transpose()
273        }
274    }
275}
276
277/// Loads a block from database.
278///
279/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
280pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
281    /// Returns the block object for the given block id.
282    #[expect(clippy::type_complexity)]
283    fn recovered_block(
284        &self,
285        block_id: BlockId,
286    ) -> impl Future<
287        Output = Result<
288            Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
289            Self::Error,
290        >,
291    > + Send {
292        async move {
293            if block_id.is_pending() {
294                // Pending block can be fetched directly without need for caching
295                if let Some(pending_block) =
296                    self.provider().pending_block().map_err(Self::Error::from_eth_err)?
297                {
298                    return Ok(Some(Arc::new(pending_block)));
299                }
300
301                // If no pending block from provider, try to get local pending block
302                return match self.local_pending_block().await? {
303                    Some(pending) => Ok(Some(pending.block)),
304                    None => Ok(None),
305                };
306            }
307
308            let block_hash = match self
309                .provider()
310                .block_hash_for_id(block_id)
311                .map_err(Self::Error::from_eth_err)?
312            {
313                Some(block_hash) => block_hash,
314                None => return Ok(None),
315            };
316
317            self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
318        }
319    }
320}