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 BABE equivocations
21
//! and some utility traits to wire together:
22
//! - a system for reporting offences;
23
//! - a system for submitting unsigned transactions;
24
//! - a way to get the current block author;
25
//!
26
//! These can be used in an offchain context in order to submit equivocation
27
//! reporting extrinsics (from the client that's import BABE blocks).
28
//! And in a runtime context, so that the BABE pallet can validate the
29
//! equivocation proofs in the extrinsic and report the offences.
30
//!
31
//! IMPORTANT:
32
//! When using this module for enabling equivocation reporting it is required
33
//! that the `ValidateUnsigned` for the BABE pallet is used in the runtime
34
//! definition.
35

            
36
use alloc::{boxed::Box, vec, vec::Vec};
37
use frame_support::traits::{Get, KeyOwnerProofSystem};
38
use frame_system::pallet_prelude::HeaderFor;
39
use log::{error, info};
40

            
41
use sp_consensus_babe::{AuthorityId, EquivocationProof, Slot, KEY_TYPE};
42
use sp_runtime::{
43
	transaction_validity::{
44
		InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
45
		TransactionValidityError, ValidTransaction,
46
	},
47
	DispatchError, KeyTypeId, Perbill,
48
};
49
use sp_session::{GetSessionNumber, GetValidatorCount};
50
use sp_staking::{
51
	offence::{Kind, Offence, OffenceReportSystem, ReportOffence},
52
	SessionIndex,
53
};
54

            
55
use crate::{Call, Config, Error, Pallet, LOG_TARGET};
56

            
57
/// BABE equivocation offence report.
58
///
59
/// When a validator released two or more blocks at the same slot.
60
pub struct EquivocationOffence<Offender> {
61
	/// A babe slot in which this incident happened.
62
	pub slot: Slot,
63
	/// The session index in which the incident happened.
64
	pub session_index: SessionIndex,
65
	/// The size of the validator set at the time of the offence.
66
	pub validator_set_count: u32,
67
	/// The authority that produced the equivocation.
68
	pub offender: Offender,
69
}
70

            
71
impl<Offender: Clone> Offence<Offender> for EquivocationOffence<Offender> {
72
	const ID: Kind = *b"babe:equivocatio";
73
	type TimeSlot = Slot;
74

            
75
	fn offenders(&self) -> Vec<Offender> {
76
		vec![self.offender.clone()]
77
	}
78

            
79
	fn session_index(&self) -> SessionIndex {
80
		self.session_index
81
	}
82

            
83
	fn validator_set_count(&self) -> u32 {
84
		self.validator_set_count
85
	}
86

            
87
	fn time_slot(&self) -> Self::TimeSlot {
88
		self.slot
89
	}
90

            
91
	// The formula is min((3k / n)^2, 1)
92
	// where k = offenders_number and n = validators_number
93
	fn slash_fraction(&self, offenders_count: u32) -> Perbill {
94
		// Perbill type domain is [0, 1] by definition
95
		Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
96
	}
97
}
98

            
99
/// BABE equivocation offence report system.
100
///
101
/// This type implements `OffenceReportSystem` such that:
102
/// - Equivocation reports are published on-chain as unsigned extrinsic via
103
///   `offchain::SendTransactionTypes`.
104
/// - On-chain validity checks and processing are mostly delegated to the user provided generic
105
///   types implementing `KeyOwnerProofSystem` and `ReportOffence` traits.
106
/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet.
107
pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
108

            
109
impl<T, R, P, L>
110
	OffenceReportSystem<Option<T::AccountId>, (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof)>
111
	for EquivocationReportSystem<T, R, P, L>
112
where
113
	T: Config + pallet_authorship::Config + frame_system::offchain::SendTransactionTypes<Call<T>>,
114
	R: ReportOffence<
115
		T::AccountId,
116
		P::IdentificationTuple,
117
		EquivocationOffence<P::IdentificationTuple>,
118
	>,
119
	P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>,
120
	P::IdentificationTuple: Clone,
121
	L: Get<u64>,
122
{
123
	type Longevity = L;
124

            
125
	fn publish_evidence(
126
		evidence: (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof),
127
	) -> Result<(), ()> {
128
		use frame_system::offchain::SubmitTransaction;
129
		let (equivocation_proof, key_owner_proof) = evidence;
130

            
131
		let call = Call::report_equivocation_unsigned {
132
			equivocation_proof: Box::new(equivocation_proof),
133
			key_owner_proof,
134
		};
135
		let res = SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into());
136
		match res {
137
			Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"),
138
			Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
139
		}
140
		res
141
	}
142

            
143
	fn check_evidence(
144
		evidence: (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof),
145
	) -> Result<(), TransactionValidityError> {
146
		let (equivocation_proof, key_owner_proof) = evidence;
147

            
148
		// Check the membership proof to extract the offender's id
149
		let key = (sp_consensus_babe::KEY_TYPE, equivocation_proof.offender.clone());
150
		let offender =
151
			P::check_proof(key, key_owner_proof.clone()).ok_or(InvalidTransaction::BadProof)?;
152

            
153
		// Check if the offence has already been reported, and if so then we can discard the report.
154
		if R::is_known_offence(&[offender], &equivocation_proof.slot) {
155
			Err(InvalidTransaction::Stale.into())
156
		} else {
157
			Ok(())
158
		}
159
	}
160

            
161
747
	fn process_evidence(
162
747
		reporter: Option<T::AccountId>,
163
747
		evidence: (EquivocationProof<HeaderFor<T>>, T::KeyOwnerProof),
164
747
	) -> Result<(), DispatchError> {
165
747
		let (equivocation_proof, key_owner_proof) = evidence;
166
747
		let reporter = reporter.or_else(|| <pallet_authorship::Pallet<T>>::author());
167
747
		let offender = equivocation_proof.offender.clone();
168
747
		let slot = equivocation_proof.slot;
169
747

            
170
747
		// Validate the equivocation proof (check votes are different and signatures are valid)
171
747
		if !sp_consensus_babe::check_equivocation_proof(equivocation_proof) {
172
747
			return Err(Error::<T>::InvalidEquivocationProof.into())
173
		}
174

            
175
		let validator_set_count = key_owner_proof.validator_count();
176
		let session_index = key_owner_proof.session();
177

            
178
		let epoch_index =
179
			*slot.saturating_sub(crate::GenesisSlot::<T>::get()) / T::EpochDuration::get();
180

            
181
		// Check that the slot number is consistent with the session index
182
		// in the key ownership proof (i.e. slot is for that epoch)
183
		if Pallet::<T>::session_index_for_epoch(epoch_index) != session_index {
184
			return Err(Error::<T>::InvalidKeyOwnershipProof.into())
185
		}
186

            
187
		// Check the membership proof and extract the offender's id
188
		let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof)
189
			.ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
190

            
191
		let offence = EquivocationOffence { slot, validator_set_count, offender, session_index };
192

            
193
		R::report_offence(reporter.into_iter().collect(), offence)
194
			.map_err(|_| Error::<T>::DuplicateOffenceReport)?;
195

            
196
		Ok(())
197
747
	}
198
}
199

            
200
/// Methods for the `ValidateUnsigned` implementation:
201
/// It restricts calls to `report_equivocation_unsigned` to local calls (i.e. extrinsics generated
202
/// on this node) or that already in a block. This guarantees that only block authors can include
203
/// unsigned equivocation reports.
204
impl<T: Config> Pallet<T> {
205
	pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
206
		if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
207
			// discard equivocation report not coming from the local node
208
			match source {
209
				TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
210
				_ => {
211
					log::warn!(
212
						target: LOG_TARGET,
213
						"rejecting unsigned report equivocation transaction because it is not local/in-block.",
214
					);
215

            
216
					return InvalidTransaction::Call.into()
217
				},
218
			}
219

            
220
			// Check report validity
221
			let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
222
			T::EquivocationReportSystem::check_evidence(evidence)?;
223

            
224
			let longevity =
225
				<T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
226

            
227
			ValidTransaction::with_tag_prefix("BabeEquivocation")
228
				// We assign the maximum priority for any equivocation report.
229
				.priority(TransactionPriority::max_value())
230
				// Only one equivocation report for the same offender at the same slot.
231
				.and_provides((equivocation_proof.offender.clone(), *equivocation_proof.slot))
232
				.longevity(longevity)
233
				// We don't propagate this. This can never be included on a remote node.
234
				.propagate(false)
235
				.build()
236
		} else {
237
			InvalidTransaction::Call.into()
238
		}
239
	}
240

            
241
	pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
242
		if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
243
			let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
244
			T::EquivocationReportSystem::check_evidence(evidence)
245
		} else {
246
			Err(InvalidTransaction::Call.into())
247
		}
248
	}
249
}