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::{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/// Warp proof processing error.
39#[derive(Debug, thiserror::Error)]
40pub enum Error {
41	/// Decoding error.
42	#[error("Failed to decode block hash: {0}.")]
43	DecodeScale(#[from] codec::Error),
44	/// Client backend error.
45	#[error("{0}")]
46	Client(#[from] sp_blockchain::Error),
47	/// Invalid request data.
48	#[error("{0}")]
49	InvalidRequest(String),
50	/// Invalid warp proof.
51	#[error("{0}")]
52	InvalidProof(String),
53	/// Missing header or authority set change data.
54	#[error("Missing required data to be able to answer request.")]
55	MissingData,
56}
57
58/// The maximum size in bytes of the `WarpSyncProof`.
59pub(super) const MAX_WARP_SYNC_PROOF_SIZE: usize = 8 * 1024 * 1024;
60
61/// A proof of an authority set change.
62#[derive(Decode, Encode, Debug)]
63pub struct WarpSyncFragment<Block: BlockT> {
64	/// The last block that the given authority set finalized. This block should contain a digest
65	/// signaling an authority set change from which we can fetch the next authority set.
66	pub header: Block::Header,
67	/// A justification for the header above which proves its finality. In order to validate it the
68	/// verifier must be aware of the authorities and set id for which the justification refers to.
69	pub justification: GrandpaJustification<Block>,
70}
71
72/// An accumulated proof of multiple authority set changes.
73#[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	/// Generates a warp sync proof starting at the given block. It will generate authority set
81	/// change proofs for all changes that happened from `begin` until the current authority set
82	/// (capped by MAX_WARP_SYNC_PROOF_SIZE).
83	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		// TODO: cache best response (i.e. the one with lowest begin_number)
92		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			// the last block in a set is the one that triggers a change to the next set,
129			// therefore the block must have a digest that signals the authority set change
130			if find_scheduled_change::<Block>(&header).is_none() {
131				// if it doesn't contain a signal for standard change then the set must have changed
132				// through a forced changed, in which case we stop collecting proofs as the chain of
133				// trust in authority handoffs was broken.
134				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			// Check for the limit. We remove some bytes from the maximum size, because we're only
148			// counting the size of the `WarpSyncFragment`s. The extra margin is here to leave
149			// room for rest of the data (the size of the `Vec` and the boolean).
150			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				// the existing best justification must be for a block higher than the
164				// last authority set change. if we didn't prove any authority set
165				// change then we fallback to make sure it's higher or equal to the
166				// initial warp sync block.
167				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				// Check for the limit. We remove some bytes from the maximum size, because we're
182				// only counting the size of the `WarpSyncFragment`s. The extra margin is here
183				// to leave room for rest of the data (the size of the `Vec` and the boolean).
184				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	/// Verifies the warp sync proof starting at the given set id and with the given authorities.
201	/// Verification stops when either the proof is exhausted or finality for the target header can
202	/// be proven. If the proof is valid the new set id and authorities is returned.
203	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, &current_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					// Only the last fragment of the last proof message is allowed to be missing the
239					// authority set change.
240					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
250/// Implements network API for warp sync.
251pub 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	/// Create a new instance for a given backend and authority set.
265	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			// we will trigger an authority set change every 10 blocks
386			if n != 0 && n % 10 == 0 {
387				// pick next authorities and add digest for the set change
388				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				// generate a justification for this block, finalize it and note the authority set
418				// change
419				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 &current_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		// generate a warp sync proof
459		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		// verifying the proof should yield the last set id and authorities
465		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}