[frame/im-online] remove network state from heartbeats (#14251)

* [frame/im-online] remove `external_addresses` from heartbeats

Users should use DHT for discovering new nodes. The reason for adding external addresses was
unstable work of authority discovery (see https://github.com/paritytech/substrate/issues/2719),
which is now stable. Hence we can safely remove `external_addresses`.

Refs https://github.com/paritytech/polkadot/issues/7181

* remove unused import

* run benchmark

* remove external_addresses from offchain NetworkState

* add missing fn to TestNetwork

* Revert "run benchmark"

This reverts commit a282042c2d6bf8bae2c383f6e2699c3fe2970a3d.

* update weights

* address @bkchr comments

* remove duplicate fn

* cleanup benchmarking.rs

* fix executor tests

* remove peer_id from hearbeat as well

https://github.com/paritytech/substrate/pull/14251#discussion_r1210887220

* remove MaxPeerDataEncodingSize

* change storage value type to `()`

https://github.com/paritytech/substrate/pull/14251#discussion_r1214268931

* scaffold storage migration

* no need to check the type actually

* remove unnecessary types from v0 mod

* add a test for migration

* expose Config types

+ pre_upgrade and post_upgrade working fn

* fix test

* replace dummy type with ConstU32

* add some comments to migration test

* fix comment

* respond to @bkchr comments

* use BoundedOpaqueNetworkState::default

intead of using default for each field
This commit is contained in:
Anton
2023-06-15 13:42:36 +04:00
committed by GitHub
parent 4057ef1554
commit 6cd2c8b395
10 changed files with 226 additions and 198 deletions
+30 -123
View File
@@ -26,8 +26,7 @@
//! in the current era or session.
//!
//! The heartbeat is a signed transaction, which was signed using the session key
//! and includes the recent best block number of the local validators chain as well
//! as the [NetworkState](../../client/offchain/struct.NetworkState.html).
//! and includes the recent best block number of the local validators chain.
//! It is submitted as an Unsigned Transaction via off-chain workers.
//!
//! - [`Config`]
@@ -78,23 +77,27 @@
#![cfg_attr(not(feature = "std"), no_std)]
mod benchmarking;
pub mod migration;
mod mock;
mod tests;
pub mod weights;
use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{
pallet_prelude::*,
traits::{
EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet,
ValidatorSetWithIdentification, WrapperOpaque,
ValidatorSetWithIdentification,
},
BoundedSlice, WeakBoundedVec,
};
use frame_system::offchain::{SendTransactionTypes, SubmitTransaction};
use frame_system::{
offchain::{SendTransactionTypes, SubmitTransaction},
pallet_prelude::*,
};
pub use pallet::*;
use scale_info::TypeInfo;
use sp_application_crypto::RuntimeAppPublic;
use sp_core::offchain::OpaqueNetworkState;
use sp_runtime::{
offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
traits::{AtLeast32BitUnsigned, Convert, Saturating, TrailingZeroInput},
@@ -190,7 +193,6 @@ enum OffchainErr<BlockNumber> {
AlreadyOnline(u32),
FailedSigning,
FailedToAcquireLock,
NetworkState,
SubmitTransaction,
}
@@ -206,7 +208,6 @@ impl<BlockNumber: sp_std::fmt::Debug> sp_std::fmt::Debug for OffchainErr<BlockNu
},
OffchainErr::FailedSigning => write!(fmt, "Failed to sign heartbeat"),
OffchainErr::FailedToAcquireLock => write!(fmt, "Failed to acquire lock"),
OffchainErr::NetworkState => write!(fmt, "Failed to fetch network state"),
OffchainErr::SubmitTransaction => write!(fmt, "Failed to submit transaction"),
}
}
@@ -222,8 +223,6 @@ where
{
/// Block number at the time heartbeat is created..
pub block_number: BlockNumber,
/// A state of local network (peer id and external addresses)
pub network_state: OpaqueNetworkState,
/// Index of the current session.
pub session_index: SessionIndex,
/// An index of the authority on the list of validators.
@@ -232,64 +231,6 @@ where
pub validators_len: u32,
}
/// A type that is the same as [`OpaqueNetworkState`] but with [`Vec`] replaced with
/// [`WeakBoundedVec<Limit>`] where Limit is the respective size limit
/// `PeerIdEncodingLimit` represents the size limit of the encoding of `PeerId`
/// `MultiAddrEncodingLimit` represents the size limit of the encoding of `MultiAddr`
/// `AddressesLimit` represents the size limit of the vector of peers connected
#[derive(Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)]
#[codec(mel_bound())]
#[scale_info(skip_type_params(PeerIdEncodingLimit, MultiAddrEncodingLimit, AddressesLimit))]
pub struct BoundedOpaqueNetworkState<PeerIdEncodingLimit, MultiAddrEncodingLimit, AddressesLimit>
where
PeerIdEncodingLimit: Get<u32>,
MultiAddrEncodingLimit: Get<u32>,
AddressesLimit: Get<u32>,
{
/// PeerId of the local node in SCALE encoded.
pub peer_id: WeakBoundedVec<u8, PeerIdEncodingLimit>,
/// List of addresses the node knows it can be reached as.
pub external_addresses:
WeakBoundedVec<WeakBoundedVec<u8, MultiAddrEncodingLimit>, AddressesLimit>,
}
impl<PeerIdEncodingLimit: Get<u32>, MultiAddrEncodingLimit: Get<u32>, AddressesLimit: Get<u32>>
BoundedOpaqueNetworkState<PeerIdEncodingLimit, MultiAddrEncodingLimit, AddressesLimit>
{
fn force_from(ons: &OpaqueNetworkState) -> Self {
let peer_id = WeakBoundedVec::<_, PeerIdEncodingLimit>::force_from(
ons.peer_id.0.clone(),
Some(
"Warning: The size of the encoding of PeerId \
is bigger than expected. A runtime configuration \
adjustment may be needed.",
),
);
let external_addresses = WeakBoundedVec::<_, AddressesLimit>::force_from(
ons.external_addresses
.iter()
.map(|x| {
WeakBoundedVec::<_, MultiAddrEncodingLimit>::force_from(
x.0.clone(),
Some(
"Warning: The size of the encoding of MultiAddr \
is bigger than expected. A runtime configuration \
adjustment may be needed.",
),
)
})
.collect(),
Some(
"Warning: The network has more peers than expected \
A runtime configuration adjustment may be needed.",
),
);
Self { peer_id, external_addresses }
}
}
/// A type for representing the validator id in a session.
pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet<
<T as frame_system::Config>::AccountId,
@@ -309,10 +250,12 @@ type OffchainResult<T, A> = Result<A, OffchainErr<<T as frame_system::Config>::B
#[frame_support::pallet]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
/// The current storage version.
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet]
#[pallet::storage_version(STORAGE_VERSION)]
pub struct Pallet<T>(_);
#[pallet::config]
@@ -331,10 +274,6 @@ pub mod pallet {
/// The maximum number of peers to be stored in `ReceivedHeartbeats`
type MaxPeerInHeartbeats: Get<u32>;
/// The maximum size of the encoding of `PeerId` and `MultiAddr` that are coming
/// from the hearbeat
type MaxPeerDataEncodingSize: Get<u32>;
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
@@ -400,38 +339,25 @@ pub mod pallet {
/// more accurate then the value we calculate for `HeartbeatAfter`.
#[pallet::storage]
#[pallet::getter(fn heartbeat_after)]
pub(crate) type HeartbeatAfter<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
pub(super) type HeartbeatAfter<T: Config> = StorageValue<_, T::BlockNumber, ValueQuery>;
/// The current set of keys that may issue a heartbeat.
#[pallet::storage]
#[pallet::getter(fn keys)]
pub(crate) type Keys<T: Config> =
pub(super) type Keys<T: Config> =
StorageValue<_, WeakBoundedVec<T::AuthorityId, T::MaxKeys>, ValueQuery>;
/// For each session index, we keep a mapping of `SessionIndex` and `AuthIndex` to
/// `WrapperOpaque<BoundedOpaqueNetworkState>`.
/// For each session index, we keep a mapping of `SessionIndex` and `AuthIndex`.
#[pallet::storage]
#[pallet::getter(fn received_heartbeats)]
pub(crate) type ReceivedHeartbeats<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
SessionIndex,
Twox64Concat,
AuthIndex,
WrapperOpaque<
BoundedOpaqueNetworkState<
T::MaxPeerDataEncodingSize,
T::MaxPeerDataEncodingSize,
T::MaxPeerInHeartbeats,
>,
>,
>;
pub(super) type ReceivedHeartbeats<T: Config> =
StorageDoubleMap<_, Twox64Concat, SessionIndex, Twox64Concat, AuthIndex, bool>;
/// For each session index, we keep a mapping of `ValidatorId<T>` to the
/// number of blocks authored by the given authority.
#[pallet::storage]
#[pallet::getter(fn authored_blocks)]
pub(crate) type AuthoredBlocks<T: Config> = StorageDoubleMap<
pub(super) type AuthoredBlocks<T: Config> = StorageDoubleMap<
_,
Twox64Concat,
SessionIndex,
@@ -457,16 +383,13 @@ pub mod pallet {
#[pallet::call]
impl<T: Config> Pallet<T> {
/// ## Complexity:
/// - `O(K + E)` where K is length of `Keys` (heartbeat.validators_len) and E is length of
/// `heartbeat.network_state.external_address`
/// - `O(K)` where K is length of `Keys` (heartbeat.validators_len)
/// - `O(K)`: decoding of length `K`
/// - `O(E)`: decoding/encoding of length `E`
// NOTE: the weight includes the cost of validate_unsigned as it is part of the cost to
// import block with such an extrinsic.
#[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::validate_unsigned_and_then_heartbeat(
heartbeat.validators_len as u32,
heartbeat.network_state.external_addresses.len() as u32,
heartbeat.validators_len,
))]
pub fn heartbeat(
origin: OriginFor<T>,
@@ -479,22 +402,13 @@ pub mod pallet {
let current_session = T::ValidatorSet::session_index();
let exists =
ReceivedHeartbeats::<T>::contains_key(&current_session, &heartbeat.authority_index);
ReceivedHeartbeats::<T>::contains_key(current_session, heartbeat.authority_index);
let keys = Keys::<T>::get();
let public = keys.get(heartbeat.authority_index as usize);
if let (false, Some(public)) = (exists, public) {
Self::deposit_event(Event::<T>::HeartbeatReceived { authority_id: public.clone() });
let network_state_bounded = BoundedOpaqueNetworkState::<
T::MaxPeerDataEncodingSize,
T::MaxPeerDataEncodingSize,
T::MaxPeerInHeartbeats,
>::force_from(&heartbeat.network_state);
ReceivedHeartbeats::<T>::insert(
&current_session,
&heartbeat.authority_index,
WrapperOpaque::from(network_state_bounded),
);
ReceivedHeartbeats::<T>::insert(current_session, heartbeat.authority_index, true);
Ok(())
} else if exists {
@@ -618,22 +532,22 @@ impl<T: Config> Pallet<T> {
fn is_online_aux(authority_index: AuthIndex, authority: &ValidatorId<T>) -> bool {
let current_session = T::ValidatorSet::session_index();
ReceivedHeartbeats::<T>::contains_key(&current_session, &authority_index) ||
AuthoredBlocks::<T>::get(&current_session, authority) != 0
ReceivedHeartbeats::<T>::contains_key(current_session, authority_index) ||
AuthoredBlocks::<T>::get(current_session, authority) != 0
}
/// Returns `true` if a heartbeat has been received for the authority at `authority_index` in
/// the authorities series, during the current session. Otherwise `false`.
pub fn received_heartbeat_in_current_session(authority_index: AuthIndex) -> bool {
let current_session = T::ValidatorSet::session_index();
ReceivedHeartbeats::<T>::contains_key(&current_session, &authority_index)
ReceivedHeartbeats::<T>::contains_key(current_session, authority_index)
}
/// Note that the given authority has authored a block in the current session.
fn note_authorship(author: ValidatorId<T>) {
let current_session = T::ValidatorSet::session_index();
AuthoredBlocks::<T>::mutate(&current_session, author, |authored| *authored += 1);
AuthoredBlocks::<T>::mutate(current_session, author, |authored| *authored += 1);
}
pub(crate) fn send_heartbeats(
@@ -705,15 +619,8 @@ impl<T: Config> Pallet<T> {
) -> OffchainResult<T, ()> {
// A helper function to prepare heartbeat call.
let prepare_heartbeat = || -> OffchainResult<T, Call<T>> {
let network_state =
sp_io::offchain::network_state().map_err(|_| OffchainErr::NetworkState)?;
let heartbeat = Heartbeat {
block_number,
network_state,
session_index,
authority_index,
validators_len,
};
let heartbeat =
Heartbeat { block_number, session_index, authority_index, validators_len };
let signature = key.sign(&heartbeat.encode()).ok_or(OffchainErr::FailedSigning)?;
@@ -887,9 +794,9 @@ impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
// current session, they have already been processed and won't be needed
// anymore.
#[allow(deprecated)]
ReceivedHeartbeats::<T>::remove_prefix(&T::ValidatorSet::session_index(), None);
ReceivedHeartbeats::<T>::remove_prefix(T::ValidatorSet::session_index(), None);
#[allow(deprecated)]
AuthoredBlocks::<T>::remove_prefix(&T::ValidatorSet::session_index(), None);
AuthoredBlocks::<T>::remove_prefix(T::ValidatorSet::session_index(), None);
if offenders.is_empty() {
Self::deposit_event(Event::<T>::AllGood);