referrerpolicy=no-referrer-when-downgrade

sc_network_sync/strategy/
polkadot.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License as published by
8// the Free Software Foundation, either version 3 of the License, or
9// (at your option) any later version.
10
11// This program is distributed in the hope that it will be useful,
12// but WITHOUT ANY WARRANTY; without even the implied warranty of
13// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14// GNU General Public License for more details.
15
16// You should have received a copy of the GNU General Public License
17// along with this program. If not, see <https://www.gnu.org/licenses/>.
18
19//! [`PolkadotSyncingStrategy`] is a proxy between [`crate::engine::SyncingEngine`]
20//! and specific syncing algorithms.
21
22use 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
46/// Corresponding `ChainSync` mode.
47fn 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/// Syncing configuration containing data for [`PolkadotSyncingStrategy`].
58#[derive(Clone)]
59pub struct PolkadotSyncingStrategyConfig<Block>
60where
61	Block: BlockT,
62{
63	/// Syncing mode.
64	pub mode: SyncMode,
65	/// The number of parallel downloads to guard against slow peers.
66	pub max_parallel_downloads: u32,
67	/// Maximum number of blocks to request.
68	pub max_blocks_per_request: u32,
69	/// Number of peers that need to be connected before warp sync is started.
70	pub min_peers_to_start_warp_sync: Option<usize>,
71	/// Prometheus metrics registry.
72	pub metrics_registry: Option<Registry>,
73	/// Protocol name used to send out state requests
74	pub state_request_protocol_name: ProtocolName,
75	/// Block downloader
76	pub block_downloader: Arc<dyn BlockDownloader<Block>>,
77	/// Resolves the gap sync body policy when a `ChainSync` instance is created.
78	pub gap_sync_body_policy: GapSyncBodyPolicyProvider,
79}
80
81/// Proxy to specific syncing strategies used in Polkadot.
82pub struct PolkadotSyncingStrategy<B: BlockT, Client> {
83	/// Initial syncing configuration.
84	config: PolkadotSyncingStrategyConfig<B>,
85	/// Client used by syncing strategies.
86	client: Arc<Client>,
87	/// Warp strategy.
88	warp: Option<WarpSync<B>>,
89	/// State strategy.
90	state: Option<StateStrategy<B>>,
91	/// `ChainSync` strategy.`
92	chain_sync: Option<ChainSync<B, Client>>,
93	/// Connected peers and their best blocks used to seed a new strategy when switching to it in
94	/// `PolkadotSyncingStrategy::proceed_to_next`.
95	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		// Fork requests are only handled by `ChainSync`.
160		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		// Justifications can only be requested via `ChainSync`.
167		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		// Justification requests can only be cleared by `ChainSync`.
174		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		// Only `ChainSync` is interested in justification import.
181		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		// Only `StateStrategy` and `ChainSync` are interested in block processing notifications.
255		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		// Only `ChainSync` is interested in block finalization notifications.
264		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		// This is relevant to `ChainSync` only.
271		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		// This function presumes that strategies are executed serially and must be refactored
291		// once we have parallel strategies.
292		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		// This function presumes that strategies are executed serially and must be refactored once
318		// we have parallel strategies.
319		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	/// Initialize a new syncing strategy.
349	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	/// Proceed with the next strategy if the active one finished.
405	pub fn proceed_to_next(&mut self) -> Result<(), ClientError> {
406		// The strategies are switched as `WarpSync` -> `StateStrategy` -> `ChainSync`.
407		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}