mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-17 17:11:02 +00:00
8d0cd4ffc8
There is a problem in the way we update `authorithy-discovery` next keys and because of that nodes that enter the active set would be noticed at the start of the session they become active, instead of the start of the previous session as it was intended. This is problematic because: 1. The node itself advertises its addresses on the DHT only when it notices it should become active on around ~10m loop, so in this case it would notice after it becomes active. 2. The other nodes won't be able to detect the new nodes addresses at the beginning of the session, so it won't added them to the reserved set. With 1 + 2, we end-up in a situation where the the new node won't be able to properly connect to its peers because it won't be in its peers reserved set. Now, the nodes accept by default`MIN_GOSSIP_PEERS: usize = 25` connections to nodes that are not in the reserved set, but given Kusama size(> 1000 nodes) you could easily have more than`25` new nodes entering the active set or simply the nodes don't have slots anymore because, they already have connections to peers not in the active set. In the end what the node would notice is 0 backing rewards because it wasn't directly connected to the peers in its backing group. ## Root-cause The flow is like this: 1. At BAD_SESSION - 1, in `rotate_session` new nodes are added to QueuedKeys https://github.com/paritytech/polkadot-sdk/blob/02e1a7f476d7d7c67153e975ab9a1bdc02ffea12/substrate/frame/session/src/lib.rs#L609 ``` <QueuedKeys<T>>::put(queued_amalgamated.clone()); <QueuedChanged<T>>::put(next_changed); ``` 2. AuthorityDiscovery::on_new_session is called with `changed` being the value of `<QueuedChanged<T>>:` at BAD_SESSION - **2** because it was saved before being updated https://github.com/paritytech/polkadot-sdk/blob/02e1a7f476d7d7c67153e975ab9a1bdc02ffea12/substrate/frame/session/src/lib.rs#L613 3. At BAD_SESSION - 1, `AuthorityDiscovery::on_new_session` doesn't updated its next_keys because `changed` was false. 4. For the entire durations of `BAD_SESSION - 1` everyone calling runtime api `authorities`(should return past, present and future authorities) won't discover the nodes that should become active . 5. At the beginning of BAD_SESSION, all nodes discover the new nodes are authorities, but it is already too late because reserved_nodes are updated only at the beginning of the session by the `gossip-support`. See above why this bad. ## Fix Update next keys with the queued_validators at every session, not matter the value of `changed` this is the same way babe pallet correctly does it. https://github.com/paritytech/polkadot-sdk/blob/02e1a7f476d7d7c67153e975ab9a1bdc02ffea12/substrate/frame/babe/src/lib.rs#L655 ## Notes - The issue doesn't reproduce with proof-authorities changes like `versi` because `changed` would always be true and `AuthorityDiscovery` correctly updates its next_keys every time. - Confirmed at session `37651` on kusama that this is exactly what it happens by looking at blocks with polkadot.js. ## TODO - [ ] Move versi on proof of stake and properly test before and after fix to confirm there is no other issue. --------- Signed-off-by: Alexandru Gheorghe <alexandru.gheorghe@parity.io> Co-authored-by: Bastian Köcher <git@kchr.de>
374 lines
11 KiB
Rust
374 lines
11 KiB
Rust
// This file is part of Substrate.
|
|
|
|
// 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.
|
|
|
|
//! # Authority discovery pallet.
|
|
//!
|
|
//! This pallet is used by the `client/authority-discovery` and by polkadot's parachain logic
|
|
//! to retrieve the current and the next set of authorities.
|
|
|
|
// Ensure we're `no_std` when compiling for Wasm.
|
|
#![cfg_attr(not(feature = "std"), no_std)]
|
|
|
|
use frame_support::{
|
|
traits::{Get, OneSessionHandler},
|
|
WeakBoundedVec,
|
|
};
|
|
use sp_authority_discovery::AuthorityId;
|
|
use sp_std::prelude::*;
|
|
|
|
pub use pallet::*;
|
|
|
|
#[frame_support::pallet]
|
|
pub mod pallet {
|
|
use super::*;
|
|
use frame_support::pallet_prelude::*;
|
|
|
|
#[pallet::pallet]
|
|
pub struct Pallet<T>(_);
|
|
|
|
#[pallet::config]
|
|
/// The pallet's config trait.
|
|
pub trait Config: frame_system::Config + pallet_session::Config {
|
|
/// The maximum number of authorities that can be added.
|
|
type MaxAuthorities: Get<u32>;
|
|
}
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn keys)]
|
|
/// Keys of the current authority set.
|
|
pub(super) type Keys<T: Config> =
|
|
StorageValue<_, WeakBoundedVec<AuthorityId, T::MaxAuthorities>, ValueQuery>;
|
|
|
|
#[pallet::storage]
|
|
#[pallet::getter(fn next_keys)]
|
|
/// Keys of the next authority set.
|
|
pub(super) type NextKeys<T: Config> =
|
|
StorageValue<_, WeakBoundedVec<AuthorityId, T::MaxAuthorities>, ValueQuery>;
|
|
|
|
#[derive(frame_support::DefaultNoBound)]
|
|
#[pallet::genesis_config]
|
|
pub struct GenesisConfig<T: Config> {
|
|
pub keys: Vec<AuthorityId>,
|
|
#[serde(skip)]
|
|
pub _config: sp_std::marker::PhantomData<T>,
|
|
}
|
|
|
|
#[pallet::genesis_build]
|
|
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
|
fn build(&self) {
|
|
Pallet::<T>::initialize_keys(&self.keys)
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> Pallet<T> {
|
|
/// Retrieve authority identifiers of the current and next authority set
|
|
/// sorted and deduplicated.
|
|
pub fn authorities() -> Vec<AuthorityId> {
|
|
let mut keys = Keys::<T>::get().to_vec();
|
|
let next = NextKeys::<T>::get().to_vec();
|
|
|
|
keys.extend(next);
|
|
keys.sort();
|
|
keys.dedup();
|
|
|
|
keys.to_vec()
|
|
}
|
|
|
|
/// Retrieve authority identifiers of the current authority set in the original order.
|
|
pub fn current_authorities() -> WeakBoundedVec<AuthorityId, T::MaxAuthorities> {
|
|
Keys::<T>::get()
|
|
}
|
|
|
|
/// Retrieve authority identifiers of the next authority set in the original order.
|
|
pub fn next_authorities() -> WeakBoundedVec<AuthorityId, T::MaxAuthorities> {
|
|
NextKeys::<T>::get()
|
|
}
|
|
|
|
fn initialize_keys(keys: &Vec<AuthorityId>) {
|
|
if !keys.is_empty() {
|
|
assert!(Keys::<T>::get().is_empty(), "Keys are already initialized!");
|
|
|
|
let bounded_keys =
|
|
WeakBoundedVec::<AuthorityId, T::MaxAuthorities>::try_from((*keys).clone())
|
|
.expect("Keys vec too big");
|
|
|
|
Keys::<T>::put(&bounded_keys);
|
|
NextKeys::<T>::put(&bounded_keys);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
|
type Public = AuthorityId;
|
|
}
|
|
|
|
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
|
type Key = AuthorityId;
|
|
|
|
fn on_genesis_session<'a, I: 'a>(authorities: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
|
|
{
|
|
Self::initialize_keys(&authorities.map(|x| x.1).collect::<Vec<_>>());
|
|
}
|
|
|
|
fn on_new_session<'a, I: 'a>(changed: bool, validators: I, queued_validators: I)
|
|
where
|
|
I: Iterator<Item = (&'a T::AccountId, Self::Key)>,
|
|
{
|
|
// Remember who the authorities are for the new and next session.
|
|
if changed {
|
|
let keys = validators.map(|x| x.1).collect::<Vec<_>>();
|
|
|
|
let bounded_keys = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
|
|
keys,
|
|
Some(
|
|
"Warning: The session has more validators than expected. \
|
|
A runtime configuration adjustment may be needed.",
|
|
),
|
|
);
|
|
|
|
Keys::<T>::put(bounded_keys);
|
|
}
|
|
|
|
// `changed` represents if queued_validators changed in the previous session not in the
|
|
// current one.
|
|
let next_keys = queued_validators.map(|x| x.1).collect::<Vec<_>>();
|
|
|
|
let next_bounded_keys = WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
|
|
next_keys,
|
|
Some(
|
|
"Warning: The session has more queued validators than expected. \
|
|
A runtime configuration adjustment may be needed.",
|
|
),
|
|
);
|
|
|
|
NextKeys::<T>::put(next_bounded_keys);
|
|
}
|
|
|
|
fn on_disabled(_i: u32) {
|
|
// ignore
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate as pallet_authority_discovery;
|
|
use frame_support::{derive_impl, parameter_types, traits::ConstU32};
|
|
use sp_application_crypto::Pair;
|
|
use sp_authority_discovery::AuthorityPair;
|
|
use sp_core::crypto::key_types;
|
|
use sp_io::TestExternalities;
|
|
use sp_runtime::{
|
|
testing::UintAuthorityId,
|
|
traits::{ConvertInto, IdentityLookup, OpaqueKeys},
|
|
BuildStorage, KeyTypeId, Perbill,
|
|
};
|
|
|
|
type Block = frame_system::mocking::MockBlock<Test>;
|
|
|
|
frame_support::construct_runtime!(
|
|
pub enum Test
|
|
{
|
|
System: frame_system,
|
|
Session: pallet_session,
|
|
AuthorityDiscovery: pallet_authority_discovery,
|
|
}
|
|
);
|
|
|
|
parameter_types! {
|
|
pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33);
|
|
}
|
|
|
|
impl Config for Test {
|
|
type MaxAuthorities = ConstU32<100>;
|
|
}
|
|
|
|
impl pallet_session::Config for Test {
|
|
type SessionManager = ();
|
|
type Keys = UintAuthorityId;
|
|
type ShouldEndSession = pallet_session::PeriodicSessions<Period, Offset>;
|
|
type SessionHandler = TestSessionHandler;
|
|
type RuntimeEvent = RuntimeEvent;
|
|
type ValidatorId = AuthorityId;
|
|
type ValidatorIdOf = ConvertInto;
|
|
type NextSessionRotation = pallet_session::PeriodicSessions<Period, Offset>;
|
|
type WeightInfo = ();
|
|
}
|
|
|
|
impl pallet_session::historical::Config for Test {
|
|
type FullIdentification = ();
|
|
type FullIdentificationOf = ();
|
|
}
|
|
|
|
pub type BlockNumber = u64;
|
|
|
|
parameter_types! {
|
|
pub const Period: BlockNumber = 1;
|
|
pub const Offset: BlockNumber = 0;
|
|
}
|
|
|
|
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
|
|
impl frame_system::Config for Test {
|
|
type AccountId = AuthorityId;
|
|
type Lookup = IdentityLookup<Self::AccountId>;
|
|
type Block = Block;
|
|
}
|
|
|
|
pub struct TestSessionHandler;
|
|
impl pallet_session::SessionHandler<AuthorityId> for TestSessionHandler {
|
|
const KEY_TYPE_IDS: &'static [KeyTypeId] = &[key_types::DUMMY];
|
|
|
|
fn on_new_session<Ks: OpaqueKeys>(
|
|
_changed: bool,
|
|
_validators: &[(AuthorityId, Ks)],
|
|
_queued_validators: &[(AuthorityId, Ks)],
|
|
) {
|
|
}
|
|
|
|
fn on_disabled(_validator_index: u32) {}
|
|
|
|
fn on_genesis_session<Ks: OpaqueKeys>(_validators: &[(AuthorityId, Ks)]) {}
|
|
}
|
|
|
|
#[test]
|
|
fn authorities_returns_current_and_next_authority_set() {
|
|
// The whole authority discovery pallet ignores account ids, but we still need them for
|
|
// `pallet_session::OneSessionHandler::on_new_session`, thus its safe to use the same value
|
|
// everywhere.
|
|
let account_id = AuthorityPair::from_seed_slice(vec![10; 32].as_ref()).unwrap().public();
|
|
|
|
let mut first_authorities: Vec<AuthorityId> = vec![0, 1]
|
|
.into_iter()
|
|
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
|
.map(AuthorityId::from)
|
|
.collect();
|
|
|
|
let second_authorities: Vec<AuthorityId> = vec![2, 3]
|
|
.into_iter()
|
|
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
|
.map(AuthorityId::from)
|
|
.collect();
|
|
// Needed for `pallet_session::OneSessionHandler::on_new_session`.
|
|
let second_authorities_and_account_ids = second_authorities
|
|
.clone()
|
|
.into_iter()
|
|
.map(|id| (&account_id, id))
|
|
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
|
|
|
let third_authorities: Vec<AuthorityId> = vec![4, 5]
|
|
.into_iter()
|
|
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
|
.map(AuthorityId::from)
|
|
.collect();
|
|
// Needed for `pallet_session::OneSessionHandler::on_new_session`.
|
|
let third_authorities_and_account_ids = third_authorities
|
|
.clone()
|
|
.into_iter()
|
|
.map(|id| (&account_id, id))
|
|
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
|
|
|
let mut fourth_authorities: Vec<AuthorityId> = vec![6, 7]
|
|
.into_iter()
|
|
.map(|i| AuthorityPair::from_seed_slice(vec![i; 32].as_ref()).unwrap().public())
|
|
.map(AuthorityId::from)
|
|
.collect();
|
|
// Needed for `pallet_session::OneSessionHandler::on_new_session`.
|
|
let fourth_authorities_and_account_ids = fourth_authorities
|
|
.clone()
|
|
.into_iter()
|
|
.map(|id| (&account_id, id))
|
|
.collect::<Vec<(&AuthorityId, AuthorityId)>>();
|
|
|
|
// Build genesis.
|
|
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
|
|
|
|
pallet_authority_discovery::GenesisConfig::<Test> { keys: vec![], ..Default::default() }
|
|
.assimilate_storage(&mut t)
|
|
.unwrap();
|
|
|
|
// Create externalities.
|
|
let mut externalities = TestExternalities::new(t);
|
|
|
|
externalities.execute_with(|| {
|
|
use frame_support::traits::OneSessionHandler;
|
|
|
|
AuthorityDiscovery::on_genesis_session(
|
|
first_authorities.iter().map(|id| (id, id.clone())),
|
|
);
|
|
first_authorities.sort();
|
|
let mut authorities_returned = AuthorityDiscovery::authorities();
|
|
authorities_returned.sort();
|
|
assert_eq!(first_authorities, authorities_returned);
|
|
|
|
// When `changed` set to false, the authority set should not be updated.
|
|
AuthorityDiscovery::on_new_session(
|
|
false,
|
|
second_authorities_and_account_ids.clone().into_iter(),
|
|
third_authorities_and_account_ids.clone().into_iter(),
|
|
);
|
|
let authorities_returned = AuthorityDiscovery::authorities();
|
|
let mut first_and_third_authorities = first_authorities
|
|
.iter()
|
|
.chain(third_authorities.iter())
|
|
.cloned()
|
|
.collect::<Vec<AuthorityId>>();
|
|
first_and_third_authorities.sort();
|
|
|
|
assert_eq!(
|
|
first_and_third_authorities, authorities_returned,
|
|
"Expected authority set not to change as `changed` was set to false.",
|
|
);
|
|
|
|
// When `changed` set to true, the authority set should be updated.
|
|
AuthorityDiscovery::on_new_session(
|
|
true,
|
|
third_authorities_and_account_ids.into_iter(),
|
|
fourth_authorities_and_account_ids.clone().into_iter(),
|
|
);
|
|
|
|
let mut third_and_fourth_authorities = third_authorities
|
|
.iter()
|
|
.chain(fourth_authorities.iter())
|
|
.cloned()
|
|
.collect::<Vec<AuthorityId>>();
|
|
third_and_fourth_authorities.sort();
|
|
assert_eq!(
|
|
third_and_fourth_authorities,
|
|
AuthorityDiscovery::authorities(),
|
|
"Expected authority set to contain both the authorities of the new as well as the \
|
|
next session."
|
|
);
|
|
|
|
// With overlapping authority sets, `authorities()` should return a deduplicated set.
|
|
AuthorityDiscovery::on_new_session(
|
|
true,
|
|
fourth_authorities_and_account_ids.clone().into_iter(),
|
|
fourth_authorities_and_account_ids.clone().into_iter(),
|
|
);
|
|
fourth_authorities.sort();
|
|
assert_eq!(
|
|
fourth_authorities,
|
|
AuthorityDiscovery::authorities(),
|
|
"Expected authority set to be deduplicated."
|
|
);
|
|
});
|
|
}
|
|
}
|