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 and validate the commit and the votes'
129	/// ancestry proofs finalize the given block.
130	pub fn decode_and_verify_finalizes(
131		encoded: &[u8],
132		finalized_target: (Block::Hash, NumberFor<Block>),
133		set_id: u64,
134		voters: &VoterSet<AuthorityId>,
135	) -> Result<Self, ClientError>
136	where
137		NumberFor<Block>: finality_grandpa::BlockNumberOps,
138	{
139		let justification = GrandpaJustification::<Block>::decode_all(&mut &*encoded)
140			.map_err(|_| ClientError::JustificationDecode)?;
141
142		if (
143			justification.justification.commit.target_hash,
144			justification.justification.commit.target_number,
145		) != finalized_target
146		{
147			let msg = "invalid commit target in grandpa justification".to_string();
148			Err(ClientError::BadJustification(msg))
149		} else {
150			justification.verify_with_voter_set(set_id, voters).map(|_| justification)
151		}
152	}
153
154	/// Validate the commit and the votes' ancestry proofs.
155	pub fn verify(&self, set_id: u64, authorities: &AuthorityList) -> Result<(), ClientError>
156	where
157		NumberFor<Block>: finality_grandpa::BlockNumberOps,
158	{
159		let voters = VoterSet::new(authorities.iter().cloned())
160			.ok_or(ClientError::Consensus(sp_consensus::Error::InvalidAuthoritiesSet))?;
161
162		self.verify_with_voter_set(set_id, &voters)
163	}
164
165	/// Validate the commit and the votes' ancestry proofs.
166	pub(crate) fn verify_with_voter_set(
167		&self,
168		set_id: u64,
169		voters: &VoterSet<AuthorityId>,
170	) -> Result<(), ClientError>
171	where
172		NumberFor<Block>: finality_grandpa::BlockNumberOps,
173	{
174		use finality_grandpa::Chain;
175
176		let ancestry_chain = AncestryChain::<Block>::new(&self.justification.votes_ancestries);
177
178		match finality_grandpa::validate_commit(&self.justification.commit, voters, &ancestry_chain)
179		{
180			Ok(ref result) if result.is_valid() => {},
181			_ => {
182				let msg = "invalid commit in grandpa justification".to_string();
183				return Err(ClientError::BadJustification(msg))
184			},
185		}
186
187		// we pick the precommit for the lowest block as the base that
188		// should serve as the root block for populating ancestry (i.e.
189		// collect all headers from all precommit blocks to the base)
190		let base_hash = self
191			.justification
192			.commit
193			.precommits
194			.iter()
195			.map(|signed| &signed.precommit)
196			.min_by_key(|precommit| precommit.target_number)
197			.map(|precommit| precommit.target_hash)
198			.expect(
199				"can only fail if precommits is empty; \
200				 commit has been validated above; \
201				 valid commits must include precommits; \
202				 qed.",
203			);
204
205		let mut buf = Vec::new();
206		let mut visited_hashes = HashSet::new();
207		for signed in self.justification.commit.precommits.iter() {
208			let signature_result = sp_consensus_grandpa::check_message_signature_with_buffer(
209				&finality_grandpa::Message::Precommit(signed.precommit.clone()),
210				&signed.id,
211				&signed.signature,
212				self.justification.round,
213				set_id,
214				&mut buf,
215			);
216			match signature_result {
217				sp_consensus_grandpa::SignatureResult::Invalid =>
218					return Err(ClientError::BadJustification(
219						"invalid signature for precommit in grandpa justification".to_string(),
220					)),
221				sp_consensus_grandpa::SignatureResult::OutdatedSet =>
222					return Err(ClientError::OutdatedJustification),
223				sp_consensus_grandpa::SignatureResult::Valid => {},
224			}
225
226			if base_hash == signed.precommit.target_hash {
227				continue
228			}
229
230			match ancestry_chain.ancestry(base_hash, signed.precommit.target_hash) {
231				Ok(route) => {
232					// ancestry starts from parent hash but the precommit target hash has been
233					// visited
234					visited_hashes.insert(signed.precommit.target_hash);
235					for hash in route {
236						visited_hashes.insert(hash);
237					}
238				},
239				_ =>
240					return Err(ClientError::BadJustification(
241						"invalid precommit ancestry proof in grandpa justification".to_string(),
242					)),
243			}
244		}
245
246		let ancestry_hashes: HashSet<_> = self
247			.justification
248			.votes_ancestries
249			.iter()
250			.map(|h: &Block::Header| h.hash())
251			.collect();
252
253		if visited_hashes != ancestry_hashes {
254			return Err(ClientError::BadJustification(
255				"invalid precommit ancestries in grandpa justification with unused headers"
256					.to_string(),
257			))
258		}
259
260		Ok(())
261	}
262
263	/// The target block number and hash that this justifications proves finality for.
264	pub fn target(&self) -> (NumberFor<Block>, Block::Hash) {
265		(self.justification.commit.target_number, self.justification.commit.target_hash)
266	}
267}
268
269/// A utility trait implementing `finality_grandpa::Chain` using a given set of headers.
270/// This is useful when validating commits, using the given set of headers to
271/// verify a valid ancestry route to the target commit block.
272struct AncestryChain<Block: BlockT> {
273	ancestry: HashMap<Block::Hash, Block::Header>,
274}
275
276impl<Block: BlockT> AncestryChain<Block> {
277	fn new(ancestry: &[Block::Header]) -> AncestryChain<Block> {
278		let ancestry: HashMap<_, _> =
279			ancestry.iter().cloned().map(|h: Block::Header| (h.hash(), h)).collect();
280
281		AncestryChain { ancestry }
282	}
283}
284
285impl<Block: BlockT> finality_grandpa::Chain<Block::Hash, NumberFor<Block>> for AncestryChain<Block>
286where
287	NumberFor<Block>: finality_grandpa::BlockNumberOps,
288{
289	fn ancestry(
290		&self,
291		base: Block::Hash,
292		block: Block::Hash,
293	) -> Result<Vec<Block::Hash>, GrandpaError> {
294		let mut route = Vec::new();
295		let mut current_hash = block;
296		loop {
297			if current_hash == base {
298				break
299			}
300			match self.ancestry.get(&current_hash) {
301				Some(current_header) => {
302					current_hash = *current_header.parent_hash();
303					route.push(current_hash);
304				},
305				_ => return Err(GrandpaError::NotDescendent),
306			}
307		}
308		route.pop(); // remove the base
309
310		Ok(route)
311	}
312}