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_eips::BlockId;
9use alloy_primitives::{Sealable, U256};
10use alloy_rlp::Encodable;
11use alloy_rpc_types_eth::{Block, BlockTransactions, Header, Index};
12use futures::Future;
13use reth_node_api::BlockBody;
14use reth_primitives_traits::{RecoveredBlock, SealedBlock};
15use reth_rpc_types_compat::block::from_block;
16use reth_storage_api::{
17    BlockIdReader, BlockReader, BlockReaderIdExt, ProviderHeader, ProviderReceipt, ProviderTx,
18};
19use reth_transaction_pool::{PoolTransaction, TransactionPool};
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        SealedBlock<<<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 {
36    /// Returns the block header for the given block id.
37    #[expect(clippy::type_complexity)]
38    fn rpc_block_header(
39        &self,
40        block_id: BlockId,
41    ) -> impl Future<Output = Result<Option<Header<ProviderHeader<Self::Provider>>>, Self::Error>> + Send
42    where
43        Self: FullEthApiTypes,
44    {
45        async move { Ok(self.rpc_block(block_id, false).await?.map(|block| block.header)) }
46    }
47
48    /// Returns the populated rpc block object for the given block id.
49    ///
50    /// If `full` is true, the block object will contain all transaction objects, otherwise it will
51    /// only contain the transaction hashes.
52    fn rpc_block(
53        &self,
54        block_id: BlockId,
55        full: bool,
56    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
57    where
58        Self: FullEthApiTypes,
59    {
60        async move {
61            let Some(block) = self.recovered_block(block_id).await? else { return Ok(None) };
62
63            let block = from_block((*block).clone(), full.into(), self.tx_resp_builder())?;
64            Ok(Some(block))
65        }
66    }
67
68    /// Returns the number transactions in the given block.
69    ///
70    /// Returns `None` if the block does not exist
71    fn block_transaction_count(
72        &self,
73        block_id: BlockId,
74    ) -> impl Future<Output = Result<Option<usize>, Self::Error>> + Send {
75        async move {
76            if block_id.is_pending() {
77                // Pending block can be fetched directly without need for caching
78                return Ok(self
79                    .provider()
80                    .pending_block()
81                    .map_err(Self::Error::from_eth_err)?
82                    .map(|block| block.body().transactions().len()))
83            }
84
85            let block_hash = match self
86                .provider()
87                .block_hash_for_id(block_id)
88                .map_err(Self::Error::from_eth_err)?
89            {
90                Some(block_hash) => block_hash,
91                None => return Ok(None),
92            };
93
94            Ok(self
95                .cache()
96                .get_recovered_block(block_hash)
97                .await
98                .map_err(Self::Error::from_eth_err)?
99                .map(|b| b.body().transaction_count()))
100        }
101    }
102
103    /// Helper function for `eth_getBlockReceipts`.
104    ///
105    /// Returns all transaction receipts in block, or `None` if block wasn't found.
106    fn block_receipts(
107        &self,
108        block_id: BlockId,
109    ) -> impl Future<Output = BlockReceiptsResult<Self::NetworkTypes, Self::Error>> + Send
110    where
111        Self: LoadReceipt;
112
113    /// Helper method that loads a block and all its receipts.
114    fn load_block_and_receipts(
115        &self,
116        block_id: BlockId,
117    ) -> impl Future<Output = BlockAndReceiptsResult<Self>> + Send
118    where
119        Self: LoadReceipt,
120        Self::Pool:
121            TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
122    {
123        async move {
124            if block_id.is_pending() {
125                // First, try to get the pending block from the provider, in case we already
126                // received the actual pending block from the CL.
127                if let Some((block, receipts)) = self
128                    .provider()
129                    .pending_block_and_receipts()
130                    .map_err(Self::Error::from_eth_err)?
131                {
132                    return Ok(Some((block, Arc::new(receipts))));
133                }
134
135                // If no pending block from provider, build the pending block locally.
136                if let Some((block, receipts)) = self.local_pending_block().await? {
137                    return Ok(Some((block.into_sealed_block(), Arc::new(receipts))));
138                }
139            }
140
141            if let Some(block_hash) =
142                self.provider().block_hash_for_id(block_id).map_err(Self::Error::from_eth_err)?
143            {
144                return self
145                    .cache()
146                    .get_block_and_receipts(block_hash)
147                    .await
148                    .map_err(Self::Error::from_eth_err)
149                    .map(|b| b.map(|(b, r)| (b.clone_sealed_block(), r)))
150            }
151
152            Ok(None)
153        }
154    }
155
156    /// Returns uncle headers of given block.
157    ///
158    /// Returns an empty vec if there are none.
159    #[expect(clippy::type_complexity)]
160    fn ommers(
161        &self,
162        block_id: BlockId,
163    ) -> Result<Option<Vec<ProviderHeader<Self::Provider>>>, Self::Error> {
164        self.provider().ommers_by_id(block_id).map_err(Self::Error::from_eth_err)
165    }
166
167    /// Returns uncle block at given index in given block.
168    ///
169    /// Returns `None` if index out of range.
170    fn ommer_by_block_and_index(
171        &self,
172        block_id: BlockId,
173        index: Index,
174    ) -> impl Future<Output = Result<Option<RpcBlock<Self::NetworkTypes>>, Self::Error>> + Send
175    {
176        async move {
177            let uncles = if block_id.is_pending() {
178                // Pending block can be fetched directly without need for caching
179                self.provider()
180                    .pending_block()
181                    .map_err(Self::Error::from_eth_err)?
182                    .and_then(|block| block.body().ommers().map(|o| o.to_vec()))
183            } else {
184                self.provider().ommers_by_id(block_id).map_err(Self::Error::from_eth_err)?
185            }
186            .unwrap_or_default();
187
188            Ok(uncles.into_iter().nth(index.into()).map(|header| {
189                let block = alloy_consensus::Block::<alloy_consensus::TxEnvelope, _>::uncle(header);
190                let size = U256::from(block.length());
191                Block {
192                    uncles: vec![],
193                    header: Header::from_consensus(block.header.seal_slow(), None, Some(size)),
194                    transactions: BlockTransactions::Uncle,
195                    withdrawals: None,
196                }
197            }))
198        }
199    }
200}
201
202/// Loads a block from database.
203///
204/// Behaviour shared by several `eth_` RPC methods, not exclusive to `eth_` blocks RPC methods.
205pub trait LoadBlock:
206    LoadPendingBlock
207    + SpawnBlocking
208    + RpcNodeCoreExt<
209        Pool: TransactionPool<Transaction: PoolTransaction<Consensus = ProviderTx<Self::Provider>>>,
210    >
211{
212    /// Returns the block object for the given block id.
213    #[expect(clippy::type_complexity)]
214    fn recovered_block(
215        &self,
216        block_id: BlockId,
217    ) -> impl Future<
218        Output = Result<
219            Option<Arc<RecoveredBlock<<Self::Provider as BlockReader>::Block>>>,
220            Self::Error,
221        >,
222    > + Send {
223        async move {
224            if block_id.is_pending() {
225                // Pending block can be fetched directly without need for caching
226                if let Some(pending_block) = self
227                    .provider()
228                    .pending_block_with_senders()
229                    .map_err(Self::Error::from_eth_err)?
230                {
231                    return Ok(Some(Arc::new(pending_block)));
232                }
233
234                // If no pending block from provider, try to get local pending block
235                return match self.local_pending_block().await? {
236                    Some((block, _)) => Ok(Some(Arc::new(block))),
237                    None => Ok(None),
238                };
239            }
240
241            let block_hash = match self
242                .provider()
243                .block_hash_for_id(block_id)
244                .map_err(Self::Error::from_eth_err)?
245            {
246                Some(block_hash) => block_hash,
247                None => return Ok(None),
248            };
249
250            self.cache().get_recovered_block(block_hash).await.map_err(Self::Error::from_eth_err)
251        }
252    }
253}