diff --git a/Cargo.lock b/Cargo.lock index c6fefcfdfe186..38d7b2452f355 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -15516,6 +15516,7 @@ dependencies = [ "pallet-bags-list 27.0.0", "pallet-balances 28.0.0", "pallet-session 28.0.0", + "pallet-staking-rc-client", "pallet-staking-reward-curve", "pallet-timestamp 27.0.0", "parity-scale-codec", @@ -18669,7 +18670,6 @@ dependencies = [ "pallet-message-queue 31.0.0", "pallet-mmr 27.0.0", "pallet-session 28.0.0", - "pallet-staking 28.0.0", "pallet-timestamp 27.0.0", "parity-scale-codec", "polkadot-core-primitives 7.0.0", @@ -31528,6 +31528,7 @@ dependencies = [ "pallet-session-benchmarking 28.0.0", "pallet-society 28.0.0", "pallet-staking 28.0.0", + "pallet-staking-ah-client", "pallet-staking-runtime-api 14.0.0", "pallet-state-trie-migration 29.0.0", "pallet-sudo 28.0.0", diff --git a/polkadot/runtime/parachains/Cargo.toml b/polkadot/runtime/parachains/Cargo.toml index 6c87f7773c235..42290bc76b551 100644 --- a/polkadot/runtime/parachains/Cargo.toml +++ b/polkadot/runtime/parachains/Cargo.toml @@ -45,7 +45,6 @@ pallet-broker = { workspace = true } pallet-message-queue = { workspace = true } pallet-mmr = { workspace = true, optional = true } pallet-session = { workspace = true } -pallet-staking = { workspace = true } pallet-timestamp = { workspace = true } polkadot-primitives = { workspace = true } @@ -93,7 +92,6 @@ std = [ "pallet-message-queue/std", "pallet-mmr?/std", "pallet-session/std", - "pallet-staking/std", "pallet-timestamp/std", "polkadot-core-primitives/std", "polkadot-parachain-primitives/std", @@ -127,7 +125,6 @@ runtime-benchmarks = [ "pallet-broker/runtime-benchmarks", "pallet-message-queue/runtime-benchmarks", "pallet-mmr/runtime-benchmarks", - "pallet-staking/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "polkadot-parachain-primitives/runtime-benchmarks", "polkadot-primitives/runtime-benchmarks", @@ -151,7 +148,6 @@ try-runtime = [ "pallet-message-queue/try-runtime", "pallet-mmr/try-runtime", "pallet-session/try-runtime", - "pallet-staking/try-runtime", "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index b1ff5419470e1..de5737118fa27 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -78,6 +78,11 @@ pub trait FeeTracker { fn decrease_fee_factor(id: Self::Id) -> FixedU128; } +/// Trait for reporting rewards for processing parachains blocks +pub trait RewardsReporter { + fn reward_by_ids(validators_points: impl IntoIterator); +} + /// Schedule a para to be initialized at the start of the next session with the given genesis data. pub fn schedule_para_initialize( id: ParaId, diff --git a/polkadot/runtime/parachains/src/reward_points.rs b/polkadot/runtime/parachains/src/reward_points.rs index 69ef2db756c21..f427f2fc19d3b 100644 --- a/polkadot/runtime/parachains/src/reward_points.rs +++ b/polkadot/runtime/parachains/src/reward_points.rs @@ -21,7 +21,7 @@ //! which doesn't currently mention availability bitfields. As such, we don't reward them //! for the time being, although we will build schemes to do so in the future. -use crate::{session_info, shared}; +use crate::{session_info, shared, RewardsReporter}; use alloc::collections::btree_set::BTreeSet; use frame_support::traits::{Defensive, ValidatorSet}; use polkadot_primitives::{SessionIndex, ValidatorIndex}; @@ -32,12 +32,16 @@ pub const BACKING_POINTS: u32 = 20; pub const DISPUTE_STATEMENT_POINTS: u32 = 20; /// Rewards validators for participating in parachains with era points in pallet-staking. -pub struct RewardValidatorsWithEraPoints(core::marker::PhantomData); +pub struct RewardValidatorsWithEraPoints( + core::marker::PhantomData, + core::marker::PhantomData, +); -impl RewardValidatorsWithEraPoints +impl RewardValidatorsWithEraPoints where - C: pallet_staking::Config + session_info::Config, + C: session_info::Config, C::ValidatorSet: ValidatorSet, + R: RewardsReporter, { /// Reward validators in session with points, but only if they are in the active set. fn reward_only_active( @@ -61,14 +65,15 @@ where .filter(|v| active_set.contains(v)) .map(|v| (v, points)); - >::reward_by_ids(rewards); + R::reward_by_ids(rewards); } } -impl crate::inclusion::RewardValidators for RewardValidatorsWithEraPoints +impl crate::inclusion::RewardValidators for RewardValidatorsWithEraPoints where - C: pallet_staking::Config + shared::Config + session_info::Config, + C: shared::Config + session_info::Config, C::ValidatorSet: ValidatorSet, + R: RewardsReporter, { fn reward_backing(indices: impl IntoIterator) { let session_index = shared::CurrentSessionIndex::::get(); @@ -78,10 +83,11 @@ where fn reward_bitfields(_validators: impl IntoIterator) {} } -impl crate::disputes::RewardValidators for RewardValidatorsWithEraPoints +impl crate::disputes::RewardValidators for RewardValidatorsWithEraPoints where - C: pallet_staking::Config + session_info::Config, + C: session_info::Config, C::ValidatorSet: ValidatorSet, + R: RewardsReporter, { fn reward_dispute_statement( session: SessionIndex, diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml index 3317484419a9a..7925b9ad52946 100644 --- a/polkadot/runtime/westend/Cargo.toml +++ b/polkadot/runtime/westend/Cargo.toml @@ -86,6 +86,7 @@ pallet-scheduler = { workspace = true } pallet-session = { workspace = true } pallet-society = { workspace = true } pallet-staking = { workspace = true } +pallet-staking-ah-client = { workspace = true } pallet-staking-runtime-api = { workspace = true } pallet-state-trie-migration = { workspace = true } pallet-sudo = { workspace = true } @@ -186,6 +187,7 @@ std = [ "pallet-session-benchmarking?/std", "pallet-session/std", "pallet-society/std", + "pallet-staking-ah-client/std", "pallet-staking-runtime-api/std", "pallet-staking/std", "pallet-state-trie-migration/std", @@ -273,6 +275,7 @@ runtime-benchmarks = [ "pallet-session-benchmarking/runtime-benchmarks", "pallet-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", + "pallet-staking-ah-client/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", @@ -334,6 +337,7 @@ try-runtime = [ "pallet-session/try-runtime", "pallet-society/try-runtime", "pallet-staking/try-runtime", + "pallet-staking-ah-client/try-runtime", "pallet-state-trie-migration/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 8ee9e073f162d..eaa8cada34420 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -134,7 +134,7 @@ pub use sp_runtime::BuildStorage; use westend_runtime_constants::{ currency::*, fee::*, - system_parachain::{coretime::TIMESLICE_PERIOD, BROKER_ID}, + system_parachain::{coretime::TIMESLICE_PERIOD, ASSET_HUB_ID, BROKER_ID}, time::*, }; @@ -504,7 +504,7 @@ impl pallet_timestamp::Config for Runtime { impl pallet_authorship::Config for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type EventHandler = Staking; + type EventHandler = AssetHubStakingClient; } parameter_types! { @@ -529,16 +529,25 @@ impl pallet_session::Config for Runtime { type ValidatorIdOf = pallet_staking::StashOf; type ShouldEndSession = Babe; type NextSessionRotation = Babe; - type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type SessionManager = + pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type DisablingStrategy = pallet_session::disabling::UpToLimitWithReEnablingDisablingStrategy; type WeightInfo = weights::pallet_session::WeightInfo; } +// Dummy implementation which returns `Some(())` +pub struct FullIdentificationOf; +impl sp_runtime::traits::Convert> for FullIdentificationOf { + fn convert(_: AccountId) -> Option<()> { + Some(Default::default()) + } +} + impl pallet_session::historical::Config for Runtime { - type FullIdentification = pallet_staking::Exposure; - type FullIdentificationOf = pallet_staking::ExposureOf; + type FullIdentification = (); + type FullIdentificationOf = FullIdentificationOf; } pub struct MaybeSignedPhase; @@ -754,7 +763,7 @@ impl pallet_staking::Config for Runtime { type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EitherOf, StakingAdmin>; - type SessionInterface = Self; + type SessionInterface = (); // Should be pallet_staking_rc_client on ah-next type EraPayout = EraPayout; type MaxExposurePageSize = MaxExposurePageSize; type NextNewSession = Session; @@ -774,6 +783,13 @@ impl pallet_staking::Config for Runtime { type MaxDisabledValidators = ConstU32<100>; } +impl pallet_staking_ah_client::Config for Runtime { + type RuntimeOrigin = RuntimeOrigin; + type CurrencyBalance = Balance; + type AssetHubId = AssetHubId; + type SendXcm = crate::xcm_config::XcmRouter; +} + impl pallet_fast_unstake::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -848,7 +864,7 @@ impl pallet_treasury::Config for Runtime { impl pallet_offences::Config for Runtime { type RuntimeEvent = RuntimeEvent; type IdentificationTuple = pallet_session::historical::IdentificationTuple; - type OnOffenceHandler = Staking; + type OnOffenceHandler = AssetHubStakingClient; } impl pallet_authority_discovery::Config for Runtime { @@ -1250,10 +1266,18 @@ impl parachains_session_info::Config for Runtime { type ValidatorSet = Historical; } +pub struct RewardsHandler; +impl polkadot_runtime_parachains::RewardsReporter for RewardsHandler { + fn reward_by_ids(validators_points: impl IntoIterator) { + >::handle_parachain_rewards(validators_points); + } +} + impl parachains_inclusion::Config for Runtime { type RuntimeEvent = RuntimeEvent; type DisputesHandler = ParasDisputes; - type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints; + type RewardValidators = + parachains_reward_points::RewardValidatorsWithEraPoints; type MessageQueue = MessageQueue; type WeightInfo = weights::polkadot_runtime_parachains_inclusion::WeightInfo; } @@ -1353,6 +1377,7 @@ impl parachains_scheduler::Config for Runtime { parameter_types! { pub const BrokerId: u32 = BROKER_ID; + pub const AssetHubId: u32 = ASSET_HUB_ID; // TODO: replace with ASSET_HUB_NEXT_ID pub const BrokerPalletId: PalletId = PalletId(*b"py/broke"); pub MaxXcmTransactWeight: Weight = Weight::from_parts(200_000_000, 20_000); } @@ -1425,7 +1450,8 @@ impl assigned_slots::Config for Runtime { impl parachains_disputes::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type RewardValidators = parachains_reward_points::RewardValidatorsWithEraPoints; + type RewardValidators = + parachains_reward_points::RewardValidatorsWithEraPoints; type SlashingHandler = parachains_slashing::SlashValidatorsForDisputes; type WeightInfo = weights::polkadot_runtime_parachains_disputes::WeightInfo; } @@ -1785,6 +1811,8 @@ mod runtime { pub type AssignedSlots = assigned_slots; #[runtime::pallet_index(66)] pub type Coretime = coretime; + #[runtime::pallet_index(67)] + pub type AssetHubStakingClient = pallet_staking_ah_client; // Migrations pallet #[runtime::pallet_index(98)] diff --git a/substrate/frame/staking/Cargo.toml b/substrate/frame/staking/Cargo.toml index ee69c29af1672..03f43ba12fd4e 100644 --- a/substrate/frame/staking/Cargo.toml +++ b/substrate/frame/staking/Cargo.toml @@ -24,9 +24,12 @@ frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } pallet-authorship = { workspace = true } +# Should be removed after `pallet_session::migrations::v1::MigrateDisabledValidators` and +# `MigrateV14ToV15` are executed pallet-session = { features = [ "historical", ], workspace = true } +pallet-staking-rc-client = { workspace = true } rand = { features = ["alloc"], workspace = true } rand_chacha = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } @@ -66,6 +69,7 @@ std = [ "pallet-bags-list/std", "pallet-balances/std", "pallet-session/std", + "pallet-staking-rc-client/std", "pallet-timestamp/std", "rand/std", "rand_chacha/std", @@ -87,6 +91,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-staking-rc-client/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", @@ -99,6 +104,7 @@ try-runtime = [ "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", "pallet-session/try-runtime", + "pallet-staking-rc-client/try-runtime", "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/staking/ah-client/src/lib.rs b/substrate/frame/staking/ah-client/src/lib.rs index 88aee9ee3e9da..a265aa025250d 100644 --- a/substrate/frame/staking/ah-client/src/lib.rs +++ b/substrate/frame/staking/ah-client/src/lib.rs @@ -31,10 +31,10 @@ extern crate alloc; use alloc::vec::Vec; use frame_support::pallet_prelude::*; +pub use pallet::*; use pallet_staking_rc_client::Offence; use sp_core::crypto::AccountId32; -use sp_runtime::traits::Convert; -use sp_staking::{offence::OffenceDetails, Exposure, SessionIndex}; +use sp_staking::{offence::OffenceDetails, SessionIndex}; use xcm::prelude::*; const LOG_TARGET: &str = "runtime::staking::ah-client"; @@ -62,6 +62,9 @@ enum StakingCalls { /// Report one or more offences. #[codec(index = 2)] NewRelayChainOffences(SessionIndex, Vec), + /// Report rewards from parachain blocks processing. + #[codec(index = 3)] + ParachainSessionPoints(Vec<(AccountId32, u32)>), } #[frame_support::pallet(dev_mode)] @@ -71,31 +74,26 @@ pub mod pallet { use core::result; use frame_system::pallet_prelude::*; use pallet_session::historical; - use pallet_staking::ExposureOf; use polkadot_primitives::Id as ParaId; use polkadot_runtime_parachains::origin::{ensure_parachain, Origin}; use sp_runtime::Perbill; - use sp_staking::{offence::OnOffenceHandler, SessionIndex}; + use sp_staking::{ + offence::{OffenceSeverity, OnOffenceHandler}, + SessionIndex, + }; const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); /// The balance type of this pallet. pub type BalanceOf = ::CurrencyBalance; - // `Exposure>` will be removed. This type alias exists only to - // suppress clippy warnings. - type ElectedValidatorSet = Vec<( - ::AccountId, - Exposure<::AccountId, BalanceOf>, - )>; - #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); - // TODO: should contain some initial state, otherwise starting from genesis won't work #[pallet::storage] - pub type ValidatorSet = StorageValue<_, Option>, ValueQuery>; + pub type ValidatorSet = + StorageValue<_, Option::AccountId>>, ValueQuery>; /// Keeps track of the session points for each block author in the current session. #[pallet::storage] @@ -132,12 +130,15 @@ pub mod pallet { } #[pallet::call] - impl Pallet { + impl Pallet + where + T::AccountId: Into, + { #[pallet::call_index(0)] // #[pallet::weight(T::WeightInfo::new_validators())] // TODO pub fn new_validator_set( origin: OriginFor, - new_validator_set: ElectedValidatorSet, + new_validator_set: Vec, ) -> DispatchResult { // Ignore requests not coming from the AssetHub or root. Self::ensure_root_or_para(origin, ::AssetHubId::get().into())?; @@ -149,18 +150,30 @@ pub mod pallet { } } - impl historical::SessionManager>> - for Pallet - { - fn new_session(_: sp_staking::SessionIndex) -> Option> { - // If there is a new validator set - return it. Otherwise return `None`. - ValidatorSet::::take() + impl historical::SessionManager for Pallet { + fn new_session( + _: sp_staking::SessionIndex, + ) -> Option::AccountId, ())>> { + let maybe_new_validator_set = ValidatorSet::::take() + .map(|validators| validators.into_iter().map(|v| (v, ())).collect()); + + // A new validator set is an indication for a new era. Clear + if maybe_new_validator_set.is_none() { + // TODO: historical sessions should be pruned. This used to happen after the bonding + // period for the session but it would be nice to avoid XCM messages for prunning + // and trigger it from RC directly. + + // >::prune_up_to(up_to); // TODO!!! + } + + return maybe_new_validator_set } fn new_session_genesis( _: SessionIndex, - ) -> Option>)>> { + ) -> Option::AccountId, ())>> { ValidatorSet::::take() + .map(|validators| validators.into_iter().map(|v| (v, ())).collect()) } fn start_session(start_index: SessionIndex) { @@ -232,21 +245,13 @@ pub mod pallet { } } - impl + impl>> OnOffenceHandler, Weight> for Pallet where T: pallet_session::Config::AccountId>, - T: pallet_session::historical::Config< - FullIdentification = Exposure<::AccountId, BalanceOf>, - FullIdentificationOf = ExposureOf, - >, - T::SessionHandler: pallet_session::SessionHandler<::AccountId>, + T: pallet_session::historical::Config, T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert< - ::AccountId, - Option<::AccountId>, - >, T::AccountId: Into, { fn on_offence( @@ -257,18 +262,22 @@ pub mod pallet { slash_fraction: &[Perbill], slash_session: SessionIndex, ) -> Weight { - let offenders_and_slashes = offenders - .iter() - .cloned() - .zip(slash_fraction) - .map(|(offence, fraction)| { - Offence::new( - offence.offender.0.into(), - offence.reporters.into_iter().map(|r| r.into()).collect(), - *fraction, - ) - }) - .collect::>(); + let mut offenders_and_slashes = Vec::new(); + + // notify pallet-session about the offences + for (offence, fraction) in offenders.iter().cloned().zip(slash_fraction) { + >::report_offence( + offence.offender.0.clone(), + OffenceSeverity(*fraction), + ); + + // prepare an `Offence` instance for the XCM message + offenders_and_slashes.push(Offence::new( + offence.offender.0.into(), + offence.reporters.into_iter().map(|r| r.into()).collect(), + *fraction, + )); + } // send the offender immediately over xcm let message = Xcm(vec![ @@ -293,7 +302,10 @@ pub mod pallet { } } - impl Pallet { + impl Pallet + where + T::AccountId: Into, + { /// Ensure the origin is one of Root or the `para` itself. fn ensure_root_or_para( origin: ::RuntimeOrigin, @@ -310,6 +322,32 @@ pub mod pallet { } Ok(()) } + + pub fn handle_parachain_rewards( + validators_points: impl IntoIterator, + ) -> Weight { + let parachain_points = validators_points + .into_iter() + .map(|(id, points)| (id.into(), points)) + .collect::>(); + + let message = Xcm(vec![ + Instruction::UnpaidExecution { + weight_limit: WeightLimit::Unlimited, + check_origin: None, + }, + mk_asset_hub_call(StakingCalls::ParachainSessionPoints(parachain_points)), + ]); + if let Err(err) = send_xcm::( + Location::new(0, [Junction::Parachain(T::AssetHubId::get())]), + message, + ) { + log::error!(target: LOG_TARGET, "Sending `ParachainSessionPoints` to AssetHub failed: {:?}", + err); + } + + Weight::zero() + } } fn mk_asset_hub_call(call: StakingCalls) -> Instruction<()> { diff --git a/substrate/frame/staking/rc-client/src/lib.rs b/substrate/frame/staking/rc-client/src/lib.rs index dc6c0b7e5c6fc..4f38541c324a1 100644 --- a/substrate/frame/staking/rc-client/src/lib.rs +++ b/substrate/frame/staking/rc-client/src/lib.rs @@ -45,7 +45,24 @@ pub trait StakingApi { /// corresponding session points are reported. fn on_relay_chain_session_end(end_index: SessionIndex, block_authors: Vec<(AccountId32, u32)>); /// Report one or more offences on the relay chain. - fn on_new_offences(offences: Vec); + fn on_new_offences(slash_session: SessionIndex, offences: Vec) -> Weight; + /// Report validator rewards. + fn reward_by_ids(validators_points: Vec<(AccountId32, u32)>); +} + +// TODO: update the comment +/// Means for interacting with a specialized version of the `session` trait. +/// +/// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` +pub trait SessionInterface { + /// Get the validators from session. + fn validators() -> Vec; +} + +impl SessionInterface for () { + fn validators() -> Vec { + Vec::new() + } } /// `pallet-staking-ah-client` pallet index on Relay chain. Used to construct remote calls. @@ -68,9 +85,9 @@ enum SessionCalls { // An offence on the relay chain. Based on [`sp_staking::offence::OffenceDetails`]. #[derive(Encode, Decode, Debug, Clone, PartialEq, TypeInfo)] pub struct Offence { - offender: AccountId32, - reporters: Vec, - slash_fraction: Perbill, + pub offender: AccountId32, + pub reporters: Vec, + pub slash_fraction: Perbill, } impl Offence { @@ -125,7 +142,7 @@ pub mod pallet { ]); if let Err(err) = send_xcm::(Location::new(1, Here), message) { - log::error!(target: LOG_TARGET, "Sending `NewValidators` to relay chain failed: {:?}", err); + log::error!(target: LOG_TARGET, "Sending `NewValidatorSet` to relay chain failed: {:?}", err); } } } @@ -134,7 +151,7 @@ pub mod pallet { impl Pallet { /// Called to indicate the start of a new session on the relay chain. #[pallet::call_index(0)] - // #[pallet::weight(T::WeightInfo::end_session())] // TODO + // #[pallet::weight(T::WeightInfo::relay_chain_session_start())] // TODO pub fn relay_chain_session_start( origin: OriginFor, start_index: SessionIndex, @@ -147,7 +164,7 @@ pub mod pallet { /// Called to indicate the end of a session on the relay chain. Accepts the session id and /// the block authors with their corresponding session points for the finished session. #[pallet::call_index(1)] - // #[pallet::weight(T::WeightInfo::end_session())] // TODO + // #[pallet::weight(T::WeightInfo::relay_chain_session_end())] // TODO pub fn relay_chain_session_end( origin: OriginFor, end_index: SessionIndex, @@ -160,15 +177,34 @@ pub mod pallet { /// Called to report one or more new offenses on the relay chain. #[pallet::call_index(2)] - // #[pallet::weight(T::WeightInfo::end_session())] // TODO + // #[pallet::weight(T::WeightInfo::new_relay_chain_offence())] // TODO pub fn new_relay_chain_offence( origin: OriginFor, + slash_session: SessionIndex, offences: Vec, ) -> DispatchResult { T::AdminOrigin::ensure_origin_or_root(origin)?; - T::StakingApi::on_new_offences(offences); + T::StakingApi::on_new_offences(slash_session, offences); Ok(()) } + + #[pallet::call_index(3)] + // #[pallet::weight(T::WeightInfo::end_session())] // TODO + pub fn parachain_session_points( + origin: OriginFor, + session_points: Vec<(AccountId32, u32)>, + ) -> DispatchResult { + T::AdminOrigin::ensure_origin_or_root(origin)?; + T::StakingApi::reward_by_ids(session_points); + Ok(()) + } + } + + impl SessionInterface for Pallet { + fn validators() -> Vec { + // TODO: cache the last applied validator set and return it here + vec![] + } } fn mk_relay_chain_call(call: SessionCalls) -> Instruction<()> { diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 9105a3e7ec13a..20a561271ead6 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -890,57 +890,6 @@ impl NominationsQuota for FixedNominationsQuot } } -/// Means for interacting with a specialized version of the `session` trait. -/// -/// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` -pub trait SessionInterface { - /// Report an offending validator. - fn report_offence(validator: AccountId, severity: OffenceSeverity); - /// Get the validators from session. - fn validators() -> Vec; - /// Prune historical session tries up to but not including the given index. - fn prune_historical_up_to(up_to: SessionIndex); -} - -impl SessionInterface<::AccountId> for T -where - T: pallet_session::Config::AccountId>, - T: pallet_session::historical::Config, - T::SessionHandler: pallet_session::SessionHandler<::AccountId>, - T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert< - ::AccountId, - Option<::AccountId>, - >, -{ - fn report_offence( - validator: ::AccountId, - severity: OffenceSeverity, - ) { - >::report_offence(validator, severity) - } - - fn validators() -> Vec<::AccountId> { - >::validators() - } - - fn prune_historical_up_to(up_to: SessionIndex) { - >::prune_up_to(up_to); - } -} - -impl SessionInterface for () { - fn report_offence(_validator: AccountId, _severity: OffenceSeverity) { - () - } - fn validators() -> Vec { - Vec::new() - } - fn prune_historical_up_to(_: SessionIndex) { - () - } -} - /// Handler for determining how much of a balance should be paid out on the current era. pub trait EraPayout { /// Determine the payout for this era. diff --git a/substrate/frame/staking/src/migrations.rs b/substrate/frame/staking/src/migrations.rs index 5b0118da67ef7..53a86506f98b2 100644 --- a/substrate/frame/staking/src/migrations.rs +++ b/substrate/frame/staking/src/migrations.rs @@ -264,6 +264,7 @@ pub mod v16 { /// Migrating `OffendingValidators` from `Vec<(u32, bool)>` to `Vec` pub mod v15 { use super::*; + use pallet_staking_rc_client::SessionInterface; // The disabling strategy used by staking pallet type DefaultDisablingStrategy = pallet_session::disabling::UpToLimitDisablingStrategy; diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index 0406c1dbbb2d8..64ac2b6f6a323 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -32,14 +32,15 @@ use frame_support::{ weights::Weight, }; use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; -use pallet_session::historical; +use pallet_staking_rc_client::{Offence, SessionInterface, StakingApi}; +use sp_core::crypto::AccountId32; use sp_runtime::{ traits::{Bounded, CheckedAdd, Convert, SaturatedConversion, Saturating, StaticLookup, Zero}, ArithmeticError, DispatchResult, Perbill, Percent, }; use sp_staking::{ currency_to_vote::CurrencyToVote, - offence::{OffenceDetails, OffenceSeverity, OnOffenceHandler}, + offence::{OffenceSeverity, OnOffenceHandler}, EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakingAccount::{self, Controller, Stash}, StakingInterface, @@ -49,8 +50,8 @@ use crate::{ asset, election_size_tracker::StaticTracker, log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, BoundedExposuresOf, EraInfo, EraPayout, Exposure, Forcing, IndividualExposure, LedgerIntegrityState, MaxNominationsOf, MaxWinnersOf, MaxWinnersPerPageOf, Nominations, - NominationsQuota, PositiveImbalanceOf, RewardDestination, SessionInterface, SnapshotStatus, - StakingLedger, ValidatorPrefs, STAKING_ID, + NominationsQuota, PositiveImbalanceOf, RewardDestination, SnapshotStatus, StakingLedger, + ValidatorPrefs, STAKING_ID, }; use alloc::{boxed::Box, vec, vec::Vec}; @@ -572,9 +573,10 @@ impl Pallet { slashing::clear_era_metadata::(pruned_era); } - if let Some(&(_, first_session)) = bonded.first() { - T::SessionInterface::prune_historical_up_to(first_session); - } + // TODO: should be handled in ah_client (see the comment there) + // if let Some(&(_, first_session)) = bonded.first() { + // T::SessionInterface::prune_historical_up_to(first_session); + // } } }); } @@ -1664,158 +1666,30 @@ impl ElectionDataProvider for Pallet { } } -/// In this implementation `new_session(session)` must be called before `end_session(session-1)` -/// i.e. the new session must be planned before the ending of the previous session. -/// -/// Once the first new_session is planned, all session must start and then end in order, though -/// some session can lag in between the newest session planned and the latest session started. -impl pallet_session::SessionManager for Pallet { - // └── Self::new_session(new_index, false) - // └── Self::try_plan_new_era(session_index, is_genesis) - // └── T::GenesisElectionProvider::elect() OR ElectableStashes::::take() - // └── Self::collect_exposures() - // └── Self::store_stakers_info() - // └── Self::plan_new_era() - // └── CurrentEra increment - // └── ErasStartSessionIndex update - // └── Self::clear_era_information() - fn new_session(new_index: SessionIndex) -> Option> { - log!(trace, "planning new session {}", new_index); - CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, false).map(|v| v.into_inner()) - } - fn new_session_genesis(new_index: SessionIndex) -> Option> { - log!(trace, "planning new session {} at genesis", new_index); - CurrentPlannedSession::::put(new_index); - Self::new_session(new_index, true).map(|v| v.into_inner()) - } - // start_session(start_session: SessionIndex) - // └── Check if this is the start of next active era - // └── Self::start_era(start_session) - // └── Update active era index - // └── Set active era start timestamp - // └── Update BondedEras - // └── Self::apply_unapplied_slashes() - // └── Get slashes for era from UnappliedSlashes - // └── Apply each slash - // └── Clear slashes metadata - // └── Process disabled validators - // └── Get all disabled validators - // └── Call T::SessionInterface::disable_validator() for each - fn start_session(start_index: SessionIndex) { +impl StakingApi for Pallet +where + T::AccountId: From, +{ + fn on_relay_chain_session_start(start_index: SessionIndex) { log!(trace, "starting session {}", start_index); Self::start_session(start_index) } - fn end_session(end_index: SessionIndex) { - log!(trace, "ending session {}", end_index); - Self::end_session(end_index) - } -} - -impl historical::SessionManager>> - for Pallet -{ - fn new_session( - new_index: SessionIndex, - ) -> Option>)>> { - >::new_session(new_index).map(|validators| { - let current_era = CurrentEra::::get() - // Must be some as a new era has been created. - .unwrap_or(0); - validators - .into_iter() - .map(|v| { - let exposure = Self::eras_stakers(current_era, &v); - (v, exposure) - }) - .collect() - }) - } - fn new_session_genesis( - new_index: SessionIndex, - ) -> Option>)>> { - >::new_session_genesis(new_index).map( - |validators| { - let current_era = CurrentEra::::get() - // Must be some as a new era has been created. - .unwrap_or(0); - - validators - .into_iter() - .map(|v| { - let exposure = Self::eras_stakers(current_era, &v); - (v, exposure) - }) - .collect() - }, - ) - } - fn start_session(start_index: SessionIndex) { - >::start_session(start_index) - } - fn end_session(end_index: SessionIndex) { - >::end_session(end_index) - } -} + fn on_relay_chain_session_end(end_index: SessionIndex, block_authors: Vec<(AccountId32, u32)>) { + log!(trace, "ending session {}", end_index); -impl historical::SessionManager for Pallet { - fn new_session(new_index: SessionIndex) -> Option> { - >::new_session(new_index) - .map(|validators| validators.into_iter().map(|v| (v, ())).collect()) - } - fn new_session_genesis(new_index: SessionIndex) -> Option> { - >::new_session_genesis(new_index) - .map(|validators| validators.into_iter().map(|v| (v, ())).collect()) - } - fn start_session(start_index: SessionIndex) { - >::start_session(start_index) - } - fn end_session(end_index: SessionIndex) { - >::end_session(end_index) - } -} + // reward block authors + for (author, block_count) in block_authors { + let author: T::AccountId = author.into(); + let block_count: u32 = block_count; + Self::reward_by_ids(vec![(author, 20 * block_count)]) + } -/// Add reward points to block authors: -/// * 20 points to the block producer for producing a (non-uncle) block, -impl pallet_authorship::EventHandler> for Pallet -where - T: Config + pallet_authorship::Config + pallet_session::Config, -{ - fn note_author(author: T::AccountId) { - Self::reward_by_ids(vec![(author, 20)]) + Self::end_session(end_index) } -} -/// This is intended to be used with `FilterHistoricalOffences`. -impl - OnOffenceHandler, Weight> - for Pallet -where - T: pallet_session::Config::AccountId>, - T: pallet_session::historical::Config, - T::SessionHandler: pallet_session::SessionHandler<::AccountId>, - T::SessionManager: pallet_session::SessionManager<::AccountId>, - T::ValidatorIdOf: Convert< - ::AccountId, - Option<::AccountId>, - >, -{ - /// When an offence is reported, it is split into pages and put in the offence queue. - /// As offence queue is processed, computed slashes are queued to be applied after the - /// `SlashDeferDuration`. - fn on_offence( - offenders: &[OffenceDetails>], - slash_fractions: &[Perbill], - slash_session: SessionIndex, - ) -> Weight { - log!( - debug, - "🦹 on_offence: offenders={:?}, slash_fractions={:?}, slash_session={}", - offenders, - slash_fractions, - slash_session, - ); + fn on_new_offences(slash_session: SessionIndex, offences: Vec) -> Weight { + log!(debug, "🦹 on_new_offences: {:?}", offences); // todo(ank4n): Needs to be properly benched. let mut consumed_weight = Weight::zero(); @@ -1826,7 +1700,7 @@ where // Find the era to which offence belongs. add_db_reads_writes(1, 0); let Some(active_era) = ActiveEra::::get() else { - log!(warn, "🦹 on_offence: no active era; ignoring offence"); + log!(warn, "🦹 on_new_offences: no active era; ignoring offence"); return consumed_weight }; @@ -1860,8 +1734,9 @@ where add_db_reads_writes(1, 0); let invulnerables = Invulnerables::::get(); - for (details, slash_fraction) in offenders.iter().zip(slash_fractions) { - let (validator, _) = &details.offender; + for o in offences { + let slash_fraction = o.slash_fraction; + let validator: ::AccountId = o.offender.into(); // Skip if the validator is invulnerable. if invulnerables.contains(&validator) { log!(debug, "🦹 on_offence: {:?} is invulnerable; ignoring offence", validator); @@ -1869,7 +1744,7 @@ where } add_db_reads_writes(1, 0); - let Some(exposure_overview) = >::get(&offence_era, validator) + let Some(exposure_overview) = >::get(&offence_era, &validator) else { // defensive: this implies offence is for a discarded era, and should already be // filtered out. @@ -1884,34 +1759,35 @@ where Self::deposit_event(Event::::OffenceReported { validator: validator.clone(), - fraction: *slash_fraction, + fraction: slash_fraction, offence_era, }); - if offence_era == active_era.index { - // offence is in the current active era. Report it to session to maybe disable the - // validator. - add_db_reads_writes(2, 2); - T::SessionInterface::report_offence( - validator.clone(), - OffenceSeverity(*slash_fraction), - ); - } + // TODO: handled in ah_client. Verify it works correctly before removing the commented + // out code. if offence_era == active_era.index { + // // offence is in the current active era. Report it to session to maybe disable the + // // validator. + // add_db_reads_writes(2, 2); + // T::SessionInterface::report_offence( + // validator.clone(), + // OffenceSeverity(slash_fraction), + // ); + // } add_db_reads_writes(1, 0); - let prior_slash_fraction = ValidatorSlashInEra::::get(offence_era, validator) + let prior_slash_fraction = ValidatorSlashInEra::::get(offence_era, &validator) .map_or(Zero::zero(), |(f, _)| f); add_db_reads_writes(1, 0); - if let Some(existing) = OffenceQueue::::get(offence_era, validator) { + if let Some(existing) = OffenceQueue::::get(offence_era, &validator) { if slash_fraction.deconstruct() > existing.slash_fraction.deconstruct() { add_db_reads_writes(0, 2); OffenceQueue::::insert( offence_era, - validator, + &validator, OffenceRecord { - reporter: details.reporters.first().cloned(), + reporter: o.reporters.first().cloned(), reported_era: active_era.index, - slash_fraction: *slash_fraction, + slash_fraction, ..existing }, ); @@ -1919,7 +1795,7 @@ where // update the slash fraction in the `ValidatorSlashInEra` storage. ValidatorSlashInEra::::insert( offence_era, - validator, + &validator, (slash_fraction, exposure_overview.own), ); @@ -1943,20 +1819,20 @@ where add_db_reads_writes(0, 3); ValidatorSlashInEra::::insert( offence_era, - validator, + &validator, (slash_fraction, exposure_overview.own), ); OffenceQueue::::insert( offence_era, - validator, + &validator, OffenceRecord { - reporter: details.reporters.first().cloned(), + reporter: o.reporters.first().cloned(), reported_era: active_era.index, // there are cases of validator with no exposure, hence 0 page, so we // saturate to avoid underflow. exposure_page: exposure_overview.page_count.saturating_sub(1), - slash_fraction: *slash_fraction, + slash_fraction, prior_slash_fraction, }, ); @@ -1995,8 +1871,38 @@ where consumed_weight } + + fn reward_by_ids(validators_points: Vec<(AccountId32, u32)>) {} } +// TODO: is `new_session` still needed? If no - who sets `CurrentPlannedSession` +/// In this implementation `new_session(session)` must be called before `end_session(session-1)` +/// i.e. the new session must be planned before the ending of the previous session. +/// +/// Once the first new_session is planned, all session must start and then end in order, though +/// some session can lag in between the newest session planned and the latest session started. +// impl pallet_session::SessionManager for Pallet { +// // └── Self::new_session(new_index, false) +// // └── Self::try_plan_new_era(session_index, is_genesis) +// // └── T::GenesisElectionProvider::elect() OR ElectableStashes::::take() +// // └── Self::collect_exposures() +// // └── Self::store_stakers_info() +// // └── Self::plan_new_era() +// // └── CurrentEra increment +// // └── ErasStartSessionIndex update +// // └── Self::clear_era_information() +// fn new_session(new_index: SessionIndex) -> Option> { +// log!(trace, "planning new session {}", new_index); +// CurrentPlannedSession::::put(new_index); +// Self::new_session(new_index, false).map(|v| v.into_inner()) +// } +// fn new_session_genesis(new_index: SessionIndex) -> Option> { +// log!(trace, "planning new session {} at genesis", new_index); +// CurrentPlannedSession::::put(new_index); +// Self::new_session(new_index, true).map(|v| v.into_inner()) +// } +// } + impl ScoreProvider for Pallet { type Score = VoteWeight; diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index 4184ac3bdde81..247eba25ba022 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -35,6 +35,7 @@ use frame_support::{ BoundedBTreeSet, BoundedVec, }; use frame_system::{ensure_root, ensure_signed, pallet_prelude::*}; +use pallet_staking_rc_client::SessionInterface; use rand::seq::SliceRandom; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, @@ -43,7 +44,7 @@ use rand_chacha::{ use sp_core::{sr25519::Pair as SrPair, Pair}; use sp_runtime::{ traits::{SaturatedConversion, StaticLookup, Zero}, - ArithmeticError, Perbill, Percent, Saturating, + AccountId32, ArithmeticError, Perbill, Percent, Saturating, }; use sp_staking::{ @@ -60,7 +61,7 @@ use crate::{ asset, slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, ExposurePage, Forcing, LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, - SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, + StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of @@ -94,7 +95,7 @@ pub mod pallet { } #[pallet::config(with_default)] - pub trait Config: frame_system::Config { + pub trait Config: frame_system::Config { /// The old trait for staking balance. Deprecated and only used for migrating old ledgers. #[pallet::no_default] type OldCurrency: InspectLockableCurrency<