feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -0,0 +1,110 @@
// 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.
//! Benchmarks for the GRANDPA pallet.
use super::*;
use pezframe_benchmarking::v2::*;
use pezframe_system::RawOrigin;
use pezsp_core::H256;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn check_equivocation_proof(x: Linear<0, 1>) {
// NOTE: generated with the test below `test_generate_equivocation_report_blob`.
// the output should be deterministic since the keys we use are static.
// with the current benchmark setup it is not possible to generate this
// programmatically from the benchmark setup.
const EQUIVOCATION_PROOF_BLOB: [u8; 257] = [
1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 136, 220, 52, 23, 213, 5, 142, 196,
180, 80, 62, 12, 18, 234, 26, 10, 137, 190, 32, 15, 233, 137, 34, 66, 61, 67, 52, 1,
79, 166, 176, 238, 207, 48, 195, 55, 171, 225, 252, 130, 161, 56, 151, 29, 193, 32, 25,
157, 249, 39, 80, 193, 214, 96, 167, 147, 25, 130, 45, 42, 64, 208, 182, 164, 10, 0, 0,
0, 0, 0, 0, 0, 234, 236, 231, 45, 70, 171, 135, 246, 136, 153, 38, 167, 91, 134, 150,
242, 215, 83, 56, 238, 16, 119, 55, 170, 32, 69, 255, 248, 164, 20, 57, 50, 122, 115,
135, 96, 80, 203, 131, 232, 73, 23, 149, 86, 174, 59, 193, 92, 121, 76, 154, 211, 44,
96, 10, 84, 159, 133, 211, 56, 103, 0, 59, 2, 96, 20, 69, 2, 32, 179, 16, 184, 108, 76,
215, 64, 195, 78, 143, 73, 177, 139, 20, 144, 98, 231, 41, 117, 255, 220, 115, 41, 59,
27, 75, 56, 10, 0, 0, 0, 0, 0, 0, 0, 128, 179, 250, 48, 211, 76, 10, 70, 74, 230, 219,
139, 96, 78, 88, 112, 33, 170, 44, 184, 59, 200, 155, 143, 128, 40, 222, 179, 210, 190,
84, 16, 182, 21, 34, 94, 28, 193, 163, 226, 51, 251, 134, 233, 187, 121, 63, 157, 240,
165, 203, 92, 16, 146, 120, 190, 229, 251, 129, 29, 45, 32, 29, 6,
];
let equivocation_proof1: pezsp_consensus_grandpa::EquivocationProof<H256, u64> =
Decode::decode(&mut &EQUIVOCATION_PROOF_BLOB[..]).unwrap();
let equivocation_proof2 = equivocation_proof1.clone();
#[block]
{
pezsp_consensus_grandpa::check_equivocation_proof(equivocation_proof1);
}
assert!(pezsp_consensus_grandpa::check_equivocation_proof(equivocation_proof2));
}
#[benchmark]
fn note_stalled() {
let delay = 1000u32.into();
let best_finalized_block_number = 1u32.into();
#[extrinsic_call]
_(RawOrigin::Root, delay, best_finalized_block_number);
assert!(Stalled::<T>::get().is_some());
}
impl_benchmark_test_suite!(
Pallet,
crate::mock::new_test_ext(vec![(1, 1), (2, 1), (3, 1)]),
crate::mock::Test,
);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mock::*;
#[test]
fn test_generate_equivocation_report_blob() {
let authorities = crate::tests::test_authorities();
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
// generate an equivocation proof, with two votes in the same round for
// different block hashes signed by the same key
let equivocation_proof = generate_equivocation_proof(
1,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
println!("equivocation_proof: {:?}", equivocation_proof);
println!("equivocation_proof.encode(): {:?}", equivocation_proof.encode());
});
}
}
@@ -0,0 +1,57 @@
// 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.
//! Default weights for the GRANDPA Pallet
//! This file was not auto-generated.
use pezframe_support::weights::{
constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS},
Weight,
};
impl crate::WeightInfo for () {
fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
// we take the validator set count from the membership proof to
// calculate the weight but we set a floor of 100 validators.
let validator_count = validator_count.max(100) as u64;
// checking membership proof
Weight::from_parts(35u64 * WEIGHT_REF_TIME_PER_MICROS, 0)
.saturating_add(
Weight::from_parts(175u64 * WEIGHT_REF_TIME_PER_NANOS, 0)
.saturating_mul(validator_count),
)
.saturating_add(DbWeight::get().reads(5))
// check equivocation proof
.saturating_add(Weight::from_parts(95u64 * WEIGHT_REF_TIME_PER_MICROS, 0))
// report offence
.saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0))
.saturating_add(Weight::from_parts(
25u64 * WEIGHT_REF_TIME_PER_MICROS * max_nominators_per_validator as u64,
0,
))
.saturating_add(DbWeight::get().reads(14 + 3 * max_nominators_per_validator as u64))
.saturating_add(DbWeight::get().writes(10 + 3 * max_nominators_per_validator as u64))
// fetching set id -> session index mappings
.saturating_add(DbWeight::get().reads(2))
}
fn note_stalled() -> Weight {
Weight::from_parts(3u64 * WEIGHT_REF_TIME_PER_MICROS, 0)
.saturating_add(DbWeight::get().writes(1))
}
}
@@ -0,0 +1,291 @@
// 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.
//! An opt-in utility module for reporting equivocations.
//!
//! This module defines an offence type for GRANDPA equivocations
//! and some utility traits to wire together:
//! - a key ownership proof system (e.g. to prove that a given authority was
//! part of a session);
//! - a system for reporting offences;
//! - a system for signing and submitting transactions;
//! - a way to get the current block author;
//!
//! These can be used in an offchain context in order to submit equivocation
//! reporting extrinsics (from the client that's running the GRANDPA protocol).
//! And in a runtime context, so that the GRANDPA module can validate the
//! equivocation proofs in the extrinsic and report the offences.
//!
//! IMPORTANT:
//! When using this module for enabling equivocation reporting it is required
//! that the `ValidateUnsigned` for the GRANDPA pallet is used in the runtime
//! definition.
use alloc::{boxed::Box, vec, vec::Vec};
use codec::{self as codec, Decode, Encode};
use pezframe_support::traits::{Get, KeyOwnerProofSystem};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
use log::{error, info};
use pezsp_consensus_grandpa::{AuthorityId, EquivocationProof, RoundNumber, SetId, KEY_TYPE};
use pezsp_runtime::{
transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
TransactionValidityError, ValidTransaction,
},
DispatchError, KeyTypeId, Perbill,
};
use pezsp_session::{GetSessionNumber, GetValidatorCount};
use pezsp_staking::{
offence::{Kind, Offence, OffenceReportSystem, ReportOffence},
SessionIndex,
};
use super::{Call, Config, Error, Pallet, LOG_TARGET};
/// A round number and set id which point on the time of an offence.
#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)]
pub struct TimeSlot {
// The order of these matters for `derive(Ord)`.
/// Grandpa Set ID.
pub set_id: SetId,
/// Round number.
pub round: RoundNumber,
}
/// GRANDPA equivocation offence report.
pub struct EquivocationOffence<Offender> {
/// Time slot at which this incident happened.
pub time_slot: TimeSlot,
/// The session index in which the incident happened.
pub session_index: SessionIndex,
/// The size of the validator set at the time of the offence.
pub validator_set_count: u32,
/// The authority which produced this equivocation.
pub offender: Offender,
}
impl<Offender: Clone> Offence<Offender> for EquivocationOffence<Offender> {
const ID: Kind = *b"grandpa:equivoca";
type TimeSlot = TimeSlot;
fn offenders(&self) -> Vec<Offender> {
vec![self.offender.clone()]
}
fn session_index(&self) -> SessionIndex {
self.session_index
}
fn validator_set_count(&self) -> u32 {
self.validator_set_count
}
fn time_slot(&self) -> Self::TimeSlot {
self.time_slot
}
// The formula is min((3k / n)^2, 1)
// where k = offenders_number and n = validators_number
fn slash_fraction(&self, offenders_count: u32) -> Perbill {
// Perbill type domain is [0, 1] by definition
Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
}
}
/// GRANDPA equivocation offence report system.
///
/// This type implements `OffenceReportSystem` such that:
/// - Equivocation reports are published on-chain as unsigned extrinsic via
/// `offchain::CreateTransactionBase`.
/// - On-chain validity checks and processing are mostly delegated to the user provided generic
/// types implementing `KeyOwnerProofSystem` and `ReportOffence` traits.
/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet.
pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
impl<T, R, P, L>
OffenceReportSystem<
Option<T::AccountId>,
(EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
> for EquivocationReportSystem<T, R, P, L>
where
T: Config + pezpallet_authorship::Config + pezframe_system::offchain::CreateBare<Call<T>>,
R: ReportOffence<
T::AccountId,
P::IdentificationTuple,
EquivocationOffence<P::IdentificationTuple>,
>,
P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>,
P::IdentificationTuple: Clone,
L: Get<u64>,
{
type Longevity = L;
fn publish_evidence(
evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
) -> Result<(), ()> {
use pezframe_system::offchain::SubmitTransaction;
let (equivocation_proof, key_owner_proof) = evidence;
let call = Call::report_equivocation_unsigned {
equivocation_proof: Box::new(equivocation_proof),
key_owner_proof,
};
let xt = T::create_bare(call.into());
let res = SubmitTransaction::<T, Call<T>>::submit_transaction(xt);
match res {
Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"),
Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e),
}
res
}
fn check_evidence(
evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
) -> Result<(), TransactionValidityError> {
let (equivocation_proof, key_owner_proof) = evidence;
// Check the membership proof to extract the offender's id
let key = (KEY_TYPE, equivocation_proof.offender().clone());
let offender = P::check_proof(key, key_owner_proof).ok_or(InvalidTransaction::BadProof)?;
// Check if the offence has already been reported, and if so then we can discard the report.
let time_slot =
TimeSlot { set_id: equivocation_proof.set_id(), round: equivocation_proof.round() };
if R::is_known_offence(&[offender], &time_slot) {
Err(InvalidTransaction::Stale.into())
} else {
Ok(())
}
}
fn process_evidence(
reporter: Option<T::AccountId>,
evidence: (EquivocationProof<T::Hash, BlockNumberFor<T>>, T::KeyOwnerProof),
) -> Result<(), DispatchError> {
let (equivocation_proof, key_owner_proof) = evidence;
let reporter = reporter.or_else(|| pezpallet_authorship::Pallet::<T>::author());
let offender = equivocation_proof.offender().clone();
// We check the equivocation within the context of its set id (and
// associated session) and round. We also need to know the validator
// set count when the offence since it is required to calculate the
// slash amount.
let set_id = equivocation_proof.set_id();
let round = equivocation_proof.round();
let session_index = key_owner_proof.session();
let validator_set_count = key_owner_proof.validator_count();
// Validate equivocation proof (check votes are different and signatures are valid).
if !pezsp_consensus_grandpa::check_equivocation_proof(equivocation_proof) {
return Err(Error::<T>::InvalidEquivocationProof.into());
}
// Validate the key ownership proof extracting the id of the offender.
let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof)
.ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
// Fetch the current and previous sets last session index.
// For genesis set there's no previous set.
let previous_set_id_session_index = if set_id != 0 {
let idx = crate::SetIdSession::<T>::get(set_id - 1)
.ok_or(Error::<T>::InvalidEquivocationProof)?;
Some(idx)
} else {
None
};
let set_id_session_index =
crate::SetIdSession::<T>::get(set_id).ok_or(Error::<T>::InvalidEquivocationProof)?;
// Check that the session id for the membership proof is within the
// bounds of the set id reported in the equivocation.
if session_index > set_id_session_index ||
previous_set_id_session_index
.map(|previous_index| session_index <= previous_index)
.unwrap_or(false)
{
return Err(Error::<T>::InvalidEquivocationProof.into());
}
let offence = EquivocationOffence {
time_slot: TimeSlot { set_id, round },
session_index,
offender,
validator_set_count,
};
R::report_offence(reporter.into_iter().collect(), offence)
.map_err(|_| Error::<T>::DuplicateOffenceReport)?;
Ok(())
}
}
/// Methods for the `ValidateUnsigned` implementation:
/// It restricts calls to `report_equivocation_unsigned` to local calls (i.e. extrinsics generated
/// on this node) or that already in a block. This guarantees that only block authors can include
/// unsigned equivocation reports.
impl<T: Config> Pallet<T> {
pub fn validate_unsigned(source: TransactionSource, call: &Call<T>) -> TransactionValidity {
if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
// discard equivocation report not coming from the local node
match source {
TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ },
_ => {
log::warn!(
target: LOG_TARGET,
"rejecting unsigned report equivocation transaction because it is not local/in-block."
);
return InvalidTransaction::Call.into();
},
}
// Check report validity
let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
T::EquivocationReportSystem::check_evidence(evidence)?;
let longevity =
<T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
ValidTransaction::with_tag_prefix("GrandpaEquivocation")
// We assign the maximum priority for any equivocation report.
.priority(TransactionPriority::max_value())
// Only one equivocation report for the same offender at the same slot.
.and_provides((
equivocation_proof.offender().clone(),
equivocation_proof.set_id(),
equivocation_proof.round(),
))
.longevity(longevity)
// We don't propagate this. This can never be included on a remote node.
.propagate(false)
.build()
} else {
InvalidTransaction::Call.into()
}
}
pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call {
let evidence = (*equivocation_proof.clone(), key_owner_proof.clone());
T::EquivocationReportSystem::check_evidence(evidence)
} else {
Err(InvalidTransaction::Call.into())
}
}
}
+661
View File
@@ -0,0 +1,661 @@
// 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.
//! GRANDPA Consensus module for runtime.
//!
//! This manages the GRANDPA authority set ready for the native code.
//! These authorities are only for GRANDPA finality, not for consensus overall.
//!
//! In the future, it will also handle misbehavior reports, and on-chain
//! finality notifications.
//!
//! For full integration with GRANDPA, the `GrandpaApi` should be implemented.
//! The necessary items are re-exported via the `fg_primitives` crate.
#![cfg_attr(not(feature = "std"), no_std)]
extern crate alloc;
// Re-export since this is necessary for `impl_apis` in runtime.
pub use pezsp_consensus_grandpa::{
self as fg_primitives, AuthorityId, AuthorityList, AuthorityWeight,
};
use alloc::{boxed::Box, vec::Vec};
use codec::{Decode, Encode, MaxEncodedLen};
use pezframe_support::{
dispatch::{DispatchResultWithPostInfo, Pays},
pezpallet_prelude::Get,
traits::OneSessionHandler,
weights::Weight,
WeakBoundedVec,
};
use pezframe_system::pezpallet_prelude::BlockNumberFor;
use scale_info::TypeInfo;
use pezsp_consensus_grandpa::{
ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_ENGINE_ID,
RUNTIME_LOG_TARGET as LOG_TARGET,
};
use pezsp_runtime::{generic::DigestItem, traits::Zero, DispatchResult};
use pezsp_session::{GetSessionNumber, GetValidatorCount};
use pezsp_staking::{offence::OffenceReportSystem, SessionIndex};
mod default_weights;
mod equivocation;
pub mod migrations;
#[cfg(any(feature = "runtime-benchmarks", test))]
mod benchmarking;
#[cfg(all(feature = "std", test))]
mod mock;
#[cfg(all(feature = "std", test))]
mod tests;
pub use equivocation::{EquivocationOffence, EquivocationReportSystem, TimeSlot};
pub use pallet::*;
#[pezframe_support::pallet]
pub mod pallet {
use super::*;
use pezframe_support::{dispatch::DispatchResult, pezpallet_prelude::*};
use pezframe_system::pezpallet_prelude::*;
/// The in-code storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(5);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
pub trait Config: pezframe_system::Config {
/// The event type of this module.
#[allow(deprecated)]
type RuntimeEvent: From<Event>
+ Into<<Self as pezframe_system::Config>::RuntimeEvent>
+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
/// Weights for this pallet.
type WeightInfo: WeightInfo;
/// Max Authorities in use
#[pallet::constant]
type MaxAuthorities: Get<u32>;
/// The maximum number of nominators for each validator.
#[pallet::constant]
type MaxNominators: Get<u32>;
/// The maximum number of entries to keep in the set id to session index mapping.
///
/// Since the `SetIdSession` map is only used for validating equivocations this
/// value should relate to the bonding duration of whatever staking system is
/// being used (if any). If equivocation handling is not enabled then this value
/// can be zero.
#[pallet::constant]
type MaxSetIdSessionEntries: Get<u64>;
/// The proof of key ownership, used for validating equivocation reports
/// The proof include the session index and validator count of the
/// session at which the equivocation occurred.
type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount;
/// The equivocation handling subsystem, defines methods to check/report an
/// offence and for submitting a transaction to report an equivocation
/// (from an offchain context).
type EquivocationReportSystem: OffenceReportSystem<
Option<Self::AccountId>,
(EquivocationProof<Self::Hash, BlockNumberFor<Self>>, Self::KeyOwnerProof),
>;
}
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
fn on_finalize(block_number: BlockNumberFor<T>) {
// check for scheduled pending authority set changes
if let Some(pending_change) = PendingChange::<T>::get() {
// emit signal if we're at the block that scheduled the change
if block_number == pending_change.scheduled_at {
let next_authorities = pending_change.next_authorities.to_vec();
if let Some(median) = pending_change.forced {
Self::deposit_log(ConsensusLog::ForcedChange(
median,
ScheduledChange { delay: pending_change.delay, next_authorities },
))
} else {
Self::deposit_log(ConsensusLog::ScheduledChange(ScheduledChange {
delay: pending_change.delay,
next_authorities,
}));
}
}
// enact the change if we've reached the enacting block
if block_number == pending_change.scheduled_at + pending_change.delay {
Authorities::<T>::put(&pending_change.next_authorities);
Self::deposit_event(Event::NewAuthorities {
authority_set: pending_change.next_authorities.into_inner(),
});
PendingChange::<T>::kill();
}
}
// check for scheduled pending state changes
match State::<T>::get() {
StoredState::PendingPause { scheduled_at, delay } => {
// signal change to pause
if block_number == scheduled_at {
Self::deposit_log(ConsensusLog::Pause(delay));
}
// enact change to paused state
if block_number == scheduled_at + delay {
State::<T>::put(StoredState::Paused);
Self::deposit_event(Event::Paused);
}
},
StoredState::PendingResume { scheduled_at, delay } => {
// signal change to resume
if block_number == scheduled_at {
Self::deposit_log(ConsensusLog::Resume(delay));
}
// enact change to live state
if block_number == scheduled_at + delay {
State::<T>::put(StoredState::Live);
Self::deposit_event(Event::Resumed);
}
},
_ => {},
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Report voter equivocation/misbehavior. This method will verify the
/// equivocation proof and validate the given key ownership proof
/// against the extracted offender. If both are valid, the offence
/// will be reported.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::report_equivocation(
key_owner_proof.validator_count(),
T::MaxNominators::get(),
))]
pub fn report_equivocation(
origin: OriginFor<T>,
equivocation_proof: Box<EquivocationProof<T::Hash, BlockNumberFor<T>>>,
key_owner_proof: T::KeyOwnerProof,
) -> DispatchResultWithPostInfo {
let reporter = ensure_signed(origin)?;
T::EquivocationReportSystem::process_evidence(
Some(reporter),
(*equivocation_proof, key_owner_proof),
)?;
// Waive the fee since the report is valid and beneficial
Ok(Pays::No.into())
}
/// Report voter equivocation/misbehavior. This method will verify the
/// equivocation proof and validate the given key ownership proof
/// against the extracted offender. If both are valid, the offence
/// will be reported.
///
/// This extrinsic must be called unsigned and it is expected that only
/// block authors will call it (validated in `ValidateUnsigned`), as such
/// if the block author is defined it will be defined as the equivocation
/// reporter.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::report_equivocation(
key_owner_proof.validator_count(),
T::MaxNominators::get(),
))]
pub fn report_equivocation_unsigned(
origin: OriginFor<T>,
equivocation_proof: Box<EquivocationProof<T::Hash, BlockNumberFor<T>>>,
key_owner_proof: T::KeyOwnerProof,
) -> DispatchResultWithPostInfo {
ensure_none(origin)?;
T::EquivocationReportSystem::process_evidence(
None,
(*equivocation_proof, key_owner_proof),
)?;
Ok(Pays::No.into())
}
/// Note that the current authority set of the GRANDPA finality gadget has stalled.
///
/// This will trigger a forced authority set change at the beginning of the next session, to
/// be enacted `delay` blocks after that. The `delay` should be high enough to safely assume
/// that the block signalling the forced change will not be re-orged e.g. 1000 blocks.
/// The block production rate (which may be slowed down because of finality lagging) should
/// be taken into account when choosing the `delay`. The GRANDPA voters based on the new
/// authority will start voting on top of `best_finalized_block_number` for new finalized
/// blocks. `best_finalized_block_number` should be the highest of the latest finalized
/// block of all validators of the new authority set.
///
/// Only callable by root.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::note_stalled())]
pub fn note_stalled(
origin: OriginFor<T>,
delay: BlockNumberFor<T>,
best_finalized_block_number: BlockNumberFor<T>,
) -> DispatchResult {
ensure_root(origin)?;
Self::on_stalled(delay, best_finalized_block_number);
Ok(())
}
}
#[pallet::event]
#[pallet::generate_deposit(fn deposit_event)]
pub enum Event {
/// New authority set has been applied.
NewAuthorities { authority_set: AuthorityList },
/// Current authority set has been paused.
Paused,
/// Current authority set has been resumed.
Resumed,
}
#[pallet::error]
pub enum Error<T> {
/// Attempt to signal GRANDPA pause when the authority set isn't live
/// (either paused or already pending pause).
PauseFailed,
/// Attempt to signal GRANDPA resume when the authority set isn't paused
/// (either live or already pending resume).
ResumeFailed,
/// Attempt to signal GRANDPA change with one already pending.
ChangePending,
/// Cannot signal forced change so soon after last.
TooSoon,
/// A key ownership proof provided as part of an equivocation report is invalid.
InvalidKeyOwnershipProof,
/// An equivocation proof provided as part of an equivocation report is invalid.
InvalidEquivocationProof,
/// A given equivocation report is valid but already previously reported.
DuplicateOffenceReport,
}
#[pallet::type_value]
pub fn DefaultForState<T: Config>() -> StoredState<BlockNumberFor<T>> {
StoredState::Live
}
/// State of the current authority set.
#[pallet::storage]
pub type State<T: Config> =
StorageValue<_, StoredState<BlockNumberFor<T>>, ValueQuery, DefaultForState<T>>;
/// Pending change: (signaled at, scheduled change).
#[pallet::storage]
pub type PendingChange<T: Config> =
StorageValue<_, StoredPendingChange<BlockNumberFor<T>, T::MaxAuthorities>>;
/// next block number where we can force a change.
#[pallet::storage]
pub type NextForced<T: Config> = StorageValue<_, BlockNumberFor<T>>;
/// `true` if we are currently stalled.
#[pallet::storage]
pub type Stalled<T: Config> = StorageValue<_, (BlockNumberFor<T>, BlockNumberFor<T>)>;
/// The number of changes (both in terms of keys and underlying economic responsibilities)
/// in the "set" of Grandpa validators from genesis.
#[pallet::storage]
pub type CurrentSetId<T: Config> = StorageValue<_, SetId, ValueQuery>;
/// A mapping from grandpa set ID to the index of the *most recent* session for which its
/// members were responsible.
///
/// This is only used for validating equivocation proofs. An equivocation proof must
/// contains a key-ownership proof for a given session, therefore we need a way to tie
/// together sessions and GRANDPA set ids, i.e. we need to validate that a validator
/// was the owner of a given key on a given session, and what the active set ID was
/// during that session.
///
/// TWOX-NOTE: `SetId` is not under user control.
#[pallet::storage]
pub type SetIdSession<T: Config> = StorageMap<_, Twox64Concat, SetId, SessionIndex>;
/// The current list of authorities.
#[pallet::storage]
pub type Authorities<T: Config> =
StorageValue<_, BoundedAuthorityList<T::MaxAuthorities>, ValueQuery>;
#[derive(pezframe_support::DefaultNoBound)]
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub authorities: AuthorityList,
#[serde(skip)]
pub _config: core::marker::PhantomData<T>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
CurrentSetId::<T>::put(SetId::default());
Pallet::<T>::initialize(self.authorities.clone())
}
}
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
type Call = Call<T>;
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
Self::validate_unsigned(source, call)
}
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
Self::pre_dispatch(call)
}
}
}
pub trait WeightInfo {
fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight;
fn note_stalled() -> Weight;
}
/// Bounded version of `AuthorityList`, `Limit` being the bound
pub type BoundedAuthorityList<Limit> = WeakBoundedVec<(AuthorityId, AuthorityWeight), Limit>;
/// A stored pending change.
/// `Limit` is the bound for `next_authorities`
#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)]
#[codec(mel_bound(N: MaxEncodedLen, Limit: Get<u32>))]
#[scale_info(skip_type_params(Limit))]
pub struct StoredPendingChange<N, Limit> {
/// The block number this was scheduled at.
pub scheduled_at: N,
/// The delay in blocks until it will be applied.
pub delay: N,
/// The next authority set, weakly bounded in size by `Limit`.
pub next_authorities: BoundedAuthorityList<Limit>,
/// If defined it means the change was forced and the given block number
/// indicates the median last finalized block when the change was signaled.
pub forced: Option<N>,
}
/// Current state of the GRANDPA authority set. State transitions must happen in
/// the same order of states defined below, e.g. `Paused` implies a prior
/// `PendingPause`.
#[derive(Decode, Encode, TypeInfo, MaxEncodedLen)]
#[cfg_attr(test, derive(Debug, PartialEq))]
pub enum StoredState<N> {
/// The current authority set is live, and GRANDPA is enabled.
Live,
/// There is a pending pause event which will be enacted at the given block
/// height.
PendingPause {
/// Block at which the intention to pause was scheduled.
scheduled_at: N,
/// Number of blocks after which the change will be enacted.
delay: N,
},
/// The current GRANDPA authority set is paused.
Paused,
/// There is a pending resume event which will be enacted at the given block
/// height.
PendingResume {
/// Block at which the intention to resume was scheduled.
scheduled_at: N,
/// Number of blocks after which the change will be enacted.
delay: N,
},
}
impl<T: Config> Pallet<T> {
/// State of the current authority set.
pub fn state() -> StoredState<BlockNumberFor<T>> {
State::<T>::get()
}
/// Pending change: (signaled at, scheduled change).
pub fn pending_change() -> Option<StoredPendingChange<BlockNumberFor<T>, T::MaxAuthorities>> {
PendingChange::<T>::get()
}
/// next block number where we can force a change.
pub fn next_forced() -> Option<BlockNumberFor<T>> {
NextForced::<T>::get()
}
/// `true` if we are currently stalled.
pub fn stalled() -> Option<(BlockNumberFor<T>, BlockNumberFor<T>)> {
Stalled::<T>::get()
}
/// The number of changes (both in terms of keys and underlying economic responsibilities)
/// in the "set" of Grandpa validators from genesis.
pub fn current_set_id() -> SetId {
CurrentSetId::<T>::get()
}
/// A mapping from grandpa set ID to the index of the *most recent* session for which its
/// members were responsible.
///
/// This is only used for validating equivocation proofs. An equivocation proof must
/// contains a key-ownership proof for a given session, therefore we need a way to tie
/// together sessions and GRANDPA set ids, i.e. we need to validate that a validator
/// was the owner of a given key on a given session, and what the active set ID was
/// during that session.
pub fn session_for_set(set_id: SetId) -> Option<SessionIndex> {
SetIdSession::<T>::get(set_id)
}
/// Get the current set of authorities, along with their respective weights.
pub fn grandpa_authorities() -> AuthorityList {
Authorities::<T>::get().into_inner()
}
/// Schedule GRANDPA to pause starting in the given number of blocks.
/// Cannot be done when already paused.
pub fn schedule_pause(in_blocks: BlockNumberFor<T>) -> DispatchResult {
if let StoredState::Live = State::<T>::get() {
let scheduled_at = pezframe_system::Pallet::<T>::block_number();
State::<T>::put(StoredState::PendingPause { delay: in_blocks, scheduled_at });
Ok(())
} else {
Err(Error::<T>::PauseFailed.into())
}
}
/// Schedule a resume of GRANDPA after pausing.
pub fn schedule_resume(in_blocks: BlockNumberFor<T>) -> DispatchResult {
if let StoredState::Paused = State::<T>::get() {
let scheduled_at = pezframe_system::Pallet::<T>::block_number();
State::<T>::put(StoredState::PendingResume { delay: in_blocks, scheduled_at });
Ok(())
} else {
Err(Error::<T>::ResumeFailed.into())
}
}
/// Schedule a change in the authorities.
///
/// The change will be applied at the end of execution of the block
/// `in_blocks` after the current block. This value may be 0, in which
/// case the change is applied at the end of the current block.
///
/// If the `forced` parameter is defined, this indicates that the current
/// set has been synchronously determined to be offline and that after
/// `in_blocks` the given change should be applied. The given block number
/// indicates the median last finalized block number and it should be used
/// as the canon block when starting the new grandpa voter.
///
/// No change should be signaled while any change is pending. Returns
/// an error if a change is already pending.
pub fn schedule_change(
next_authorities: AuthorityList,
in_blocks: BlockNumberFor<T>,
forced: Option<BlockNumberFor<T>>,
) -> DispatchResult {
if !PendingChange::<T>::exists() {
let scheduled_at = pezframe_system::Pallet::<T>::block_number();
if forced.is_some() {
if NextForced::<T>::get().map_or(false, |next| next > scheduled_at) {
return Err(Error::<T>::TooSoon.into());
}
// only allow the next forced change when twice the window has passed since
// this one.
NextForced::<T>::put(scheduled_at + in_blocks * 2u32.into());
}
let next_authorities = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
next_authorities,
Some(
"Warning: The number of authorities given is too big. \
A runtime configuration adjustment may be needed.",
),
);
PendingChange::<T>::put(StoredPendingChange {
delay: in_blocks,
scheduled_at,
next_authorities,
forced,
});
Ok(())
} else {
Err(Error::<T>::ChangePending.into())
}
}
/// Deposit one of this module's logs.
fn deposit_log(log: ConsensusLog<BlockNumberFor<T>>) {
let log = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode());
pezframe_system::Pallet::<T>::deposit_log(log);
}
// Perform module initialization, abstracted so that it can be called either through genesis
// config builder or through `on_genesis_session`.
fn initialize(authorities: AuthorityList) {
if !authorities.is_empty() {
assert!(Self::grandpa_authorities().is_empty(), "Authorities are already initialized!");
Authorities::<T>::put(
&BoundedAuthorityList::<T::MaxAuthorities>::try_from(authorities).expect(
"Grandpa: `Config::MaxAuthorities` is smaller than the number of genesis authorities!",
),
);
}
// NOTE: initialize first session of first set. this is necessary for
// the genesis set and session since we only update the set -> session
// mapping whenever a new session starts, i.e. through `on_new_session`.
SetIdSession::<T>::insert(0, 0);
}
/// Submits an extrinsic to report an equivocation. This method will create
/// an unsigned extrinsic with a call to `report_equivocation_unsigned` and
/// will push the transaction to the pool. Only useful in an offchain
/// context.
pub fn submit_unsigned_equivocation_report(
equivocation_proof: EquivocationProof<T::Hash, BlockNumberFor<T>>,
key_owner_proof: T::KeyOwnerProof,
) -> Option<()> {
T::EquivocationReportSystem::publish_evidence((equivocation_proof, key_owner_proof)).ok()
}
fn on_stalled(further_wait: BlockNumberFor<T>, median: BlockNumberFor<T>) {
// when we record old authority sets we could try to figure out _who_
// failed. until then, we can't meaningfully guard against
// `next == last` the way that normal session changes do.
Stalled::<T>::put((further_wait, median));
}
}
impl<T: Config> pezsp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
type Public = AuthorityId;
}
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T>
where
T: pezpallet_session::Config,
{
type Key = AuthorityId;
fn on_genesis_session<'a, I: 'a>(validators: I)
where
I: Iterator<Item = (&'a T::AccountId, AuthorityId)>,
{
let authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>();
Self::initialize(authorities);
}
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, _queued_validators: I)
where
I: Iterator<Item = (&'a T::AccountId, AuthorityId)>,
{
// Always issue a change if `session` says that the validators have changed.
// Even if their session keys are the same as before, the underlying economic
// identities have changed.
let current_set_id = if changed || Stalled::<T>::exists() {
let next_authorities = validators.map(|(_, k)| (k, 1)).collect::<Vec<_>>();
let res = if let Some((further_wait, median)) = Stalled::<T>::take() {
Self::schedule_change(next_authorities, further_wait, Some(median))
} else {
Self::schedule_change(next_authorities, Zero::zero(), None)
};
if res.is_ok() {
let current_set_id = CurrentSetId::<T>::mutate(|s| {
*s += 1;
*s
});
let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1);
if current_set_id >= max_set_id_session_entries {
SetIdSession::<T>::remove(current_set_id - max_set_id_session_entries);
}
current_set_id
} else {
// either the session module signalled that the validators have changed
// or the set was stalled. but since we didn't successfully schedule
// an authority set change we do not increment the set id.
CurrentSetId::<T>::get()
}
} else {
// nothing's changed, neither economic conditions nor session keys. update the pointer
// of the current set.
CurrentSetId::<T>::get()
};
// update the mapping to note that the current set corresponds to the
// latest equivalent session (i.e. now).
let session_index = pezpallet_session::Pallet::<T>::current_index();
SetIdSession::<T>::insert(current_set_id, &session_index);
}
fn on_disabled(i: u32) {
Self::deposit_log(ConsensusLog::OnDisabled(i as u64))
}
}
@@ -0,0 +1,68 @@
// 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.
use pezframe_support::{
traits::{Get, OnRuntimeUpgrade},
weights::Weight,
};
use crate::{Config, CurrentSetId, SetIdSession, LOG_TARGET};
pub use v5::MigrateV4ToV5;
/// Version 4.
pub mod v4;
mod v5;
/// This migration will clean up all stale set id -> session entries from the
/// `SetIdSession` storage map, only the latest `max_set_id_session_entries`
/// will be kept.
///
/// This migration should be added with a runtime upgrade that introduces the
/// `MaxSetIdSessionEntries` constant to the pallet (although it could also be
/// done later on).
pub struct CleanupSetIdSessionMap<T>(core::marker::PhantomData<T>);
impl<T: Config> OnRuntimeUpgrade for CleanupSetIdSessionMap<T> {
fn on_runtime_upgrade() -> Weight {
// NOTE: since this migration will loop over all stale entries in the
// map we need to set some cutoff value, otherwise the migration might
// take too long to run. for scenarios where there are that many entries
// to cleanup a multiblock migration will be needed instead.
if CurrentSetId::<T>::get() > 25_000 {
log::warn!(
target: LOG_TARGET,
"CleanupSetIdSessionMap migration was aborted since there are too many entries to cleanup."
);
return T::DbWeight::get().reads(1);
}
cleanup_set_id_sesion_map::<T>()
}
}
fn cleanup_set_id_sesion_map<T: Config>() -> Weight {
let until_set_id = CurrentSetId::<T>::get().saturating_sub(T::MaxSetIdSessionEntries::get());
for set_id in 0..=until_set_id {
SetIdSession::<T>::remove(set_id);
}
T::DbWeight::get()
.reads(1)
.saturating_add(T::DbWeight::get().writes(until_set_id + 1))
}
@@ -0,0 +1,111 @@
// 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.
use crate::LOG_TARGET;
use pezframe_support::{
traits::{Get, StorageVersion},
weights::Weight,
};
use pezsp_io::hashing::twox_128;
/// The old prefix.
pub const OLD_PREFIX: &[u8] = b"GrandpaFinality";
/// Migrate the entire storage of this pallet to a new prefix.
///
/// This new prefix must be the same as the one set in construct_runtime. For safety, use
/// `PalletInfo` to get it, as:
/// `<Runtime as pezframe_system::Config>::PalletInfo::name::<GrandpaPallet>`.
///
/// The old storage prefix, `GrandpaFinality` is hardcoded in the migration code.
pub fn migrate<T: crate::Config, N: AsRef<str>>(new_pallet_name: N) -> Weight {
if new_pallet_name.as_ref().as_bytes() == OLD_PREFIX {
log::info!(
target: LOG_TARGET,
"New pallet name is equal to the old prefix. No migration needs to be done.",
);
return Weight::zero();
}
let storage_version = StorageVersion::get::<crate::Pallet<T>>();
log::info!(
target: LOG_TARGET,
"Running migration to v3.1 for grandpa with storage version {:?}",
storage_version,
);
if storage_version <= 3 {
log::info!("new prefix: {}", new_pallet_name.as_ref());
pezframe_support::storage::migration::move_pallet(
OLD_PREFIX,
new_pallet_name.as_ref().as_bytes(),
);
StorageVersion::new(4).put::<crate::Pallet<T>>();
<T as pezframe_system::Config>::BlockWeights::get().max_block
} else {
Weight::zero()
}
}
/// Some checks prior to migration. This can be linked to
/// `pezframe_support::traits::OnRuntimeUpgrade::pre_upgrade` for further testing.
///
/// Panics if anything goes wrong.
pub fn pre_migration<T: crate::Config, N: AsRef<str>>(new: N) {
let new = new.as_ref();
log::info!("pre-migration grandpa test with new = {}", new);
// the next key must exist, and start with the hash of `OLD_PREFIX`.
let next_key = pezsp_io::storage::next_key(&twox_128(OLD_PREFIX)).unwrap();
assert!(next_key.starts_with(&twox_128(OLD_PREFIX)));
// The pallet version is already stored using the pallet name
let storage_key = StorageVersion::storage_key::<crate::Pallet<T>>();
// ensure nothing is stored in the new prefix.
assert!(
pezsp_io::storage::next_key(&twox_128(new.as_bytes())).map_or(
// either nothing is there
true,
// or we ensure that it has no common prefix with twox_128(new),
// or isn't the pallet version that is already stored using the pallet name
|next_key| {
!next_key.starts_with(&twox_128(new.as_bytes())) || next_key == storage_key
},
),
"unexpected next_key({}) = {:?}",
new,
pezsp_core::hexdisplay::HexDisplay::from(
&pezsp_io::storage::next_key(&twox_128(new.as_bytes())).unwrap()
),
);
// ensure storage version is 3.
assert_eq!(StorageVersion::get::<crate::Pallet<T>>(), 3);
}
/// Some checks for after migration. This can be linked to
/// `pezframe_support::traits::OnRuntimeUpgrade::post_upgrade` for further testing.
///
/// Panics if anything goes wrong.
pub fn post_migration() {
log::info!("post-migration grandpa");
// Assert that nothing remains at the old prefix
assert!(pezsp_io::storage::next_key(&twox_128(OLD_PREFIX))
.map_or(true, |next_key| !next_key.starts_with(&twox_128(OLD_PREFIX))));
}
@@ -0,0 +1,102 @@
// 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.
use crate::{BoundedAuthorityList, Pallet};
use alloc::vec::Vec;
use codec::Decode;
use core::marker::PhantomData;
use pezframe_support::{
migrations::VersionedMigration,
storage,
traits::{Get, UncheckedOnRuntimeUpgrade},
weights::Weight,
};
use pezsp_consensus_grandpa::AuthorityList;
const GRANDPA_AUTHORITIES_KEY: &[u8] = b":grandpa_authorities";
fn load_authority_list() -> AuthorityList {
storage::unhashed::get_raw(GRANDPA_AUTHORITIES_KEY).map_or_else(
|| Vec::new(),
|l| <(u8, AuthorityList)>::decode(&mut &l[..]).unwrap_or_default().1,
)
}
/// Actual implementation of [`MigrateV4ToV5`].
pub struct UncheckedMigrateImpl<T>(PhantomData<T>);
impl<T: crate::Config> UncheckedOnRuntimeUpgrade for UncheckedMigrateImpl<T> {
#[cfg(feature = "try-runtime")]
fn pre_upgrade() -> Result<Vec<u8>, pezsp_runtime::TryRuntimeError> {
use codec::Encode;
let authority_list_len = load_authority_list().len() as u32;
if authority_list_len > T::MaxAuthorities::get() {
return Err(
"Grandpa: `Config::MaxAuthorities` is smaller than the actual number of authorities.".into()
);
}
if authority_list_len == 0 {
return Err("Grandpa: Authority list is empty!".into());
}
Ok(authority_list_len.encode())
}
#[cfg(feature = "try-runtime")]
fn post_upgrade(state: Vec<u8>) -> Result<(), pezsp_runtime::TryRuntimeError> {
let len = u32::decode(&mut &state[..]).unwrap();
pezframe_support::ensure!(
len == crate::Pallet::<T>::grandpa_authorities().len() as u32,
"Grandpa: pre-migrated and post-migrated list should have the same length"
);
pezframe_support::ensure!(
load_authority_list().is_empty(),
"Old authority list shouldn't exist anymore"
);
Ok(())
}
fn on_runtime_upgrade() -> Weight {
crate::Authorities::<T>::put(
&BoundedAuthorityList::<T::MaxAuthorities>::force_from(
load_authority_list(),
Some("Grandpa: `Config::MaxAuthorities` is smaller than the actual number of authorities.")
)
);
storage::unhashed::kill(GRANDPA_AUTHORITIES_KEY);
T::DbWeight::get().reads_writes(1, 2)
}
}
/// Migrate the storage from V4 to V5.
///
/// Switches from `GRANDPA_AUTHORITIES_KEY` to a normal FRAME storage item.
pub type MigrateV4ToV5<T> = VersionedMigration<
4,
5,
UncheckedMigrateImpl<T>,
Pallet<T>,
<T as pezframe_system::Config>::DbWeight,
>;
+343
View File
@@ -0,0 +1,343 @@
// 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.
//! Test utilities
#![cfg(test)]
use crate::{self as pezpallet_grandpa, AuthorityId, AuthorityList, Config, ConsensusLog};
use codec::Encode;
use finality_grandpa;
use pezframe_election_provider_support::{
bounds::{ElectionBounds, ElectionBoundsBuilder},
onchain, SequentialPhragmen,
};
use pezframe_support::{
derive_impl, parameter_types,
traits::{ConstU128, ConstU32, ConstU64, OnFinalize, OnInitialize},
};
use pezpallet_session::historical as pezpallet_session_historical;
use pezsp_consensus_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID};
use pezsp_core::{ConstBool, H256};
use pezsp_keyring::Ed25519Keyring;
use pezsp_runtime::{
curve::PiecewiseLinear,
impl_opaque_keys,
testing::{TestXt, UintAuthorityId},
traits::OpaqueKeys,
BuildStorage, DigestItem, Perbill,
};
use pezsp_staking::{EraIndex, SessionIndex};
type Block = pezframe_system::mocking::MockBlock<Test>;
pezframe_support::construct_runtime!(
pub enum Test
{
System: pezframe_system,
Authorship: pezpallet_authorship,
Timestamp: pezpallet_timestamp,
Balances: pezpallet_balances,
Staking: pezpallet_staking,
Session: pezpallet_session,
Grandpa: pezpallet_grandpa,
Offences: pezpallet_offences,
Historical: pezpallet_session_historical,
}
);
impl_opaque_keys! {
pub struct TestSessionKeys {
pub grandpa_authority: super::Pallet<Test>,
}
}
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
impl pezframe_system::Config for Test {
type Block = Block;
type AccountData = pezpallet_balances::AccountData<Balance>;
}
impl<C> pezframe_system::offchain::CreateTransactionBase<C> for Test
where
RuntimeCall: From<C>,
{
type RuntimeCall = RuntimeCall;
type Extrinsic = TestXt<RuntimeCall, ()>;
}
impl<C> pezframe_system::offchain::CreateBare<C> for Test
where
RuntimeCall: From<C>,
{
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
TestXt::new_bare(call)
}
}
parameter_types! {
pub const Period: u64 = 1;
pub const Offset: u64 = 0;
}
/// Custom `SessionHandler` since we use `TestSessionKeys` as `Keys`.
impl pezpallet_session::Config for Test {
type RuntimeEvent = RuntimeEvent;
type ValidatorId = u64;
type ValidatorIdOf = pezsp_runtime::traits::ConvertInto;
type ShouldEndSession = pezpallet_session::PeriodicSessions<ConstU64<1>, ConstU64<0>>;
type NextSessionRotation = pezpallet_session::PeriodicSessions<ConstU64<1>, ConstU64<0>>;
type SessionManager = pezpallet_session::historical::NoteHistoricalRoot<Self, Staking>;
type SessionHandler = <TestSessionKeys as OpaqueKeys>::KeyTypeIdProviders;
type Keys = TestSessionKeys;
type DisablingStrategy = ();
type WeightInfo = ();
type Currency = Balances;
type KeyDeposit = ();
}
impl pezpallet_session::historical::Config for Test {
type RuntimeEvent = RuntimeEvent;
type FullIdentification = ();
type FullIdentificationOf = pezpallet_staking::UnitIdentificationOf<Self>;
}
impl pezpallet_authorship::Config for Test {
type FindAuthor = ();
type EventHandler = ();
}
type Balance = u128;
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
impl pezpallet_balances::Config for Test {
type Balance = Balance;
type ExistentialDeposit = ConstU128<1>;
type AccountStore = System;
}
impl pezpallet_timestamp::Config for Test {
type Moment = u64;
type OnTimestampSet = ();
type MinimumPeriod = ConstU64<3>;
type WeightInfo = ();
}
pezpallet_staking_reward_curve::build! {
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
min_inflation: 0_025_000u64,
max_inflation: 0_100_000,
ideal_stake: 0_500_000,
falloff: 0_050_000,
max_piece_count: 40,
test_precision: 0_005_000,
);
}
parameter_types! {
pub const SessionsPerEra: SessionIndex = 3;
pub const BondingDuration: EraIndex = 3;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build();
}
pub struct OnChainSeqPhragmen;
impl onchain::Config for OnChainSeqPhragmen {
type System = Test;
type Solver = SequentialPhragmen<u64, Perbill>;
type DataProvider = Staking;
type WeightInfo = ();
type MaxWinnersPerPage = ConstU32<100>;
type MaxBackersPerWinner = ConstU32<100>;
type Sort = ConstBool<true>;
type Bounds = ElectionsBoundsOnChain;
}
#[derive_impl(pezpallet_staking::config_preludes::TestDefaultConfig)]
impl pezpallet_staking::Config for Test {
type OldCurrency = Balances;
type Currency = Balances;
type CurrencyBalance = <Self as pezpallet_balances::Config>::Balance;
type SessionsPerEra = SessionsPerEra;
type BondingDuration = BondingDuration;
type AdminOrigin = pezframe_system::EnsureRoot<Self::AccountId>;
type SessionInterface = Self;
type UnixTime = pezpallet_timestamp::Pallet<Test>;
type EraPayout = pezpallet_staking::ConvertCurve<RewardCurve>;
type NextNewSession = Session;
type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
type GenesisElectionProvider = Self::ElectionProvider;
type VoterList = pezpallet_staking::UseNominatorsAndValidatorsMap<Self>;
type TargetList = pezpallet_staking::UseValidatorsMap<Self>;
type NominationsQuota = pezpallet_staking::FixedNominationsQuota<16>;
}
impl pezpallet_offences::Config for Test {
type RuntimeEvent = RuntimeEvent;
type IdentificationTuple = pezpallet_session::historical::IdentificationTuple<Self>;
type OnOffenceHandler = Staking;
}
parameter_types! {
pub const ReportLongevity: u64 =
BondingDuration::get() as u64 * SessionsPerEra::get() as u64 * Period::get();
pub const MaxSetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get();
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type WeightInfo = ();
type MaxAuthorities = ConstU32<100>;
type MaxNominators = ConstU32<1000>;
type MaxSetIdSessionEntries = MaxSetIdSessionEntries;
type KeyOwnerProof = pezsp_session::MembershipProof;
type EquivocationReportSystem =
super::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
}
pub fn grandpa_log(log: ConsensusLog<u64>) -> DigestItem {
DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode())
}
pub fn to_authorities(vec: Vec<(u64, u64)>) -> AuthorityList {
vec.into_iter()
.map(|(id, weight)| (UintAuthorityId(id).to_public_key::<AuthorityId>(), weight))
.collect()
}
pub fn extract_keyring(id: &AuthorityId) -> Ed25519Keyring {
let mut raw_public = [0; 32];
raw_public.copy_from_slice(id.as_ref());
Ed25519Keyring::from_raw_public(raw_public).unwrap()
}
pub fn new_test_ext(vec: Vec<(u64, u64)>) -> pezsp_io::TestExternalities {
new_test_ext_raw_authorities(to_authorities(vec))
}
pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> pezsp_io::TestExternalities {
pezsp_tracing::try_init_simple();
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect();
pezpallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
// stashes are the index.
let session_keys: Vec<_> = authorities
.iter()
.enumerate()
.map(|(i, (k, _))| {
(
i as u64,
i as u64,
TestSessionKeys { grandpa_authority: AuthorityId::from(k.clone()) },
)
})
.collect();
// NOTE: this will initialize the grandpa authorities
// through OneSessionHandler::on_genesis_session
pezpallet_session::GenesisConfig::<Test> { keys: session_keys, ..Default::default() }
.assimilate_storage(&mut t)
.unwrap();
// controllers are the same as stash
let stakers: Vec<_> = (0..authorities.len())
.map(|i| (i as u64, i as u64, 10_000, pezpallet_staking::StakerStatus::<u64>::Validator))
.collect();
let staking_config = pezpallet_staking::GenesisConfig::<Test> {
stakers,
validator_count: 8,
force_era: pezpallet_staking::Forcing::ForceNew,
minimum_validator_count: 0,
invulnerables: vec![],
..Default::default()
};
staking_config.assimilate_storage(&mut t).unwrap();
t.into()
}
pub fn start_session(session_index: SessionIndex) {
for i in Session::current_index()..session_index {
System::on_finalize(System::block_number());
Session::on_finalize(System::block_number());
Staking::on_finalize(System::block_number());
Grandpa::on_finalize(System::block_number());
let parent_hash = if System::block_number() > 1 {
let hdr = System::finalize();
hdr.hash()
} else {
System::parent_hash()
};
System::reset_events();
System::initialize(&(i as u64 + 1), &parent_hash, &Default::default());
System::set_block_number((i + 1).into());
Timestamp::set_timestamp(System::block_number() * 6000);
System::on_initialize(System::block_number());
Session::on_initialize(System::block_number());
Staking::on_initialize(System::block_number());
Grandpa::on_initialize(System::block_number());
}
assert_eq!(Session::current_index(), session_index);
}
pub fn start_era(era_index: EraIndex) {
start_session((era_index * 3).into());
assert_eq!(pezpallet_staking::CurrentEra::<Test>::get(), Some(era_index));
}
pub fn initialize_block(number: u64, parent_hash: H256) {
System::reset_events();
System::initialize(&number, &parent_hash, &Default::default());
}
pub fn generate_equivocation_proof(
set_id: SetId,
vote1: (RoundNumber, H256, u64, &Ed25519Keyring),
vote2: (RoundNumber, H256, u64, &Ed25519Keyring),
) -> pezsp_consensus_grandpa::EquivocationProof<H256, u64> {
let signed_prevote = |round, hash, number, keyring: &Ed25519Keyring| {
let prevote = finality_grandpa::Prevote { target_hash: hash, target_number: number };
let prevote_msg = finality_grandpa::Message::Prevote(prevote.clone());
let payload = pezsp_consensus_grandpa::localized_payload(round, set_id, &prevote_msg);
let signed = keyring.sign(&payload).into();
(prevote, signed)
};
let (prevote1, signed1) = signed_prevote(vote1.0, vote1.1, vote1.2, vote1.3);
let (prevote2, signed2) = signed_prevote(vote2.0, vote2.1, vote2.2, vote2.3);
pezsp_consensus_grandpa::EquivocationProof::new(
set_id,
pezsp_consensus_grandpa::Equivocation::Prevote(finality_grandpa::Equivocation {
round_number: vote1.0,
identity: vote1.3.public().into(),
first: (prevote1, signed1),
second: (prevote2, signed2),
}),
)
}
+919
View File
@@ -0,0 +1,919 @@
// 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.
//! Tests for the module.
#![cfg(test)]
use super::{Call, Event, *};
use crate::mock::*;
use fg_primitives::ScheduledChange;
use pezframe_support::{
assert_err, assert_noop, assert_ok,
dispatch::{GetDispatchInfo, Pays},
traits::{Currency, KeyOwnerProofSystem, OnFinalize, OneSessionHandler},
};
use pezframe_system::{EventRecord, Phase};
use pezsp_core::H256;
use pezsp_keyring::Ed25519Keyring;
use pezsp_runtime::testing::Digest;
#[test]
fn authorities_change_logged() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
initialize_block(1, Default::default());
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 0, None).unwrap();
System::note_finished_extrinsics();
Grandpa::on_finalize(1);
let header = System::finalize();
assert_eq!(
header.digest,
Digest {
logs: vec![grandpa_log(ConsensusLog::ScheduledChange(ScheduledChange {
delay: 0,
next_authorities: to_authorities(vec![(4, 1), (5, 1), (6, 1)])
})),],
}
);
assert_eq!(
System::events(),
vec![EventRecord {
phase: Phase::Finalization,
event: Event::NewAuthorities {
authority_set: to_authorities(vec![(4, 1), (5, 1), (6, 1)])
}
.into(),
topics: vec![],
},]
);
});
}
#[test]
fn authorities_change_logged_after_delay() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
initialize_block(1, Default::default());
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap();
Grandpa::on_finalize(1);
let header = System::finalize();
assert_eq!(
header.digest,
Digest {
logs: vec![grandpa_log(ConsensusLog::ScheduledChange(ScheduledChange {
delay: 1,
next_authorities: to_authorities(vec![(4, 1), (5, 1), (6, 1)])
})),],
}
);
// no change at this height.
assert_eq!(System::events(), vec![]);
initialize_block(2, header.hash());
System::note_finished_extrinsics();
Grandpa::on_finalize(2);
let _header = System::finalize();
assert_eq!(
System::events(),
vec![EventRecord {
phase: Phase::Finalization,
event: Event::NewAuthorities {
authority_set: to_authorities(vec![(4, 1), (5, 1), (6, 1)])
}
.into(),
topics: vec![],
},]
);
});
}
#[test]
fn cannot_schedule_change_when_one_pending() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
initialize_block(1, Default::default());
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 1, None).unwrap();
assert!(PendingChange::<Test>::exists());
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None),
Error::<Test>::ChangePending
);
Grandpa::on_finalize(1);
let header = System::finalize();
initialize_block(2, header.hash());
assert!(PendingChange::<Test>::exists());
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None),
Error::<Test>::ChangePending
);
Grandpa::on_finalize(2);
let header = System::finalize();
initialize_block(3, header.hash());
assert!(!PendingChange::<Test>::exists());
assert_ok!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None));
Grandpa::on_finalize(3);
let _header = System::finalize();
});
}
#[test]
fn dispatch_forced_change() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
initialize_block(1, Default::default());
Grandpa::schedule_change(to_authorities(vec![(4, 1), (5, 1), (6, 1)]), 5, Some(0)).unwrap();
assert!(PendingChange::<Test>::exists());
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)),
Error::<Test>::ChangePending
);
Grandpa::on_finalize(1);
let mut header = System::finalize();
for i in 2..7 {
initialize_block(i, header.hash());
assert!(PendingChange::<Test>::get().unwrap().forced.is_some());
assert_eq!(NextForced::<Test>::get(), Some(11));
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None),
Error::<Test>::ChangePending
);
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, Some(0)),
Error::<Test>::ChangePending
);
Grandpa::on_finalize(i);
header = System::finalize();
}
// change has been applied at the end of block 6.
// add a normal change.
{
initialize_block(7, header.hash());
assert!(!PendingChange::<Test>::exists());
assert_eq!(
Grandpa::grandpa_authorities(),
to_authorities(vec![(4, 1), (5, 1), (6, 1)])
);
assert_ok!(Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None));
Grandpa::on_finalize(7);
header = System::finalize();
}
// run the normal change.
{
initialize_block(8, header.hash());
assert!(PendingChange::<Test>::exists());
assert_eq!(
Grandpa::grandpa_authorities(),
to_authorities(vec![(4, 1), (5, 1), (6, 1)])
);
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1)]), 1, None),
Error::<Test>::ChangePending
);
Grandpa::on_finalize(8);
header = System::finalize();
}
// normal change applied. but we can't apply a new forced change for some
// time.
for i in 9..11 {
initialize_block(i, header.hash());
assert!(!PendingChange::<Test>::exists());
assert_eq!(Grandpa::grandpa_authorities(), to_authorities(vec![(5, 1)]));
assert_eq!(NextForced::<Test>::get(), Some(11));
assert_noop!(
Grandpa::schedule_change(to_authorities(vec![(5, 1), (6, 1)]), 5, Some(0)),
Error::<Test>::TooSoon
);
Grandpa::on_finalize(i);
header = System::finalize();
}
{
initialize_block(11, header.hash());
assert!(!PendingChange::<Test>::exists());
assert_ok!(Grandpa::schedule_change(
to_authorities(vec![(5, 1), (6, 1), (7, 1)]),
5,
Some(0)
));
assert_eq!(NextForced::<Test>::get(), Some(21));
Grandpa::on_finalize(11);
header = System::finalize();
}
let _ = header;
});
}
#[test]
fn schedule_pause_only_when_live() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
// we schedule a pause at block 1 with delay of 1
initialize_block(1, Default::default());
Grandpa::schedule_pause(1).unwrap();
// we've switched to the pending pause state
assert_eq!(
State::<Test>::get(),
StoredState::PendingPause { scheduled_at: 1u64, delay: 1 }
);
Grandpa::on_finalize(1);
let _ = System::finalize();
initialize_block(2, Default::default());
// signaling a pause now should fail
assert_noop!(Grandpa::schedule_pause(1), Error::<Test>::PauseFailed);
Grandpa::on_finalize(2);
let _ = System::finalize();
// after finalizing block 2 the set should have switched to paused state
assert_eq!(State::<Test>::get(), StoredState::Paused);
});
}
#[test]
fn schedule_resume_only_when_paused() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
initialize_block(1, Default::default());
// the set is currently live, resuming it is an error
assert_noop!(Grandpa::schedule_resume(1), Error::<Test>::ResumeFailed);
assert_eq!(State::<Test>::get(), StoredState::Live);
// we schedule a pause to be applied instantly
Grandpa::schedule_pause(0).unwrap();
Grandpa::on_finalize(1);
let _ = System::finalize();
assert_eq!(State::<Test>::get(), StoredState::Paused);
// we schedule the set to go back live in 2 blocks
initialize_block(2, Default::default());
Grandpa::schedule_resume(2).unwrap();
Grandpa::on_finalize(2);
let _ = System::finalize();
initialize_block(3, Default::default());
Grandpa::on_finalize(3);
let _ = System::finalize();
initialize_block(4, Default::default());
Grandpa::on_finalize(4);
let _ = System::finalize();
// it should be live at block 4
assert_eq!(State::<Test>::get(), StoredState::Live);
});
}
#[test]
fn time_slot_have_sane_ord() {
// Ensure that `Ord` implementation is sane.
const FIXTURE: &[TimeSlot] = &[
TimeSlot { set_id: 0, round: 0 },
TimeSlot { set_id: 0, round: 1 },
TimeSlot { set_id: 1, round: 0 },
TimeSlot { set_id: 1, round: 1 },
TimeSlot { set_id: 1, round: 2 },
];
assert!(FIXTURE.windows(2).all(|f| f[0] < f[1]));
}
/// Returns a list with 3 authorities with known keys:
/// Alice, Bob and Charlie.
pub fn test_authorities() -> AuthorityList {
let authorities = vec![Ed25519Keyring::Alice, Ed25519Keyring::Bob, Ed25519Keyring::Charlie];
authorities.into_iter().map(|id| (id.public().into(), 1u64)).collect()
}
#[test]
fn report_equivocation_current_set_works() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
assert_eq!(pezpallet_staking::CurrentEra::<Test>::get(), Some(0));
assert_eq!(Session::current_index(), 0);
start_era(1);
let authorities = Grandpa::grandpa_authorities();
let validators = Session::validators();
// make sure that all validators have the same balance
for validator in &validators {
assert_eq!(Balances::total_balance(validator), 10_000_000);
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
assert_eq!(
Staking::eras_stakers(1, &validator),
pezpallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] },
);
}
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
let set_id = CurrentSetId::<Test>::get();
// generate an equivocation proof, with two votes in the same round for
// different block hashes signed by the same key
let equivocation_proof = generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
// create the key ownership proof
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
// report the equivocation and the tx should be dispatched successfully
assert_ok!(Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof,
),);
start_era(2);
// check that the balance of 0-th validator is slashed 100%.
let equivocation_validator_id = validators[equivocation_authority_index];
assert_eq!(Balances::total_balance(&equivocation_validator_id), 10_000_000 - 10_000);
assert_eq!(Staking::slashable_balance_of(&equivocation_validator_id), 0);
assert_eq!(
Staking::eras_stakers(2, &equivocation_validator_id),
pezpallet_staking::Exposure { total: 0, own: 0, others: vec![] },
);
// check that the balances of all other validators are left intact.
for validator in &validators {
if *validator == equivocation_validator_id {
continue;
}
assert_eq!(Balances::total_balance(validator), 10_000_000);
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
assert_eq!(
Staking::eras_stakers(2, &validator),
pezpallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] },
);
}
});
}
#[test]
fn report_equivocation_old_set_works() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let authorities = Grandpa::grandpa_authorities();
let validators = Session::validators();
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
// create the key ownership proof in the "old" set
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
start_era(2);
// make sure that all authorities have the same balance
for validator in &validators {
assert_eq!(Balances::total_balance(validator), 10_000_000);
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
assert_eq!(
Staking::eras_stakers(2, &validator),
pezpallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] },
);
}
let equivocation_keyring = extract_keyring(equivocation_key);
let set_id = CurrentSetId::<Test>::get();
// generate an equivocation proof for the old set,
let equivocation_proof = generate_equivocation_proof(
set_id - 1,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
// report the equivocation using the key ownership proof generated on
// the old set, the tx should be dispatched successfully
assert_ok!(Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof,
),);
start_era(3);
// check that the balance of 0-th validator is slashed 100%.
let equivocation_validator_id = validators[equivocation_authority_index];
assert_eq!(Balances::total_balance(&equivocation_validator_id), 10_000_000 - 10_000);
assert_eq!(Staking::slashable_balance_of(&equivocation_validator_id), 0);
assert_eq!(
Staking::eras_stakers(3, &equivocation_validator_id),
pezpallet_staking::Exposure { total: 0, own: 0, others: vec![] },
);
// check that the balances of all other validators are left intact.
for validator in &validators {
if *validator == equivocation_validator_id {
continue;
}
assert_eq!(Balances::total_balance(validator), 10_000_000);
assert_eq!(Staking::slashable_balance_of(validator), 10_000);
assert_eq!(
Staking::eras_stakers(3, &validator),
pezpallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] },
);
}
});
}
#[test]
fn report_equivocation_invalid_set_id() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let authorities = Grandpa::grandpa_authorities();
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
let set_id = CurrentSetId::<Test>::get();
// generate an equivocation for a future set
let equivocation_proof = generate_equivocation_proof(
set_id + 1,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
// the call for reporting the equivocation should error
assert_err!(
Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof,
),
Error::<Test>::InvalidEquivocationProof,
);
});
}
#[test]
fn report_equivocation_invalid_session() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let authorities = Grandpa::grandpa_authorities();
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
// generate a key ownership proof at set id = 1
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
start_era(2);
let set_id = CurrentSetId::<Test>::get();
// generate an equivocation proof at set id = 2
let equivocation_proof = generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
// report an equivocation for the current set using an key ownership
// proof from the previous set, the session should be invalid.
assert_err!(
Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof,
),
Error::<Test>::InvalidEquivocationProof,
);
});
}
#[test]
fn report_equivocation_invalid_key_owner_proof() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let authorities = Grandpa::grandpa_authorities();
let invalid_owner_authority_index = 1;
let invalid_owner_key = &authorities[invalid_owner_authority_index].0;
// generate a key ownership proof for the authority at index 1
let invalid_key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &invalid_owner_key)).unwrap();
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
let set_id = CurrentSetId::<Test>::get();
// generate an equivocation proof for the authority at index 0
let equivocation_proof = generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
// we need to start a new era otherwise the key ownership proof won't be
// checked since the authorities are part of the current session
start_era(2);
// report an equivocation for the current set using a key ownership
// proof for a different key than the one in the equivocation proof.
assert_err!(
Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
invalid_key_owner_proof,
),
Error::<Test>::InvalidKeyOwnershipProof,
);
});
}
#[test]
fn report_equivocation_invalid_equivocation_proof() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let authorities = Grandpa::grandpa_authorities();
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
// generate a key ownership proof at set id = 1
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
let set_id = CurrentSetId::<Test>::get();
let assert_invalid_equivocation_proof = |equivocation_proof| {
assert_err!(
Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof.clone(),
),
Error::<Test>::InvalidEquivocationProof,
);
};
start_era(2);
// both votes target the same block number and hash,
// there is no equivocation.
assert_invalid_equivocation_proof(generate_equivocation_proof(
set_id,
(1, H256::zero(), 10, &equivocation_keyring),
(1, H256::zero(), 10, &equivocation_keyring),
));
// votes targeting different rounds, there is no equivocation.
assert_invalid_equivocation_proof(generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(2, H256::random(), 10, &equivocation_keyring),
));
// votes signed with different authority keys
assert_invalid_equivocation_proof(generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &Ed25519Keyring::Charlie),
));
// votes signed with a key that isn't part of the authority set
assert_invalid_equivocation_proof(generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &Ed25519Keyring::Dave),
));
});
}
#[test]
fn report_equivocation_validate_unsigned_prevents_duplicates() {
use pezsp_runtime::transaction_validity::{
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
ValidTransaction,
};
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let authorities = Grandpa::grandpa_authorities();
// generate and report an equivocation for the validator at index 0
let equivocation_authority_index = 0;
let equivocation_key = &authorities[equivocation_authority_index].0;
let equivocation_keyring = extract_keyring(equivocation_key);
let set_id = CurrentSetId::<Test>::get();
let equivocation_proof = generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
let call = Call::report_equivocation_unsigned {
equivocation_proof: Box::new(equivocation_proof.clone()),
key_owner_proof: key_owner_proof.clone(),
};
// only local/inblock reports are allowed
assert_eq!(
<Grandpa as pezsp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::External,
&call,
),
InvalidTransaction::Call.into(),
);
// the transaction is valid when passed as local
let tx_tag = (equivocation_key, set_id, 1u64);
assert_eq!(
<Grandpa as pezsp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::Local,
&call,
),
TransactionValidity::Ok(ValidTransaction {
priority: TransactionPriority::max_value(),
requires: vec![],
provides: vec![("GrandpaEquivocation", tx_tag).encode()],
longevity: ReportLongevity::get(),
propagate: false,
})
);
// the pre dispatch checks should also pass
assert_ok!(<Grandpa as pezsp_runtime::traits::ValidateUnsigned>::pre_dispatch(&call));
// we submit the report
Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof,
)
.unwrap();
// the report should now be considered stale and the transaction is invalid
// the check for staleness should be done on both `validate_unsigned` and on `pre_dispatch`
assert_err!(
<Grandpa as pezsp_runtime::traits::ValidateUnsigned>::validate_unsigned(
TransactionSource::Local,
&call,
),
InvalidTransaction::Stale,
);
assert_err!(
<Grandpa as pezsp_runtime::traits::ValidateUnsigned>::pre_dispatch(&call),
InvalidTransaction::Stale,
);
});
}
#[test]
fn on_new_session_doesnt_start_new_set_if_schedule_change_failed() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
assert_eq!(CurrentSetId::<Test>::get(), 0);
// starting a new era should lead to a change in the session
// validators and trigger a new set
start_era(1);
assert_eq!(CurrentSetId::<Test>::get(), 1);
// we schedule a change delayed by 2 blocks, this should make it so that
// when we try to rotate the session at the beginning of the era we will
// fail to schedule a change (there's already one pending), so we should
// not increment the set id.
Grandpa::schedule_change(to_authorities(vec![(1, 1)]), 2, None).unwrap();
start_era(2);
assert_eq!(CurrentSetId::<Test>::get(), 1);
// everything should go back to normal after.
start_era(3);
assert_eq!(CurrentSetId::<Test>::get(), 2);
// session rotation might also fail to schedule a change if it's for a
// forced change (i.e. grandpa is stalled) and it is too soon.
NextForced::<Test>::put(1000);
Stalled::<Test>::put((30, 1));
// NOTE: we cannot go through normal era rotation since having `Stalled`
// defined will also trigger a new set (regardless of whether the
// session validators changed)
Grandpa::on_new_session(true, std::iter::empty(), std::iter::empty());
assert_eq!(CurrentSetId::<Test>::get(), 2);
});
}
#[test]
fn cleans_up_old_set_id_session_mappings() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
let max_set_id_session_entries = MaxSetIdSessionEntries::get();
start_era(max_set_id_session_entries);
// we should have a session id mapping for all the set ids from
// `max_set_id_session_entries` eras we have observed
for i in 1..=max_set_id_session_entries {
assert!(SetIdSession::<Test>::get(i as u64).is_some());
}
start_era(max_set_id_session_entries * 2);
// we should keep tracking the new mappings for new eras
for i in max_set_id_session_entries + 1..=max_set_id_session_entries * 2 {
assert!(SetIdSession::<Test>::get(i as u64).is_some());
}
// but the old ones should have been pruned by now
for i in 1..=max_set_id_session_entries {
assert!(SetIdSession::<Test>::get(i as u64).is_none());
}
});
}
#[test]
fn always_schedules_a_change_on_new_session_when_stalled() {
new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| {
start_era(1);
assert!(PendingChange::<Test>::get().is_none());
assert_eq!(CurrentSetId::<Test>::get(), 1);
// if the session handler reports no change then we should not schedule
// any pending change
Grandpa::on_new_session(false, std::iter::empty(), std::iter::empty());
assert!(PendingChange::<Test>::get().is_none());
assert_eq!(CurrentSetId::<Test>::get(), 1);
// if grandpa is stalled then we should **always** schedule a forced
// change on a new session
Stalled::<Test>::put((10, 1));
Grandpa::on_new_session(false, std::iter::empty(), std::iter::empty());
assert!(PendingChange::<Test>::get().is_some());
assert!(PendingChange::<Test>::get().unwrap().forced.is_some());
assert_eq!(CurrentSetId::<Test>::get(), 2);
});
}
#[test]
fn report_equivocation_has_valid_weight() {
// the weight depends on the size of the validator set,
// but there's a lower bound of 100 validators.
assert!((1..=100)
.map(|validators| <Test as Config>::WeightInfo::report_equivocation(validators, 1000))
.collect::<Vec<_>>()
.windows(2)
.all(|w| w[0] == w[1]));
// after 100 validators the weight should keep increasing
// with every extra validator.
assert!((100..=1000)
.map(|validators| <Test as Config>::WeightInfo::report_equivocation(validators, 1000))
.collect::<Vec<_>>()
.windows(2)
.all(|w| w[0].ref_time() < w[1].ref_time()));
}
#[test]
fn valid_equivocation_reports_dont_pay_fees() {
let authorities = test_authorities();
new_test_ext_raw_authorities(authorities).execute_with(|| {
start_era(1);
let equivocation_key = &Grandpa::grandpa_authorities()[0].0;
let equivocation_keyring = extract_keyring(equivocation_key);
let set_id = CurrentSetId::<Test>::get();
// generate an equivocation proof.
let equivocation_proof = generate_equivocation_proof(
set_id,
(1, H256::random(), 10, &equivocation_keyring),
(1, H256::random(), 10, &equivocation_keyring),
);
// create the key ownership proof.
let key_owner_proof =
Historical::prove((pezsp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap();
// check the dispatch info for the call.
let info = Call::<Test>::report_equivocation_unsigned {
equivocation_proof: Box::new(equivocation_proof.clone()),
key_owner_proof: key_owner_proof.clone(),
}
.get_dispatch_info();
// it should have non-zero weight and the fee has to be paid.
assert!(info.call_weight.any_gt(Weight::zero()));
assert_eq!(info.pays_fee, Pays::Yes);
// report the equivocation.
let post_info = Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof.clone()),
key_owner_proof.clone(),
)
.unwrap();
// the original weight should be kept, but given that the report
// is valid the fee is waived.
assert!(post_info.actual_weight.is_none());
assert_eq!(post_info.pays_fee, Pays::No);
// report the equivocation again which is invalid now since it is
// duplicate.
let post_info = Grandpa::report_equivocation_unsigned(
RuntimeOrigin::none(),
Box::new(equivocation_proof),
key_owner_proof,
)
.err()
.unwrap()
.post_info;
// the fee is not waived and the original weight is kept.
assert!(post_info.actual_weight.is_none());
assert_eq!(post_info.pays_fee, Pays::Yes);
})
}
+126
View File
@@ -0,0 +1,126 @@
// 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.
// 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.
//! Autogenerated weights for `pezpallet_grandpa`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
// Executed Command:
// frame-omni-bencher
// v1
// benchmark
// pallet
// --extrinsic=*
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
// --pallet=pezpallet_grandpa
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/grandpa/src/weights.rs
// --wasm-execution=compiled
// --steps=50
// --repeat=20
// --heap-pages=4096
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
// --no-storage-info
// --no-min-squares
// --no-median-slopes
// --genesis-builder-policy=none
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
#![allow(dead_code)]
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pezpallet_grandpa`.
pub trait WeightInfo {
fn check_equivocation_proof(x: u32, ) -> Weight;
fn note_stalled() -> Weight;
}
/// Weights for `pezpallet_grandpa` using the Bizinikiwi node and recommended hardware.
pub struct BizinikiwiWeight<T>(PhantomData<T>);
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
/// The range of component `x` is `[0, 1]`.
fn check_equivocation_proof(x: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 77_943_000 picoseconds.
Weight::from_parts(78_252_373, 0)
// Standard Error: 17_672
.saturating_add(Weight::from_parts(29_726, 0).saturating_mul(x.into()))
}
/// Storage: `Grandpa::Stalled` (r:0 w:1)
/// Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`)
fn note_stalled() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 2_006_000 picoseconds.
Weight::from_parts(2_117_000, 0)
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// The range of component `x` is `[0, 1]`.
fn check_equivocation_proof(x: u32, ) -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 77_943_000 picoseconds.
Weight::from_parts(78_252_373, 0)
// Standard Error: 17_672
.saturating_add(Weight::from_parts(29_726, 0).saturating_mul(x.into()))
}
/// Storage: `Grandpa::Stalled` (r:0 w:1)
/// Proof: `Grandpa::Stalled` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`)
fn note_stalled() -> Weight {
// Proof Size summary in bytes:
// Measured: `0`
// Estimated: `0`
// Minimum execution time: 2_006_000 picoseconds.
Weight::from_parts(2_117_000, 0)
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}