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::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::{
15    AlloyBlockHeader, RecoveredBlock, SealedHeader, SignedTransaction, TransactionMeta,
16};
17use reth_rpc_convert::{transaction::ConvertReceiptInput, RpcConvert, RpcHeader};
18use reth_storage_api::{BlockIdReader, BlockReader, ProviderHeader, ProviderReceipt, ProviderTx};
19use reth_transaction_pool::{PoolTransaction, TransactionPool};
20use std::{borrow::Cow, 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:
36    LoadBlock<RpcConvert: RpcConvert<Primitives = Self::Primitives, Error = Self::Error>>
37{
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 { Ok(self.rpc_block(block_id, false).await?.map(|block| block.header)) }
47    }
48
49    /// Returns the populated rpc block object for the given block id.
50    ///
51    /// If `full` is true, the block object will contain all transaction objects, otherwise it will
52    /// only contain the transaction hashes.
53    fn rpc_block(
54        &self,
55        block_id: BlockId,
56        full: bool,
57    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
58    where
59        Self: FullEthApiTypes,
60    {
61        async move {
62            let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
63
64            let block = block.clone_into_rpc_block(
65                full.into(),
66                |tx, tx_info| self.tx_resp_builder().fill(tx, tx_info),
67                |header, size| self.tx_resp_builder().convert_header(header, size),
68            )?;
69            Ok(Some(block))
70        }
71    }
72
73    /// Returns the number transactions in the given block.
74    ///
75    /// Returns `None` if the block does not exist
76    fn block_transaction_count(
77        &self,
78        block_id: BlockId,
79    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + Send {
80        async move {
81            if block_id.is_pending() {
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(receipts.iter())
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 input = ConvertReceiptInput {
144                            receipt: Cow::Borrowed(receipt),
145                            tx,
146                            gas_used: receipt.cumulative_gas_used() - gas_used,
147                            next_log_index,
148                            meta,
149                        };
150
151                        gas_used = receipt.cumulative_gas_used();
152                        next_log_index += receipt.logs().len();
153
154                        input
155                    })
156                    .collect::<Vec<_>>();
157
158                return self.tx_resp_builder().convert_receipts(inputs).map(Some)
159            }
160
161            Ok(None)
162        }
163    }
164
165    /// Helper method that loads a block and all its receipts.
166    fn load_block_and_receipts(
167        &self,
168        block_id: BlockId,
169    ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
170    where
171        Self: LoadReceipt,
172        Self::Pool:
173            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
174    {
175        async move {
176            if block_id.is_pending() {
177                // First, try to get the pending block from the provider, in case we already
178                // received the actual pending block from the CL.
179                if let Some((block, receipts)) = self
180                    .provider()
181                    .pending_block_and_receipts()
182                    .map_err(Self::Error::from_eth_err)?
183                {
184                    return Ok(Some((Arc::new(block), Arc::new(receipts))));
185                }
186
187                // If no pending block from provider, build the pending block locally.
188                if let Some((block, receipts)) = self.local_pending_block().await? {
189                    return Ok(Some((block, receipts)));
190                }
191            }
192
193            if let Some(block_hash) =
194                self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)?
195            {
196                if let Some((block, receipts)) = self
197                    .cache()
198                    .get_block_and_receipts(block_hash)
199                    .await
200                    .map_err(Self::Error::from_eth_err)?
201                {
202                    return Ok(Some((block, receipts)));
203                }
204            }
205
206            Ok(None)
207        }
208    }
209
210    /// Returns uncle headers of given block.
211    ///
212    /// Returns an empty vec if there are none.
213    #[expect(clippy::type_complexity)]
214    fn ommers(
215        &self,
216        block_id: BlockId,
217    ) -> impl Future<Output = Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
218    {
219        async move {
220            if let Some(block) = self.recovered_block(block_id).await? {
221                Ok(block.body().ommers().map(|o| o.to_vec()))
222            } else {
223                Ok(None)
224            }
225        }
226    }
227
228    /// Returns uncle block at given index in given block.
229    ///
230    /// Returns `None` if index out of range.
231    fn ommer_by_block_and_index(
232        &self,
233        block_id: BlockId,
234        index: Index,
235    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
236    {
237        async move {
238            let uncles = if block_id.is_pending() {
239                // Pending block can be fetched directly without need for caching
240                self.provider()
241                    .pending_block()
242                    .map_err(Self::Error::from_eth_err)?
243                    .and_then(|block| block.body().ommers().map(|o| o.to_vec()))
244            } else {
245                self.recovered_block(block_id)
246                    .await?
247                    .map(|block| block.body().ommers().map(|o| o.to_vec()).unwrap_or_default())
248            }
249            .unwrap_or_default();
250
251            uncles
252                .into_iter()
253                .nth(index.into())
254                .map(|header| {
255                    let block =
256                        alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
257                    let size = block.length();
258                    let header = self
259                        .tx_resp_builder()
260                        .convert_header(SealedHeader::new_unhashed(block.header), size)?;
261                    Ok(Block {
262                        uncles: vec![],
263                        header,
264                        transactions: BlockTransactions::Uncle,
265                        withdrawals: None,
266                    })
267                })
268                .transpose()
269        }
270    }
271}
272
273/// Loads a block from database.
274///
275/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
276pub trait LoadBlock: LoadPendingBlock + SpawnBlocking + RpcNodeCoreExt {
277    /// Returns the block object for the given block id.
278    #[expect(clippy::type_complexity)]
279    fn recovered_block(
280        &self,
281        block_id: BlockId,
282    ) -> impl Future<
283        Output = Result<
284            Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
285            Self::Error,
286        >,
287    > + Send {
288        async move {
289            if block_id.is_pending() {
290                // Pending block can be fetched directly without need for caching
291                if let Some(pending_block) =
292                    self.provider().pending_block().map_err(Self::Error::from_eth_err)?
293                {
294                    return Ok(Some(Arc::new(pending_block)));
295                }
296
297                // If no pending block from provider, try to get local pending block
298                return match self.local_pending_block().await? {
299                    Some((block, _)) => Ok(Some(block)),
300                    None => Ok(None),
301                };
302            }
303
304            let block_hash = match self
305                .provider()
306                .block_hash_for_id(block_id)
307                .map_err(Self::Error::from_eth_err)?
308            {
309                Some(block_hash) => block_hash,
310                None => return Ok(None),
311            };
312
313            self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
314        }
315    }
316}