referrerpolicy=no-referrer-when-downgrade

pallet_grandpa/
equivocation.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! An opt-in utility module for reporting equivocations.
19//!
20//! This module defines an offence type for GRANDPA equivocations
21//! and some utility traits to wire together:
22//! - a key ownership proof system (e.g. to prove that a given authority was
23//! part of a session);
24//! - a system for reporting offences;
25//! - a system for signing and submitting transactions;
26//! - a way to get the current block author;
27//!
28//! These can be used in an offchain context in order to submit equivocation
29//! reporting extrinsics (from the client that's running the GRANDPA protocol).
30//! And in a runtime context, so that the GRANDPA module can validate the
31//! equivocation proofs in the extrinsic and report the offences.
32//!
33//! IMPORTANT:
34//! When using this module for enabling equivocation reporting it is required
35//! that the `ValidateUnsigned` for the GRANDPA pallet is used in the runtime
36//! definition.
37
38use alloc::{boxed::Box, vec, vec::Vec};
39use codec::{self as codec, Decode, Encode};
40use frame_support::traits::{Get, KeyOwnerProofSystem};
41use frame_system::pallet_prelude::BlockNumberFor;
42use log::{error, info};
43use sp_consensus_grandpa::{AuthorityId, EquivocationProof, RoundNumber, SetId, KEY_TYPE};
44use sp_runtime::{
45	transaction_validity::{
46		InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
47		TransactionValidityError, ValidTransaction,
48	},
49	DispatchError, KeyTypeId, Perbill,
50};
51use sp_session::{GetSessionNumber, GetValidatorCount};
52use sp_staking::{
53	offence::{Kind, Offence, OffenceReportSystem, ReportOffence},
54	SessionIndex,
55};
56
57use super::{Call, Config, Error, Pallet, LOG_TARGET};
58
59/// A round number and set id which point on the time of an offence.
60#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)]
61pub struct TimeSlot {
62	// The order of these matters for `derive(Ord)`.
63	/// Grandpa Set ID.
64	pub set_id: SetId,
65	/// Round number.
66	pub round: RoundNumber,
67}
68
69/// GRANDPA equivocation offence report.
70pub struct EquivocationOffence<Offender> {
71	/// Time slot at which this incident happened.
72	pub time_slot: TimeSlot,
73	/// The session index in which the incident happened.
74	pub session_index: SessionIndex,
75	/// The size of the validator set at the time of the offence.
76	pub validator_set_count: u32,
77	/// The authority which produced this equivocation.
78	pub offender: Offender,
79}
80
81impl<Offender: Clone> Offence<Offender> for EquivocationOffence<Offender> {
82	const ID: Kind = *b"grandpa:equivoca";
83	type TimeSlot = TimeSlot;
84
85	fn offenders(&self) -> Vec<Offender> {
86		vec![self.offender.clone()]
87	}
88
89	fn session_index(&self) -> SessionIndex {
90		self.session_index
91	}
92
93	fn validator_set_count(&self) -> u32 {
94		self.validator_set_count
95	}
96
97	fn time_slot(&self) -> Self::TimeSlot {
98		self.time_slot
99	}
100
101	// The formula is min((3k / n)^2, 1)
102	// where k = offenders_number and n = validators_number
103	fn slash_fraction(&self, offenders_count: u32) -> Perbill {
104		// Perbill type domain is [0, 1] by definition
105		Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
106	}
107}
108
109/// GRANDPA equivocation offence report system.
110///
111/// This type implements `OffenceReportSystem` such that:
112/// - Equivocation reports are published on-chain as unsigned extrinsic via
113///   `offchain::CreateTransactionBase`.
114/// - On-chain validity checks and processing are mostly delegated to the user provided generic
115///   types implementing `KeyOwnerProofSystem` and `ReportOffence` traits.
116/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet.
117pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
118
119impl<T, R, P, L>
120	OffenceReportSystem<
121		Option<T::AccountId>,
122		(EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
123	> for EquivocationReportSystem<T, R, P, L>
124where
125	T: Config + pallet_authorship::Config + frame_system::offchain::CreateBare<Call<T>>,
126	R: ReportOffence<
127		T::AccountId,
128		P::IdentificationTuple,
129		EquivocationOffence<P::IdentificationTuple>,
130	>,
131	P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>,
132	P::IdentificationTuple: Clone,
133	L: Get<u64>,
134{
135	type Longevity = L;
136
137	fn publish_evidence(
138		evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
139	) -> Result<(), ()> {
140		use frame_system::offchain::SubmitTransaction;
141		let (equivocation_proof, key_owner_proof) = evidence;
142
143		let call = Call::report_equivocation_unsigned {
144			equivocation_proof: Box::new(equivocation_proof),
145			key_owner_proof,
146		};
147		let xt = T::create_bare(call.into());
148		let res = SubmitTransaction::<T, Call<T>>::submit_transaction(xt);
149		match res {
150			Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"),
151			Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
152		}
153		res
154	}
155
156	fn check_evidence(
157		evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
158	) -> Result<(), TransactionValidityError> {
159		let (equivocation_proof, key_owner_proof) = evidence;
160
161		// Check the membership proof to extract the offender's id
162		let key = (KEY_TYPE, equivocation_proof.offender().clone());
163		let offender = P::check_proof(key, key_owner_proof).ok_or(InvalidTransaction::BadProof)?;
164
165		// Check if the offence has already been reported, and if so then we can discard the report.
166		let time_slot =
167			TimeSlot { set_id: equivocation_proof.set_id(), round: equivocation_proof.round() };
168		if R::is_known_offence(&[offender], &time_slot) {
169			Err(InvalidTransaction::Stale.into())
170		} else {
171			Ok(())
172		}
173	}
174
175	fn process_evidence(
176		reporter: Option<T::AccountId>,
177		evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
178	) -> Result<(), DispatchError> {
179		let (equivocation_proof, key_owner_proof) = evidence;
180		let reporter = reporter.or_else(|| pallet_authorship::Pallet::<T>::author());
181		let offender = equivocation_proof.offender().clone();
182
183		// We check the equivocation within the context of its set id (and
184		// associated session) and round. We also need to know the validator
185		// set count when the offence since it is required to calculate the
186		// slash amount.
187		let set_id = equivocation_proof.set_id();
188		let round = equivocation_proof.round();
189		let session_index = key_owner_proof.session();
190		let validator_set_count = key_owner_proof.validator_count();
191
192		// Validate equivocation proof (check votes are different and signatures are valid).
193		if !sp_consensus_grandpa::check_equivocation_proof(equivocation_proof) {
194			return Err(Error::<T>::InvalidEquivocationProof.into())
195		}
196
197		// Validate the key ownership proof extracting the id of the offender.
198		let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof)
199			.ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
200
201		// Fetch the current and previous sets last session index.
202		// For genesis set there's no previous set.
203		let previous_set_id_session_index = if set_id != 0 {
204			let idx = crate::SetIdSession::<T>::get(set_id - 1)
205				.ok_or(Error::<T>::InvalidEquivocationProof)?;
206			Some(idx)
207		} else {
208			None
209		};
210
211		let set_id_session_index =
212			crate::SetIdSession::<T>::get(set_id).ok_or(Error::<T>::InvalidEquivocationProof)?;
213
214		// Check that the session id for the membership proof is within the
215		// bounds of the set id reported in the equivocation.
216		if session_index > set_id_session_index ||
217			previous_set_id_session_index
218				.map(|previous_index| session_index <= previous_index)
219				.unwrap_or(false)
220		{
221			return Err(Error::<T>::InvalidEquivocationProof.into())
222		}
223
224		let offence = EquivocationOffence {
225			time_slot: TimeSlot { set_id, round },
226			session_index,
227			offender,
228			validator_set_count,
229		};
230
231		R::report_offence(reporter.into_iter().collect(), offence)
232			.map_err(|_| Error::<T>::DuplicateOffenceReport)?;
233
234		Ok(())
235	}
236}
237
238/// Methods for the `ValidateUnsigned` implementation:
239/// It restricts calls to `report_equivocation_unsigned` to local calls (i.e. extrinsics generated
240/// on this node) or that already in a block. This guarantees that only block authors can include
241/// unsigned equivocation reports.
242impl<T: Config> Pallet<T> {
243	pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
244		if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
245			// discard equivocation report not coming from the local node
246			match source {
247				TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
248				_ => {
249					log::warn!(
250						target: LOG_TARGET,
251						"rejecting unsigned report equivocation transaction because it is not local/in-block."
252					);
253
254					return InvalidTransaction::Call.into()
255				},
256			}
257
258			// Check report validity
259			let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
260			T::EquivocationReportSystem::check_evidence(evidence)?;
261
262			let longevity =
263				<T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
264
265			ValidTransaction::with_tag_prefix("GrandpaEquivocation")
266				// We assign the maximum priority for any equivocation report.
267				.priority(TransactionPriority::max_value())
268				// Only one equivocation report for the same offender at the same slot.
269				.and_provides((
270					equivocation_proof.offender().clone(),
271					equivocation_proof.set_id(),
272					equivocation_proof.round(),
273				))
274				.longevity(longevity)
275				// We don't propagate this. This can never be included on a remote node.
276				.propagate(false)
277				.build()
278		} else {
279			InvalidTransaction::Call.into()
280		}
281	}
282
283	pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
284		if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
285			let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
286			T::EquivocationReportSystem::check_evidence(evidence)
287		} else {
288			Err(InvalidTransaction::Call.into())
289		}
290	}
291}