diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 9e8adfcd09..28f8e5dc3f 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -932,6 +932,9 @@ parameter_types! { /// We prioritize im-online heartbeats over election solution submission. pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; pub const MaxAuthorities: u32 = 100; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; } impl frame_system::offchain::CreateSignedTransaction for Runtime @@ -996,6 +999,9 @@ impl pallet_im_online::Config for Runtime { type ReportUnresponsiveness = Offences; type UnsignedPriority = ImOnlineUnsignedPriority; type WeightInfo = pallet_im_online::weights::SubstrateWeight; + type MaxKeys = MaxKeys; + type MaxPeerInHeartbeats = MaxPeerInHeartbeats; + type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; } impl pallet_offences::Config for Runtime { diff --git a/substrate/frame/im-online/src/benchmarking.rs b/substrate/frame/im-online/src/benchmarking.rs index 1043a97f67..20812f03d2 100644 --- a/substrate/frame/im-online/src/benchmarking.rs +++ b/substrate/frame/im-online/src/benchmarking.rs @@ -22,7 +22,7 @@ use super::*; use frame_benchmarking::{benchmarks, impl_benchmark_test_suite}; -use frame_support::traits::UnfilteredDispatchable; +use frame_support::{traits::UnfilteredDispatchable, WeakBoundedVec}; use frame_system::RawOrigin; use sp_core::{offchain::OpaqueMultiaddr, OpaquePeerId}; use sp_runtime::{ @@ -46,7 +46,9 @@ pub fn create_heartbeat( for _ in 0..k { keys.push(T::AuthorityId::generate_pair(None)); } - Keys::::put(keys.clone()); + let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::try_from(keys.clone()) + .map_err(|()| "More than the maximum number of keys provided")?; + Keys::::put(bounded_keys); let network_state = OpaqueNetworkState { peer_id: OpaquePeerId::default(), diff --git a/substrate/frame/im-online/src/lib.rs b/substrate/frame/im-online/src/lib.rs index ab4f700157..2fcaed1820 100644 --- a/substrate/frame/im-online/src/lib.rs +++ b/substrate/frame/im-online/src/lib.rs @@ -74,9 +74,14 @@ mod mock; mod tests; pub mod weights; -use codec::{Decode, Encode}; -use frame_support::traits::{ - EstimateNextSessionRotation, OneSessionHandler, ValidatorSet, ValidatorSetWithIdentification, +use codec::{Decode, Encode, MaxEncodedLen}; +use core::convert::TryFrom; +use frame_support::{ + traits::{ + EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet, + ValidatorSetWithIdentification, WrapperOpaque, + }, + BoundedSlice, WeakBoundedVec, }; use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; pub use pallet::*; @@ -220,6 +225,65 @@ where pub validators_len: u32, } +/// A type that is the same as [`OpaqueNetworkState`] but with [`Vec`] replaced with +/// [`WeakBoundedVec`] 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(PeerIdEncodingLimit: Get, + MultiAddrEncodingLimit: Get, AddressesLimit: Get))] +#[scale_info(skip_type_params(PeerIdEncodingLimit, MultiAddrEncodingLimit, AddressesLimit))] +pub struct BoundedOpaqueNetworkState +where + PeerIdEncodingLimit: Get, + MultiAddrEncodingLimit: Get, + AddressesLimit: Get, +{ + /// PeerId of the local node in SCALE encoded. + pub peer_id: WeakBoundedVec, + /// List of addresses the node knows it can be reached as. + pub external_addresses: + WeakBoundedVec, AddressesLimit>, +} + +impl, MultiAddrEncodingLimit: Get, AddressesLimit: Get> + BoundedOpaqueNetworkState +{ + 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 = <::ValidatorSet as ValidatorSet< ::AccountId, @@ -251,6 +315,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] + #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] @@ -261,7 +326,18 @@ pub mod pallet { + RuntimeAppPublic + Default + Ord - + MaybeSerializeDeserialize; + + MaybeSerializeDeserialize + + MaxEncodedLen; + + /// The maximum number of keys that can be added. + type MaxKeys: Get; + + /// The maximum number of peers to be stored in `ReceivedHeartbeats` + type MaxPeerInHeartbeats: Get; + + /// The maximum size of the encoding of `PeerId` and `MultiAddr` that are coming + /// from the hearbeat + type MaxPeerDataEncodingSize: Get; /// The overarching event type. type Event: From> + IsType<::Event>; @@ -333,14 +409,27 @@ pub mod pallet { /// The current set of keys that may issue a heartbeat. #[pallet::storage] #[pallet::getter(fn keys)] - pub(crate) type Keys = StorageValue<_, Vec, ValueQuery>; + pub(crate) type Keys = + StorageValue<_, WeakBoundedVec, ValueQuery>; - /// For each session index, we keep a mapping of `AuthIndex` to - /// `offchain::OpaqueNetworkState`. + /// For each session index, we keep a mapping of 'SessionIndex` and `AuthIndex` to + /// `WrapperOpaque`. #[pallet::storage] #[pallet::getter(fn received_heartbeats)] - pub(crate) type ReceivedHeartbeats = - StorageDoubleMap<_, Twox64Concat, SessionIndex, Twox64Concat, AuthIndex, Vec>; + pub(crate) type ReceivedHeartbeats = StorageDoubleMap< + _, + Twox64Concat, + SessionIndex, + Twox64Concat, + AuthIndex, + WrapperOpaque< + BoundedOpaqueNetworkState< + T::MaxPeerDataEncodingSize, + T::MaxPeerDataEncodingSize, + T::MaxPeerInHeartbeats, + >, + >, + >; /// For each session index, we keep a mapping of `ValidatorId` to the /// number of blocks authored by the given authority. @@ -409,11 +498,15 @@ pub mod pallet { if let (false, Some(public)) = (exists, public) { Self::deposit_event(Event::::HeartbeatReceived(public.clone())); - let network_state = heartbeat.network_state.encode(); + let network_state_bounded = BoundedOpaqueNetworkState::< + T::MaxPeerDataEncodingSize, + T::MaxPeerDataEncodingSize, + T::MaxPeerInHeartbeats, + >::force_from(&heartbeat.network_state); ReceivedHeartbeats::::insert( ¤t_session, &heartbeat.authority_index, - &network_state, + WrapperOpaque::from(network_state_bounded), ); Ok(()) @@ -739,13 +832,17 @@ impl Pallet { fn initialize_keys(keys: &[T::AuthorityId]) { if !keys.is_empty() { assert!(Keys::::get().is_empty(), "Keys are already initialized!"); - Keys::::put(keys); + let bounded_keys = >::try_from(keys) + .expect("More than the maximum number of keys provided"); + Keys::::put(bounded_keys); } } #[cfg(test)] fn set_keys(keys: Vec) { - Keys::::put(&keys) + let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::try_from(keys) + .expect("More than the maximum number of keys provided"); + Keys::::put(bounded_keys); } } @@ -776,7 +873,15 @@ impl OneSessionHandler for Pallet { >::put(block_number + half_session); // Remember who the authorities are for the new session. - Keys::::put(validators.map(|x| x.1).collect::>()); + let keys = validators.map(|x| x.1).collect::>(); + let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::force_from( + keys, + Some( + "Warning: The session has more keys than expected. \ + A runtime configuration adjustment may be needed.", + ), + ); + Keys::::put(bounded_keys); } fn on_before_session_ending() { diff --git a/substrate/frame/im-online/src/mock.rs b/substrate/frame/im-online/src/mock.rs index e4031b0427..92d1fe8e3f 100644 --- a/substrate/frame/im-online/src/mock.rs +++ b/substrate/frame/im-online/src/mock.rs @@ -217,6 +217,9 @@ impl frame_support::traits::EstimateNextSessionRotation for TestNextSession parameter_types! { pub const UnsignedPriority: u64 = 1 << 20; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; } impl Config for Runtime { @@ -227,6 +230,9 @@ impl Config for Runtime { type ReportUnresponsiveness = OffenceHandler; type UnsignedPriority = UnsignedPriority; type WeightInfo = (); + type MaxKeys = MaxKeys; + type MaxPeerInHeartbeats = MaxPeerInHeartbeats; + type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index 82662295de..6973e25371 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -146,6 +146,9 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxKeys: u32 = 10_000; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; } pub type Extrinsic = sp_runtime::testing::TestXt; @@ -186,6 +189,9 @@ impl pallet_im_online::Config for Test { type ReportUnresponsiveness = Offences; type UnsignedPriority = (); type WeightInfo = (); + type MaxKeys = MaxKeys; + type MaxPeerInHeartbeats = MaxPeerInHeartbeats; + type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; } impl pallet_offences::Config for Test { diff --git a/substrate/frame/session/src/lib.rs b/substrate/frame/session/src/lib.rs index 3f5d853d4f..e57decec8c 100644 --- a/substrate/frame/session/src/lib.rs +++ b/substrate/frame/session/src/lib.rs @@ -114,7 +114,7 @@ mod mock; mod tests; pub mod weights; -use codec::Decode; +use codec::{Decode, MaxEncodedLen}; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, dispatch::{self, DispatchError, DispatchResult}, @@ -367,7 +367,7 @@ pub trait Config: frame_system::Config { type Event: From + Into<::Event>; /// A stable ID for a validator. - type ValidatorId: Member + Parameter; + type ValidatorId: Member + Parameter + MaxEncodedLen; /// A conversion from account ID to validator ID. /// diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index efb5559ed0..d5d0decd11 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -52,7 +52,7 @@ mod misc; pub use misc::{ Backing, ConstU32, EnsureInherentsAreFirst, EstimateCallFee, ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, IsSubType, IsType, Len, OffchainWorker, - OnKilledAccount, OnNewAccount, SameOrOther, Time, TryDrop, UnixTime, + OnKilledAccount, OnNewAccount, SameOrOther, Time, TryDrop, UnixTime, WrapperOpaque, }; mod stored_map; diff --git a/substrate/frame/support/src/traits/misc.rs b/substrate/frame/support/src/traits/misc.rs index 1776e1ba32..75f2f8ac3f 100644 --- a/substrate/frame/support/src/traits/misc.rs +++ b/substrate/frame/support/src/traits/misc.rs @@ -17,8 +17,10 @@ //! Smaller traits used in FRAME which don't need their own file. -use crate::dispatch::Parameter; +use crate::{dispatch::Parameter, TypeInfo}; +use codec::{Decode, Encode, EncodeLike, Input, MaxEncodedLen}; use sp_runtime::{traits::Block as BlockT, DispatchError}; +use sp_std::vec::Vec; /// Anything that can have a `::len()` method. pub trait Len { @@ -377,3 +379,48 @@ impl, const T: u32> EstimateCallFee for T.into() } } + +/// A wrapper for any type `T` which implement encode/decode in a way compatible with `Vec`. +/// +/// The encoding is the encoding of `T` prepended with the compact encoding of its size in bytes. +/// Thus the encoded value can be decoded as a `Vec`. +#[derive(Debug, Eq, PartialEq, Default, Clone, MaxEncodedLen, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +pub struct WrapperOpaque(pub T); + +impl EncodeLike for WrapperOpaque {} + +impl Encode for WrapperOpaque { + fn size_hint(&self) -> usize { + // Compact usually takes at most 4 bytes + self.0.size_hint().saturating_add(4) + } + + fn encode_to(&self, dest: &mut O) { + self.0.encode().encode_to(dest); + } + + fn encode(&self) -> Vec { + self.0.encode().encode() + } + + fn using_encoded R>(&self, f: F) -> R { + self.0.encode().using_encoded(f) + } +} + +impl Decode for WrapperOpaque { + fn decode(input: &mut I) -> Result { + Ok(Self(T::decode(&mut &>::decode(input)?[..])?)) + } + + fn skip(input: &mut I) -> Result<(), codec::Error> { + >::skip(input) + } +} + +impl From for WrapperOpaque { + fn from(t: T) -> Self { + Self(t) + } +} diff --git a/substrate/frame/support/src/traits/validation.rs b/substrate/frame/support/src/traits/validation.rs index f4107ef6e2..11ea5a79f6 100644 --- a/substrate/frame/support/src/traits/validation.rs +++ b/substrate/frame/support/src/traits/validation.rs @@ -18,7 +18,7 @@ //! Traits for dealing with validation and validators. use crate::{dispatch::Parameter, weights::Weight}; -use codec::{Codec, Decode}; +use codec::{Codec, Decode, MaxEncodedLen}; use sp_runtime::{ traits::{Convert, Zero}, BoundToRuntimeAppPublic, ConsensusEngineId, Permill, RuntimeAppPublic, @@ -31,7 +31,7 @@ use sp_std::prelude::*; /// Something that can give information about the current validator set. pub trait ValidatorSet { /// Type for representing validator id in a session. - type ValidatorId: Parameter; + type ValidatorId: Parameter + MaxEncodedLen; /// A type for converting `AccountId` to `ValidatorId`. type ValidatorIdOf: Convert>; diff --git a/substrate/primitives/runtime/src/testing.rs b/substrate/primitives/runtime/src/testing.rs index 781f342d43..fe9ba588ad 100644 --- a/substrate/primitives/runtime/src/testing.rs +++ b/substrate/primitives/runtime/src/testing.rs @@ -18,7 +18,7 @@ //! Testing utilities. use crate::{ - codec::{Codec, Decode, Encode}, + codec::{Codec, Decode, Encode, MaxEncodedLen}, generic, scale_info::TypeInfo, traits::{ @@ -59,6 +59,7 @@ use std::{ Deserialize, PartialOrd, Ord, + MaxEncodedLen, TypeInfo, )] pub struct UintAuthorityId(pub u64);