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,94 @@
|
||||
[package]
|
||||
name = "pezpallet-beefy"
|
||||
version = "28.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
repository.workspace = true
|
||||
description = "BEEFY FRAME pallet"
|
||||
homepage.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], 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 }
|
||||
serde = { optional = true, workspace = true, default-features = true }
|
||||
pezsp-consensus-beefy = { features = ["serde"], workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-session = { workspace = true }
|
||||
pezsp-staking = { features = ["serde"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
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-core = { workspace = true, default-features = true }
|
||||
pezsp-io = { workspace = true, default-features = true }
|
||||
pezsp-staking = { workspace = true, default-features = true }
|
||||
pezsp-state-machine = { workspace = true }
|
||||
pezsp-tracing = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/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",
|
||||
"serde/std",
|
||||
"pezsp-consensus-beefy/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-session/std",
|
||||
"pezsp-staking/std",
|
||||
"pezsp-state-machine/std",
|
||||
]
|
||||
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",
|
||||
]
|
||||
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-beefy/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-session/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
"pezsp-state-machine/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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 BEEFY 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_voting_equivocation(
|
||||
votes_count: u32,
|
||||
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(
|
||||
(50u64 * WEIGHT_REF_TIME_PER_MICROS).saturating_mul(votes_count as u64),
|
||||
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 set_new_genesis() -> Weight {
|
||||
DbWeight::get().writes(1)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
// 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 BEEFY 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 BEEFY protocol).
|
||||
//! And in a runtime context, so that the BEEFY pallet 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 BEEFY pallet is used in the runtime
|
||||
//! definition.
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{self as codec, Decode, Encode};
|
||||
use pezframe_support::traits::{Get, KeyOwnerProofSystem};
|
||||
use pezframe_system::pezpallet_prelude::{BlockNumberFor, HeaderFor};
|
||||
use log::{error, info};
|
||||
use pezsp_consensus_beefy::{
|
||||
check_commitment_signature, AncestryHelper, DoubleVotingProof, ForkVotingProof,
|
||||
FutureBlockVotingProof, ValidatorSetId, KEY_TYPE as BEEFY_KEY_TYPE,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
transaction_validity::{
|
||||
InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity,
|
||||
TransactionValidityError, ValidTransaction,
|
||||
},
|
||||
DispatchError, KeyTypeId, Perbill, RuntimeAppPublic,
|
||||
};
|
||||
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<N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode> {
|
||||
// The order of these matters for `derive(Ord)`.
|
||||
/// BEEFY Set ID.
|
||||
pub set_id: ValidatorSetId,
|
||||
/// Round number.
|
||||
pub round: N,
|
||||
}
|
||||
|
||||
/// BEEFY equivocation offence report.
|
||||
pub struct EquivocationOffence<Offender, N>
|
||||
where
|
||||
N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode,
|
||||
{
|
||||
/// Time slot at which this incident happened.
|
||||
pub time_slot: TimeSlot<N>,
|
||||
/// 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,
|
||||
/// Optional slash fraction
|
||||
maybe_slash_fraction: Option<Perbill>,
|
||||
}
|
||||
|
||||
impl<Offender: Clone, N> Offence<Offender> for EquivocationOffence<Offender, N>
|
||||
where
|
||||
N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode,
|
||||
{
|
||||
const ID: Kind = *b"beefy:equivocati";
|
||||
type TimeSlot = TimeSlot<N>;
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
fn slash_fraction(&self, offenders_count: u32) -> Perbill {
|
||||
if let Some(slash_fraction) = self.maybe_slash_fraction {
|
||||
return slash_fraction;
|
||||
}
|
||||
|
||||
// `Perbill` type domain is [0, 1] by definition
|
||||
// The formula is min((3k / n)^2, 1)
|
||||
// where k = offenders_number and n = validators_number
|
||||
Perbill::from_rational(3 * offenders_count, self.validator_set_count).square()
|
||||
}
|
||||
}
|
||||
|
||||
/// BEEFY 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 authorship pallet.
|
||||
pub struct EquivocationReportSystem<T, R, P, L>(core::marker::PhantomData<(T, R, P, L)>);
|
||||
|
||||
/// Equivocation evidence convenience alias.
|
||||
pub enum EquivocationEvidenceFor<T: Config> {
|
||||
DoubleVotingProof(
|
||||
DoubleVotingProof<
|
||||
BlockNumberFor<T>,
|
||||
T::BeefyId,
|
||||
<T::BeefyId as RuntimeAppPublic>::Signature,
|
||||
>,
|
||||
T::KeyOwnerProof,
|
||||
),
|
||||
ForkVotingProof(
|
||||
ForkVotingProof<
|
||||
HeaderFor<T>,
|
||||
T::BeefyId,
|
||||
<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
|
||||
>,
|
||||
T::KeyOwnerProof,
|
||||
),
|
||||
FutureBlockVotingProof(FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>, T::KeyOwnerProof),
|
||||
}
|
||||
|
||||
impl<T: Config> EquivocationEvidenceFor<T> {
|
||||
/// Returns the authority id of the equivocator.
|
||||
fn offender_id(&self) -> &T::BeefyId {
|
||||
match self {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
|
||||
equivocation_proof.offender_id(),
|
||||
EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
|
||||
&equivocation_proof.vote.id,
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
|
||||
&equivocation_proof.vote.id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the round number at which the equivocation occurred.
|
||||
fn round_number(&self) -> &BlockNumberFor<T> {
|
||||
match self {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
|
||||
equivocation_proof.round_number(),
|
||||
EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
|
||||
&equivocation_proof.vote.commitment.block_number,
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
|
||||
&equivocation_proof.vote.commitment.block_number,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the set id at which the equivocation occurred.
|
||||
fn set_id(&self) -> ValidatorSetId {
|
||||
match self {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) =>
|
||||
equivocation_proof.set_id(),
|
||||
EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) =>
|
||||
equivocation_proof.vote.commitment.validator_set_id,
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) =>
|
||||
equivocation_proof.vote.commitment.validator_set_id,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the set id at which the equivocation occurred.
|
||||
fn key_owner_proof(&self) -> &T::KeyOwnerProof {
|
||||
match self {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(_, key_owner_proof) => key_owner_proof,
|
||||
EquivocationEvidenceFor::ForkVotingProof(_, key_owner_proof) => key_owner_proof,
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(_, key_owner_proof) => key_owner_proof,
|
||||
}
|
||||
}
|
||||
|
||||
fn checked_offender<P>(&self) -> Option<P::IdentificationTuple>
|
||||
where
|
||||
P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>,
|
||||
{
|
||||
let key = (BEEFY_KEY_TYPE, self.offender_id().clone());
|
||||
P::check_proof(key, self.key_owner_proof().clone())
|
||||
}
|
||||
|
||||
fn check_equivocation_proof(self) -> Result<(), Error<T>> {
|
||||
match self {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, _) => {
|
||||
// Validate equivocation proof (check votes are different and signatures are valid).
|
||||
if !pezsp_consensus_beefy::check_double_voting_proof(&equivocation_proof) {
|
||||
return Err(Error::<T>::InvalidDoubleVotingProof);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, _) => {
|
||||
let ForkVotingProof { vote, ancestry_proof, header } = equivocation_proof;
|
||||
|
||||
if !<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::is_proof_optimal(
|
||||
&ancestry_proof,
|
||||
) {
|
||||
return Err(Error::<T>::InvalidForkVotingProof);
|
||||
}
|
||||
|
||||
let maybe_validation_context = <T::AncestryHelper as AncestryHelper<
|
||||
HeaderFor<T>,
|
||||
>>::extract_validation_context(header);
|
||||
let validation_context = match maybe_validation_context {
|
||||
Some(validation_context) => validation_context,
|
||||
None => {
|
||||
return Err(Error::<T>::InvalidForkVotingProof);
|
||||
},
|
||||
};
|
||||
|
||||
let is_non_canonical =
|
||||
<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::is_non_canonical(
|
||||
&vote.commitment,
|
||||
ancestry_proof,
|
||||
validation_context,
|
||||
);
|
||||
if !is_non_canonical {
|
||||
return Err(Error::<T>::InvalidForkVotingProof);
|
||||
}
|
||||
|
||||
let is_signature_valid =
|
||||
check_commitment_signature(&vote.commitment, &vote.id, &vote.signature);
|
||||
if !is_signature_valid {
|
||||
return Err(Error::<T>::InvalidForkVotingProof);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, _) => {
|
||||
let FutureBlockVotingProof { vote } = equivocation_proof;
|
||||
// Check if the commitment actually targets a future block
|
||||
if vote.commitment.block_number < pezframe_system::Pallet::<T>::block_number() {
|
||||
return Err(Error::<T>::InvalidFutureBlockVotingProof);
|
||||
}
|
||||
|
||||
let is_signature_valid =
|
||||
check_commitment_signature(&vote.commitment, &vote.id, &vote.signature);
|
||||
if !is_signature_valid {
|
||||
return Err(Error::<T>::InvalidForkVotingProof);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
fn slash_fraction(&self) -> Option<Perbill> {
|
||||
match self {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(_, _) => None,
|
||||
EquivocationEvidenceFor::ForkVotingProof(_, _) |
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(_, _) => Some(Perbill::from_percent(50)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, R, P, L> OffenceReportSystem<Option<T::AccountId>, EquivocationEvidenceFor<T>>
|
||||
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, BlockNumberFor<T>>,
|
||||
>,
|
||||
P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>,
|
||||
P::IdentificationTuple: Clone,
|
||||
L: Get<u64>,
|
||||
{
|
||||
type Longevity = L;
|
||||
|
||||
fn publish_evidence(evidence: EquivocationEvidenceFor<T>) -> Result<(), ()> {
|
||||
use pezframe_system::offchain::SubmitTransaction;
|
||||
|
||||
let call: Call<T> = evidence.into();
|
||||
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: EquivocationEvidenceFor<T>,
|
||||
) -> Result<(), TransactionValidityError> {
|
||||
let offender = evidence.checked_offender::<P>().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: evidence.set_id(), round: *evidence.round_number() };
|
||||
if R::is_known_offence(&[offender], &time_slot) {
|
||||
Err(InvalidTransaction::Stale.into())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn process_evidence(
|
||||
reporter: Option<T::AccountId>,
|
||||
evidence: EquivocationEvidenceFor<T>,
|
||||
) -> Result<(), DispatchError> {
|
||||
let maybe_slash_fraction = evidence.slash_fraction();
|
||||
let reporter = reporter.or_else(|| pezpallet_authorship::Pallet::<T>::author());
|
||||
|
||||
// We check the equivocation within the context of its set id (and associated session).
|
||||
let set_id = evidence.set_id();
|
||||
let round = *evidence.round_number();
|
||||
let set_id_session_index = crate::SetIdSession::<T>::get(set_id)
|
||||
.ok_or(Error::<T>::InvalidEquivocationProofSessionMember)?;
|
||||
|
||||
// Check that the session id for the membership proof is within the bounds
|
||||
// of the set id reported in the equivocation.
|
||||
let key_owner_proof = evidence.key_owner_proof();
|
||||
let validator_count = key_owner_proof.validator_count();
|
||||
let session_index = key_owner_proof.session();
|
||||
if session_index != set_id_session_index {
|
||||
return Err(Error::<T>::InvalidEquivocationProofSession.into());
|
||||
}
|
||||
|
||||
// Validate the key ownership proof extracting the id of the offender.
|
||||
let offender =
|
||||
evidence.checked_offender::<P>().ok_or(Error::<T>::InvalidKeyOwnershipProof)?;
|
||||
|
||||
evidence.check_equivocation_proof()?;
|
||||
|
||||
let offence = EquivocationOffence {
|
||||
time_slot: TimeSlot { set_id, round },
|
||||
session_index,
|
||||
validator_set_count: validator_count,
|
||||
offender,
|
||||
maybe_slash_fraction,
|
||||
};
|
||||
R::report_offence(reporter.into_iter().collect(), offence)
|
||||
.map_err(|_| Error::<T>::DuplicateOffenceReport.into())
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
// 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();
|
||||
},
|
||||
}
|
||||
|
||||
let evidence = call.to_equivocation_evidence_for().ok_or(InvalidTransaction::Call)?;
|
||||
let tag = (evidence.offender_id().clone(), evidence.set_id(), *evidence.round_number());
|
||||
T::EquivocationReportSystem::check_evidence(evidence)?;
|
||||
|
||||
let longevity =
|
||||
<T::EquivocationReportSystem as OffenceReportSystem<_, _>>::Longevity::get();
|
||||
ValidTransaction::with_tag_prefix("BeefyEquivocation")
|
||||
// We assign the maximum priority for any equivocation report.
|
||||
.priority(TransactionPriority::MAX)
|
||||
// Only one equivocation report for the same offender at the same slot.
|
||||
.and_provides(tag)
|
||||
.longevity(longevity)
|
||||
// We don't propagate this. This can never be included on a remote node.
|
||||
.propagate(false)
|
||||
.build()
|
||||
}
|
||||
|
||||
pub fn pre_dispatch(call: &Call<T>) -> Result<(), TransactionValidityError> {
|
||||
let evidence = call.to_equivocation_evidence_for().ok_or(InvalidTransaction::Call)?;
|
||||
T::EquivocationReportSystem::check_evidence(evidence)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,790 @@
|
||||
// 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.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod default_weights;
|
||||
mod equivocation;
|
||||
#[cfg(test)]
|
||||
mod mock;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use alloc::{boxed::Box, vec::Vec};
|
||||
use codec::{Encode, MaxEncodedLen};
|
||||
use log;
|
||||
|
||||
use pezframe_support::{
|
||||
dispatch::{DispatchResultWithPostInfo, Pays},
|
||||
pezpallet_prelude::*,
|
||||
traits::{Get, OneSessionHandler},
|
||||
weights::{constants::RocksDbWeight as DbWeight, Weight},
|
||||
BoundedSlice, BoundedVec, Parameter,
|
||||
};
|
||||
use pezframe_system::{
|
||||
ensure_none, ensure_signed,
|
||||
pezpallet_prelude::{BlockNumberFor, HeaderFor, OriginFor},
|
||||
};
|
||||
use pezsp_consensus_beefy::{
|
||||
AncestryHelper, AncestryHelperWeightInfo, AuthorityIndex, BeefyAuthorityId, ConsensusLog,
|
||||
DoubleVotingProof, ForkVotingProof, FutureBlockVotingProof, OnNewValidatorSet, ValidatorSet,
|
||||
BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID,
|
||||
};
|
||||
use pezsp_runtime::{
|
||||
generic::DigestItem,
|
||||
traits::{IsMember, Member, One},
|
||||
RuntimeAppPublic,
|
||||
};
|
||||
use pezsp_session::{GetSessionNumber, GetValidatorCount};
|
||||
use pezsp_staking::{offence::OffenceReportSystem, SessionIndex};
|
||||
|
||||
use crate::equivocation::EquivocationEvidenceFor;
|
||||
pub use crate::equivocation::{EquivocationOffence, EquivocationReportSystem, TimeSlot};
|
||||
pub use pallet::*;
|
||||
|
||||
const LOG_TARGET: &str = "runtime::beefy";
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use pezframe_system::{ensure_root, pezpallet_prelude::BlockNumberFor};
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: pezframe_system::Config {
|
||||
/// Authority identifier type
|
||||
type BeefyId: Member
|
||||
+ Parameter
|
||||
// todo: use custom signature hashing type instead of hardcoded `Keccak256`
|
||||
+ BeefyAuthorityId<pezsp_runtime::traits::Keccak256>
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
|
||||
/// The maximum number of authorities that can be added.
|
||||
#[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>;
|
||||
|
||||
/// A hook to act on the new BEEFY validator set.
|
||||
///
|
||||
/// For some applications it might be beneficial to make the BEEFY validator set available
|
||||
/// externally apart from having it in the storage. For instance you might cache a light
|
||||
/// weight MMR root over validators and make it available for Light Clients.
|
||||
type OnNewValidatorSet: OnNewValidatorSet<<Self as Config>::BeefyId>;
|
||||
|
||||
/// Hook for checking commitment canonicity.
|
||||
type AncestryHelper: AncestryHelper<HeaderFor<Self>>
|
||||
+ AncestryHelperWeightInfo<HeaderFor<Self>>;
|
||||
|
||||
/// Weights for this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// The proof of key ownership, used for validating equivocation reports
|
||||
/// The proof must 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 publish, check and process an equivocation offence.
|
||||
type EquivocationReportSystem: OffenceReportSystem<
|
||||
Option<Self::AccountId>,
|
||||
EquivocationEvidenceFor<Self>,
|
||||
>;
|
||||
}
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// The current authorities set
|
||||
#[pallet::storage]
|
||||
pub type Authorities<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
|
||||
|
||||
/// The current validator set id
|
||||
#[pallet::storage]
|
||||
pub type ValidatorSetId<T: Config> =
|
||||
StorageValue<_, pezsp_consensus_beefy::ValidatorSetId, ValueQuery>;
|
||||
|
||||
/// Authorities set scheduled to be used with the next session
|
||||
#[pallet::storage]
|
||||
pub type NextAuthorities<T: Config> =
|
||||
StorageValue<_, BoundedVec<T::BeefyId, T::MaxAuthorities>, ValueQuery>;
|
||||
|
||||
/// A mapping from BEEFY 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 BEEFY 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: `ValidatorSetId` is not under user control.
|
||||
#[pallet::storage]
|
||||
pub type SetIdSession<T: Config> =
|
||||
StorageMap<_, Twox64Concat, pezsp_consensus_beefy::ValidatorSetId, SessionIndex>;
|
||||
|
||||
/// Block number where BEEFY consensus is enabled/started.
|
||||
/// By changing this (through privileged `set_new_genesis()`), BEEFY consensus is effectively
|
||||
/// restarted from the newly set block number.
|
||||
#[pallet::storage]
|
||||
pub type GenesisBlock<T: Config> = StorageValue<_, Option<BlockNumberFor<T>>, ValueQuery>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
/// Initial set of BEEFY authorities.
|
||||
pub authorities: Vec<T::BeefyId>,
|
||||
/// Block number where BEEFY consensus should start.
|
||||
/// Should match the session where initial authorities are active.
|
||||
/// *Note:* Ideally use block number where GRANDPA authorities are changed,
|
||||
/// to guarantee the client gets a finality notification for exactly this block.
|
||||
pub genesis_block: Option<BlockNumberFor<T>>,
|
||||
}
|
||||
|
||||
impl<T: Config> Default for GenesisConfig<T> {
|
||||
fn default() -> Self {
|
||||
// BEEFY genesis will be first BEEFY-MANDATORY block,
|
||||
// use block number one instead of chain-genesis.
|
||||
let genesis_block = Some(One::one());
|
||||
Self { authorities: Vec::new(), genesis_block }
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
Pallet::<T>::initialize(&self.authorities)
|
||||
// we panic here as runtime maintainers can simply reconfigure genesis and restart
|
||||
// the chain easily
|
||||
.expect("Authorities vec too big");
|
||||
GenesisBlock::<T>::put(&self.genesis_block);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// A key ownership proof provided as part of an equivocation report is invalid.
|
||||
InvalidKeyOwnershipProof,
|
||||
/// A double voting proof provided as part of an equivocation report is invalid.
|
||||
InvalidDoubleVotingProof,
|
||||
/// A fork voting proof provided as part of an equivocation report is invalid.
|
||||
InvalidForkVotingProof,
|
||||
/// A future block voting proof provided as part of an equivocation report is invalid.
|
||||
InvalidFutureBlockVotingProof,
|
||||
/// The session of the equivocation proof is invalid
|
||||
InvalidEquivocationProofSession,
|
||||
/// The session of the equivocation proof is not in the mapping (anymore)
|
||||
InvalidEquivocationProofSessionMember,
|
||||
/// A given equivocation report is valid but already previously reported.
|
||||
DuplicateOffenceReport,
|
||||
/// Submitted configuration is invalid.
|
||||
InvalidConfiguration,
|
||||
}
|
||||
|
||||
#[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_double_voting(
|
||||
key_owner_proof.validator_count(),
|
||||
T::MaxNominators::get(),
|
||||
))]
|
||||
pub fn report_double_voting(
|
||||
origin: OriginFor<T>,
|
||||
equivocation_proof: Box<
|
||||
DoubleVotingProof<
|
||||
BlockNumberFor<T>,
|
||||
T::BeefyId,
|
||||
<T::BeefyId as RuntimeAppPublic>::Signature,
|
||||
>,
|
||||
>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let reporter = ensure_signed(origin)?;
|
||||
|
||||
T::EquivocationReportSystem::process_evidence(
|
||||
Some(reporter),
|
||||
EquivocationEvidenceFor::DoubleVotingProof(*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_double_voting(
|
||||
key_owner_proof.validator_count(),
|
||||
T::MaxNominators::get(),
|
||||
))]
|
||||
pub fn report_double_voting_unsigned(
|
||||
origin: OriginFor<T>,
|
||||
equivocation_proof: Box<
|
||||
DoubleVotingProof<
|
||||
BlockNumberFor<T>,
|
||||
T::BeefyId,
|
||||
<T::BeefyId as RuntimeAppPublic>::Signature,
|
||||
>,
|
||||
>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
|
||||
T::EquivocationReportSystem::process_evidence(
|
||||
None,
|
||||
EquivocationEvidenceFor::DoubleVotingProof(*equivocation_proof, key_owner_proof),
|
||||
)?;
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Reset BEEFY consensus by setting a new BEEFY genesis at `delay_in_blocks` blocks in the
|
||||
/// future.
|
||||
///
|
||||
/// Note: `delay_in_blocks` has to be at least 1.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::set_new_genesis())]
|
||||
pub fn set_new_genesis(
|
||||
origin: OriginFor<T>,
|
||||
delay_in_blocks: BlockNumberFor<T>,
|
||||
) -> DispatchResult {
|
||||
ensure_root(origin)?;
|
||||
ensure!(delay_in_blocks >= One::one(), Error::<T>::InvalidConfiguration);
|
||||
let genesis_block = pezframe_system::Pallet::<T>::block_number() + delay_in_blocks;
|
||||
GenesisBlock::<T>::put(Some(genesis_block));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Report fork voting equivocation. 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(3)]
|
||||
#[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
|
||||
key_owner_proof.validator_count(),
|
||||
T::MaxNominators::get(),
|
||||
&equivocation_proof.ancestry_proof
|
||||
))]
|
||||
pub fn report_fork_voting(
|
||||
origin: OriginFor<T>,
|
||||
equivocation_proof: Box<
|
||||
ForkVotingProof<
|
||||
HeaderFor<T>,
|
||||
T::BeefyId,
|
||||
<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
|
||||
>,
|
||||
>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let reporter = ensure_signed(origin)?;
|
||||
|
||||
T::EquivocationReportSystem::process_evidence(
|
||||
Some(reporter),
|
||||
EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
|
||||
)?;
|
||||
// Waive the fee since the report is valid and beneficial
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Report fork voting equivocation. 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(4)]
|
||||
#[pallet::weight(T::WeightInfo::report_fork_voting::<T>(
|
||||
key_owner_proof.validator_count(),
|
||||
T::MaxNominators::get(),
|
||||
&equivocation_proof.ancestry_proof
|
||||
))]
|
||||
pub fn report_fork_voting_unsigned(
|
||||
origin: OriginFor<T>,
|
||||
equivocation_proof: Box<
|
||||
ForkVotingProof<
|
||||
HeaderFor<T>,
|
||||
T::BeefyId,
|
||||
<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
|
||||
>,
|
||||
>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
|
||||
T::EquivocationReportSystem::process_evidence(
|
||||
None,
|
||||
EquivocationEvidenceFor::ForkVotingProof(*equivocation_proof, key_owner_proof),
|
||||
)?;
|
||||
// Waive the fee since the report is valid and beneficial
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Report future block voting equivocation. 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(5)]
|
||||
#[pallet::weight(T::WeightInfo::report_future_block_voting(
|
||||
key_owner_proof.validator_count(),
|
||||
T::MaxNominators::get(),
|
||||
))]
|
||||
pub fn report_future_block_voting(
|
||||
origin: OriginFor<T>,
|
||||
equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
let reporter = ensure_signed(origin)?;
|
||||
|
||||
T::EquivocationReportSystem::process_evidence(
|
||||
Some(reporter),
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(
|
||||
*equivocation_proof,
|
||||
key_owner_proof,
|
||||
),
|
||||
)?;
|
||||
// Waive the fee since the report is valid and beneficial
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
|
||||
/// Report future block voting equivocation. 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(6)]
|
||||
#[pallet::weight(T::WeightInfo::report_future_block_voting(
|
||||
key_owner_proof.validator_count(),
|
||||
T::MaxNominators::get(),
|
||||
))]
|
||||
pub fn report_future_block_voting_unsigned(
|
||||
origin: OriginFor<T>,
|
||||
equivocation_proof: Box<FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> DispatchResultWithPostInfo {
|
||||
ensure_none(origin)?;
|
||||
|
||||
T::EquivocationReportSystem::process_evidence(
|
||||
None,
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(
|
||||
*equivocation_proof,
|
||||
key_owner_proof,
|
||||
),
|
||||
)?;
|
||||
// Waive the fee since the report is valid and beneficial
|
||||
Ok(Pays::No.into())
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn try_state(_n: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::do_try_state()
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
|
||||
fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> {
|
||||
Self::pre_dispatch(call)
|
||||
}
|
||||
|
||||
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
Self::validate_unsigned(source, call)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Call<T> {
|
||||
pub fn to_equivocation_evidence_for(&self) -> Option<EquivocationEvidenceFor<T>> {
|
||||
match self {
|
||||
Call::report_double_voting_unsigned { equivocation_proof, key_owner_proof } =>
|
||||
Some(EquivocationEvidenceFor::<T>::DoubleVotingProof(
|
||||
*equivocation_proof.clone(),
|
||||
key_owner_proof.clone(),
|
||||
)),
|
||||
Call::report_fork_voting_unsigned { equivocation_proof, key_owner_proof } =>
|
||||
Some(EquivocationEvidenceFor::<T>::ForkVotingProof(
|
||||
*equivocation_proof.clone(),
|
||||
key_owner_proof.clone(),
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> From<EquivocationEvidenceFor<T>> for Call<T> {
|
||||
fn from(evidence: EquivocationEvidenceFor<T>) -> Self {
|
||||
match evidence {
|
||||
EquivocationEvidenceFor::DoubleVotingProof(equivocation_proof, key_owner_proof) =>
|
||||
Call::report_double_voting_unsigned {
|
||||
equivocation_proof: Box::new(equivocation_proof),
|
||||
key_owner_proof,
|
||||
},
|
||||
EquivocationEvidenceFor::ForkVotingProof(equivocation_proof, key_owner_proof) =>
|
||||
Call::report_fork_voting_unsigned {
|
||||
equivocation_proof: Box::new(equivocation_proof),
|
||||
key_owner_proof,
|
||||
},
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(
|
||||
equivocation_proof,
|
||||
key_owner_proof,
|
||||
) => Call::report_future_block_voting_unsigned {
|
||||
equivocation_proof: Box::new(equivocation_proof),
|
||||
key_owner_proof,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "try-runtime", test))]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Ensure the correctness of the state of this pallet.
|
||||
///
|
||||
/// This should be valid before or after each state transition of this pallet.
|
||||
pub fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
Self::try_state_authorities()?;
|
||||
Self::try_state_validators()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Invariants
|
||||
///
|
||||
/// * `Authorities` should not exceed the `MaxAuthorities` capacity.
|
||||
/// * `NextAuthorities` should not exceed the `MaxAuthorities` capacity.
|
||||
fn try_state_authorities() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
if let Some(authorities_len) = <Authorities<T>>::decode_len() {
|
||||
ensure!(
|
||||
authorities_len as u32 <= T::MaxAuthorities::get(),
|
||||
"Authorities number exceeds what the pallet config allows."
|
||||
);
|
||||
} else {
|
||||
return Err(pezsp_runtime::TryRuntimeError::Other(
|
||||
"Failed to decode length of authorities",
|
||||
));
|
||||
}
|
||||
|
||||
if let Some(next_authorities_len) = <NextAuthorities<T>>::decode_len() {
|
||||
ensure!(
|
||||
next_authorities_len as u32 <= T::MaxAuthorities::get(),
|
||||
"Next authorities number exceeds what the pallet config allows."
|
||||
);
|
||||
} else {
|
||||
return Err(pezsp_runtime::TryRuntimeError::Other(
|
||||
"Failed to decode length of next authorities",
|
||||
));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// # Invariants
|
||||
///
|
||||
/// `ValidatorSetId` must be present in `SetIdSession`
|
||||
fn try_state_validators() -> Result<(), pezsp_runtime::TryRuntimeError> {
|
||||
let validator_set_id = <ValidatorSetId<T>>::get();
|
||||
ensure!(
|
||||
SetIdSession::<T>::get(validator_set_id).is_some(),
|
||||
"Validator set id must be present in SetIdSession"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Return the current active BEEFY validator set.
|
||||
pub fn validator_set() -> Option<ValidatorSet<T::BeefyId>> {
|
||||
let validators: BoundedVec<T::BeefyId, T::MaxAuthorities> = Authorities::<T>::get();
|
||||
let id: pezsp_consensus_beefy::ValidatorSetId = ValidatorSetId::<T>::get();
|
||||
ValidatorSet::<T::BeefyId>::new(validators, id)
|
||||
}
|
||||
|
||||
/// Submits an extrinsic to report a double voting equivocation. This method will create
|
||||
/// an unsigned extrinsic with a call to `report_double_voting_unsigned` and
|
||||
/// will push the transaction to the pool. Only useful in an offchain context.
|
||||
pub fn submit_unsigned_double_voting_report(
|
||||
equivocation_proof: DoubleVotingProof<
|
||||
BlockNumberFor<T>,
|
||||
T::BeefyId,
|
||||
<T::BeefyId as RuntimeAppPublic>::Signature,
|
||||
>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> Option<()> {
|
||||
T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::DoubleVotingProof(
|
||||
equivocation_proof,
|
||||
key_owner_proof,
|
||||
))
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Submits an extrinsic to report a fork voting equivocation. This method will create
|
||||
/// an unsigned extrinsic with a call to `report_fork_voting_unsigned` and
|
||||
/// will push the transaction to the pool. Only useful in an offchain context.
|
||||
pub fn submit_unsigned_fork_voting_report(
|
||||
equivocation_proof: ForkVotingProof<
|
||||
HeaderFor<T>,
|
||||
T::BeefyId,
|
||||
<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
|
||||
>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> Option<()> {
|
||||
T::EquivocationReportSystem::publish_evidence(EquivocationEvidenceFor::ForkVotingProof(
|
||||
equivocation_proof,
|
||||
key_owner_proof,
|
||||
))
|
||||
.ok()
|
||||
}
|
||||
|
||||
/// Submits an extrinsic to report a future block voting equivocation. This method will create
|
||||
/// an unsigned extrinsic with a call to `report_future_block_voting_unsigned` and
|
||||
/// will push the transaction to the pool. Only useful in an offchain context.
|
||||
pub fn submit_unsigned_future_block_voting_report(
|
||||
equivocation_proof: FutureBlockVotingProof<BlockNumberFor<T>, T::BeefyId>,
|
||||
key_owner_proof: T::KeyOwnerProof,
|
||||
) -> Option<()> {
|
||||
T::EquivocationReportSystem::publish_evidence(
|
||||
EquivocationEvidenceFor::FutureBlockVotingProof(equivocation_proof, key_owner_proof),
|
||||
)
|
||||
.ok()
|
||||
}
|
||||
|
||||
fn change_authorities(
|
||||
new: BoundedVec<T::BeefyId, T::MaxAuthorities>,
|
||||
queued: BoundedVec<T::BeefyId, T::MaxAuthorities>,
|
||||
) {
|
||||
Authorities::<T>::put(&new);
|
||||
|
||||
let new_id = ValidatorSetId::<T>::get() + 1u64;
|
||||
ValidatorSetId::<T>::put(new_id);
|
||||
|
||||
NextAuthorities::<T>::put(&queued);
|
||||
|
||||
if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, new_id) {
|
||||
let log = DigestItem::Consensus(
|
||||
BEEFY_ENGINE_ID,
|
||||
ConsensusLog::AuthoritiesChange(validator_set.clone()).encode(),
|
||||
);
|
||||
pezframe_system::Pallet::<T>::deposit_log(log);
|
||||
|
||||
let next_id = new_id + 1;
|
||||
if let Some(next_validator_set) = ValidatorSet::<T::BeefyId>::new(queued, next_id) {
|
||||
<T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
|
||||
&validator_set,
|
||||
&next_validator_set,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(authorities: &Vec<T::BeefyId>) -> Result<(), ()> {
|
||||
if authorities.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if !Authorities::<T>::get().is_empty() {
|
||||
return Err(());
|
||||
}
|
||||
|
||||
let bounded_authorities =
|
||||
BoundedSlice::<T::BeefyId, T::MaxAuthorities>::try_from(authorities.as_slice())
|
||||
.map_err(|_| ())?;
|
||||
|
||||
let id = GENESIS_AUTHORITY_SET_ID;
|
||||
Authorities::<T>::put(bounded_authorities);
|
||||
ValidatorSetId::<T>::put(id);
|
||||
// Like `pezpallet_session`, initialize the next validator set as well.
|
||||
NextAuthorities::<T>::put(bounded_authorities);
|
||||
|
||||
if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(authorities.clone(), id) {
|
||||
let next_id = id + 1;
|
||||
if let Some(next_validator_set) =
|
||||
ValidatorSet::<T::BeefyId>::new(authorities.clone(), next_id)
|
||||
{
|
||||
<T::OnNewValidatorSet as OnNewValidatorSet<_>>::on_new_validator_set(
|
||||
&validator_set,
|
||||
&next_validator_set,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> pezsp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
||||
type Public = T::BeefyId;
|
||||
}
|
||||
|
||||
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T>
|
||||
where
|
||||
T: pezpallet_session::Config,
|
||||
{
|
||||
type Key = T::BeefyId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
|
||||
{
|
||||
let authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
// we panic here as runtime maintainers can simply reconfigure genesis and restart the
|
||||
// chain easily
|
||||
Self::initialize(&authorities).expect("Authorities vec too big");
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
|
||||
{
|
||||
let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
if next_authorities.len() as u32 > T::MaxAuthorities::get() {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"authorities list {:?} truncated to length {}",
|
||||
next_authorities,
|
||||
T::MaxAuthorities::get(),
|
||||
);
|
||||
}
|
||||
let bounded_next_authorities =
|
||||
BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_authorities);
|
||||
|
||||
let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::<Vec<_>>();
|
||||
if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"queued authorities list {:?} truncated to length {}",
|
||||
next_queued_authorities,
|
||||
T::MaxAuthorities::get(),
|
||||
);
|
||||
}
|
||||
let bounded_next_queued_authorities =
|
||||
BoundedVec::<_, T::MaxAuthorities>::truncate_from(next_queued_authorities);
|
||||
|
||||
// Always issue a change on each `session`, even if validator set hasn't changed.
|
||||
// We want to have at least one BEEFY mandatory block per session.
|
||||
Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities);
|
||||
|
||||
let validator_set_id = ValidatorSetId::<T>::get();
|
||||
// Update the mapping for the new set id that corresponds to the latest session (i.e. now).
|
||||
let session_index = pezpallet_session::Pallet::<T>::current_index();
|
||||
SetIdSession::<T>::insert(validator_set_id, &session_index);
|
||||
// Prune old entry if limit reached.
|
||||
let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1);
|
||||
if validator_set_id >= max_set_id_session_entries {
|
||||
SetIdSession::<T>::remove(validator_set_id - max_set_id_session_entries);
|
||||
}
|
||||
}
|
||||
|
||||
fn on_disabled(i: u32) {
|
||||
let log = DigestItem::Consensus(
|
||||
BEEFY_ENGINE_ID,
|
||||
ConsensusLog::<T::BeefyId>::OnDisabled(i as AuthorityIndex).encode(),
|
||||
);
|
||||
|
||||
pezframe_system::Pallet::<T>::deposit_log(log);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> IsMember<T::BeefyId> for Pallet<T> {
|
||||
fn is_member(authority_id: &T::BeefyId) -> bool {
|
||||
Authorities::<T>::get().iter().any(|id| id == authority_id)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait WeightInfo {
|
||||
fn report_voting_equivocation(
|
||||
votes_count: u32,
|
||||
validator_count: u32,
|
||||
max_nominators_per_validator: u32,
|
||||
) -> Weight;
|
||||
|
||||
fn set_new_genesis() -> Weight;
|
||||
}
|
||||
|
||||
pub(crate) trait WeightInfoExt: WeightInfo {
|
||||
fn report_double_voting(validator_count: u32, max_nominators_per_validator: u32) -> Weight {
|
||||
Self::report_voting_equivocation(2, validator_count, max_nominators_per_validator)
|
||||
}
|
||||
|
||||
fn report_fork_voting<T: Config>(
|
||||
validator_count: u32,
|
||||
max_nominators_per_validator: u32,
|
||||
ancestry_proof: &<T::AncestryHelper as AncestryHelper<HeaderFor<T>>>::Proof,
|
||||
) -> Weight {
|
||||
<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_proof_optimal(&ancestry_proof)
|
||||
.saturating_add(<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::extract_validation_context())
|
||||
.saturating_add(
|
||||
<T::AncestryHelper as AncestryHelperWeightInfo<HeaderFor<T>>>::is_non_canonical(
|
||||
ancestry_proof,
|
||||
),
|
||||
)
|
||||
.saturating_add(Self::report_voting_equivocation(
|
||||
1,
|
||||
validator_count,
|
||||
max_nominators_per_validator,
|
||||
))
|
||||
}
|
||||
|
||||
fn report_future_block_voting(
|
||||
validator_count: u32,
|
||||
max_nominators_per_validator: u32,
|
||||
) -> Weight {
|
||||
// checking if the report is for a future block
|
||||
DbWeight::get()
|
||||
.reads(1)
|
||||
// check and report the equivocated vote
|
||||
.saturating_add(Self::report_voting_equivocation(
|
||||
1,
|
||||
validator_count,
|
||||
max_nominators_per_validator,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> WeightInfoExt for T where T: WeightInfo {}
|
||||
@@ -0,0 +1,379 @@
|
||||
// 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 codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use pezframe_election_provider_support::{
|
||||
bounds::{ElectionBounds, ElectionBoundsBuilder},
|
||||
onchain, SequentialPhragmen, Weight,
|
||||
};
|
||||
use pezframe_support::{
|
||||
construct_runtime, derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize},
|
||||
};
|
||||
use pezframe_system::pezpallet_prelude::HeaderFor;
|
||||
use pezpallet_session::historical as pezpallet_session_historical;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::{crypto::KeyTypeId, ConstBool, ConstU128};
|
||||
use pezsp_runtime::{
|
||||
app_crypto::ecdsa::Public,
|
||||
curve::PiecewiseLinear,
|
||||
impl_opaque_keys,
|
||||
testing::TestXt,
|
||||
traits::{Header as HeaderT, OpaqueKeys},
|
||||
BuildStorage, Perbill,
|
||||
};
|
||||
use pezsp_staking::{EraIndex, SessionIndex};
|
||||
use pezsp_state_machine::BasicExternalities;
|
||||
|
||||
use crate as pezpallet_beefy;
|
||||
|
||||
pub use pezsp_consensus_beefy::{ecdsa_crypto::AuthorityId as BeefyId, ConsensusLog, BEEFY_ENGINE_ID};
|
||||
use pezsp_consensus_beefy::{AncestryHelper, AncestryHelperWeightInfo, Commitment};
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct MockSessionKeys {
|
||||
pub dummy: pezpallet_beefy::Pallet<Test>,
|
||||
}
|
||||
}
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Test>;
|
||||
|
||||
construct_runtime!(
|
||||
pub enum Test
|
||||
{
|
||||
System: pezframe_system,
|
||||
Authorship: pezpallet_authorship,
|
||||
Timestamp: pezpallet_timestamp,
|
||||
Balances: pezpallet_balances,
|
||||
Beefy: pezpallet_beefy,
|
||||
Staking: pezpallet_staking,
|
||||
Session: pezpallet_session,
|
||||
Offences: pezpallet_offences,
|
||||
Historical: pezpallet_session_historical,
|
||||
}
|
||||
);
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Test {
|
||||
type Block = Block;
|
||||
type AccountData = pezpallet_balances::AccountData<u128>;
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)]
|
||||
pub struct MockAncestryProofContext {
|
||||
pub is_valid: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
|
||||
pub struct MockAncestryProof {
|
||||
pub is_optimal: bool,
|
||||
pub is_non_canonical: bool,
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const Period: u64 = 1;
|
||||
pub const ReportLongevity: u64 =
|
||||
BondingDuration::get() as u64 * SessionsPerEra::get() as u64 * Period::get();
|
||||
pub const MaxSetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get();
|
||||
|
||||
pub storage AncestryProofContext: Option<MockAncestryProofContext> = Some(
|
||||
MockAncestryProofContext {
|
||||
is_valid: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
pub struct MockAncestryHelper;
|
||||
|
||||
impl<Header: HeaderT> AncestryHelper<Header> for MockAncestryHelper {
|
||||
type Proof = MockAncestryProof;
|
||||
type ValidationContext = MockAncestryProofContext;
|
||||
|
||||
fn is_proof_optimal(proof: &Self::Proof) -> bool {
|
||||
proof.is_optimal
|
||||
}
|
||||
|
||||
fn extract_validation_context(_header: Header) -> Option<Self::ValidationContext> {
|
||||
AncestryProofContext::get()
|
||||
}
|
||||
|
||||
fn is_non_canonical(
|
||||
_commitment: &Commitment<Header::Number>,
|
||||
proof: Self::Proof,
|
||||
context: Self::ValidationContext,
|
||||
) -> bool {
|
||||
context.is_valid && proof.is_non_canonical
|
||||
}
|
||||
}
|
||||
|
||||
impl<Header: HeaderT> AncestryHelperWeightInfo<Header> for MockAncestryHelper {
|
||||
fn is_proof_optimal(_proof: &<Self as AncestryHelper<HeaderFor<Test>>>::Proof) -> Weight {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn extract_validation_context() -> Weight {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn is_non_canonical(_proof: &<Self as AncestryHelper<HeaderFor<Test>>>::Proof) -> Weight {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl pezpallet_beefy::Config for Test {
|
||||
type BeefyId = BeefyId;
|
||||
type MaxAuthorities = ConstU32<100>;
|
||||
type MaxNominators = ConstU32<1000>;
|
||||
type MaxSetIdSessionEntries = MaxSetIdSessionEntries;
|
||||
type OnNewValidatorSet = ();
|
||||
type AncestryHelper = MockAncestryHelper;
|
||||
type WeightInfo = ();
|
||||
type KeyOwnerProof = <Historical as KeyOwnerProofSystem<(KeyTypeId, BeefyId)>>::Proof;
|
||||
type EquivocationReportSystem =
|
||||
super::EquivocationReportSystem<Self, Offences, Historical, ReportLongevity>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
|
||||
}
|
||||
|
||||
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 = <MockSessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = MockSessionKeys;
|
||||
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 RuntimeEvent = RuntimeEvent;
|
||||
type OldCurrency = Balances;
|
||||
type Currency = Balances;
|
||||
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>;
|
||||
}
|
||||
|
||||
impl pezpallet_offences::Config for Test {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type IdentificationTuple = pezpallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = Staking;
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ExtBuilder {
|
||||
authorities: Vec<BeefyId>,
|
||||
}
|
||||
|
||||
impl ExtBuilder {
|
||||
/// Add some AccountIds to insert into `List`.
|
||||
#[cfg(test)]
|
||||
pub(crate) fn add_authorities(mut self, ids: Vec<BeefyId>) -> Self {
|
||||
self.authorities = ids;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> pezsp_io::TestExternalities {
|
||||
pezsp_tracing::try_init_simple();
|
||||
let mut t = pezframe_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
||||
|
||||
let balances: Vec<_> =
|
||||
(0..self.authorities.len()).map(|i| (i as u64, 10_000_000)).collect();
|
||||
|
||||
pezpallet_balances::GenesisConfig::<Test> { balances, ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let session_keys: Vec<_> = self
|
||||
.authorities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, k)| (i as u64, i as u64, MockSessionKeys { dummy: k.clone() }))
|
||||
.collect();
|
||||
|
||||
BasicExternalities::execute_with_storage(&mut t, || {
|
||||
for (ref id, ..) in &session_keys {
|
||||
pezframe_system::Pallet::<Test>::inc_providers(id);
|
||||
}
|
||||
});
|
||||
|
||||
pezpallet_session::GenesisConfig::<Test> { keys: session_keys, ..Default::default() }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
// controllers are same as stash
|
||||
let stakers: Vec<_> = (0..self.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: 2,
|
||||
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 build_and_execute(self, test: impl FnOnce() -> ()) {
|
||||
self.build().execute_with(|| {
|
||||
test();
|
||||
Beefy::do_try_state().expect("All invariants must hold after a test");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Note, that we can't use `UintAuthorityId` here. Reason is that the implementation
|
||||
// of `to_public_key()` assumes, that a public key is 32 bytes long. This is true for
|
||||
// ed25519 and sr25519 but *not* for ecdsa. A compressed ecdsa public key is 33 bytes,
|
||||
// with the first one containing information to reconstruct the uncompressed key.
|
||||
pub fn mock_beefy_id(id: u8) -> BeefyId {
|
||||
let mut buf: [u8; 33] = [id; 33];
|
||||
// Set to something valid.
|
||||
buf[0] = 0x02;
|
||||
let pk = Public::from_raw(buf);
|
||||
BeefyId::from(pk)
|
||||
}
|
||||
|
||||
pub fn mock_authorities(vec: Vec<u8>) -> Vec<BeefyId> {
|
||||
vec.into_iter().map(|id| mock_beefy_id(id)).collect()
|
||||
}
|
||||
|
||||
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());
|
||||
Beefy::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());
|
||||
Beefy::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));
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user