feat(rc): integrate StakingAhClient pallet and upgrade staking to production parameters

- Add pezpallet-staking-async-ah-client and rc-client dependencies
- Wire StakingAhClient as SessionManager and EventHandler (replacing ValidatorManager for session routing)
- Replace FullIdentificationOf with ExposureOfOrDefault for proper historical session tracking
- Route parachain reward_points through RewardValidatorsWithEraPoints
- EraPayout: switch from dynamic total_issuance to fixed 200M HEZ baseline (prevents compound inflation)
- MaxExposurePageSize: 64 → 512 (Polkadot production value)
- MaxSessionReportRetries: 5 → 64 (~6min retry window for AH reliability)
- Bump spec_version to 1_020_007
This commit is contained in:
2026-02-18 21:22:45 +03:00
parent 6f4bbe2d86
commit 098e3041bb
4 changed files with 191 additions and 29 deletions
+8
View File
@@ -81,6 +81,8 @@ pezpallet-root-testing = { workspace = true }
pezpallet-scheduler = { workspace = true }
pezpallet-session = { workspace = true }
pezpallet-staking = { workspace = true }
pezpallet-staking-async-ah-client = { workspace = true }
pezpallet-staking-async-rc-client = { workspace = true }
pezpallet-staking-runtime-api = { workspace = true }
pezpallet-state-trie-migration = { workspace = true }
pezpallet-sudo = { workspace = true }
@@ -182,6 +184,8 @@ std = [
"pezpallet-scheduler/std",
"pezpallet-session-benchmarking?/std",
"pezpallet-session/std",
"pezpallet-staking-async-ah-client/std",
"pezpallet-staking-async-rc-client/std",
"pezpallet-staking-runtime-api/std",
"pezpallet-staking/std",
"pezpallet-state-trie-migration/std",
@@ -274,6 +278,8 @@ runtime-benchmarks = [
"pezpallet-scheduler/runtime-benchmarks",
"pezpallet-session-benchmarking/runtime-benchmarks",
"pezpallet-session/runtime-benchmarks",
"pezpallet-staking-async-ah-client/runtime-benchmarks",
"pezpallet-staking-async-rc-client/runtime-benchmarks",
"pezpallet-staking-runtime-api/runtime-benchmarks",
"pezpallet-staking/runtime-benchmarks",
"pezpallet-state-trie-migration/runtime-benchmarks",
@@ -357,6 +363,8 @@ try-runtime = [
"pezpallet-scheduler/try-runtime",
"pezpallet-session-benchmarking?/try-runtime",
"pezpallet-session/try-runtime",
"pezpallet-staking-async-ah-client/try-runtime",
"pezpallet-staking-async-rc-client/try-runtime",
"pezpallet-staking/try-runtime",
"pezpallet-state-trie-migration/try-runtime",
"pezpallet-sudo/try-runtime",
+167 -25
View File
@@ -73,9 +73,11 @@ use pezkuwi_runtime_teyrchains::{
v13 as teyrchains_runtime_api_impl, vstaging as teyrchains_staging_runtime_api_impl,
},
scheduler as teyrchains_scheduler, session_info as teyrchains_session_info,
shared as teyrchains_shared,
reward_points as teyrchains_reward_points, shared as teyrchains_shared,
};
use pezkuwichain_runtime_constants::system_teyrchain::{
coretime::TIMESLICE_PERIOD, ASSET_HUB_ID, BROKER_ID,
};
use pezkuwichain_runtime_constants::system_teyrchain::{coretime::TIMESLICE_PERIOD, BROKER_ID};
use pezpallet_balances::WeightInfo;
use pezsp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use pezsp_consensus_beefy::{
@@ -100,6 +102,8 @@ use pezframe_support::{
use pezframe_system::EnsureRoot;
use pezpallet_grandpa::{fg_primitives, AuthorityId as GrandpaId};
use pezpallet_session::historical as session_historical;
use pezpallet_staking_async_ah_client as ah_client;
use pezpallet_staking_async_rc_client as rc_client;
use pezpallet_transaction_payment::{FeeDetails, FungibleAdapter, RuntimeDispatchInfo};
use pezsp_core::{ConstBool, ConstU128, ConstUint, Get, OpaqueMetadata, H256};
use pezsp_runtime::{
@@ -170,7 +174,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: alloc::borrow::Cow::Borrowed("pezkuwichain"),
impl_name: alloc::borrow::Cow::Borrowed("parity-pezkuwichain"),
authoring_version: 0,
spec_version: 1_020_004,
spec_version: 1_020_007,
impl_version: 0,
apis: RUNTIME_API_VERSIONS,
transaction_version: 26,
@@ -413,7 +417,7 @@ impl pezpallet_timestamp::Config for Runtime {
impl pezpallet_authorship::Config for Runtime {
type FindAuthor = pezpallet_session::FindAccountFromAuthorIndex<Self, Babe>;
type EventHandler = Staking;
type EventHandler = StakingAhClient;
}
impl_opaque_keys! {
@@ -441,7 +445,7 @@ impl pezpallet_session::Config for Runtime {
type ValidatorIdOf = ValidatorIdOf;
type ShouldEndSession = Babe;
type NextSessionRotation = Babe;
type SessionManager = pezpallet_session::historical::NoteHistoricalRoot<Self, ValidatorManager>;
type SessionManager = pezpallet_session::historical::NoteHistoricalRoot<Self, StakingAhClient>;
type SessionHandler = <SessionKeys as OpaqueKeys>::KeyTypeIdProviders;
type Keys = SessionKeys;
type DisablingStrategy = ();
@@ -450,17 +454,27 @@ impl pezpallet_session::Config for Runtime {
type KeyDeposit = ();
}
pub struct FullIdentificationOf;
impl pezsp_runtime::traits::Convert<AccountId, Option<()>> for FullIdentificationOf {
fn convert(_: AccountId) -> Option<()> {
Some(Default::default())
/// Returns staking exposure for historical session tracking.
/// Falls back to a default empty exposure when none exists yet (e.g. at genesis),
/// preventing validators from being filtered out of the authority set.
pub struct ExposureOfOrDefault;
impl pezsp_runtime::traits::Convert<AccountId, Option<pezsp_staking::Exposure<AccountId, Balance>>>
for ExposureOfOrDefault
{
fn convert(
validator: AccountId,
) -> Option<pezsp_staking::Exposure<AccountId, Balance>> {
Some(
<pezpallet_staking::DefaultExposureOf<Runtime>>::convert(validator)
.unwrap_or_default(),
)
}
}
impl pezpallet_session::historical::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type FullIdentification = ();
type FullIdentificationOf = FullIdentificationOf;
type FullIdentification = pezsp_staking::Exposure<AccountId, Balance>;
type FullIdentificationOf = ExposureOfOrDefault;
}
// =====================================================
@@ -506,15 +520,16 @@ pub struct EraPayout;
impl pezpallet_staking::EraPayout<Balance> for EraPayout {
fn era_payout(
_total_staked: Balance,
total_issuance: Balance,
_total_issuance: Balance,
era_duration_millis: u64,
) -> (Balance, Balance) {
// 8% annual inflation rate
const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100;
let relative_era_len =
FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into());
let inflation_rate = FixedU128::from_rational(8, 100);
let yearly_emission = inflation_rate.saturating_mul_int(total_issuance as i128);
// Fixed baseline: 200M HEZ (12 decimals) — prevents compound inflation
let fixed_total_issuance: i128 = 200_000_000_000_000_000_000;
let fixed_inflation_rate = FixedU128::from_rational(8, 100);
let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance);
let era_emission = relative_era_len.saturating_mul_int(yearly_emission);
// 15% to treasury, 85% to stakers
let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission);
@@ -544,7 +559,7 @@ impl pezpallet_staking::Config for Runtime {
type SessionInterface = ();
type EraPayout = EraPayout;
type NextNewSession = Session;
type MaxExposurePageSize = ConstU32<64>;
type MaxExposurePageSize = ConstU32<512>;
type MaxValidatorSet = MaxActiveValidators;
type ElectionProvider =
pezframe_election_provider_support::onchain::OnChainExecution<OnChainSeqPhragmen>;
@@ -565,6 +580,137 @@ impl pezpallet_staking::Config for Runtime {
type BenchmarkingConfig = PezkuwiStakingBenchmarkingConfig;
}
// =====================================================
// STAKING AH CLIENT CONFIGURATION (XCM Session Reports)
// =====================================================
#[derive(Encode, Decode)]
enum AssetHubRuntimePallets<AccountId> {
// Audit: `StakingRcClient` in asset-hub-pezkuwichain (pallet index 89)
#[codec(index = 89)]
RcClient(RcClientCalls<AccountId>),
}
#[derive(Encode, Decode)]
enum RcClientCalls<AccountId> {
#[codec(index = 0)]
RelaySessionReport(rc_client::SessionReport<AccountId>),
#[codec(index = 1)]
RelayNewOffencePaged(Vec<(SessionIndex, rc_client::Offence<AccountId>)>),
}
pub struct AssetHubLocation;
impl Get<Location> for AssetHubLocation {
fn get() -> Location {
Location::new(0, [Junction::Teyrchain(ASSET_HUB_ID)])
}
}
pub struct EnsureAssetHub;
impl pezframe_support::traits::EnsureOrigin<RuntimeOrigin> for EnsureAssetHub {
type Success = ();
fn try_origin(o: RuntimeOrigin) -> Result<Self::Success, RuntimeOrigin> {
match <RuntimeOrigin as Into<Result<teyrchains_origin::Origin, RuntimeOrigin>>>::into(
o.clone(),
) {
Ok(teyrchains_origin::Origin::Teyrchain(id)) if id == ASSET_HUB_ID.into() => Ok(()),
_ => Err(o),
}
}
#[cfg(feature = "runtime-benchmarks")]
fn try_successful_origin() -> Result<RuntimeOrigin, ()> {
Ok(RuntimeOrigin::root())
}
}
pub struct SessionReportToXcm;
impl pezsp_runtime::traits::Convert<rc_client::SessionReport<AccountId>, Xcm<()>>
for SessionReportToXcm
{
fn convert(a: rc_client::SessionReport<AccountId>) -> Xcm<()> {
Xcm(vec![
Instruction::UnpaidExecution {
weight_limit: WeightLimit::Unlimited,
check_origin: None,
},
Instruction::Transact {
origin_kind: OriginKind::Superuser,
fallback_max_weight: None,
call: AssetHubRuntimePallets::RcClient(RcClientCalls::RelaySessionReport(a))
.encode()
.into(),
},
])
}
}
pub struct QueuedOffenceToXcm;
impl pezsp_runtime::traits::Convert<Vec<ah_client::QueuedOffenceOf<Runtime>>, Xcm<()>>
for QueuedOffenceToXcm
{
fn convert(offences: Vec<ah_client::QueuedOffenceOf<Runtime>>) -> Xcm<()> {
Xcm(vec![
Instruction::UnpaidExecution {
weight_limit: WeightLimit::Unlimited,
check_origin: None,
},
Instruction::Transact {
origin_kind: OriginKind::Superuser,
fallback_max_weight: None,
call: AssetHubRuntimePallets::RcClient(RcClientCalls::RelayNewOffencePaged(
offences,
))
.encode()
.into(),
},
])
}
}
pub struct StakingXcmToAssetHub;
impl ah_client::SendToAssetHub for StakingXcmToAssetHub {
type AccountId = AccountId;
fn relay_session_report(
session_report: rc_client::SessionReport<Self::AccountId>,
) -> Result<(), ()> {
rc_client::XCMSender::<
xcm_config::XcmRouter,
AssetHubLocation,
rc_client::SessionReport<AccountId>,
SessionReportToXcm,
>::send(session_report)
}
fn relay_new_offence_paged(
offences: Vec<ah_client::QueuedOffenceOf<Runtime>>,
) -> Result<(), ()> {
rc_client::XCMSender::<
xcm_config::XcmRouter,
AssetHubLocation,
Vec<ah_client::QueuedOffenceOf<Runtime>>,
QueuedOffenceToXcm,
>::send(offences)
}
}
impl ah_client::Config for Runtime {
type CurrencyBalance = Balance;
type AssetHubOrigin =
pezframe_support::traits::EitherOfDiverse<EnsureRoot<AccountId>, EnsureAssetHub>;
type AdminOrigin = EnsureRoot<AccountId>;
type SessionInterface = Self;
type SendToAssetHub = StakingXcmToAssetHub;
type MinimumValidatorSetSize = ConstU32<1>;
type UnixTime = Timestamp;
type PointsPerBlock = ConstU32<20>;
type MaxOffenceBatchSize = ConstU32<50>;
type Fallback = Staking;
type MaximumValidatorsWithPoints = ConstU32<{ MaxActiveValidators::get() * 4 }>;
type MaxSessionReportRetries = ConstU32<64>;
}
// =====================================================
// FAST UNSTAKE CONFIGURATION
// =====================================================
@@ -1075,17 +1221,11 @@ impl teyrchains_session_info::Config for Runtime {
type ValidatorSet = Historical;
}
/// Special `RewardValidators` that does nothing ;)
pub struct RewardValidators;
impl pezkuwi_runtime_teyrchains::inclusion::RewardValidators for RewardValidators {
fn reward_backing(_: impl IntoIterator<Item = ValidatorIndex>) {}
fn reward_bitfields(_: impl IntoIterator<Item = ValidatorIndex>) {}
}
impl teyrchains_inclusion::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type DisputesHandler = ParasDisputes;
type RewardValidators = RewardValidators;
type RewardValidators =
teyrchains_reward_points::RewardValidatorsWithEraPoints<Runtime, StakingAhClient>;
type MessageQueue = MessageQueue;
type WeightInfo = weights::pezkuwi_runtime_teyrchains_inclusion::WeightInfo<Runtime>;
}
@@ -1243,7 +1383,8 @@ impl teyrchains_initializer::Config for Runtime {
impl teyrchains_disputes::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RewardValidators = ();
type RewardValidators =
teyrchains_reward_points::RewardValidatorsWithEraPoints<Runtime, StakingAhClient>;
type SlashingHandler = teyrchains_slashing::SlashValidatorsForDisputes<ParasSlashing>;
type WeightInfo = weights::pezkuwi_runtime_teyrchains_disputes::WeightInfo<Runtime>;
}
@@ -1536,6 +1677,7 @@ construct_runtime! {
ParasSlashing: teyrchains_slashing = 63,
MessageQueue: pezpallet_message_queue = 64,
OnDemandAssignmentProvider: teyrchains_on_demand = 66,
StakingAhClient: pezpallet_staking_async_ah_client = 67,
CoretimeAssignmentProvider: teyrchains_assigner_coretime = 68,
// Teyrchain Onboarding Pallets. Start indices at 70 to leave room.
@@ -17,6 +17,7 @@
//! A pezpallet for managing validators on Pezkuwichain.
use alloc::vec::Vec;
use pezsp_runtime::traits::Convert;
use pezsp_staking::SessionIndex;
pub use pezpallet::*;
@@ -141,10 +142,19 @@ impl<T: Config> pezpallet_session::SessionManager<T::ValidatorId> for Pezpallet<
}
}
impl<T: Config> pezpallet_session::historical::SessionManager<T::ValidatorId, ()> for Pezpallet<T> {
fn new_session(new_index: SessionIndex) -> Option<Vec<(T::ValidatorId, ())>> {
<Self as pezpallet_session::SessionManager<_>>::new_session(new_index)
.map(|r| r.into_iter().map(|v| (v, Default::default())).collect())
impl<T: Config + pezpallet_session::historical::Config>
pezpallet_session::historical::SessionManager<T::ValidatorId, T::FullIdentification>
for Pezpallet<T>
{
fn new_session(new_index: SessionIndex) -> Option<Vec<(T::ValidatorId, T::FullIdentification)>> {
<Self as pezpallet_session::SessionManager<_>>::new_session(new_index).map(|r| {
r.into_iter()
.filter_map(|v| {
let full_id = T::FullIdentificationOf::convert(v.clone());
full_id.map(|id| (v, id))
})
.collect()
})
}
fn start_session(start_index: SessionIndex) {