1use crate::{
23 block_relay_protocol::BlockDownloader,
24 block_request_handler::MAX_BLOCKS_IN_RESPONSE,
25 service::network::NetworkServiceHandle,
26 strategy::{
27 chain_sync::{ChainSync, ChainSyncMode, GapSyncBodyPolicyProvider},
28 state::StateStrategy,
29 warp::{WarpSync, WarpSyncConfig},
30 StrategyKey, SyncingAction, SyncingStrategy,
31 },
32 types::SyncStatus,
33 LOG_TARGET,
34};
35use log::{debug, error, info, warn};
36use prometheus_endpoint::Registry;
37use sc_client_api::{BlockBackend, ProofProvider};
38use sc_consensus::{BlockImportError, BlockImportStatus};
39use sc_network::ProtocolName;
40use sc_network_common::sync::{message::BlockAnnounce, SyncMode};
41use sc_network_types::PeerId;
42use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata};
43use sp_runtime::traits::{Block as BlockT, Header, NumberFor};
44use std::{any::Any, collections::HashMap, sync::Arc};
45
46fn chain_sync_mode(sync_mode: SyncMode) -> ChainSyncMode {
48 match sync_mode {
49 SyncMode::Full => ChainSyncMode::Full,
50 SyncMode::LightState { skip_proofs, storage_chain_mode } => {
51 ChainSyncMode::LightState { skip_proofs, storage_chain_mode }
52 },
53 SyncMode::Warp => ChainSyncMode::Full,
54 }
55}
56
57#[derive(Clone)]
59pub struct PolkadotSyncingStrategyConfig<Block>
60where
61 Block: BlockT,
62{
63 pub mode: SyncMode,
65 pub max_parallel_downloads: u32,
67 pub max_blocks_per_request: u32,
69 pub min_peers_to_start_warp_sync: Option<usize>,
71 pub metrics_registry: Option<Registry>,
73 pub state_request_protocol_name: ProtocolName,
75 pub block_downloader: Arc<dyn BlockDownloader<Block>>,
77 pub gap_sync_body_policy: GapSyncBodyPolicyProvider,
79}
80
81pub struct PolkadotSyncingStrategy<B: BlockT, Client> {
83 config: PolkadotSyncingStrategyConfig<B>,
85 client: Arc<Client>,
87 warp: Option<WarpSync<B>>,
89 state: Option<StateStrategy<B>>,
91 chain_sync: Option<ChainSync<B, Client>>,
93 peer_best_blocks: HashMap<PeerId, (B::Hash, NumberFor<B>)>,
96}
97
98impl<B: BlockT, Client> SyncingStrategy<B> for PolkadotSyncingStrategy<B, Client>
99where
100 B: BlockT,
101 Client: HeaderBackend<B>
102 + BlockBackend<B>
103 + HeaderMetadata<B, Error = sp_blockchain::Error>
104 + ProofProvider<B>
105 + Send
106 + Sync
107 + 'static,
108{
109 fn add_peer(&mut self, peer_id: PeerId, best_hash: B::Hash, best_number: NumberFor<B>) {
110 self.peer_best_blocks.insert(peer_id, (best_hash, best_number));
111
112 self.warp.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number));
113 self.state.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number));
114 self.chain_sync.as_mut().map(|s| s.add_peer(peer_id, best_hash, best_number));
115 }
116
117 fn remove_peer(&mut self, peer_id: &PeerId) {
118 self.warp.as_mut().map(|s| s.remove_peer(peer_id));
119 self.state.as_mut().map(|s| s.remove_peer(peer_id));
120 self.chain_sync.as_mut().map(|s| s.remove_peer(peer_id));
121
122 self.peer_best_blocks.remove(peer_id);
123 }
124
125 fn on_validated_block_announce(
126 &mut self,
127 is_best: bool,
128 peer_id: PeerId,
129 announce: &BlockAnnounce<B::Header>,
130 ) -> Option<(B::Hash, NumberFor<B>)> {
131 let new_best = if let Some(ref mut warp) = self.warp {
132 warp.on_validated_block_announce(is_best, peer_id, announce)
133 } else if let Some(ref mut state) = self.state {
134 state.on_validated_block_announce(is_best, peer_id, announce)
135 } else if let Some(ref mut chain_sync) = self.chain_sync {
136 chain_sync.on_validated_block_announce(is_best, peer_id, announce)
137 } else {
138 error!(target: LOG_TARGET, "No syncing strategy is active.");
139 debug_assert!(false);
140 Some((announce.header.hash(), *announce.header.number()))
141 };
142
143 if let Some(new_best) = new_best {
144 if let Some(best) = self.peer_best_blocks.get_mut(&peer_id) {
145 *best = new_best;
146 } else {
147 debug!(
148 target: LOG_TARGET,
149 "Cannot update `peer_best_blocks` as peer {peer_id} is not known to `Strategy` \
150 (already disconnected?)",
151 );
152 }
153 }
154
155 new_best
156 }
157
158 fn set_sync_fork_request(&mut self, peers: Vec<PeerId>, hash: &B::Hash, number: NumberFor<B>) {
159 if let Some(ref mut chain_sync) = self.chain_sync {
161 chain_sync.set_sync_fork_request(peers.clone(), hash, number);
162 }
163 }
164
165 fn request_justification(&mut self, hash: &B::Hash, number: NumberFor<B>) {
166 if let Some(ref mut chain_sync) = self.chain_sync {
168 chain_sync.request_justification(hash, number);
169 }
170 }
171
172 fn clear_justification_requests(&mut self) {
173 if let Some(ref mut chain_sync) = self.chain_sync {
175 chain_sync.clear_justification_requests();
176 }
177 }
178
179 fn on_justification_import(&mut self, hash: B::Hash, number: NumberFor<B>, success: bool) {
180 if let Some(ref mut chain_sync) = self.chain_sync {
182 chain_sync.on_justification_import(hash, number, success);
183 }
184 }
185
186 fn on_generic_response(
187 &mut self,
188 peer_id: &PeerId,
189 key: StrategyKey,
190 protocol_name: ProtocolName,
191 response: Box<dyn Any + Send>,
192 ) {
193 match key {
194 StateStrategy::<B>::STRATEGY_KEY => {
195 if let Some(state) = &mut self.state {
196 let Ok(response) = response.downcast::<Vec<u8>>() else {
197 warn!(target: LOG_TARGET, "Failed to downcast state response");
198 debug_assert!(false);
199 return;
200 };
201
202 state.on_state_response(peer_id, *response);
203 } else if let Some(chain_sync) = &mut self.chain_sync {
204 chain_sync.on_generic_response(peer_id, key, protocol_name, response);
205 } else {
206 error!(
207 target: LOG_TARGET,
208 "`on_generic_response()` called with unexpected key {key:?} \
209 or corresponding strategy is not active.",
210 );
211 debug_assert!(false);
212 }
213 },
214 WarpSync::<B>::STRATEGY_KEY => {
215 if let Some(warp) = &mut self.warp {
216 warp.on_generic_response(peer_id, protocol_name, response);
217 } else {
218 error!(
219 target: LOG_TARGET,
220 "`on_generic_response()` called with unexpected key {key:?} \
221 or warp strategy is not active",
222 );
223 debug_assert!(false);
224 }
225 },
226 ChainSync::<B, Client>::STRATEGY_KEY => {
227 if let Some(chain_sync) = &mut self.chain_sync {
228 chain_sync.on_generic_response(peer_id, key, protocol_name, response);
229 } else {
230 error!(
231 target: LOG_TARGET,
232 "`on_generic_response()` called with unexpected key {key:?} \
233 or corresponding strategy is not active.",
234 );
235 debug_assert!(false);
236 }
237 },
238 key => {
239 warn!(
240 target: LOG_TARGET,
241 "Unexpected generic response strategy key {key:?}, protocol {protocol_name}",
242 );
243 debug_assert!(false);
244 },
245 }
246 }
247
248 fn on_blocks_processed(
249 &mut self,
250 imported: usize,
251 count: usize,
252 results: Vec<(Result<BlockImportStatus<NumberFor<B>>, BlockImportError>, B::Hash)>,
253 ) {
254 if let Some(ref mut state) = self.state {
256 state.on_blocks_processed(imported, count, results);
257 } else if let Some(ref mut chain_sync) = self.chain_sync {
258 chain_sync.on_blocks_processed(imported, count, results);
259 }
260 }
261
262 fn on_block_finalized(&mut self, hash: &B::Hash, number: NumberFor<B>) {
263 if let Some(ref mut chain_sync) = self.chain_sync {
265 chain_sync.on_block_finalized(hash, number);
266 }
267 }
268
269 fn update_chain_info(&mut self, best_hash: &B::Hash, best_number: NumberFor<B>) {
270 if let Some(ref mut chain_sync) = self.chain_sync {
272 chain_sync.update_chain_info(best_hash, best_number);
273 }
274 }
275
276 fn is_major_syncing(&self) -> bool {
277 self.warp.is_some() ||
278 self.state.is_some() ||
279 match self.chain_sync {
280 Some(ref s) => s.status().state.is_major_syncing(),
281 None => unreachable!("At least one syncing strategy is active; qed"),
282 }
283 }
284
285 fn num_peers(&self) -> usize {
286 self.peer_best_blocks.len()
287 }
288
289 fn status(&self) -> SyncStatus<B> {
290 if let Some(ref warp) = self.warp {
293 warp.status()
294 } else if let Some(ref state) = self.state {
295 state.status()
296 } else if let Some(ref chain_sync) = self.chain_sync {
297 chain_sync.status()
298 } else {
299 unreachable!("At least one syncing strategy is always active; qed")
300 }
301 }
302
303 fn num_downloaded_blocks(&self) -> usize {
304 self.chain_sync
305 .as_ref()
306 .map_or(0, |chain_sync| chain_sync.num_downloaded_blocks())
307 }
308
309 fn num_sync_requests(&self) -> usize {
310 self.chain_sync.as_ref().map_or(0, |chain_sync| chain_sync.num_sync_requests())
311 }
312
313 fn actions(
314 &mut self,
315 network_service: &NetworkServiceHandle,
316 ) -> Result<Vec<SyncingAction<B>>, ClientError> {
317 let actions: Vec<_> = if let Some(ref mut warp) = self.warp {
320 warp.actions(network_service).map(Into::into).collect()
321 } else if let Some(ref mut state) = self.state {
322 state.actions(network_service).map(Into::into).collect()
323 } else if let Some(ref mut chain_sync) = self.chain_sync {
324 chain_sync.actions(network_service)?
325 } else {
326 unreachable!("At least one syncing strategy is always active; qed")
327 };
328
329 if actions.iter().any(SyncingAction::is_finished) {
330 self.proceed_to_next()?;
331 }
332
333 Ok(actions)
334 }
335}
336
337impl<B: BlockT, Client> PolkadotSyncingStrategy<B, Client>
338where
339 B: BlockT,
340 Client: HeaderBackend<B>
341 + BlockBackend<B>
342 + HeaderMetadata<B, Error = sp_blockchain::Error>
343 + ProofProvider<B>
344 + Send
345 + Sync
346 + 'static,
347{
348 pub fn new(
350 mut config: PolkadotSyncingStrategyConfig<B>,
351 client: Arc<Client>,
352 warp_sync_config: Option<WarpSyncConfig<B>>,
353 warp_sync_protocol_name: Option<ProtocolName>,
354 ) -> Result<Self, ClientError> {
355 if config.max_blocks_per_request > MAX_BLOCKS_IN_RESPONSE as u32 {
356 info!(
357 target: LOG_TARGET,
358 "clamping maximum blocks per request to {MAX_BLOCKS_IN_RESPONSE}",
359 );
360 config.max_blocks_per_request = MAX_BLOCKS_IN_RESPONSE as u32;
361 }
362
363 if let SyncMode::Warp = config.mode {
364 let warp_sync_config = warp_sync_config
365 .expect("Warp sync configuration must be supplied in warp sync mode.");
366 let warp_sync = WarpSync::new(
367 client.clone(),
368 warp_sync_config,
369 warp_sync_protocol_name,
370 config.block_downloader.clone(),
371 config.min_peers_to_start_warp_sync,
372 );
373 Ok(Self {
374 config,
375 client,
376 warp: Some(warp_sync),
377 state: None,
378 chain_sync: None,
379 peer_best_blocks: Default::default(),
380 })
381 } else {
382 let chain_sync = ChainSync::new(
383 chain_sync_mode(config.mode),
384 client.clone(),
385 config.max_parallel_downloads,
386 config.max_blocks_per_request,
387 config.state_request_protocol_name.clone(),
388 config.block_downloader.clone(),
389 (config.gap_sync_body_policy)()?,
390 config.metrics_registry.as_ref(),
391 std::iter::empty(),
392 )?;
393 Ok(Self {
394 config,
395 client,
396 warp: None,
397 state: None,
398 chain_sync: Some(chain_sync),
399 peer_best_blocks: Default::default(),
400 })
401 }
402 }
403
404 pub fn proceed_to_next(&mut self) -> Result<(), ClientError> {
406 if let Some(ref mut warp) = self.warp {
408 match warp.take_result() {
409 Some(res) => {
410 info!(
411 target: LOG_TARGET,
412 "Warp sync is complete, continuing with state sync."
413 );
414 let state_sync = StateStrategy::new(
415 self.client.clone(),
416 res.target_header,
417 res.target_body,
418 res.target_justifications,
419 false,
420 self.peer_best_blocks
421 .iter()
422 .map(|(peer_id, (_, best_number))| (*peer_id, *best_number)),
423 self.config.state_request_protocol_name.clone(),
424 );
425
426 self.warp = None;
427 self.state = Some(state_sync);
428 Ok(())
429 },
430 None => {
431 error!(
432 target: LOG_TARGET,
433 "Warp sync failed. Continuing with full sync."
434 );
435 let chain_sync = match ChainSync::new(
436 chain_sync_mode(self.config.mode),
437 self.client.clone(),
438 self.config.max_parallel_downloads,
439 self.config.max_blocks_per_request,
440 self.config.state_request_protocol_name.clone(),
441 self.config.block_downloader.clone(),
442 (self.config.gap_sync_body_policy)()?,
443 self.config.metrics_registry.as_ref(),
444 self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| {
445 (*peer_id, *best_hash, *best_number)
446 }),
447 ) {
448 Ok(chain_sync) => chain_sync,
449 Err(e) => {
450 error!(target: LOG_TARGET, "Failed to start `ChainSync`.");
451 return Err(e);
452 },
453 };
454
455 self.warp = None;
456 self.chain_sync = Some(chain_sync);
457 Ok(())
458 },
459 }
460 } else if let Some(state) = &self.state {
461 if state.is_succeeded() {
462 info!(target: LOG_TARGET, "State sync is complete, continuing with block sync.");
463 } else {
464 error!(target: LOG_TARGET, "State sync failed. Falling back to full sync.");
465 }
466 let chain_sync = match ChainSync::new(
467 chain_sync_mode(self.config.mode),
468 self.client.clone(),
469 self.config.max_parallel_downloads,
470 self.config.max_blocks_per_request,
471 self.config.state_request_protocol_name.clone(),
472 self.config.block_downloader.clone(),
473 (self.config.gap_sync_body_policy)()?,
474 self.config.metrics_registry.as_ref(),
475 self.peer_best_blocks.iter().map(|(peer_id, (best_hash, best_number))| {
476 (*peer_id, *best_hash, *best_number)
477 }),
478 ) {
479 Ok(chain_sync) => chain_sync,
480 Err(e) => {
481 error!(target: LOG_TARGET, "Failed to start `ChainSync`.");
482 return Err(e);
483 },
484 };
485
486 self.state = None;
487 self.chain_sync = Some(chain_sync);
488 Ok(())
489 } else {
490 unreachable!("Only warp & state strategies can finish; qed")
491 }
492 }
493}