feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
[package]
|
||||
name = "pezpallet-im-online"
|
||||
version = "27.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "FRAME's I'm online pallet"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezframe-benchmarking = { optional = true, workspace = true }
|
||||
pezframe-support = { workspace = true }
|
||||
pezframe-system = { workspace = true }
|
||||
log = { workspace = true }
|
||||
pezpallet-authorship = { workspace = true }
|
||||
scale-info = { features = ["derive", "serde"], workspace = true }
|
||||
pezsp-application-crypto = { features = ["serde"], workspace = true }
|
||||
pezsp-core = { features = ["serde"], workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-runtime = { features = ["serde"], workspace = true }
|
||||
pezsp-staking = { features = ["serde"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezpallet-balances = { workspace = true }
|
||||
pezpallet-session = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"pezframe-benchmarking?/std",
|
||||
"pezframe-support/std",
|
||||
"pezframe-system/std",
|
||||
"log/std",
|
||||
"pezpallet-authorship/std",
|
||||
"pezpallet-balances/std",
|
||||
"pezpallet-session/std",
|
||||
"scale-info/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-staking/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezframe-benchmarking/runtime-benchmarks",
|
||||
"pezframe-support/runtime-benchmarks",
|
||||
"pezframe-system/runtime-benchmarks",
|
||||
"pezpallet-authorship/runtime-benchmarks",
|
||||
"pezpallet-balances/runtime-benchmarks",
|
||||
"pezpallet-session/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-staking/runtime-benchmarks",
|
||||
]
|
||||
try-runtime = [
|
||||
"pezframe-support/try-runtime",
|
||||
"pezframe-system/try-runtime",
|
||||
"pezpallet-authorship/try-runtime",
|
||||
"pezpallet-balances/try-runtime",
|
||||
"pezpallet-session/try-runtime",
|
||||
"pezsp-runtime/try-runtime",
|
||||
]
|
||||
@@ -0,0 +1,59 @@
|
||||
# I'm online Module
|
||||
|
||||
If the local node is a validator (i.e. contains an authority key), this module
|
||||
gossips a heartbeat transaction with each new session. The heartbeat functions
|
||||
as a simple mechanism to signal that the node is online in the current era.
|
||||
|
||||
Received heartbeats are tracked for one era and reset with each new era. The
|
||||
module exposes two public functions to query if a heartbeat has been received
|
||||
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`.
|
||||
It is submitted as an Unsigned Transaction via off-chain workers.
|
||||
|
||||
- [`im_online::Config`](https://docs.rs/pezpallet-im-online/latest/pallet_im_online/trait.Config.html)
|
||||
- [`Call`](https://docs.rs/pezpallet-im-online/latest/pallet_im_online/enum.Call.html)
|
||||
- [`Module`](https://docs.rs/pezpallet-im-online/latest/pallet_im_online/struct.Module.html)
|
||||
|
||||
## Interface
|
||||
|
||||
### Public Functions
|
||||
|
||||
- `is_online` - True if the validator sent a heartbeat in the current session.
|
||||
|
||||
## Usage
|
||||
|
||||
```rust
|
||||
use pallet_im_online::{self as im_online};
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
use frame_support::pallet_prelude::*;
|
||||
use frame_system::pallet_prelude::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config + im_online::Config {}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
#[pallet::weight(0)]
|
||||
pub fn is_online(origin: OriginFor<T>, authority_index: u32) -> DispatchResult {
|
||||
let _sender = ensure_signed(origin)?;
|
||||
let _is_online = <im_online::Pallet<T>>::is_online(authority_index);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
This module depends on the [Session module](https://docs.rs/pezpallet-session/latest/pallet_session/).
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,118 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! I'm Online pallet benchmarking.
|
||||
|
||||
#![cfg(feature = "runtime-benchmarks")]
|
||||
|
||||
use pezframe_benchmarking::v2::*;
|
||||
use pezframe_support::{traits::UnfilteredDispatchable, WeakBoundedVec};
|
||||
use pezframe_system::RawOrigin;
|
||||
use pezsp_runtime::{
|
||||
traits::{ValidateUnsigned, Zero},
|
||||
transaction_validity::TransactionSource,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub fn create_heartbeat<T: Config>(
|
||||
k: u32,
|
||||
) -> Result<
|
||||
(
|
||||
crate::Heartbeat<pezframe_system::pezpallet_prelude::BlockNumberFor<T>>,
|
||||
<T::AuthorityId as RuntimeAppPublic>::Signature,
|
||||
),
|
||||
&'static str,
|
||||
> {
|
||||
let mut keys = Vec::new();
|
||||
for _ in 0..k {
|
||||
keys.push(T::AuthorityId::generate_pair(None));
|
||||
}
|
||||
let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::try_from(keys.clone())
|
||||
.map_err(|()| "More than the maximum number of keys provided")?;
|
||||
Keys::<T>::put(bounded_keys);
|
||||
|
||||
let input_heartbeat = Heartbeat {
|
||||
block_number: pezframe_system::pezpallet_prelude::BlockNumberFor::<T>::zero(),
|
||||
session_index: 0,
|
||||
authority_index: k - 1,
|
||||
validators_len: keys.len() as u32,
|
||||
};
|
||||
|
||||
let encoded_heartbeat = input_heartbeat.encode();
|
||||
let authority_id = keys.get((k - 1) as usize).ok_or("out of range")?;
|
||||
let signature = authority_id.sign(&encoded_heartbeat).ok_or("couldn't make signature")?;
|
||||
|
||||
Ok((input_heartbeat, signature))
|
||||
}
|
||||
|
||||
#[benchmarks]
|
||||
mod benchmarks {
|
||||
use super::*;
|
||||
|
||||
#[benchmark(extra)]
|
||||
fn heartbeat(k: Linear<1, { <T as Config>::MaxKeys::get() }>) -> Result<(), BenchmarkError> {
|
||||
let (input_heartbeat, signature) = create_heartbeat::<T>(k)?;
|
||||
|
||||
#[extrinsic_call]
|
||||
_(RawOrigin::None, input_heartbeat, signature);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark(extra)]
|
||||
fn validate_unsigned(
|
||||
k: Linear<1, { <T as Config>::MaxKeys::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let (input_heartbeat, signature) = create_heartbeat::<T>(k)?;
|
||||
let call = Call::heartbeat { heartbeat: input_heartbeat, signature };
|
||||
|
||||
#[block]
|
||||
{
|
||||
Pallet::<T>::validate_unsigned(TransactionSource::InBlock, &call)
|
||||
.map_err(<&str>::from)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[benchmark]
|
||||
fn validate_unsigned_and_then_heartbeat(
|
||||
k: Linear<1, { <T as Config>::MaxKeys::get() }>,
|
||||
) -> Result<(), BenchmarkError> {
|
||||
let (input_heartbeat, signature) = create_heartbeat::<T>(k)?;
|
||||
let call = Call::heartbeat { heartbeat: input_heartbeat, signature };
|
||||
let call_enc = call.encode();
|
||||
|
||||
#[block]
|
||||
{
|
||||
Pallet::<T>::validate_unsigned(TransactionSource::InBlock, &call)
|
||||
.map_err(<&str>::from)?;
|
||||
<Call<T> as Decode>::decode(&mut &*call_enc)
|
||||
.expect("call is encoded above, encoding must be correct")
|
||||
.dispatch_bypass_filter(RawOrigin::None.into())?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl_benchmark_test_suite! {
|
||||
Pallet,
|
||||
mock::new_test_ext(),
|
||||
mock::Runtime
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,865 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! # I'm online Pallet
|
||||
//!
|
||||
//! If the local node is a validator (i.e. contains an authority key), this pallet
|
||||
//! gossips a heartbeat transaction with each new session. The heartbeat functions
|
||||
//! as a simple mechanism to signal that the node is online in the current era.
|
||||
//!
|
||||
//! Received heartbeats are tracked for one era and reset with each new era. The
|
||||
//! pallet exposes two public functions to query if a heartbeat has been received
|
||||
//! 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.
|
||||
//! It is submitted as an Unsigned Transaction via off-chain workers.
|
||||
//!
|
||||
//! - [`Config`]
|
||||
//! - [`Call`]
|
||||
//! - [`Pallet`]
|
||||
//!
|
||||
//! ## Interface
|
||||
//!
|
||||
//! ### Public Functions
|
||||
//!
|
||||
//! - `is_online` - True if the validator sent a heartbeat in the current session.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! ```
|
||||
//! use pezpallet_im_online::{self as im_online};
|
||||
//!
|
||||
//! #[pezframe_support::pallet]
|
||||
//! pub mod pallet {
|
||||
//! use super::*;
|
||||
//! use pezframe_support::pezpallet_prelude::*;
|
||||
//! use pezframe_system::pezpallet_prelude::*;
|
||||
//!
|
||||
//! #[pallet::pallet]
|
||||
//! pub struct Pallet<T>(_);
|
||||
//!
|
||||
//! #[pallet::config]
|
||||
//! pub trait Config: pezframe_system::Config + im_online::Config {}
|
||||
//!
|
||||
//! #[pallet::call]
|
||||
//! impl<T: Config> Pallet<T> {
|
||||
//! #[pallet::weight(0)]
|
||||
//! pub fn is_online(origin: OriginFor<T>, authority_index: u32) -> DispatchResult {
|
||||
//! let _sender = ensure_signed(origin)?;
|
||||
//! let _is_online = <im_online::Pallet<T>>::is_online(authority_index);
|
||||
//! Ok(())
|
||||
//! }
|
||||
//! }
|
||||
//! }
|
||||
//! # fn main() { }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Dependencies
|
||||
//!
|
||||
//! This pallet depends on the [Session pallet](../pezpallet_session/index.html).
|
||||
|
||||
// Ensure we're `no_std` when compiling for Wasm.
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
mod benchmarking;
|
||||
pub mod migration;
|
||||
mod mock;
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use pezframe_support::{
|
||||
pezpallet_prelude::*,
|
||||
traits::{
|
||||
EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet,
|
||||
ValidatorSetWithIdentification,
|
||||
},
|
||||
BoundedSlice, WeakBoundedVec,
|
||||
};
|
||||
use pezframe_system::{
|
||||
offchain::{CreateBare, SubmitTransaction},
|
||||
pezpallet_prelude::*,
|
||||
};
|
||||
pub use pallet::*;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_application_crypto::RuntimeAppPublic;
|
||||
use pezsp_runtime::{
|
||||
offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
|
||||
traits::{AtLeast32BitUnsigned, Convert, Saturating, TrailingZeroInput},
|
||||
PerThing, Perbill, Permill, RuntimeDebug, SaturatedConversion,
|
||||
};
|
||||
use pezsp_staking::{
|
||||
offence::{Kind, Offence, ReportOffence},
|
||||
SessionIndex,
|
||||
};
|
||||
pub use weights::WeightInfo;
|
||||
|
||||
pub mod sr25519 {
|
||||
mod app_sr25519 {
|
||||
use pezsp_application_crypto::{app_crypto, key_types::IM_ONLINE, sr25519};
|
||||
app_crypto!(sr25519, IM_ONLINE);
|
||||
}
|
||||
|
||||
pezsp_application_crypto::with_pair! {
|
||||
/// An i'm online keypair using sr25519 as its crypto.
|
||||
pub type AuthorityPair = app_sr25519::Pair;
|
||||
}
|
||||
|
||||
/// An i'm online signature using sr25519 as its crypto.
|
||||
pub type AuthoritySignature = app_sr25519::Signature;
|
||||
|
||||
/// An i'm online identifier using sr25519 as its crypto.
|
||||
pub type AuthorityId = app_sr25519::Public;
|
||||
}
|
||||
|
||||
pub mod ed25519 {
|
||||
mod app_ed25519 {
|
||||
use pezsp_application_crypto::{app_crypto, ed25519, key_types::IM_ONLINE};
|
||||
app_crypto!(ed25519, IM_ONLINE);
|
||||
}
|
||||
|
||||
pezsp_application_crypto::with_pair! {
|
||||
/// An i'm online keypair using ed25519 as its crypto.
|
||||
pub type AuthorityPair = app_ed25519::Pair;
|
||||
}
|
||||
|
||||
/// An i'm online signature using ed25519 as its crypto.
|
||||
pub type AuthoritySignature = app_ed25519::Signature;
|
||||
|
||||
/// An i'm online identifier using ed25519 as its crypto.
|
||||
pub type AuthorityId = app_ed25519::Public;
|
||||
}
|
||||
|
||||
const DB_PREFIX: &[u8] = b"parity/im-online-heartbeat/";
|
||||
/// How many blocks do we wait for heartbeat transaction to be included
|
||||
/// before sending another one.
|
||||
const INCLUDE_THRESHOLD: u32 = 3;
|
||||
|
||||
/// Status of the offchain worker code.
|
||||
///
|
||||
/// This stores the block number at which heartbeat was requested and when the worker
|
||||
/// has actually managed to produce it.
|
||||
/// Note we store such status for every `authority_index` separately.
|
||||
#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
struct HeartbeatStatus<BlockNumber> {
|
||||
/// An index of the session that we are supposed to send heartbeat for.
|
||||
pub session_index: SessionIndex,
|
||||
/// A block number at which the heartbeat for that session has been actually sent.
|
||||
///
|
||||
/// It may be 0 in case the sending failed. In such case we should just retry
|
||||
/// as soon as possible (i.e. in a worker running for the next block).
|
||||
pub sent_at: BlockNumber,
|
||||
}
|
||||
|
||||
impl<BlockNumber: PartialEq + AtLeast32BitUnsigned + Copy> HeartbeatStatus<BlockNumber> {
|
||||
/// Returns true if heartbeat has been recently sent.
|
||||
///
|
||||
/// Parameters:
|
||||
/// `session_index` - index of current session.
|
||||
/// `now` - block at which the offchain worker is running.
|
||||
///
|
||||
/// This function will return `true` iff:
|
||||
/// 1. the session index is the same (we don't care if it went up or down)
|
||||
/// 2. the heartbeat has been sent recently (within the threshold)
|
||||
///
|
||||
/// The reasoning for 1. is that it's better to send an extra heartbeat than
|
||||
/// to stall or not send one in case of a bug.
|
||||
fn is_recent(&self, session_index: SessionIndex, now: BlockNumber) -> bool {
|
||||
self.session_index == session_index && self.sent_at + INCLUDE_THRESHOLD.into() > now
|
||||
}
|
||||
}
|
||||
|
||||
/// Error which may occur while executing the off-chain code.
|
||||
#[cfg_attr(test, derive(PartialEq))]
|
||||
enum OffchainErr<BlockNumber> {
|
||||
TooEarly,
|
||||
WaitingForInclusion(BlockNumber),
|
||||
AlreadyOnline(u32),
|
||||
FailedSigning,
|
||||
FailedToAcquireLock,
|
||||
SubmitTransaction,
|
||||
}
|
||||
|
||||
impl<BlockNumber: core::fmt::Debug> core::fmt::Debug for OffchainErr<BlockNumber> {
|
||||
fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result {
|
||||
match *self {
|
||||
OffchainErr::TooEarly => write!(fmt, "Too early to send heartbeat."),
|
||||
OffchainErr::WaitingForInclusion(ref block) => {
|
||||
write!(fmt, "Heartbeat already sent at {:?}. Waiting for inclusion.", block)
|
||||
},
|
||||
OffchainErr::AlreadyOnline(auth_idx) => {
|
||||
write!(fmt, "Authority {} is already online", auth_idx)
|
||||
},
|
||||
OffchainErr::FailedSigning => write!(fmt, "Failed to sign heartbeat"),
|
||||
OffchainErr::FailedToAcquireLock => write!(fmt, "Failed to acquire lock"),
|
||||
OffchainErr::SubmitTransaction => write!(fmt, "Failed to submit transaction"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub type AuthIndex = u32;
|
||||
|
||||
/// Heartbeat which is sent/received.
|
||||
#[derive(Encode, Decode, DecodeWithMemTracking, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)]
|
||||
pub struct Heartbeat<BlockNumber>
|
||||
where
|
||||
BlockNumber: PartialEq + Eq + Decode + Encode,
|
||||
{
|
||||
/// Block number at the time heartbeat is created..
|
||||
pub block_number: BlockNumber,
|
||||
/// Index of the current session.
|
||||
pub session_index: SessionIndex,
|
||||
/// An index of the authority on the list of validators.
|
||||
pub authority_index: AuthIndex,
|
||||
/// The length of session validator set
|
||||
pub validators_len: u32,
|
||||
}
|
||||
|
||||
/// A type for representing the validator id in a session.
|
||||
pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::ValidatorId;
|
||||
|
||||
/// A tuple of (ValidatorId, Identification) where `Identification` is the full identification of
|
||||
/// `ValidatorId`.
|
||||
pub type IdentificationTuple<T> = (
|
||||
ValidatorId<T>,
|
||||
<<T as Config>::ValidatorSet as ValidatorSetWithIdentification<
|
||||
<T as pezframe_system::Config>::AccountId,
|
||||
>>::Identification,
|
||||
);
|
||||
|
||||
type OffchainResult<T, A> = Result<A, OffchainErr<BlockNumberFor<T>>>;
|
||||
|
||||
#[pezframe_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
/// The in-code storage version.
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
#[pallet::pallet]
|
||||
#[pallet::storage_version(STORAGE_VERSION)]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: CreateBare<Call<Self>> + pezframe_system::Config {
|
||||
/// The identifier type for an authority.
|
||||
type AuthorityId: Member
|
||||
+ Parameter
|
||||
+ RuntimeAppPublic
|
||||
+ Ord
|
||||
+ MaybeSerializeDeserialize
|
||||
+ MaxEncodedLen;
|
||||
|
||||
/// The maximum number of keys that can be added.
|
||||
type MaxKeys: Get<u32>;
|
||||
|
||||
/// The maximum number of peers to be stored in `ReceivedHeartbeats`
|
||||
type MaxPeerInHeartbeats: Get<u32>;
|
||||
|
||||
/// The overarching event type.
|
||||
#[allow(deprecated)]
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// A type for retrieving the validators supposed to be online in a session.
|
||||
type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>;
|
||||
|
||||
/// A trait that allows us to estimate the current session progress and also the
|
||||
/// average session length.
|
||||
///
|
||||
/// This parameter is used to determine the longevity of `heartbeat` transaction and a
|
||||
/// rough time when we should start considering sending heartbeats, since the workers
|
||||
/// avoids sending them at the very beginning of the session, assuming there is a
|
||||
/// chance the authority will produce a block and they won't be necessary.
|
||||
type NextSessionRotation: EstimateNextSessionRotation<BlockNumberFor<Self>>;
|
||||
|
||||
/// A type that gives us the ability to submit unresponsiveness offence reports.
|
||||
type ReportUnresponsiveness: ReportOffence<
|
||||
Self::AccountId,
|
||||
IdentificationTuple<Self>,
|
||||
UnresponsivenessOffence<IdentificationTuple<Self>>,
|
||||
>;
|
||||
|
||||
/// A configuration for base priority of unsigned transactions.
|
||||
///
|
||||
/// This is exposed so that it can be tuned for particular runtime, when
|
||||
/// multiple pallets send unsigned transactions.
|
||||
#[pallet::constant]
|
||||
type UnsignedPriority: Get<TransactionPriority>;
|
||||
|
||||
/// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// A new heartbeat was received from `AuthorityId`.
|
||||
HeartbeatReceived { authority_id: T::AuthorityId },
|
||||
/// At the end of the session, no offence was committed.
|
||||
AllGood,
|
||||
/// At the end of the session, at least one validator was found to be offline.
|
||||
SomeOffline { offline: Vec<IdentificationTuple<T>> },
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// Non existent public key.
|
||||
InvalidKey,
|
||||
/// Duplicated heartbeat.
|
||||
DuplicatedHeartbeat,
|
||||
}
|
||||
|
||||
/// The block number after which it's ok to send heartbeats in the current
|
||||
/// session.
|
||||
///
|
||||
/// At the beginning of each session we set this to a value that should fall
|
||||
/// roughly in the middle of the session duration. The idea is to first wait for
|
||||
/// the validators to produce a block in the current session, so that the
|
||||
/// heartbeat later on will not be necessary.
|
||||
///
|
||||
/// This value will only be used as a fallback if we fail to get a proper session
|
||||
/// progress estimate from `NextSessionRotation`, as those estimates should be
|
||||
/// more accurate then the value we calculate for `HeartbeatAfter`.
|
||||
#[pallet::storage]
|
||||
pub type HeartbeatAfter<T: Config> = StorageValue<_, BlockNumberFor<T>, ValueQuery>;
|
||||
|
||||
/// The current set of keys that may issue a heartbeat.
|
||||
#[pallet::storage]
|
||||
pub type Keys<T: Config> =
|
||||
StorageValue<_, WeakBoundedVec<T::AuthorityId, T::MaxKeys>, ValueQuery>;
|
||||
|
||||
/// For each session index, we keep a mapping of `SessionIndex` and `AuthIndex`.
|
||||
#[pallet::storage]
|
||||
pub 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]
|
||||
pub type AuthoredBlocks<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
SessionIndex,
|
||||
Twox64Concat,
|
||||
ValidatorId<T>,
|
||||
u32,
|
||||
ValueQuery,
|
||||
>;
|
||||
|
||||
#[pallet::genesis_config]
|
||||
#[derive(pezframe_support::DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub keys: Vec<T::AuthorityId>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
Pallet::<T>::initialize_keys(&self.keys);
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// ## Complexity:
|
||||
/// - `O(K)` where K is length of `Keys` (heartbeat.validators_len)
|
||||
/// - `O(K)`: decoding of length `K`
|
||||
// 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,
|
||||
))]
|
||||
pub fn heartbeat(
|
||||
origin: OriginFor<T>,
|
||||
heartbeat: Heartbeat<BlockNumberFor<T>>,
|
||||
// since signature verification is done in `validate_unsigned`
|
||||
// we can skip doing it here again.
|
||||
_signature: <T::AuthorityId as RuntimeAppPublic>::Signature,
|
||||
) -> DispatchResult {
|
||||
ensure_none(origin)?;
|
||||
|
||||
let current_session = T::ValidatorSet::session_index();
|
||||
let exists =
|
||||
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() });
|
||||
|
||||
ReceivedHeartbeats::<T>::insert(current_session, heartbeat.authority_index, true);
|
||||
|
||||
Ok(())
|
||||
} else if exists {
|
||||
Err(Error::<T>::DuplicatedHeartbeat.into())
|
||||
} else {
|
||||
Err(Error::<T>::InvalidKey.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
fn offchain_worker(now: BlockNumberFor<T>) {
|
||||
// Only send messages if we are a potential validator.
|
||||
if pezsp_io::offchain::is_validator() {
|
||||
for res in Self::send_heartbeats(now).into_iter().flatten() {
|
||||
if let Err(e) = res {
|
||||
log::debug!(
|
||||
target: "runtime::im-online",
|
||||
"Skipping heartbeat at {:?}: {:?}",
|
||||
now,
|
||||
e,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::trace!(
|
||||
target: "runtime::im-online",
|
||||
"Skipping heartbeat at {:?}. Not a validator.",
|
||||
now,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Invalid transaction custom error. Returned when validators_len field in heartbeat is
|
||||
/// incorrect.
|
||||
pub(crate) const INVALID_VALIDATORS_LEN: u8 = 10;
|
||||
|
||||
#[pallet::validate_unsigned]
|
||||
impl<T: Config> ValidateUnsigned for Pallet<T> {
|
||||
type Call = Call<T>;
|
||||
|
||||
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
|
||||
if let Call::heartbeat { heartbeat, signature } = call {
|
||||
if <Pallet<T>>::is_online(heartbeat.authority_index) {
|
||||
// we already received a heartbeat for this authority
|
||||
return InvalidTransaction::Stale.into();
|
||||
}
|
||||
|
||||
// check if session index from heartbeat is recent
|
||||
let current_session = T::ValidatorSet::session_index();
|
||||
if heartbeat.session_index != current_session {
|
||||
return InvalidTransaction::Stale.into();
|
||||
}
|
||||
|
||||
// verify that the incoming (unverified) pubkey is actually an authority id
|
||||
let keys = Keys::<T>::get();
|
||||
if keys.len() as u32 != heartbeat.validators_len {
|
||||
return InvalidTransaction::Custom(INVALID_VALIDATORS_LEN).into();
|
||||
}
|
||||
let authority_id = match keys.get(heartbeat.authority_index as usize) {
|
||||
Some(id) => id,
|
||||
None => return InvalidTransaction::BadProof.into(),
|
||||
};
|
||||
|
||||
// check signature (this is expensive so we do it last).
|
||||
let signature_valid = heartbeat.using_encoded(|encoded_heartbeat| {
|
||||
authority_id.verify(&encoded_heartbeat, signature)
|
||||
});
|
||||
|
||||
if !signature_valid {
|
||||
return InvalidTransaction::BadProof.into();
|
||||
}
|
||||
|
||||
ValidTransaction::with_tag_prefix("ImOnline")
|
||||
.priority(T::UnsignedPriority::get())
|
||||
.and_provides((current_session, authority_id))
|
||||
.longevity(
|
||||
TryInto::<u64>::try_into(
|
||||
T::NextSessionRotation::average_session_length() / 2u32.into(),
|
||||
)
|
||||
.unwrap_or(64_u64),
|
||||
)
|
||||
.propagate(true)
|
||||
.build()
|
||||
} else {
|
||||
InvalidTransaction::Call.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Keep track of number of authored blocks per authority, uncles are counted as
|
||||
/// well since they're a valid proof of being online.
|
||||
impl<T: Config + pezpallet_authorship::Config>
|
||||
pezpallet_authorship::EventHandler<ValidatorId<T>, BlockNumberFor<T>> for Pallet<T>
|
||||
{
|
||||
fn note_author(author: ValidatorId<T>) {
|
||||
Self::note_authorship(author);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Returns `true` if a heartbeat has been received for the authority at
|
||||
/// `authority_index` in the authorities series or if the authority has
|
||||
/// authored at least one block, during the current session. Otherwise
|
||||
/// `false`.
|
||||
pub fn is_online(authority_index: AuthIndex) -> bool {
|
||||
let current_validators = T::ValidatorSet::validators();
|
||||
|
||||
if authority_index >= current_validators.len() as u32 {
|
||||
return false;
|
||||
}
|
||||
|
||||
let authority = ¤t_validators[authority_index as usize];
|
||||
|
||||
Self::is_online_aux(authority_index, authority)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
|
||||
/// 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);
|
||||
}
|
||||
|
||||
pub(crate) fn send_heartbeats(
|
||||
block_number: BlockNumberFor<T>,
|
||||
) -> OffchainResult<T, impl Iterator<Item = OffchainResult<T, ()>>> {
|
||||
const START_HEARTBEAT_RANDOM_PERIOD: Permill = Permill::from_percent(10);
|
||||
const START_HEARTBEAT_FINAL_PERIOD: Permill = Permill::from_percent(80);
|
||||
|
||||
// this should give us a residual probability of 1/SESSION_LENGTH of sending an heartbeat,
|
||||
// i.e. all heartbeats spread uniformly, over most of the session. as the session progresses
|
||||
// the probability of sending an heartbeat starts to increase exponentially.
|
||||
let random_choice = |progress: Permill| {
|
||||
// given session progress `p` and session length `l`
|
||||
// the threshold formula is: p^6 + 1/l
|
||||
let session_length = T::NextSessionRotation::average_session_length();
|
||||
let residual = Permill::from_rational(1u32, session_length.saturated_into());
|
||||
let threshold: Permill = progress.saturating_pow(6).saturating_add(residual);
|
||||
|
||||
let seed = pezsp_io::offchain::random_seed();
|
||||
let random = <u32>::decode(&mut TrailingZeroInput::new(seed.as_ref()))
|
||||
.expect("input is padded with zeroes; qed");
|
||||
let random = Permill::from_parts(random % Permill::ACCURACY);
|
||||
|
||||
random <= threshold
|
||||
};
|
||||
|
||||
let should_heartbeat = if let (Some(progress), _) =
|
||||
T::NextSessionRotation::estimate_current_session_progress(block_number)
|
||||
{
|
||||
// we try to get an estimate of the current session progress first since it should
|
||||
// provide more accurate results. we will start an early heartbeat period where we'll
|
||||
// randomly pick whether to heartbeat. after 80% of the session has elapsed, if we
|
||||
// haven't sent an heartbeat yet we'll send one unconditionally. the idea is to prevent
|
||||
// all nodes from sending the heartbeats at the same block and causing a temporary (but
|
||||
// deterministic) spike in transactions.
|
||||
progress >= START_HEARTBEAT_FINAL_PERIOD ||
|
||||
progress >= START_HEARTBEAT_RANDOM_PERIOD && random_choice(progress)
|
||||
} else {
|
||||
// otherwise we fallback to using the block number calculated at the beginning
|
||||
// of the session that should roughly correspond to the middle of the session
|
||||
let heartbeat_after = <HeartbeatAfter<T>>::get();
|
||||
block_number >= heartbeat_after
|
||||
};
|
||||
|
||||
if !should_heartbeat {
|
||||
return Err(OffchainErr::TooEarly);
|
||||
}
|
||||
|
||||
let session_index = T::ValidatorSet::session_index();
|
||||
let validators_len = Keys::<T>::decode_len().unwrap_or_default() as u32;
|
||||
|
||||
Ok(Self::local_authority_keys().map(move |(authority_index, key)| {
|
||||
Self::send_single_heartbeat(
|
||||
authority_index,
|
||||
key,
|
||||
session_index,
|
||||
block_number,
|
||||
validators_len,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
fn send_single_heartbeat(
|
||||
authority_index: u32,
|
||||
key: T::AuthorityId,
|
||||
session_index: SessionIndex,
|
||||
block_number: BlockNumberFor<T>,
|
||||
validators_len: u32,
|
||||
) -> OffchainResult<T, ()> {
|
||||
// A helper function to prepare heartbeat call.
|
||||
let prepare_heartbeat = || -> OffchainResult<T, Call<T>> {
|
||||
let heartbeat =
|
||||
Heartbeat { block_number, session_index, authority_index, validators_len };
|
||||
|
||||
let signature = key.sign(&heartbeat.encode()).ok_or(OffchainErr::FailedSigning)?;
|
||||
|
||||
Ok(Call::heartbeat { heartbeat, signature })
|
||||
};
|
||||
|
||||
if Self::is_online(authority_index) {
|
||||
return Err(OffchainErr::AlreadyOnline(authority_index));
|
||||
}
|
||||
|
||||
// acquire lock for that authority at current heartbeat to make sure we don't
|
||||
// send concurrent heartbeats.
|
||||
Self::with_heartbeat_lock(authority_index, session_index, block_number, || {
|
||||
let call = prepare_heartbeat()?;
|
||||
log::info!(
|
||||
target: "runtime::im-online",
|
||||
"[index: {:?}] Reporting im-online at block: {:?} (session: {:?}): {:?}",
|
||||
authority_index,
|
||||
block_number,
|
||||
session_index,
|
||||
call,
|
||||
);
|
||||
|
||||
let xt = T::create_bare(call.into());
|
||||
SubmitTransaction::<T, Call<T>>::submit_transaction(xt)
|
||||
.map_err(|_| OffchainErr::SubmitTransaction)?;
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn local_authority_keys() -> impl Iterator<Item = (u32, T::AuthorityId)> {
|
||||
// on-chain storage
|
||||
//
|
||||
// At index `idx`:
|
||||
// 1. A (ImOnline) public key to be used by a validator at index `idx` to send im-online
|
||||
// heartbeats.
|
||||
let authorities = Keys::<T>::get();
|
||||
|
||||
// local keystore
|
||||
//
|
||||
// All `ImOnline` public (+private) keys currently in the local keystore.
|
||||
let mut local_keys = T::AuthorityId::all();
|
||||
|
||||
local_keys.sort();
|
||||
|
||||
authorities.into_iter().enumerate().filter_map(move |(index, authority)| {
|
||||
local_keys
|
||||
.binary_search(&authority)
|
||||
.ok()
|
||||
.map(|location| (index as u32, local_keys[location].clone()))
|
||||
})
|
||||
}
|
||||
|
||||
fn with_heartbeat_lock<R>(
|
||||
authority_index: u32,
|
||||
session_index: SessionIndex,
|
||||
now: BlockNumberFor<T>,
|
||||
f: impl FnOnce() -> OffchainResult<T, R>,
|
||||
) -> OffchainResult<T, R> {
|
||||
let key = {
|
||||
let mut key = DB_PREFIX.to_vec();
|
||||
key.extend(authority_index.encode());
|
||||
key
|
||||
};
|
||||
let storage = StorageValueRef::persistent(&key);
|
||||
let res = storage.mutate(
|
||||
|status: Result<Option<HeartbeatStatus<BlockNumberFor<T>>>, StorageRetrievalError>| {
|
||||
// Check if there is already a lock for that particular block.
|
||||
// This means that the heartbeat has already been sent, and we are just waiting
|
||||
// for it to be included. However if it doesn't get included for INCLUDE_THRESHOLD
|
||||
// we will re-send it.
|
||||
match status {
|
||||
// we are still waiting for inclusion.
|
||||
Ok(Some(status)) if status.is_recent(session_index, now) =>
|
||||
Err(OffchainErr::WaitingForInclusion(status.sent_at)),
|
||||
// attempt to set new status
|
||||
_ => Ok(HeartbeatStatus { session_index, sent_at: now }),
|
||||
}
|
||||
},
|
||||
);
|
||||
if let Err(MutateStorageError::ValueFunctionFailed(err)) = res {
|
||||
return Err(err);
|
||||
}
|
||||
|
||||
let mut new_status = res.map_err(|_| OffchainErr::FailedToAcquireLock)?;
|
||||
|
||||
// we got the lock, let's try to send the heartbeat.
|
||||
let res = f();
|
||||
|
||||
// clear the lock in case we have failed to send transaction.
|
||||
if res.is_err() {
|
||||
new_status.sent_at = 0u32.into();
|
||||
storage.set(&new_status);
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
fn initialize_keys(keys: &[T::AuthorityId]) {
|
||||
if !keys.is_empty() {
|
||||
assert!(Keys::<T>::get().is_empty(), "Keys are already initialized!");
|
||||
let bounded_keys = <BoundedSlice<'_, _, T::MaxKeys>>::try_from(keys)
|
||||
.expect("More than the maximum number of keys provided");
|
||||
Keys::<T>::put(bounded_keys);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn set_keys(keys: Vec<T::AuthorityId>) {
|
||||
let bounded_keys = WeakBoundedVec::<_, T::MaxKeys>::try_from(keys)
|
||||
.expect("More than the maximum number of keys provided");
|
||||
Keys::<T>::put(bounded_keys);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> pezsp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
|
||||
type Public = T::AuthorityId;
|
||||
}
|
||||
|
||||
impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
|
||||
type Key = T::AuthorityId;
|
||||
|
||||
fn on_genesis_session<'a, I: 'a>(validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
||||
{
|
||||
let keys = validators.map(|x| x.1).collect::<Vec<_>>();
|
||||
Self::initialize_keys(&keys);
|
||||
}
|
||||
|
||||
fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, _queued_validators: I)
|
||||
where
|
||||
I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
|
||||
{
|
||||
// Tell the offchain worker to start making the next session's heartbeats.
|
||||
// Since we consider producing blocks as being online,
|
||||
// the heartbeat is deferred a bit to prevent spamming.
|
||||
let block_number = <pezframe_system::Pallet<T>>::block_number();
|
||||
let half_session = T::NextSessionRotation::average_session_length() / 2u32.into();
|
||||
<HeartbeatAfter<T>>::put(block_number + half_session);
|
||||
|
||||
// Remember who the authorities are for the new session.
|
||||
let keys = validators.map(|x| x.1).collect::<Vec<_>>();
|
||||
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::<T>::put(bounded_keys);
|
||||
}
|
||||
|
||||
fn on_before_session_ending() {
|
||||
let session_index = T::ValidatorSet::session_index();
|
||||
let keys = Keys::<T>::get();
|
||||
let current_validators = T::ValidatorSet::validators();
|
||||
|
||||
let offenders = current_validators
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
.filter(|(index, id)| !Self::is_online_aux(*index as u32, id))
|
||||
.filter_map(|(_, id)| {
|
||||
<T::ValidatorSet as ValidatorSetWithIdentification<T::AccountId>>::IdentificationOf::convert(
|
||||
id.clone()
|
||||
).map(|full_id| (id, full_id))
|
||||
})
|
||||
.collect::<Vec<IdentificationTuple<T>>>();
|
||||
|
||||
// Remove all received heartbeats and number of authored blocks from the
|
||||
// current session, they have already been processed and won't be needed
|
||||
// anymore.
|
||||
#[allow(deprecated)]
|
||||
ReceivedHeartbeats::<T>::remove_prefix(T::ValidatorSet::session_index(), None);
|
||||
#[allow(deprecated)]
|
||||
AuthoredBlocks::<T>::remove_prefix(T::ValidatorSet::session_index(), None);
|
||||
|
||||
if offenders.is_empty() {
|
||||
Self::deposit_event(Event::<T>::AllGood);
|
||||
} else {
|
||||
Self::deposit_event(Event::<T>::SomeOffline { offline: offenders.clone() });
|
||||
|
||||
let validator_set_count = keys.len() as u32;
|
||||
let offence = UnresponsivenessOffence { session_index, validator_set_count, offenders };
|
||||
if let Err(e) = T::ReportUnresponsiveness::report_offence(vec![], offence) {
|
||||
pezsp_runtime::print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn on_disabled(_i: u32) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
/// An offence that is filed if a validator didn't send a heartbeat message.
|
||||
#[derive(RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Clone, PartialEq, Eq))]
|
||||
pub struct UnresponsivenessOffence<Offender> {
|
||||
/// The current session index in which we report the unresponsive validators.
|
||||
///
|
||||
/// It acts as a time measure for unresponsiveness reports and effectively will always point
|
||||
/// at the end of the session.
|
||||
pub session_index: SessionIndex,
|
||||
/// The size of the validator set in current session/era.
|
||||
pub validator_set_count: u32,
|
||||
/// Authorities that were unresponsive during the current era.
|
||||
pub offenders: Vec<Offender>,
|
||||
}
|
||||
|
||||
impl<Offender: Clone> Offence<Offender> for UnresponsivenessOffence<Offender> {
|
||||
const ID: Kind = *b"im-online:offlin";
|
||||
type TimeSlot = SessionIndex;
|
||||
|
||||
fn offenders(&self) -> Vec<Offender> {
|
||||
self.offenders.clone()
|
||||
}
|
||||
|
||||
fn session_index(&self) -> SessionIndex {
|
||||
self.session_index
|
||||
}
|
||||
|
||||
fn validator_set_count(&self) -> u32 {
|
||||
self.validator_set_count
|
||||
}
|
||||
|
||||
fn time_slot(&self) -> Self::TimeSlot {
|
||||
self.session_index
|
||||
}
|
||||
|
||||
fn slash_fraction(&self, offenders: u32) -> Perbill {
|
||||
// the formula is min((3 * (k - (n / 10 + 1))) / n, 1) * 0.07
|
||||
// basically, 10% can be offline with no slash, but after that, it linearly climbs up to 7%
|
||||
// when 13/30 are offline (around 5% when 1/3 are offline).
|
||||
if let Some(threshold) = offenders.checked_sub(self.validator_set_count / 10 + 1) {
|
||||
let x = Perbill::from_rational(3 * threshold, self.validator_set_count);
|
||||
x.saturating_mul(Perbill::from_percent(7))
|
||||
} else {
|
||||
Perbill::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Storage migrations for the im-online pallet.
|
||||
|
||||
use super::*;
|
||||
use alloc::vec::Vec;
|
||||
use pezframe_support::{storage_alias, traits::OnRuntimeUpgrade};
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezframe_support::ensure;
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use pezsp_runtime::TryRuntimeError;
|
||||
|
||||
/// The log target.
|
||||
const TARGET: &str = "runtime::im-online::migration::v1";
|
||||
|
||||
/// The original data layout of the im-online pallet (`ReceivedHeartbeats` storage item).
|
||||
mod v0 {
|
||||
use super::*;
|
||||
use pezframe_support::traits::WrapperOpaque;
|
||||
|
||||
#[derive(Encode, Decode, Default)]
|
||||
pub(super) struct BoundedOpaqueNetworkState {
|
||||
/// PeerId of the local node in SCALE encoded.
|
||||
pub peer_id: Vec<u8>,
|
||||
/// List of addresses the node knows it can be reached as.
|
||||
pub external_addresses: Vec<Vec<u8>>,
|
||||
}
|
||||
|
||||
#[storage_alias]
|
||||
pub(super) type ReceivedHeartbeats<T: Config> = StorageDoubleMap<
|
||||
Pallet<T>,
|
||||
Twox64Concat,
|
||||
SessionIndex,
|
||||
Twox64Concat,
|
||||
AuthIndex,
|
||||
WrapperOpaque<BoundedOpaqueNetworkState>,
|
||||
>;
|
||||
}
|
||||
|
||||
pub mod v1 {
|
||||
use super::*;
|
||||
|
||||
/// Simple migration that replaces `ReceivedHeartbeats` values with `true`.
|
||||
pub struct Migration<T>(core::marker::PhantomData<T>);
|
||||
|
||||
impl<T: Config> OnRuntimeUpgrade for Migration<T> {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
|
||||
let count = v0::ReceivedHeartbeats::<T>::iter().count();
|
||||
log::info!(target: TARGET, "Migrating {} received heartbeats", count);
|
||||
|
||||
Ok((count as u32).encode())
|
||||
}
|
||||
|
||||
fn on_runtime_upgrade() -> Weight {
|
||||
let mut weight = T::DbWeight::get().reads(1);
|
||||
if StorageVersion::get::<Pallet<T>>() != 0 {
|
||||
log::warn!(
|
||||
target: TARGET,
|
||||
"Skipping migration because in-code storage version is not 0"
|
||||
);
|
||||
return weight;
|
||||
}
|
||||
|
||||
let heartbeats = v0::ReceivedHeartbeats::<T>::drain().collect::<Vec<_>>();
|
||||
|
||||
weight.saturating_accrue(T::DbWeight::get().reads(heartbeats.len() as u64));
|
||||
weight.saturating_accrue(T::DbWeight::get().writes(heartbeats.len() as u64));
|
||||
|
||||
for (session_index, auth_index, _) in heartbeats {
|
||||
log::trace!(
|
||||
target: TARGET,
|
||||
"Migrated received heartbeat for {:?}...",
|
||||
(session_index, auth_index)
|
||||
);
|
||||
crate::ReceivedHeartbeats::<T>::insert(session_index, auth_index, true);
|
||||
}
|
||||
|
||||
StorageVersion::new(1).put::<Pallet<T>>();
|
||||
weight.saturating_add(T::DbWeight::get().writes(1))
|
||||
}
|
||||
|
||||
#[cfg(feature = "try-runtime")]
|
||||
fn post_upgrade(state: Vec<u8>) -> DispatchResult {
|
||||
let old_received_heartbeats: u32 =
|
||||
Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed");
|
||||
let new_received_heartbeats = crate::ReceivedHeartbeats::<T>::iter().count();
|
||||
|
||||
if new_received_heartbeats != old_received_heartbeats as usize {
|
||||
log::error!(
|
||||
target: TARGET,
|
||||
"migrated {} received heartbeats, expected {}",
|
||||
new_received_heartbeats,
|
||||
old_received_heartbeats
|
||||
);
|
||||
}
|
||||
ensure!(StorageVersion::get::<Pallet<T>>() >= 1, "must upgrade");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Clears the pallet's offchain storage.
|
||||
///
|
||||
/// Must be put in `OffchainWorkerApi::offchain_worker` after
|
||||
/// the pallet was removed.
|
||||
pub fn clear_offchain_storage(validator_set_size: u32) {
|
||||
(0..validator_set_size).for_each(|idx| {
|
||||
let key = {
|
||||
let mut key = DB_PREFIX.to_vec();
|
||||
key.extend(idx.encode());
|
||||
key
|
||||
};
|
||||
pezsp_runtime::offchain::storage::StorageValueRef::persistent(&key).clear();
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "try-runtime", test))]
|
||||
mod test {
|
||||
use super::*;
|
||||
use crate::mock::{new_test_ext, Runtime as T};
|
||||
use pezframe_support::traits::WrapperOpaque;
|
||||
|
||||
#[test]
|
||||
fn migration_works() {
|
||||
new_test_ext().execute_with(|| {
|
||||
assert_eq!(StorageVersion::get::<Pallet<T>>(), 0);
|
||||
|
||||
// Insert some received heartbeats into the v0 storage:
|
||||
let current_session = <T as pallet::Config>::ValidatorSet::session_index();
|
||||
v0::ReceivedHeartbeats::<T>::insert(
|
||||
¤t_session,
|
||||
0,
|
||||
WrapperOpaque(v0::BoundedOpaqueNetworkState::default()),
|
||||
);
|
||||
v0::ReceivedHeartbeats::<T>::insert(
|
||||
¤t_session,
|
||||
1,
|
||||
WrapperOpaque(v0::BoundedOpaqueNetworkState::default()),
|
||||
);
|
||||
|
||||
// Check that the v0 storage is populated
|
||||
assert_eq!(v0::ReceivedHeartbeats::<T>::iter().count(), 2);
|
||||
assert_eq!(crate::ReceivedHeartbeats::<T>::iter().count(), 0, "V1 storage corrupted");
|
||||
|
||||
// Perform the migration
|
||||
let state = v1::Migration::<T>::pre_upgrade().unwrap();
|
||||
let _w = v1::Migration::<T>::on_runtime_upgrade();
|
||||
v1::Migration::<T>::post_upgrade(state).unwrap();
|
||||
|
||||
// Check that the v1 storage is populated and v0 storage is empty
|
||||
assert_eq!(v0::ReceivedHeartbeats::<T>::iter().count(), 0);
|
||||
assert_eq!(crate::ReceivedHeartbeats::<T>::iter().count(), 2);
|
||||
assert!(crate::ReceivedHeartbeats::<T>::contains_key(¤t_session, 0));
|
||||
assert_eq!(Some(true), crate::ReceivedHeartbeats::<T>::get(¤t_session, 1));
|
||||
|
||||
assert_eq!(StorageVersion::get::<Pallet<T>>(), 1);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Test utilities
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use pezframe_support::{
|
||||
derive_impl, parameter_types,
|
||||
traits::{ConstU32, ConstU64},
|
||||
weights::Weight,
|
||||
};
|
||||
use pezpallet_session::historical as pezpallet_session_historical;
|
||||
use pezsp_runtime::{testing::UintAuthorityId, traits::ConvertInto, BuildStorage, Permill};
|
||||
use pezsp_staking::{
|
||||
offence::{OffenceError, ReportOffence},
|
||||
SessionIndex,
|
||||
};
|
||||
|
||||
use crate as imonline;
|
||||
use crate::Config;
|
||||
|
||||
type Block = pezframe_system::mocking::MockBlock<Runtime>;
|
||||
|
||||
pezframe_support::construct_runtime!(
|
||||
pub enum Runtime {
|
||||
System: pezframe_system,
|
||||
Session: pezpallet_session,
|
||||
Balances: pezpallet_balances,
|
||||
ImOnline: imonline,
|
||||
Historical: pezpallet_session_historical,
|
||||
}
|
||||
);
|
||||
|
||||
parameter_types! {
|
||||
pub static Validators: Option<Vec<u64>> = Some(vec![
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
]);
|
||||
}
|
||||
|
||||
pub struct TestSessionManager;
|
||||
impl pezpallet_session::SessionManager<u64> for TestSessionManager {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<u64>> {
|
||||
Validators::mutate(|l| l.take())
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
impl pezpallet_session::historical::SessionManager<u64, u64> for TestSessionManager {
|
||||
fn new_session(_new_index: SessionIndex) -> Option<Vec<(u64, u64)>> {
|
||||
Validators::mutate(|l| {
|
||||
l.take().map(|validators| validators.iter().map(|v| (*v, *v)).collect())
|
||||
})
|
||||
}
|
||||
fn end_session(_: SessionIndex) {}
|
||||
fn start_session(_: SessionIndex) {}
|
||||
}
|
||||
|
||||
/// An extrinsic type used for tests.
|
||||
pub type Extrinsic = pezsp_runtime::testing::TestXt<RuntimeCall, ()>;
|
||||
type IdentificationTuple = (u64, u64);
|
||||
type Offence = crate::UnresponsivenessOffence<IdentificationTuple>;
|
||||
|
||||
parameter_types! {
|
||||
pub static Offences: Vec<(Vec<u64>, Offence)> = vec![];
|
||||
}
|
||||
|
||||
/// A mock offence report handler.
|
||||
pub struct OffenceHandler;
|
||||
impl ReportOffence<u64, IdentificationTuple, Offence> for OffenceHandler {
|
||||
fn report_offence(reporters: Vec<u64>, offence: Offence) -> Result<(), OffenceError> {
|
||||
Offences::mutate(|l| l.push((reporters, offence)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_known_offence(_offenders: &[IdentificationTuple], _time_slot: &SessionIndex) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_test_ext() -> pezsp_io::TestExternalities {
|
||||
let t = pezframe_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
|
||||
let mut result: pezsp_io::TestExternalities = t.into();
|
||||
// Set the default keys, otherwise session will discard the validator.
|
||||
result.execute_with(|| {
|
||||
for i in 1..=6 {
|
||||
System::inc_providers(&i);
|
||||
assert_eq!(Session::set_keys(RuntimeOrigin::signed(i), (i - 1).into(), vec![]), Ok(()));
|
||||
}
|
||||
});
|
||||
result
|
||||
}
|
||||
|
||||
#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
|
||||
impl pezframe_system::Config for Runtime {
|
||||
type AccountData = pezpallet_balances::AccountData<u64>;
|
||||
type Block = Block;
|
||||
}
|
||||
|
||||
#[derive_impl(pezpallet_balances::config_preludes::TestDefaultConfig)]
|
||||
impl pezpallet_balances::Config for Runtime {
|
||||
type AccountStore = System;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub const Period: u64 = 1;
|
||||
pub const Offset: u64 = 0;
|
||||
}
|
||||
|
||||
impl pezpallet_session::Config for Runtime {
|
||||
type ShouldEndSession = pezpallet_session::PeriodicSessions<Period, Offset>;
|
||||
type SessionManager =
|
||||
pezpallet_session::historical::NoteHistoricalRoot<Runtime, TestSessionManager>;
|
||||
type SessionHandler = (ImOnline,);
|
||||
type ValidatorId = u64;
|
||||
type ValidatorIdOf = ConvertInto;
|
||||
type Keys = UintAuthorityId;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type NextSessionRotation = pezpallet_session::PeriodicSessions<Period, Offset>;
|
||||
type DisablingStrategy = ();
|
||||
type WeightInfo = ();
|
||||
type Currency = Balances;
|
||||
type KeyDeposit = ();
|
||||
}
|
||||
|
||||
impl pezpallet_session::historical::Config for Runtime {
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type FullIdentification = u64;
|
||||
type FullIdentificationOf = ConvertInto;
|
||||
}
|
||||
|
||||
impl pezpallet_authorship::Config for Runtime {
|
||||
type FindAuthor = ();
|
||||
type EventHandler = ImOnline;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MockCurrentSessionProgress: Option<Option<Permill>> = None;
|
||||
}
|
||||
|
||||
parameter_types! {
|
||||
pub static MockAverageSessionLength: Option<u64> = None;
|
||||
}
|
||||
|
||||
pub struct TestNextSessionRotation;
|
||||
|
||||
impl pezframe_support::traits::EstimateNextSessionRotation<u64> for TestNextSessionRotation {
|
||||
fn average_session_length() -> u64 {
|
||||
// take the mock result if any and return it
|
||||
let mock = MockAverageSessionLength::mutate(|p| p.take());
|
||||
|
||||
mock.unwrap_or(pezpallet_session::PeriodicSessions::<Period, Offset>::average_session_length())
|
||||
}
|
||||
|
||||
fn estimate_current_session_progress(now: u64) -> (Option<Permill>, Weight) {
|
||||
let (estimate, weight) =
|
||||
pezpallet_session::PeriodicSessions::<Period, Offset>::estimate_current_session_progress(
|
||||
now,
|
||||
);
|
||||
|
||||
// take the mock result if any and return it
|
||||
let mock = MockCurrentSessionProgress::mutate(|p| p.take());
|
||||
|
||||
(mock.unwrap_or(estimate), weight)
|
||||
}
|
||||
|
||||
fn estimate_next_session_rotation(now: u64) -> (Option<u64>, Weight) {
|
||||
pezpallet_session::PeriodicSessions::<Period, Offset>::estimate_next_session_rotation(now)
|
||||
}
|
||||
}
|
||||
|
||||
impl Config for Runtime {
|
||||
type AuthorityId = UintAuthorityId;
|
||||
type RuntimeEvent = RuntimeEvent;
|
||||
type ValidatorSet = Historical;
|
||||
type NextSessionRotation = TestNextSessionRotation;
|
||||
type ReportUnresponsiveness = OffenceHandler;
|
||||
type UnsignedPriority = ConstU64<{ 1 << 20 }>;
|
||||
type WeightInfo = ();
|
||||
type MaxKeys = ConstU32<10_000>;
|
||||
type MaxPeerInHeartbeats = ConstU32<10_000>;
|
||||
}
|
||||
|
||||
impl<LocalCall> pezframe_system::offchain::CreateTransactionBase<LocalCall> for Runtime
|
||||
where
|
||||
RuntimeCall: From<LocalCall>,
|
||||
{
|
||||
type RuntimeCall = RuntimeCall;
|
||||
type Extrinsic = Extrinsic;
|
||||
}
|
||||
|
||||
impl<LocalCall> pezframe_system::offchain::CreateBare<LocalCall> for Runtime
|
||||
where
|
||||
RuntimeCall: From<LocalCall>,
|
||||
{
|
||||
fn create_bare(call: Self::RuntimeCall) -> Self::Extrinsic {
|
||||
Extrinsic::new_bare(call)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn advance_session() {
|
||||
let now = System::block_number().max(1);
|
||||
System::set_block_number(now + 1);
|
||||
Session::rotate_session();
|
||||
let keys = Session::validators().into_iter().map(UintAuthorityId).collect();
|
||||
ImOnline::set_keys(keys);
|
||||
assert_eq!(Session::current_index(), (now / Period::get()) as u32);
|
||||
}
|
||||
@@ -0,0 +1,505 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Tests for the im-online module.
|
||||
|
||||
#![cfg(test)]
|
||||
|
||||
use super::*;
|
||||
use crate::mock::*;
|
||||
use pezframe_support::{assert_noop, dispatch};
|
||||
use pezsp_core::offchain::{
|
||||
testing::{TestOffchainExt, TestTransactionPoolExt},
|
||||
OffchainDbExt, OffchainWorkerExt, TransactionPoolExt,
|
||||
};
|
||||
use pezsp_runtime::testing::UintAuthorityId;
|
||||
|
||||
#[test]
|
||||
fn test_unresponsiveness_slash_fraction() {
|
||||
let dummy_offence =
|
||||
UnresponsivenessOffence { session_index: 0, validator_set_count: 50, offenders: vec![()] };
|
||||
// A single case of unresponsiveness is not slashed.
|
||||
assert_eq!(dummy_offence.slash_fraction(1), Perbill::zero());
|
||||
|
||||
assert_eq!(
|
||||
dummy_offence.slash_fraction(5),
|
||||
Perbill::zero(), // 0%
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
dummy_offence.slash_fraction(7),
|
||||
Perbill::from_parts(4200000), // 0.42%
|
||||
);
|
||||
|
||||
// One third offline should be punished around 5%.
|
||||
assert_eq!(
|
||||
dummy_offence.slash_fraction(17),
|
||||
Perbill::from_parts(46200000), // 4.62%
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_report_offline_validators() {
|
||||
new_test_ext().execute_with(|| {
|
||||
// given
|
||||
let block = 1;
|
||||
System::set_block_number(block);
|
||||
// buffer new validators
|
||||
advance_session();
|
||||
// enact the change and buffer another one
|
||||
let validators = vec![1, 2, 3, 4, 5, 6];
|
||||
Validators::mutate(|l| *l = Some(validators.clone()));
|
||||
advance_session();
|
||||
|
||||
// when
|
||||
// we end current session and start the next one
|
||||
advance_session();
|
||||
|
||||
// then
|
||||
let offences = Offences::take();
|
||||
assert_eq!(
|
||||
offences,
|
||||
vec![(
|
||||
vec![],
|
||||
UnresponsivenessOffence {
|
||||
session_index: 2,
|
||||
validator_set_count: 3,
|
||||
offenders: vec![(1, 1), (2, 2), (3, 3),],
|
||||
}
|
||||
)]
|
||||
);
|
||||
|
||||
// should not report when heartbeat is sent
|
||||
for (idx, v) in validators.into_iter().take(4).enumerate() {
|
||||
let _ = heartbeat(block, 3, idx as u32, v.into(), Session::validators()).unwrap();
|
||||
}
|
||||
advance_session();
|
||||
|
||||
// then
|
||||
let offences = Offences::take();
|
||||
assert_eq!(
|
||||
offences,
|
||||
vec![(
|
||||
vec![],
|
||||
UnresponsivenessOffence {
|
||||
session_index: 3,
|
||||
validator_set_count: 6,
|
||||
offenders: vec![(5, 5), (6, 6),],
|
||||
}
|
||||
)]
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
fn heartbeat(
|
||||
block_number: u64,
|
||||
session_index: u32,
|
||||
authority_index: u32,
|
||||
id: UintAuthorityId,
|
||||
validators: Vec<u64>,
|
||||
) -> dispatch::DispatchResult {
|
||||
let heartbeat = Heartbeat {
|
||||
block_number,
|
||||
session_index,
|
||||
authority_index,
|
||||
validators_len: validators.len() as u32,
|
||||
};
|
||||
let signature = id.sign(&heartbeat.encode()).unwrap();
|
||||
|
||||
ImOnline::pre_dispatch(&crate::Call::heartbeat {
|
||||
heartbeat: heartbeat.clone(),
|
||||
signature: signature.clone(),
|
||||
})
|
||||
.map_err(|e| match e {
|
||||
TransactionValidityError::Invalid(InvalidTransaction::Custom(INVALID_VALIDATORS_LEN)) =>
|
||||
"invalid validators len",
|
||||
e @ _ => <&'static str>::from(e),
|
||||
})?;
|
||||
ImOnline::heartbeat(RuntimeOrigin::none(), heartbeat, signature)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_mark_online_validator_when_heartbeat_is_received() {
|
||||
new_test_ext().execute_with(|| {
|
||||
advance_session();
|
||||
// given
|
||||
Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6]));
|
||||
assert_eq!(Session::validators(), Vec::<u64>::new());
|
||||
// enact the change and buffer another one
|
||||
advance_session();
|
||||
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
|
||||
assert!(!ImOnline::is_online(0));
|
||||
assert!(!ImOnline::is_online(1));
|
||||
assert!(!ImOnline::is_online(2));
|
||||
|
||||
// when
|
||||
let _ = heartbeat(1, 2, 0, 1.into(), Session::validators()).unwrap();
|
||||
|
||||
// then
|
||||
assert!(ImOnline::is_online(0));
|
||||
assert!(!ImOnline::is_online(1));
|
||||
assert!(!ImOnline::is_online(2));
|
||||
|
||||
// and when
|
||||
let _ = heartbeat(1, 2, 2, 3.into(), Session::validators()).unwrap();
|
||||
|
||||
// then
|
||||
assert!(ImOnline::is_online(0));
|
||||
assert!(!ImOnline::is_online(1));
|
||||
assert!(ImOnline::is_online(2));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn late_heartbeat_and_invalid_keys_len_should_fail() {
|
||||
new_test_ext().execute_with(|| {
|
||||
advance_session();
|
||||
// given
|
||||
Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6]));
|
||||
assert_eq!(Session::validators(), Vec::<u64>::new());
|
||||
// enact the change and buffer another one
|
||||
advance_session();
|
||||
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
|
||||
// when
|
||||
assert_noop!(
|
||||
heartbeat(1, 3, 0, 1.into(), Session::validators()),
|
||||
"Transaction is outdated"
|
||||
);
|
||||
assert_noop!(
|
||||
heartbeat(1, 1, 0, 1.into(), Session::validators()),
|
||||
"Transaction is outdated"
|
||||
);
|
||||
|
||||
// invalid validators_len
|
||||
assert_noop!(heartbeat(1, 2, 0, 1.into(), vec![]), "invalid validators len");
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_generate_heartbeats() {
|
||||
let mut ext = new_test_ext();
|
||||
let (offchain, _state) = TestOffchainExt::new();
|
||||
let (pool, state) = TestTransactionPoolExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(TransactionPoolExt::new(pool));
|
||||
|
||||
ext.execute_with(|| {
|
||||
// given
|
||||
let block = 1;
|
||||
System::set_block_number(block);
|
||||
UintAuthorityId::set_all_keys(vec![0, 1, 2]);
|
||||
// buffer new validators
|
||||
Session::rotate_session();
|
||||
// enact the change and buffer another one
|
||||
Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6]));
|
||||
Session::rotate_session();
|
||||
|
||||
// when
|
||||
ImOnline::offchain_worker(block);
|
||||
|
||||
// then
|
||||
let transaction = state.write().transactions.pop().unwrap();
|
||||
// All validators have `0` as their session key, so we generate 2 transactions.
|
||||
assert_eq!(state.read().transactions.len(), 2);
|
||||
|
||||
// check stuff about the transaction.
|
||||
let ex: Extrinsic = Decode::decode(&mut &*transaction).unwrap();
|
||||
let heartbeat = match ex.function {
|
||||
crate::mock::RuntimeCall::ImOnline(crate::Call::heartbeat { heartbeat, .. }) =>
|
||||
heartbeat,
|
||||
e => panic!("Unexpected call: {:?}", e),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
heartbeat,
|
||||
Heartbeat {
|
||||
block_number: block,
|
||||
session_index: 2,
|
||||
authority_index: 2,
|
||||
validators_len: 3,
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_cleanup_received_heartbeats_on_session_end() {
|
||||
new_test_ext().execute_with(|| {
|
||||
advance_session();
|
||||
|
||||
Validators::mutate(|l| *l = Some(vec![1, 2, 3]));
|
||||
assert_eq!(Session::validators(), Vec::<u64>::new());
|
||||
|
||||
// enact the change and buffer another one
|
||||
advance_session();
|
||||
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
|
||||
// send an heartbeat from authority id 0 at session 2
|
||||
let _ = heartbeat(1, 2, 0, 1.into(), Session::validators()).unwrap();
|
||||
|
||||
// the heartbeat is stored
|
||||
assert!(!super::pallet::ReceivedHeartbeats::<Runtime>::get(2, 0).is_none());
|
||||
|
||||
advance_session();
|
||||
|
||||
// after the session has ended we have already processed the heartbeat
|
||||
// message, so any messages received on the previous session should have
|
||||
// been pruned.
|
||||
assert!(super::pallet::ReceivedHeartbeats::<Runtime>::get(2, 0).is_none());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_mark_online_validator_when_block_is_authored() {
|
||||
use pezpallet_authorship::EventHandler;
|
||||
|
||||
new_test_ext().execute_with(|| {
|
||||
advance_session();
|
||||
// given
|
||||
Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6]));
|
||||
assert_eq!(Session::validators(), Vec::<u64>::new());
|
||||
// enact the change and buffer another one
|
||||
advance_session();
|
||||
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
|
||||
for i in 0..3 {
|
||||
assert!(!ImOnline::is_online(i));
|
||||
}
|
||||
|
||||
// when
|
||||
ImOnline::note_author(1);
|
||||
|
||||
// then
|
||||
assert!(ImOnline::is_online(0));
|
||||
assert!(!ImOnline::is_online(1));
|
||||
assert!(!ImOnline::is_online(2));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_not_send_a_report_if_already_online() {
|
||||
use pezpallet_authorship::EventHandler;
|
||||
|
||||
let mut ext = new_test_ext();
|
||||
let (offchain, _state) = TestOffchainExt::new();
|
||||
let (pool, pool_state) = TestTransactionPoolExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(TransactionPoolExt::new(pool));
|
||||
|
||||
ext.execute_with(|| {
|
||||
advance_session();
|
||||
// given
|
||||
Validators::mutate(|l| *l = Some(vec![1, 2, 3, 4, 5, 6]));
|
||||
assert_eq!(Session::validators(), Vec::<u64>::new());
|
||||
// enact the change and buffer another one
|
||||
advance_session();
|
||||
assert_eq!(Session::current_index(), 2);
|
||||
assert_eq!(Session::validators(), vec![1, 2, 3]);
|
||||
ImOnline::note_author(2);
|
||||
ImOnline::note_author(3);
|
||||
|
||||
// when
|
||||
UintAuthorityId::set_all_keys(vec![1, 2, 3]);
|
||||
// we expect error, since the authority is already online.
|
||||
let mut res = ImOnline::send_heartbeats(4).unwrap();
|
||||
res.next().unwrap().unwrap();
|
||||
assert_eq!(res.next().unwrap().unwrap_err(), OffchainErr::AlreadyOnline(1));
|
||||
assert_eq!(res.next().unwrap().unwrap_err(), OffchainErr::AlreadyOnline(2));
|
||||
assert_eq!(res.next(), None);
|
||||
|
||||
// then
|
||||
let transaction = pool_state.write().transactions.pop().unwrap();
|
||||
// All validators have `0` as their session key, but we should only produce 1 heartbeat.
|
||||
assert_eq!(pool_state.read().transactions.len(), 0);
|
||||
// check stuff about the transaction.
|
||||
let ex: Extrinsic = Decode::decode(&mut &*transaction).unwrap();
|
||||
let heartbeat = match ex.function {
|
||||
crate::mock::RuntimeCall::ImOnline(crate::Call::heartbeat { heartbeat, .. }) =>
|
||||
heartbeat,
|
||||
e => panic!("Unexpected call: {:?}", e),
|
||||
};
|
||||
|
||||
assert_eq!(
|
||||
heartbeat,
|
||||
Heartbeat { block_number: 4, session_index: 2, authority_index: 0, validators_len: 3 }
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_missing_progress_estimates() {
|
||||
let mut ext = new_test_ext();
|
||||
let (offchain, _state) = TestOffchainExt::new();
|
||||
let (pool, state) = TestTransactionPoolExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(TransactionPoolExt::new(pool));
|
||||
|
||||
ext.execute_with(|| {
|
||||
let block = 1;
|
||||
|
||||
System::set_block_number(block);
|
||||
UintAuthorityId::set_all_keys(vec![0, 1, 2]);
|
||||
|
||||
// buffer new validators
|
||||
Session::rotate_session();
|
||||
|
||||
// enact the change and buffer another one
|
||||
Validators::mutate(|l| *l = Some(vec![0, 1, 2]));
|
||||
Session::rotate_session();
|
||||
|
||||
// we will return `None` on the next call to `estimate_current_session_progress`
|
||||
// and the offchain worker should fallback to checking `HeartbeatAfter`
|
||||
MockCurrentSessionProgress::mutate(|p| *p = Some(None));
|
||||
ImOnline::offchain_worker(block);
|
||||
|
||||
assert_eq!(state.read().transactions.len(), 3);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_handle_non_linear_session_progress() {
|
||||
// NOTE: this is the reason why we started using `EstimateNextSessionRotation` to figure out if
|
||||
// we should send a heartbeat, it's possible that between successive blocks we progress through
|
||||
// the session more than just one block increment (in BABE session length is defined in slots,
|
||||
// not block numbers).
|
||||
|
||||
let mut ext = new_test_ext();
|
||||
let (offchain, _state) = TestOffchainExt::new();
|
||||
let (pool, _) = TestTransactionPoolExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(TransactionPoolExt::new(pool));
|
||||
|
||||
ext.execute_with(|| {
|
||||
UintAuthorityId::set_all_keys(vec![0, 1, 2]);
|
||||
|
||||
// buffer new validator
|
||||
Session::rotate_session();
|
||||
|
||||
// mock the session length as being 10 blocks long,
|
||||
// enact the change and buffer another one
|
||||
Validators::mutate(|l| *l = Some(vec![0, 1, 2]));
|
||||
|
||||
// mock the session length has being 10 which should make us assume the fallback for half
|
||||
// session will be reached by block 5.
|
||||
MockAverageSessionLength::mutate(|p| *p = Some(10));
|
||||
|
||||
Session::rotate_session();
|
||||
|
||||
// if we don't have valid results for the current session progress then
|
||||
// we'll fallback to `HeartbeatAfter` and only heartbeat on block 5.
|
||||
MockCurrentSessionProgress::mutate(|p| *p = Some(None));
|
||||
assert_eq!(ImOnline::send_heartbeats(2).err(), Some(OffchainErr::TooEarly));
|
||||
|
||||
MockCurrentSessionProgress::mutate(|p| *p = Some(None));
|
||||
assert!(ImOnline::send_heartbeats(5).ok().is_some());
|
||||
|
||||
// if we have a valid current session progress then we'll heartbeat as soon
|
||||
// as we're past 80% of the session regardless of the block number
|
||||
MockCurrentSessionProgress::mutate(|p| *p = Some(Some(Permill::from_percent(81))));
|
||||
|
||||
assert!(ImOnline::send_heartbeats(2).ok().is_some());
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_does_not_heartbeat_early_in_the_session() {
|
||||
let mut ext = new_test_ext();
|
||||
let (offchain, _state) = TestOffchainExt::new();
|
||||
let (pool, _) = TestTransactionPoolExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(TransactionPoolExt::new(pool));
|
||||
|
||||
ext.execute_with(|| {
|
||||
// mock current session progress as being 5%. we only randomly start
|
||||
// heartbeating after 10% of the session has elapsed.
|
||||
MockCurrentSessionProgress::mutate(|p| *p = Some(Some(Permill::from_float(0.05))));
|
||||
assert_eq!(ImOnline::send_heartbeats(2).err(), Some(OffchainErr::TooEarly));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_probability_of_heartbeating_increases_with_session_progress() {
|
||||
let mut ext = new_test_ext();
|
||||
let (offchain, state) = TestOffchainExt::new();
|
||||
let (pool, _) = TestTransactionPoolExt::new();
|
||||
ext.register_extension(OffchainDbExt::new(offchain.clone()));
|
||||
ext.register_extension(OffchainWorkerExt::new(offchain));
|
||||
ext.register_extension(TransactionPoolExt::new(pool));
|
||||
|
||||
ext.execute_with(|| {
|
||||
let set_test = |progress, random: f64| {
|
||||
// the average session length is 100 blocks, therefore the residual
|
||||
// probability of sending a heartbeat is 1%
|
||||
MockAverageSessionLength::mutate(|p| *p = Some(100));
|
||||
MockCurrentSessionProgress::mutate(|p| *p = Some(Some(Permill::from_float(progress))));
|
||||
|
||||
let mut seed = [0u8; 32];
|
||||
let encoded = ((random * Permill::ACCURACY as f64) as u32).encode();
|
||||
seed[0..4].copy_from_slice(&encoded);
|
||||
state.write().seed = seed;
|
||||
};
|
||||
|
||||
let assert_too_early = |progress, random| {
|
||||
set_test(progress, random);
|
||||
assert_eq!(ImOnline::send_heartbeats(2).err(), Some(OffchainErr::TooEarly));
|
||||
};
|
||||
|
||||
let assert_heartbeat_ok = |progress, random| {
|
||||
set_test(progress, random);
|
||||
assert!(ImOnline::send_heartbeats(2).ok().is_some());
|
||||
};
|
||||
|
||||
assert_too_early(0.05, 1.0);
|
||||
|
||||
assert_too_early(0.1, 0.1);
|
||||
assert_too_early(0.1, 0.011);
|
||||
assert_heartbeat_ok(0.1, 0.010);
|
||||
|
||||
assert_too_early(0.4, 0.015);
|
||||
assert_heartbeat_ok(0.4, 0.014);
|
||||
|
||||
assert_too_early(0.5, 0.026);
|
||||
assert_heartbeat_ok(0.5, 0.025);
|
||||
|
||||
assert_too_early(0.6, 0.057);
|
||||
assert_heartbeat_ok(0.6, 0.056);
|
||||
|
||||
assert_too_early(0.65, 0.086);
|
||||
assert_heartbeat_ok(0.65, 0.085);
|
||||
|
||||
assert_too_early(0.7, 0.13);
|
||||
assert_heartbeat_ok(0.7, 0.12);
|
||||
|
||||
assert_too_early(0.75, 0.19);
|
||||
assert_heartbeat_ok(0.75, 0.18);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
// This file is part of Bizinikiwi.
|
||||
|
||||
// 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.
|
||||
|
||||
//! Autogenerated weights for `pezpallet_im_online`
|
||||
//!
|
||||
//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0
|
||||
//! DATE: 2025-02-21, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
|
||||
//! WORST CASE MAP SIZE: `1000000`
|
||||
//! HOSTNAME: `4563561839a5`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
|
||||
//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024`
|
||||
|
||||
// Executed Command:
|
||||
// frame-omni-bencher
|
||||
// v1
|
||||
// benchmark
|
||||
// pallet
|
||||
// --extrinsic=*
|
||||
// --runtime=target/production/wbuild/kitchensink-runtime/kitchensink_runtime.wasm
|
||||
// --pallet=pezpallet_im_online
|
||||
// --header=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/HEADER-APACHE2
|
||||
// --output=/__w/pezkuwi-sdk/pezkuwi-sdk/bizinikiwi/pezframe/im-online/src/weights.rs
|
||||
// --wasm-execution=compiled
|
||||
// --steps=50
|
||||
// --repeat=20
|
||||
// --heap-pages=4096
|
||||
// --template=bizinikiwi/.maintain/frame-weight-template.hbs
|
||||
// --no-storage-info
|
||||
// --no-min-squares
|
||||
// --no-median-slopes
|
||||
// --genesis-builder-policy=none
|
||||
// --exclude-pallets=pezpallet_xcm,pezpallet_xcm_benchmarks::fungible,pezpallet_xcm_benchmarks::generic,pezpallet_nomination_pools,pezpallet_remark,pezpallet_transaction_storage,pezpallet_election_provider_multi_block,pezpallet_election_provider_multi_block::signed,pezpallet_election_provider_multi_block::unsigned,pezpallet_election_provider_multi_block::verifier
|
||||
|
||||
#![cfg_attr(rustfmt, rustfmt_skip)]
|
||||
#![allow(unused_parens)]
|
||||
#![allow(unused_imports)]
|
||||
#![allow(missing_docs)]
|
||||
#![allow(dead_code)]
|
||||
|
||||
use pezframe_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
|
||||
use core::marker::PhantomData;
|
||||
|
||||
/// Weight functions needed for `pezpallet_im_online`.
|
||||
pub trait WeightInfo {
|
||||
fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight;
|
||||
}
|
||||
|
||||
/// Weights for `pezpallet_im_online` using the Bizinikiwi node and recommended hardware.
|
||||
pub struct BizinikiwiWeight<T>(PhantomData<T>);
|
||||
impl<T: pezframe_system::Config> WeightInfo for BizinikiwiWeight<T> {
|
||||
/// Storage: `Session::Validators` (r:1 w:0)
|
||||
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Session::CurrentIndex` (r:1 w:0)
|
||||
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `ImOnline::Keys` (r:1 w:0)
|
||||
/// Proof: `ImOnline::Keys` (`max_values`: Some(1), `max_size`: Some(320002), added: 320497, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ImOnline::ReceivedHeartbeats` (r:1 w:1)
|
||||
/// Proof: `ImOnline::ReceivedHeartbeats` (`max_values`: None, `max_size`: Some(25), added: 2500, mode: `MaxEncodedLen`)
|
||||
/// The range of component `k` is `[1, 1000]`.
|
||||
fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `6 + k * (32 ±0)`
|
||||
// Estimated: `321487 + k * (32 ±0)`
|
||||
// Minimum execution time: 55_456_000 picoseconds.
|
||||
Weight::from_parts(71_841_451, 321487)
|
||||
// Standard Error: 561
|
||||
.saturating_add(Weight::from_parts(43_801, 0).saturating_mul(k.into()))
|
||||
.saturating_add(T::DbWeight::get().reads(4_u64))
|
||||
.saturating_add(T::DbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 32).saturating_mul(k.into()))
|
||||
}
|
||||
}
|
||||
|
||||
// For backwards compatibility and tests.
|
||||
impl WeightInfo for () {
|
||||
/// Storage: `Session::Validators` (r:1 w:0)
|
||||
/// Proof: `Session::Validators` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `Session::CurrentIndex` (r:1 w:0)
|
||||
/// Proof: `Session::CurrentIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
|
||||
/// Storage: `ImOnline::Keys` (r:1 w:0)
|
||||
/// Proof: `ImOnline::Keys` (`max_values`: Some(1), `max_size`: Some(320002), added: 320497, mode: `MaxEncodedLen`)
|
||||
/// Storage: `ImOnline::ReceivedHeartbeats` (r:1 w:1)
|
||||
/// Proof: `ImOnline::ReceivedHeartbeats` (`max_values`: None, `max_size`: Some(25), added: 2500, mode: `MaxEncodedLen`)
|
||||
/// The range of component `k` is `[1, 1000]`.
|
||||
fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight {
|
||||
// Proof Size summary in bytes:
|
||||
// Measured: `6 + k * (32 ±0)`
|
||||
// Estimated: `321487 + k * (32 ±0)`
|
||||
// Minimum execution time: 55_456_000 picoseconds.
|
||||
Weight::from_parts(71_841_451, 321487)
|
||||
// Standard Error: 561
|
||||
.saturating_add(Weight::from_parts(43_801, 0).saturating_mul(k.into()))
|
||||
.saturating_add(RocksDbWeight::get().reads(4_u64))
|
||||
.saturating_add(RocksDbWeight::get().writes(1_u64))
|
||||
.saturating_add(Weight::from_parts(0, 32).saturating_mul(k.into()))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user