reth_node_ethereum/snap/
context.rs1use alloy_primitives::B256;
4use reth_network_p2p::download::DownloadClient;
5use reth_provider::{
6 providers::ProviderNodeTypes, BlockNumReader, DatabaseProviderFactory, ProviderFactory,
7};
8use reth_snap_sync::{SnapSyncContext, SnapSyncError};
9use reth_tracing::tracing::debug;
10use std::{pin::pin, time::Duration};
11use tokio::{sync::watch, time::Instant};
12
13const DEFAULT_SAMPLE_INTERVAL: Duration = Duration::from_secs(2);
15
16const DEFAULT_RETRY_INTERVAL: Duration = Duration::from_secs(30);
19
20#[derive(Debug)]
23pub(crate) struct NodeSnapContext<N: ProviderNodeTypes, C> {
24 factory: ProviderFactory<N>,
25 client: C,
27 targets: watch::Receiver<B256>,
29 finalized: watch::Receiver<B256>,
31 interval: Duration,
33 retry: Duration,
35}
36
37impl<N: ProviderNodeTypes, C> NodeSnapContext<N, C> {
38 pub(crate) const fn new(
39 factory: ProviderFactory<N>,
40 client: C,
41 targets: watch::Receiver<B256>,
42 finalized: watch::Receiver<B256>,
43 ) -> Self {
44 Self {
45 factory,
46 client,
47 targets,
48 finalized,
49 interval: DEFAULT_SAMPLE_INTERVAL,
50 retry: DEFAULT_RETRY_INTERVAL,
51 }
52 }
53
54 #[cfg(test)]
55 const fn with_intervals(mut self, interval: Duration, retry: Duration) -> Self {
56 self.interval = interval;
57 self.retry = retry;
58 self
59 }
60}
61
62impl<N, C> SnapSyncContext for NodeSnapContext<N, C>
63where
64 N: ProviderNodeTypes,
65 C: DownloadClient,
66{
67 fn head(&self) -> Result<u64, SnapSyncError> {
68 Ok(self.factory.database_provider_ro()?.last_block_number()?)
69 }
70
71 fn finalized(&self) -> Result<Option<u64>, SnapSyncError> {
72 let hash = *self.finalized.borrow();
73 if hash.is_zero() {
74 return Ok(None)
75 }
76 Ok(self.factory.provider()?.block_number(hash)?)
78 }
79
80 async fn wait_for_progress(&mut self, _head: u64) -> bool {
81 let peers = self.client.num_connected_peers();
83 let mut retry = pin!(tokio::time::sleep(self.retry));
84 let mut sample = tokio::time::interval_at(Instant::now() + self.interval, self.interval);
85 loop {
86 tokio::select! {
87 _ = self.targets.changed() => return false,
90 () = &mut retry => {
92 debug!(target: "sync::snap", peers, "Retrying the stalled snap step");
93 return true
94 }
95 _ = sample.tick() => {}
96 }
97 if self.client.num_connected_peers() > peers {
98 return true
99 }
100 }
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 use super::*;
107 use reth_db::{tables, transaction::DbTxMut};
108 use reth_network_peers::PeerId;
109 use reth_provider::{
110 test_utils::{create_test_provider_factory, insert_headers, MockNodeTypesWithDB},
111 DBProvider,
112 };
113 use reth_testing_utils::generators::{self, random_header_range};
114 use std::sync::atomic::{AtomicUsize, Ordering};
115
116 const TEST_INTERVAL: Duration = Duration::from_millis(5);
117 const TEST_RETRY: Duration = Duration::from_millis(500);
118
119 fn context(
120 peers: &TestPeers,
121 ) -> (watch::Sender<B256>, NodeSnapContext<MockNodeTypesWithDB, &TestPeers>) {
122 let (targets, receiver) = watch::channel(B256::repeat_byte(1));
123 let finalized = watch::channel(B256::ZERO).1;
124 let context =
125 NodeSnapContext::new(create_test_provider_factory(), peers, receiver, finalized)
126 .with_intervals(TEST_INTERVAL, TEST_RETRY);
127 (targets, context)
128 }
129
130 #[derive(Debug, Default)]
132 struct TestPeers(AtomicUsize);
133
134 impl DownloadClient for TestPeers {
135 fn report_bad_message(&self, _peer_id: PeerId) {}
136
137 fn num_connected_peers(&self) -> usize {
138 self.0.load(Ordering::Relaxed)
139 }
140 }
141
142 #[test]
143 fn the_finalized_block_resolves_once_its_header_is_synced() {
144 let factory = create_test_provider_factory();
145 let headers = random_header_range(&mut generators::rng(), 0..4, B256::ZERO);
146 insert_headers(&factory, &headers);
147 let provider = factory.database_provider_rw().unwrap();
149 for header in &headers {
150 provider.tx_ref().put::<tables::HeaderNumbers>(header.hash(), header.number).unwrap();
151 }
152 provider.commit().unwrap();
153 let peers = TestPeers::default();
154 let (finalized, receiver) = watch::channel(B256::ZERO);
155 let context = NodeSnapContext::new(factory, &peers, watch::channel(B256::ZERO).1, receiver);
156
157 assert_eq!(context.finalized().unwrap(), None);
159
160 finalized.send(headers[2].hash()).unwrap();
161 assert_eq!(context.finalized().unwrap(), Some(2));
162
163 finalized.send(B256::repeat_byte(0xff)).unwrap();
165 assert_eq!(context.finalized().unwrap(), None);
166 }
167
168 #[tokio::test]
169 async fn a_new_target_ends_the_wait_for_headers() {
170 let peers = TestPeers::default();
171 let (targets, mut context) = context(&peers);
172
173 targets.send(B256::repeat_byte(2)).unwrap();
174
175 assert!(!context.wait_for_progress(0).await);
176 }
177
178 #[tokio::test]
179 async fn a_dropped_backfill_ends_the_wait() {
180 let peers = TestPeers::default();
181 let (targets, mut context) = context(&peers);
182
183 drop(targets);
184
185 assert!(!context.wait_for_progress(0).await);
186 }
187
188 #[tokio::test]
189 async fn new_peers_end_the_wait() {
190 let peers = TestPeers::default();
191 let (_targets, mut context) = context(&peers);
192
193 let (progressed, ()) = tokio::join!(context.wait_for_progress(0), async {
195 tokio::time::sleep(TEST_INTERVAL).await;
196 peers.0.fetch_add(1, Ordering::Relaxed);
197 });
198
199 assert!(progressed);
200 }
201
202 #[tokio::test]
203 async fn an_unchanged_peer_count_retries_once_the_wait_is_out() {
204 let peers = TestPeers(AtomicUsize::new(1));
206 let (_targets, mut context) = context(&peers);
207
208 assert!(context.wait_for_progress(0).await);
209 }
210}