reth_rpc_eth_api/helpers/
spec.rs1use 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#[auto_impl::auto_impl(&, Arc)]
25pub trait EthApiSpec: RpcNodeCore + EthApiTypes {
26 fn starting_block(&self) -> U256;
28
29 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 fn chain_id(&self) -> U64 {
39 U64::from(self.network().chain_id())
40 }
41
42 fn chain_info(&self) -> RethResult<ChainInfo> {
44 Ok(self.provider().chain_info()?)
45 }
46
47 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 fn is_syncing(&self) -> bool {
95 self.network().is_syncing()
96 }
97
98 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
128fn 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
183pub 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}