// Copyright 2019-2020 Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // Substrate is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . //! # Offences Module //! //! Tracks reported offences // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] mod mock; mod tests; use sp_std::vec::Vec; use frame_support::{ decl_module, decl_event, decl_storage, Parameter, weights::{Weight, SimpleDispatchInfo, WeighData}, }; use sp_runtime::traits::Hash; use sp_staking::{ offence::{Offence, ReportOffence, Kind, OnOffenceHandler, OffenceDetails, OffenceError}, }; use codec::{Encode, Decode}; use frame_system as system; /// A binary blob which represents a SCALE codec-encoded `O::TimeSlot`. type OpaqueTimeSlot = Vec; /// A type alias for a report identifier. type ReportIdOf = ::Hash; /// Offences trait pub trait Trait: frame_system::Trait { /// The overarching event type. type Event: From + Into<::Event>; /// Full identification of the validator. type IdentificationTuple: Parameter + Ord; /// A handler called for every offence report. type OnOffenceHandler: OnOffenceHandler; } decl_storage! { trait Store for Module as Offences { /// The primary structure that holds all offence records keyed by report identifiers. Reports get(fn reports): map hasher(twox_64_concat) ReportIdOf => Option>; /// A vector of reports of the same kind that happened at the same time slot. ConcurrentReportsIndex: double_map hasher(twox_64_concat) Kind, hasher(twox_64_concat) OpaqueTimeSlot => Vec>; /// 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. ReportsByKindIndex: map hasher(twox_64_concat) Kind => Vec; // (O::TimeSlot, ReportIdOf) } } decl_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. Offence(Kind, OpaqueTimeSlot), } ); decl_module! { /// Offences module, currently just responsible for taking offence reports. pub struct Module for enum Call where origin: T::Origin { fn deposit_event() = default; fn on_runtime_upgrade() -> Weight { Reports::::remove_all(); ConcurrentReportsIndex::::remove_all(); ReportsByKindIndex::remove_all(); SimpleDispatchInfo::default().weigh_data(()) } } } impl> ReportOffence for Module 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 was 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), }; // Deposit the event. Self::deposit_event(Event::Offence(O::ID, time_slot.encode())); 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(), ); Ok(()) } } impl Module { /// 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 = ::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 = match self .same_kind_reports .binary_search_by_key(&time_slot, |&(ref when, _)| when) { Ok(pos) => pos, Err(pos) => pos, }; 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) { ::insert(&O::ID, self.same_kind_reports.encode()); >::insert( &O::ID, &self.opaque_time_slot, &self.concurrent_reports, ); } }