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::{PruneCheckpoint, 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 checkpoints = self.provider().get_prune_checkpoints_for_availability()?;
57
58 let state = effective_resource(
59 &checkpoints,
60 &[PruneSegment::AccountHistory, PruneSegment::StorageHistory],
61 );
62 let tx = effective_resource(
63 &checkpoints,
64 &[PruneSegment::TransactionLookup, PruneSegment::Bodies],
65 );
66 let logs =
67 effective_resource(&checkpoints, &[PruneSegment::Receipts, PruneSegment::ContractLogs]);
68 let receipts = effective_resource(
69 &checkpoints,
70 &[PruneSegment::Receipts, PruneSegment::TransactionLookup, PruneSegment::Bodies],
71 );
72 let blocks = effective_resource(&checkpoints, &[PruneSegment::Bodies]);
73
74 let proof_window = self.max_proof_window();
75 let proof_oldest = chain_info
76 .best_number
77 .saturating_sub(proof_window)
78 .max(state.oldest_block.map(|number| number.to::<u64>()).unwrap_or_default());
79 let state_proofs = EthCapabilitiesResource::window(proof_oldest, proof_window);
80
81 Ok(EthCapabilities {
82 head: EthCapabilitiesHead {
83 number: chain_info.best_number,
84 hash: chain_info.best_hash,
85 },
86 state,
87 tx,
88 logs,
89 receipts,
90 blocks,
91 state_proofs,
92 })
93 }
94
95 fn is_syncing(&self) -> bool {
97 self.network().is_syncing()
98 }
99
100 fn sync_status(&self) -> RethResult<SyncStatus> {
102 let status = if self.is_syncing() {
103 let current_block = U256::from(
104 self.provider().chain_info().map(|info| info.best_number).unwrap_or_default(),
105 );
106
107 let stages = self
108 .provider()
109 .get_all_checkpoints()
110 .unwrap_or_default()
111 .into_iter()
112 .map(|(name, checkpoint)| Stage { name, block: checkpoint.block_number })
113 .collect();
114
115 SyncStatus::Info(Box::new(SyncInfo {
116 starting_block: self.starting_block(),
117 current_block,
118 highest_block: current_block,
119 warp_chunks_amount: None,
120 warp_chunks_processed: None,
121 stages: Some(stages),
122 }))
123 } else {
124 SyncStatus::None
125 };
126 Ok(status)
127 }
128}
129
130fn effective_resource(
141 checkpoints: &[(PruneSegment, PruneCheckpoint)],
142 segments: &[PruneSegment],
143) -> EthCapabilitiesResource {
144 let mut oldest_block = 0;
145 let mut retention_blocks = None::<u64>;
146 let mut disabled = false;
147
148 for segment in segments {
149 let Some((_, checkpoint)) = checkpoints.iter().find(|(key, _)| key == segment) else {
150 continue;
151 };
152
153 if let Some(block_number) = checkpoint.block_number {
154 oldest_block = oldest_block.max(block_number.saturating_add(1));
155 }
156
157 match checkpoint.prune_mode {
158 PruneMode::Distance(distance) => {
159 retention_blocks =
160 Some(retention_blocks.map_or(distance, |current| current.min(distance)));
161 }
162 PruneMode::Full => {
163 let min_blocks = segment.min_blocks();
164 if min_blocks == 0 {
165 disabled = true;
166 } else {
167 retention_blocks = Some(
168 retention_blocks.map_or(min_blocks, |current| current.min(min_blocks)),
169 );
170 }
171 }
172 PruneMode::Before(_) => {}
173 }
174 }
175
176 if disabled {
177 EthCapabilitiesResource::disabled()
178 } else if let Some(retention_blocks) = retention_blocks {
179 EthCapabilitiesResource::window(oldest_block, retention_blocks)
180 } else {
181 EthCapabilitiesResource::available_from(oldest_block)
182 }
183}
184
185pub type SignersForRpc<Provider, Rpc> = parking_lot::RwLock<
188 Vec<Box<dyn EthSigner<<Provider as TransactionsProvider>::Transaction, RpcTxReq<Rpc>>>>,
189>;
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn full_prune_segment_without_retention_disables_resource() {
197 let checkpoints = [(
198 PruneSegment::TransactionLookup,
199 PruneCheckpoint {
200 block_number: Some(10),
201 tx_number: None,
202 prune_mode: PruneMode::Full,
203 },
204 )];
205
206 let resource = effective_resource(&checkpoints, &[PruneSegment::TransactionLookup]);
207
208 assert_eq!(resource, EthCapabilitiesResource::disabled());
209 }
210
211 #[test]
212 fn full_prune_segment_with_minimum_retention_uses_window() {
213 let checkpoints = [(
214 PruneSegment::Bodies,
215 PruneCheckpoint {
216 block_number: Some(10),
217 tx_number: None,
218 prune_mode: PruneMode::Full,
219 },
220 )];
221
222 let resource = effective_resource(&checkpoints, &[PruneSegment::Bodies]);
223
224 assert_eq!(
225 resource,
226 EthCapabilitiesResource::window(11, PruneSegment::Bodies.min_blocks())
227 );
228 }
229
230 #[test]
231 fn retained_receipts_preserve_resource_limits() {
232 let checkpoints = [
233 (
234 PruneSegment::Receipts,
235 PruneCheckpoint {
236 block_number: None,
237 tx_number: None,
238 prune_mode: PruneMode::Distance(100),
239 },
240 ),
241 (
242 PruneSegment::ContractLogs,
243 PruneCheckpoint {
244 prune_mode: PruneMode::Distance(50),
245 ..PruneCheckpoint::pruned_through(19)
246 },
247 ),
248 (PruneSegment::TransactionLookup, PruneCheckpoint::pruned_through(39)),
249 (PruneSegment::Bodies, PruneCheckpoint::pruned_through(29)),
250 ];
251 for (segments, expected) in [
252 (vec![PruneSegment::Receipts], EthCapabilitiesResource::window(0, 100)),
253 (
254 vec![PruneSegment::Receipts, PruneSegment::ContractLogs],
255 EthCapabilitiesResource::window(20, 50),
256 ),
257 (
258 vec![PruneSegment::Receipts, PruneSegment::TransactionLookup, PruneSegment::Bodies],
259 EthCapabilitiesResource::window(40, 100),
260 ),
261 (vec![PruneSegment::Bodies], EthCapabilitiesResource::available_from(30)),
262 (vec![PruneSegment::AccountHistory], EthCapabilitiesResource::available_from(0)),
263 ] {
264 assert_eq!(effective_resource(&checkpoints, &segments), expected, "{segments:?}");
265 }
266 }
267}