Skip to main content

reth_rpc_eth_api/helpers/
spec.rs

1//! Loads chain metadata.
2
3use alloy_primitives::{U256, U64};
4use alloy_rpc_types_eth::{Stage, SyncInfo, SyncStatus};
5use futures::Future;
6use reth_chainspec::ChainInfo;
7use reth_errors::{RethError, RethResult};
8use reth_network_api::NetworkInfo;
9use reth_prune_types::{PruneMode, PruneSegment};
10use reth_rpc_convert::RpcTxReq;
11use reth_rpc_eth_types::{EthCapabilities, EthCapabilitiesHead, EthCapabilitiesResource};
12use reth_storage_api::{
13    BlockNumReader, PruneCheckpointReader, StageCheckpointReader, TransactionsProvider,
14};
15
16use crate::{
17    helpers::{EthSigner, EthState},
18    EthApiTypes, RpcNodeCore,
19};
20
21/// `Eth` API trait.
22///
23/// Defines core functionality of the `eth` API implementation.
24#[auto_impl::auto_impl(&, Arc)]
25pub trait EthApiSpec: RpcNodeCore + EthApiTypes {
26    /// Returns the block node is started on.
27    fn starting_block(&self) -> U256;
28
29    /// Returns the current ethereum protocol version.
30    fn protocol_version(&self) -> impl Future<Output = RethResult<U64>> + Send {
31        async move {
32            let status = self.network().network_status().await.map_err(RethError::other)?;
33            Ok(U64::from(status.protocol_version))
34        }
35    }
36
37    /// Returns the chain id
38    fn chain_id(&self) -> U64 {
39        U64::from(self.network().chain_id())
40    }
41
42    /// Returns provider chain info
43    fn chain_info(&self) -> RethResult<ChainInfo> {
44        Ok(self.provider().chain_info()?)
45    }
46
47    /// Returns effective routing capabilities for this node.
48    ///
49    /// The response follows the `eth_capabilities` execution API proposal:
50    /// <https://github.com/ethereum/execution-apis/pull/755>.
51    fn capabilities(&self) -> RethResult<EthCapabilities>
52    where
53        Self: EthState,
54    {
55        let chain_info = self.chain_info()?;
56        let provider = self.provider();
57
58        let state = effective_resource(
59            provider,
60            &[PruneSegment::AccountHistory, PruneSegment::StorageHistory],
61        )?;
62        let tx =
63            effective_resource(provider, &[PruneSegment::TransactionLookup, PruneSegment::Bodies])?;
64        let logs =
65            effective_resource(provider, &[PruneSegment::Receipts, PruneSegment::ContractLogs])?;
66        let receipts = effective_resource(
67            provider,
68            &[PruneSegment::Receipts, PruneSegment::TransactionLookup, PruneSegment::Bodies],
69        )?;
70        let blocks = effective_resource(provider, &[PruneSegment::Bodies])?;
71
72        let proof_window = self.max_proof_window();
73        let proof_oldest = chain_info
74            .best_number
75            .saturating_sub(proof_window)
76            .max(state.oldest_block.map(|number| number.to::<u64>()).unwrap_or_default());
77        let state_proofs = EthCapabilitiesResource::window(proof_oldest, proof_window);
78
79        Ok(EthCapabilities {
80            head: EthCapabilitiesHead {
81                number: chain_info.best_number,
82                hash: chain_info.best_hash,
83            },
84            state,
85            tx,
86            logs,
87            receipts,
88            blocks,
89            state_proofs,
90        })
91    }
92
93    /// Returns `true` if the network is undergoing sync.
94    fn is_syncing(&self) -> bool {
95        self.network().is_syncing()
96    }
97
98    /// Returns the [`SyncStatus`] of the network
99    fn sync_status(&self) -> RethResult<SyncStatus> {
100        let status = if self.is_syncing() {
101            let current_block = U256::from(
102                self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(),
103            );
104
105            let stages = self
106                .provider()
107                .get_all_checkpoints()
108                .unwrap_or_default()
109                .into_iter()
110                .map(|(name, checkpoint)| Stage { name, block: checkpoint.block_number })
111                .collect();
112
113            SyncStatus::Info(Box::new(SyncInfo {
114                starting_block: self.starting_block(),
115                current_block,
116                highest_block: current_block,
117                warp_chunks_amount: None,
118                warp_chunks_processed: None,
119                stages: Some(stages),
120            }))
121        } else {
122            SyncStatus::None
123        };
124        Ok(status)
125    }
126}
127
128/// Derives the effective block availability for a capability resource from the prune checkpoints of
129/// all storage segments needed to serve it.
130///
131/// A resource is only available where every required segment is available, so the oldest available
132/// block is the maximum pruned checkpoint across the segments. If any segment is configured with a
133/// distance-based prune mode, or with full pruning that still keeps the segment minimum, expose the
134/// smallest retention window because that is the tightest limit callers can rely on. If full
135/// pruning leaves no reliable window for any required segment, the resource is disabled.
136///
137/// See also: <https://github.com/ethereum/execution-apis/pull/755>
138fn effective_resource(
139    provider: &impl PruneCheckpointReader,
140    segments: &[PruneSegment],
141) -> RethResult<EthCapabilitiesResource> {
142    let mut oldest_block = 0;
143    let mut retention_blocks = None::<u64>;
144    let mut disabled = false;
145
146    for segment in segments {
147        let Some(checkpoint) = provider.get_prune_checkpoint(*segment)? else {
148            continue;
149        };
150
151        if let Some(block_number) = checkpoint.block_number {
152            oldest_block = oldest_block.max(block_number.saturating_add(1));
153        }
154
155        match checkpoint.prune_mode {
156            PruneMode::Distance(distance) => {
157                retention_blocks =
158                    Some(retention_blocks.map_or(distance, |current| current.min(distance)));
159            }
160            PruneMode::Full => {
161                let min_blocks = segment.min_blocks();
162                if min_blocks == 0 {
163                    disabled = true;
164                } else {
165                    retention_blocks = Some(
166                        retention_blocks.map_or(min_blocks, |current| current.min(min_blocks)),
167                    );
168                }
169            }
170            PruneMode::Before(_) => {}
171        }
172    }
173
174    Ok(if disabled {
175        EthCapabilitiesResource::disabled()
176    } else if let Some(retention_blocks) = retention_blocks {
177        EthCapabilitiesResource::window(oldest_block, retention_blocks)
178    } else {
179        EthCapabilitiesResource::available_from(oldest_block)
180    })
181}
182
183/// A handle to [`EthSigner`]s with its generics set from [`TransactionsProvider`] and
184/// [`reth_rpc_convert::RpcTypes`].
185pub type SignersForRpc<Provider, Rpc> = parking_lot::RwLock<
186    Vec<Box<dyn EthSigner<<Provider as TransactionsProvider>::Transaction, RpcTxReq<Rpc>>>>,
187>;
188
189#[cfg(test)]
190mod tests {
191    use super::*;
192    use reth_prune_types::PruneCheckpoint;
193    use reth_storage_api::errors::provider::ProviderResult;
194    use std::collections::HashMap;
195
196    #[derive(Default)]
197    struct TestPruneCheckpointReader {
198        checkpoints: HashMap<PruneSegment, PruneCheckpoint>,
199    }
200
201    impl TestPruneCheckpointReader {
202        fn with_checkpoint(segment: PruneSegment, checkpoint: PruneCheckpoint) -> Self {
203            Self { checkpoints: HashMap::from([(segment, checkpoint)]) }
204        }
205    }
206
207    impl PruneCheckpointReader for TestPruneCheckpointReader {
208        fn get_prune_checkpoint(
209            &self,
210            segment: PruneSegment,
211        ) -> ProviderResult<Option<PruneCheckpoint>> {
212            Ok(self.checkpoints.get(&segment).copied())
213        }
214
215        fn get_prune_checkpoints(&self) -> ProviderResult<Vec<(PruneSegment, PruneCheckpoint)>> {
216            Ok(self
217                .checkpoints
218                .iter()
219                .map(|(segment, checkpoint)| (*segment, *checkpoint))
220                .collect())
221        }
222    }
223
224    #[test]
225    fn full_prune_segment_without_retention_disables_resource() {
226        let provider = TestPruneCheckpointReader::with_checkpoint(
227            PruneSegment::TransactionLookup,
228            PruneCheckpoint {
229                block_number: Some(10),
230                tx_number: None,
231                prune_mode: PruneMode::Full,
232            },
233        );
234
235        let resource = effective_resource(&provider, &[PruneSegment::TransactionLookup]).unwrap();
236
237        assert_eq!(resource, EthCapabilitiesResource::disabled());
238    }
239
240    #[test]
241    fn full_prune_segment_with_minimum_retention_uses_window() {
242        let provider = TestPruneCheckpointReader::with_checkpoint(
243            PruneSegment::Bodies,
244            PruneCheckpoint {
245                block_number: Some(10),
246                tx_number: None,
247                prune_mode: PruneMode::Full,
248            },
249        );
250
251        let resource = effective_resource(&provider, &[PruneSegment::Bodies]).unwrap();
252
253        assert_eq!(
254            resource,
255            EthCapabilitiesResource::window(11, PruneSegment::Bodies.min_blocks())
256        );
257    }
258}