mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-14 04:01:10 +00:00
babe: report equivocations (#6362)
* slots: create primitives crate for consensus slots * offences: add method to check if an offence is unknown * babe: initial equivocation reporting implementation * babe: organize imports * babe: working equivocation reporting * babe: add slot number to equivocation proof * session: move duplicate traits to session primitives * babe: move equivocation stuff to its own file * offences: fix test * session: don't have primitives depend on frame_support * babe: use opaque type for key owner proof * babe: cleanup client equivocation reporting * babe: cleanup equivocation code in pallet * babe: allow sending signed equivocation reports * node: fix compilation * fix test compilation * babe: return bool on check_equivocation_proof * babe: add test for equivocation reporting * babe: add more tests * babe: add test for validate unsigned * babe: take slot number in generate_key_ownership_proof API * babe: add benchmark for equivocation proof checking * session: add benchmark for membership proof checking * offences: fix babe benchmark * babe: add weights based on benchmark results * babe: adjust weights after benchmarking on reference hardware * babe: reorder checks in check_and_report_equivocation
This commit is contained in:
@@ -18,27 +18,37 @@
|
||||
//! Test utilities
|
||||
|
||||
use codec::Encode;
|
||||
use super::{Trait, Module, GenesisConfig, CurrentSlot};
|
||||
use super::{Trait, Module, CurrentSlot};
|
||||
use sp_runtime::{
|
||||
Perbill, impl_opaque_keys,
|
||||
testing::{Header, UintAuthorityId, Digest, DigestItem},
|
||||
traits::IdentityLookup,
|
||||
curve::PiecewiseLinear,
|
||||
testing::{Digest, DigestItem, Header, TestXt,},
|
||||
traits::{Convert, Header as _, IdentityLookup, OpaqueKeys, SaturatedConversion},
|
||||
};
|
||||
use frame_system::InitKind;
|
||||
use frame_support::{
|
||||
impl_outer_origin, parameter_types, StorageValue,
|
||||
traits::OnInitialize,
|
||||
impl_outer_dispatch, impl_outer_origin, parameter_types, StorageValue,
|
||||
traits::{KeyOwnerProofSystem, OnInitialize},
|
||||
weights::Weight,
|
||||
};
|
||||
use sp_io;
|
||||
use sp_core::{H256, U256, crypto::Pair};
|
||||
use sp_consensus_babe::AuthorityPair;
|
||||
use sp_core::{H256, U256, crypto::{KeyTypeId, Pair}};
|
||||
use sp_consensus_babe::{AuthorityId, AuthorityPair, SlotNumber};
|
||||
use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof};
|
||||
use sp_staking::SessionIndex;
|
||||
use pallet_staking::EraIndex;
|
||||
|
||||
impl_outer_origin!{
|
||||
pub enum Origin for Test where system = frame_system {}
|
||||
}
|
||||
|
||||
impl_outer_dispatch! {
|
||||
pub enum Call for Test where origin: Origin {
|
||||
babe::Babe,
|
||||
staking::Staking,
|
||||
}
|
||||
}
|
||||
|
||||
type DummyValidatorId = u64;
|
||||
|
||||
// Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted.
|
||||
@@ -50,7 +60,6 @@ parameter_types! {
|
||||
pub const MaximumBlockWeight: Weight = 1024;
|
||||
pub const MaximumBlockLength: u32 = 2 * 1024;
|
||||
pub const AvailableBlockRatio: Perbill = Perbill::one();
|
||||
pub const MinimumPeriod: u64 = 1;
|
||||
pub const EpochDuration: u64 = 3;
|
||||
pub const ExpectedBlockTime: u64 = 1;
|
||||
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(16);
|
||||
@@ -61,7 +70,7 @@ impl frame_system::Trait for Test {
|
||||
type Origin = Origin;
|
||||
type Index = u64;
|
||||
type BlockNumber = u64;
|
||||
type Call = ();
|
||||
type Call = Call;
|
||||
type Hash = H256;
|
||||
type Version = ();
|
||||
type Hashing = sp_runtime::traits::BlakeTwo256;
|
||||
@@ -78,27 +87,55 @@ impl frame_system::Trait for Test {
|
||||
type AvailableBlockRatio = AvailableBlockRatio;
|
||||
type MaximumBlockLength = MaximumBlockLength;
|
||||
type ModuleToIndex = ();
|
||||
type AccountData = ();
|
||||
type AccountData = pallet_balances::AccountData<u128>;
|
||||
type OnNewAccount = ();
|
||||
type OnKilledAccount = ();
|
||||
}
|
||||
|
||||
impl<C> frame_system::offchain::SendTransactionTypes<C> for Test
|
||||
where
|
||||
Call: From<C>,
|
||||
{
|
||||
type OverarchingCall = Call;
|
||||
type Extrinsic = TestXt<Call, ()>;
|
||||
}
|
||||
|
||||
impl_opaque_keys! {
|
||||
pub struct MockSessionKeys {
|
||||
pub dummy: UintAuthorityId,
|
||||
pub babe_authority: super::Module<Test>,
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_session::Trait for Test {
|
||||
type Event = ();
|
||||
type ValidatorId = <Self as frame_system::Trait>::AccountId;
|
||||
type ValidatorIdOf = pallet_staking::StashOf<Self>;
|
||||
type ShouldEndSession = Babe;
|
||||
type SessionHandler = (Babe,);
|
||||
type SessionManager = ();
|
||||
type ValidatorIdOf = ();
|
||||
type NextSessionRotation = Babe;
|
||||
type SessionManager = pallet_session::historical::NoteHistoricalRoot<Self, Staking>;
|
||||
type SessionHandler = <MockSessionKeys as OpaqueKeys>::KeyTypeIdProviders;
|
||||
type Keys = MockSessionKeys;
|
||||
type DisabledValidatorsThreshold = DisabledValidatorsThreshold;
|
||||
type NextSessionRotation = Babe;
|
||||
}
|
||||
|
||||
impl pallet_session::historical::Trait for Test {
|
||||
type FullIdentification = pallet_staking::Exposure<u64, u128>;
|
||||
type FullIdentificationOf = pallet_staking::ExposureOf<Self>;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const UncleGenerations: u64 = 0;
|
||||
}
|
||||
|
||||
impl pallet_authorship::Trait for Test {
|
||||
type FindAuthor = pallet_session::FindAccountFromAuthorIndex<Self, Babe>;
|
||||
type UncleGenerations = UncleGenerations;
|
||||
type FilterUncle = ();
|
||||
type EventHandler = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const MinimumPeriod: u64 = 1;
|
||||
}
|
||||
|
||||
impl pallet_timestamp::Trait for Test {
|
||||
@@ -107,33 +144,142 @@ impl pallet_timestamp::Trait for Test {
|
||||
type MinimumPeriod = MinimumPeriod;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const ExistentialDeposit: u128 = 1;
|
||||
}
|
||||
|
||||
impl pallet_balances::Trait for Test {
|
||||
type Balance = u128;
|
||||
type DustRemoval = ();
|
||||
type Event = ();
|
||||
type ExistentialDeposit = ExistentialDeposit;
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
pallet_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 SlashDeferDuration: EraIndex = 0;
|
||||
pub const AttestationPeriod: u64 = 100;
|
||||
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
|
||||
pub const MaxNominatorRewardedPerValidator: u32 = 64;
|
||||
pub const ElectionLookahead: u64 = 0;
|
||||
pub const StakingUnsignedPriority: u64 = u64::max_value() / 2;
|
||||
}
|
||||
|
||||
pub struct CurrencyToVoteHandler;
|
||||
|
||||
impl Convert<u128, u128> for CurrencyToVoteHandler {
|
||||
fn convert(x: u128) -> u128 {
|
||||
x
|
||||
}
|
||||
}
|
||||
|
||||
impl Convert<u128, u64> for CurrencyToVoteHandler {
|
||||
fn convert(x: u128) -> u64 {
|
||||
x.saturated_into()
|
||||
}
|
||||
}
|
||||
|
||||
impl pallet_staking::Trait for Test {
|
||||
type RewardRemainder = ();
|
||||
type CurrencyToVote = CurrencyToVoteHandler;
|
||||
type Event = ();
|
||||
type Currency = Balances;
|
||||
type Slash = ();
|
||||
type Reward = ();
|
||||
type SessionsPerEra = SessionsPerEra;
|
||||
type BondingDuration = BondingDuration;
|
||||
type SlashDeferDuration = SlashDeferDuration;
|
||||
type SlashCancelOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||
type SessionInterface = Self;
|
||||
type UnixTime = pallet_timestamp::Module<Test>;
|
||||
type RewardCurve = RewardCurve;
|
||||
type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator;
|
||||
type NextNewSession = Session;
|
||||
type ElectionLookahead = ElectionLookahead;
|
||||
type Call = Call;
|
||||
type UnsignedPriority = StakingUnsignedPriority;
|
||||
type MaxIterations = ();
|
||||
type MinSolutionScoreBump = ();
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub OffencesWeightSoftLimit: Weight = Perbill::from_percent(60) * MaximumBlockWeight::get();
|
||||
}
|
||||
|
||||
impl pallet_offences::Trait for Test {
|
||||
type Event = ();
|
||||
type IdentificationTuple = pallet_session::historical::IdentificationTuple<Self>;
|
||||
type OnOffenceHandler = Staking;
|
||||
type WeightSoftLimit = OffencesWeightSoftLimit;
|
||||
}
|
||||
|
||||
impl Trait for Test {
|
||||
type EpochDuration = EpochDuration;
|
||||
type ExpectedBlockTime = ExpectedBlockTime;
|
||||
type EpochChangeTrigger = crate::ExternalTrigger;
|
||||
|
||||
type KeyOwnerProofSystem = Historical;
|
||||
|
||||
type KeyOwnerProof =
|
||||
<Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(KeyTypeId, AuthorityId)>>::Proof;
|
||||
|
||||
type KeyOwnerIdentification = <Self::KeyOwnerProofSystem as KeyOwnerProofSystem<(
|
||||
KeyTypeId,
|
||||
AuthorityId,
|
||||
)>>::IdentificationTuple;
|
||||
|
||||
type HandleEquivocation = super::EquivocationHandler<Self::KeyOwnerIdentification, Offences>;
|
||||
}
|
||||
|
||||
pub fn new_test_ext(authorities_len: usize) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
|
||||
let pairs = (0..authorities_len).map(|i| {
|
||||
AuthorityPair::from_seed(&U256::from(i).into())
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let mut t = frame_system::GenesisConfig::default().build_storage::<Test>().unwrap();
|
||||
GenesisConfig {
|
||||
authorities: pairs.iter().map(|a| (a.public(), 1)).collect(),
|
||||
}.assimilate_storage::<Test>(&mut t).unwrap();
|
||||
(pairs, t.into())
|
||||
}
|
||||
pub type Balances = pallet_balances::Module<Test>;
|
||||
pub type Historical = pallet_session::historical::Module<Test>;
|
||||
pub type Offences = pallet_offences::Module<Test>;
|
||||
pub type Session = pallet_session::Module<Test>;
|
||||
pub type Staking = pallet_staking::Module<Test>;
|
||||
pub type System = frame_system::Module<Test>;
|
||||
pub type Timestamp = pallet_timestamp::Module<Test>;
|
||||
pub type Babe = Module<Test>;
|
||||
|
||||
pub fn go_to_block(n: u64, s: u64) {
|
||||
use frame_support::traits::OnFinalize;
|
||||
|
||||
System::on_finalize(System::block_number());
|
||||
Session::on_finalize(System::block_number());
|
||||
Staking::on_finalize(System::block_number());
|
||||
|
||||
let parent_hash = if System::block_number() > 1 {
|
||||
let hdr = System::finalize();
|
||||
hdr.hash()
|
||||
} else {
|
||||
System::parent_hash()
|
||||
};
|
||||
|
||||
let pre_digest = make_secondary_plain_pre_digest(0, s);
|
||||
System::initialize(&n, &Default::default(), &Default::default(), &pre_digest, InitKind::Full);
|
||||
|
||||
System::initialize(&n, &parent_hash, &Default::default(), &pre_digest, InitKind::Full);
|
||||
System::set_block_number(n);
|
||||
Timestamp::set_timestamp(n);
|
||||
|
||||
if s > 1 {
|
||||
CurrentSlot::put(s);
|
||||
}
|
||||
// includes a call into `Babe::do_initialize`.
|
||||
|
||||
System::on_initialize(n);
|
||||
Session::on_initialize(n);
|
||||
Staking::on_initialize(n);
|
||||
}
|
||||
|
||||
/// Slots will grow accordingly to blocks
|
||||
@@ -145,6 +291,19 @@ pub fn progress_to_block(n: u64) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Progress to the first block at the given session
|
||||
pub fn start_session(session_index: SessionIndex) {
|
||||
let missing = (session_index - Session::current_index()) * 3;
|
||||
progress_to_block(System::block_number() + missing as u64 + 1);
|
||||
assert_eq!(Session::current_index(), session_index);
|
||||
}
|
||||
|
||||
/// Progress to the first block at the given era
|
||||
pub fn start_era(era_index: EraIndex) {
|
||||
start_session((era_index * 3).into());
|
||||
assert_eq!(Staking::current_era(), Some(era_index));
|
||||
}
|
||||
|
||||
pub fn make_pre_digest(
|
||||
authority_index: sp_consensus_babe::AuthorityIndex,
|
||||
slot_number: sp_consensus_babe::SlotNumber,
|
||||
@@ -177,6 +336,124 @@ pub fn make_secondary_plain_pre_digest(
|
||||
Digest { logs: vec![log] }
|
||||
}
|
||||
|
||||
pub type System = frame_system::Module<Test>;
|
||||
pub type Babe = Module<Test>;
|
||||
pub type Session = pallet_session::Module<Test>;
|
||||
pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities {
|
||||
new_test_ext_with_pairs(authorities_len).1
|
||||
}
|
||||
|
||||
pub fn new_test_ext_with_pairs(authorities_len: usize) -> (Vec<AuthorityPair>, sp_io::TestExternalities) {
|
||||
let pairs = (0..authorities_len).map(|i| {
|
||||
AuthorityPair::from_seed(&U256::from(i).into())
|
||||
}).collect::<Vec<_>>();
|
||||
|
||||
let public = pairs.iter().map(|p| p.public()).collect();
|
||||
|
||||
(pairs, new_test_ext_raw_authorities(public))
|
||||
}
|
||||
|
||||
pub fn new_test_ext_raw_authorities(authorities: Vec<AuthorityId>) -> sp_io::TestExternalities {
|
||||
let mut t = frame_system::GenesisConfig::default()
|
||||
.build_storage::<Test>()
|
||||
.unwrap();
|
||||
|
||||
// stashes are the index.
|
||||
let session_keys: Vec<_> = authorities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, k)| {
|
||||
(
|
||||
i as u64,
|
||||
i as u64,
|
||||
MockSessionKeys {
|
||||
babe_authority: AuthorityId::from(k.clone()),
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
// controllers are the index + 1000
|
||||
let stakers: Vec<_> = (0..authorities.len())
|
||||
.map(|i| {
|
||||
(
|
||||
i as u64,
|
||||
i as u64 + 1000,
|
||||
10_000,
|
||||
pallet_staking::StakerStatus::<u64>::Validator,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let balances: Vec<_> = (0..authorities.len())
|
||||
.map(|i| (i as u64, 10_000_000))
|
||||
.collect();
|
||||
|
||||
// NOTE: this will initialize the babe authorities
|
||||
// through OneSessionHandler::on_genesis_session
|
||||
pallet_session::GenesisConfig::<Test> { keys: session_keys }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
pallet_balances::GenesisConfig::<Test> { balances }
|
||||
.assimilate_storage(&mut t)
|
||||
.unwrap();
|
||||
|
||||
let staking_config = pallet_staking::GenesisConfig::<Test> {
|
||||
stakers,
|
||||
validator_count: 8,
|
||||
force_era: pallet_staking::Forcing::ForceNew,
|
||||
minimum_validator_count: 0,
|
||||
invulnerables: vec![],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
staking_config.assimilate_storage(&mut t).unwrap();
|
||||
|
||||
t.into()
|
||||
}
|
||||
|
||||
/// Creates an equivocation at the current block, by generating two headers.
|
||||
pub fn generate_equivocation_proof(
|
||||
offender_authority_index: u32,
|
||||
offender_authority_pair: &AuthorityPair,
|
||||
slot_number: SlotNumber,
|
||||
) -> sp_consensus_babe::EquivocationProof<Header> {
|
||||
use sp_consensus_babe::digests::CompatibleDigestItem;
|
||||
|
||||
let current_block = System::block_number();
|
||||
let current_slot = CurrentSlot::get();
|
||||
|
||||
let make_header = || {
|
||||
let parent_hash = System::parent_hash();
|
||||
let pre_digest = make_secondary_plain_pre_digest(offender_authority_index, slot_number);
|
||||
System::initialize(¤t_block, &parent_hash, &Default::default(), &pre_digest, InitKind::Full);
|
||||
System::set_block_number(current_block);
|
||||
Timestamp::set_timestamp(current_block);
|
||||
System::finalize()
|
||||
};
|
||||
|
||||
// sign the header prehash and sign it, adding it to the block as the seal
|
||||
// digest item
|
||||
let seal_header = |header: &mut Header| {
|
||||
let prehash = header.hash();
|
||||
let seal = <DigestItem as CompatibleDigestItem>::babe_seal(
|
||||
offender_authority_pair.sign(prehash.as_ref()),
|
||||
);
|
||||
header.digest_mut().push(seal);
|
||||
};
|
||||
|
||||
// generate two headers at the current block
|
||||
let mut h1 = make_header();
|
||||
let mut h2 = make_header();
|
||||
|
||||
seal_header(&mut h1);
|
||||
seal_header(&mut h2);
|
||||
|
||||
// restore previous runtime state
|
||||
go_to_block(current_block, current_slot);
|
||||
|
||||
sp_consensus_babe::EquivocationProof {
|
||||
slot_number,
|
||||
offender: offender_authority_pair.public(),
|
||||
first_header: h1,
|
||||
second_header: h2,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user