1use codec::{Decode, DecodeAll, Encode};
21
22use crate::{
23 best_justification, find_scheduled_change, AuthoritySetChanges, AuthoritySetHardFork,
24 BlockNumberOps, GrandpaJustification, SharedAuthoritySet,
25};
26use sc_client_api::Backend as ClientBackend;
27use sc_network_sync::strategy::warp::{
28 EncodedProof, VerificationResult, Verifier, WarpSyncProvider,
29};
30use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend};
31use sp_consensus_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID};
32use sp_runtime::{
33 generic::BlockId,
34 traits::{Block as BlockT, Header as HeaderT, NumberFor, One},
35 Justifications,
36};
37
38use std::{collections::HashMap, sync::Arc};
39
40#[derive(Debug, thiserror::Error)]
42pub enum Error {
43 #[error("Failed to decode block hash: {0}.")]
45 DecodeScale(#[from] codec::Error),
46 #[error("{0}")]
48 Client(#[from] sp_blockchain::Error),
49 #[error("{0}")]
51 InvalidRequest(String),
52 #[error("{0}")]
54 InvalidProof(String),
55 #[error("Missing required data to be able to answer request.")]
57 MissingData,
58}
59
60pub(super) const MAX_WARP_SYNC_PROOF_SIZE: usize = 8 * 1024 * 1024;
62
63#[derive(Decode, Encode, Debug)]
65pub struct WarpSyncFragment<Block: BlockT> {
66 pub header: Block::Header,
69 pub justification: GrandpaJustification<Block>,
72}
73
74#[derive(Decode, Encode)]
76pub struct WarpSyncProof<Block: BlockT> {
77 proofs: Vec<WarpSyncFragment<Block>>,
78 is_finished: bool,
79}
80
81impl<Block: BlockT> WarpSyncProof<Block> {
82 fn generate<Backend>(
86 backend: &Backend,
87 begin: Block::Hash,
88 set_changes: &AuthoritySetChanges<NumberFor<Block>>,
89 ) -> Result<WarpSyncProof<Block>, Error>
90 where
91 Backend: ClientBackend<Block>,
92 {
93 let blockchain = backend.blockchain();
95
96 let begin_number = blockchain
97 .block_number_from_id(&BlockId::Hash(begin))?
98 .ok_or_else(|| Error::InvalidRequest("Missing start block".to_string()))?;
99
100 if begin_number > blockchain.info().finalized_number {
101 return Err(Error::InvalidRequest("Start block is not finalized".to_string()));
102 }
103
104 let canon_hash = blockchain.hash(begin_number)?.expect(
105 "begin number is lower than finalized number; \
106 all blocks below finalized number must have been imported; \
107 qed.",
108 );
109
110 if canon_hash != begin {
111 return Err(Error::InvalidRequest(
112 "Start block is not in the finalized chain".to_string(),
113 ));
114 }
115
116 let mut proofs = Vec::new();
117 let mut proofs_encoded_len = 0;
118 let mut proof_limit_reached = false;
119
120 let set_changes = set_changes.iter_from(begin_number).ok_or(Error::MissingData)?;
121
122 for (_, last_block) in set_changes {
123 let hash = blockchain.block_hash_from_id(&BlockId::Number(*last_block))?
124 .expect("header number comes from previously applied set changes; corresponding hash must exist in db; qed.");
125
126 let header = blockchain
127 .header(hash)?
128 .expect("header hash obtained from header number exists in db; corresponding header must exist in db too; qed.");
129
130 if find_scheduled_change::<Block>(&header).is_none() {
133 break;
137 }
138
139 let justification = blockchain
140 .justifications(header.hash())?
141 .and_then(|just| just.into_justification(GRANDPA_ENGINE_ID))
142 .ok_or_else(|| Error::MissingData)?;
143
144 let justification = GrandpaJustification::<Block>::decode_all(&mut &justification[..])?;
145
146 let proof = WarpSyncFragment { header: header.clone(), justification };
147 let proof_size = proof.encoded_size();
148
149 if proofs_encoded_len + proof_size >= MAX_WARP_SYNC_PROOF_SIZE - 50 {
153 proof_limit_reached = true;
154 break;
155 }
156
157 proofs_encoded_len += proof_size;
158 proofs.push(proof);
159 }
160
161 let is_finished = if proof_limit_reached {
162 false
163 } else {
164 let latest_justification = best_justification(backend)?.filter(|justification| {
165 let limit = proofs
170 .last()
171 .map(|proof| proof.justification.target().0 + One::one())
172 .unwrap_or(begin_number);
173
174 justification.target().0 >= limit
175 });
176
177 if let Some(latest_justification) = latest_justification {
178 let header = blockchain.header(latest_justification.target().1)?
179 .expect("header hash corresponds to a justification in db; must exist in db as well; qed.");
180
181 let proof = WarpSyncFragment { header, justification: latest_justification };
182
183 if proofs_encoded_len + proof.encoded_size() >= MAX_WARP_SYNC_PROOF_SIZE - 50 {
187 false
188 } else {
189 proofs.push(proof);
190 true
191 }
192 } else {
193 true
194 }
195 };
196
197 let final_outcome = WarpSyncProof { proofs, is_finished };
198 debug_assert!(final_outcome.encoded_size() <= MAX_WARP_SYNC_PROOF_SIZE);
199 Ok(final_outcome)
200 }
201
202 fn verify(
206 &self,
207 set_id: SetId,
208 authorities: AuthorityList,
209 hard_forks: &HashMap<(Block::Hash, NumberFor<Block>), (SetId, AuthorityList)>,
210 ) -> Result<(SetId, AuthorityList), Error>
211 where
212 NumberFor<Block>: BlockNumberOps,
213 {
214 let mut current_set_id = set_id;
215 let mut current_authorities = authorities;
216
217 for (fragment_num, proof) in self.proofs.iter().enumerate() {
218 let hash = proof.header.hash();
219 let number = *proof.header.number();
220
221 if let Some((set_id, list)) = hard_forks.get(&(hash, number)) {
222 current_set_id = *set_id;
223 current_authorities = list.clone();
224 } else {
225 proof
226 .justification
227 .verify(current_set_id, ¤t_authorities)
228 .map_err(|err| Error::InvalidProof(err.to_string()))?;
229
230 if proof.justification.target().1 != hash {
231 return Err(Error::InvalidProof(
232 "Mismatch between header and justification".to_owned(),
233 ));
234 }
235
236 if let Some(scheduled_change) = find_scheduled_change::<Block>(&proof.header) {
237 current_authorities = scheduled_change.next_authorities;
238 current_set_id += 1;
239 } else if fragment_num != self.proofs.len() - 1 || !self.is_finished {
240 return Err(Error::InvalidProof(
243 "Header is missing authority set change digest".to_string(),
244 ));
245 }
246 }
247 }
248 Ok((current_set_id, current_authorities))
249 }
250}
251
252pub struct NetworkProvider<Block: BlockT, Backend: ClientBackend<Block>>
254where
255 NumberFor<Block>: BlockNumberOps,
256{
257 backend: Arc<Backend>,
258 authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
259 hard_forks: HashMap<(Block::Hash, NumberFor<Block>), (SetId, AuthorityList)>,
260}
261
262impl<Block: BlockT, Backend: ClientBackend<Block>> NetworkProvider<Block, Backend>
263where
264 NumberFor<Block>: BlockNumberOps,
265{
266 pub fn new(
268 backend: Arc<Backend>,
269 authority_set: SharedAuthoritySet<Block::Hash, NumberFor<Block>>,
270 hard_forks: Vec<AuthoritySetHardFork<Block>>,
271 ) -> Self {
272 NetworkProvider {
273 backend,
274 authority_set,
275 hard_forks: hard_forks
276 .into_iter()
277 .map(|fork| (fork.block, (fork.set_id, fork.authorities)))
278 .collect(),
279 }
280 }
281}
282
283struct VerifierState<Block: BlockT> {
285 set_id: SetId,
286 authorities: AuthorityList,
287 next_proof_context: Block::Hash,
288}
289
290struct GrandpaVerifier<Block: BlockT> {
292 state: VerifierState<Block>,
293 hard_forks: HashMap<(Block::Hash, NumberFor<Block>), (SetId, AuthorityList)>,
294 eras_synced: u64,
295}
296
297impl<Block: BlockT> Verifier<Block> for GrandpaVerifier<Block>
298where
299 NumberFor<Block>: BlockNumberOps,
300{
301 fn verify(
302 &mut self,
303 proof: &EncodedProof,
304 ) -> Result<VerificationResult<Block>, Box<dyn std::error::Error + Send + Sync>> {
305 let EncodedProof(proof) = proof;
306 let proof = WarpSyncProof::<Block>::decode_all(&mut proof.as_slice())
307 .map_err(|e| format!("Proof decoding error: {:?}", e))?;
308 let last_header = proof
309 .proofs
310 .last()
311 .map(|p| p.header.clone())
312 .ok_or_else(|| "Empty proof".to_string())?;
313
314 let (current_set_id, current_authorities) =
315 (self.state.set_id, self.state.authorities.clone());
316
317 let (next_set_id, next_authorities) = proof
318 .verify(current_set_id, current_authorities, &self.hard_forks)
319 .map_err(Box::new)?;
320
321 self.state = VerifierState {
322 set_id: next_set_id,
323 authorities: next_authorities,
324 next_proof_context: last_header.hash(),
325 };
326
327 self.eras_synced += proof.proofs.len() as u64;
329
330 let justifications = proof
331 .proofs
332 .into_iter()
333 .map(|p| {
334 let justifications =
335 Justifications::new(vec![(GRANDPA_ENGINE_ID, p.justification.encode())]);
336 (p.header, justifications)
337 })
338 .collect::<Vec<_>>();
339
340 if proof.is_finished {
341 Ok(VerificationResult::Complete(last_header, justifications))
342 } else {
343 Ok(VerificationResult::Partial(justifications))
344 }
345 }
346
347 fn next_proof_context(&self) -> Block::Hash {
348 self.state.next_proof_context
349 }
350
351 fn status(&self) -> Option<String> {
352 Some(format!("{} eras synced", self.eras_synced))
353 }
354}
355
356impl<Block: BlockT, Backend: ClientBackend<Block>> WarpSyncProvider<Block>
357 for NetworkProvider<Block, Backend>
358where
359 NumberFor<Block>: BlockNumberOps,
360{
361 fn generate(
362 &self,
363 start: Block::Hash,
364 ) -> Result<EncodedProof, Box<dyn std::error::Error + Send + Sync>> {
365 let proof = WarpSyncProof::<Block>::generate(
366 &*self.backend,
367 start,
368 &self.authority_set.authority_set_changes(),
369 )
370 .map_err(Box::new)?;
371 Ok(EncodedProof(proof.encode()))
372 }
373
374 fn create_verifier(&self) -> Box<dyn Verifier<Block>> {
375 let authority_set = self.authority_set.inner();
376 let genesis_hash = self.backend.blockchain().info().genesis_hash;
377 Box::new(GrandpaVerifier {
378 state: VerifierState {
379 set_id: authority_set.set_id,
380 authorities: authority_set.current_authorities.clone(),
381 next_proof_context: genesis_hash,
382 },
383 hard_forks: self.hard_forks.clone(),
384 eras_synced: 0,
385 })
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::WarpSyncProof;
392 use crate::{AuthoritySetChanges, GrandpaJustification};
393 use codec::Encode;
394 use rand::prelude::*;
395 use sc_block_builder::BlockBuilderBuilder;
396 use sp_blockchain::HeaderBackend;
397 use sp_consensus::BlockOrigin;
398 use sp_consensus_grandpa::GRANDPA_ENGINE_ID;
399 use sp_keyring::Ed25519Keyring;
400 use std::sync::Arc;
401 use substrate_test_runtime_client::{
402 BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt,
403 TestClientBuilder, TestClientBuilderExt,
404 };
405
406 #[test]
407 fn warp_sync_proof_generate_verify() {
408 let mut rng = rand::rngs::StdRng::from_seed([0; 32]);
409 let builder = TestClientBuilder::new();
410 let backend = builder.backend();
411 let client = Arc::new(builder.build());
412
413 let available_authorities = Ed25519Keyring::iter().collect::<Vec<_>>();
414 let genesis_authorities = vec![(Ed25519Keyring::Alice.public().into(), 1)];
415
416 let mut current_authorities = vec![Ed25519Keyring::Alice];
417 let mut current_set_id = 0;
418 let mut authority_set_changes = Vec::new();
419
420 for n in 1..=100 {
421 let mut builder = BlockBuilderBuilder::new(&*client)
422 .on_parent_block(client.chain_info().best_hash)
423 .with_parent_block_number(client.chain_info().best_number)
424 .build()
425 .unwrap();
426 let mut new_authorities = None;
427
428 if n != 0 && n % 10 == 0 {
430 let n_authorities = rng.gen_range(1..available_authorities.len());
432 let next_authorities = available_authorities
433 .choose_multiple(&mut rng, n_authorities)
434 .cloned()
435 .collect::<Vec<_>>();
436
437 new_authorities = Some(next_authorities.clone());
438
439 let next_authorities = next_authorities
440 .iter()
441 .map(|keyring| (keyring.public().into(), 1))
442 .collect::<Vec<_>>();
443
444 let digest = sp_runtime::generic::DigestItem::Consensus(
445 sp_consensus_grandpa::GRANDPA_ENGINE_ID,
446 sp_consensus_grandpa::ConsensusLog::ScheduledChange(
447 sp_consensus_grandpa::ScheduledChange { delay: 0u64, next_authorities },
448 )
449 .encode(),
450 );
451
452 builder.push_deposit_log_digest_item(digest).unwrap();
453 }
454
455 let block = builder.build().unwrap().block;
456
457 futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap();
458
459 if let Some(new_authorities) = new_authorities {
460 let (target_hash, target_number) = {
463 let info = client.info();
464 (info.best_hash, info.best_number)
465 };
466
467 let mut precommits = Vec::new();
468 for keyring in ¤t_authorities {
469 let precommit = finality_grandpa::Precommit { target_hash, target_number };
470
471 let msg = finality_grandpa::Message::Precommit(precommit.clone());
472 let encoded = sp_consensus_grandpa::localized_payload(42, current_set_id, &msg);
473 let signature = keyring.sign(&encoded[..]).into();
474
475 let precommit = finality_grandpa::SignedPrecommit {
476 precommit,
477 signature,
478 id: keyring.public().into(),
479 };
480
481 precommits.push(precommit);
482 }
483
484 let commit = finality_grandpa::Commit { target_hash, target_number, precommits };
485
486 let justification = GrandpaJustification::from_commit(&client, 42, commit).unwrap();
487
488 client
489 .finalize_block(target_hash, Some((GRANDPA_ENGINE_ID, justification.encode())))
490 .unwrap();
491
492 authority_set_changes.push((current_set_id, n));
493
494 current_set_id += 1;
495 current_authorities = new_authorities;
496 }
497 }
498
499 let authority_set_changes = AuthoritySetChanges::from(authority_set_changes);
500
501 let genesis_hash = client.hash(0).unwrap().unwrap();
503
504 let warp_sync_proof =
505 WarpSyncProof::generate(&*backend, genesis_hash, &authority_set_changes).unwrap();
506
507 let (new_set_id, new_authorities) =
509 warp_sync_proof.verify(0, genesis_authorities, &Default::default()).unwrap();
510
511 let expected_authorities = current_authorities
512 .iter()
513 .map(|keyring| (keyring.public().into(), 1))
514 .collect::<Vec<_>>();
515
516 assert_eq!(new_set_id, current_set_id);
517 assert_eq!(new_authorities, expected_authorities);
518 }
519}