1use crate::{SnapGeneration, SnapSyncError};
8use reth_storage_api::{
9 MetadataProvider, MetadataWriter, SnapAttempt, SnapAttemptId, StorageSettings,
10};
11
12pub trait SnapAttemptStore {
17 fn start_snap_attempt(&self, generation: SnapGeneration) -> Result<SnapWrite, SnapSyncError>;
19
20 fn active_snap_write(&self) -> Result<Option<SnapWrite>, SnapSyncError>;
22
23 fn authorize_snap_write(&self, write: SnapWrite) -> Result<SnapAttempt, SnapSyncError>;
25
26 fn advance_snap_pivot(
28 &self,
29 write: SnapWrite,
30 generation: SnapGeneration,
31 ) -> Result<SnapWrite, SnapSyncError>;
32
33 fn verify_snap_attempt(&self, write: SnapWrite) -> Result<(), SnapSyncError>;
35
36 fn abandon_snap_attempt(&self) -> Result<(), SnapSyncError>;
38}
39
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
42pub struct SnapWrite {
43 attempt: SnapAttemptId,
45 state_version: u64,
47}
48
49impl SnapWrite {
50 const fn of(attempt: &SnapAttempt) -> Self {
52 Self { attempt: attempt.id(), state_version: attempt.state_version() }
53 }
54
55 pub const fn attempt(&self) -> SnapAttemptId {
57 self.attempt
58 }
59
60 pub const fn state_version(&self) -> u64 {
62 self.state_version
63 }
64}
65
66impl<T> SnapAttemptStore for T
67where
68 T: MetadataProvider + MetadataWriter,
69{
70 fn start_snap_attempt(&self, generation: SnapGeneration) -> Result<SnapWrite, SnapSyncError> {
71 if !self.storage_settings()?.unwrap_or_else(StorageSettings::v1).use_hashed_state() {
73 return Err(SnapSyncError::UnsupportedStorage)
74 }
75
76 let attempt =
77 SnapAttempt::start(self.snap_attempt()?, generation.target(), generation.state_root());
78 self.write_snap_attempt(&attempt)?;
79 Ok(SnapWrite::of(&attempt))
80 }
81
82 fn active_snap_write(&self) -> Result<Option<SnapWrite>, SnapSyncError> {
83 Ok(self.snap_attempt()?.filter(SnapAttempt::is_unfinished).as_ref().map(SnapWrite::of))
84 }
85
86 fn authorize_snap_write(&self, write: SnapWrite) -> Result<SnapAttempt, SnapSyncError> {
87 let attempt = self.snap_attempt()?.ok_or(SnapSyncError::NoAttempt)?;
88 if !attempt.is_unfinished() || SnapWrite::of(&attempt) != write {
89 return Err(SnapSyncError::StaleWrite {
90 attempt: write.attempt,
91 state_version: write.state_version,
92 })
93 }
94 Ok(attempt)
95 }
96
97 fn advance_snap_pivot(
98 &self,
99 write: SnapWrite,
100 generation: SnapGeneration,
101 ) -> Result<SnapWrite, SnapSyncError> {
102 let mut attempt = self.authorize_snap_write(write)?;
103 attempt.re_anchor(generation.target(), generation.state_root());
104 self.write_snap_attempt(&attempt)?;
105 Ok(SnapWrite::of(&attempt))
106 }
107
108 fn verify_snap_attempt(&self, write: SnapWrite) -> Result<(), SnapSyncError> {
109 let mut attempt = self.authorize_snap_write(write)?;
110 attempt.verify();
111 self.write_snap_attempt(&attempt)?;
112 Ok(())
113 }
114
115 fn abandon_snap_attempt(&self) -> Result<(), SnapSyncError> {
116 if let Some(mut attempt) = self.snap_attempt()? &&
117 attempt.is_unfinished()
118 {
119 attempt.abandon();
120 self.write_snap_attempt(&attempt)?;
121 }
122 Ok(())
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use alloy_eips::BlockNumHash;
130 use alloy_primitives::{Bytes, B256};
131 use reth_db_api::{tables, transaction::DbTx};
132 use reth_primitives_traits::Account;
133 use reth_provider::{
134 test_utils::{create_test_provider_factory, MockNodeTypesWithDB},
135 DBProvider, DatabaseProviderFactory, ProviderFactory,
136 };
137 use reth_storage_api::{metadata::keys, StateWriter};
138 use reth_trie_common::HashedPostState;
139 use revm::{bytecode::Bytecode, database::states::StateChangeset};
140
141 const HASHED_ADDRESS: B256 = B256::repeat_byte(0xbb);
142
143 fn code() -> Bytecode {
144 Bytecode::new_raw(Bytes::from_static(&[0x60, 0x00]))
145 }
146
147 fn generation(block: u64) -> SnapGeneration {
148 SnapGeneration::new(
149 BlockNumHash::new(block, B256::repeat_byte(block as u8)),
150 B256::repeat_byte(0xaa),
151 )
152 }
153
154 fn factory() -> ProviderFactory<MockNodeTypesWithDB> {
156 let factory = create_test_provider_factory();
157 let provider = factory.database_provider_rw().unwrap();
158 provider.write_storage_settings(StorageSettings::v2()).unwrap();
159 provider.commit().unwrap();
160 factory
161 }
162
163 fn download(provider: &impl StateWriter) {
165 let mut state = HashedPostState::default();
166 state.accounts.insert(HASHED_ADDRESS, Some(Account::default()));
167 provider.write_hashed_state(&state.into_sorted()).unwrap();
168 provider
169 .write_state_changes(StateChangeset {
170 contracts: vec![(code().hash_slow(), code())],
171 ..Default::default()
172 })
173 .unwrap();
174 }
175
176 fn downloaded(provider: &impl DBProvider) -> (bool, bool) {
177 let tx = provider.tx_ref();
178 (
179 tx.get::<tables::HashedAccounts>(HASHED_ADDRESS).unwrap().is_some(),
180 tx.get::<tables::Bytecodes>(code().hash_slow()).unwrap().is_some(),
181 )
182 }
183
184 #[test]
185 fn nothing_owns_the_state_before_an_attempt_starts() {
186 let factory = factory();
187 let provider = factory.database_provider_rw().unwrap();
188
189 assert_eq!(provider.active_snap_write().unwrap(), None);
190 let write = SnapWrite { attempt: SnapAttemptId::FIRST, state_version: 0 };
191 assert!(matches!(provider.authorize_snap_write(write), Err(SnapSyncError::NoAttempt)));
192 }
193
194 #[test]
195 fn address_keyed_state_cannot_host_an_attempt() {
196 let factory = create_test_provider_factory();
197 let provider = factory.database_provider_rw().unwrap();
198
199 assert!(matches!(
201 provider.start_snap_attempt(generation(1)),
202 Err(SnapSyncError::UnsupportedStorage)
203 ));
204 provider.write_storage_settings(StorageSettings::v1()).unwrap();
205 assert!(matches!(
206 provider.start_snap_attempt(generation(1)),
207 Err(SnapSyncError::UnsupportedStorage)
208 ));
209 assert_eq!(provider.snap_attempt().unwrap(), None);
210 }
211
212 #[test]
213 fn restarting_at_the_same_pivot_takes_a_new_identity() {
214 let factory = factory();
215 let provider = factory.database_provider_rw().unwrap();
216
217 let first = provider.start_snap_attempt(generation(1)).unwrap();
218 let second = provider.start_snap_attempt(generation(1)).unwrap();
219
220 assert_ne!(first.attempt(), second.attempt());
221 assert!(matches!(
223 provider.authorize_snap_write(first),
224 Err(SnapSyncError::StaleWrite { .. })
225 ));
226 provider.authorize_snap_write(second).unwrap();
227 }
228
229 #[test]
230 fn abandoning_an_attempt_keeps_its_identity_taken() {
231 let factory = factory();
232 let provider = factory.database_provider_rw().unwrap();
233
234 let abandoned = provider.start_snap_attempt(generation(1)).unwrap();
235 provider.abandon_snap_attempt().unwrap();
236 assert_eq!(provider.active_snap_write().unwrap(), None);
237 let started = provider.start_snap_attempt(generation(2)).unwrap();
238
239 assert_ne!(abandoned.attempt(), started.attempt());
240 assert!(matches!(
242 provider.authorize_snap_write(abandoned),
243 Err(SnapSyncError::StaleWrite { .. })
244 ));
245 }
246
247 #[test]
248 fn the_attempt_survives_reopening_the_database() {
249 let factory = factory();
250 let provider = factory.database_provider_rw().unwrap();
251 let write = provider.start_snap_attempt(generation(7)).unwrap();
252 provider.commit().unwrap();
253
254 let reopened = factory.database_provider_rw().unwrap();
255
256 assert_eq!(reopened.active_snap_write().unwrap(), Some(write));
257 let attempt = reopened.snap_attempt().unwrap().unwrap();
258 assert_eq!(attempt.pivot(), BlockNumHash::new(7, B256::repeat_byte(7)));
259 assert_eq!(attempt.state_root(), B256::repeat_byte(0xaa));
260 assert!(attempt.is_unfinished());
261 }
262
263 #[test]
264 fn committing_keeps_downloaded_state_and_progress_together() {
265 let factory = factory();
266 let provider = factory.database_provider_rw().unwrap();
267 let write = provider.start_snap_attempt(generation(1)).unwrap();
268 download(&provider);
269 provider.commit().unwrap();
270
271 let provider = factory.database_provider_rw().unwrap();
272 assert_eq!(provider.active_snap_write().unwrap(), Some(write));
273 assert_eq!(downloaded(&provider), (true, true));
274 }
275
276 #[test]
277 fn rolling_back_drops_downloaded_state_and_progress_together() {
278 let factory = factory();
279 let provider = factory.database_provider_rw().unwrap();
280 provider.start_snap_attempt(generation(1)).unwrap();
281 download(&provider);
282 drop(provider);
284
285 let provider = factory.database_provider_rw().unwrap();
286 assert_eq!(provider.active_snap_write().unwrap(), None);
287 assert_eq!(downloaded(&provider), (false, false));
288 }
289
290 #[test]
291 fn advancing_the_pivot_rejects_writes_proved_against_the_old_root() {
292 let factory = factory();
293 let provider = factory.database_provider_rw().unwrap();
294
295 let before = provider.start_snap_attempt(generation(1)).unwrap();
296 let after = provider.advance_snap_pivot(before, generation(2)).unwrap();
297
298 assert_eq!(after.attempt(), before.attempt());
299 assert_eq!(after.state_version(), before.state_version() + 1);
300 assert!(matches!(
301 provider.authorize_snap_write(before),
302 Err(SnapSyncError::StaleWrite { .. })
303 ));
304 provider.authorize_snap_write(after).unwrap();
305 }
306
307 #[test]
308 fn a_verified_attempt_accepts_no_further_writes() {
309 let factory = factory();
310 let provider = factory.database_provider_rw().unwrap();
311 let write = provider.start_snap_attempt(generation(1)).unwrap();
312
313 provider.verify_snap_attempt(write).unwrap();
314
315 assert!(!provider.snap_attempt().unwrap().unwrap().is_unfinished());
316 assert_eq!(provider.active_snap_write().unwrap(), None);
317 assert!(matches!(
318 provider.authorize_snap_write(write),
319 Err(SnapSyncError::StaleWrite { .. })
320 ));
321 provider.abandon_snap_attempt().unwrap();
323 assert!(provider.snap_attempt().unwrap().unwrap().is_verified());
324 }
325
326 #[test]
327 fn a_record_this_build_cannot_read_is_reported_rather_than_ignored() {
328 let factory = factory();
329
330 for record in [br#"{"version":999}"#.to_vec(), b"{}".to_vec(), b"not json".to_vec()] {
331 let provider = factory.database_provider_rw().unwrap();
332 provider.write_metadata(keys::SNAP_ATTEMPT, record).unwrap();
333
334 assert!(provider.snap_attempt().is_err());
335 assert!(provider.start_snap_attempt(generation(1)).is_err());
337 }
338 }
339}