// 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; type DataProvider = Staking; type MaxBackersPerWinner = MaxBackersPerWinner; type MaxWinnersPerPage = MaxWinnersPerPage; type Solver = pezframe_election_provider_support::SequentialPhragmen; type System = Runtime; type WeightInfo = (); } impl multi_block::Config for Runtime { type AreWeDone = multi_block::RevertToSignedIfNotQueuedOf; type Pages = Pages; type UnsignedPhase = UnsignedPhase; type SignedPhase = SignedPhase; type SignedValidationPhase = SignedValidationPhase; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; type TargetSnapshotPerBlock = TargetSnapshotPerBlock; type AdminOrigin = EnsureRoot; type ManagerOrigin = EnsureRoot; type DataProvider = Staking; #[cfg(not(feature = "runtime-benchmarks"))] type Fallback = multi_block::Continue; #[cfg(feature = "runtime-benchmarks")] type Fallback = pezframe_election_provider_support::onchain::OnChainExecution; type MinerConfig = Self; type Verifier = MultiBlockElectionVerifier; type OnRoundRotation = multi_block::CleanRound; type WeightInfo = multi_block::weights::pezkuwi::MultiBlockWeightInfo; } 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; } 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; } parameter_types! { /// Priority of the offchain miner transactions. pub MinerTxPriority: TransactionPriority = TransactionPriority::max_value() / 2; } pub struct Balancing; impl Get> for Balancing { fn get() -> Option { Some(BalancingConfig { iterations: 10, tolerance: 0 }) } } impl multi_block::unsigned::Config for Runtime { type MinerPages = MinerPages; type OffchainSolver = SequentialPhragmen, Balancing>; type MinerTxPriority = MinerTxPriority; type OffchainRepeat = OffchainRepeat; type OffchainStorage = ConstBool; type WeightInfo = multi_block::weights::pezkuwi::MultiBlockUnsignedWeightInfo; } 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 = ::MaxBackersPerWinner; type MaxBackersPerWinnerFinal = ::MaxBackersPerWinnerFinal; type MaxWinnersPerPage = ::MaxWinnersPerPage; type MaxVotesPerVoter = <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; type MaxLength = MinerMaxLength; type Solver = ::OffchainSolver; type Pages = Pages; type Solution = NposCompactSolution16; type VoterSnapshotPerBlock = ::VoterSnapshotPerBlock; type TargetSnapshotPerBlock = ::TargetSnapshotPerBlock; } parameter_types! { pub const BagThresholds: &'static [u64] = &bag_thresholds::THRESHOLDS; } type VoterBagsListInstance = pezpallet_bags_list::Instance1; impl pezpallet_bags_list::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ScoreProvider = Staking; type WeightInfo = weights::pezpallet_bags_list::WeightInfo; type BagThresholds = BagThresholds; type Score = pezsp_npos_elections::VoteWeight; type MaxAutoRebagPerBlock = (); } pub struct EraPayout; impl pezpallet_staking_async::EraPayout 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 = ::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, StakingAdmin>; type EraPayout = EraPayout; type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; type TargetList = UseValidatorsMap; 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; type MaxInvulnerables = pezframe_support::traits::ConstU32<20>; type MaxEraDuration = MaxEraDuration; type MaxPruningItems = MaxPruningItems; type PlanningEraOffset = pezpallet_staking_async::PlanningEraOffsetOf>; type RcClientInterface = StakingRcClient; } impl pezpallet_staking_async_rc_client::Config for Runtime { type RelayChainOrigin = EnsureRoot; 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), } pub struct ValidatorSetToXcm; impl Convert, Xcm<()>> for ValidatorSetToXcm { fn convert(report: rc_client::ValidatorSetReport) -> 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) -> Result<(), ()> { rc_client::XCMSender::< xcm_config::XcmRouter, StakingXcmDestination, rc_client::ValidatorSetReport, 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; type Staking = Staking; type MaxErasToCheckPerBlock = ConstU32<1>; type WeightInfo = weights::pezpallet_fast_unstake::WeightInfo; } 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; type Currency = Balances; type RuntimeFreezeReason = RuntimeFreezeReason; type RewardCounter = FixedU128; type BalanceToU256 = BalanceToU256; type U256ToBalance = U256ToBalance; type StakeAdapter = pezpallet_nomination_pools::adapter::DelegateStake; type PostUnbondingPoolsWindow = ConstU32<4>; type MaxMetadataLen = ConstU32<256>; // we use the same number of allowed unlocking chunks as with staking. type MaxUnbonding = ::MaxUnlockingChunks; type PalletId = PoolsPalletId; type MaxPointsToBalance = MaxPointsToBalance; type AdminOrigin = EitherOf, 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; /// Unchecked extrinsic type as expected by this runtime. pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; impl pezframe_system::offchain::SigningTypes for Runtime { type Public = ::Signer; type Signature = Signature; } impl pezframe_system::offchain::CreateTransactionBase for Runtime where RuntimeCall: From, { type RuntimeCall = RuntimeCall; type Extrinsic = UncheckedExtrinsic; } impl pezframe_system::offchain::CreateTransaction for Runtime where RuntimeCall: From, { 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 pezframe_system::offchain::CreateSignedTransaction for Runtime where RuntimeCall: From, { fn create_signed_transaction< C: pezframe_system::offchain::AppCrypto, >( call: RuntimeCall, public: ::Signer, account: AccountId, nonce: ::Nonce, ) -> Option { 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::() // 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::::new(), pezframe_system::CheckSpecVersion::::new(), pezframe_system::CheckTxVersion::::new(), pezframe_system::CheckGenesis::::new(), pezframe_system::CheckEra::::from(generic::Era::mortal(period, current_block)), pezframe_system::CheckNonce::::from(nonce), pezframe_system::CheckWeight::::new(), pezpallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None), pezframe_metadata_hash_extension::CheckMetadataHash::::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 = ::Lookup::unlookup(account); let transaction = UncheckedExtrinsic::new_signed(call, address, signature, tx_ext); Some(transaction) } } impl pezframe_system::offchain::CreateInherent for Runtime where RuntimeCall: From, { 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 = ::BlockWeights::get().max_block; let pezkuwi_signed_submission = mb::weights::pezkuwi::MultiBlockSignedWeightInfo::::submit_page(); let kusama_signed_submission = mb::weights::kusama::MultiBlockSignedWeightInfo::::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::::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::::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::::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::::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 = var("SNAP").map(|s| s.into()).ok(); let mut ext = Builder::::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::::asap(); for page in 1..=32 { mb::unsigned::miner::OffchainWorkerMiner::::mine_solution(page, true) .inspect(|p| log::info!(target: "runtime", "{:?}", p.score.pretty("HEZ", 10))) .unwrap(); } }); } }