referrerpolicy=no-referrer-when-downgrade

sc_consensus_grandpa/
warp_proof.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Substrate.
3// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
4
5// Substrate is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9
10// Substrate is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14
15// You should have received a copy of the GNU General Public License
16// along with Substrate. If not, see <https://www.gnu.org/licenses/>.
17
18//! Utilities for generating and verifying GRANDPA warp sync proofs.
19
20use 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/// Warp proof processing error.
41#[derive(Debug, thiserror::Error)]
42pub enum Error {
43	/// Decoding error.
44	#[error("Failed to decode block hash: {0}.")]
45	DecodeScale(#[from] codec::Error),
46	/// Client backend error.
47	#[error("{0}")]
48	Client(#[from] sp_blockchain::Error),
49	/// Invalid request data.
50	#[error("{0}")]
51	InvalidRequest(String),
52	/// Invalid warp proof.
53	#[error("{0}")]
54	InvalidProof(String),
55	/// Missing header or authority set change data.
56	#[error("Missing required data to be able to answer request.")]
57	MissingData,
58}
59
60/// The maximum size in bytes of the `WarpSyncProof`.
61pub(super) const MAX_WARP_SYNC_PROOF_SIZE: usize = 8 * 1024 * 1024;
62
63/// A proof of an authority set change.
64#[derive(Decode, Encode, Debug)]
65pub struct WarpSyncFragment<Block: BlockT> {
66	/// The last block that the given authority set finalized. This block should contain a digest
67	/// signaling an authority set change from which we can fetch the next authority set.
68	pub header: Block::Header,
69	/// A justification for the header above which proves its finality. In order to validate it the
70	/// verifier must be aware of the authorities and set id for which the justification refers to.
71	pub justification: GrandpaJustification<Block>,
72}
73
74/// An accumulated proof of multiple authority set changes.
75#[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	/// Generates a warp sync proof starting at the given block. It will generate authority set
83	/// change proofs for all changes that happened from `begin` until the current authority set
84	/// (capped by MAX_WARP_SYNC_PROOF_SIZE).
85	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		// TODO: cache best response (i.e. the one with lowest begin_number)
94		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			// the last block in a set is the one that triggers a change to the next set,
131			// therefore the block must have a digest that signals the authority set change
132			if find_scheduled_change::<Block>(&header).is_none() {
133				// if it doesn't contain a signal for standard change then the set must have changed
134				// through a forced changed, in which case we stop collecting proofs as the chain of
135				// trust in authority handoffs was broken.
136				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			// Check for the limit. We remove some bytes from the maximum size, because we're only
150			// counting the size of the `WarpSyncFragment`s. The extra margin is here to leave
151			// room for rest of the data (the size of the `Vec` and the boolean).
152			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				// the existing best justification must be for a block higher than the
166				// last authority set change. if we didn't prove any authority set
167				// change then we fallback to make sure it's higher or equal to the
168				// initial warp sync block.
169				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				// Check for the limit. We remove some bytes from the maximum size, because we're
184				// only counting the size of the `WarpSyncFragment`s. The extra margin is here
185				// to leave room for rest of the data (the size of the `Vec` and the boolean).
186				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	/// Verifies the warp sync proof starting at the given set id and with the given authorities.
203	/// Verification stops when either the proof is exhausted or finality for the target header can
204	/// be proven. If the proof is valid the new set id and authorities is returned.
205	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, &current_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					// Only the last fragment of the last proof message is allowed to be missing the
241					// authority set change.
242					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
252/// Implements network API for warp sync.
253pub 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	/// Create a new instance for a given backend and authority set.
267	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
283/// Verifier state for GRANDPA warp sync.
284struct VerifierState<Block: BlockT> {
285	set_id: SetId,
286	authorities: AuthorityList,
287	next_proof_context: Block::Hash,
288}
289
290/// Verifier implementation for GRANDPA warp sync.
291struct 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		// Track eras synced
328		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			// we will trigger an authority set change every 10 blocks
429			if n != 0 && n % 10 == 0 {
430				// pick next authorities and add digest for the set change
431				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				// generate a justification for this block, finalize it and note the authority set
461				// change
462				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 &current_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		// generate a warp sync proof
502		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		// verifying the proof should yield the last set id and authorities
508		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}