From 9bc4f62effa9218ac0703e2b4e91df8de92c51ab Mon Sep 17 00:00:00 2001
From: ordian <write@reusable.software>
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. <marcin@bytedude.com>

* revert unrelated fmt changes

* post merge fixes

* fix compilation

---------

Co-authored-by: Marcin S. <marcin@bytedude.com>
---
 polkadot/node/core/runtime-api/src/cache.rs   | 64 +++++++++++-
 polkadot/node/core/runtime-api/src/lib.rs     | 95 +++++++++++++-----
 polkadot/node/subsystem-types/src/messages.rs | 29 +++++-
 .../subsystem-types/src/runtime_client.rs     | 55 ++++++++++-
 polkadot/primitives/src/runtime_api.rs        | 26 ++++-
 polkadot/primitives/src/vstaging/mod.rs       |  1 +
 polkadot/primitives/src/vstaging/slashing.rs  | 99 +++++++++++++++++++
 .../parachains/src/disputes/slashing.rs       | 74 ++++----------
 .../src/disputes/slashing/benchmarking.rs     |  1 +
 .../src/runtime_api_impl/vstaging.rs          | 29 ++++++
 polkadot/runtime/rococo/src/lib.rs            | 30 +++++-
 polkadot/runtime/westend/src/lib.rs           | 34 ++++++-
 12 files changed, 441 insertions(+), 96 deletions(-)
 create mode 100644 polkadot/primitives/src/vstaging/slashing.rs

diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs
index ea7135696e3..4c23ce2fa3c 100644
--- a/polkadot/node/core/runtime-api/src/cache.rs
+++ b/polkadot/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<ValidationCodeHash>>,
 	version: LruCache<Hash, u32>,
 	disputes: LruCache<Hash, Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>>,
+	unapplied_slashes:
+		LruCache<Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>,
+	key_ownership_proof:
+		LruCache<(Hash, ValidatorId), Option<vstaging::slashing::OpaqueKeyOwnershipProof>>,
 }
 
 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<vstaging::slashing::OpaqueKeyOwnershipProof>> {
+		self.key_ownership_proof.get(&key)
+	}
+
+	pub(crate) fn cache_key_ownership_proof(
+		&mut self,
+		key: (Hash, ValidatorId),
+		value: Option<vstaging::slashing::OpaqueKeyOwnershipProof>,
+	) {
+		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<ValidationCodeHash>),
 	Version(Hash, u32),
 	Disputes(Hash, Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>),
+	UnappliedSlashes(Hash, Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>),
+	KeyOwnershipProof(Hash, ValidatorId, Option<vstaging::slashing::OpaqueKeyOwnershipProof>),
+	// This is a request with side-effects.
+	SubmitReportDisputeLost(
+		Hash,
+		vstaging::slashing::DisputeProof,
+		vstaging::slashing::OpaqueKeyOwnershipProof,
+		Option<()>,
+	),
 }
diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs
index 57947ccdc1d..252bb21b0ed 100644
--- a/polkadot/node/core/runtime-api/src/lib.rs
+++ b/polkadot/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/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs
index 689d210096a..cd61236e671 100644
--- a/polkadot/node/subsystem-types/src/messages.rs
+++ b/polkadot/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<Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>>),
+	/// Returns a list of validators that lost a past session dispute and need to be slashed.
+	/// `VStaging`
+	UnappliedSlashes(
+		RuntimeApiSender<Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>>,
+	),
+	/// Returns a merkle proof of a validator session key.
+	/// `VStaging`
+	KeyOwnershipProof(
+		ValidatorId,
+		RuntimeApiSender<Option<vstaging::slashing::OpaqueKeyOwnershipProof>>,
+	),
+	/// Submits an unsigned extrinsic to slash validator who lost a past session dispute.
+	/// `VStaging`
+	SubmitReportDisputeLost(
+		vstaging::slashing::DisputeProof,
+		vstaging::slashing::OpaqueKeyOwnershipProof,
+		RuntimeApiSender<Option<()>>,
+	),
 }
 
 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/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs
index 5f8db5a8d0c..2d6d7afcfd0 100644
--- a/polkadot/node/subsystem-types/src/runtime_client.rs
+++ b/polkadot/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<Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>, 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<Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>, 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<Option<vstaging::slashing::OpaqueKeyOwnershipProof>, 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<Option<()>, ApiError>;
+
 	/// Get the execution environment parameter set by parent hash, if stored
 	async fn session_executor_params(
 		&self,
@@ -374,4 +402,29 @@ where
 	) -> Result<Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)>, ApiError> {
 		self.runtime_api().disputes(at)
 	}
+
+	async fn unapplied_slashes(
+		&self,
+		at: Hash,
+	) -> Result<Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)>, ApiError> {
+		self.runtime_api().unapplied_slashes(at)
+	}
+
+	async fn key_ownership_proof(
+		&self,
+		at: Hash,
+		validator_id: ValidatorId,
+	) -> Result<Option<vstaging::slashing::OpaqueKeyOwnershipProof>, 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<Option<()>, ApiError> {
+		self.runtime_api()
+			.submit_report_dispute_lost(at, dispute_proof, key_ownership_proof)
+	}
 }
diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs
index f6269e60dd0..c60ec8c9229 100644
--- a/polkadot/primitives/src/runtime_api.rs
+++ b/polkadot/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<ExecutorParams>;
+
+		/// 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<vstaging::slashing::OpaqueKeyOwnershipProof>;
+
+		/// 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/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs
index c5ef4b56164..6dc294fe86a 100644
--- a/polkadot/primitives/src/vstaging/mod.rs
+++ b/polkadot/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/polkadot/primitives/src/vstaging/slashing.rs b/polkadot/primitives/src/vstaging/slashing.rs
new file mode 100644
index 00000000000..c5782c7c2ab
--- /dev/null
+++ b/polkadot/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 <http://www.gnu.org/licenses/>.
+
+//! 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<ValidatorIndex, ValidatorId>,
+	/// 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<u8>);
+impl OpaqueKeyOwnershipProof {
+	/// Create a new `OpaqueKeyOwnershipProof` using the given encoded
+	/// representation.
+	pub fn new(inner: Vec<u8>) -> OpaqueKeyOwnershipProof {
+		OpaqueKeyOwnershipProof(inner)
+	}
+
+	/// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key
+	/// ownership proof type.
+	pub fn decode<T: Decode>(self) -> Option<T> {
+		Decode::decode(&mut &self.0[..]).ok()
+	}
+}
diff --git a/polkadot/runtime/parachains/src/disputes/slashing.rs b/polkadot/runtime/parachains/src/disputes/slashing.rs
index 58b452d6db0..daf10814df0 100644
--- a/polkadot/runtime/parachains/src/disputes/slashing.rs
+++ b/polkadot/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<const M: u32> BenchmarkingConfiguration for BenchConfig<M> {
 	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<KeyOwnerIdentification> {
 	/// 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<ValidatorIndex, ValidatorId>,
-	/// 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<T: Config> Pallet<T> {
 		let old_session = session_index - config.dispute_period - 1;
 		let _ = <UnappliedSlashes<T>>::clear_prefix(old_session, REMOVE_LIMIT, None);
 	}
+
+	pub(crate) fn unapplied_slashes() -> Vec<(SessionIndex, CandidateHash, PendingSlashes)> {
+		<UnappliedSlashes<T>>::iter().collect()
+	}
+
+	pub(crate) fn submit_unsigned_slashing_report(
+		dispute_proof: DisputeProof,
+		key_ownership_proof: <T as Config>::KeyOwnerProof,
+	) -> Option<()> {
+		T::HandleReports::submit_unsigned_slashing_report(dispute_proof, key_ownership_proof).ok()
+	}
 }
 
 /// Methods for the `ValidateUnsigned` implementation:
diff --git a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs b/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs
index d7f2eeed1ac..4debc41d330 100644
--- a/polkadot/runtime/parachains/src/disputes/slashing/benchmarking.rs
+++ b/polkadot/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/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs
index d01b543630c..be7c58e3f24 100644
--- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs
+++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs
@@ -15,3 +15,32 @@
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
 //! 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<T: disputes::Config>(
+) -> Vec<(SessionIndex, CandidateHash, DisputeState<T::BlockNumber>)> {
+	<disputes::Pallet<T>>::disputes()
+}
+
+/// Implementation of `unapplied_slashes` runtime API
+pub fn unapplied_slashes<T: disputes::slashing::Config>(
+) -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)> {
+	<disputes::slashing::Pallet<T>>::unapplied_slashes()
+}
+
+/// Implementation of `submit_report_dispute_lost` runtime API
+pub fn submit_unsigned_slashing_report<T: disputes::slashing::Config>(
+	dispute_proof: vstaging::slashing::DisputeProof,
+	key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof,
+) -> Option<()> {
+	let key_ownership_proof = key_ownership_proof.decode()?;
+
+	<disputes::slashing::Pallet<T>>::submit_unsigned_slashing_report(
+		dispute_proof,
+		key_ownership_proof,
+	)
+}
diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs
index fa59d019e0e..c91fa1948aa 100644
--- a/polkadot/runtime/rococo/src/lib.rs
+++ b/polkadot/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<Block, Hash, BlockNumber> for Runtime {
 		fn validators() -> Vec<ValidatorId> {
 			parachains_runtime_api_impl::validators::<Runtime>()
@@ -1905,6 +1906,31 @@ sp_api::impl_runtime_apis! {
 		fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)> {
 			parachains_runtime_api_impl::get_session_disputes::<Runtime>()
 		}
+
+		fn unapplied_slashes(
+		) -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)> {
+			runtime_parachains::runtime_api_impl::vstaging::unapplied_slashes::<Runtime>()
+		}
+
+		fn key_ownership_proof(
+			validator_id: ValidatorId,
+		) -> Option<vstaging::slashing::OpaqueKeyOwnershipProof> {
+			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::<Runtime>(
+				dispute_proof,
+				key_ownership_proof,
+			)
+		}
 	}
 
 	#[api_version(2)]
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 3ad7d730176..7963e911d01 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/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<Block, Hash, BlockNumber> for Runtime {
 		fn validators() -> Vec<ValidatorId> {
 			parachains_runtime_api_impl::validators::<Runtime>()
@@ -1577,6 +1580,31 @@ sp_api::impl_runtime_apis! {
 		fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)> {
 			parachains_runtime_api_impl::get_session_disputes::<Runtime>()
 		}
+
+		fn unapplied_slashes(
+		) -> Vec<(SessionIndex, CandidateHash, vstaging::slashing::PendingSlashes)> {
+			parachains_runtime_api_impl_staging::unapplied_slashes::<Runtime>()
+		}
+
+		fn key_ownership_proof(
+			validator_id: ValidatorId,
+		) -> Option<vstaging::slashing::OpaqueKeyOwnershipProof> {
+			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::<Runtime>(
+				dispute_proof,
+				key_ownership_proof,
+			)
+		}
 	}
 
 	impl beefy_primitives::BeefyApi<Block> for Runtime {
-- 
GitLab