mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-01 17:07:24 +00:00
498e8c181f
Simplified BEEFY worker logic based on the invariant that GRANDPA will always finalize 1st block of each new session, meaning BEEFY worker is guaranteed to receive finality notification for the BEEFY mandatory blocks. Under these conditions the current design is as follows: - session changes are detected based on BEEFY Digest present in BEEFY mandatory blocks, - on each new session new `Rounds` of voting is created, with old rounds being dropped (for gossip rounds, last 3 are still alive so votes are still being gossiped), - after processing finality for a block, the worker votes if a new voting target has become available as a result of said block finality processing, - incoming votes as well as self-created votes are processed and signed commitments are created for completed BEEFY voting rounds, - the worker votes if a new voting target becomes available once a round successfully completes. On worker startup, the current validator set is retrieved from the BEEFY pallet. If it is the genesis validator set, worker starts voting right away considering Block #1 as session start. Otherwise (not genesis), the worker will vote starting with mandatory block of the next session. Later on when we add the BEEFY initial-sync (catch-up) logic, the worker will sync all past mandatory blocks Signed Commitments and will be able to start voting right away. BEEFY mandatory block is the block with header containing the BEEFY `AuthoritiesChange` Digest, this block is guaranteed to be finalized by GRANDPA. This session-boundary block is signed by the ending-session's validator set. Next blocks will be signed by the new session's validator set. This behavior is consistent with what GRANDPA does as well. Also drop the limit N on active gossip rounds. In an adversarial network, a bad actor could create and gossip N invalid votes with round numbers larger than the current correct round number. This would lead to votes for correct rounds to no longer be gossiped. Add unit-tests for all components, including full voter consensus tests. Signed-off-by: Adrian Catangiu <adrian@parity.io> Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com> Co-authored-by: David Salami <Wizdave97>
183 lines
5.2 KiB
Rust
183 lines
5.2 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// Copyright (C) 2021-2022 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)]
|
|
|
|
use codec::Encode;
|
|
|
|
use frame_support::{traits::OneSessionHandler, Parameter};
|
|
|
|
use sp_runtime::{
|
|
generic::DigestItem,
|
|
traits::{IsMember, Member},
|
|
RuntimeAppPublic,
|
|
};
|
|
use sp_std::prelude::*;
|
|
|
|
use beefy_primitives::{AuthorityIndex, ConsensusLog, ValidatorSet, BEEFY_ENGINE_ID};
|
|
|
|
#[cfg(test)]
|
|
mod mock;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
pub use pallet::*;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
use frame_support::pallet_prelude::*;
|
|
use frame_system::pallet_prelude::*;
|
|
|
|
#[pallet::config]
|
|
pub trait Config: frame_system::Config {
|
|
/// Authority identifier type
|
|
type BeefyId: Member + Parameter + RuntimeAppPublic + MaybeSerializeDeserialize;
|
|
}
|
|
|
|
#[pallet::pallet]
|
|
#[pallet::without_storage_info]
|
|
pub struct Pallet<T>(PhantomData<T>);
|
|
|
|
#[pallet::hooks]
|
|
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {}
|
|
|
|
#[pallet::call]
|
|
impl<T: Config> Pallet<T> {}
|
|
|
|
/// The current authorities set
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn authorities)]
|
|
pub(super) type Authorities<T: Config> = StorageValue<_, Vec<T::BeefyId>, ValueQuery>;
|
|
|
|
/// The current validator set id
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn validator_set_id)]
|
|
pub(super) type ValidatorSetId<T: Config> =
|
|
StorageValue<_, beefy_primitives::ValidatorSetId, ValueQuery>;
|
|
|
|
/// Authorities set scheduled to be used with the next session
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn next_authorities)]
|
|
pub(super) type NextAuthorities<T: Config> = StorageValue<_, Vec<T::BeefyId>, ValueQuery>;
|
|
|
|
#[pallet::genesis_config]
|
|
pub struct GenesisConfig<T: Config> {
|
|
pub authorities: Vec<T::BeefyId>,
|
|
}
|
|
|
|
#[cfg(feature = "std")]
|
|
impl<T: Config> Default for GenesisConfig<T> {
|
|
fn default() -> Self {
|
|
Self { authorities: Vec::new() }
|
|
}
|
|
}
|
|
|
|
#[pallet::genesis_build]
|
|
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
|
|
fn build(&self) {
|
|
Pallet::<T>::initialize_authorities(&self.authorities);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
/// Return the current active BEEFY validator set.
|
|
pub fn validator_set() -> Option<ValidatorSet<T::BeefyId>> {
|
|
let validators: Vec<T::BeefyId> = Self::authorities();
|
|
let id: beefy_primitives::ValidatorSetId = Self::validator_set_id();
|
|
ValidatorSet::<T::BeefyId>::new(validators, id)
|
|
}
|
|
|
|
fn change_authorities(new: Vec<T::BeefyId>, queued: Vec<T::BeefyId>) {
|
|
// 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. Furthermore, the digest below is used to signal
|
|
// BEEFY mandatory blocks.
|
|
<Authorities<T>>::put(&new);
|
|
|
|
let next_id = Self::validator_set_id() + 1u64;
|
|
<ValidatorSetId<T>>::put(next_id);
|
|
if let Some(validator_set) = ValidatorSet::<T::BeefyId>::new(new, next_id) {
|
|
let log = DigestItem::Consensus(
|
|
BEEFY_ENGINE_ID,
|
|
ConsensusLog::AuthoritiesChange(validator_set).encode(),
|
|
);
|
|
<frame_system::Pallet<T>>::deposit_log(log);
|
|
}
|
|
|
|
<NextAuthorities<T>>::put(&queued);
|
|
}
|
|
|
|
fn initialize_authorities(authorities: &[T::BeefyId]) {
|
|
if authorities.is_empty() {
|
|
return
|
|
}
|
|
|
|
assert!(<Authorities<T>>::get().is_empty(), "Authorities are already initialized!");
|
|
|
|
<Authorities<T>>::put(authorities);
|
|
<ValidatorSetId<T>>::put(0);
|
|
// Like `pallet_session`, initialize the next validator set as well.
|
|
<NextAuthorities<T>>::put(authorities);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
|
type Public = T::BeefyId;
|
|
}
|
|
|
|
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
|
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<_>>();
|
|
Self::initialize_authorities(&authorities);
|
|
}
|
|
|
|
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, T::BeefyId)>,
|
|
{
|
|
if changed {
|
|
let next_authorities = validators.map(|(_, k)| k).collect::<Vec<_>>();
|
|
let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::<Vec<_>>();
|
|
|
|
Self::change_authorities(next_authorities, next_queued_authorities);
|
|
}
|
|
}
|
|
|
|
fn on_disabled(i: u32) {
|
|
let log = DigestItem::Consensus(
|
|
BEEFY_ENGINE_ID,
|
|
ConsensusLog::<T::BeefyId>::OnDisabled(i as AuthorityIndex).encode(),
|
|
);
|
|
|
|
<frame_system::Pallet<T>>::deposit_log(log);
|
|
}
|
|
}
|
|
|
|
impl<T: Config> IsMember<T::BeefyId> for Pallet<T> {
|
|
fn is_member(authority_id: &T::BeefyId) -> bool {
|
|
Self::authorities().iter().any(|id| id == authority_id)
|
|
}
|
|
}
|