reth_snap_sync/
session.rs1use crate::{SnapGeneration, SnapPivotPolicy, SnapSyncError};
4use reth_storage_api::HeaderProvider;
5use tokio_util::sync::CancellationToken;
6
7#[derive(Debug)]
12pub struct SnapSyncSession {
13 policy: SnapPivotPolicy,
15 state: SnapSyncSessionState,
17 cancellation: CancellationToken,
19}
20
21impl SnapSyncSession {
22 pub fn new(policy: SnapPivotPolicy) -> Self {
24 Self {
25 policy,
26 state: SnapSyncSessionState::Waiting,
27 cancellation: CancellationToken::new(),
28 }
29 }
30
31 pub const fn state(&self) -> &SnapSyncSessionState {
33 &self.state
34 }
35
36 pub const fn target(&self) -> Option<&SnapGeneration> {
38 self.state.target()
39 }
40
41 pub fn is_cancelled(&self) -> bool {
43 self.cancellation.is_cancelled()
44 }
45
46 pub fn select(
52 &mut self,
53 provider: &impl HeaderProvider,
54 head: u64,
55 finalized: Option<u64>,
56 ) -> Result<&SnapSyncSessionState, SnapSyncError> {
57 if matches!(self.state, SnapSyncSessionState::Waiting | SnapSyncSessionState::Selected(_)) {
58 self.state = match self.policy.select(provider, head, finalized)? {
59 Some(generation) => SnapSyncSessionState::Selected(generation),
60 None => SnapSyncSessionState::Waiting,
61 };
62 }
63 Ok(&self.state)
64 }
65
66 pub fn start(&mut self) -> Option<(SnapGeneration, CancellationToken)> {
71 let SnapSyncSessionState::Selected(generation) = self.state else { return None };
72 self.state = SnapSyncSessionState::Downloading(generation);
73 Some((generation, self.cancellation.clone()))
74 }
75
76 pub fn cancel(&mut self) {
80 self.cancellation.cancel();
81 self.state = SnapSyncSessionState::Cancelled;
82 }
83}
84
85#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub enum SnapSyncSessionState {
88 Waiting,
90 Selected(SnapGeneration),
92 Downloading(SnapGeneration),
94 Cancelled,
96}
97
98impl SnapSyncSessionState {
99 pub const fn target(&self) -> Option<&SnapGeneration> {
101 match self {
102 Self::Selected(generation) | Self::Downloading(generation) => Some(generation),
103 Self::Waiting | Self::Cancelled => None,
104 }
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::test_utils::{chain, policy, provider_with};
112
113 fn session() -> SnapSyncSession {
114 SnapSyncSession::new(policy())
115 }
116
117 #[test]
118 fn waits_while_no_block_is_eligible() {
119 let provider = provider_with(chain(Some(3)));
121 let mut session = session();
122
123 assert_eq!(session.select(&provider, 3, None).unwrap(), &SnapSyncSessionState::Waiting);
124 assert_eq!(session.target(), None);
125 assert!(session.start().is_none());
126 }
127
128 #[test]
129 fn selects_an_eligible_target() {
130 let headers = chain(Some(0));
131 let expected = headers[2].clone();
132 let provider = provider_with(headers);
133 let mut session = session();
134
135 session.select(&provider, 3, None).unwrap();
136
137 let target = session.target().unwrap();
138 assert_eq!(target.target().number, 2);
139 assert_eq!(target.target().hash, expected.hash_slow());
140 }
141
142 #[test]
143 fn a_target_no_work_took_is_replaced_as_the_head_advances() {
144 let provider = provider_with(chain(Some(0)));
145 let mut session = session();
146
147 session.select(&provider, 2, None).unwrap();
148 assert_eq!(session.target().unwrap().target().number, 1);
149
150 session.select(&provider, 3, None).unwrap();
151 assert_eq!(session.target().unwrap().target().number, 2);
152 }
153
154 #[test]
155 fn a_target_no_work_took_is_dropped_once_it_is_no_longer_eligible() {
156 let provider = provider_with(chain(Some(0)));
157 let mut session = session();
158 session.select(&provider, 3, None).unwrap();
159
160 session.select(&provider, 9, None).unwrap();
162
163 assert_eq!(session.state(), &SnapSyncSessionState::Waiting);
164 assert!(session.start().is_none());
165 }
166
167 #[test]
168 fn a_target_work_took_is_kept() {
169 let provider = provider_with(chain(Some(0)));
170 let mut session = session();
171 session.select(&provider, 2, None).unwrap();
172 let (started, _) = session.start().unwrap();
173
174 session.select(&provider, 3, None).unwrap();
175
176 assert_eq!(session.state(), &SnapSyncSessionState::Downloading(started));
177 }
178
179 #[test]
180 fn only_one_worker_takes_a_target() {
181 let provider = provider_with(chain(Some(0)));
182 let mut session = session();
183 session.select(&provider, 3, None).unwrap();
184
185 assert!(session.start().is_some());
186 assert!(session.start().is_none());
187 }
188
189 #[test]
190 fn cancellation_stops_outstanding_work() {
191 let provider = provider_with(chain(Some(0)));
192 let mut session = session();
193 session.select(&provider, 3, None).unwrap();
194 let (_, outstanding) = session.start().unwrap();
195
196 session.cancel();
197
198 assert!(outstanding.is_cancelled());
199 assert!(session.is_cancelled());
200 assert_eq!(session.state(), &SnapSyncSessionState::Cancelled);
201 assert_eq!(session.target(), None);
202 }
203
204 #[test]
205 fn a_cancelled_session_selects_nothing() {
206 let provider = provider_with(chain(Some(0)));
207 let mut session = session();
208 session.cancel();
209
210 assert_eq!(session.select(&provider, 3, None).unwrap(), &SnapSyncSessionState::Cancelled);
211 assert!(session.start().is_none());
212 }
213}