1use crate::{SnapAttemptStore, SnapGeneration, SnapPivotPolicy, SnapSyncError, SnapWrite};
4use reth_storage_api::{BlockHashReader, HeaderProvider, MetadataProvider, MetadataWriter};
5
6#[derive(Debug)]
11pub struct SnapSyncSession {
12 policy: SnapPivotPolicy,
14 state: SnapSyncSessionState,
16}
17
18impl SnapSyncSession {
19 pub const fn new(policy: SnapPivotPolicy) -> Self {
21 Self { policy, state: SnapSyncSessionState::Waiting }
22 }
23
24 pub const fn state(&self) -> &SnapSyncSessionState {
26 &self.state
27 }
28
29 pub const fn target(&self) -> Option<&SnapGeneration> {
31 self.state.target()
32 }
33
34 pub fn select(
40 &mut self,
41 provider: &impl HeaderProvider,
42 head: u64,
43 finalized: Option<u64>,
44 ) -> Result<&SnapSyncSessionState, SnapSyncError> {
45 if matches!(self.state, SnapSyncSessionState::Waiting | SnapSyncSessionState::Selected(_)) {
46 self.state = match self.policy.select(provider, head, finalized)? {
47 Some(generation) => SnapSyncSessionState::Selected(generation),
48 None => SnapSyncSessionState::Waiting,
49 };
50 }
51 Ok(&self.state)
52 }
53
54 pub const fn start(&mut self) -> Option<SnapGeneration> {
59 let SnapSyncSessionState::Selected(generation) = self.state else { return None };
60 self.state = SnapSyncSessionState::Downloading(generation);
61 Some(generation)
62 }
63
64 pub const fn resume(&mut self, generation: SnapGeneration) -> bool {
70 if !matches!(self.state, SnapSyncSessionState::Waiting | SnapSyncSessionState::Selected(_))
71 {
72 return false
73 }
74 self.state = SnapSyncSessionState::Downloading(generation);
75 true
76 }
77
78 pub fn advance<P>(
84 &mut self,
85 provider: &P,
86 write: SnapWrite,
87 head: u64,
88 finalized: Option<u64>,
89 ) -> Result<Option<SnapWrite>, SnapSyncError>
90 where
91 P: HeaderProvider + BlockHashReader + MetadataProvider + MetadataWriter,
92 {
93 let SnapSyncSessionState::Downloading(mut current) = self.state else { return Ok(None) };
94 let attempt = provider.authorize_snap_write(write)?;
95 if attempt.pivot() != current.target() {
96 current = SnapGeneration::new(attempt.pivot(), attempt.state_root());
97 self.state = SnapSyncSessionState::Downloading(current);
98 }
99 if !self.policy.needs_advance(current, head) {
100 return Ok(None)
101 }
102 let Some(next) = self.policy.select(provider, head, finalized)? else { return Ok(None) };
103 if next.target().number <= current.target().number {
105 return Ok(None)
106 }
107 let write = provider.advance_snap_pivot(write, next)?;
108 self.state = SnapSyncSessionState::Downloading(next);
109 Ok(Some(write))
110 }
111}
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum SnapSyncSessionState {
116 Waiting,
118 Selected(SnapGeneration),
120 Downloading(SnapGeneration),
122}
123
124impl SnapSyncSessionState {
125 pub const fn target(&self) -> Option<&SnapGeneration> {
127 match self {
128 Self::Selected(generation) | Self::Downloading(generation) => Some(generation),
129 Self::Waiting => None,
130 }
131 }
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137 use crate::test_utils::{chain, hashed_factory, policy, provider_with};
138 use reth_primitives_traits::SealedHeader;
139 use reth_provider::{
140 test_utils::{insert_headers, MockNodeTypesWithDB},
141 DBProvider, DatabaseProviderFactory, ProviderFactory,
142 };
143
144 fn session() -> SnapSyncSession {
145 SnapSyncSession::new(policy())
146 }
147
148 fn downloading() -> (ProviderFactory<MockNodeTypesWithDB>, SnapSyncSession, SnapWrite) {
151 let factory = hashed_factory();
152 let headers: Vec<_> = chain(Some(0)).into_iter().map(SealedHeader::seal_slow).collect();
153 insert_headers(&factory, &headers);
154 let provider = factory.database_provider_rw().unwrap();
155 let mut session = SnapSyncSession::new(policy().with_advance_after(1));
156 session.select(&provider, 2, None).unwrap();
157 let generation = session.start().unwrap();
158 let write = provider.start_snap_attempt(generation).unwrap();
159 provider.commit().unwrap();
160 (factory, session, write)
161 }
162
163 #[test]
164 fn a_target_no_work_took_is_replaced_as_the_head_advances() {
165 let provider = provider_with(chain(Some(0)));
166 let mut session = session();
167
168 session.select(&provider, 2, None).unwrap();
169 assert_eq!(session.target().unwrap().target().number, 1);
170
171 session.select(&provider, 3, None).unwrap();
172 assert_eq!(session.target().unwrap().target().number, 2);
173 }
174
175 #[test]
176 fn a_target_no_work_took_is_dropped_once_it_is_no_longer_eligible() {
177 let provider = provider_with(chain(Some(0)));
178 let mut session = session();
179 session.select(&provider, 3, None).unwrap();
180
181 session.select(&provider, 9, None).unwrap();
183
184 assert_eq!(session.state(), &SnapSyncSessionState::Waiting);
185 assert!(session.start().is_none());
186 }
187
188 #[test]
189 fn a_target_work_took_is_kept() {
190 let provider = provider_with(chain(Some(0)));
191 let mut session = session();
192 session.select(&provider, 2, None).unwrap();
193 let started = session.start().unwrap();
194
195 session.select(&provider, 3, None).unwrap();
196
197 assert_eq!(session.state(), &SnapSyncSessionState::Downloading(started));
198 }
199
200 #[test]
201 fn a_recorded_target_is_resumed_once() {
202 let (factory, _, write) = downloading();
203 let provider = factory.database_provider_rw().unwrap();
204 let attempt = provider.authorize_snap_write(write).unwrap();
205 let recorded = SnapGeneration::new(attempt.pivot(), attempt.state_root());
206 let mut session = SnapSyncSession::new(policy().with_advance_after(1));
207
208 assert!(session.resume(recorded));
209 assert!(!session.resume(recorded));
210 assert!(session.start().is_none());
211 assert_eq!(session.state(), &SnapSyncSessionState::Downloading(recorded));
212 assert!(session.advance(&provider, write, 3, None).unwrap().is_some());
214 assert_eq!(session.target().unwrap().target().number, 2);
215 }
216
217 #[test]
218 fn a_lagging_target_advances_with_the_attempt() {
219 let (factory, mut session, write) = downloading();
220 let provider = factory.database_provider_rw().unwrap();
221
222 let advanced = session.advance(&provider, write, 3, None).unwrap().unwrap();
223
224 let target = *session.target().unwrap();
225 assert_eq!(target.target().number, 2);
226 assert!(matches!(session.state(), SnapSyncSessionState::Downloading(_)));
227 let attempt = provider.authorize_snap_write(advanced).unwrap();
228 assert_eq!((attempt.pivot(), attempt.state_root()), (target.target(), target.state_root()));
229 assert!(matches!(
230 provider.authorize_snap_write(write),
231 Err(SnapSyncError::StaleWrite { .. })
232 ));
233 }
234
235 #[test]
236 fn a_target_within_the_window_is_kept() {
237 let (factory, mut session, write) = downloading();
238 let provider = factory.database_provider_rw().unwrap();
239 let before = *session.target().unwrap();
240
241 assert_eq!(session.advance(&provider, write, 2, None).unwrap(), None);
242
243 assert_eq!(session.target(), Some(&before));
244 provider.authorize_snap_write(write).unwrap();
245 }
246
247 #[test]
248 fn a_refused_advance_keeps_the_target() {
249 let (factory, mut session, write) = downloading();
250 let provider = factory.database_provider_rw().unwrap();
251 let before = *session.target().unwrap();
252 provider.abandon_snap_attempt().unwrap();
253
254 let refused = session.advance(&provider, write, 3, None);
255
256 assert!(matches!(refused, Err(SnapSyncError::StaleWrite { .. })));
257 assert_eq!(session.state(), &SnapSyncSessionState::Downloading(before));
258 }
259
260 #[test]
261 fn a_rolled_back_advance_is_retried() {
262 let (factory, mut session, write) = downloading();
263 let provider = factory.database_provider_rw().unwrap();
264 session.advance(&provider, write, 3, None).unwrap().unwrap();
265 drop(provider);
266
267 let provider = factory.database_provider_rw().unwrap();
268 let advanced = session.advance(&provider, write, 3, None).unwrap().unwrap();
269
270 assert_eq!(session.target().unwrap().target().number, 2);
271 assert_eq!(provider.authorize_snap_write(advanced).unwrap().pivot().number, 2);
272 }
273
274 #[test]
275 fn a_target_no_work_took_is_not_advanced() {
276 let (factory, _, write) = downloading();
277 let provider = factory.database_provider_rw().unwrap();
278 let mut session = SnapSyncSession::new(policy().with_advance_after(1));
279 session.select(&provider, 2, None).unwrap();
280
281 assert_eq!(session.advance(&provider, write, 3, None).unwrap(), None);
282 assert_eq!(session.target().unwrap().target().number, 1);
283 }
284}