// This file is part of Substrate. // Copyright (C) 2019-2022 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. //! # Offences Pallet //! //! Tracks reported offences // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] mod migration; mod mock; mod tests; use codec::{Decode, Encode}; use frame_support::weights::Weight; use sp_runtime::{traits::Hash, Perbill}; use sp_staking::{ offence::{Kind, Offence, OffenceDetails, OffenceError, OnOffenceHandler, ReportOffence}, SessionIndex, }; use sp_std::prelude::*; pub use pallet::*; /// A binary blob which represents a SCALE codec-encoded `O::TimeSlot`. type OpaqueTimeSlot = Vec; /// A type alias for a report identifier. type ReportIdOf = ::Hash; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// The pallet's config trait. #[pallet::config] pub trait Config: frame_system::Config { /// The overarching event type. type Event: From + IsType<::Event>; /// Full identification of the validator. type IdentificationTuple: Parameter + Ord; /// A handler called for every offence report. type OnOffenceHandler: OnOffenceHandler; } /// The primary structure that holds all offence records keyed by report identifiers. #[pallet::storage] #[pallet::getter(fn reports)] pub type Reports = StorageMap< _, Twox64Concat, ReportIdOf, OffenceDetails, >; /// A vector of reports of the same kind that happened at the same time slot. #[pallet::storage] pub type ConcurrentReportsIndex = StorageDoubleMap< _, Twox64Concat, Kind, Twox64Concat, OpaqueTimeSlot, Vec>, ValueQuery, >; /// Enumerates all reports of a kind along with the time they happened. /// /// All reports are sorted by the time of offence. /// /// Note that the actual type of this mapping is `Vec`, this is because values of /// different types are not supported at the moment so we are doing the manual serialization. #[pallet::storage] pub type ReportsByKindIndex = StorageMap< _, Twox64Concat, Kind, Vec, // (O::TimeSlot, ReportIdOf) ValueQuery, >; /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// There is an offence reported of the given `kind` happened at the `session_index` and /// (kind-specific) time slot. This event is not deposited for duplicate slashes. /// \[kind, timeslot\]. Offence { kind: Kind, timeslot: OpaqueTimeSlot }, } #[pallet::hooks] impl Hooks> for Pallet { fn on_runtime_upgrade() -> Weight { migration::remove_deferred_storage::() } } } impl> ReportOffence for Pallet where T::IdentificationTuple: Clone, { fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { let offenders = offence.offenders(); let time_slot = offence.time_slot(); let validator_set_count = offence.validator_set_count(); // Go through all offenders in the offence report and find all offenders that were spotted // in unique reports. let TriageOutcome { concurrent_offenders } = match Self::triage_offence_report::(reporters, &time_slot, offenders) { Some(triage) => triage, // The report contained only duplicates, so there is no need to slash again. None => return Err(OffenceError::DuplicateReport), }; let offenders_count = concurrent_offenders.len() as u32; // The amount new offenders are slashed let new_fraction = O::slash_fraction(offenders_count, validator_set_count); let slash_perbill: Vec<_> = (0..concurrent_offenders.len()).map(|_| new_fraction.clone()).collect(); T::OnOffenceHandler::on_offence( &concurrent_offenders, &slash_perbill, offence.session_index(), offence.disable_strategy(), ); // Deposit the event. Self::deposit_event(Event::Offence { kind: O::ID, timeslot: time_slot.encode() }); Ok(()) } fn is_known_offence(offenders: &[T::IdentificationTuple], time_slot: &O::TimeSlot) -> bool { let any_unknown = offenders.iter().any(|offender| { let report_id = Self::report_id::(time_slot, offender); !>::contains_key(&report_id) }); !any_unknown } } impl Pallet { /// Compute the ID for the given report properties. /// /// The report id depends on the offence kind, time slot and the id of offender. fn report_id>( time_slot: &O::TimeSlot, offender: &T::IdentificationTuple, ) -> ReportIdOf { (O::ID, time_slot.encode(), offender).using_encoded(T::Hashing::hash) } /// Triages the offence report and returns the set of offenders that was involved in unique /// reports along with the list of the concurrent offences. fn triage_offence_report>( reporters: Vec, time_slot: &O::TimeSlot, offenders: Vec, ) -> Option> { let mut storage = ReportIndexStorage::::load(time_slot); let mut any_new = false; for offender in offenders { let report_id = Self::report_id::(time_slot, &offender); if !>::contains_key(&report_id) { any_new = true; >::insert( &report_id, OffenceDetails { offender, reporters: reporters.clone() }, ); storage.insert(time_slot, report_id); } } if any_new { // Load report details for the all reports happened at the same time. let concurrent_offenders = storage .concurrent_reports .iter() .filter_map(|report_id| >::get(report_id)) .collect::>(); storage.save(); Some(TriageOutcome { concurrent_offenders }) } else { None } } } struct TriageOutcome { /// Other reports for the same report kinds. concurrent_offenders: Vec>, } /// An auxiliary struct for working with storage of indexes localized for a specific offence /// kind (specified by the `O` type parameter). /// /// This struct is responsible for aggregating storage writes and the underlying storage should not /// accessed directly meanwhile. #[must_use = "The changes are not saved without called `save`"] struct ReportIndexStorage> { opaque_time_slot: OpaqueTimeSlot, concurrent_reports: Vec>, same_kind_reports: Vec<(O::TimeSlot, ReportIdOf)>, } impl> ReportIndexStorage { /// Preload indexes from the storage for the specific `time_slot` and the kind of the offence. fn load(time_slot: &O::TimeSlot) -> Self { let opaque_time_slot = time_slot.encode(); let same_kind_reports = ReportsByKindIndex::::get(&O::ID); let same_kind_reports = Vec::<(O::TimeSlot, ReportIdOf)>::decode(&mut &same_kind_reports[..]) .unwrap_or_default(); let concurrent_reports = >::get(&O::ID, &opaque_time_slot); Self { opaque_time_slot, concurrent_reports, same_kind_reports } } /// Insert a new report to the index. fn insert(&mut self, time_slot: &O::TimeSlot, report_id: ReportIdOf) { // Insert the report id into the list while maintaining the ordering by the time // slot. let pos = self.same_kind_reports.partition_point(|&(ref when, _)| when <= time_slot); self.same_kind_reports.insert(pos, (time_slot.clone(), report_id)); // Update the list of concurrent reports. self.concurrent_reports.push(report_id); } /// Dump the indexes to the storage. fn save(self) { ReportsByKindIndex::::insert(&O::ID, self.same_kind_reports.encode()); >::insert( &O::ID, &self.opaque_time_slot, &self.concurrent_reports, ); } }