From 710419524ece26228f70562147f704eacd00e3af Mon Sep 17 00:00:00 2001 From: ordian Date: Fri, 26 May 2023 11:35:46 +0200 Subject: [PATCH] runtime: past session slashing runtime API (#6667) * runtime/vstaging: unapplied_slashes runtime API * runtime/vstaging: key_ownership_proof runtime API * runtime/ParachainHost: submit_report_dispute_lost * fix key_ownership_proof API * runtime: submit_report_dispute_lost runtime API * nits * Update node/subsystem-types/src/messages.rs Co-authored-by: Marcin S. * revert unrelated fmt changes * post merge fixes * fix compilation --------- Co-authored-by: Marcin S. --- node/core/runtime-api/src/cache.rs | 64 +++++++++++- node/core/runtime-api/src/lib.rs | 95 +++++++++++++----- node/subsystem-types/src/messages.rs | 29 +++++- node/subsystem-types/src/runtime_client.rs | 55 ++++++++++- primitives/src/runtime_api.rs | 26 ++++- primitives/src/vstaging/mod.rs | 1 + primitives/src/vstaging/slashing.rs | 99 +++++++++++++++++++ runtime/parachains/src/disputes/slashing.rs | 74 ++++---------- .../src/disputes/slashing/benchmarking.rs | 1 + .../src/runtime_api_impl/vstaging.rs | 29 ++++++ runtime/rococo/src/lib.rs | 30 +++++- runtime/westend/src/lib.rs | 34 ++++++- 12 files changed, 441 insertions(+), 96 deletions(-) create mode 100644 primitives/src/vstaging/slashing.rs diff --git a/node/core/runtime-api/src/cache.rs b/node/core/runtime-api/src/cache.rs index ea7135696e34..4c23ce2fa3c7 100644 --- a/node/core/runtime-api/src/cache.rs +++ b/node/core/runtime-api/src/cache.rs @@ -20,11 +20,12 @@ use lru::LruCache; use sp_consensus_babe::Epoch; use polkadot_primitives::{ - AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, - CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, - Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, + vstaging, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, + CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, + GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, + OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, + SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + ValidatorSignature, }; /// For consistency we have the same capacity for all caches. We use 128 as we'll only need that @@ -63,6 +64,10 @@ pub(crate) struct RequestResultCache { LruCache<(Hash, ParaId, OccupiedCoreAssumption), Option>, version: LruCache, disputes: LruCache)>>, + unapplied_slashes: + LruCache>, + key_ownership_proof: + LruCache<(Hash, ValidatorId), Option>, } impl Default for RequestResultCache { @@ -90,6 +95,8 @@ impl Default for RequestResultCache { validation_code_hash: LruCache::new(DEFAULT_CACHE_CAP), version: LruCache::new(DEFAULT_CACHE_CAP), disputes: LruCache::new(DEFAULT_CACHE_CAP), + unapplied_slashes: LruCache::new(DEFAULT_CACHE_CAP), + key_ownership_proof: LruCache::new(DEFAULT_CACHE_CAP), } } } @@ -385,6 +392,44 @@ impl RequestResultCache { ) { self.disputes.put(relay_parent, value); } + + pub(crate) fn unapplied_slashes( + &mut self, + relay_parent: &Hash, + ) -> Option<&Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>> { + self.unapplied_slashes.get(relay_parent) + } + + pub(crate) fn cache_unapplied_slashes( + &mut self, + relay_parent: Hash, + value: Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>, + ) { + self.unapplied_slashes.put(relay_parent, value); + } + + pub(crate) fn key_ownership_proof( + &mut self, + key: (Hash, ValidatorId), + ) -> Option<&Option> { + self.key_ownership_proof.get(&key) + } + + pub(crate) fn cache_key_ownership_proof( + &mut self, + key: (Hash, ValidatorId), + value: Option, + ) { + self.key_ownership_proof.put(key, value); + } + + // This request is never cached, hence always returns `None`. + pub(crate) fn submit_report_dispute_lost( + &mut self, + _key: (Hash, vstaging::slashing::DisputeProof, vstaging::slashing::OpaqueKeyOwnershipProof), + ) -> Option<&Option<()>> { + None + } } pub(crate) enum RequestResult { @@ -422,4 +467,13 @@ pub(crate) enum RequestResult { ValidationCodeHash(Hash, ParaId, OccupiedCoreAssumption, Option), Version(Hash, u32), Disputes(Hash, Vec<(SessionIndex, CandidateHash, DisputeState)>), + UnappliedSlashes(Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>), + KeyOwnershipProof(Hash, ValidatorId, Option), + // This is a request with side-effects. + SubmitReportDisputeLost( + Hash, + vstaging::slashing::DisputeProof, + vstaging::slashing::OpaqueKeyOwnershipProof, + Option<()>, + ), } diff --git a/node/core/runtime-api/src/lib.rs b/node/core/runtime-api/src/lib.rs index 57947ccdc1de..252bb21b0edb 100644 --- a/node/core/runtime-api/src/lib.rs +++ b/node/core/runtime-api/src/lib.rs @@ -157,6 +157,12 @@ where self.requests_cache.cache_version(relay_parent, version), Disputes(relay_parent, disputes) => self.requests_cache.cache_disputes(relay_parent, disputes), + UnappliedSlashes(relay_parent, unapplied_slashes) => + self.requests_cache.cache_unapplied_slashes(relay_parent, unapplied_slashes), + KeyOwnershipProof(relay_parent, validator_id, key_ownership_proof) => self + .requests_cache + .cache_key_ownership_proof((relay_parent, validator_id), key_ownership_proof), + SubmitReportDisputeLost(_, _, _, _) => {}, } } @@ -271,6 +277,17 @@ where .map(|sender| Request::ValidationCodeHash(para, assumption, sender)), Request::Disputes(sender) => query!(disputes(), sender).map(|sender| Request::Disputes(sender)), + Request::UnappliedSlashes(sender) => + query!(unapplied_slashes(), sender).map(|sender| Request::UnappliedSlashes(sender)), + Request::KeyOwnershipProof(validator_id, sender) => + query!(key_ownership_proof(validator_id), sender) + .map(|sender| Request::KeyOwnershipProof(validator_id, sender)), + Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) => + query!(submit_report_dispute_lost(dispute_proof, key_ownership_proof), sender).map( + |sender| { + Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) + }, + ), } } @@ -419,33 +436,38 @@ where Request::Authorities(sender) => query!(Authorities, authorities(), ver = 1, sender), Request::Validators(sender) => query!(Validators, validators(), ver = 1, sender), - Request::ValidatorGroups(sender) => - query!(ValidatorGroups, validator_groups(), ver = 1, sender), - Request::AvailabilityCores(sender) => - query!(AvailabilityCores, availability_cores(), ver = 1, sender), + Request::ValidatorGroups(sender) => { + query!(ValidatorGroups, validator_groups(), ver = 1, sender) + }, + Request::AvailabilityCores(sender) => { + query!(AvailabilityCores, availability_cores(), ver = 1, sender) + }, Request::PersistedValidationData(para, assumption, sender) => query!( PersistedValidationData, persisted_validation_data(para, assumption), ver = 1, sender ), - Request::AssumedValidationData(para, expected_persisted_validation_data_hash, sender) => + Request::AssumedValidationData(para, expected_persisted_validation_data_hash, sender) => { query!( AssumedValidationData, assumed_validation_data(para, expected_persisted_validation_data_hash), ver = 1, sender - ), + ) + }, Request::CheckValidationOutputs(para, commitments, sender) => query!( CheckValidationOutputs, check_validation_outputs(para, commitments), ver = 1, sender ), - Request::SessionIndexForChild(sender) => - query!(SessionIndexForChild, session_index_for_child(), ver = 1, sender), - Request::ValidationCode(para, assumption, sender) => - query!(ValidationCode, validation_code(para, assumption), ver = 1, sender), + Request::SessionIndexForChild(sender) => { + query!(SessionIndexForChild, session_index_for_child(), ver = 1, sender) + }, + Request::ValidationCode(para, assumption, sender) => { + query!(ValidationCode, validation_code(para, assumption), ver = 1, sender) + }, Request::ValidationCodeByHash(validation_code_hash, sender) => query!( ValidationCodeByHash, validation_code_by_hash(validation_code_hash), @@ -458,10 +480,12 @@ where ver = 1, sender ), - Request::CandidateEvents(sender) => - query!(CandidateEvents, candidate_events(), ver = 1, sender), - Request::SessionInfo(index, sender) => - query!(SessionInfo, session_info(index), ver = 2, sender), + Request::CandidateEvents(sender) => { + query!(CandidateEvents, candidate_events(), ver = 1, sender) + }, + Request::SessionInfo(index, sender) => { + query!(SessionInfo, session_info(index), ver = 2, sender) + }, Request::SessionExecutorParams(session_index, sender) => query!( SessionExecutorParams, session_executor_params(session_index), @@ -469,12 +493,15 @@ where sender ), Request::DmqContents(id, sender) => query!(DmqContents, dmq_contents(id), ver = 1, sender), - Request::InboundHrmpChannelsContents(id, sender) => - query!(InboundHrmpChannelsContents, inbound_hrmp_channels_contents(id), ver = 1, sender), - Request::CurrentBabeEpoch(sender) => - query!(CurrentBabeEpoch, current_epoch(), ver = 1, sender), - Request::FetchOnChainVotes(sender) => - query!(FetchOnChainVotes, on_chain_votes(), ver = 1, sender), + Request::InboundHrmpChannelsContents(id, sender) => { + query!(InboundHrmpChannelsContents, inbound_hrmp_channels_contents(id), ver = 1, sender) + }, + Request::CurrentBabeEpoch(sender) => { + query!(CurrentBabeEpoch, current_epoch(), ver = 1, sender) + }, + Request::FetchOnChainVotes(sender) => { + query!(FetchOnChainVotes, on_chain_votes(), ver = 1, sender) + }, Request::SubmitPvfCheckStatement(stmt, signature, sender) => { query!( SubmitPvfCheckStatement, @@ -486,9 +513,29 @@ where Request::PvfsRequirePrecheck(sender) => { query!(PvfsRequirePrecheck, pvfs_require_precheck(), ver = 2, sender) }, - Request::ValidationCodeHash(para, assumption, sender) => - query!(ValidationCodeHash, validation_code_hash(para, assumption), ver = 2, sender), - Request::Disputes(sender) => - query!(Disputes, disputes(), ver = Request::DISPUTES_RUNTIME_REQUIREMENT, sender), + Request::ValidationCodeHash(para, assumption, sender) => { + query!(ValidationCodeHash, validation_code_hash(para, assumption), ver = 2, sender) + }, + Request::Disputes(sender) => { + query!(Disputes, disputes(), ver = Request::DISPUTES_RUNTIME_REQUIREMENT, sender) + }, + Request::UnappliedSlashes(sender) => query!( + UnappliedSlashes, + unapplied_slashes(), + ver = Request::UNAPPLIED_SLASHES_RUNTIME_REQUIREMENT, + sender + ), + Request::KeyOwnershipProof(validator_id, sender) => query!( + KeyOwnershipProof, + key_ownership_proof(validator_id), + ver = Request::KEY_OWNERSHIP_PROOF_RUNTIME_REQUIREMENT, + sender + ), + Request::SubmitReportDisputeLost(dispute_proof, key_ownership_proof, sender) => query!( + SubmitReportDisputeLost, + submit_report_dispute_lost(dispute_proof, key_ownership_proof), + ver = Request::SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT, + sender + ), } } diff --git a/node/subsystem-types/src/messages.rs b/node/subsystem-types/src/messages.rs index 689d210096ac..cd61236e6715 100644 --- a/node/subsystem-types/src/messages.rs +++ b/node/subsystem-types/src/messages.rs @@ -39,7 +39,7 @@ use polkadot_node_primitives::{ SignedDisputeStatement, SignedFullStatement, ValidationResult, }; use polkadot_primitives::{ - AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash, + vstaging, AuthorityDiscoveryId, BackedCandidate, BlockNumber, CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Header as BlockHeader, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, MultiDisputeStatementSet, @@ -606,6 +606,24 @@ pub enum RuntimeApiRequest { ), /// Returns all on-chain disputes at given block number. Available in `v3`. Disputes(RuntimeApiSender)>>), + /// Returns a list of validators that lost a past session dispute and need to be slashed. + /// `VStaging` + UnappliedSlashes( + RuntimeApiSender>, + ), + /// Returns a merkle proof of a validator session key. + /// `VStaging` + KeyOwnershipProof( + ValidatorId, + RuntimeApiSender>, + ), + /// Submits an unsigned extrinsic to slash validator who lost a past session dispute. + /// `VStaging` + SubmitReportDisputeLost( + vstaging::slashing::DisputeProof, + vstaging::slashing::OpaqueKeyOwnershipProof, + RuntimeApiSender>, + ), } impl RuntimeApiRequest { @@ -614,8 +632,17 @@ impl RuntimeApiRequest { /// `Disputes` pub const DISPUTES_RUNTIME_REQUIREMENT: u32 = 3; + /// `UnappliedSlashes` + pub const UNAPPLIED_SLASHES_RUNTIME_REQUIREMENT: u32 = 4; + /// `ExecutorParams` pub const EXECUTOR_PARAMS_RUNTIME_REQUIREMENT: u32 = 4; + + /// `KeyOwnershipProof` + pub const KEY_OWNERSHIP_PROOF_RUNTIME_REQUIREMENT: u32 = 4; + + /// `SubmitReportDisputeLost` + pub const SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT: u32 = 4; } /// A message to the Runtime API subsystem. diff --git a/node/subsystem-types/src/runtime_client.rs b/node/subsystem-types/src/runtime_client.rs index 5f8db5a8d0cb..2d6d7afcfd08 100644 --- a/node/subsystem-types/src/runtime_client.rs +++ b/node/subsystem-types/src/runtime_client.rs @@ -16,7 +16,7 @@ use async_trait::async_trait; use polkadot_primitives::{ - runtime_api::ParachainHost, Block, BlockNumber, CandidateCommitments, CandidateEvent, + runtime_api::ParachainHost, vstaging, Block, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, @@ -182,6 +182,34 @@ pub trait RuntimeApiSubsystemClient { at: Hash, ) -> Result)>, ApiError>; + /// Returns a list of validators that lost a past session dispute and need to be slashed. + /// + /// WARNING: This is a staging method! Do not use on production runtimes! + async fn unapplied_slashes( + &self, + at: Hash, + ) -> Result, ApiError>; + + /// Returns a merkle proof of a validator session key in a past session. + /// + /// WARNING: This is a staging method! Do not use on production runtimes! + async fn key_ownership_proof( + &self, + at: Hash, + validator_id: ValidatorId, + ) -> Result, ApiError>; + + /// Submits an unsigned extrinsic to slash validators who lost a dispute about + /// a candidate of a past session. + /// + /// WARNING: This is a staging method! Do not use on production runtimes! + async fn submit_report_dispute_lost( + &self, + at: Hash, + dispute_proof: vstaging::slashing::DisputeProof, + key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, + ) -> Result, ApiError>; + /// Get the execution environment parameter set by parent hash, if stored async fn session_executor_params( &self, @@ -374,4 +402,29 @@ where ) -> Result)>, ApiError> { self.runtime_api().disputes(at) } + + async fn unapplied_slashes( + &self, + at: Hash, + ) -> Result, ApiError> { + self.runtime_api().unapplied_slashes(at) + } + + async fn key_ownership_proof( + &self, + at: Hash, + validator_id: ValidatorId, + ) -> Result, ApiError> { + self.runtime_api().key_ownership_proof(at, validator_id) + } + + async fn submit_report_dispute_lost( + &self, + at: Hash, + dispute_proof: vstaging::slashing::DisputeProof, + key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, + ) -> Result, ApiError> { + self.runtime_api() + .submit_report_dispute_lost(at, dispute_proof, key_ownership_proof) + } } diff --git a/primitives/src/runtime_api.rs b/primitives/src/runtime_api.rs index f6269e60dd0c..c60ec8c92298 100644 --- a/primitives/src/runtime_api.rs +++ b/primitives/src/runtime_api.rs @@ -111,10 +111,10 @@ //! from the stable primitives. use crate::{ - BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, - CoreState, DisputeState, ExecutorParams, GroupRotationInfo, OccupiedCoreAssumption, - PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionIndex, SessionInfo, - ValidatorId, ValidatorIndex, ValidatorSignature, + vstaging, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, + OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, + SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorSignature, }; use parity_scale_codec::{Decode, Encode}; use polkadot_core_primitives as pcp; @@ -218,5 +218,23 @@ sp_api::decl_runtime_apis! { /// Returns execution parameters for the session. fn session_executor_params(session_index: SessionIndex) -> Option; + + /// Returns a list of validators that lost a past session dispute and need to be slashed. + #[api_version(5)] + fn unapplied_slashes() -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>; + + /// Returns a merkle proof of a validator session key. + #[api_version(5)] + fn key_ownership_proof( + validator_id: ValidatorId, + ) -> Option; + + /// Submit an unsigned extrinsic to slash validators who lost a dispute about + /// a candidate of a past session. + #[api_version(5)] + fn submit_report_dispute_lost( + dispute_proof: vstaging::slashing::DisputeProof, + key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, + ) -> Option<()>; } } diff --git a/primitives/src/vstaging/mod.rs b/primitives/src/vstaging/mod.rs index c5ef4b56164d..6dc294fe86a6 100644 --- a/primitives/src/vstaging/mod.rs +++ b/primitives/src/vstaging/mod.rs @@ -18,6 +18,7 @@ // Put any primitives used by staging APIs functions here pub use crate::v4::*; +pub mod slashing; use sp_std::prelude::*; use parity_scale_codec::{Decode, Encode}; diff --git a/primitives/src/vstaging/slashing.rs b/primitives/src/vstaging/slashing.rs new file mode 100644 index 000000000000..c5782c7c2ab4 --- /dev/null +++ b/primitives/src/vstaging/slashing.rs @@ -0,0 +1,99 @@ +// Copyright 2017-2023 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot 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. + +// Polkadot 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 Polkadot. If not, see . + +//! Primitives types used for dispute slashing. + +use crate::v4::{CandidateHash, SessionIndex, ValidatorId, ValidatorIndex}; +use parity_scale_codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +/// The kind of the dispute offence. +#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, TypeInfo, Debug)] +pub enum SlashingOffenceKind { + /// A severe offence when a validator backed an invalid block. + #[codec(index = 0)] + ForInvalid, + /// A minor offence when a validator disputed a valid block. + #[codec(index = 1)] + AgainstValid, +} + +/// Timeslots should uniquely identify offences and are used for the offence +/// deduplication. +#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, TypeInfo, Debug)] +pub struct DisputesTimeSlot { + // The order of the fields matters for `derive(Ord)`. + /// Session index when the candidate was backed/included. + pub session_index: SessionIndex, + /// Candidate hash of the disputed candidate. + pub candidate_hash: CandidateHash, +} + +impl DisputesTimeSlot { + /// Create a new instance of `Self`. + pub fn new(session_index: SessionIndex, candidate_hash: CandidateHash) -> Self { + Self { session_index, candidate_hash } + } +} + +/// We store most of the information about a lost dispute on chain. This struct +/// is required to identify and verify it. +#[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo, Debug)] +pub struct DisputeProof { + /// Time slot when the dispute occured. + pub time_slot: DisputesTimeSlot, + /// The dispute outcome. + pub kind: SlashingOffenceKind, + /// The index of the validator who lost a dispute. + pub validator_index: ValidatorIndex, + /// The parachain session key of the validator. + pub validator_id: ValidatorId, +} + +/// Slashes that are waiting to be applied once we have validator key +/// identification. +#[derive(Encode, Decode, TypeInfo, Debug, Clone)] +pub struct PendingSlashes { + /// Indices and keys of the validators who lost a dispute and are pending + /// slashes. + pub keys: BTreeMap, + /// The dispute outcome. + pub kind: SlashingOffenceKind, +} + +// TODO: can we reuse this type between BABE, GRANDPA and disputes? +/// An opaque type used to represent the key ownership proof at the runtime API +/// boundary. The inner value is an encoded representation of the actual key +/// ownership proof which will be parameterized when defining the runtime. At +/// the runtime API boundary this type is unknown and as such we keep this +/// opaque representation, implementors of the runtime API will have to make +/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type. +#[derive(Decode, Encode, PartialEq, Eq, Debug, Clone, TypeInfo)] +pub struct OpaqueKeyOwnershipProof(Vec); +impl OpaqueKeyOwnershipProof { + /// Create a new `OpaqueKeyOwnershipProof` using the given encoded + /// representation. + pub fn new(inner: Vec) -> OpaqueKeyOwnershipProof { + OpaqueKeyOwnershipProof(inner) + } + + /// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key + /// ownership proof type. + pub fn decode(self) -> Option { + Decode::decode(&mut &self.0[..]).ok() + } +} diff --git a/runtime/parachains/src/disputes/slashing.rs b/runtime/parachains/src/disputes/slashing.rs index 58b452d6db07..daf10814df0f 100644 --- a/runtime/parachains/src/disputes/slashing.rs +++ b/runtime/parachains/src/disputes/slashing.rs @@ -49,8 +49,10 @@ use frame_support::{ weights::Weight, }; -use parity_scale_codec::{Decode, Encode}; -use primitives::{CandidateHash, SessionIndex, ValidatorId, ValidatorIndex}; +use primitives::{ + vstaging::slashing::{DisputeProof, DisputesTimeSlot, PendingSlashes, SlashingOffenceKind}, + CandidateHash, SessionIndex, ValidatorId, ValidatorIndex, +}; use scale_info::TypeInfo; use sp_runtime::{ traits::Convert, @@ -58,15 +60,12 @@ use sp_runtime::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, }, - KeyTypeId, Perbill, RuntimeDebug, + KeyTypeId, Perbill, }; use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::offence::{DisableStrategy, Kind, Offence, OffenceError, ReportOffence}; use sp_std::{ - collections::{ - btree_map::{BTreeMap, Entry}, - btree_set::BTreeSet, - }, + collections::{btree_map::Entry, btree_set::BTreeSet}, prelude::*, }; @@ -92,23 +91,8 @@ impl BenchmarkingConfiguration for BenchConfig { const MAX_VALIDATORS: u32 = M; } -/// Timeslots should uniquely identify offences and are used for the offence -/// deduplication. -#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, TypeInfo, RuntimeDebug)] -pub struct DisputesTimeSlot { - // The order of these matters for `derive(Ord)`. - session_index: SessionIndex, - candidate_hash: CandidateHash, -} - -impl DisputesTimeSlot { - pub fn new(session_index: SessionIndex, candidate_hash: CandidateHash) -> Self { - Self { session_index, candidate_hash } - } -} - /// An offence that is filed when a series of validators lost a dispute. -#[derive(RuntimeDebug, TypeInfo)] +#[derive(TypeInfo)] #[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))] pub struct SlashingOffence { /// The size of the validator set in that session. @@ -323,39 +307,6 @@ where } } -#[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum SlashingOffenceKind { - #[codec(index = 0)] - ForInvalid, - #[codec(index = 1)] - AgainstValid, -} - -/// We store most of the information about a lost dispute on chain. This struct -/// is required to identify and verify it. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct DisputeProof { - /// Time slot when the dispute occured. - pub time_slot: DisputesTimeSlot, - /// The dispute outcome. - pub kind: SlashingOffenceKind, - /// The index of the validator who lost a dispute. - pub validator_index: ValidatorIndex, - /// The parachain session key of the validator. - pub validator_id: ValidatorId, -} - -/// Slashes that are waiting to be applied once we have validator key -/// identification. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct PendingSlashes { - /// Indices and keys of the validators who lost a dispute and are pending - /// slashes. - pub keys: BTreeMap, - /// The dispute outcome. - pub kind: SlashingOffenceKind, -} - /// A trait that defines methods to report an offence (after the slashing report /// has been validated) and for submitting a transaction to report a slash (from /// an offchain context). @@ -603,6 +554,17 @@ impl Pallet { let old_session = session_index - config.dispute_period - 1; let _ = >::clear_prefix(old_session, REMOVE_LIMIT, None); } + + pub(crate) fn unapplied_slashes() -> Vec<(SessionIndex, CandidateHash, PendingSlashes)> { + >::iter().collect() + } + + pub(crate) fn submit_unsigned_slashing_report( + dispute_proof: DisputeProof, + key_ownership_proof: ::KeyOwnerProof, + ) -> Option<()> { + T::HandleReports::submit_unsigned_slashing_report(dispute_proof, key_ownership_proof).ok() + } } /// Methods for the `ValidateUnsigned` implementation: diff --git a/runtime/parachains/src/disputes/slashing/benchmarking.rs b/runtime/parachains/src/disputes/slashing/benchmarking.rs index d7f2eeed1ac4..4debc41d3306 100644 --- a/runtime/parachains/src/disputes/slashing/benchmarking.rs +++ b/runtime/parachains/src/disputes/slashing/benchmarking.rs @@ -21,6 +21,7 @@ use frame_benchmarking::{benchmarks, whitelist_account}; use frame_support::traits::{OnFinalize, OnInitialize}; use frame_system::RawOrigin; use pallet_staking::testing_utils::create_validators; +use parity_scale_codec::Decode; use primitives::{Hash, PARACHAIN_KEY_TYPE_ID}; use sp_runtime::traits::{One, StaticLookup}; use sp_session::MembershipProof; diff --git a/runtime/parachains/src/runtime_api_impl/vstaging.rs b/runtime/parachains/src/runtime_api_impl/vstaging.rs index d01b543630c3..be7c58e3f24e 100644 --- a/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,3 +15,32 @@ // along with Polkadot. If not, see . //! Put implementations of functions from staging APIs here. + +use crate::disputes; +use primitives::{vstaging, CandidateHash, DisputeState, SessionIndex}; +use sp_std::prelude::*; + +/// Implementation for `get_session_disputes` function from the runtime API +pub fn get_session_disputes( +) -> Vec<(SessionIndex, CandidateHash, DisputeState)> { + >::disputes() +} + +/// Implementation of `unapplied_slashes` runtime API +pub fn unapplied_slashes( +) -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)> { + >::unapplied_slashes() +} + +/// Implementation of `submit_report_dispute_lost` runtime API +pub fn submit_unsigned_slashing_report( + dispute_proof: vstaging::slashing::DisputeProof, + key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, +) -> Option<()> { + let key_ownership_proof = key_ownership_proof.decode()?; + + >::submit_unsigned_slashing_report( + dispute_proof, + key_ownership_proof, + ) +} diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index fa59d019e0e6..c91fa1948aad 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -23,11 +23,11 @@ use pallet_nis::WithMaximumOf; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ - AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + vstaging, AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionInfo, Signature, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, PARACHAIN_KEY_TYPE_ID, }; use runtime_common::{ assigned_slots, auctions, claims, crowdloan, impl_runtime_weights, impls::ToAuthor, @@ -1800,6 +1800,7 @@ sp_api::impl_runtime_apis! { } } + #[api_version(5)] impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -1905,6 +1906,31 @@ sp_api::impl_runtime_apis! { fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState)> { parachains_runtime_api_impl::get_session_disputes::() } + + fn unapplied_slashes( + ) -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)> { + runtime_parachains::runtime_api_impl::vstaging::unapplied_slashes::() + } + + fn key_ownership_proof( + validator_id: ValidatorId, + ) -> Option { + use parity_scale_codec::Encode; + + Historical::prove((PARACHAIN_KEY_TYPE_ID, validator_id)) + .map(|p| p.encode()) + .map(vstaging::slashing::OpaqueKeyOwnershipProof::new) + } + + fn submit_report_dispute_lost( + dispute_proof: vstaging::slashing::DisputeProof, + key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, + ) -> Option<()> { + runtime_parachains::runtime_api_impl::vstaging::submit_unsigned_slashing_report::( + dispute_proof, + key_ownership_proof, + ) + } } #[api_version(2)] diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 3ad7d7301766..7963e911d010 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -39,12 +39,12 @@ use pallet_session::historical as session_historical; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::{ - AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + vstaging, AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement, ScrapedOnChainVotes, SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, - ValidatorSignature, + ValidatorSignature, PARACHAIN_KEY_TYPE_ID, }; use runtime_common::{ assigned_slots, auctions, crowdloan, elections::OnChainAccuracy, impl_runtime_weights, @@ -58,7 +58,9 @@ use runtime_parachains::{ inclusion::{AggregateMessageOrigin, UmpQueueId}, initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::v4 as parachains_runtime_api_impl, + runtime_api_impl::{ + v4 as parachains_runtime_api_impl, vstaging as parachains_runtime_api_impl_staging, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1472,6 +1474,7 @@ sp_api::impl_runtime_apis! { } } + #[api_version(5)] impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -1577,6 +1580,31 @@ sp_api::impl_runtime_apis! { fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState)> { parachains_runtime_api_impl::get_session_disputes::() } + + fn unapplied_slashes( + ) -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)> { + parachains_runtime_api_impl_staging::unapplied_slashes::() + } + + fn key_ownership_proof( + validator_id: ValidatorId, + ) -> Option { + use parity_scale_codec::Encode; + + Historical::prove((PARACHAIN_KEY_TYPE_ID, validator_id)) + .map(|p| p.encode()) + .map(vstaging::slashing::OpaqueKeyOwnershipProof::new) + } + + fn submit_report_dispute_lost( + dispute_proof: vstaging::slashing::DisputeProof, + key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, + ) -> Option<()> { + parachains_runtime_api_impl_staging::submit_unsigned_slashing_report::( + dispute_proof, + key_ownership_proof, + ) + } } impl beefy_primitives::BeefyApi for Runtime {