// This file is part of Substrate. // Copyright (C) 2017-2020 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! //! An opt-in utility module for reporting equivocations. //! //! This module defines an offence type for GRANDPA equivocations //! and some utility traits to wire together: //! - a key ownership proof system (e.g. to prove that a given authority was //! part of a session); //! - a system for reporting offences; //! - a system for signing and submitting transactions; //! //! These can be used in an offchain context in order to submit equivocation //! reporting extrinsics (from the client that's running the GRANDPA protocol). //! And in a runtime context, so that the GRANDPA module can validate the //! equivocation proofs in the extrinsic and report the offences. //! //! IMPORTANT: //! When using this module for enabling equivocation reporting it is required //! that the `ValidateEquivocationReport` signed extension is used in the runtime //! definition. Failure to do so will allow invalid equivocation reports to be //! accepted by the runtime. //! use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; use frame_support::{debug, dispatch::IsSubType, traits::KeyOwnerProofSystem}; use frame_system::offchain::{AppCrypto, CreateSignedTransaction, Signer}; use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; use sp_runtime::{ traits::{DispatchInfoOf, SignedExtension}, transaction_validity::{ InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, }, DispatchResult, Perbill, }; use sp_staking::{ offence::{Kind, Offence, OffenceError, ReportOffence}, SessionIndex, }; /// Ensure that equivocation reports are only processed if valid. #[derive(Encode, Decode, Clone, Eq, PartialEq)] pub struct ValidateEquivocationReport(sp_std::marker::PhantomData); impl Default for ValidateEquivocationReport { fn default() -> ValidateEquivocationReport { ValidateEquivocationReport::new() } } impl ValidateEquivocationReport { pub fn new() -> ValidateEquivocationReport { ValidateEquivocationReport(Default::default()) } } impl sp_std::fmt::Debug for ValidateEquivocationReport { fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { write!(f, "ValidateEquivocationReport") } } /// Custom validity error used when validating equivocation reports. #[derive(Debug)] #[repr(u8)] pub enum ReportEquivocationValidityError { /// The proof provided in the report is not valid. InvalidEquivocationProof = 1, /// The proof provided in the report is not valid. InvalidKeyOwnershipProof = 2, /// The set id provided in the report is not valid. InvalidSetId = 3, /// The session index provided in the report is not valid. InvalidSession = 4, } impl From for TransactionValidityError { fn from(e: ReportEquivocationValidityError) -> TransactionValidityError { TransactionValidityError::from(InvalidTransaction::Custom(e as u8)) } } impl SignedExtension for ValidateEquivocationReport where ::Call: IsSubType, T>, { const IDENTIFIER: &'static str = "ValidateEquivocationReport"; type AccountId = T::AccountId; type Call = ::Call; type AdditionalSigned = (); type Pre = (); fn additional_signed( &self, ) -> sp_std::result::Result { Ok(()) } fn validate( &self, _who: &Self::AccountId, call: &Self::Call, _info: &DispatchInfoOf, _len: usize, ) -> TransactionValidity { let (equivocation_proof, key_owner_proof) = match call.is_sub_type() { Some(super::Call::report_equivocation(equivocation_proof, key_owner_proof)) => { (equivocation_proof, key_owner_proof) } _ => return Ok(ValidTransaction::default()), }; // validate the key ownership proof extracting the id of the offender. if let None = T::KeyOwnerProofSystem::check_proof( ( sp_finality_grandpa::KEY_TYPE, equivocation_proof.offender().clone(), ), key_owner_proof.clone(), ) { return Err(ReportEquivocationValidityError::InvalidKeyOwnershipProof.into()); } // we check the equivocation within the context of its set id (and // associated session). let set_id = equivocation_proof.set_id(); let session_index = key_owner_proof.session(); // validate equivocation proof (check votes are different and // signatures are valid). if let Err(_) = sp_finality_grandpa::check_equivocation_proof(equivocation_proof.clone()) { return Err(ReportEquivocationValidityError::InvalidEquivocationProof.into()); } // fetch the current and previous sets last session index. on the // genesis set there's no previous set. let previous_set_id_session_index = if set_id == 0 { None } else { let session_index = if let Some(session_id) = >::session_for_set(set_id - 1) { session_id } else { return Err(ReportEquivocationValidityError::InvalidSetId.into()); }; Some(session_index) }; let set_id_session_index = if let Some(session_id) = >::session_for_set(set_id) { session_id } else { return Err(ReportEquivocationValidityError::InvalidSetId.into()); }; // check that the session id for the membership proof is within the // bounds of the set id reported in the equivocation. if session_index > set_id_session_index || previous_set_id_session_index .map(|previous_index| session_index <= previous_index) .unwrap_or(false) { return Err(ReportEquivocationValidityError::InvalidSession.into()); } Ok(ValidTransaction::default()) } } /// A trait with utility methods for handling equivocation reports in GRANDPA. /// The offence type is generic, and the trait provides , reporting an offence /// triggered by a valid equivocation report, and also for creating and /// submitting equivocation report extrinsics (useful only in offchain context). pub trait HandleEquivocation { /// The offence type used for reporting offences on valid equivocation reports. type Offence: GrandpaOffence; /// Report an offence proved by the given reporters. fn report_offence( reporters: Vec, offence: Self::Offence, ) -> Result<(), OffenceError>; /// Create and dispatch an equivocation report extrinsic. fn submit_equivocation_report( equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult; } impl HandleEquivocation for () { type Offence = GrandpaEquivocationOffence; fn report_offence( _reporters: Vec, _offence: GrandpaEquivocationOffence, ) -> Result<(), OffenceError> { Ok(()) } fn submit_equivocation_report( _equivocation_proof: EquivocationProof, _key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult { Ok(()) } } /// Generic equivocation handler. This type implements `HandleEquivocation` /// using existing subsystems that are part of frame (type bounds described /// below) and will dispatch to them directly, it's only purpose is to wire all /// subsystems together. pub struct EquivocationHandler> { _phantom: sp_std::marker::PhantomData<(I, C, S, R, O)>, } impl Default for EquivocationHandler { fn default() -> Self { Self { _phantom: Default::default(), } } } impl HandleEquivocation for EquivocationHandler where // A signed transaction creator. Used for signing and submitting equivocation reports. T: super::Trait + CreateSignedTransaction>, // Application-specific crypto bindings. C: AppCrypto, // The offence type that should be used when reporting. O: GrandpaOffence, // A system for reporting offences after valid equivocation reports are // processed. R: ReportOffence, { type Offence = O; fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { R::report_offence(reporters, offence) } fn submit_equivocation_report( equivocation_proof: EquivocationProof, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResult { use frame_system::offchain::SendSignedTransaction; let signer = Signer::::all_accounts(); if !signer.can_sign() { return Err( "No local accounts available. Consider adding one via `author_insertKey` RPC.", )?; } let results = signer.send_signed_transaction(|_account| { super::Call::report_equivocation(equivocation_proof.clone(), key_owner_proof.clone()) }); for (acc, res) in &results { match res { Ok(()) => debug::info!("[{:?}] Submitted GRANDPA equivocation report.", acc.id), Err(e) => debug::error!( "[{:?}] Error submitting equivocation report: {:?}", acc.id, e ), } } Ok(()) } } /// A round number and set id which point on the time of an offence. #[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] pub struct GrandpaTimeSlot { // The order of these matters for `derive(Ord)`. /// Grandpa Set ID. pub set_id: SetId, /// Round number. pub round: RoundNumber, } /// A grandpa equivocation offence report. #[allow(dead_code)] pub struct GrandpaEquivocationOffence { /// Time slot at which this incident happened. pub time_slot: GrandpaTimeSlot, /// The session index in which the incident happened. pub session_index: SessionIndex, /// The size of the validator set at the time of the offence. pub validator_set_count: u32, /// The authority which produced this equivocation. pub offender: FullIdentification, } /// An interface for types that will be used as GRANDPA offences and must also /// implement the `Offence` trait. This trait provides a constructor that is /// provided all available data during processing of GRANDPA equivocations. pub trait GrandpaOffence: Offence { /// Create a new GRANDPA offence using the given equivocation details. fn new( session_index: SessionIndex, validator_set_count: u32, offender: FullIdentification, set_id: SetId, round: RoundNumber, ) -> Self; } impl GrandpaOffence for GrandpaEquivocationOffence { fn new( session_index: SessionIndex, validator_set_count: u32, offender: FullIdentification, set_id: SetId, round: RoundNumber, ) -> Self { GrandpaEquivocationOffence { session_index, validator_set_count, offender, time_slot: GrandpaTimeSlot { set_id, round }, } } } impl Offence for GrandpaEquivocationOffence { const ID: Kind = *b"grandpa:equivoca"; type TimeSlot = GrandpaTimeSlot; fn offenders(&self) -> Vec { vec![self.offender.clone()] } fn session_index(&self) -> SessionIndex { self.session_index } fn validator_set_count(&self) -> u32 { self.validator_set_count } fn time_slot(&self) -> Self::TimeSlot { self.time_slot } fn slash_fraction(offenders_count: u32, validator_set_count: u32) -> Perbill { // the formula is min((3k / n)^2, 1) let x = Perbill::from_rational_approximation(3 * offenders_count, validator_set_count); // _ ^ 2 x.square() } } /// A trait to get a session number the `MembershipProof` belongs to. pub trait GetSessionNumber { fn session(&self) -> SessionIndex; } /// A trait to get the validator count at the session the `MembershipProof` /// belongs to. pub trait GetValidatorCount { fn validator_count(&self) -> sp_session::ValidatorCount; } impl GetSessionNumber for frame_support::Void { fn session(&self) -> SessionIndex { Default::default() } } impl GetValidatorCount for frame_support::Void { fn validator_count(&self) -> sp_session::ValidatorCount { Default::default() } } impl GetSessionNumber for sp_session::MembershipProof { fn session(&self) -> SessionIndex { self.session } } impl GetValidatorCount for sp_session::MembershipProof { fn validator_count(&self) -> sp_session::ValidatorCount { self.validator_count } }