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:
@@ -0,0 +1,103 @@
|
||||
[package]
|
||||
name = "pezpallet-grandpa"
|
||||
version = "28.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME pallet for GRANDPA finality gadget"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-authorship = { workspace = true }
|
||||
pezpallet-session = { workspace = true }
|
||||
scale-info = { features = ["derive", "serde"], workspace = true }
|
||||
pezsp-application-crypto = { features = ["serde"], workspace = true }
|
||||
pezsp-consensus-grandpa = { features = ["serde"], workspace = true }
|
||||
pezsp-core = { features = ["serde"], workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-session = { workspace = true }
|
||||
pezsp-staking = { features = ["serde"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
finality-grandpa = { features = [
|
||||
"derive-codec",
|
||||
], workspace = true, default-features = true }
|
||||
pezframe-benchmarking = { workspace = true, default-features = true }
|
||||
pezframe-election-provider-support = { workspace = true, default-features = true }
|
||||
pezpallet-balances = { workspace = true, default-features = true }
|
||||
pezpallet-offences = { workspace = true, default-features = true }
|
||||
pezpallet-staking = { workspace = true, default-features = true }
|
||||
pezpallet-staking-reward-curve = { workspace = true, default-features = true }
|
||||
pezpallet-timestamp = { workspace = true, default-features = true }
|
||||
pezsp-keyring = { workspace = true, default-features = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-election-provider-support/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-authorship/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-offences/std",
|
||||
"pezpallet-session/std",
|
||||
"pezpallet-staking/std",
|
||||
"pezpallet-timestamp/std",
|
||||
"scale-info/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-grandpa/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-session/std",
|
||||
"pezsp-staking/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-election-provider-support/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-authorship/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-offences/runtime-benchmarks",
|
||||
"pezpallet-session/runtime-benchmarks",
|
||||
"pezpallet-staking-reward-curve/runtime-benchmarks",
|
||||
"pezpallet-staking/runtime-benchmarks",
|
||||
"pezpallet-timestamp/runtime-benchmarks",
|
||||
"pezsp-consensus-grandpa/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-keyring/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-session/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-election-provider-support/try-runtime",
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-authorship/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-offences/try-runtime",
|
||||
"pezpallet-session/try-runtime",
|
||||
"pezpallet-staking/try-runtime",
|
||||
"pezpallet-timestamp/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,12 @@
|
||||
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.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
>;
|
||||
@@ -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),
|
||||
}),
|
||||
)
|
||||
}
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user