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