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