referrerpolicy=no-referrer-when-downgrade

sc_consensus_grandpa/
justification.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
19use std::{
20	collections::{HashMap, HashSet},
21	marker::PhantomData,
22	sync::Arc,
23};
24
25use codec::{Decode, DecodeAll, Encode};
26use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError};
27use sp_blockchain::{Error as ClientError, HeaderBackend};
28use sp_consensus_grandpa::AuthorityId;
29use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
30
31use crate::{AuthorityList, Commit, Error};
32
33/// A GRANDPA justification for block finality, it includes a commit message and
34/// an ancestry proof including all headers routing all precommit target blocks
35/// to the commit target block. Due to the current voting strategy the precommit
36/// targets should be the same as the commit target, since honest voters don't
37/// vote past authority set change blocks.
38///
39/// This is meant to be stored in the db and passed around the network to other
40/// nodes, and are used by syncing nodes to prove authority set handoffs.
41#[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)]
42pub struct GrandpaJustification<Block: BlockT> {
43	/// The GRANDPA justification for block finality.
44	pub justification: sp_consensus_grandpa::GrandpaJustification<Block::Header>,
45	_block: PhantomData<Block>,
46}
47
48impl<Block: BlockT> From<sp_consensus_grandpa::GrandpaJustification<Block::Header>>
49	for GrandpaJustification<Block>
50{
51	fn from(justification: sp_consensus_grandpa::GrandpaJustification<Block::Header>) -> Self {
52		Self { justification, _block: Default::default() }
53	}
54}
55
56impl<Block: BlockT> Into<sp_consensus_grandpa::GrandpaJustification<Block::Header>>
57	for GrandpaJustification<Block>
58{
59	fn into(self) -> sp_consensus_grandpa::GrandpaJustification<Block::Header> {
60		self.justification
61	}
62}
63
64impl<Block: BlockT> GrandpaJustification<Block> {
65	/// Create a GRANDPA justification from the given commit. This method
66	/// assumes the commit is valid and well-formed.
67	pub fn from_commit<C>(
68		client: &Arc<C>,
69		round: u64,
70		commit: Commit<Block::Header>,
71	) -> Result<Self, Error>
72	where
73		C: HeaderBackend<Block>,
74	{
75		let mut votes_ancestries_hashes = HashSet::new();
76		let mut votes_ancestries = Vec::new();
77
78		let error = || {
79			let msg = "invalid precommits for target commit".to_string();
80			Err(Error::Client(ClientError::BadJustification(msg)))
81		};
82
83		// we pick the precommit for the lowest block as the base that
84		// should serve as the root block for populating ancestry (i.e.
85		// collect all headers from all precommit blocks to the base)
86		let (base_hash, base_number) = match commit
87			.precommits
88			.iter()
89			.map(|signed| &signed.precommit)
90			.min_by_key(|precommit| precommit.target_number)
91			.map(|precommit| (precommit.target_hash, precommit.target_number))
92		{
93			None => return error(),
94			Some(base) => base,
95		};
96
97		for signed in commit.precommits.iter() {
98			let mut current_hash = signed.precommit.target_hash;
99			loop {
100				if current_hash == base_hash {
101					break;
102				}
103
104				match client.header(current_hash)? {
105					Some(current_header) => {
106						// NOTE: this should never happen as we pick the lowest block
107						// as base and only traverse backwards from the other blocks
108						// in the commit. but better be safe to avoid an unbound loop.
109						if *current_header.number() <= base_number {
110							return error();
111						}
112
113						let parent_hash = *current_header.parent_hash();
114						if votes_ancestries_hashes.insert(current_hash) {
115							votes_ancestries.push(current_header);
116						}
117
118						current_hash = parent_hash;
119					},
120					_ => return error(),
121				}
122			}
123		}
124
125		Ok(sp_consensus_grandpa::GrandpaJustification { round, commit, votes_ancestries }.into())
126	}
127
128	/// Decode a GRANDPA justification from its SCALE encoding.
129	pub fn decode(encoded: &[u8]) -> Result<Self, ClientError> {
130		GrandpaJustification::<Block>::decode_all(&mut &*encoded)
131			.map_err(|_| ClientError::JustificationDecode)
132	}
133
134	/// Validate that this justification finalizes the given block and that its
135	/// commit and ancestry proofs are valid for the given voter set.
136	pub fn verify_finalizes(
137		&self,
138		finalized_target: (Block::Hash, NumberFor<Block>),
139		set_id: u64,
140		voters: &VoterSet<AuthorityId>,
141	) -> Result<(), ClientError>
142	where
143		NumberFor<Block>: finality_grandpa::BlockNumberOps,
144	{
145		if (self.justification.commit.target_hash, self.justification.commit.target_number) !=
146			finalized_target
147		{
148			let msg = "invalid commit target in grandpa justification".to_string();
149			return Err(ClientError::BadJustification(msg));
150		}
151
152		self.verify_with_voter_set(set_id, voters)
153	}
154
155	/// Decode a GRANDPA justification and validate the commit and the votes'
156	/// ancestry proofs finalize the given block.
157	pub fn decode_and_verify_finalizes(
158		encoded: &[u8],
159		finalized_target: (Block::Hash, NumberFor<Block>),
160		set_id: u64,
161		voters: &VoterSet<AuthorityId>,
162	) -> Result<Self, ClientError>
163	where
164		NumberFor<Block>: finality_grandpa::BlockNumberOps,
165	{
166		let justification = Self::decode(encoded)?;
167		justification.verify_finalizes(finalized_target, set_id, voters)?;
168		Ok(justification)
169	}
170
171	/// Validate the commit and the votes' ancestry proofs.
172	pub fn verify(&self, set_id: u64, authorities: &AuthorityList) -> Result<(), ClientError>
173	where
174		NumberFor<Block>: finality_grandpa::BlockNumberOps,
175	{
176		let voters = VoterSet::new(authorities.iter().cloned())
177			.ok_or(ClientError::Consensus(sp_consensus::Error::InvalidAuthoritiesSet))?;
178
179		self.verify_with_voter_set(set_id, &voters)
180	}
181
182	/// Validate the commit and the votes' ancestry proofs.
183	pub(crate) fn verify_with_voter_set(
184		&self,
185		set_id: u64,
186		voters: &VoterSet<AuthorityId>,
187	) -> Result<(), ClientError>
188	where
189		NumberFor<Block>: finality_grandpa::BlockNumberOps,
190	{
191		use finality_grandpa::Chain;
192
193		let ancestry_chain = AncestryChain::<Block>::new(&self.justification.votes_ancestries);
194
195		match finality_grandpa::validate_commit(&self.justification.commit, voters, &ancestry_chain)
196		{
197			Ok(ref result) if result.is_valid() => {},
198			_ => {
199				let msg = "invalid commit in grandpa justification".to_string();
200				return Err(ClientError::BadJustification(msg));
201			},
202		}
203
204		// we pick the precommit for the lowest block as the base that
205		// should serve as the root block for populating ancestry (i.e.
206		// collect all headers from all precommit blocks to the base)
207		let base_hash = self
208			.justification
209			.commit
210			.precommits
211			.iter()
212			.map(|signed| &signed.precommit)
213			.min_by_key(|precommit| precommit.target_number)
214			.map(|precommit| precommit.target_hash)
215			.expect(
216				"can only fail if precommits is empty; \
217				 commit has been validated above; \
218				 valid commits must include precommits; \
219				 qed.",
220			);
221
222		let mut buf = Vec::new();
223		let mut visited_hashes = HashSet::new();
224		for signed in self.justification.commit.precommits.iter() {
225			let signature_result = sp_consensus_grandpa::check_message_signature_with_buffer(
226				&finality_grandpa::Message::Precommit(signed.precommit.clone()),
227				&signed.id,
228				&signed.signature,
229				self.justification.round,
230				set_id,
231				&mut buf,
232			);
233			match signature_result {
234				sp_consensus_grandpa::SignatureResult::Invalid => {
235					return Err(ClientError::BadJustification(
236						"invalid signature for precommit in grandpa justification".to_string(),
237					))
238				},
239				sp_consensus_grandpa::SignatureResult::OutdatedSet => {
240					return Err(ClientError::OutdatedJustification)
241				},
242				sp_consensus_grandpa::SignatureResult::Valid => {},
243			}
244
245			if base_hash == signed.precommit.target_hash {
246				continue;
247			}
248
249			match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) {
250				Ok(route) => {
251					// ancestry starts from parent hash but the precommit target hash has been
252					// visited
253					visited_hashes.insert(signed.precommit.target_hash);
254					for hash in route {
255						visited_hashes.insert(hash);
256					}
257				},
258				_ => {
259					return Err(ClientError::BadJustification(
260						"invalid precommit ancestry proof in grandpa justification".to_string(),
261					))
262				},
263			}
264		}
265
266		let ancestry_hashes: HashSet<_> = self
267			.justification
268			.votes_ancestries
269			.iter()
270			.map(|h: &Block::Header| h.hash())
271			.collect();
272
273		if visited_hashes != ancestry_hashes {
274			return Err(ClientError::BadJustification(
275				"invalid precommit ancestries in grandpa justification with unused headers"
276					.to_string(),
277			));
278		}
279
280		Ok(())
281	}
282
283	/// The target block number and hash that this justifications proves finality for.
284	pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
285		(self.justification.commit.target_number, self.justification.commit.target_hash)
286	}
287}
288
289/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers.
290/// This is useful when validating commits, using the given set of headers to
291/// verify a valid ancestry route to the target commit block.
292struct AncestryChain<Block: BlockT> {
293	ancestry: HashMap<Block::Hash, Block::Header>,
294}
295
296impl<Block: BlockT> AncestryChain<Block> {
297	fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
298		let ancestry: HashMap<_, _> =
299			ancestry.iter().cloned().map(|h: Block::Header| (h.hash(), h)).collect();
300
301		AncestryChain { ancestry }
302	}
303}
304
305impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
306where
307	NumberFor<Block>: finality_grandpa::BlockNumberOps,
308{
309	fn ancestry(
310		&self,
311		base: Block::Hash,
312		block: Block::Hash,
313	) -> Result<Vec<Block::Hash>, GrandpaError> {
314		let mut route = Vec::new();
315		let mut current_hash = block;
316		loop {
317			if current_hash == base {
318				break;
319			}
320			match self.ancestry.get(&current_hash) {
321				Some(current_header) => {
322					current_hash = *current_header.parent_hash();
323					route.push(current_hash);
324				},
325				_ => return Err(GrandpaError::NotDescendent),
326			}
327		}
328		route.pop(); // remove the base
329
330		Ok(route)
331	}
332}