Files
pezkuwi-sdk/bizinikiwi/pezframe/staking-async/runtimes/teyrchain/src/staking.rs
T
pezkuwichain 3680848df2 Development (#172)
* docs: Add CLAUDE_RULES.md with strict rebrand protection rules

- Define immutable rebrand rules that cannot be violated
- Prohibit reverting rebrand for cargo check convenience
- Establish checkpoint and audit trail requirements
- Document correct error handling approach

* refactor: Complete kurdistan-sdk to pezkuwi-sdk rebrand

- Update README.md with pezkuwi-sdk branding
- Replace all kurdistan-sdk URL references with pezkuwi-sdk
- Replace kurdistan-tech with pezkuwichain in workflows
- Update email domains from @kurdistan-tech.io to @pezkuwichain.io
- Rename tool references: kurdistan-tech-publish → pezkuwi-publish
- Update runner names: kurdistan-tech-* → pezkuwichain-*
- Update analytics/forum/matrix domains to pezkuwichain.io
- Keep 'Kurdistan Tech Institute' as organization name
- Keep tech@kurdistan.gov as official government contact
2025-12-19 23:30:43 +03:00

815 lines
30 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// This file is part of Bizinikiwi.
// 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, and election related pezpallet configurations.
use super::*;
use pezcumulus_primitives_core::relay_chain::SessionIndex;
use pezframe_election_provider_support::{ElectionDataProvider, SequentialPhragmen};
use pezframe_support::traits::{ConstU128, EitherOf};
use pezkuwi_runtime_common::{prod_or_fast, BalanceToU256, U256ToBalance};
use pezpallet_election_provider_multi_block::{self as multi_block, SolutionAccuracyOf};
use pezpallet_staking_async::UseValidatorsMap;
use pezpallet_staking_async_rc_client as rc_client;
use pezsp_core::Get;
use pezsp_npos_elections::BalancingConfig;
use pezsp_runtime::{
traits::Convert, transaction_validity::TransactionPriority, FixedPointNumber, FixedU128,
SaturatedConversion,
};
use xcm::latest::prelude::*;
pub(crate) fn enable_hez_preset(fast: bool) {
Pages::set(&32);
MinerPages::set(&4);
MaxElectingVoters::set(&22_500);
TargetSnapshotPerBlock::set(&2000);
if !fast {
SignedValidationPhase::set(&(8 * Pages::get()));
SignedPhase::set(&(20 * MINUTES));
}
}
pub(crate) fn enable_ksm_preset(fast: bool) {
Pages::set(&16);
MinerPages::set(&4);
MaxElectingVoters::set(&12_500);
TargetSnapshotPerBlock::set(&2500);
if !fast {
SignedValidationPhase::set(&(4 * Pages::get()));
SignedPhase::set(&(20 * MINUTES));
}
}
// This macro contains all of the variable parameters that we intend to use for Pezkuwi and
// Kusama.
//
// Note that this runtime has 3 broad presets:
//
// 1. dev: fast development preset.
// 2. dot-size: as close to Pezkuwi as possible.
// 3. ksm-size: as close to Kusama as possible.
//
// The default values here are related to `dev`. The above helper functions are used at launch (see
// `build_state` runtime-api) to enable dot/ksm presets.
parameter_types! {
/// Number of election pages that we operate upon.
///
/// * Pezkuwi: 32 (3.2m snapshot)
/// * Kusama: 16 (1.6m snapshot)
///
/// Reasoning: Both leads to around 700 nominators per-page, yielding the weights in
/// https://github.com/pezkuwichain/pezkuwi-sdk/issues/128, the maximum of which being around 1mb
/// compressed PoV and 2mb uncompressed.
///
/// NOTE: in principle, there is nothing preventing us from stretching these values further, it
/// will only reduce the per-page POVs. Although, some operations like the first snapshot, and
/// the last page of export (where we operate on `MaxValidatorSet` validators) will not get any
/// better.
pub storage Pages: u32 = 4;
/// * Pezkuwi: 16 * 32 (512 blocks, 51.2m).
/// * Kusama: 8 * 16 (12 blocks, 12.8m).
///
/// (MaxSubmissions * Pages) for both, enough to verify all solutions.
///
/// Reasoning: Less security needed in Kusama, to compensate for the shorter session duration.
pub storage SignedValidationPhase: u32 = Pages::get() * 2;
/// * Pezkuwi: 200 blocks, 20m.
/// * Kusama: 100 blocks, 10m.
///
/// Reasoning:
///
/// * Pezkuwi wishes at least 8 submitters to be able to submit. That is 8 * 32 = 256 pages
/// for all submitters. Weight of each submission page is roughly 0.0007 of block weight. 200
/// blocks is more than enough.
/// * Kusama wishes at least 4 submitters to be able to submit. That is 4 * 16 = 64 pages for
/// all submitters. Weight of each submission page is roughly 0.0007 of block weight. 100
/// blocks is more than enough.
///
/// See `signed_weight_ratios` test below for more info.
pub storage SignedPhase: u32 = 4 * MINUTES;
/// * Pezkuwi: 4
/// * Kusama: 4
///
/// Reasoning: with 4 pages, the `ElectionScore` computed in both Kusama and Pezkuwi is pretty
/// good. See and run `run_election_with_pages` below to see. With 4 pages, roughly 2800
/// nominators will be elected. This is not great for staking reward, but is good enough for
/// chain's economic security.
pub storage MinerPages: u32 = 4;
/// * Pezkuwi: 300 blocks, 30m
/// * Kusama: 150 blocks, 15m
///
/// Reasoning: The only criteria is for the phase to be long enough such that the OCW miner is
/// able to run the mining code at least twice. Note that `OffchainRepeat` limits execution of
/// the OCW to at most 4 times per round, for faster collators.
///
/// Benchmarks logs from tests below are:
///
/// * exec_time of pezkuwi miner in WASM with 4 pages is 27369ms
/// * exec_time of kusama miner in WASM with 4 pages is 23848ms
///
/// See `max_ocw_miner_pages_as_per_weights` test below.
pub storage UnsignedPhase: u32 = MINUTES;
/// * Pezkuwi: 22_500
/// * Kusama: 12_500
///
/// Reasoning: Yielding 703 nominators per page in both. See [`Pages`] for more info. Path to
/// Upgrade: We may wish to increase the number of "active nominators" in both networks by 1)
/// increasing the `Pages` and `MaxElectingVoters` in sync. This update needs to happen while an
/// election is NOT ongoing.
pub storage MaxElectingVoters: u32 = 1000;
/// * Pezkuwi: 2000 (always equal to `staking.maxValidatorCount`)
/// * Kusama: 4000 (always equal to `staking.maxValidatorCount`)
///
/// Reasoning: As of now, we don't have a way to sort validators, so we wish to select all of
/// them. In case this limit is reached, governance should introduce `minValidatorBond`, and
/// validators would have to compete with their self-stake to force-chill one another. More
/// info: SRL-417
pub storage TargetSnapshotPerBlock: u32 = 4000;
// NOTE: rest of the parameters are computed identically in both Kusama and Pezkuwi.
/// Allow OCW miner to at most run 4 times in the entirety of the 10m Unsigned Phase.
pub OffchainRepeat: u32 = UnsignedPhase::get() / 4;
/// Upper bound of `Staking.ValidatorCount`, which translates to
/// `ElectionProvider::DesiredTargets`. 1000 is the end-game for both Kusama and Pezkuwi for
/// the foreseeable future.
pub const MaxValidatorSet: u32 = 1000;
/// Number of nominators per page of the snapshot, and consequently number of backers in the
/// solution.
///
/// 703 in both Pezkuwi and Kusama.
pub VoterSnapshotPerBlock: u32 = MaxElectingVoters::get() / Pages::get();
/// In each page, we may observe up to all of the validators.
pub const MaxWinnersPerPage: u32 = MaxValidatorSet::get();
/// In each page of the election, we allow up to all of the nominators of that page to be
/// present.
///
/// This in essence translates to "no limit on this as of now".
pub MaxBackersPerWinner: u32 = VoterSnapshotPerBlock::get();
/// Total number of backers per winner across all pages.
///
/// This in essence translates to "no limit on this as of now".
pub MaxBackersPerWinnerFinal: u32 = MaxElectingVoters::get();
/// Size of the exposures. This should be small enough to make the reward payouts cheap and
/// lightweight per-page.
// TODO: this is currently 512 in all networks, but 64 might yield better PoV, need to check logs.
pub const MaxExposurePageSize: u32 = 512;
}
// Signed phase parameters.
parameter_types! {
/// * Pezkuwi: 16
/// * Kusama: 8
///
/// Reasoning: This is double the capacity of verification. There is no point for someone to be
/// a submitter if they cannot be verified, yet, it is beneficial to act as a "reserve", in case
/// someone bails out last minute.
pub MaxSubmissions: u32 = 8;
/// * Pezkuwi: Geometric progression with starting value 4 HEZ, common factor 2. For 16
/// submissions, it will be [4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384,
/// 32768, 65536, 131072]. Sum is `262140 HEZ` for all 16 submissions.
/// * Kusama: Geometric progression with with starting value 0.1 KSM, common factor 4. For 8
/// submissions, values will be: `[0.1, 0.4, 1.6, 6.4, 25.6, 102.4, 409.6, 1638.4]`. Sum is
/// `2184.5 KSM` for all 8 submissions.
pub DepositBase: Balance = 5 * UNITS;
/// * Pezkuwi: standard byte deposit configured in PAH.
/// * Kusama: standard byte deposit configured in KAH.
///
/// TODO: need a maximum solution length for each runtime.
pub DepositPerPage: Balance = 1 * UNITS;
/// * Pezkuwi: 20 HEZ
/// * Kusama: 1 KSM
///
///
/// Fixed deposit for invulnerable accounts.
pub InvulnerableDeposit: Balance = UNITS;
/// * Pezkuwi: 10% (more restrictive, don't bail!)
/// * Kusama: 25%
///
/// Reasoning: The weight/fee of the `bail` transaction is already assuming you delete all pages
/// of your solution while bailing, and charges you accordingly. So the chain is being
/// compensated. The risk would be for an attacker to submit a lot of high score pages, and bail
/// at the end to avoid getting slashed.
pub BailoutGraceRatio: Perbill = Perbill::from_percent(5);
/// * Pezkuwi: 100%
/// * Kusama: 100%
///
/// The transaction fee of `register` takes into account the cost of possibly ejecting another
/// submission into account. In the scenario that the honest submitter is being ejected by an
/// attacker, the cost is on the attacker, and having 100% grace ratio here is only to the
/// benefit of the honest submitter.
pub EjectGraceRatio: Perbill = Perbill::from_percent(50);
/// * Pezkuwi: 5 DOTs per era/day
/// * Kusama: 1 KSM per era/6h
pub RewardBase: Balance = 10 * UNITS;
}
// * Pezkuwi: as seen here.
// * Kusama, we will use a similar type, but with 24 as the maximum filed length.
//
// Reasoning: using u16, we can have up to 65,536 nominators and validators represented in the
// snapshot. If we every go beyond this, we have to first adjust this type.
pezframe_election_provider_support::generate_solution_type!(
#[compact]
pub struct NposCompactSolution16::<
VoterIndex = u16,
TargetIndex = u16,
Accuracy = pezsp_runtime::PerU16,
MaxVoters = VoterSnapshotPerBlock,
>(16)
);
#[cfg(feature = "runtime-benchmarks")]
parameter_types! {
pub BenchElectionBounds: pezframe_election_provider_support::bounds::ElectionBounds =
pezframe_election_provider_support::bounds::ElectionBoundsBuilder::default().build();
}
#[cfg(feature = "runtime-benchmarks")]
pub struct OnChainConfig;
#[cfg(feature = "runtime-benchmarks")]
impl pezframe_election_provider_support::onchain::Config for OnChainConfig {
// unbounded
type Bounds = BenchElectionBounds;
// We should not need sorting, as our bounds are large enough for the number of
// nominators/validators in this test setup.
type Sort = ConstBool<false>;
type DataProvider = Staking;
type MaxBackersPerWinner = MaxBackersPerWinner;
type MaxWinnersPerPage = MaxWinnersPerPage;
type Solver = pezframe_election_provider_support::SequentialPhragmen<AccountId, Perbill>;
type System = Runtime;
type WeightInfo = ();
}
impl multi_block::Config for Runtime {
type AreWeDone = multi_block::RevertToSignedIfNotQueuedOf<Self>;
type Pages = Pages;
type UnsignedPhase = UnsignedPhase;
type SignedPhase = SignedPhase;
type SignedValidationPhase = SignedValidationPhase;
type VoterSnapshotPerBlock = VoterSnapshotPerBlock;
type TargetSnapshotPerBlock = TargetSnapshotPerBlock;
type AdminOrigin = EnsureRoot<AccountId>;
type ManagerOrigin = EnsureRoot<AccountId>;
type DataProvider = Staking;
#[cfg(not(feature = "runtime-benchmarks"))]
type Fallback = multi_block::Continue<Self>;
#[cfg(feature = "runtime-benchmarks")]
type Fallback = pezframe_election_provider_support::onchain::OnChainExecution<OnChainConfig>;
type MinerConfig = Self;
type Verifier = MultiBlockElectionVerifier;
type OnRoundRotation = multi_block::CleanRound<Self>;
type WeightInfo = multi_block::weights::pezkuwi::MultiBlockWeightInfo<Self>;
}
impl multi_block::verifier::Config for Runtime {
type MaxWinnersPerPage = MaxWinnersPerPage;
type MaxBackersPerWinner = MaxBackersPerWinner;
type MaxBackersPerWinnerFinal = MaxBackersPerWinnerFinal;
type SolutionDataProvider = MultiBlockElectionSigned;
type WeightInfo = multi_block::weights::pezkuwi::MultiBlockVerifierWeightInfo<Self>;
}
impl multi_block::signed::Config for Runtime {
type Currency = Balances;
type BailoutGraceRatio = BailoutGraceRatio;
type EjectGraceRatio = EjectGraceRatio;
type DepositBase = DepositBase;
type DepositPerPage = DepositPerPage;
type InvulnerableDeposit = InvulnerableDeposit;
type RewardBase = RewardBase;
type MaxSubmissions = MaxSubmissions;
type EstimateCallFee = TransactionPayment;
type WeightInfo = multi_block::weights::pezkuwi::MultiBlockSignedWeightInfo<Self>;
}
parameter_types! {
/// Priority of the offchain miner transactions.
pub MinerTxPriority: TransactionPriority = TransactionPriority::max_value() / 2;
}
pub struct Balancing;
impl Get<Option<BalancingConfig>> for Balancing {
fn get() -> Option<BalancingConfig> {
Some(BalancingConfig { iterations: 10, tolerance: 0 })
}
}
impl multi_block::unsigned::Config for Runtime {
type MinerPages = MinerPages;
type OffchainSolver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>, Balancing>;
type MinerTxPriority = MinerTxPriority;
type OffchainRepeat = OffchainRepeat;
type OffchainStorage = ConstBool<true>;
type WeightInfo = multi_block::weights::pezkuwi::MultiBlockUnsignedWeightInfo<Self>;
}
parameter_types! {
/// Miner transaction can fill up to 75% of the block size.
pub MinerMaxLength: u32 = Perbill::from_rational(75u32, 100) *
*RuntimeBlockLength::get()
.max
.get(DispatchClass::Normal);
}
impl multi_block::unsigned::miner::MinerConfig for Runtime {
type AccountId = AccountId;
type Hash = Hash;
type MaxBackersPerWinner = <Self as multi_block::verifier::Config>::MaxBackersPerWinner;
type MaxBackersPerWinnerFinal =
<Self as multi_block::verifier::Config>::MaxBackersPerWinnerFinal;
type MaxWinnersPerPage = <Self as multi_block::verifier::Config>::MaxWinnersPerPage;
type MaxVotesPerVoter =
<<Self as multi_block::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
type MaxLength = MinerMaxLength;
type Solver = <Runtime as multi_block::unsigned::Config>::OffchainSolver;
type Pages = Pages;
type Solution = NposCompactSolution16;
type VoterSnapshotPerBlock = <Runtime as multi_block::Config>::VoterSnapshotPerBlock;
type TargetSnapshotPerBlock = <Runtime as multi_block::Config>::TargetSnapshotPerBlock;
}
parameter_types! {
pub const BagThresholds: &'static [u64] = &bag_thresholds::THRESHOLDS;
}
type VoterBagsListInstance = pezpallet_bags_list::Instance1;
impl pezpallet_bags_list::Config<VoterBagsListInstance> for Runtime {
type RuntimeEvent = RuntimeEvent;
type ScoreProvider = Staking;
type WeightInfo = weights::pezpallet_bags_list::WeightInfo<Runtime>;
type BagThresholds = BagThresholds;
type Score = pezsp_npos_elections::VoteWeight;
type MaxAutoRebagPerBlock = ();
}
pub struct EraPayout;
impl pezpallet_staking_async::EraPayout<Balance> for EraPayout {
fn era_payout(
_total_staked: Balance,
_total_issuance: Balance,
era_duration_millis: u64,
) -> (Balance, Balance) {
const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100;
// A normal-sized era will have 1 / 365.25 here:
let relative_era_len =
FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into());
// Fixed total TI that we use as baseline for the issuance.
let fixed_total_issuance: i128 = 5_216_342_402_773_185_773;
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, as per Pezkuwi ref 1139.
let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission);
let to_stakers = era_emission.saturating_sub(to_treasury);
(to_stakers.saturated_into(), to_treasury.saturated_into())
}
}
parameter_types! {
// Six sessions in an era (6 hours).
pub const SessionsPerEra: SessionIndex = prod_or_fast!(6, 1);
/// Duration of a relay session in our blocks. Needs to be hardcoded per-runtime.
pub const RelaySessionDuration: BlockNumber = 10;
// 2 eras for unbonding (12 hours).
pub const BondingDuration: pezsp_staking::EraIndex = 2;
// 1 era in which slashes can be cancelled (6 hours).
pub const SlashDeferDuration: pezsp_staking::EraIndex = 1;
// Note: this is not really correct as Max Nominators is (MaxExposurePageSize * page_count) but
// this is an unbounded number. We just set it to a reasonably high value, 1 full page
// of nominators.
pub const MaxControllersInDeprecationBatch: u32 = 751;
pub const MaxNominations: u32 = <NposCompactSolution16 as pezframe_election_provider_support::NposSolution>::LIMIT as u32;
// Note: In WAH, this should be set closer to the ideal era duration to trigger capping more
// frequently. On Kusama and Pezkuwi, a higher value like 7 × ideal_era_duration is more
// appropriate.
pub const MaxEraDuration: u64 = RelaySessionDuration::get() as u64 * RELAY_CHAIN_SLOT_DURATION_MILLIS as u64 * SessionsPerEra::get() as u64;
pub MaxPruningItems: u32 = 100;
}
impl pezpallet_staking_async::Config for Runtime {
type Filter = ();
type OldCurrency = Balances;
type Currency = Balances;
type CurrencyBalance = Balance;
type RuntimeHoldReason = RuntimeHoldReason;
type CurrencyToVote = pezsp_staking::currency_to_vote::SaturatingCurrencyToVote;
type RewardRemainder = ();
type Slash = ();
type Reward = ();
type SessionsPerEra = SessionsPerEra;
type BondingDuration = BondingDuration;
type SlashDeferDuration = SlashDeferDuration;
type AdminOrigin = EitherOf<EnsureRoot<AccountId>, StakingAdmin>;
type EraPayout = EraPayout;
type MaxExposurePageSize = MaxExposurePageSize;
type ElectionProvider = MultiBlockElection;
type VoterList = VoterList;
type TargetList = UseValidatorsMap<Self>;
type MaxValidatorSet = MaxValidatorSet;
type NominationsQuota =
pezpallet_staking_async::FixedNominationsQuota<{ MaxNominations::get() }>;
type MaxUnlockingChunks = pezframe_support::traits::ConstU32<32>;
type HistoryDepth = ConstU32<1>;
type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch;
type EventListeners = (NominationPools, DelegatedStaking);
type WeightInfo = pezpallet_staking_async::weights::BizinikiwiWeight<Runtime>;
type MaxInvulnerables = pezframe_support::traits::ConstU32<20>;
type MaxEraDuration = MaxEraDuration;
type MaxPruningItems = MaxPruningItems;
type PlanningEraOffset =
pezpallet_staking_async::PlanningEraOffsetOf<Self, RelaySessionDuration, ConstU32<10>>;
type RcClientInterface = StakingRcClient;
}
impl pezpallet_staking_async_rc_client::Config for Runtime {
type RelayChainOrigin = EnsureRoot<AccountId>;
type AHStakingInterface = Staking;
type SendToRelayChain = StakingXcmToRelayChain;
type MaxValidatorSetRetries = ConstU32<5>;
}
parameter_types! {
pub StakingXcmDestination: Location = Location::parent();
}
#[derive(Encode, Decode)]
// Call indices taken from zagros-next runtime.
pub enum RelayChainRuntimePallets {
#[codec(index = 67)]
AhClient(AhClientCalls),
}
#[derive(Encode, Decode)]
pub enum AhClientCalls {
#[codec(index = 0)]
ValidatorSet(rc_client::ValidatorSetReport<AccountId>),
}
pub struct ValidatorSetToXcm;
impl Convert<rc_client::ValidatorSetReport<AccountId>, Xcm<()>> for ValidatorSetToXcm {
fn convert(report: rc_client::ValidatorSetReport<AccountId>) -> Xcm<()> {
Xcm(vec![
Instruction::UnpaidExecution {
weight_limit: WeightLimit::Unlimited,
check_origin: None,
},
Instruction::Transact {
origin_kind: OriginKind::Native,
fallback_max_weight: None,
call: RelayChainRuntimePallets::AhClient(AhClientCalls::ValidatorSet(report))
.encode()
.into(),
},
])
}
}
pub struct StakingXcmToRelayChain;
impl rc_client::SendToRelayChain for StakingXcmToRelayChain {
type AccountId = AccountId;
fn validator_set(report: rc_client::ValidatorSetReport<Self::AccountId>) -> Result<(), ()> {
rc_client::XCMSender::<
xcm_config::XcmRouter,
StakingXcmDestination,
rc_client::ValidatorSetReport<Self::AccountId>,
ValidatorSetToXcm,
>::send(report)
}
}
impl pezpallet_fast_unstake::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type BatchSize = ConstU32<64>;
type Deposit = ConstU128<{ UNITS }>;
type ControlOrigin = EnsureRoot<AccountId>;
type Staking = Staking;
type MaxErasToCheckPerBlock = ConstU32<1>;
type WeightInfo = weights::pezpallet_fast_unstake::WeightInfo<Runtime>;
}
parameter_types! {
pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
pub const MaxPointsToBalance: u8 = 10;
}
impl pezpallet_nomination_pools::Config for Runtime {
type Filter = ();
type RuntimeEvent = RuntimeEvent;
type WeightInfo = weights::pezpallet_nomination_pools::WeightInfo<Self>;
type Currency = Balances;
type RuntimeFreezeReason = RuntimeFreezeReason;
type RewardCounter = FixedU128;
type BalanceToU256 = BalanceToU256;
type U256ToBalance = U256ToBalance;
type StakeAdapter =
pezpallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
type PostUnbondingPoolsWindow = ConstU32<4>;
type MaxMetadataLen = ConstU32<256>;
// we use the same number of allowed unlocking chunks as with staking.
type MaxUnbonding = <Self as pezpallet_staking_async::Config>::MaxUnlockingChunks;
type PalletId = PoolsPalletId;
type MaxPointsToBalance = MaxPointsToBalance;
type AdminOrigin = EitherOf<EnsureRoot<AccountId>, StakingAdmin>;
type BlockNumberProvider = RelayChainBlockNumberProvider;
}
parameter_types! {
pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
}
impl pezpallet_delegated_staking::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type PalletId = DelegatedStakingPalletId;
type Currency = Balances;
type OnSlash = ();
type SlashRewardFraction = SlashRewardFraction;
type RuntimeHoldReason = RuntimeHoldReason;
type CoreStaking = Staking;
}
/// The payload being signed in transactions.
pub type SignedPayload = generic::SignedPayload<RuntimeCall, TxExtension>;
/// Unchecked extrinsic type as expected by this runtime.
pub type UncheckedExtrinsic =
generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
impl pezframe_system::offchain::SigningTypes for Runtime {
type Public = <Signature as Verify>::Signer;
type Signature = Signature;
}
impl<C> pezframe_system::offchain::CreateTransactionBase<C> for Runtime
where
RuntimeCall: From<C>,
{
type RuntimeCall = RuntimeCall;
type Extrinsic = UncheckedExtrinsic;
}
impl<LocalCall> pezframe_system::offchain::CreateTransaction<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
type Extension = TxExtension;
fn create_transaction(call: RuntimeCall, extension: TxExtension) -> UncheckedExtrinsic {
UncheckedExtrinsic::new_transaction(call, extension)
}
}
/// Submits a transaction with the node's public and signature type. Adheres to the signed extension
/// format of the chain.
impl<LocalCall> pezframe_system::offchain::CreateSignedTransaction<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
fn create_signed_transaction<
C: pezframe_system::offchain::AppCrypto<Self::Public, Self::Signature>,
>(
call: RuntimeCall,
public: <Signature as Verify>::Signer,
account: AccountId,
nonce: <Runtime as pezframe_system::Config>::Nonce,
) -> Option<UncheckedExtrinsic> {
use pezsp_runtime::traits::StaticLookup;
// take the biggest period possible.
let period =
BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64;
let current_block = System::block_number()
.saturated_into::<u64>()
// The `System::block_number` is initialized with `n+1`,
// so the actual block number is `n`.
.saturating_sub(1);
let tip = 0;
let tx_ext = TxExtension::from((
pezframe_system::CheckNonZeroSender::<Runtime>::new(),
pezframe_system::CheckSpecVersion::<Runtime>::new(),
pezframe_system::CheckTxVersion::<Runtime>::new(),
pezframe_system::CheckGenesis::<Runtime>::new(),
pezframe_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
pezframe_system::CheckNonce::<Runtime>::from(nonce),
pezframe_system::CheckWeight::<Runtime>::new(),
pezpallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None),
pezframe_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(true),
));
let raw_payload = SignedPayload::new(call, tx_ext)
.map_err(|e| {
log::warn!("Unable to create signed payload: {:?}", e);
})
.ok()?;
let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?;
let (call, tx_ext, _) = raw_payload.deconstruct();
let address = <Runtime as pezframe_system::Config>::Lookup::unlookup(account);
let transaction = UncheckedExtrinsic::new_signed(call, address, signature, tx_ext);
Some(transaction)
}
}
impl<LocalCall> pezframe_system::offchain::CreateInherent<LocalCall> for Runtime
where
RuntimeCall: From<LocalCall>,
{
fn create_bare(call: RuntimeCall) -> UncheckedExtrinsic {
UncheckedExtrinsic::new_bare(call)
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezframe_election_provider_support::ElectionProvider;
use pezframe_support::weights::constants::{
WEIGHT_PROOF_SIZE_PER_KB, WEIGHT_REF_TIME_PER_MILLIS,
};
use pezpallet_election_provider_multi_block::{
self as mb, signed::WeightInfo as _, unsigned::WeightInfo as _,
};
use remote_externalities::{
Builder, Mode, OfflineConfig, OnlineConfig, SnapshotConfig, Transport,
};
use std::env::var;
fn weight_diff(block: Weight, op: Weight) {
log::info!(
target: "runtime",
"ref_time: {:?}ms {:.4} of total",
op.ref_time() / WEIGHT_REF_TIME_PER_MILLIS,
op.ref_time() as f64 / block.ref_time() as f64
);
log::info!(
target: "runtime",
"proof_size: {:?}kb {:.4} of total",
op.proof_size() / WEIGHT_PROOF_SIZE_PER_KB,
op.proof_size() as f64 / block.proof_size() as f64
);
}
#[test]
fn signed_weight_ratios() {
pezsp_tracing::try_init_simple();
let block_weight = <Runtime as pezframe_system::Config>::BlockWeights::get().max_block;
let pezkuwi_signed_submission =
mb::weights::pezkuwi::MultiBlockSignedWeightInfo::<Runtime>::submit_page();
let kusama_signed_submission =
mb::weights::kusama::MultiBlockSignedWeightInfo::<Runtime>::submit_page();
log::info!(target: "runtime", "Pezkuwi:");
weight_diff(block_weight, pezkuwi_signed_submission);
log::info!(target: "runtime", "Kusama:");
weight_diff(block_weight, kusama_signed_submission);
}
#[test]
fn election_duration() {
pezsp_tracing::try_init_simple();
pezsp_io::TestExternalities::default().execute_with(|| {
super::enable_hez_preset(false);
let duration = mb::Pezpallet::<Runtime>::average_election_duration();
let pezkuwi_session = 6 * HOURS;
log::info!(
target: "runtime",
"Pezkuwi election duration: {:?}, session: {:?} ({} sessions)",
duration,
pezkuwi_session,
duration / pezkuwi_session
);
});
pezsp_io::TestExternalities::default().execute_with(|| {
super::enable_ksm_preset(false);
let duration = mb::Pezpallet::<Runtime>::average_election_duration();
let kusama_session = 1 * HOURS;
log::info!(
target: "runtime",
"Kusama election duration: {:?}, session: {:?} ({} sessions)",
duration,
kusama_session,
duration / kusama_session
);
});
}
#[test]
fn max_ocw_miner_pages_as_per_weights() {
pezsp_tracing::try_init_simple();
for p in 1..=32 {
log::info!(
target: "runtime",
"exec_time of pezkuwi miner in WASM with {} pages is {:?}ms",
p,
mb::weights::pezkuwi::MultiBlockUnsignedWeightInfo::<Runtime>::mine_solution(p).ref_time() / WEIGHT_REF_TIME_PER_MILLIS
);
}
for p in 1..=16 {
log::info!(
target: "runtime",
"exec_time of kusama miner in WASM with {} pages is {:?}ms",
p,
mb::weights::kusama::MultiBlockUnsignedWeightInfo::<Runtime>::mine_solution(p).ref_time() / WEIGHT_REF_TIME_PER_MILLIS
);
}
}
/// Run it like:
///
/// ```text
/// RUST_BACKTRACE=full \
/// RUST_LOG=remote-ext=info,runtime::staking-async=debug \
/// REMOTE_TESTS=1 \
/// WS=ws://127.0.0.1:9999 \
/// cargo test --release -p pezpallet-staking-async-teyrchain-runtime \
/// --features try-runtime run_try
/// ```
///
/// Just replace the node with your local node.
///
/// Pass `SNAP=pezkuwi` or similar to store and reuse a snapshot.
#[tokio::test]
async fn run_election_with_pages() {
if var("REMOTE_TESTS").is_err() {
return;
}
pezsp_tracing::try_init_simple();
let transport: Transport =
var("WS").unwrap_or("wss://zagros-rpc.pezkuwichain.io:443".to_string()).into();
let maybe_state_snapshot: Option<SnapshotConfig> = var("SNAP").map(|s| s.into()).ok();
let mut ext = Builder::<Block>::default()
.mode(if let Some(state_snapshot) = maybe_state_snapshot {
Mode::OfflineOrElseOnline(
OfflineConfig { state_snapshot: state_snapshot.clone() },
OnlineConfig {
transport,
hashed_prefixes: vec![vec![]],
state_snapshot: Some(state_snapshot),
..Default::default()
},
)
} else {
Mode::Online(OnlineConfig {
hashed_prefixes: vec![vec![]],
transport,
..Default::default()
})
})
.build()
.await
.unwrap();
ext.execute_with(|| {
pezsp_core::crypto::set_default_ss58_version(1u8.into());
super::enable_hez_preset(true);
// prepare all snapshot in EPMB pezpallet.
mb::Pezpallet::<Runtime>::asap();
for page in 1..=32 {
mb::unsigned::miner::OffchainWorkerMiner::<Runtime>::mine_solution(page, true)
.inspect(|p| log::info!(target: "runtime", "{:?}", p.score.pretty("HEZ", 10)))
.unwrap();
}
});
}
}