From c10b11bdcae2522bbc43a81fa3bde7fbc3fb2b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 24 Oct 2024 10:03:23 +0200 Subject: [PATCH 1/3] init staking-rc-client pallet --- Cargo.lock | 14 + Cargo.toml | 2 + substrate/frame/staking/rc-client/Cargo.toml | 39 ++ substrate/frame/staking/rc-client/src/lib.rs | 350 ++++++++++++++++++ substrate/frame/staking/rc-client/src/mock.rs | 91 +++++ .../frame/staking/rc-client/src/tests.rs | 21 ++ 6 files changed, 517 insertions(+) create mode 100644 substrate/frame/staking/rc-client/Cargo.toml create mode 100644 substrate/frame/staking/rc-client/src/lib.rs create mode 100644 substrate/frame/staking/rc-client/src/mock.rs create mode 100644 substrate/frame/staking/rc-client/src/tests.rs diff --git a/Cargo.lock b/Cargo.lock index a42f5baa4765..73f8609cffc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12763,6 +12763,20 @@ dependencies = [ "substrate-test-utils", ] +[[package]] +name = "pallet-staking-rc-client" +version = "0.0.1" +dependencies = [ + "frame-support", + "frame-system", + "pallet-authorship", + "parity-scale-codec", + "scale-info", + "sp-runtime 31.0.1", + "sp-staking", + "sp-std 14.0.0", +] + [[package]] name = "pallet-staking-reward-curve" version = "11.0.0" diff --git a/Cargo.toml b/Cargo.toml index 049de32b54cf..c1888734ec60 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -412,6 +412,7 @@ members = [ "substrate/frame/session/benchmarking", "substrate/frame/society", "substrate/frame/staking", + "substrate/frame/staking/rc-client", "substrate/frame/staking/reward-curve", "substrate/frame/staking/reward-fn", "substrate/frame/staking/runtime-api", @@ -978,6 +979,7 @@ pallet-session-benchmarking = { path = "substrate/frame/session/benchmarking", d pallet-skip-feeless-payment = { path = "substrate/frame/transaction-payment/skip-feeless-payment", default-features = false } pallet-society = { path = "substrate/frame/society", default-features = false } pallet-staking = { path = "substrate/frame/staking", default-features = false } +pallet-staking-rc-client = { path = "substrate/frame/staking/rc-client", default-features = false } pallet-staking-reward-curve = { path = "substrate/frame/staking/reward-curve", default-features = false } pallet-staking-reward-fn = { path = "substrate/frame/staking/reward-fn", default-features = false } pallet-staking-runtime-api = { path = "substrate/frame/staking/runtime-api", default-features = false } diff --git a/substrate/frame/staking/rc-client/Cargo.toml b/substrate/frame/staking/rc-client/Cargo.toml new file mode 100644 index 000000000000..3d5c1b5e9727 --- /dev/null +++ b/substrate/frame/staking/rc-client/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "pallet-staking-rc-client" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME pallet staking relay chain client" + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { features = ["derive"], workspace = true } + +sp-runtime = { default-features = true, workspace = true } +sp-std = { default-features = true, workspace = true } +sp-staking = { default-features = true, workspace = true } +frame-support = { default-features = true, workspace = true } +frame-system = { default-features = true, workspace = true } +pallet-authorship = { workspace = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "sp-staking/std", + "frame-support/std", + "frame-system/std", + "pallet-authorship/std", +] diff --git a/substrate/frame/staking/rc-client/src/lib.rs b/substrate/frame/staking/rc-client/src/lib.rs new file mode 100644 index 000000000000..495d090075b4 --- /dev/null +++ b/substrate/frame/staking/rc-client/src/lib.rs @@ -0,0 +1,350 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! # Staking Relay chain client +//! +//! The Staking Relay chain client is used as a interface between the Staking pallet and an external +//! consensus system (e.g. Polkadot's Relay chain). +//! +//! ## Overview +//! +//! The Staking Relay chain client (`rc-client` pallet) implements an abstraction for the i/o +//! of the staking pallet. This abstraction is especially helpfull when the staking pallet +//! and the its "consumers" live in different consensus systems. Most notably, this pallet handles +//! the following i/o tasks: +//! +//! - Communicates a new set of validators to a party an external consensus system; +//! - Communicates setting and purging validator keys to an external consensus system; +//! - Receives and pre-processes offence reports from a trusteed external consensus system; +//! - Receives and pre-processes block authoring reports; +//! +//! This pallet also exposes an extrinsic for signed origins to report staking offences which are +//! communicated to both the staking pallet and an external consensus system. +//! +//! In sum, this pallet works as an adapter pallet that can be used for the staking pallet to +//! communicate with external consensus systems. +//! +//! ## Inbound +//! +//! All the inbound request should be performed through extrinsics. External consensus systems may +//! call the inbound extrinsics through XCM transact. +//! +//! ### Block authoring +//! +//! This pallet exposes an extrinsict, [`Call::author`], that processes block authoring events. +//! Block authoring information can only be submitted by the runtime's root origin. Successfull +//! calls will be redirected to staking through the [`pallet_authorship::EventHandler`]) interface. +//! +//! ### Offence reports +//! +//! This pallet exposes an extrinsict, [`Call::report_offence`], that processes priviledged offence +//! reports. These reports can only be submitted by the runtime's root origin. Successfull calls +//! will be redirected to staking through the [`sp_staking::offence::OnOffenceHandler`]) interface. +//! +//! ## Outbound +//! +//! ### Validator keys +//! +//! This pallet implements a set of extrinsics that handles session key management requests for +//! staking validators. Note, however, that this pallet is not the source of truth for session +//! keys. It only exposes interfaces for accounts to request session key initialization and +//! termination, performs pre-checks and propagates that information to another pallet or consensus +//! system through the [`crate::Config::SessionKeysHandler`]. +//! +//! Callers can request to 1) set validator keys and 2) purge validator keys. These actions *may +//! not be atomic*, i.e., the action and correspoding data may need to be propagated to an external +//! consensus system and take several blocks to be enacted. +// +//! The session key management actions exposed by this pallet are: +//! +//! 1. **Set session keys**: Extrinsic [`Call::set_session_keys`] alows an account to set its +//! session key prior to becoming a validator. +//! 2. **Purge session keys**: Extrinsic [`Call::purge_session_keys`] removes any session key(s) of +//! the caller. +//! +//! ### New set of validator IDs +//! +//! This pallet exposes a configuration type that implements the [`traits::ValidatorSetHandler`], +//! which defines what to do when pallet staking has a new validator set ready. Note, however, that +//! this pallet is not the source of truth for validator sets. +//! +//! ### Signed offence reports + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +/// Re-exports this crate's trait implementations. +pub use impls::*; +/// Re-exports this crate's traits. +pub use traits::*; + +use frame_support::{dispatch::Parameter, weights::Weight}; +use frame_system::pallet_prelude::*; +use sp_runtime::{ + traits::{Member, OpaqueKeys}, + Perbill, +}; + +use pallet_authorship::EventHandler as AuthorshipEventHandler; +use sp_staking::{ + offence::{OffenceDetails, OnOffenceHandler}, + SessionIndex, +}; + +/// The type of session key proof expected by this pallet. +pub(crate) type SessionKeyProof = Vec; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: IsType<::RuntimeEvent> + From>; + + /// The staking interface. + type Staking: AuthorshipEventHandler> + + OnOffenceHandler; + + /// The max offenders a report supports. + type MaxOffenders: Get; + + /// The session keys. + type SessionKeys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize; + + /// The session keys handler that requests from this pallet. + type SessionKeysHandler: SessionKeysHandler< + AccountId = Self::AccountId, + Keys = Self::SessionKeys, + Proof = SessionKeyProof, + >; + + /// The max mumber of validators a [`Self::ValidatorSetHandler`] can operate. + type MaxValidators: Get; + + /// An handler for when a new validator set must be enacted. + type ValidatorSetHandler: ValidatorSetHandler< + AccountId = Self::AccountId, + MaxValidators = Self::MaxValidators, + >; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::event] + pub enum Event {} + + #[pallet::error] + #[derive(PartialEq)] + pub enum Error { + /// Session key set request was unsuccessful. + SetKeys, + /// Session key purge request was unsuccessful. + PurgeKeys, + } + + #[pallet::call] + impl Pallet { + /// Sets the session key(s) of the function caller to `keys`. + #[pallet::call_index(0)] + pub fn set_validator_keys( + origin: OriginFor, + session_keys: T::SessionKeys, + proof: Vec, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + // TODO(gpestana): any pre-checks? + + ::set_keys(who, session_keys, proof) + .map_err(|_| Error::::SetKeys)?; + + todo!() + } + + /// Removes any session key(s) of the function caller. + #[pallet::call_index(1)] + pub fn purge_validator_keys(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + // TODO(gpestana): any pre-checks? + + ::purge_keys(who) + .map_err(|_| Error::::PurgeKeys)?; + + Ok(()) + } + + /// Receives block authoring information and redirects it to staking. + /// + /// Only `RuntimeOrigin::Root` is authorized to call this extrinsic. + #[pallet::call_index(2)] + pub fn author(origin: OriginFor, author: T::AccountId) -> DispatchResult { + let _ = ensure_root(origin); + + // TODO: (perhaps?) instead of calling directly staking, batch authoring points instead + // and use `on_initialize` or some other mechanism to notify staking of a set of + // authoring notes. + + >::note_author(author); + + Ok(()) + } + + /// Receives offence reports and redirects them to staking. + /// + /// Only `RuntimeOrigin::Root` is authorized to call this extrinsic. + #[pallet::call_index(3)] + pub fn report_offence( + origin: OriginFor, + offenders: BoundedVec, T::MaxOffenders>, + slash_fraction: BoundedVec, + session: SessionIndex, + ) -> DispatchResult { + let _ = ensure_root(origin); + + let _weight = >::on_offence( + &offenders, + &slash_fraction, + session, + ); + + Ok(()) + } + } +} + +pub mod traits { + use super::*; + + /// Something that handles the management of session keys. + /// + /// It allows to define the behaviour when new session keys are set and purged. + pub trait SessionKeysHandler { + /// The account ID type. + type AccountId; + /// The keys type that is supported by the manager. + type Keys; + /// The proof type for [`Self::Keys`]. + type Proof; + /// The error type. + type Error; + + fn set_keys( + who: Self::AccountId, + session_keys: Self::Keys, + proof: Self::Proof, + ) -> Result<(), Self::Error>; + + fn purge_keys(who: Self::AccountId) -> Result<(), Self::Error>; + } + + /// Something that handles a new validator set. + pub trait ValidatorSetHandler { + /// The account ID type. + type AccountId; + /// The max number of validators the provider can return. + type MaxValidators; + /// The error type. + type Error; + + /// A new validator set is ready. + fn new_validator_set( + session_index: SessionIndex, + validator_set: sp_runtime::BoundedVec, + ) -> Result<(), Self::Error>; + } +} + +pub mod impls { + use std::marker::PhantomData; + + use super::{ + pallet::{Config, Error as PalletError}, + *, + }; + + /// Propagates session key management actions and data through XCM. + pub struct SessionKeysHandlerXCM(PhantomData); + + impl SessionKeysHandler for SessionKeysHandlerXCM { + type AccountId = T::AccountId; + type Keys = T::SessionKeys; + type Proof = SessionKeyProof; + type Error = PalletError; + + fn set_keys( + _who: Self::AccountId, + _session_keys: Self::Keys, + _proof: Self::Proof, + ) -> Result<(), Self::Error> { + todo!() + } + + fn purge_keys(_who: Self::AccountId) -> Result<(), Self::Error> { + todo!() + } + } + + /// Propagates a new set of validators through XCM. + pub struct ValidatorSetHandlerXCM(PhantomData); + + impl ValidatorSetHandler for ValidatorSetHandlerXCM { + type AccountId = T::AccountId; + type MaxValidators = T::MaxValidators; + type Error = PalletError; + + fn new_validator_set( + _session: SessionIndex, + _validator_set: sp_runtime::BoundedVec, + ) -> Result<(), Self::Error> { + // TODO: consider doing batching, buffering, etc. + + /* + // TODO: preparing and sending the XCM messages should probably be part of the + // parachain's runtime config, not here. + + let new_validator_set_call = RelayRuntimePallets::StakingClient(validator_set, session_index); + let call_weight: Weight = Default::default(); + + let message = Xcm( + Vec![Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + origin_kind: OriginKind::Native, + require_weight_at_most: call_weight, + call: new_validator_set_call, + ] + ); + + match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { + Ok(_) => (), + Err(_) => (), + }; + */ + + Ok(()) + } + } +} diff --git a/substrate/frame/staking/rc-client/src/mock.rs b/substrate/frame/staking/rc-client/src/mock.rs new file mode 100644 index 000000000000..92637e95e7d5 --- /dev/null +++ b/substrate/frame/staking/rc-client/src/mock.rs @@ -0,0 +1,91 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use crate::{self as pallet_rc_client, *}; + +use frame_support::{derive_impl, parameter_types}; +use sp_runtime::{impl_opaque_keys, testing::UintAuthorityId, Perbill}; +use sp_staking::{offence::OnOffenceHandler, SessionIndex}; + +use std::collections::BTreeMap; + +type Block = frame_system::mocking::MockBlock; + +type AccountId = u64; +type AuthorshipPoints = u32; +type Weight = sp_runtime::Weight; + +frame_support::construct_runtime!( + pub enum Test { + System: frame_system, + RCClient: pallet_rc_client, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Block = Block; +} + +impl_opaque_keys! { + pub struct MockSessionKeys { + pub dummy: UintAuthorityId, + } +} + +parameter_types! { + pub static MaxOffenders: u32 = 5; + pub static MaxValidators: u32 = 10; +} + +impl crate::pallet::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Staking = MockStaking; + type MaxOffenders = MaxOffenders; + type MaxValidators = MaxValidators; + type SessionKeys = MockSessionKeys; + type SessionKeysHandler = impls::SessionKeysHandlerXCM; + type ValidatorSetHandler = impls::ValidatorSetHandlerXCM; +} + +parameter_types! { + pub static AuthoringState: Vec<(AccountId, SessionIndex, AuthorshipPoints)> = vec![]; + pub static OffencesState: BTreeMap = BTreeMap::new(); +} + +pub struct MockStaking; + +impl AuthorshipEventHandler for MockStaking { + fn note_author(author: AccountId) { + // one point per authoring. + AuthoringState::mutate(|s| { + let session = 0; // TODO + s.push((author, session, 1)) + }); + } +} + +impl OnOffenceHandler for MockStaking { + fn on_offence( + _offenders: &[sp_staking::offence::OffenceDetails], + _slash_fraction: &[Perbill], + _session: SessionIndex, + ) -> Weight { + todo!() + } +} diff --git a/substrate/frame/staking/rc-client/src/tests.rs b/substrate/frame/staking/rc-client/src/tests.rs new file mode 100644 index 000000000000..4bbffbf15ce0 --- /dev/null +++ b/substrate/frame/staking/rc-client/src/tests.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use crate::mock::*; + +#[cfg(test)] +mod tests {} From 7c88c55b3088450356721ef767bab968b5429bdc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 25 Oct 2024 13:31:04 +0200 Subject: [PATCH 2/3] new approach --- Cargo.lock | 2 + substrate/frame/staking/rc-client/Cargo.toml | 5 + substrate/frame/staking/rc-client/src/lib.rs | 255 +++++++++--------- substrate/frame/staking/rc-client/src/mock.rs | 87 +++++- 4 files changed, 209 insertions(+), 140 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73f8609cffc7..8207504bb0cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12770,6 +12770,8 @@ dependencies = [ "frame-support", "frame-system", "pallet-authorship", + "pallet-session", + "pallet-staking", "parity-scale-codec", "scale-info", "sp-runtime 31.0.1", diff --git a/substrate/frame/staking/rc-client/Cargo.toml b/substrate/frame/staking/rc-client/Cargo.toml index 3d5c1b5e9727..d1b625f732ce 100644 --- a/substrate/frame/staking/rc-client/Cargo.toml +++ b/substrate/frame/staking/rc-client/Cargo.toml @@ -24,6 +24,9 @@ sp-staking = { default-features = true, workspace = true } frame-support = { default-features = true, workspace = true } frame-system = { default-features = true, workspace = true } pallet-authorship = { workspace = true } +pallet-session = { workspace = true } +pallet-staking = { workspace = true } + [features] default = ["std"] @@ -36,4 +39,6 @@ std = [ "frame-support/std", "frame-system/std", "pallet-authorship/std", + "pallet-session/std", + "pallet-staking/std", ] diff --git a/substrate/frame/staking/rc-client/src/lib.rs b/substrate/frame/staking/rc-client/src/lib.rs index 495d090075b4..0f3f388723db 100644 --- a/substrate/frame/staking/rc-client/src/lib.rs +++ b/substrate/frame/staking/rc-client/src/lib.rs @@ -93,8 +93,6 @@ mod mock; #[cfg(test)] mod tests; -/// Re-exports this crate's trait implementations. -pub use impls::*; /// Re-exports this crate's traits. pub use traits::*; @@ -106,51 +104,135 @@ use sp_runtime::{ }; use pallet_authorship::EventHandler as AuthorshipEventHandler; +use pallet_session::SessionManager; +use pallet_staking::SessionInterface; use sp_staking::{ offence::{OffenceDetails, OnOffenceHandler}, SessionIndex, }; -/// The type of session key proof expected by this pallet. -pub(crate) type SessionKeyProof = Vec; +/// An account ID type for the runtime. +pub(crate) type AccountIdOf = ::AccountId; +/// The type of session keys proof expected by this pallet. +pub(crate) type SessionKeysProof = Vec; + +// TODO: +// - This pallet is the session manager from Staking (not RC-session-pallet) +// - All the outbound actions should be a *existing* or new trait +// RC Session (impl SessionManager) - Broker(impl SessionManager) - Staking(impl SessionManager) +// - Rename pallet to "Broker" or something else (client is actually the type that sends the XCM, +// which is implemented in the runtime config). +// +// pallet_session needs two traits as config +// - Session Manager: Staking +// - Session Handler: (Babe, grandpa, para_validator, para_assignment, authority_discover, beefy.) +// (rename: relay-chain session proxy OR relay-chain proxy?) +// - + track relay-chain session here (mapping RC session <> Staking session here) +// +// - New inbound message: +// - When a new session changes (potentially with a delay) in the RC +// +// pallet-rc-proxy/broker +// - type AsyncSessionBroker +// - type OffenceBroker +// +// two traits: +// - XCM relay-chain client (outbound trait) `trait RelayChainClient` +// - XCM inbound trait `trait StakingClient` +// - maybe add these traits in `sp-staking` (later) + +pub mod traits { + use sp_staking::offence::OffenceReportSystem; + + use super::*; + + /// Marker trait that encapsulates all the behaviour that an async broker (staking -> relay + /// chain) must implement. + pub trait AsyncBroker: AsyncSessionBroker + AsyncOffenceBroker {} + + /// Something that implements a session broker. + /// + /// It supports the following functionality: + /// + /// * Handles setting and purging validator session keys. + /// * Handles a new set of validator keys computed by staking. + /// * Implements the [`SessionInterface`] trait to manage sessions. + pub trait AsyncSessionBroker: SessionInterface { + /// The account ID type. + type AccountId; + + /// The session keys type that is supported by staking and the relay chain. + type SessionKeys; + + /// The proof type for [`Self::SessionKeys`]. + type SessionKeysProof; + + /// A bound for the max number of validators in the set. + type MaxValidatorSet; + + /// The error type. + type Error; + + // Sets the validator session keys. + fn set_session_keys( + who: Self::AccountId, + session_keys: Self::SessionKeys, + proof: Self::SessionKeysProof, + ) -> Result<(), Self::Error>; + + /// Purges the validator session keys. + fn purge_session_keys(who: Self::AccountId) -> Result<(), Self::Error>; + + /// A new validator set has been computed and it is ready to be communicated to the + /// relay-chain. + fn new_validator_set( + session_index: SessionIndex, + validator_set: sp_runtime::BoundedVec, + ) -> Result<(), Self::Error>; + } + + /// Something that implement a offence broker for staking. + pub trait AsyncOffenceBroker {} +} #[frame_support::pallet(dev_mode)] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; + /// The in-code storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: IsType<::RuntimeEvent> + From>; - /// The staking interface. - type Staking: AuthorshipEventHandler> + /// The staking type to redirect inbound calls. + type Staking: SessionManager + + AuthorshipEventHandler> + OnOffenceHandler; /// The max offenders a report supports. type MaxOffenders: Get; + /// The max mumber of validators a [`Self::ValidatorSetHandler`] can operate. + type MaxValidatorSet: Get; + /// The session keys. type SessionKeys: OpaqueKeys + Member + Parameter + MaybeSerializeDeserialize; - /// The session keys handler that requests from this pallet. - type SessionKeysHandler: SessionKeysHandler< - AccountId = Self::AccountId, - Keys = Self::SessionKeys, - Proof = SessionKeyProof, - >; - - /// The max mumber of validators a [`Self::ValidatorSetHandler`] can operate. - type MaxValidators: Get; - - /// An handler for when a new validator set must be enacted. - type ValidatorSetHandler: ValidatorSetHandler< + /// The async broker that handles the communication and logic with the relay-chain by + /// sending outbound XCM messages. + type RelayChainClient: AsyncBroker< AccountId = Self::AccountId, - MaxValidators = Self::MaxValidators, + MaxValidatorSet = Self::MaxValidatorSet, + SessionKeys = Self::SessionKeys, + SessionKeysProof = SessionKeysProof, >; } #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::event] @@ -165,6 +247,11 @@ pub mod pallet { PurgeKeys, } + /// Keepts track of the active validator set, as seen by the relay chain. + #[pallet::storage] + pub type ActiveValidators = + StorageValue<_, BoundedVec, T::MaxValidatorSet>>; + #[pallet::call] impl Pallet { /// Sets the session key(s) of the function caller to `keys`. @@ -177,10 +264,10 @@ pub mod pallet { let who = ensure_signed(origin)?; // TODO(gpestana): any pre-checks? - ::set_keys(who, session_keys, proof) - .map_err(|_| Error::::SetKeys)?; + ::set_session_keys(who, session_keys, proof) + .map_err(|_| Error::::PurgeKeys)?; - todo!() + Ok(()) } /// Removes any session key(s) of the function caller. @@ -189,7 +276,7 @@ pub mod pallet { let who = ensure_signed(origin)?; // TODO(gpestana): any pre-checks? - ::purge_keys(who) + ::purge_session_keys(who) .map_err(|_| Error::::PurgeKeys)?; Ok(()) @@ -202,10 +289,6 @@ pub mod pallet { pub fn author(origin: OriginFor, author: T::AccountId) -> DispatchResult { let _ = ensure_root(origin); - // TODO: (perhaps?) instead of calling directly staking, batch authoring points instead - // and use `on_initialize` or some other mechanism to notify staking of a set of - // authoring notes. - >::note_author(author); Ok(()) @@ -234,117 +317,19 @@ pub mod pallet { } } -pub mod traits { - use super::*; - - /// Something that handles the management of session keys. - /// - /// It allows to define the behaviour when new session keys are set and purged. - pub trait SessionKeysHandler { - /// The account ID type. - type AccountId; - /// The keys type that is supported by the manager. - type Keys; - /// The proof type for [`Self::Keys`]. - type Proof; - /// The error type. - type Error; - - fn set_keys( - who: Self::AccountId, - session_keys: Self::Keys, - proof: Self::Proof, - ) -> Result<(), Self::Error>; - - fn purge_keys(who: Self::AccountId) -> Result<(), Self::Error>; - } - - /// Something that handles a new validator set. - pub trait ValidatorSetHandler { - /// The account ID type. - type AccountId; - /// The max number of validators the provider can return. - type MaxValidators; - /// The error type. - type Error; - - /// A new validator set is ready. - fn new_validator_set( - session_index: SessionIndex, - validator_set: sp_runtime::BoundedVec, - ) -> Result<(), Self::Error>; +impl SessionInterface> for Pallet { + fn disable_validator(validator_index: u32) -> bool { + >>::disable_validator( + validator_index, + ) } -} - -pub mod impls { - use std::marker::PhantomData; - - use super::{ - pallet::{Config, Error as PalletError}, - *, - }; - - /// Propagates session key management actions and data through XCM. - pub struct SessionKeysHandlerXCM(PhantomData); - - impl SessionKeysHandler for SessionKeysHandlerXCM { - type AccountId = T::AccountId; - type Keys = T::SessionKeys; - type Proof = SessionKeyProof; - type Error = PalletError; - - fn set_keys( - _who: Self::AccountId, - _session_keys: Self::Keys, - _proof: Self::Proof, - ) -> Result<(), Self::Error> { - todo!() - } - fn purge_keys(_who: Self::AccountId) -> Result<(), Self::Error> { - todo!() - } + // TODO: this trait needs to be bounded. + fn validators() -> Vec> { + ActiveValidators::::get().map(|v| v.into()).unwrap_or_default() } - /// Propagates a new set of validators through XCM. - pub struct ValidatorSetHandlerXCM(PhantomData); - - impl ValidatorSetHandler for ValidatorSetHandlerXCM { - type AccountId = T::AccountId; - type MaxValidators = T::MaxValidators; - type Error = PalletError; - - fn new_validator_set( - _session: SessionIndex, - _validator_set: sp_runtime::BoundedVec, - ) -> Result<(), Self::Error> { - // TODO: consider doing batching, buffering, etc. - - /* - // TODO: preparing and sending the XCM messages should probably be part of the - // parachain's runtime config, not here. - - let new_validator_set_call = RelayRuntimePallets::StakingClient(validator_set, session_index); - let call_weight: Weight = Default::default(); - - let message = Xcm( - Vec![Instruction::UnpaidExecution { - weight_limit: WeightLimit::Unlimited, - check_origin: None, - }, - origin_kind: OriginKind::Native, - require_weight_at_most: call_weight, - call: new_validator_set_call, - ] - ); - - match PolkadotXcm::send_xcm(Here, Location::parent(), message.clone()) { - Ok(_) => (), - Err(_) => (), - }; - */ - - Ok(()) - } + fn prune_historical_up_to(up_to: SessionIndex) { + >>::prune_historical_up_to(up_to); } } diff --git a/substrate/frame/staking/rc-client/src/mock.rs b/substrate/frame/staking/rc-client/src/mock.rs index 92637e95e7d5..d084d63cab5c 100644 --- a/substrate/frame/staking/rc-client/src/mock.rs +++ b/substrate/frame/staking/rc-client/src/mock.rs @@ -19,7 +19,10 @@ use crate::{self as pallet_rc_client, *}; use frame_support::{derive_impl, parameter_types}; use sp_runtime::{impl_opaque_keys, testing::UintAuthorityId, Perbill}; -use sp_staking::{offence::OnOffenceHandler, SessionIndex}; +use sp_staking::{ + offence::{OffenceReportSystem, OnOffenceHandler}, + SessionIndex, +}; use std::collections::BTreeMap; @@ -50,17 +53,77 @@ impl_opaque_keys! { parameter_types! { pub static MaxOffenders: u32 = 5; - pub static MaxValidators: u32 = 10; + pub static MaxValidatorSet: u32 = 10; } impl crate::pallet::Config for Test { type RuntimeEvent = RuntimeEvent; type Staking = MockStaking; type MaxOffenders = MaxOffenders; - type MaxValidators = MaxValidators; + type MaxValidatorSet = MaxValidatorSet; type SessionKeys = MockSessionKeys; - type SessionKeysHandler = impls::SessionKeysHandlerXCM; - type ValidatorSetHandler = impls::ValidatorSetHandlerXCM; + type RelayChainClient = MockRelayClient; +} + +parameter_types! { + // Stores receved messages from `MockRelayClient` for testing. + // pub static SessionMessages: BtreeMap> = BTreeMap::new(); +} + +pub(crate) struct MockRelay; +impl MockRelay { + fn message() { + // TODO: receive an XCM transact message sent by `MockRelayClient` and processa and store it + // depending on the used call. + } +} + +pub struct MockRelayClient; +impl AsyncBroker for MockRelayClient {} + +impl AsyncSessionBroker for MockRelayClient { + type AccountId = AccountId; + type SessionKeys = MockSessionKeys; + type SessionKeysProof = SessionKeysProof; + type MaxValidatorSet = MaxValidatorSet; + type Error = &'static str; + + fn set_session_keys( + _who: Self::AccountId, + _session_keys: Self::SessionKeys, + _proof: Self::SessionKeysProof, + ) -> Result<(), Self::Error> { + // TODO: build XCM and "send" it to MockRelay + todo!() + } + fn purge_session_keys(_who: Self::AccountId) -> Result<(), Self::Error> { + // TODO: build XCM and "send" ut to MockRelay + todo!() + } + fn new_validator_set( + _session_index: SessionIndex, + _validator_set: sp_runtime::BoundedVec, + ) -> Result<(), Self::Error> { + // TODO: build XCM and "send" ut to MockRelay + todo!() + } +} + +impl AsyncOffenceBroker for MockRelayClient {} + +impl SessionInterface for MockRelayClient +where + RCClient: SessionInterface, +{ + fn validators() -> Vec { + >::validators() + } + fn disable_validator(validator_index: u32) -> bool { + >::disable_validator(validator_index) + } + fn prune_historical_up_to(up_to: SessionIndex) { + >::prune_historical_up_to(up_to) + } } parameter_types! { @@ -69,6 +132,20 @@ parameter_types! { } pub struct MockStaking; +impl SessionManager for MockStaking { + fn new_session(_new_index: SessionIndex) -> Option> { + todo!() + } + fn end_session(_end_index: SessionIndex) { + todo!() + } + fn start_session(_start_index: SessionIndex) { + todo!() + } + fn new_session_genesis(_new_index: SessionIndex) -> Option> { + todo!() + } +} impl AuthorshipEventHandler for MockStaking { fn note_author(author: AccountId) { From fd287f5079e566133e61e9cdc9a02b629422743c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 28 Oct 2024 10:43:20 +0100 Subject: [PATCH 3/3] stabilises types and traits; adds integration tests framework --- Cargo.lock | 25 ++ Cargo.toml | 2 + substrate/frame/staking/rc-client/Cargo.toml | 3 + .../rc-client/integration-tests/Cargo.toml | 58 ++++ .../rc-client/integration-tests/src/lib.rs | 29 ++ .../rc-client/integration-tests/src/mock.rs | 278 ++++++++++++++++++ substrate/frame/staking/rc-client/src/lib.rs | 142 +++++---- substrate/frame/staking/rc-client/src/mock.rs | 87 ++++-- .../frame/staking/rc-client/src/tests.rs | 21 +- 9 files changed, 567 insertions(+), 78 deletions(-) create mode 100644 substrate/frame/staking/rc-client/integration-tests/Cargo.toml create mode 100644 substrate/frame/staking/rc-client/integration-tests/src/lib.rs create mode 100644 substrate/frame/staking/rc-client/integration-tests/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 8207504bb0cc..6748bffad5be 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12774,9 +12774,34 @@ dependencies = [ "pallet-staking", "parity-scale-codec", "scale-info", + "sp-io 30.0.0", "sp-runtime 31.0.1", "sp-staking", "sp-std 14.0.0", + "sp-tracing 16.0.0", +] + +[[package]] +name = "pallet-staking-rc-client-tests" +version = "0.0.1" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "pallet-authorship", + "pallet-balances", + "pallet-session", + "pallet-staking", + "pallet-staking-rc-client", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-core 28.0.0", + "sp-io 30.0.0", + "sp-runtime 31.0.1", + "sp-staking", + "sp-std 14.0.0", + "sp-tracing 16.0.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c1888734ec60..05dfc45576d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -413,6 +413,7 @@ members = [ "substrate/frame/society", "substrate/frame/staking", "substrate/frame/staking/rc-client", + "substrate/frame/staking/rc-client/integration-tests", "substrate/frame/staking/reward-curve", "substrate/frame/staking/reward-fn", "substrate/frame/staking/runtime-api", @@ -980,6 +981,7 @@ pallet-skip-feeless-payment = { path = "substrate/frame/transaction-payment/skip pallet-society = { path = "substrate/frame/society", default-features = false } pallet-staking = { path = "substrate/frame/staking", default-features = false } pallet-staking-rc-client = { path = "substrate/frame/staking/rc-client", default-features = false } +pallet-staking-rc-client-tests = { path = "substrate/frame/staking/rc-client/integration-tests", default-features = false } pallet-staking-reward-curve = { path = "substrate/frame/staking/reward-curve", default-features = false } pallet-staking-reward-fn = { path = "substrate/frame/staking/reward-fn", default-features = false } pallet-staking-runtime-api = { path = "substrate/frame/staking/runtime-api", default-features = false } diff --git a/substrate/frame/staking/rc-client/Cargo.toml b/substrate/frame/staking/rc-client/Cargo.toml index d1b625f732ce..061df2f77f90 100644 --- a/substrate/frame/staking/rc-client/Cargo.toml +++ b/substrate/frame/staking/rc-client/Cargo.toml @@ -27,6 +27,9 @@ pallet-authorship = { workspace = true } pallet-session = { workspace = true } pallet-staking = { workspace = true } +[dev-dependencies] +sp-tracing = { workspace = true } +sp-io = { workspace = true } [features] default = ["std"] diff --git a/substrate/frame/staking/rc-client/integration-tests/Cargo.toml b/substrate/frame/staking/rc-client/integration-tests/Cargo.toml new file mode 100644 index 000000000000..213fc6cfa747 --- /dev/null +++ b/substrate/frame/staking/rc-client/integration-tests/Cargo.toml @@ -0,0 +1,58 @@ +[package] +name = "pallet-staking-rc-client-tests" +version = "0.0.1" +authors.workspace = true +edition.workspace = true +license = "Apache-2.0" +homepage.workspace = true +repository.workspace = true +description = "FRAME integration tests for pallet staking relay chain client" +publish = false + +[lints] +workspace = true + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { workspace = true } +scale-info = { features = ["derive"], workspace = true } + +sp-runtime = { default-features = true, workspace = true } +sp-std = { default-features = true, workspace = true } +sp-io = { default-features = true, workspace = true } +sp-core = { default-features = true, workspace = true } +sp-tracing = { default-features = true, workspace = true } +sp-staking = { default-features = true, workspace = true } +frame-support = { default-features = true, workspace = true } +frame-system = { default-features = true, workspace = true } +pallet-session = { workspace = true } +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +pallet-authorship = { workspace = true } +pallet-staking = { workspace = true } +pallet-staking-rc-client = { workspace = true } +frame-election-provider-support = { workspace = true } + + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "sp-core/std", + "sp-staking/std", + "frame-support/std", + "frame-system/std", + "pallet-balances/std", + "pallet-timestamp/std", + "pallet-session/std", + "pallet-authorship/std", + "pallet-staking/std", + "pallet-staking-rc-client/std", + "frame-election-provider-support/std", +] diff --git a/substrate/frame/staking/rc-client/integration-tests/src/lib.rs b/substrate/frame/staking/rc-client/integration-tests/src/lib.rs new file mode 100644 index 000000000000..999803058e20 --- /dev/null +++ b/substrate/frame/staking/rc-client/integration-tests/src/lib.rs @@ -0,0 +1,29 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! # Staking Relay chain client tests + +mod mock; + +use crate::mock::*; + +#[test] +fn test_setup_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(true); + }) +} diff --git a/substrate/frame/staking/rc-client/integration-tests/src/mock.rs b/substrate/frame/staking/rc-client/integration-tests/src/mock.rs new file mode 100644 index 000000000000..d54d4706bbd5 --- /dev/null +++ b/substrate/frame/staking/rc-client/integration-tests/src/mock.rs @@ -0,0 +1,278 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use frame_support::{derive_impl, parameter_types, traits::FindAuthor}; +use pallet_session::{SessionHandler, SessionManager, ShouldEndSession}; +use pallet_staking::{SessionInterface, UseNominatorsAndValidatorsMap, UseValidatorsMap}; +use pallet_staking_rc_client::{AsyncBroker, AsyncOffenceBroker, AsyncSessionBroker}; +use sp_core::{crypto::KeyTypeId, ConstU64}; +use sp_runtime::{impl_opaque_keys, testing::UintAuthorityId}; +use sp_staking::SessionIndex; + +type Block = frame_system::mocking::MockBlock; +type BlockNumber = u64; +type AccountId = u64; +type Balance = u128; + +pub const KEY_TYPE_IDS: KeyTypeId = KeyTypeId(*b"para"); + +/// Author of block is always 11 +pub struct Author11; +impl FindAuthor for Author11 { + fn find_author<'a, I>(_digests: I) -> Option + where + I: 'a + IntoIterator, + { + Some(11) + } +} + +frame_support::construct_runtime!( + pub enum Runtime { + // pallets that will be part of both relay-chain and parachain runtimes. Using the same in + // the tests for simplicity. + System: frame_system, + Balances: pallet_balances, + + // relay-chain pallets. + RCSession: pallet_session, + RCHistorical: pallet_session::historical, + RCAuthorship: pallet_authorship, + + // pallets in a different consensus system than relay-chain. The Client pallet is used as a + // broker for the staking to communcate (one way, async) with the staking pallet. + Staking: pallet_staking, + Timestamp: pallet_timestamp, + Client: pallet_staking_rc_client, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +parameter_types! { + pub static ExistentialDeposit: Balance = 1; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for Runtime { + type MaxLocks = frame_support::traits::ConstU32<1024>; + type Balance = Balance; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; +} + +impl pallet_timestamp::Config for Runtime { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<5>; + type WeightInfo = (); +} + +impl_opaque_keys! { + pub struct SessionKeys { + pub dummy: UintAuthorityId, + } +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = TransparentAccountConvertion; + type ShouldEndSession = TestShouldEndSession; + type NextSessionRotation = (); + // implemented by the relay-chain side staking client pallet. + type SessionManager = TestSessionManager; + // implemented by the relay-chain side staking client pallet. + type SessionHandler = TestSessionHandler; + type Keys = SessionKeys; + type WeightInfo = (); +} + +impl pallet_session::historical::Config for Runtime { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} +impl pallet_authorship::Config for Runtime { + type FindAuthor = Author11; + // implemented by the relay-chain side staking client pallet. + type EventHandler = Staking; +} + +pub struct TransparentAccountConvertion; +impl sp_runtime::traits::Convert> for TransparentAccountConvertion { + fn convert(a: AccountId) -> Option { + Some(a) + } +} + +pub struct TestShouldEndSession; +impl ShouldEndSession for TestShouldEndSession { + fn should_end_session(_now: BlockNumber) -> bool { + todo!() + } +} + +pub struct TestSessionManager; +impl SessionManager for TestSessionManager { + fn new_session(_new_index: SessionIndex) -> Option> { + todo!() + } + fn end_session(_end_index: SessionIndex) { + todo!() + } + fn start_session(_start_index: SessionIndex) { + todo!() + } + fn new_session_genesis(_new_index: SessionIndex) -> Option> { + todo!() + } +} + +pub struct TestSessionHandler; +impl SessionHandler for TestSessionHandler { + const KEY_TYPE_IDS: &'static [sp_runtime::KeyTypeId] = &[KEY_TYPE_IDS]; + + fn on_disabled(_validator_index: u32) { + todo!() + } + fn on_new_session( + _changed: bool, + _validators: &[(AccountId, Ks)], + _queued_validators: &[(AccountId, Ks)], + ) { + todo!() + } + fn on_genesis_session(_validators: &[(AccountId, Ks)]) { + todo!() + } + fn on_before_session_ending() { + todo!() + } +} + +parameter_types! { + pub static MaxWinners: u32 = 10; +} + +#[derive_impl(pallet_staking::config_preludes::TestDefaultConfig)] +impl pallet_staking::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UnixTime = Timestamp; + type ElectionProvider = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, MaxWinners)>; + type GenesisElectionProvider = Self::ElectionProvider; + type AdminOrigin = frame_system::EnsureRoot; + type EraPayout = (); + type VoterList = UseNominatorsAndValidatorsMap; + type TargetList = UseValidatorsMap; + + // session interfaces are implemented by the rc-client pallet. + + // session related types must live in the parachain (ie, rc-client acts as broker). + type NextNewSession = Client; + type SessionInterface = Client; +} + +parameter_types! { + pub static MaxOffenders: u32 = 10; +} + +impl pallet_staking_rc_client::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type FullValidatorId = pallet_staking::Exposure; + type Staking = Staking; + type MaxOffenders = MaxOffenders; + type MaxValidatorSet = MaxWinners; + type SessionKeys = SessionKeys; + type RelayChainClient = Broker; +} + +/// Type that implements the XCM communication between the rc-client and the relay chain. +pub struct Broker; +impl AsyncBroker for Broker {} + +impl AsyncSessionBroker for Broker { + type AccountId = AccountId; + type SessionKeys = SessionKeys; + type SessionKeysProof = pallet_staking_rc_client::SessionKeysProof; + type MaxValidatorSet = MaxWinners; + type Error = &'static str; + + fn set_session_keys( + _who: Self::AccountId, + _session_keys: Self::SessionKeys, + _proof: Self::SessionKeysProof, + ) -> Result<(), Self::Error> { + todo!() + } + + fn purge_session_keys(_who: Self::AccountId) -> Result<(), Self::Error> { + todo!() + } + + fn new_validator_set( + _session_index: SessionIndex, + _validator_set: sp_runtime::BoundedVec, + ) -> Result<(), Self::Error> { + todo!() + } +} + +impl SessionInterface for Broker { + fn validators() -> Vec { + todo!() + } + fn disable_validator(_validator_index: u32) -> bool { + todo!() + } + fn prune_historical_up_to(_up_to: SessionIndex) { + todo!() + } +} + +impl AsyncOffenceBroker for Broker {} + +#[derive(Default)] +pub struct ExtBuilder {} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + use sp_runtime::BuildStorage; + + sp_tracing::try_init_simple(); + + let mut storage = + frame_system::GenesisConfig::::default().build_storage().unwrap(); + + // set pallet's genesis state + sp_io::TestExternalities::from(storage) + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); + + let mut ext = self.build(); + ext.execute_with(test); + } +} diff --git a/substrate/frame/staking/rc-client/src/lib.rs b/substrate/frame/staking/rc-client/src/lib.rs index 0f3f388723db..2ddc21ecb775 100644 --- a/substrate/frame/staking/rc-client/src/lib.rs +++ b/substrate/frame/staking/rc-client/src/lib.rs @@ -18,36 +18,75 @@ //! # Staking Relay chain client //! //! The Staking Relay chain client is used as a interface between the Staking pallet and an external -//! consensus system (e.g. Polkadot's Relay chain). +//! consensus system (e.g. Polkadot's Relay chain). The main goalof this pallet is to abstract the +//! relay chain interfaces locally and handle the requests from staking in a sync and async manner. //! //! ## Overview //! //! The Staking Relay chain client (`rc-client` pallet) implements an abstraction for the i/o //! of the staking pallet. This abstraction is especially helpfull when the staking pallet -//! and the its "consumers" live in different consensus systems. Most notably, this pallet handles +//! and the "consumers" live in different consensus systems. Most notably, this pallet handles //! the following i/o tasks: //! -//! - Communicates a new set of validators to a party an external consensus system; -//! - Communicates setting and purging validator keys to an external consensus system; -//! - Receives and pre-processes offence reports from a trusteed external consensus system; -//! - Receives and pre-processes block authoring reports; +//! - Communicates a new set of validators to a party an external consensus system by implementing +//! [`crate::AsyncSessionBroker`]. +//! - Communicates setting and purging validator keys to an external consensus system by +//! implementing [`crate::AsyncSessionBroker`]. +//! - Receives and pre-processes offence reports from a trusted external consensus system, handled +//! by the implementation of [`crate::AsyncOffenceBroker`]. +//! - Receives and pre-processes block authoring reports through the extrinsic [`Call::author`]; //! //! This pallet also exposes an extrinsic for signed origins to report staking offences which are -//! communicated to both the staking pallet and an external consensus system. +//! communicated to both the staking pallet and an external consensus system, as implemented by +//! [`crate::AsyncOffenceBroker`]. //! //! In sum, this pallet works as an adapter pallet that can be used for the staking pallet to //! communicate with external consensus systems. //! -//! ## Inbound +//! ## Pallet structure and Inbound vs Outbound requests +//! +//! As a rule of thumb, all the **inbound** requests are handled by extrinsics in this pallet. The +//! extrinsics are exposed for trusted or signed origins to *push* reports, requests and +//! information expected by the staking interface (e.g. offence reports, session key management, +//! etc). +//! +//! These extrinsics are used by external entities may may live in a different consensus system as +//! the staking interface to communicate with it (e.g. the relay chain in the context of staking +//! being part of the AssetHub system parachain). This pallet may pre-process external requests +//! before redirecting them to the staking interface. +//! +//! On the opposite direction, **outbout** requests from staking to external consensus systems are +//! implemented by the [`crate::AsyncBroker`] trait. The implementor is responsible to pre-process +//! requests and, potentially, forward the requests to an external consensus system (e.g. relay +//! chain). +//! +//! One of the design goals of the [`crate::AsyncBroker`] trait and implementation is to abstract +//! all the complexity derived from sync/async and the fact that the consumers and producers of the +//! data expected by staking *may* be in a different consensus system. From the staking implementor +//! POV, it should be transparent whether the broker is between e.g. the session manager or not. +//! +//! ## Async vs Sync communication +//! +//! This pallet is designed to support an async comminication channel between staking and the +//! session interfaces. This pallet should abstract the potential asynchronicity of the +//! channel to the staking interface. As such and when possible, this pallet caches information +//! from external consensus systems so that the requests from staking can be served in a +//! synchronous manner. For example, this pallet caches the current validator set as seen by the +//! external consensus system to be able to serve the from staking request without having to query +//! the external consensus system, which is ultimately the source of truth for the active validator +//! set. +//! +//! ## Inbound flow //! //! All the inbound request should be performed through extrinsics. External consensus systems may -//! call the inbound extrinsics through XCM transact. +//! call the inbound extrinsics through XCM Transact. //! //! ### Block authoring //! //! This pallet exposes an extrinsict, [`Call::author`], that processes block authoring events. //! Block authoring information can only be submitted by the runtime's root origin. Successfull -//! calls will be redirected to staking through the [`pallet_authorship::EventHandler`]) interface. +//! calls will be redirected to [`Config::Staking`] through the [`pallet_authorship::EventHandler`]) +//! interface. //! //! ### Offence reports //! @@ -55,7 +94,7 @@ //! reports. These reports can only be submitted by the runtime's root origin. Successfull calls //! will be redirected to staking through the [`sp_staking::offence::OnOffenceHandler`]) interface. //! -//! ## Outbound +//! ## Outbound flow //! //! ### Validator keys //! @@ -67,7 +106,7 @@ //! //! Callers can request to 1) set validator keys and 2) purge validator keys. These actions *may //! not be atomic*, i.e., the action and correspoding data may need to be propagated to an external -//! consensus system and take several blocks to be enacted. +//! consensus system and take several blocks to be enacted at sourch of truth. // //! The session key management actions exposed by this pallet are: //! @@ -78,11 +117,14 @@ //! //! ### New set of validator IDs //! -//! This pallet exposes a configuration type that implements the [`traits::ValidatorSetHandler`], +//! This pallet exposes a configuration type that implements the [`crate::AsyncSessionBroker`], //! which defines what to do when pallet staking has a new validator set ready. Note, however, that -//! this pallet is not the source of truth for validator sets. +//! this pallet is not the source of truth of the current validator set. //! //! ### Signed offence reports +//! +//! This pallet exposes a configuration type that implements the [`crate::AsyncOffenceBroker`], +//! which defines what to do when an external entity reports offences. #![cfg_attr(not(feature = "std"), no_std)] @@ -96,11 +138,11 @@ mod tests; /// Re-exports this crate's traits. pub use traits::*; -use frame_support::{dispatch::Parameter, weights::Weight}; +use frame_support::{dispatch::Parameter, traits::EstimateNextNewSession, weights::Weight}; use frame_system::pallet_prelude::*; use sp_runtime::{ traits::{Member, OpaqueKeys}, - Perbill, + BoundedVec, Perbill, }; use pallet_authorship::EventHandler as AuthorshipEventHandler; @@ -114,36 +156,11 @@ use sp_staking::{ /// An account ID type for the runtime. pub(crate) type AccountIdOf = ::AccountId; /// The type of session keys proof expected by this pallet. -pub(crate) type SessionKeysProof = Vec; - -// TODO: -// - This pallet is the session manager from Staking (not RC-session-pallet) -// - All the outbound actions should be a *existing* or new trait -// RC Session (impl SessionManager) - Broker(impl SessionManager) - Staking(impl SessionManager) -// - Rename pallet to "Broker" or something else (client is actually the type that sends the XCM, -// which is implemented in the runtime config). -// -// pallet_session needs two traits as config -// - Session Manager: Staking -// - Session Handler: (Babe, grandpa, para_validator, para_assignment, authority_discover, beefy.) -// (rename: relay-chain session proxy OR relay-chain proxy?) -// - + track relay-chain session here (mapping RC session <> Staking session here) -// -// - New inbound message: -// - When a new session changes (potentially with a delay) in the RC -// -// pallet-rc-proxy/broker -// - type AsyncSessionBroker -// - type OffenceBroker -// -// two traits: -// - XCM relay-chain client (outbound trait) `trait RelayChainClient` -// - XCM inbound trait `trait StakingClient` -// - maybe add these traits in `sp-staking` (later) +pub type SessionKeysProof = Vec; +/// The offender type expected by this pallet. +pub(crate) type OffenderOf = (::ValidatorId, ::FullValidatorId); pub mod traits { - use sp_staking::offence::OffenceReportSystem; - use super::*; /// Marker trait that encapsulates all the behaviour that an async broker (staking -> relay @@ -187,11 +204,11 @@ pub mod traits { /// relay-chain. fn new_validator_set( session_index: SessionIndex, - validator_set: sp_runtime::BoundedVec, + validator_set: BoundedVec, ) -> Result<(), Self::Error>; } - /// Something that implement a offence broker for staking. + /// Something that implements an offence broker for staking. pub trait AsyncOffenceBroker {} } @@ -207,10 +224,20 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: IsType<::RuntimeEvent> + From>; - /// The staking type to redirect inbound calls. + /// A stable ID for a validator, as expected by [`Self::Staking`]. + type ValidatorId: Member + + Parameter + + MaybeSerializeDeserialize + + MaxEncodedLen + + TryFrom; + + /// Full validator identification, as expected by [`Self::Staking`]. + type FullValidatorId: Parameter; + + /// The staking interface. type Staking: SessionManager + AuthorshipEventHandler> - + OnOffenceHandler; + + OnOffenceHandler; /// The max offenders a report supports. type MaxOffenders: Get; @@ -300,14 +327,14 @@ pub mod pallet { #[pallet::call_index(3)] pub fn report_offence( origin: OriginFor, - offenders: BoundedVec, T::MaxOffenders>, + offenders: BoundedVec>, T::MaxOffenders>, slash_fraction: BoundedVec, session: SessionIndex, ) -> DispatchResult { let _ = ensure_root(origin); let _weight = >::on_offence( - &offenders, + offenders.as_slice(), &slash_fraction, session, ); @@ -326,6 +353,9 @@ impl SessionInterface> for Pallet { // TODO: this trait needs to be bounded. fn validators() -> Vec> { + // this pallet keeps track of the active validators in the relay chain. The + // `ActiveValidators` map should be kept up to date as much as possible for this pallet, + // considering the async nature of the communication with the relay chain. ActiveValidators::::get().map(|v| v.into()).unwrap_or_default() } @@ -333,3 +363,15 @@ impl SessionInterface> for Pallet { >>::prune_historical_up_to(up_to); } } + +impl EstimateNextNewSession> for Pallet { + fn average_session_length() -> BlockNumberFor { + // this call should be sync; keep track of session length as much as possible for this + // pallet, considering the async nature of the communication with the relay chain. + todo!() + } + fn estimate_next_new_session(_: BlockNumberFor) -> (Option>, Weight) { + // this should be sync; + todo!() + } +} diff --git a/substrate/frame/staking/rc-client/src/mock.rs b/substrate/frame/staking/rc-client/src/mock.rs index d084d63cab5c..211997148677 100644 --- a/substrate/frame/staking/rc-client/src/mock.rs +++ b/substrate/frame/staking/rc-client/src/mock.rs @@ -19,11 +19,9 @@ use crate::{self as pallet_rc_client, *}; use frame_support::{derive_impl, parameter_types}; use sp_runtime::{impl_opaque_keys, testing::UintAuthorityId, Perbill}; -use sp_staking::{ - offence::{OffenceReportSystem, OnOffenceHandler}, - SessionIndex, -}; +use sp_staking::{offence::OnOffenceHandler, SessionIndex}; +use core::marker::PhantomData; use std::collections::BTreeMap; type Block = frame_system::mocking::MockBlock; @@ -35,7 +33,7 @@ type Weight = sp_runtime::Weight; frame_support::construct_runtime!( pub enum Test { System: frame_system, - RCClient: pallet_rc_client, + Client: pallet_rc_client, } ); @@ -51,6 +49,12 @@ impl_opaque_keys! { } } +impl From for MockSessionKeys { + fn from(dummy: UintAuthorityId) -> Self { + Self { dummy } + } +} + parameter_types! { pub static MaxOffenders: u32 = 5; pub static MaxValidatorSet: u32 = 10; @@ -58,7 +62,9 @@ parameter_types! { impl crate::pallet::Config for Test { type RuntimeEvent = RuntimeEvent; - type Staking = MockStaking; + type Staking = MockStaking; + type ValidatorId = AccountId; + type FullValidatorId = pallet_staking::Exposure; type MaxOffenders = MaxOffenders; type MaxValidatorSet = MaxValidatorSet; type SessionKeys = MockSessionKeys; @@ -67,14 +73,20 @@ impl crate::pallet::Config for Test { parameter_types! { // Stores receved messages from `MockRelayClient` for testing. - // pub static SessionMessages: BtreeMap> = BTreeMap::new(); + pub static Outbound: Vec = vec![]; +} + +#[derive(Clone, PartialEq, Debug)] +pub enum MockMessages { + SetSessionKeys((AccountId, MockSessionKeys, SessionKeysProof)), } pub(crate) struct MockRelay; impl MockRelay { - fn message() { - // TODO: receive an XCM transact message sent by `MockRelayClient` and processa and store it - // depending on the used call. + fn send_it(msg: MockMessages) { + Outbound::mutate(|o| { + o.push(msg); + }) } } @@ -89,12 +101,12 @@ impl AsyncSessionBroker for MockRelayClient { type Error = &'static str; fn set_session_keys( - _who: Self::AccountId, - _session_keys: Self::SessionKeys, - _proof: Self::SessionKeysProof, + who: Self::AccountId, + session_keys: Self::SessionKeys, + proof: Self::SessionKeysProof, ) -> Result<(), Self::Error> { - // TODO: build XCM and "send" it to MockRelay - todo!() + MockRelay::send_it(MockMessages::SetSessionKeys((who, session_keys, proof))); + Ok(()) } fn purge_session_keys(_who: Self::AccountId) -> Result<(), Self::Error> { // TODO: build XCM and "send" ut to MockRelay @@ -113,16 +125,16 @@ impl AsyncOffenceBroker for MockRelayClient {} impl SessionInterface for MockRelayClient where - RCClient: SessionInterface, + Client: SessionInterface, { fn validators() -> Vec { - >::validators() + >::validators() } fn disable_validator(validator_index: u32) -> bool { - >::disable_validator(validator_index) + >::disable_validator(validator_index) } fn prune_historical_up_to(up_to: SessionIndex) { - >::prune_historical_up_to(up_to) + >::prune_historical_up_to(up_to) } } @@ -131,9 +143,9 @@ parameter_types! { pub static OffencesState: BTreeMap = BTreeMap::new(); } -pub struct MockStaking; -impl SessionManager for MockStaking { - fn new_session(_new_index: SessionIndex) -> Option> { +pub struct MockStaking(PhantomData); +impl SessionManager for MockStaking { + fn new_session(_new_index: SessionIndex) -> Option> { todo!() } fn end_session(_end_index: SessionIndex) { @@ -142,12 +154,12 @@ impl SessionManager for MockStaking { fn start_session(_start_index: SessionIndex) { todo!() } - fn new_session_genesis(_new_index: SessionIndex) -> Option> { + fn new_session_genesis(_new_index: SessionIndex) -> Option> { todo!() } } -impl AuthorshipEventHandler for MockStaking { +impl AuthorshipEventHandler> for MockStaking { fn note_author(author: AccountId) { // one point per authoring. AuthoringState::mutate(|s| { @@ -157,12 +169,35 @@ impl AuthorshipEventHandler for MockStaking } } -impl OnOffenceHandler for MockStaking { +impl OnOffenceHandler, Weight> for MockStaking { fn on_offence( - _offenders: &[sp_staking::offence::OffenceDetails], + _offenders: &[sp_staking::offence::OffenceDetails>], _slash_fraction: &[Perbill], _session: SessionIndex, ) -> Weight { todo!() } } + +#[derive(Default)] +pub struct ExtBuilder {} + +impl ExtBuilder { + pub fn build(self) -> sp_io::TestExternalities { + use sp_runtime::BuildStorage; + + sp_tracing::try_init_simple(); + + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + // set pallet's genesis state + sp_io::TestExternalities::from(storage) + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); + + let mut ext = self.build(); + ext.execute_with(test); + } +} diff --git a/substrate/frame/staking/rc-client/src/tests.rs b/substrate/frame/staking/rc-client/src/tests.rs index 4bbffbf15ce0..c97b720d77d1 100644 --- a/substrate/frame/staking/rc-client/src/tests.rs +++ b/substrate/frame/staking/rc-client/src/tests.rs @@ -17,5 +17,22 @@ use crate::mock::*; -#[cfg(test)] -mod tests {} +use frame_support::assert_ok; +use sp_runtime::testing::UintAuthorityId; + +#[test] +fn set_session_keys_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(Outbound::get().is_empty()); + + let keys: ::SessionKeys = UintAuthorityId(1).into(); + + assert_ok!(Client::set_validator_keys( + RuntimeOrigin::signed(1), + keys.clone(), + vec![0, 0, 0] + )); + + assert_eq!(Outbound::get(), vec![MockMessages::SetSessionKeys((1, keys, vec![0, 0, 0]))]); + }) +}