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