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,56 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-aura"
|
||||
version = "0.32.0"
|
||||
authors.workspace = true
|
||||
description = "Primitives for Aura consensus"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { optional = true, workspace = true }
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-consensus-slots = { workspace = true }
|
||||
pezsp-inherents = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-timestamp = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"async-trait",
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-slots/std",
|
||||
"pezsp-inherents/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-timestamp/std",
|
||||
]
|
||||
|
||||
# Serde support without relying on std features.
|
||||
serde = [
|
||||
"scale-info/serde",
|
||||
"pezsp-application-crypto/serde",
|
||||
"pezsp-consensus-slots/serde",
|
||||
"pezsp-runtime/serde",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-consensus-slots/runtime-benchmarks",
|
||||
"pezsp-inherents/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-timestamp/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Primitives for Aura.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,62 @@
|
||||
// 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.
|
||||
|
||||
//! Aura (Authority-Round) digests
|
||||
//!
|
||||
//! This implements the digests for AuRa, to allow the private
|
||||
//! `CompatibleDigestItem` trait to appear in public interfaces.
|
||||
|
||||
use crate::AURA_ENGINE_ID;
|
||||
use codec::{Codec, Encode};
|
||||
use pezsp_consensus_slots::Slot;
|
||||
use pezsp_runtime::generic::DigestItem;
|
||||
|
||||
/// A digest item which is usable with aura consensus.
|
||||
pub trait CompatibleDigestItem<Signature>: Sized {
|
||||
/// Construct a digest item which contains a signature on the hash.
|
||||
fn aura_seal(signature: Signature) -> Self;
|
||||
|
||||
/// If this item is an Aura seal, return the signature.
|
||||
fn as_aura_seal(&self) -> Option<Signature>;
|
||||
|
||||
/// Construct a digest item which contains the slot number
|
||||
fn aura_pre_digest(slot: Slot) -> Self;
|
||||
|
||||
/// If this item is an AuRa pre-digest, return the slot number
|
||||
fn as_aura_pre_digest(&self) -> Option<Slot>;
|
||||
}
|
||||
|
||||
impl<Signature> CompatibleDigestItem<Signature> for DigestItem
|
||||
where
|
||||
Signature: Codec,
|
||||
{
|
||||
fn aura_seal(signature: Signature) -> Self {
|
||||
DigestItem::Seal(AURA_ENGINE_ID, signature.encode())
|
||||
}
|
||||
|
||||
fn as_aura_seal(&self) -> Option<Signature> {
|
||||
self.seal_try_to(&AURA_ENGINE_ID)
|
||||
}
|
||||
|
||||
fn aura_pre_digest(slot: Slot) -> Self {
|
||||
DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())
|
||||
}
|
||||
|
||||
fn as_aura_pre_digest(&self) -> Option<Slot> {
|
||||
self.pre_runtime_try_to(&AURA_ENGINE_ID)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// 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.
|
||||
|
||||
/// Contains the inherents for the AURA module
|
||||
use pezsp_inherents::{Error, InherentData, InherentIdentifier};
|
||||
|
||||
/// The Aura inherent identifier.
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"auraslot";
|
||||
|
||||
/// The type of the Aura inherent.
|
||||
pub type InherentType = pezsp_consensus_slots::Slot;
|
||||
|
||||
/// Auxiliary trait to extract Aura inherent data.
|
||||
pub trait AuraInherentData {
|
||||
/// Get aura inherent data.
|
||||
fn aura_inherent_data(&self) -> Result<Option<InherentType>, Error>;
|
||||
/// Replace aura inherent data.
|
||||
fn aura_replace_inherent_data(&mut self, new: InherentType);
|
||||
}
|
||||
|
||||
impl AuraInherentData for InherentData {
|
||||
fn aura_inherent_data(&self) -> Result<Option<InherentType>, Error> {
|
||||
self.get_data(&INHERENT_IDENTIFIER)
|
||||
}
|
||||
|
||||
fn aura_replace_inherent_data(&mut self, new: InherentType) {
|
||||
self.replace_data(INHERENT_IDENTIFIER, &new);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the slot duration inherent data for `Aura`.
|
||||
// TODO: Remove in the future. https://github.com/pezkuwichain/kurdistan-sdk/issues/31
|
||||
#[cfg(feature = "std")]
|
||||
pub struct InherentDataProvider {
|
||||
slot: InherentType,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl InherentDataProvider {
|
||||
/// Create a new instance with the given slot.
|
||||
pub fn new(slot: InherentType) -> Self {
|
||||
Self { slot }
|
||||
}
|
||||
|
||||
/// Creates the inherent data provider by calculating the slot from the given
|
||||
/// `timestamp` and `duration`.
|
||||
pub fn from_timestamp_and_slot_duration(
|
||||
timestamp: pezsp_timestamp::Timestamp,
|
||||
slot_duration: pezsp_consensus_slots::SlotDuration,
|
||||
) -> Self {
|
||||
let slot = InherentType::from_timestamp(timestamp, slot_duration);
|
||||
|
||||
Self { slot }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl core::ops::Deref for InherentDataProvider {
|
||||
type Target = InherentType;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[async_trait::async_trait]
|
||||
impl pezsp_inherents::InherentDataProvider for InherentDataProvider {
|
||||
async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> {
|
||||
inherent_data.put_data(INHERENT_IDENTIFIER, &self.slot)
|
||||
}
|
||||
|
||||
async fn try_handle_error(
|
||||
&self,
|
||||
_: &InherentIdentifier,
|
||||
_: &[u8],
|
||||
) -> Option<Result<(), Error>> {
|
||||
// There is no error anymore
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for Aura.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Codec, Decode, Encode};
|
||||
use pezsp_runtime::ConsensusEngineId;
|
||||
|
||||
pub mod digests;
|
||||
pub mod inherents;
|
||||
|
||||
pub mod sr25519 {
|
||||
mod app_sr25519 {
|
||||
use pezsp_application_crypto::{app_crypto, key_types::AURA, sr25519};
|
||||
app_crypto!(sr25519, AURA);
|
||||
}
|
||||
|
||||
pezsp_application_crypto::with_pair! {
|
||||
/// An Aura authority keypair using S/R 25519 as its crypto.
|
||||
pub type AuthorityPair = app_sr25519::Pair;
|
||||
}
|
||||
|
||||
/// An Aura authority signature using S/R 25519 as its crypto.
|
||||
pub type AuthoritySignature = app_sr25519::Signature;
|
||||
|
||||
/// An Aura authority identifier using S/R 25519 as its crypto.
|
||||
pub type AuthorityId = app_sr25519::Public;
|
||||
}
|
||||
|
||||
pub mod ed25519 {
|
||||
mod app_ed25519 {
|
||||
use pezsp_application_crypto::{app_crypto, ed25519, key_types::AURA};
|
||||
app_crypto!(ed25519, AURA);
|
||||
}
|
||||
|
||||
pezsp_application_crypto::with_pair! {
|
||||
/// An Aura authority keypair using Ed25519 as its crypto.
|
||||
pub type AuthorityPair = app_ed25519::Pair;
|
||||
}
|
||||
|
||||
/// An Aura authority signature using Ed25519 as its crypto.
|
||||
pub type AuthoritySignature = app_ed25519::Signature;
|
||||
|
||||
/// An Aura authority identifier using Ed25519 as its crypto.
|
||||
pub type AuthorityId = app_ed25519::Public;
|
||||
}
|
||||
|
||||
pub use pezsp_consensus_slots::{Slot, SlotDuration};
|
||||
|
||||
/// The `ConsensusEngineId` of AuRa.
|
||||
pub const AURA_ENGINE_ID: ConsensusEngineId = [b'a', b'u', b'r', b'a'];
|
||||
|
||||
/// The index of an authority.
|
||||
pub type AuthorityIndex = u32;
|
||||
|
||||
/// An consensus log item for Aura.
|
||||
#[derive(Decode, Encode)]
|
||||
pub enum ConsensusLog<AuthorityId: Codec> {
|
||||
/// The authorities have changed.
|
||||
#[codec(index = 1)]
|
||||
AuthoritiesChange(Vec<AuthorityId>),
|
||||
/// Disable the authority with given index.
|
||||
#[codec(index = 2)]
|
||||
OnDisabled(AuthorityIndex),
|
||||
}
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// API necessary for block authorship with aura.
|
||||
pub trait AuraApi<AuthorityId: Codec> {
|
||||
/// Returns the slot duration for Aura.
|
||||
///
|
||||
/// Currently, only the value provided by this type at genesis will be used.
|
||||
fn slot_duration() -> SlotDuration;
|
||||
|
||||
/// Return the current set of authorities.
|
||||
fn authorities() -> Vec<AuthorityId>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-babe"
|
||||
version = "0.32.0"
|
||||
authors.workspace = true
|
||||
description = "Primitives for BABE consensus"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { optional = true, workspace = true }
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], optional = true, workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-consensus-slots = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-inherents = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-timestamp = { optional = true, workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"async-trait",
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-slots/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-inherents/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-timestamp/std",
|
||||
]
|
||||
|
||||
# Serde support without relying on std features.
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"scale-info/serde",
|
||||
"pezsp-application-crypto/serde",
|
||||
"pezsp-consensus-slots/serde",
|
||||
"pezsp-core/serde",
|
||||
"pezsp-runtime/serde",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-consensus-slots/runtime-benchmarks",
|
||||
"pezsp-inherents/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-timestamp?/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Primitives for BABE.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,224 @@
|
||||
// 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.
|
||||
|
||||
//! Private implementation details of BABE digests.
|
||||
|
||||
use super::{
|
||||
AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight,
|
||||
BabeEpochConfiguration, Randomness, Slot, BABE_ENGINE_ID,
|
||||
};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use pezsp_core::sr25519::vrf::VrfSignature;
|
||||
use pezsp_runtime::{DigestItem, RuntimeDebug};
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
/// Raw BABE primary slot assignment pre-digest.
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
pub struct PrimaryPreDigest {
|
||||
/// Authority index
|
||||
pub authority_index: super::AuthorityIndex,
|
||||
/// Slot
|
||||
pub slot: Slot,
|
||||
/// VRF signature
|
||||
pub vrf_signature: VrfSignature,
|
||||
}
|
||||
|
||||
/// BABE secondary slot assignment pre-digest.
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
pub struct SecondaryPlainPreDigest {
|
||||
/// Authority index
|
||||
///
|
||||
/// This is not strictly-speaking necessary, since the secondary slots
|
||||
/// are assigned based on slot number and epoch randomness. But including
|
||||
/// it makes things easier for higher-level users of the chain data to
|
||||
/// be aware of the author of a secondary-slot block.
|
||||
pub authority_index: super::AuthorityIndex,
|
||||
/// Slot
|
||||
pub slot: Slot,
|
||||
}
|
||||
|
||||
/// BABE secondary deterministic slot assignment with VRF outputs.
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
pub struct SecondaryVRFPreDigest {
|
||||
/// Authority index
|
||||
pub authority_index: super::AuthorityIndex,
|
||||
/// Slot
|
||||
pub slot: Slot,
|
||||
/// VRF signature
|
||||
pub vrf_signature: VrfSignature,
|
||||
}
|
||||
|
||||
/// A BABE pre-runtime digest. This contains all data required to validate a
|
||||
/// block and for the BABE runtime module. Slots can be assigned to a primary
|
||||
/// (VRF based) and to a secondary (slot number based).
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
pub enum PreDigest {
|
||||
/// A primary VRF-based slot assignment.
|
||||
#[codec(index = 1)]
|
||||
Primary(PrimaryPreDigest),
|
||||
/// A secondary deterministic slot assignment.
|
||||
#[codec(index = 2)]
|
||||
SecondaryPlain(SecondaryPlainPreDigest),
|
||||
/// A secondary deterministic slot assignment with VRF outputs.
|
||||
#[codec(index = 3)]
|
||||
SecondaryVRF(SecondaryVRFPreDigest),
|
||||
}
|
||||
|
||||
impl PreDigest {
|
||||
/// Returns the slot number of the pre digest.
|
||||
pub fn authority_index(&self) -> AuthorityIndex {
|
||||
match self {
|
||||
PreDigest::Primary(primary) => primary.authority_index,
|
||||
PreDigest::SecondaryPlain(secondary) => secondary.authority_index,
|
||||
PreDigest::SecondaryVRF(secondary) => secondary.authority_index,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the slot of the pre digest.
|
||||
pub fn slot(&self) -> Slot {
|
||||
match self {
|
||||
PreDigest::Primary(primary) => primary.slot,
|
||||
PreDigest::SecondaryPlain(secondary) => secondary.slot,
|
||||
PreDigest::SecondaryVRF(secondary) => secondary.slot,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if this pre-digest is for a primary slot assignment.
|
||||
pub fn is_primary(&self) -> bool {
|
||||
matches!(self, PreDigest::Primary(..))
|
||||
}
|
||||
|
||||
/// Returns the weight _added_ by this digest, not the cumulative weight
|
||||
/// of the chain.
|
||||
pub fn added_weight(&self) -> crate::BabeBlockWeight {
|
||||
match self {
|
||||
PreDigest::Primary(_) => 1,
|
||||
PreDigest::SecondaryPlain(_) | PreDigest::SecondaryVRF(_) => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the VRF output and proof, if they exist.
|
||||
pub fn vrf_signature(&self) -> Option<&VrfSignature> {
|
||||
match self {
|
||||
PreDigest::Primary(primary) => Some(&primary.vrf_signature),
|
||||
PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_signature),
|
||||
PreDigest::SecondaryPlain(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about the next epoch. This is broadcast in the first block
|
||||
/// of the epoch.
|
||||
#[derive(Decode, Encode, PartialEq, Eq, Clone, RuntimeDebug)]
|
||||
pub struct NextEpochDescriptor {
|
||||
/// The authorities.
|
||||
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
|
||||
|
||||
/// The value of randomness to use for the slot-assignment.
|
||||
pub randomness: Randomness,
|
||||
}
|
||||
|
||||
/// Information about the next epoch config, if changed. This is broadcast in the first
|
||||
/// block of the epoch, and applies using the same rules as `NextEpochDescriptor`.
|
||||
#[derive(
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
RuntimeDebug,
|
||||
MaxEncodedLen,
|
||||
scale_info::TypeInfo,
|
||||
)]
|
||||
pub enum NextConfigDescriptor {
|
||||
/// Version 1.
|
||||
#[codec(index = 1)]
|
||||
V1 {
|
||||
/// Value of `c` in `BabeEpochConfiguration`.
|
||||
c: (u64, u64),
|
||||
/// Value of `allowed_slots` in `BabeEpochConfiguration`.
|
||||
allowed_slots: AllowedSlots,
|
||||
},
|
||||
}
|
||||
|
||||
impl From<NextConfigDescriptor> for BabeEpochConfiguration {
|
||||
fn from(desc: NextConfigDescriptor) -> Self {
|
||||
match desc {
|
||||
NextConfigDescriptor::V1 { c, allowed_slots } => Self { c, allowed_slots },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A digest item which is usable with BABE consensus.
|
||||
pub trait CompatibleDigestItem: Sized {
|
||||
/// Construct a digest item which contains a BABE pre-digest.
|
||||
fn babe_pre_digest(seal: PreDigest) -> Self;
|
||||
|
||||
/// If this item is an BABE pre-digest, return it.
|
||||
fn as_babe_pre_digest(&self) -> Option<PreDigest>;
|
||||
|
||||
/// Construct a digest item which contains a BABE seal.
|
||||
fn babe_seal(signature: AuthoritySignature) -> Self;
|
||||
|
||||
/// If this item is a BABE signature, return the signature.
|
||||
fn as_babe_seal(&self) -> Option<AuthoritySignature>;
|
||||
|
||||
/// If this item is a BABE epoch descriptor, return it.
|
||||
fn as_next_epoch_descriptor(&self) -> Option<NextEpochDescriptor>;
|
||||
|
||||
/// If this item is a BABE config descriptor, return it.
|
||||
fn as_next_config_descriptor(&self) -> Option<NextConfigDescriptor>;
|
||||
}
|
||||
|
||||
impl CompatibleDigestItem for DigestItem {
|
||||
fn babe_pre_digest(digest: PreDigest) -> Self {
|
||||
DigestItem::PreRuntime(BABE_ENGINE_ID, digest.encode())
|
||||
}
|
||||
|
||||
fn as_babe_pre_digest(&self) -> Option<PreDigest> {
|
||||
self.pre_runtime_try_to(&BABE_ENGINE_ID)
|
||||
}
|
||||
|
||||
fn babe_seal(signature: AuthoritySignature) -> Self {
|
||||
DigestItem::Seal(BABE_ENGINE_ID, signature.encode())
|
||||
}
|
||||
|
||||
fn as_babe_seal(&self) -> Option<AuthoritySignature> {
|
||||
self.seal_try_to(&BABE_ENGINE_ID)
|
||||
}
|
||||
|
||||
fn as_next_epoch_descriptor(&self) -> Option<NextEpochDescriptor> {
|
||||
self.consensus_try_to(&BABE_ENGINE_ID)
|
||||
.and_then(|x: super::ConsensusLog| match x {
|
||||
super::ConsensusLog::NextEpochData(n) => Some(n),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
|
||||
fn as_next_config_descriptor(&self) -> Option<NextConfigDescriptor> {
|
||||
self.consensus_try_to(&BABE_ENGINE_ID)
|
||||
.and_then(|x: super::ConsensusLog| match x {
|
||||
super::ConsensusLog::NextConfigData(n) => Some(n),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// 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.
|
||||
|
||||
//! Inherents for BABE
|
||||
|
||||
use pezsp_inherents::{Error, InherentData, InherentIdentifier};
|
||||
|
||||
/// The BABE inherent identifier.
|
||||
pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"babeslot";
|
||||
|
||||
/// The type of the BABE inherent.
|
||||
pub type InherentType = pezsp_consensus_slots::Slot;
|
||||
|
||||
/// Create inherent data providers for BABE with timestamp.
|
||||
#[cfg(feature = "std")]
|
||||
pub type BabeCreateInherentDataProviders<Block> = std::sync::Arc<
|
||||
dyn pezsp_inherents::CreateInherentDataProviders<
|
||||
Block,
|
||||
(),
|
||||
InherentDataProviders = (InherentDataProvider, pezsp_timestamp::InherentDataProvider),
|
||||
>,
|
||||
>;
|
||||
|
||||
/// Auxiliary trait to extract BABE inherent data.
|
||||
pub trait BabeInherentData {
|
||||
/// Get BABE inherent data.
|
||||
fn babe_inherent_data(&self) -> Result<Option<InherentType>, Error>;
|
||||
/// Replace BABE inherent data.
|
||||
fn babe_replace_inherent_data(&mut self, new: InherentType);
|
||||
}
|
||||
|
||||
impl BabeInherentData for InherentData {
|
||||
fn babe_inherent_data(&self) -> Result<Option<InherentType>, Error> {
|
||||
self.get_data(&INHERENT_IDENTIFIER)
|
||||
}
|
||||
|
||||
fn babe_replace_inherent_data(&mut self, new: InherentType) {
|
||||
self.replace_data(INHERENT_IDENTIFIER, &new);
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides the slot duration inherent data for BABE.
|
||||
// TODO: Remove in the future. https://github.com/pezkuwichain/kurdistan-sdk/issues/31
|
||||
#[cfg(feature = "std")]
|
||||
pub struct InherentDataProvider {
|
||||
slot: InherentType,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl InherentDataProvider {
|
||||
/// Create new inherent data provider from the given `slot`.
|
||||
pub fn new(slot: InherentType) -> Self {
|
||||
Self { slot }
|
||||
}
|
||||
|
||||
/// Creates the inherent data provider by calculating the slot from the given
|
||||
/// `timestamp` and `duration`.
|
||||
pub fn from_timestamp_and_slot_duration(
|
||||
timestamp: pezsp_timestamp::Timestamp,
|
||||
slot_duration: pezsp_consensus_slots::SlotDuration,
|
||||
) -> Self {
|
||||
let slot = InherentType::from_timestamp(timestamp, slot_duration);
|
||||
|
||||
Self { slot }
|
||||
}
|
||||
|
||||
/// Returns the `slot` of this inherent data provider.
|
||||
pub fn slot(&self) -> InherentType {
|
||||
self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl core::ops::Deref for InherentDataProvider {
|
||||
type Target = InherentType;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.slot
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
#[async_trait::async_trait]
|
||||
impl pezsp_inherents::InherentDataProvider for InherentDataProvider {
|
||||
async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> {
|
||||
inherent_data.put_data(INHERENT_IDENTIFIER, &self.slot)
|
||||
}
|
||||
|
||||
async fn try_handle_error(
|
||||
&self,
|
||||
_: &InherentIdentifier,
|
||||
_: &[u8],
|
||||
) -> Option<Result<(), Error>> {
|
||||
// There is no error anymore
|
||||
None
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,447 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for BABE.
|
||||
#![deny(warnings)]
|
||||
#![forbid(unsafe_code, missing_docs, unused_variables, unused_imports)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
pub mod digests;
|
||||
pub mod inherents;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use pezsp_runtime::{traits::Header, ConsensusEngineId, RuntimeDebug};
|
||||
|
||||
use crate::digests::{NextConfigDescriptor, NextEpochDescriptor};
|
||||
|
||||
pub use pezsp_core::sr25519::vrf::{
|
||||
VrfInput, VrfPreOutput, VrfProof, VrfSignData, VrfSignature, VrfTranscript,
|
||||
};
|
||||
|
||||
/// Key type for BABE module.
|
||||
pub const KEY_TYPE: pezsp_core::crypto::KeyTypeId = pezsp_application_crypto::key_types::BABE;
|
||||
|
||||
mod app {
|
||||
use pezsp_application_crypto::{app_crypto, key_types::BABE, sr25519};
|
||||
app_crypto!(sr25519, BABE);
|
||||
}
|
||||
|
||||
/// VRF context used for per-slot randomness generation.
|
||||
pub const RANDOMNESS_VRF_CONTEXT: &[u8] = b"BabeVRFInOutContext";
|
||||
|
||||
/// VRF output length for per-slot randomness.
|
||||
pub const RANDOMNESS_LENGTH: usize = 32;
|
||||
|
||||
/// Randomness type required by BABE operations.
|
||||
pub type Randomness = [u8; RANDOMNESS_LENGTH];
|
||||
|
||||
/// A Babe authority keypair. Necessarily equivalent to the schnorrkel public key used in
|
||||
/// the main Babe module. If that ever changes, then this must, too.
|
||||
#[cfg(feature = "std")]
|
||||
pub type AuthorityPair = app::Pair;
|
||||
|
||||
/// A Babe authority signature.
|
||||
pub type AuthoritySignature = app::Signature;
|
||||
|
||||
/// A Babe authority identifier. Necessarily equivalent to the schnorrkel public key used in
|
||||
/// the main Babe module. If that ever changes, then this must, too.
|
||||
pub type AuthorityId = app::Public;
|
||||
|
||||
/// The `ConsensusEngineId` of BABE.
|
||||
pub const BABE_ENGINE_ID: ConsensusEngineId = *b"BABE";
|
||||
|
||||
/// The length of the public key
|
||||
pub const PUBLIC_KEY_LENGTH: usize = 32;
|
||||
|
||||
/// How many blocks to wait before running the median algorithm for relative time
|
||||
/// This will not vary from chain to chain as it is not dependent on slot duration
|
||||
/// or epoch length.
|
||||
pub const MEDIAN_ALGORITHM_CARDINALITY: usize = 1200; // arbitrary suggestion by w3f-research.
|
||||
|
||||
/// The index of an authority.
|
||||
pub type AuthorityIndex = u32;
|
||||
|
||||
pub use pezsp_consensus_slots::{Slot, SlotDuration};
|
||||
|
||||
/// An equivocation proof for multiple block authorships on the same slot (i.e. double vote).
|
||||
pub type EquivocationProof<H> = pezsp_consensus_slots::EquivocationProof<H, AuthorityId>;
|
||||
|
||||
/// The weight of an authority.
|
||||
// NOTE: we use a unique name for the weight to avoid conflicts with other
|
||||
// `Weight` types, since the metadata isn't able to disambiguate.
|
||||
pub type BabeAuthorityWeight = u64;
|
||||
|
||||
/// The cumulative weight of a BABE block, i.e. sum of block weights starting
|
||||
/// at this block until the genesis block.
|
||||
///
|
||||
/// Primary blocks have a weight of 1 whereas secondary blocks have a weight
|
||||
/// of 0 (regardless of whether they are plain or vrf secondary blocks).
|
||||
pub type BabeBlockWeight = u32;
|
||||
|
||||
/// Make VRF input suitable for BABE's randomness generation.
|
||||
pub fn make_vrf_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput {
|
||||
VrfInput::new(
|
||||
&BABE_ENGINE_ID,
|
||||
&[
|
||||
(b"slot number", &slot.to_le_bytes()),
|
||||
(b"current epoch", &epoch.to_le_bytes()),
|
||||
(b"chain randomness", randomness),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
/// Make VRF signing data suitable for BABE's protocol.
|
||||
pub fn make_vrf_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfSignData {
|
||||
make_vrf_transcript(randomness, slot, epoch).into()
|
||||
}
|
||||
|
||||
/// An consensus log item for BABE.
|
||||
#[derive(Decode, Encode, Clone, PartialEq, Eq)]
|
||||
pub enum ConsensusLog {
|
||||
/// The epoch has changed. This provides information about the _next_
|
||||
/// epoch - information about the _current_ epoch (i.e. the one we've just
|
||||
/// entered) should already be available earlier in the chain.
|
||||
#[codec(index = 1)]
|
||||
NextEpochData(NextEpochDescriptor),
|
||||
/// Disable the authority with given index.
|
||||
#[codec(index = 2)]
|
||||
OnDisabled(AuthorityIndex),
|
||||
/// The epoch has changed, and the epoch after the current one will
|
||||
/// enact different epoch configurations.
|
||||
#[codec(index = 3)]
|
||||
NextConfigData(NextConfigDescriptor),
|
||||
}
|
||||
|
||||
/// Configuration data used by the BABE consensus engine.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct BabeConfigurationV1 {
|
||||
/// The slot duration in milliseconds for BABE. Currently, only
|
||||
/// the value provided by this type at genesis will be used.
|
||||
///
|
||||
/// Dynamic slot duration may be supported in the future.
|
||||
pub slot_duration: u64,
|
||||
|
||||
/// The duration of epochs in slots.
|
||||
pub epoch_length: u64,
|
||||
|
||||
/// A constant value that is used in the threshold calculation formula.
|
||||
/// Expressed as a rational where the first member of the tuple is the
|
||||
/// numerator and the second is the denominator. The rational should
|
||||
/// represent a value between 0 and 1.
|
||||
/// In the threshold formula calculation, `1 - c` represents the probability
|
||||
/// of a slot being empty.
|
||||
pub c: (u64, u64),
|
||||
|
||||
/// The authorities for the genesis epoch.
|
||||
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
|
||||
|
||||
/// The randomness for the genesis epoch.
|
||||
pub randomness: Randomness,
|
||||
|
||||
/// Whether this chain should run with secondary slots, which are assigned
|
||||
/// in round-robin manner.
|
||||
pub secondary_slots: bool,
|
||||
}
|
||||
|
||||
impl From<BabeConfigurationV1> for BabeConfiguration {
|
||||
fn from(v1: BabeConfigurationV1) -> Self {
|
||||
Self {
|
||||
slot_duration: v1.slot_duration,
|
||||
epoch_length: v1.epoch_length,
|
||||
c: v1.c,
|
||||
authorities: v1.authorities,
|
||||
randomness: v1.randomness,
|
||||
allowed_slots: if v1.secondary_slots {
|
||||
AllowedSlots::PrimaryAndSecondaryPlainSlots
|
||||
} else {
|
||||
AllowedSlots::PrimarySlots
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration data used by the BABE consensus engine.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
pub struct BabeConfiguration {
|
||||
/// The slot duration in milliseconds for BABE. Currently, only
|
||||
/// the value provided by this type at genesis will be used.
|
||||
///
|
||||
/// Dynamic slot duration may be supported in the future.
|
||||
pub slot_duration: u64,
|
||||
|
||||
/// The duration of epochs in slots.
|
||||
pub epoch_length: u64,
|
||||
|
||||
/// A constant value that is used in the threshold calculation formula.
|
||||
/// Expressed as a rational where the first member of the tuple is the
|
||||
/// numerator and the second is the denominator. The rational should
|
||||
/// represent a value between 0 and 1.
|
||||
/// In the threshold formula calculation, `1 - c` represents the probability
|
||||
/// of a slot being empty.
|
||||
pub c: (u64, u64),
|
||||
|
||||
/// The authorities
|
||||
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
|
||||
|
||||
/// The randomness
|
||||
pub randomness: Randomness,
|
||||
|
||||
/// Type of allowed slots.
|
||||
pub allowed_slots: AllowedSlots,
|
||||
}
|
||||
|
||||
impl BabeConfiguration {
|
||||
/// Convenience method to get the slot duration as a `SlotDuration` value.
|
||||
pub fn slot_duration(&self) -> SlotDuration {
|
||||
SlotDuration::from_millis(self.slot_duration)
|
||||
}
|
||||
}
|
||||
|
||||
/// Types of allowed slots.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
MaxEncodedLen,
|
||||
TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum AllowedSlots {
|
||||
/// Only allow primary slots.
|
||||
PrimarySlots,
|
||||
/// Allow primary and secondary plain slots.
|
||||
PrimaryAndSecondaryPlainSlots,
|
||||
/// Allow primary and secondary VRF slots.
|
||||
PrimaryAndSecondaryVRFSlots,
|
||||
}
|
||||
|
||||
impl AllowedSlots {
|
||||
/// Whether plain secondary slots are allowed.
|
||||
pub fn is_secondary_plain_slots_allowed(&self) -> bool {
|
||||
*self == Self::PrimaryAndSecondaryPlainSlots
|
||||
}
|
||||
|
||||
/// Whether VRF secondary slots are allowed.
|
||||
pub fn is_secondary_vrf_slots_allowed(&self) -> bool {
|
||||
*self == Self::PrimaryAndSecondaryVRFSlots
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration data used by the BABE consensus engine that may change with epochs.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct BabeEpochConfiguration {
|
||||
/// A constant value that is used in the threshold calculation formula.
|
||||
/// Expressed as a rational where the first member of the tuple is the
|
||||
/// numerator and the second is the denominator. The rational should
|
||||
/// represent a value between 0 and 1.
|
||||
/// In the threshold formula calculation, `1 - c` represents the probability
|
||||
/// of a slot being empty.
|
||||
pub c: (u64, u64),
|
||||
|
||||
/// Whether this chain should run with secondary slots, which are assigned
|
||||
/// in round-robin manner.
|
||||
pub allowed_slots: AllowedSlots,
|
||||
}
|
||||
|
||||
impl Default for BabeEpochConfiguration {
|
||||
fn default() -> Self {
|
||||
Self { c: (1, 4), allowed_slots: AllowedSlots::PrimaryAndSecondaryVRFSlots }
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the equivocation proof by making sure that: both headers have
|
||||
/// different hashes, are targeting the same slot, and have valid signatures by
|
||||
/// the same authority.
|
||||
pub fn check_equivocation_proof<H>(proof: EquivocationProof<H>) -> bool
|
||||
where
|
||||
H: Header,
|
||||
{
|
||||
use digests::*;
|
||||
use pezsp_application_crypto::RuntimeAppPublic;
|
||||
|
||||
let find_pre_digest =
|
||||
|header: &H| header.digest().logs().iter().find_map(|log| log.as_babe_pre_digest());
|
||||
|
||||
let verify_seal_signature = |mut header: H, offender: &AuthorityId| {
|
||||
let seal = header.digest_mut().pop()?.as_babe_seal()?;
|
||||
let pre_hash = header.hash();
|
||||
|
||||
if !offender.verify(&pre_hash.as_ref(), &seal) {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(())
|
||||
};
|
||||
|
||||
let verify_proof = || {
|
||||
// we must have different headers for the equivocation to be valid
|
||||
if proof.first_header.hash() == proof.second_header.hash() {
|
||||
return None;
|
||||
}
|
||||
|
||||
let first_pre_digest = find_pre_digest(&proof.first_header)?;
|
||||
let second_pre_digest = find_pre_digest(&proof.second_header)?;
|
||||
|
||||
// both headers must be targeting the same slot and it must
|
||||
// be the same as the one in the proof.
|
||||
if proof.slot != first_pre_digest.slot() ||
|
||||
first_pre_digest.slot() != second_pre_digest.slot()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
// both headers must have been authored by the same authority
|
||||
if first_pre_digest.authority_index() != second_pre_digest.authority_index() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// we finally verify that the expected authority has signed both headers and
|
||||
// that the signature is valid.
|
||||
verify_seal_signature(proof.first_header, &proof.offender)?;
|
||||
verify_seal_signature(proof.second_header, &proof.offender)?;
|
||||
|
||||
Some(())
|
||||
};
|
||||
|
||||
// NOTE: we isolate the verification code into an helper function that
|
||||
// returns `Option<()>` so that we can use `?` to deal with any intermediate
|
||||
// errors and discard the proof as invalid.
|
||||
verify_proof().is_some()
|
||||
}
|
||||
|
||||
/// An opaque type used to represent the key ownership proof at the runtime API
|
||||
/// boundary. The inner value is an encoded representation of the actual key
|
||||
/// ownership proof which will be parameterized when defining the runtime. At
|
||||
/// the runtime API boundary this type is unknown and as such we keep this
|
||||
/// opaque representation, implementors of the runtime API will have to make
|
||||
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
|
||||
#[derive(Decode, Encode, PartialEq, TypeInfo)]
|
||||
pub struct OpaqueKeyOwnershipProof(Vec<u8>);
|
||||
impl OpaqueKeyOwnershipProof {
|
||||
/// Create a new `OpaqueKeyOwnershipProof` using the given encoded
|
||||
/// representation.
|
||||
pub fn new(inner: Vec<u8>) -> OpaqueKeyOwnershipProof {
|
||||
OpaqueKeyOwnershipProof(inner)
|
||||
}
|
||||
|
||||
/// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key
|
||||
/// ownership proof type.
|
||||
pub fn decode<T: Decode>(self) -> Option<T> {
|
||||
Decode::decode(&mut &self.0[..]).ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// BABE epoch information
|
||||
#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)]
|
||||
pub struct Epoch {
|
||||
/// The epoch index.
|
||||
pub epoch_index: u64,
|
||||
/// The starting slot of the epoch.
|
||||
pub start_slot: Slot,
|
||||
/// The duration of this epoch.
|
||||
pub duration: u64,
|
||||
/// The authorities and their weights.
|
||||
pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>,
|
||||
/// Randomness for this epoch.
|
||||
pub randomness: Randomness,
|
||||
/// Configuration of the epoch.
|
||||
pub config: BabeEpochConfiguration,
|
||||
}
|
||||
|
||||
/// Returns the epoch index the given slot belongs to.
|
||||
pub fn epoch_index(slot: Slot, genesis_slot: Slot, epoch_duration: u64) -> u64 {
|
||||
*slot.saturating_sub(genesis_slot) / epoch_duration
|
||||
}
|
||||
|
||||
/// Returns the first slot at the given epoch index.
|
||||
pub fn epoch_start_slot(epoch_index: u64, genesis_slot: Slot, epoch_duration: u64) -> Slot {
|
||||
// (epoch_index * epoch_duration) + genesis_slot
|
||||
|
||||
const PROOF: &str = "slot number is u64; it should relate in some way to wall clock time; \
|
||||
if u64 is not enough we should crash for safety; qed.";
|
||||
|
||||
epoch_index
|
||||
.checked_mul(epoch_duration)
|
||||
.and_then(|slot| slot.checked_add(*genesis_slot))
|
||||
.expect(PROOF)
|
||||
.into()
|
||||
}
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// API necessary for block authorship with BABE.
|
||||
#[api_version(2)]
|
||||
pub trait BabeApi {
|
||||
/// Return the configuration for BABE.
|
||||
fn configuration() -> BabeConfiguration;
|
||||
|
||||
/// Return the configuration for BABE. Version 1.
|
||||
#[changed_in(2)]
|
||||
fn configuration() -> BabeConfigurationV1;
|
||||
|
||||
/// Returns the slot that started the current epoch.
|
||||
fn current_epoch_start() -> Slot;
|
||||
|
||||
/// Returns information regarding the current epoch.
|
||||
fn current_epoch() -> Epoch;
|
||||
|
||||
/// Returns information regarding the next epoch (which was already
|
||||
/// previously announced).
|
||||
fn next_epoch() -> Epoch;
|
||||
|
||||
/// Generates a proof of key ownership for the given authority in the
|
||||
/// current epoch. An example usage of this module is coupled with the
|
||||
/// session historical module to prove that a given authority key is
|
||||
/// tied to a given staking identity during a specific session. Proofs
|
||||
/// of key ownership are necessary for submitting equivocation reports.
|
||||
/// NOTE: even though the API takes a `slot` as parameter the current
|
||||
/// implementations ignores this parameter and instead relies on this
|
||||
/// method being called at the correct block height, i.e. any point at
|
||||
/// which the epoch for the given slot is live on-chain. Future
|
||||
/// implementations will instead use indexed data through an offchain
|
||||
/// worker, not requiring older states to be available.
|
||||
fn generate_key_ownership_proof(
|
||||
slot: Slot,
|
||||
authority_id: AuthorityId,
|
||||
) -> Option<OpaqueKeyOwnershipProof>;
|
||||
|
||||
/// Submits an unsigned extrinsic to report an equivocation. The caller
|
||||
/// must provide the equivocation proof and a key ownership proof
|
||||
/// (should be obtained using `generate_key_ownership_proof`). The
|
||||
/// extrinsic will be unsigned and should only be accepted for local
|
||||
/// authorship (not to be broadcast to the network). This method returns
|
||||
/// `None` when creation of the extrinsic fails, e.g. if equivocation
|
||||
/// reporting is disabled for the given runtime (i.e. this method is
|
||||
/// hardcoded to return `None`). Only useful in an offchain context.
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
equivocation_proof: EquivocationProof<Block::Header>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()>;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-beefy"
|
||||
version = "13.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Primitives for BEEFY protocol."
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { optional = true, features = ["alloc", "derive"], workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-crypto-hashing = { workspace = true }
|
||||
pezsp-io = { workspace = true }
|
||||
pezsp-keystore = { workspace = true }
|
||||
pezsp-mmr-primitives = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
pezsp-weights = { workspace = true }
|
||||
strum = { features = ["derive"], workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
array-bytes = { workspace = true, default-features = true }
|
||||
w3f-bls = { features = ["std"], workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-crypto-hashing/std",
|
||||
"pezsp-io/std",
|
||||
"pezsp-keystore/std",
|
||||
"pezsp-mmr-primitives/std",
|
||||
"pezsp-runtime/std",
|
||||
"pezsp-weights/std",
|
||||
"strum/std",
|
||||
]
|
||||
|
||||
# Serde support without relying on std features.
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"scale-info/serde",
|
||||
"pezsp-application-crypto/serde",
|
||||
"pezsp-core/serde",
|
||||
"pezsp-runtime/serde",
|
||||
]
|
||||
|
||||
# This feature adds BLS crypto primitives. It should not be used in production since
|
||||
# the BLS implementation and interface may still be subject to significant change.
|
||||
bls-experimental = [
|
||||
"pezsp-application-crypto/bls-experimental",
|
||||
"pezsp-core/bls-experimental",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-io/runtime-benchmarks",
|
||||
"pezsp-mmr-primitives/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,588 @@
|
||||
// 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.
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, Error, Input};
|
||||
use core::cmp;
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_application_crypto::RuntimeAppPublic;
|
||||
use pezsp_runtime::traits::Hash;
|
||||
|
||||
use crate::{BeefyAuthorityId, Payload, ValidatorSet, ValidatorSetId};
|
||||
|
||||
/// A commitment signature, accompanied by the id of the validator that it belongs to.
|
||||
#[derive(Debug)]
|
||||
pub struct KnownSignature<TAuthorityId, TSignature> {
|
||||
/// The signing validator.
|
||||
pub validator_id: TAuthorityId,
|
||||
/// The signature.
|
||||
pub signature: TSignature,
|
||||
}
|
||||
|
||||
impl<TAuthorityId: Clone, TSignature: Clone> KnownSignature<&TAuthorityId, &TSignature> {
|
||||
/// Creates a `KnownSignature<TAuthorityId, TSignature>` from an
|
||||
/// `KnownSignature<&TAuthorityId, &TSignature>`.
|
||||
pub fn to_owned(&self) -> KnownSignature<TAuthorityId, TSignature> {
|
||||
KnownSignature {
|
||||
validator_id: self.validator_id.clone(),
|
||||
signature: self.signature.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A commitment signed by GRANDPA validators as part of BEEFY protocol.
|
||||
///
|
||||
/// The commitment contains a [payload](Commitment::payload) extracted from the finalized block at
|
||||
/// height [block_number](Commitment::block_number).
|
||||
/// GRANDPA validators collect signatures on commitments and a stream of such signed commitments
|
||||
/// (see [SignedCommitment]) forms the BEEFY protocol.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, TypeInfo)]
|
||||
pub struct Commitment<TBlockNumber> {
|
||||
/// A collection of payloads to be signed, see [`Payload`] for details.
|
||||
///
|
||||
/// One of the payloads should be some form of cumulative representation of the chain (think
|
||||
/// MMR root hash). Additionally one of the payloads should also contain some details that
|
||||
/// allow the light client to verify next validator set. The protocol does not enforce any
|
||||
/// particular format of this data, nor how often it should be present in commitments, however
|
||||
/// the light client has to be provided with full validator set whenever it performs the
|
||||
/// transition (i.e. importing first block with
|
||||
/// [validator_set_id](Commitment::validator_set_id) incremented).
|
||||
pub payload: Payload,
|
||||
|
||||
/// Finalized block number this commitment is for.
|
||||
///
|
||||
/// GRANDPA validators agree on a block they create a commitment for and start collecting
|
||||
/// signatures. This process is called a round.
|
||||
/// There might be multiple rounds in progress (depending on the block choice rule), however
|
||||
/// since the payload is supposed to be cumulative, it is not required to import all
|
||||
/// commitments.
|
||||
/// BEEFY light client is expected to import at least one commitment per epoch,
|
||||
/// but is free to import as many as it requires.
|
||||
pub block_number: TBlockNumber,
|
||||
|
||||
/// BEEFY validator set supposed to sign this commitment.
|
||||
///
|
||||
/// Validator set is changing once per epoch. The Light Client must be provided by details
|
||||
/// about the validator set whenever it's importing first commitment with a new
|
||||
/// `validator_set_id`. Validator set data MUST be verifiable, for instance using
|
||||
/// [payload](Commitment::payload) information.
|
||||
pub validator_set_id: ValidatorSetId,
|
||||
}
|
||||
|
||||
impl<TBlockNumber> cmp::PartialOrd for Commitment<TBlockNumber>
|
||||
where
|
||||
TBlockNumber: cmp::Ord,
|
||||
{
|
||||
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl<TBlockNumber> cmp::Ord for Commitment<TBlockNumber>
|
||||
where
|
||||
TBlockNumber: cmp::Ord,
|
||||
{
|
||||
fn cmp(&self, other: &Self) -> cmp::Ordering {
|
||||
self.validator_set_id
|
||||
.cmp(&other.validator_set_id)
|
||||
.then_with(|| self.block_number.cmp(&other.block_number))
|
||||
.then_with(|| self.payload.cmp(&other.payload))
|
||||
}
|
||||
}
|
||||
|
||||
/// A commitment with matching GRANDPA validators' signatures.
|
||||
///
|
||||
/// Note that SCALE-encoding of the structure is optimized for size efficiency over the wire,
|
||||
/// please take a look at custom [`Encode`] and [`Decode`] implementations and
|
||||
/// `CompactSignedCommitment` struct.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, TypeInfo)]
|
||||
pub struct SignedCommitment<TBlockNumber, TSignature> {
|
||||
/// The commitment signatures are collected for.
|
||||
pub commitment: Commitment<TBlockNumber>,
|
||||
/// GRANDPA validators' signatures for the commitment.
|
||||
///
|
||||
/// The length of this `Vec` must match number of validators in the current set (see
|
||||
/// [Commitment::validator_set_id]).
|
||||
pub signatures: Vec<Option<TSignature>>,
|
||||
}
|
||||
|
||||
impl<TBlockNumber: core::fmt::Debug, TSignature> core::fmt::Display
|
||||
for SignedCommitment<TBlockNumber, TSignature>
|
||||
{
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
let signatures_count = self.signatures.iter().filter(|s| s.is_some()).count();
|
||||
write!(
|
||||
f,
|
||||
"SignedCommitment(commitment: {:?}, signatures_count: {})",
|
||||
self.commitment, signatures_count
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TBlockNumber, TSignature> SignedCommitment<TBlockNumber, TSignature> {
|
||||
/// Return the number of collected signatures.
|
||||
pub fn signature_count(&self) -> usize {
|
||||
self.signatures.iter().filter(|x| x.is_some()).count()
|
||||
}
|
||||
|
||||
/// Verify all the commitment signatures against the validator set that was active
|
||||
/// at the block where the commitment was generated.
|
||||
///
|
||||
/// Returns the valid validator-signature pairs if the commitment can be verified.
|
||||
pub fn verify_signatures<'a, TAuthorityId, MsgHash>(
|
||||
&'a self,
|
||||
target_number: TBlockNumber,
|
||||
validator_set: &'a ValidatorSet<TAuthorityId>,
|
||||
) -> Result<Vec<KnownSignature<&'a TAuthorityId, &'a TSignature>>, u32>
|
||||
where
|
||||
TBlockNumber: Clone + Encode + PartialEq,
|
||||
TAuthorityId: RuntimeAppPublic<Signature = TSignature> + BeefyAuthorityId<MsgHash>,
|
||||
MsgHash: Hash,
|
||||
{
|
||||
if self.signatures.len() != validator_set.len() ||
|
||||
self.commitment.validator_set_id != validator_set.id() ||
|
||||
self.commitment.block_number != target_number
|
||||
{
|
||||
return Err(0);
|
||||
}
|
||||
|
||||
// Arrangement of signatures in the commitment should be in the same order
|
||||
// as validators for that set.
|
||||
let encoded_commitment = self.commitment.encode();
|
||||
let signatories: Vec<_> = validator_set
|
||||
.validators()
|
||||
.into_iter()
|
||||
.zip(self.signatures.iter())
|
||||
.filter_map(|(id, maybe_signature)| {
|
||||
let signature = maybe_signature.as_ref()?;
|
||||
match BeefyAuthorityId::verify(id, signature, &encoded_commitment) {
|
||||
true => Some(KnownSignature { validator_id: id, signature }),
|
||||
false => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
Ok(signatories)
|
||||
}
|
||||
}
|
||||
|
||||
/// Type to be used to denote placement of signatures
|
||||
type BitField = Vec<u8>;
|
||||
/// Compress 8 bit values into a single u8 Byte
|
||||
const CONTAINER_BIT_SIZE: usize = 8;
|
||||
|
||||
/// Compressed representation of [`SignedCommitment`], used for encoding efficiency.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Encode, Decode)]
|
||||
struct CompactSignedCommitment<TBlockNumber, TSignature> {
|
||||
/// The commitment, unchanged compared to regular [`SignedCommitment`].
|
||||
commitment: Commitment<TBlockNumber>,
|
||||
/// A bitfield representing presence of a signature coming from a validator at some index.
|
||||
///
|
||||
/// The bit at index `0` is set to `1` in case we have a signature coming from a validator at
|
||||
/// index `0` in in the original validator set. In case the [`SignedCommitment`] does not
|
||||
/// contain that signature the `bit` will be set to `0`. Bits are packed into `Vec<u8>`
|
||||
signatures_from: BitField,
|
||||
/// Number of validators in the Validator Set and hence number of significant bits in the
|
||||
/// [`signatures_from`] collection.
|
||||
///
|
||||
/// Note this might be smaller than the size of `signatures_compact` in case some signatures
|
||||
/// are missing.
|
||||
validator_set_len: u32,
|
||||
/// A `Vec` containing all `Signature`s present in the original [`SignedCommitment`].
|
||||
///
|
||||
/// Note that in order to associate a `Signature` from this `Vec` with a validator, one needs
|
||||
/// to look at the `signatures_from` bitfield, since some validators might have not produced a
|
||||
/// signature.
|
||||
signatures_compact: Vec<TSignature>,
|
||||
}
|
||||
|
||||
impl<'a, TBlockNumber: Clone, TSignature> CompactSignedCommitment<TBlockNumber, &'a TSignature> {
|
||||
/// Packs a `SignedCommitment` into the compressed `CompactSignedCommitment` format for
|
||||
/// efficient network transport.
|
||||
fn pack(signed_commitment: &'a SignedCommitment<TBlockNumber, TSignature>) -> Self {
|
||||
let SignedCommitment { commitment, signatures } = signed_commitment;
|
||||
let validator_set_len = signatures.len() as u32;
|
||||
|
||||
let signatures_compact: Vec<&'a TSignature> =
|
||||
signatures.iter().filter_map(|x| x.as_ref()).collect();
|
||||
let bits = {
|
||||
let mut bits: Vec<u8> =
|
||||
signatures.iter().map(|x| if x.is_some() { 1 } else { 0 }).collect();
|
||||
// Resize with excess bits for placement purposes
|
||||
let excess_bits_len =
|
||||
CONTAINER_BIT_SIZE - (validator_set_len as usize % CONTAINER_BIT_SIZE);
|
||||
bits.resize(bits.len() + excess_bits_len, 0);
|
||||
bits
|
||||
};
|
||||
|
||||
let mut signatures_from: BitField = vec![];
|
||||
let chunks = bits.chunks(CONTAINER_BIT_SIZE);
|
||||
for chunk in chunks {
|
||||
let mut iter = chunk.iter().copied();
|
||||
let mut v = iter.next().unwrap() as u8;
|
||||
|
||||
for bit in iter {
|
||||
v <<= 1;
|
||||
v |= bit as u8;
|
||||
}
|
||||
|
||||
signatures_from.push(v);
|
||||
}
|
||||
|
||||
Self {
|
||||
commitment: commitment.clone(),
|
||||
signatures_from,
|
||||
validator_set_len,
|
||||
signatures_compact,
|
||||
}
|
||||
}
|
||||
|
||||
/// Unpacks a `CompactSignedCommitment` into the uncompressed `SignedCommitment` form.
|
||||
fn unpack(
|
||||
temporary_signatures: CompactSignedCommitment<TBlockNumber, TSignature>,
|
||||
) -> SignedCommitment<TBlockNumber, TSignature> {
|
||||
let CompactSignedCommitment {
|
||||
commitment,
|
||||
signatures_from,
|
||||
validator_set_len,
|
||||
signatures_compact,
|
||||
} = temporary_signatures;
|
||||
let mut bits: Vec<u8> = vec![];
|
||||
|
||||
for block in signatures_from {
|
||||
for bit in 0..CONTAINER_BIT_SIZE {
|
||||
bits.push((block >> (CONTAINER_BIT_SIZE - bit - 1)) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
bits.truncate(validator_set_len as usize);
|
||||
|
||||
let mut next_signature = signatures_compact.into_iter();
|
||||
let signatures: Vec<Option<TSignature>> = bits
|
||||
.iter()
|
||||
.map(|&x| if x == 1 { next_signature.next() } else { None })
|
||||
.collect();
|
||||
|
||||
SignedCommitment { commitment, signatures }
|
||||
}
|
||||
}
|
||||
|
||||
impl<TBlockNumber, TSignature> Encode for SignedCommitment<TBlockNumber, TSignature>
|
||||
where
|
||||
TBlockNumber: Encode + Clone,
|
||||
TSignature: Encode,
|
||||
{
|
||||
fn using_encoded<R, F: FnOnce(&[u8]) -> R>(&self, f: F) -> R {
|
||||
let temp = CompactSignedCommitment::pack(self);
|
||||
temp.using_encoded(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<TBlockNumber, TSignature> Decode for SignedCommitment<TBlockNumber, TSignature>
|
||||
where
|
||||
TBlockNumber: Decode + Clone,
|
||||
TSignature: Decode,
|
||||
{
|
||||
fn decode<I: Input>(input: &mut I) -> Result<Self, Error> {
|
||||
let temp = CompactSignedCommitment::decode(input)?;
|
||||
Ok(CompactSignedCommitment::unpack(temp))
|
||||
}
|
||||
}
|
||||
|
||||
/// A [SignedCommitment] with a version number.
|
||||
///
|
||||
/// This variant will be appended to the block justifications for the block
|
||||
/// for which the signed commitment has been generated.
|
||||
///
|
||||
/// Note that this enum is subject to change in the future with introduction
|
||||
/// of additional cryptographic primitives to BEEFY.
|
||||
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
|
||||
pub enum VersionedFinalityProof<N, S> {
|
||||
#[codec(index = 1)]
|
||||
/// Current active version
|
||||
V1(SignedCommitment<N, S>),
|
||||
}
|
||||
|
||||
impl<N: core::fmt::Debug, S> core::fmt::Display for VersionedFinalityProof<N, S> {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
match self {
|
||||
VersionedFinalityProof::V1(sc) => write!(f, "VersionedFinalityProof::V1({})", sc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N, S> From<SignedCommitment<N, S>> for VersionedFinalityProof<N, S> {
|
||||
fn from(commitment: SignedCommitment<N, S>) -> Self {
|
||||
VersionedFinalityProof::V1(commitment)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{ecdsa_crypto::Signature as EcdsaSignature, known_payloads};
|
||||
use codec::Decode;
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_crypto_hashing::keccak_256;
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
use crate::bls_crypto::Signature as BlsSignature;
|
||||
|
||||
type TestCommitment = Commitment<u128>;
|
||||
|
||||
const LARGE_RAW_COMMITMENT: &[u8] = include_bytes!("../test-res/large-raw-commitment");
|
||||
|
||||
// Types for bls-less commitment
|
||||
type TestEcdsaSignedCommitment = SignedCommitment<u128, EcdsaSignature>;
|
||||
type TestVersionedFinalityProof = VersionedFinalityProof<u128, EcdsaSignature>;
|
||||
|
||||
// Types for commitment supporting aggregatable bls signature
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
|
||||
struct BlsAggregatableSignature(BlsSignature);
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
|
||||
struct EcdsaBlsSignaturePair(EcdsaSignature, BlsSignature);
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
type TestBlsSignedCommitment = SignedCommitment<u128, EcdsaBlsSignaturePair>;
|
||||
|
||||
// Generates mock aggregatable ecdsa signature for generating test commitment
|
||||
// BLS signatures
|
||||
fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
|
||||
let alice = pezsp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
|
||||
|
||||
let msg = keccak_256(b"This is the first message");
|
||||
let sig1 = alice.sign_prehashed(&msg);
|
||||
|
||||
let msg = keccak_256(b"This is the second message");
|
||||
let sig2 = alice.sign_prehashed(&msg);
|
||||
|
||||
(sig1.into(), sig2.into())
|
||||
}
|
||||
|
||||
// Generates mock aggregatable bls signature for generating test commitment
|
||||
// BLS signatures
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
|
||||
let alice = pezsp_core::bls::Pair::from_string("//Alice", None).unwrap();
|
||||
|
||||
let msg = b"This is the first message";
|
||||
let sig1 = alice.sign(msg);
|
||||
|
||||
let msg = b"This is the second message";
|
||||
let sig2 = alice.sign(msg);
|
||||
|
||||
(sig1.into(), sig2.into())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commitment_encode_decode() {
|
||||
// given
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
// when
|
||||
let encoded = codec::Encode::encode(&commitment);
|
||||
let decoded = TestCommitment::decode(&mut &*encoded);
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, Ok(commitment));
|
||||
assert_eq!(
|
||||
encoded,
|
||||
array_bytes::hex2bytes_unchecked(
|
||||
"046d68343048656c6c6f20576f726c6421050000000000000000000000000000000000000000000000"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_commitment_encode_decode_ecdsa() {
|
||||
// given
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let ecdsa_sigs = mock_ecdsa_signatures();
|
||||
|
||||
let ecdsa_signed = SignedCommitment {
|
||||
commitment: commitment.clone(),
|
||||
signatures: vec![None, None, Some(ecdsa_sigs.0.clone()), Some(ecdsa_sigs.1.clone())],
|
||||
};
|
||||
|
||||
// when
|
||||
let encoded = codec::Encode::encode(&ecdsa_signed);
|
||||
let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, Ok(ecdsa_signed));
|
||||
assert_eq!(
|
||||
encoded,
|
||||
array_bytes::hex2bytes_unchecked(
|
||||
"\
|
||||
046d68343048656c6c6f20576f726c64210500000000000000000000000000000000000000000000000\
|
||||
4300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746c\
|
||||
c321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba012d6e1f8105c337a86cdd9aa\
|
||||
acdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6\
|
||||
a0046395a71681be3d0c2a00\
|
||||
"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn signed_commitment_encode_decode_ecdsa_n_bls() {
|
||||
// given
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let ecdsa_sigs = mock_ecdsa_signatures();
|
||||
|
||||
//including bls signature
|
||||
let bls_signed_msgs = mock_bls_signatures();
|
||||
|
||||
let ecdsa_and_bls_signed = SignedCommitment {
|
||||
commitment,
|
||||
signatures: vec![
|
||||
None,
|
||||
None,
|
||||
Some(EcdsaBlsSignaturePair(ecdsa_sigs.0, bls_signed_msgs.0)),
|
||||
Some(EcdsaBlsSignaturePair(ecdsa_sigs.1, bls_signed_msgs.1)),
|
||||
],
|
||||
};
|
||||
|
||||
//when
|
||||
let encoded = codec::Encode::encode(&ecdsa_and_bls_signed);
|
||||
let decoded = TestBlsSignedCommitment::decode(&mut &*encoded);
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, Ok(ecdsa_and_bls_signed));
|
||||
assert_eq!(
|
||||
encoded,
|
||||
array_bytes::hex2bytes_unchecked(
|
||||
"046d68343048656c6c6f20576f726c642105000000000000000000000000000000000000000000000004300400000008558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba0182022df4689ef25499205f7154a1a62eb2d6d5c4a3657efed321e2c277998130d1b01a264c928afb79534cb0fa9dcf79f67ed4e6bf2de576bb936146f2fa60fa56b8651677cc764ea4fe317c62294c2a0c5966e439653eed0572fded5e2461c888518e0769718dcce9f3ff612fb89d262d6e1f8105c337a86cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487bca2324b6a0046395a71681be3d0c2a00a90973bea76fac3a4e2d76a25ec3926d6a5a20aacee15ec0756cd268088ed5612b67b4a49349cee70bc1185078d17c7f7df9d944e8be30022d9680d0437c4ba4600d74050692e8ee9b96e37df2a39d1cb4b4af4b6a058342dd9e8c7481a3a0b8975ad8614c953e950253aa327698d842"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn signed_commitment_count_signatures() {
|
||||
// given
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let sigs = mock_ecdsa_signatures();
|
||||
|
||||
let mut signed = SignedCommitment {
|
||||
commitment,
|
||||
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
|
||||
};
|
||||
assert_eq!(signed.signature_count(), 2);
|
||||
|
||||
// when
|
||||
signed.signatures[2] = None;
|
||||
|
||||
// then
|
||||
assert_eq!(signed.signature_count(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn commitment_ordering() {
|
||||
fn commitment(
|
||||
block_number: u128,
|
||||
validator_set_id: crate::ValidatorSetId,
|
||||
) -> TestCommitment {
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
Commitment { payload, block_number, validator_set_id }
|
||||
}
|
||||
|
||||
// given
|
||||
let a = commitment(1, 0);
|
||||
let b = commitment(2, 1);
|
||||
let c = commitment(10, 0);
|
||||
let d = commitment(10, 1);
|
||||
|
||||
// then
|
||||
assert!(a < b);
|
||||
assert!(a < c);
|
||||
assert!(c < b);
|
||||
assert!(c < d);
|
||||
assert!(b < d);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn versioned_commitment_encode_decode() {
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let sigs = mock_ecdsa_signatures();
|
||||
|
||||
let signed = SignedCommitment {
|
||||
commitment,
|
||||
signatures: vec![None, None, Some(sigs.0), Some(sigs.1)],
|
||||
};
|
||||
|
||||
let versioned = TestVersionedFinalityProof::V1(signed.clone());
|
||||
|
||||
let encoded = codec::Encode::encode(&versioned);
|
||||
|
||||
assert_eq!(1, encoded[0]);
|
||||
assert_eq!(encoded[1..], codec::Encode::encode(&signed));
|
||||
|
||||
let decoded = TestVersionedFinalityProof::decode(&mut &*encoded);
|
||||
|
||||
assert_eq!(decoded, Ok(versioned));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn large_signed_commitment_encode_decode() {
|
||||
// given
|
||||
let payload =
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode());
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let sigs = mock_ecdsa_signatures();
|
||||
|
||||
let signatures: Vec<Option<_>> = (0..1024)
|
||||
.into_iter()
|
||||
.map(|x| if x < 340 { None } else { Some(sigs.0.clone()) })
|
||||
.collect();
|
||||
let signed = SignedCommitment { commitment, signatures };
|
||||
|
||||
// when
|
||||
let encoded = codec::Encode::encode(&signed);
|
||||
let decoded = TestEcdsaSignedCommitment::decode(&mut &*encoded);
|
||||
|
||||
// then
|
||||
assert_eq!(decoded, Ok(signed));
|
||||
assert_eq!(encoded, LARGE_RAW_COMMITMENT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,649 @@
|
||||
// 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.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![warn(missing_docs)]
|
||||
|
||||
//! Primitives for BEEFY protocol.
|
||||
//!
|
||||
//! The crate contains shared data types used by BEEFY protocol and documentation (in a form of
|
||||
//! code) for building a BEEFY light client.
|
||||
//!
|
||||
//! BEEFY is a gadget that runs alongside another finality gadget (for instance GRANDPA).
|
||||
//! For simplicity (and the initially intended use case) the documentation says GRANDPA in places
|
||||
//! where a more abstract "Finality Gadget" term could be used, but there is no reason why BEEFY
|
||||
//! wouldn't run with some other finality scheme.
|
||||
//! BEEFY validator set is supposed to be tracking the Finality Gadget validator set, but note that
|
||||
//! it will use a different set of keys. For Pezkuwi use case we plan to use `secp256k1` for BEEFY,
|
||||
//! while GRANDPA uses `ed25519`.
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
mod commitment;
|
||||
mod payload;
|
||||
|
||||
pub mod mmr;
|
||||
pub mod witness;
|
||||
|
||||
/// Test utilities
|
||||
#[cfg(feature = "std")]
|
||||
pub mod test_utils;
|
||||
|
||||
pub use commitment::{Commitment, KnownSignature, SignedCommitment, VersionedFinalityProof};
|
||||
pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider};
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Codec, Decode, DecodeWithMemTracking, Encode};
|
||||
use core::fmt::{Debug, Display};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_application_crypto::{AppPublic, RuntimeAppPublic};
|
||||
use pezsp_core::H256;
|
||||
use pezsp_runtime::{
|
||||
traits::{Hash, Header as HeaderT, Keccak256, NumberFor},
|
||||
OpaqueValue,
|
||||
};
|
||||
use pezsp_weights::Weight;
|
||||
|
||||
/// Key type for BEEFY module.
|
||||
pub const KEY_TYPE: pezsp_core::crypto::KeyTypeId = pezsp_application_crypto::key_types::BEEFY;
|
||||
|
||||
/// Trait representing BEEFY authority id, including custom signature verification.
|
||||
///
|
||||
/// Accepts custom hashing fn for the message and custom convertor fn for the signer.
|
||||
pub trait BeefyAuthorityId<MsgHash: Hash>: RuntimeAppPublic {
|
||||
/// Verify a signature.
|
||||
///
|
||||
/// Return `true` if signature over `msg` is valid for this id.
|
||||
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool;
|
||||
}
|
||||
|
||||
/// Hasher used for BEEFY signatures.
|
||||
pub type BeefySignatureHasher = pezsp_runtime::traits::Keccak256;
|
||||
|
||||
/// A trait bound which lists all traits which are required to be implemented by
|
||||
/// a BEEFY AuthorityId type in order to be able to be used in BEEFY Keystore
|
||||
pub trait AuthorityIdBound:
|
||||
Ord
|
||||
+ AppPublic
|
||||
+ Display
|
||||
+ BeefyAuthorityId<BeefySignatureHasher, Signature = Self::BoundedSignature>
|
||||
{
|
||||
/// Necessary bounds on the Signature associated with the AuthorityId
|
||||
type BoundedSignature: Debug + Eq + PartialEq + Clone + TypeInfo + Codec + Send + Sync;
|
||||
}
|
||||
|
||||
/// BEEFY cryptographic types for ECDSA crypto
|
||||
///
|
||||
/// This module basically introduces four crypto types:
|
||||
/// - `ecdsa_crypto::Pair`
|
||||
/// - `ecdsa_crypto::Public`
|
||||
/// - `ecdsa_crypto::Signature`
|
||||
/// - `ecdsa_crypto::AuthorityId`
|
||||
///
|
||||
/// Your code should use the above types as concrete types for all crypto related
|
||||
/// functionality.
|
||||
pub mod ecdsa_crypto {
|
||||
use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
|
||||
use pezsp_application_crypto::{app_crypto, ecdsa};
|
||||
use pezsp_core::crypto::Wraps;
|
||||
|
||||
app_crypto!(ecdsa, KEY_TYPE);
|
||||
|
||||
/// Identity of a BEEFY authority using ECDSA as its crypto.
|
||||
pub type AuthorityId = Public;
|
||||
|
||||
/// Signature for a BEEFY authority using ECDSA as its crypto.
|
||||
pub type AuthoritySignature = Signature;
|
||||
|
||||
impl<MsgHash: Hash> BeefyAuthorityId<MsgHash> for AuthorityId
|
||||
where
|
||||
<MsgHash as Hash>::Output: Into<[u8; 32]>,
|
||||
{
|
||||
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
|
||||
let msg_hash = <MsgHash as Hash>::hash(msg).into();
|
||||
match pezsp_io::crypto::secp256k1_ecdsa_recover_compressed(
|
||||
signature.as_inner_ref().as_ref(),
|
||||
&msg_hash,
|
||||
) {
|
||||
Ok(raw_pubkey) => raw_pubkey.as_ref() == AsRef::<[u8]>::as_ref(self),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
impl AuthorityIdBound for AuthorityId {
|
||||
type BoundedSignature = Signature;
|
||||
}
|
||||
}
|
||||
|
||||
/// BEEFY cryptographic types for BLS crypto
|
||||
///
|
||||
/// This module basically introduces four crypto types:
|
||||
/// - `bls_crypto::Pair`
|
||||
/// - `bls_crypto::Public`
|
||||
/// - `bls_crypto::Signature`
|
||||
/// - `bls_crypto::AuthorityId`
|
||||
///
|
||||
/// Your code should use the above types as concrete types for all crypto related
|
||||
/// functionality.
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
pub mod bls_crypto {
|
||||
use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
|
||||
use pezsp_application_crypto::{app_crypto, bls381};
|
||||
use pezsp_core::{bls381::Pair as BlsPair, crypto::Wraps, Pair as _};
|
||||
|
||||
app_crypto!(bls381, KEY_TYPE);
|
||||
|
||||
/// Identity of a BEEFY authority using BLS as its crypto.
|
||||
pub type AuthorityId = Public;
|
||||
|
||||
/// Signature for a BEEFY authority using BLS as its crypto.
|
||||
pub type AuthoritySignature = Signature;
|
||||
|
||||
impl<MsgHash: Hash> BeefyAuthorityId<MsgHash> for AuthorityId
|
||||
where
|
||||
<MsgHash as Hash>::Output: Into<[u8; 32]>,
|
||||
{
|
||||
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
|
||||
// `w3f-bls` library uses IETF hashing standard and as such does not expose
|
||||
// a choice of hash-to-field function.
|
||||
// We are directly calling into the library to avoid introducing new host call.
|
||||
// and because BeefyAuthorityId::verify is being called in the runtime so we don't have
|
||||
|
||||
BlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref())
|
||||
}
|
||||
}
|
||||
impl AuthorityIdBound for AuthorityId {
|
||||
type BoundedSignature = Signature;
|
||||
}
|
||||
}
|
||||
|
||||
/// BEEFY cryptographic types for (ECDSA,BLS) crypto pair
|
||||
///
|
||||
/// This module basically introduces four crypto types:
|
||||
/// - `ecdsa_bls_crypto::Pair`
|
||||
/// - `ecdsa_bls_crypto::Public`
|
||||
/// - `ecdsa_bls_crypto::Signature`
|
||||
/// - `ecdsa_bls_crypto::AuthorityId`
|
||||
///
|
||||
/// Your code should use the above types as concrete types for all crypto related
|
||||
/// functionality.
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
pub mod ecdsa_bls_crypto {
|
||||
use super::{AuthorityIdBound, BeefyAuthorityId, Hash, RuntimeAppPublic, KEY_TYPE};
|
||||
use pezsp_application_crypto::{app_crypto, ecdsa_bls381};
|
||||
use pezsp_core::{crypto::Wraps, ecdsa_bls381::Pair as EcdsaBlsPair};
|
||||
|
||||
app_crypto!(ecdsa_bls381, KEY_TYPE);
|
||||
|
||||
/// Identity of a BEEFY authority using (ECDSA,BLS) as its crypto.
|
||||
pub type AuthorityId = Public;
|
||||
|
||||
/// Signature for a BEEFY authority using (ECDSA,BLS) as its crypto.
|
||||
pub type AuthoritySignature = Signature;
|
||||
|
||||
impl<H> BeefyAuthorityId<H> for AuthorityId
|
||||
where
|
||||
H: Hash,
|
||||
H::Output: Into<[u8; 32]>,
|
||||
{
|
||||
fn verify(&self, signature: &<Self as RuntimeAppPublic>::Signature, msg: &[u8]) -> bool {
|
||||
// We can not simply call
|
||||
// `EcdsaBlsPair::verify(signature.as_inner_ref(), msg, self.as_inner_ref())`
|
||||
// because that invokes ECDSA default verification which performs Blake2b hash
|
||||
// which we don't want. This is because ECDSA signatures are meant to be verified
|
||||
// on Ethereum network where Keccak hasher is significantly cheaper than Blake2b.
|
||||
// See Figure 3 of [OnSc21](https://www.scitepress.org/Papers/2021/106066/106066.pdf)
|
||||
// for comparison.
|
||||
EcdsaBlsPair::verify_with_hasher::<H>(
|
||||
signature.as_inner_ref(),
|
||||
msg,
|
||||
self.as_inner_ref(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl AuthorityIdBound for AuthorityId {
|
||||
type BoundedSignature = Signature;
|
||||
}
|
||||
}
|
||||
|
||||
/// The `ConsensusEngineId` of BEEFY.
|
||||
pub const BEEFY_ENGINE_ID: pezsp_runtime::ConsensusEngineId = *b"BEEF";
|
||||
|
||||
/// Authority set id starts with zero at BEEFY pallet genesis.
|
||||
pub const GENESIS_AUTHORITY_SET_ID: u64 = 0;
|
||||
|
||||
/// A typedef for validator set id.
|
||||
pub type ValidatorSetId = u64;
|
||||
|
||||
/// A set of BEEFY authorities, a.k.a. validators.
|
||||
#[derive(Decode, Encode, Debug, PartialEq, Clone, TypeInfo)]
|
||||
pub struct ValidatorSet<AuthorityId> {
|
||||
/// Public keys of the validator set elements
|
||||
validators: Vec<AuthorityId>,
|
||||
/// Identifier of the validator set
|
||||
id: ValidatorSetId,
|
||||
}
|
||||
|
||||
impl<AuthorityId> ValidatorSet<AuthorityId> {
|
||||
/// Return a validator set with the given validators and set id.
|
||||
pub fn new<I>(validators: I, id: ValidatorSetId) -> Option<Self>
|
||||
where
|
||||
I: IntoIterator<Item = AuthorityId>,
|
||||
{
|
||||
let validators: Vec<AuthorityId> = validators.into_iter().collect();
|
||||
if validators.is_empty() {
|
||||
// No validators; the set would be empty.
|
||||
None
|
||||
} else {
|
||||
Some(Self { validators, id })
|
||||
}
|
||||
}
|
||||
|
||||
/// Return a reference to the vec of validators.
|
||||
pub fn validators(&self) -> &[AuthorityId] {
|
||||
&self.validators
|
||||
}
|
||||
|
||||
/// Return the validator set id.
|
||||
pub fn id(&self) -> ValidatorSetId {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Return the number of validators in the set.
|
||||
pub fn len(&self) -> usize {
|
||||
self.validators.len()
|
||||
}
|
||||
}
|
||||
|
||||
/// The index of an authority.
|
||||
pub type AuthorityIndex = u32;
|
||||
|
||||
/// The Hashing used within MMR.
|
||||
pub type MmrHashing = Keccak256;
|
||||
/// The type used to represent an MMR root hash.
|
||||
pub type MmrRootHash = H256;
|
||||
|
||||
/// A consensus log item for BEEFY.
|
||||
#[derive(Decode, Encode, TypeInfo)]
|
||||
pub enum ConsensusLog<AuthorityId: Codec> {
|
||||
/// The authorities have changed.
|
||||
#[codec(index = 1)]
|
||||
AuthoritiesChange(ValidatorSet<AuthorityId>),
|
||||
/// Disable the authority with given index.
|
||||
#[codec(index = 2)]
|
||||
OnDisabled(AuthorityIndex),
|
||||
/// MMR root hash.
|
||||
#[codec(index = 3)]
|
||||
MmrRoot(MmrRootHash),
|
||||
}
|
||||
|
||||
/// BEEFY vote message.
|
||||
///
|
||||
/// A vote message is a direct vote created by a BEEFY node on every voting round
|
||||
/// and is gossiped to its peers.
|
||||
// TODO: Remove `Signature` generic type, instead get it from `Id::Signature`.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
|
||||
pub struct VoteMessage<Number, Id, Signature> {
|
||||
/// Commit to information extracted from a finalized block
|
||||
pub commitment: Commitment<Number>,
|
||||
/// Node authority id
|
||||
pub id: Id,
|
||||
/// Node signature
|
||||
pub signature: Signature,
|
||||
}
|
||||
|
||||
/// Proof showing that an authority voted twice in the same round.
|
||||
///
|
||||
/// One type of misbehavior in BEEFY happens when an authority votes in the same round/block
|
||||
/// for different payloads.
|
||||
/// Proving is achieved by collecting the signed commitments of conflicting votes.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
|
||||
pub struct DoubleVotingProof<Number, Id, Signature> {
|
||||
/// The first vote in the equivocation.
|
||||
pub first: VoteMessage<Number, Id, Signature>,
|
||||
/// The second vote in the equivocation.
|
||||
pub second: VoteMessage<Number, Id, Signature>,
|
||||
}
|
||||
|
||||
impl<Number, Id, Signature> DoubleVotingProof<Number, Id, Signature> {
|
||||
/// Returns the authority id of the equivocator.
|
||||
pub fn offender_id(&self) -> &Id {
|
||||
&self.first.id
|
||||
}
|
||||
/// Returns the round number at which the equivocation occurred.
|
||||
pub fn round_number(&self) -> &Number {
|
||||
&self.first.commitment.block_number
|
||||
}
|
||||
/// Returns the set id at which the equivocation occurred.
|
||||
pub fn set_id(&self) -> ValidatorSetId {
|
||||
self.first.commitment.validator_set_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Proof showing that an authority voted for a non-canonical chain.
|
||||
///
|
||||
/// Proving is achieved by providing a proof that contains relevant info about the canonical chain
|
||||
/// at `commitment.block_number`. The `commitment` can be checked against this info.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
|
||||
pub struct ForkVotingProof<Header: HeaderT, Id: RuntimeAppPublic, AncestryProof> {
|
||||
/// The equivocated vote.
|
||||
pub vote: VoteMessage<Header::Number, Id, Id::Signature>,
|
||||
/// Proof containing info about the canonical chain at `commitment.block_number`.
|
||||
pub ancestry_proof: AncestryProof,
|
||||
/// The header of the block where the ancestry proof was generated
|
||||
pub header: Header,
|
||||
}
|
||||
|
||||
impl<Header: HeaderT, Id: RuntimeAppPublic> ForkVotingProof<Header, Id, OpaqueValue> {
|
||||
/// Try to decode the `AncestryProof`.
|
||||
pub fn try_into<AncestryProof: Decode>(
|
||||
self,
|
||||
) -> Option<ForkVotingProof<Header, Id, AncestryProof>> {
|
||||
Some(ForkVotingProof::<Header, Id, AncestryProof> {
|
||||
vote: self.vote,
|
||||
ancestry_proof: self.ancestry_proof.decode()?,
|
||||
header: self.header,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Proof showing that an authority voted for a future block.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo)]
|
||||
pub struct FutureBlockVotingProof<Number, Id: RuntimeAppPublic> {
|
||||
/// The equivocated vote.
|
||||
pub vote: VoteMessage<Number, Id, Id::Signature>,
|
||||
}
|
||||
|
||||
/// Check a commitment signature by encoding the commitment and
|
||||
/// verifying the provided signature using the expected authority id.
|
||||
pub fn check_commitment_signature<Number, Id, MsgHash>(
|
||||
commitment: &Commitment<Number>,
|
||||
authority_id: &Id,
|
||||
signature: &<Id as RuntimeAppPublic>::Signature,
|
||||
) -> bool
|
||||
where
|
||||
Id: BeefyAuthorityId<MsgHash>,
|
||||
Number: Clone + Encode + PartialEq,
|
||||
MsgHash: Hash,
|
||||
{
|
||||
let encoded_commitment = commitment.encode();
|
||||
BeefyAuthorityId::<MsgHash>::verify(authority_id, signature, &encoded_commitment)
|
||||
}
|
||||
|
||||
/// Verifies the equivocation proof by making sure that both votes target
|
||||
/// different blocks and that its signatures are valid.
|
||||
pub fn check_double_voting_proof<Number, Id, MsgHash>(
|
||||
report: &DoubleVotingProof<Number, Id, <Id as RuntimeAppPublic>::Signature>,
|
||||
) -> bool
|
||||
where
|
||||
Id: BeefyAuthorityId<MsgHash> + PartialEq,
|
||||
Number: Clone + Encode + PartialEq,
|
||||
MsgHash: Hash,
|
||||
{
|
||||
let first = &report.first;
|
||||
let second = &report.second;
|
||||
|
||||
// if votes
|
||||
// come from different authorities,
|
||||
// are for different rounds,
|
||||
// have different validator set ids,
|
||||
// or both votes have the same commitment,
|
||||
// --> the equivocation is invalid.
|
||||
if first.id != second.id ||
|
||||
first.commitment.block_number != second.commitment.block_number ||
|
||||
first.commitment.validator_set_id != second.commitment.validator_set_id ||
|
||||
first.commitment.payload == second.commitment.payload
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check signatures on both votes are valid
|
||||
let valid_first = check_commitment_signature(&first.commitment, &first.id, &first.signature);
|
||||
let valid_second =
|
||||
check_commitment_signature(&second.commitment, &second.id, &second.signature);
|
||||
|
||||
return valid_first && valid_second;
|
||||
}
|
||||
|
||||
/// New BEEFY validator set notification hook.
|
||||
pub trait OnNewValidatorSet<AuthorityId> {
|
||||
/// Function called by the pallet when BEEFY validator set changes.
|
||||
fn on_new_validator_set(
|
||||
validator_set: &ValidatorSet<AuthorityId>,
|
||||
next_validator_set: &ValidatorSet<AuthorityId>,
|
||||
);
|
||||
}
|
||||
|
||||
/// No-op implementation of [OnNewValidatorSet].
|
||||
impl<AuthorityId> OnNewValidatorSet<AuthorityId> for () {
|
||||
fn on_new_validator_set(_: &ValidatorSet<AuthorityId>, _: &ValidatorSet<AuthorityId>) {}
|
||||
}
|
||||
|
||||
/// Hook containing helper methods for proving/checking commitment canonicity.
|
||||
pub trait AncestryHelper<Header: HeaderT> {
|
||||
/// Type containing proved info about the canonical chain at a certain height.
|
||||
type Proof: Clone + Debug + Decode + Encode + PartialEq + TypeInfo;
|
||||
/// The data needed for validating the proof.
|
||||
type ValidationContext;
|
||||
|
||||
/// Check if the proof is optimal.
|
||||
fn is_proof_optimal(proof: &Self::Proof) -> bool;
|
||||
|
||||
/// Extract the validation context from the provided header.
|
||||
fn extract_validation_context(header: Header) -> Option<Self::ValidationContext>;
|
||||
|
||||
/// Check if a commitment is pointing to a header on a non-canonical chain
|
||||
/// against a canonicity proof generated at the same header height.
|
||||
fn is_non_canonical(
|
||||
commitment: &Commitment<Header::Number>,
|
||||
proof: Self::Proof,
|
||||
context: Self::ValidationContext,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// Weight information for the logic in `AncestryHelper`.
|
||||
pub trait AncestryHelperWeightInfo<Header: HeaderT>: AncestryHelper<Header> {
|
||||
/// Weight info for the `AncestryHelper::is_proof_optimal()` method.
|
||||
fn is_proof_optimal(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight;
|
||||
|
||||
/// Weight info for the `AncestryHelper::extract_validation_context()` method.
|
||||
fn extract_validation_context() -> Weight;
|
||||
|
||||
/// Weight info for the `AncestryHelper::is_non_canonical()` method.
|
||||
fn is_non_canonical(proof: &<Self as AncestryHelper<Header>>::Proof) -> Weight;
|
||||
}
|
||||
|
||||
/// An opaque type used to represent the key ownership proof at the runtime API
|
||||
/// boundary. The inner value is an encoded representation of the actual key
|
||||
/// ownership proof which will be parameterized when defining the runtime. At
|
||||
/// the runtime API boundary this type is unknown and as such we keep this
|
||||
/// opaque representation, implementors of the runtime API will have to make
|
||||
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
|
||||
pub type OpaqueKeyOwnershipProof = OpaqueValue;
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// API necessary for BEEFY voters.
|
||||
#[api_version(6)]
|
||||
pub trait BeefyApi<AuthorityId> where
|
||||
AuthorityId : Codec + RuntimeAppPublic,
|
||||
{
|
||||
/// Return the block number where BEEFY consensus is enabled/started
|
||||
fn beefy_genesis() -> Option<NumberFor<Block>>;
|
||||
|
||||
/// Return the current active BEEFY validator set
|
||||
fn validator_set() -> Option<ValidatorSet<AuthorityId>>;
|
||||
|
||||
/// Submits an unsigned extrinsic to report a double voting equivocation. The caller
|
||||
/// must provide the double voting proof and a key ownership proof
|
||||
/// (should be obtained using `generate_key_ownership_proof`). The
|
||||
/// extrinsic will be unsigned and should only be accepted for local
|
||||
/// authorship (not to be broadcast to the network). This method returns
|
||||
/// `None` when creation of the extrinsic fails, e.g. if equivocation
|
||||
/// reporting is disabled for the given runtime (i.e. this method is
|
||||
/// hardcoded to return `None`). Only useful in an offchain context.
|
||||
fn submit_report_double_voting_unsigned_extrinsic(
|
||||
equivocation_proof:
|
||||
DoubleVotingProof<NumberFor<Block>, AuthorityId, <AuthorityId as RuntimeAppPublic>::Signature>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()>;
|
||||
|
||||
/// Submits an unsigned extrinsic to report a fork voting equivocation. The caller
|
||||
/// must provide the fork voting proof (the ancestry proof should be obtained using
|
||||
/// `generate_ancestry_proof`) and a key ownership proof (should be obtained using
|
||||
/// `generate_key_ownership_proof`). The extrinsic will be unsigned and should only
|
||||
/// be accepted for local authorship (not to be broadcast to the network). This method
|
||||
/// returns `None` when creation of the extrinsic fails, e.g. if equivocation
|
||||
/// reporting is disabled for the given runtime (i.e. this method is
|
||||
/// hardcoded to return `None`). Only useful in an offchain context.
|
||||
fn submit_report_fork_voting_unsigned_extrinsic(
|
||||
equivocation_proof:
|
||||
ForkVotingProof<Block::Header, AuthorityId, OpaqueValue>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()>;
|
||||
|
||||
/// Submits an unsigned extrinsic to report a future block voting equivocation. The caller
|
||||
/// must provide the future block voting proof and a key ownership proof
|
||||
/// (should be obtained using `generate_key_ownership_proof`).
|
||||
/// The extrinsic will be unsigned and should only be accepted for local
|
||||
/// authorship (not to be broadcast to the network). This method returns
|
||||
/// `None` when creation of the extrinsic fails, e.g. if equivocation
|
||||
/// reporting is disabled for the given runtime (i.e. this method is
|
||||
/// hardcoded to return `None`). Only useful in an offchain context.
|
||||
fn submit_report_future_block_voting_unsigned_extrinsic(
|
||||
equivocation_proof:
|
||||
FutureBlockVotingProof<NumberFor<Block>, AuthorityId>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()>;
|
||||
|
||||
/// Generates a proof of key ownership for the given authority in the
|
||||
/// given set. An example usage of this module is coupled with the
|
||||
/// session historical module to prove that a given authority key is
|
||||
/// tied to a given staking identity during a specific session. Proofs
|
||||
/// of key ownership are necessary for submitting equivocation reports.
|
||||
/// NOTE: even though the API takes a `set_id` as parameter the current
|
||||
/// implementations ignores this parameter and instead relies on this
|
||||
/// method being called at the correct block height, i.e. any point at
|
||||
/// which the given set id is live on-chain. Future implementations will
|
||||
/// instead use indexed data through an offchain worker, not requiring
|
||||
/// older states to be available.
|
||||
fn generate_key_ownership_proof(
|
||||
set_id: ValidatorSetId,
|
||||
authority_id: AuthorityId,
|
||||
) -> Option<OpaqueKeyOwnershipProof>;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use pezsp_application_crypto::ecdsa::{self, Public};
|
||||
use pezsp_core::crypto::{Pair, Wraps};
|
||||
use pezsp_crypto_hashing::{blake2_256, keccak_256};
|
||||
use pezsp_runtime::traits::{BlakeTwo256, Keccak256};
|
||||
|
||||
#[test]
|
||||
fn validator_set() {
|
||||
// Empty set not allowed.
|
||||
assert_eq!(ValidatorSet::<Public>::new(vec![], 0), None);
|
||||
|
||||
let alice = ecdsa::Pair::from_string("//Alice", None).unwrap();
|
||||
let set_id = 0;
|
||||
let validators = ValidatorSet::<Public>::new(vec![alice.public()], set_id).unwrap();
|
||||
|
||||
assert_eq!(validators.id(), set_id);
|
||||
assert_eq!(validators.validators(), &vec![alice.public()]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ecdsa_beefy_verify_works() {
|
||||
let msg = &b"test-message"[..];
|
||||
let (pair, _) = ecdsa_crypto::Pair::generate();
|
||||
|
||||
let keccak_256_signature: ecdsa_crypto::Signature =
|
||||
pair.as_inner_ref().sign_prehashed(&keccak_256(msg)).into();
|
||||
|
||||
let blake2_256_signature: ecdsa_crypto::Signature =
|
||||
pair.as_inner_ref().sign_prehashed(&blake2_256(msg)).into();
|
||||
|
||||
// Verification works if same hashing function is used when signing and verifying.
|
||||
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &keccak_256_signature, msg));
|
||||
assert!(BeefyAuthorityId::<BlakeTwo256>::verify(
|
||||
&pair.public(),
|
||||
&blake2_256_signature,
|
||||
msg
|
||||
));
|
||||
// Verification fails if distinct hashing functions are used when signing and verifying.
|
||||
assert!(!BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &blake2_256_signature, msg));
|
||||
assert!(!BeefyAuthorityId::<BlakeTwo256>::verify(
|
||||
&pair.public(),
|
||||
&keccak_256_signature,
|
||||
msg
|
||||
));
|
||||
|
||||
// Other public key doesn't work
|
||||
let (other_pair, _) = ecdsa_crypto::Pair::generate();
|
||||
assert!(!BeefyAuthorityId::<Keccak256>::verify(
|
||||
&other_pair.public(),
|
||||
&keccak_256_signature,
|
||||
msg,
|
||||
));
|
||||
assert!(!BeefyAuthorityId::<BlakeTwo256>::verify(
|
||||
&other_pair.public(),
|
||||
&blake2_256_signature,
|
||||
msg,
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn bls_beefy_verify_works() {
|
||||
let msg = &b"test-message"[..];
|
||||
let (pair, _) = bls_crypto::Pair::generate();
|
||||
|
||||
let signature: bls_crypto::Signature = pair.as_inner_ref().sign(&msg).into();
|
||||
|
||||
// Verification works if same hashing function is used when signing and verifying.
|
||||
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &signature, msg));
|
||||
|
||||
// Other public key doesn't work
|
||||
let (other_pair, _) = bls_crypto::Pair::generate();
|
||||
assert!(!BeefyAuthorityId::<Keccak256>::verify(&other_pair.public(), &signature, msg,));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn ecdsa_bls_beefy_verify_works() {
|
||||
let msg = &b"test-message"[..];
|
||||
let (pair, _) = ecdsa_bls_crypto::Pair::generate();
|
||||
|
||||
let signature: ecdsa_bls_crypto::Signature =
|
||||
pair.as_inner_ref().sign_with_hasher::<Keccak256>(&msg).into();
|
||||
|
||||
// Verification works if same hashing function is used when signing and verifying.
|
||||
assert!(BeefyAuthorityId::<Keccak256>::verify(&pair.public(), &signature, msg));
|
||||
|
||||
// Verification doesn't work if we verify function provided by pair_crypto implementation
|
||||
assert!(!ecdsa_bls_crypto::Pair::verify(&signature, msg, &pair.public()));
|
||||
|
||||
// Other public key doesn't work
|
||||
let (other_pair, _) = ecdsa_bls_crypto::Pair::generate();
|
||||
assert!(!BeefyAuthorityId::<Keccak256>::verify(&other_pair.public(), &signature, msg,));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,261 @@
|
||||
// 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.
|
||||
|
||||
//! BEEFY + MMR utilities.
|
||||
//!
|
||||
//! While BEEFY can be used completely independently as an additional consensus gadget,
|
||||
//! it is designed around a main use case of bridging standalone networks together.
|
||||
//! For that use case it's common to use some aggregated data structure (like MMR) to be
|
||||
//! used in conjunction with BEEFY, to be able to efficiently prove any past blockchain data.
|
||||
//!
|
||||
//! This module contains primitives used by Pezkuwi implementation of the BEEFY+MMR bridge,
|
||||
//! but we imagine they will be useful for other chains that either want to bridge with Pezkuwi
|
||||
//! or are completely standalone, but heavily inspired by Pezkuwi.
|
||||
|
||||
use crate::{ecdsa_crypto::AuthorityId, ConsensusLog, MmrRootHash, BEEFY_ENGINE_ID};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::{
|
||||
generic::OpaqueDigestItemId,
|
||||
traits::{Block, Header},
|
||||
};
|
||||
|
||||
/// A provider for extra data that gets added to the Mmr leaf
|
||||
pub trait BeefyDataProvider<ExtraData> {
|
||||
/// Return a vector of bytes, ideally should be a merkle root hash
|
||||
fn extra_data() -> ExtraData;
|
||||
}
|
||||
|
||||
/// A default implementation for runtimes.
|
||||
impl BeefyDataProvider<Vec<u8>> for () {
|
||||
fn extra_data() -> Vec<u8> {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// A standard leaf that gets added every block to the MMR constructed by Bizinikiwi's `pezpallet_mmr`.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
|
||||
pub struct MmrLeaf<BlockNumber, Hash, MerkleRoot, ExtraData> {
|
||||
/// Version of the leaf format.
|
||||
///
|
||||
/// Can be used to enable future format migrations and compatibility.
|
||||
/// See [`MmrLeafVersion`] documentation for details.
|
||||
pub version: MmrLeafVersion,
|
||||
/// Current block parent number and hash.
|
||||
pub parent_number_and_hash: (BlockNumber, Hash),
|
||||
/// A merkle root of the next BEEFY authority set.
|
||||
pub beefy_next_authority_set: BeefyNextAuthoritySet<MerkleRoot>,
|
||||
/// Arbitrary extra leaf data to be used by downstream pallets to include custom data in the
|
||||
/// [`MmrLeaf`]
|
||||
pub leaf_extra: ExtraData,
|
||||
}
|
||||
|
||||
/// An MMR leaf versioning scheme.
|
||||
///
|
||||
/// Version is a single byte that consists of two components:
|
||||
/// - `major` - 3 bits
|
||||
/// - `minor` - 5 bits
|
||||
///
|
||||
/// Any change in encoding that adds new items to the structure is considered non-breaking, hence
|
||||
/// only requires an update of `minor` version. Any backward incompatible change (i.e. decoding to a
|
||||
/// previous leaf format fails) should be indicated with `major` version bump.
|
||||
///
|
||||
/// Given that adding new struct elements in SCALE is backward compatible (i.e. old format can be
|
||||
/// still decoded, the new fields will simply be ignored). We expect the major version to be bumped
|
||||
/// very rarely (hopefully never).
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo)]
|
||||
pub struct MmrLeafVersion(u8);
|
||||
impl MmrLeafVersion {
|
||||
/// Create new version object from `major` and `minor` components.
|
||||
///
|
||||
/// Panics if any of the component occupies more than 4 bits.
|
||||
pub fn new(major: u8, minor: u8) -> Self {
|
||||
if major > 0b111 || minor > 0b11111 {
|
||||
panic!("Version components are too big.");
|
||||
}
|
||||
let version = (major << 5) + minor;
|
||||
Self(version)
|
||||
}
|
||||
|
||||
/// Split the version into `major` and `minor` sub-components.
|
||||
pub fn split(&self) -> (u8, u8) {
|
||||
let major = self.0 >> 5;
|
||||
let minor = self.0 & 0b11111;
|
||||
(major, minor)
|
||||
}
|
||||
}
|
||||
|
||||
/// Details of a BEEFY authority set.
|
||||
#[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
pub struct BeefyAuthoritySet<AuthoritySetCommitment> {
|
||||
/// Id of the set.
|
||||
///
|
||||
/// Id is required to correlate BEEFY signed commitments with the validator set.
|
||||
/// Light Client can easily verify that the commitment witness it is getting is
|
||||
/// produced by the latest validator set.
|
||||
pub id: crate::ValidatorSetId,
|
||||
/// Number of validators in the set.
|
||||
///
|
||||
/// Some BEEFY Light Clients may use an interactive protocol to verify only a subset
|
||||
/// of signatures. We put set length here, so that these clients can verify the minimal
|
||||
/// number of required signatures.
|
||||
pub len: u32,
|
||||
|
||||
/// Commitment(s) to BEEFY AuthorityIds.
|
||||
///
|
||||
/// This is used by Light Clients to confirm that the commitments are signed by the correct
|
||||
/// validator set. Light Clients using interactive protocol, might verify only subset of
|
||||
/// signatures, hence don't require the full list here (will receive inclusion proofs).
|
||||
///
|
||||
/// This could be Merkle Root Hash built from BEEFY ECDSA public keys and/or
|
||||
/// polynomial commitment to the polynomial interpolating BLS public keys
|
||||
/// which is used by APK proof based light clients to verify the validity
|
||||
/// of aggregated BLS keys using APK proofs.
|
||||
/// Multiple commitments can be tupled together.
|
||||
pub keyset_commitment: AuthoritySetCommitment,
|
||||
}
|
||||
|
||||
/// Details of the next BEEFY authority set.
|
||||
pub type BeefyNextAuthoritySet<MerkleRoot> = BeefyAuthoritySet<MerkleRoot>;
|
||||
|
||||
/// Extract the MMR root hash from a digest in the given header, if it exists.
|
||||
pub fn find_mmr_root_digest<B: Block>(header: &B::Header) -> Option<MmrRootHash> {
|
||||
let id = OpaqueDigestItemId::Consensus(&BEEFY_ENGINE_ID);
|
||||
|
||||
let filter = |log: ConsensusLog<AuthorityId>| match log {
|
||||
ConsensusLog::MmrRoot(root) => Some(root),
|
||||
_ => None,
|
||||
};
|
||||
header.digest().convert_first(|l| l.try_to(id).and_then(filter))
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub use mmr_root_provider::MmrRootProvider;
|
||||
#[cfg(feature = "std")]
|
||||
mod mmr_root_provider {
|
||||
use super::*;
|
||||
use crate::{known_payloads, payload::PayloadProvider, Payload};
|
||||
use alloc::sync::Arc;
|
||||
use core::marker::PhantomData;
|
||||
use pezsp_api::ProvideRuntimeApi;
|
||||
use pezsp_mmr_primitives::MmrApi;
|
||||
use pezsp_runtime::traits::NumberFor;
|
||||
|
||||
/// A [`crate::Payload`] provider where payload is Merkle Mountain Range root hash.
|
||||
///
|
||||
/// Encoded payload contains a [`crate::MmrRootHash`] type (i.e. 32-bytes hash).
|
||||
pub struct MmrRootProvider<B, R> {
|
||||
runtime: Arc<R>,
|
||||
_phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl<B, R> Clone for MmrRootProvider<B, R> {
|
||||
fn clone(&self) -> Self {
|
||||
Self { runtime: self.runtime.clone(), _phantom: PhantomData }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B, R> MmrRootProvider<B, R>
|
||||
where
|
||||
B: Block,
|
||||
R: ProvideRuntimeApi<B>,
|
||||
R::Api: MmrApi<B, MmrRootHash, NumberFor<B>>,
|
||||
{
|
||||
/// Create new BEEFY Payload provider with MMR Root as payload.
|
||||
pub fn new(runtime: Arc<R>) -> Self {
|
||||
Self { runtime, _phantom: PhantomData }
|
||||
}
|
||||
|
||||
/// Simple wrapper that gets MMR root from header digests or from client state.
|
||||
fn mmr_root_from_digest_or_runtime(&self, header: &B::Header) -> Option<MmrRootHash> {
|
||||
find_mmr_root_digest::<B>(header).or_else(|| {
|
||||
self.runtime.runtime_api().mmr_root(header.hash()).ok().and_then(|r| r.ok())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Block, R> PayloadProvider<B> for MmrRootProvider<B, R>
|
||||
where
|
||||
B: Block,
|
||||
R: ProvideRuntimeApi<B>,
|
||||
R::Api: MmrApi<B, MmrRootHash, NumberFor<B>>,
|
||||
{
|
||||
fn payload(&self, header: &B::Header) -> Option<Payload> {
|
||||
self.mmr_root_from_digest_or_runtime(header).map(|mmr_root| {
|
||||
Payload::from_single_entry(known_payloads::MMR_ROOT_ID, mmr_root.encode())
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::H256;
|
||||
use pezsp_runtime::{traits::BlakeTwo256, Digest, DigestItem, OpaqueExtrinsic};
|
||||
|
||||
#[test]
|
||||
fn should_construct_version_correctly() {
|
||||
let tests = vec![(0, 0, 0b00000000), (7, 2, 0b11100010), (7, 31, 0b11111111)];
|
||||
|
||||
for (major, minor, version) in tests {
|
||||
let v = MmrLeafVersion::new(major, minor);
|
||||
assert_eq!(v.encode(), vec![version], "Encoding does not match.");
|
||||
assert_eq!(v.split(), (major, minor));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn should_panic_if_major_too_large() {
|
||||
MmrLeafVersion::new(8, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn should_panic_if_minor_too_large() {
|
||||
MmrLeafVersion::new(0, 32);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_mmr_root_digest() {
|
||||
type Header = pezsp_runtime::generic::Header<u64, BlakeTwo256>;
|
||||
type Block = pezsp_runtime::generic::Block<Header, OpaqueExtrinsic>;
|
||||
let mut header = Header::new(
|
||||
1u64,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
Digest::default(),
|
||||
);
|
||||
|
||||
// verify empty digest shows nothing
|
||||
assert!(find_mmr_root_digest::<Block>(&header).is_none());
|
||||
|
||||
let mmr_root_hash = H256::random();
|
||||
header.digest_mut().push(DigestItem::Consensus(
|
||||
BEEFY_ENGINE_ID,
|
||||
ConsensusLog::<AuthorityId>::MmrRoot(mmr_root_hash).encode(),
|
||||
));
|
||||
|
||||
// verify validator set is correctly extracted from digest
|
||||
let extracted = find_mmr_root_digest::<Block>(&header);
|
||||
assert_eq!(extracted, Some(mmr_root_hash));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
// 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.
|
||||
|
||||
use alloc::{vec, vec::Vec};
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_runtime::traits::Block;
|
||||
|
||||
/// Id of different payloads in the [`crate::Commitment`] data.
|
||||
pub type BeefyPayloadId = [u8; 2];
|
||||
|
||||
/// Registry of all known [`BeefyPayloadId`].
|
||||
pub mod known_payloads {
|
||||
use crate::BeefyPayloadId;
|
||||
|
||||
/// A [`Payload`](super::Payload) identifier for Merkle Mountain Range root hash.
|
||||
///
|
||||
/// Encoded value should contain a [`crate::MmrRootHash`] type (i.e. 32-bytes hash).
|
||||
pub const MMR_ROOT_ID: BeefyPayloadId = *b"mh";
|
||||
}
|
||||
|
||||
/// A BEEFY payload type allowing for future extensibility of adding additional kinds of payloads.
|
||||
///
|
||||
/// The idea is to store a vector of SCALE-encoded values with an extra identifier.
|
||||
/// Identifiers MUST be sorted by the [`BeefyPayloadId`] to allow efficient lookup of expected
|
||||
/// value. Duplicated identifiers are disallowed. It's okay for different implementations to only
|
||||
/// support a subset of possible values.
|
||||
#[derive(
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Encode,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Hash,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct Payload(Vec<(BeefyPayloadId, Vec<u8>)>);
|
||||
|
||||
impl Payload {
|
||||
/// Construct a new payload given an initial value
|
||||
pub fn from_single_entry(id: BeefyPayloadId, value: Vec<u8>) -> Self {
|
||||
Self(vec![(id, value)])
|
||||
}
|
||||
|
||||
/// Returns a raw payload under given `id`.
|
||||
///
|
||||
/// If the [`BeefyPayloadId`] is not found in the payload `None` is returned.
|
||||
pub fn get_raw(&self, id: &BeefyPayloadId) -> Option<&Vec<u8>> {
|
||||
let index = self.0.binary_search_by(|probe| probe.0.cmp(id)).ok()?;
|
||||
Some(&self.0[index].1)
|
||||
}
|
||||
|
||||
/// Returns all the raw payloads under given `id`.
|
||||
pub fn get_all_raw<'a>(
|
||||
&'a self,
|
||||
id: &'a BeefyPayloadId,
|
||||
) -> impl Iterator<Item = &'a Vec<u8>> + 'a {
|
||||
self.0
|
||||
.iter()
|
||||
.filter_map(move |probe| if &probe.0 != id { return None } else { Some(&probe.1) })
|
||||
}
|
||||
|
||||
/// Returns a decoded payload value under given `id`.
|
||||
///
|
||||
/// In case the value is not there, or it cannot be decoded `None` is returned.
|
||||
pub fn get_decoded<T: Decode>(&self, id: &BeefyPayloadId) -> Option<T> {
|
||||
self.get_raw(id).and_then(|raw| T::decode(&mut &raw[..]).ok())
|
||||
}
|
||||
|
||||
/// Returns all decoded payload values under given `id`.
|
||||
pub fn get_all_decoded<'a, T: Decode>(
|
||||
&'a self,
|
||||
id: &'a BeefyPayloadId,
|
||||
) -> impl Iterator<Item = Option<T>> + 'a {
|
||||
self.get_all_raw(id).map(|raw| T::decode(&mut &raw[..]).ok())
|
||||
}
|
||||
|
||||
/// Push a `Vec<u8>` with a given id into the payload vec.
|
||||
/// This method will internally sort the payload vec after every push.
|
||||
///
|
||||
/// Returns self to allow for daisy chaining.
|
||||
pub fn push_raw(mut self, id: BeefyPayloadId, value: Vec<u8>) -> Self {
|
||||
self.0.push((id, value));
|
||||
self.0.sort_by_key(|(id, _)| *id);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for custom BEEFY payload providers.
|
||||
pub trait PayloadProvider<B: Block> {
|
||||
/// Provide BEEFY payload if available for `header`.
|
||||
fn payload(&self, header: &B::Header) -> Option<Payload>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn payload_methods_work_as_expected() {
|
||||
let id1: BeefyPayloadId = *b"hw";
|
||||
let msg1: String = "1. Hello World!".to_string();
|
||||
let id2: BeefyPayloadId = *b"yb";
|
||||
let msg2: String = "2. Yellow Board!".to_string();
|
||||
let id3: BeefyPayloadId = *b"cs";
|
||||
let msg3: String = "3. Cello Cord!".to_string();
|
||||
|
||||
let payload = Payload::from_single_entry(id1, msg1.encode())
|
||||
.push_raw(id2, msg2.encode())
|
||||
.push_raw(id3, msg3.encode());
|
||||
|
||||
assert_eq!(payload.get_decoded(&id1), Some(msg1));
|
||||
assert_eq!(payload.get_decoded(&id2), Some(msg2));
|
||||
assert_eq!(payload.get_raw(&id3), Some(&msg3.encode()));
|
||||
assert_eq!(payload.get_raw(&known_payloads::MMR_ROOT_ID), None);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
// 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.
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
use crate::ecdsa_bls_crypto;
|
||||
use crate::{
|
||||
ecdsa_crypto, AuthorityIdBound, BeefySignatureHasher, Commitment, DoubleVotingProof,
|
||||
ForkVotingProof, FutureBlockVotingProof, Payload, ValidatorSetId, VoteMessage,
|
||||
};
|
||||
use pezsp_application_crypto::{AppCrypto, AppPair, RuntimeAppPublic, Wraps};
|
||||
use pezsp_core::{ecdsa, Pair};
|
||||
use pezsp_runtime::traits::{BlockNumber, Hash, Header as HeaderT};
|
||||
|
||||
use codec::Encode;
|
||||
use std::{collections::HashMap, marker::PhantomData, sync::LazyLock};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
/// Set of test accounts using [`crate::ecdsa_crypto`] types.
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)]
|
||||
pub enum Keyring<AuthorityId> {
|
||||
Alice,
|
||||
Bob,
|
||||
Charlie,
|
||||
Dave,
|
||||
Eve,
|
||||
Ferdie,
|
||||
One,
|
||||
Two,
|
||||
_Marker(PhantomData<AuthorityId>),
|
||||
}
|
||||
|
||||
/// Trait representing BEEFY specific generation and signing behavior of authority id
|
||||
///
|
||||
/// Accepts custom hashing fn for the message and custom convertor fn for the signer.
|
||||
pub trait BeefySignerAuthority<MsgHash: Hash>: AppPair {
|
||||
/// Generate and return signature for `message` using custom hashing `MsgHash`
|
||||
fn sign_with_hasher(&self, message: &[u8]) -> <Self as AppCrypto>::Signature;
|
||||
}
|
||||
|
||||
impl<MsgHash> BeefySignerAuthority<MsgHash> for <ecdsa_crypto::AuthorityId as AppCrypto>::Pair
|
||||
where
|
||||
MsgHash: Hash,
|
||||
<MsgHash as Hash>::Output: Into<[u8; 32]>,
|
||||
{
|
||||
fn sign_with_hasher(&self, message: &[u8]) -> <Self as AppCrypto>::Signature {
|
||||
let hashed_message = <MsgHash as Hash>::hash(message).into();
|
||||
self.as_inner_ref().sign_prehashed(&hashed_message).into()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
impl<MsgHash> BeefySignerAuthority<MsgHash> for <ecdsa_bls_crypto::AuthorityId as AppCrypto>::Pair
|
||||
where
|
||||
MsgHash: Hash,
|
||||
<MsgHash as Hash>::Output: Into<[u8; 32]>,
|
||||
{
|
||||
fn sign_with_hasher(&self, message: &[u8]) -> <Self as AppCrypto>::Signature {
|
||||
self.as_inner_ref().sign_with_hasher::<MsgHash>(&message).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Implement Keyring functionalities generically over AuthorityId
|
||||
impl<AuthorityId> Keyring<AuthorityId>
|
||||
where
|
||||
AuthorityId: AuthorityIdBound + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Public>,
|
||||
<AuthorityId as AppCrypto>::Pair: BeefySignerAuthority<BeefySignatureHasher>,
|
||||
<AuthorityId as RuntimeAppPublic>::Signature:
|
||||
Send + Sync + From<<<AuthorityId as AppCrypto>::Pair as AppCrypto>::Signature>,
|
||||
{
|
||||
/// Sign `msg`.
|
||||
pub fn sign(&self, msg: &[u8]) -> <AuthorityId as RuntimeAppPublic>::Signature {
|
||||
let key_pair: <AuthorityId as AppCrypto>::Pair = self.pair();
|
||||
key_pair.sign_with_hasher(&msg).into()
|
||||
}
|
||||
|
||||
/// Return key pair.
|
||||
pub fn pair(&self) -> <AuthorityId as AppCrypto>::Pair {
|
||||
<AuthorityId as AppCrypto>::Pair::from_string(self.to_seed().as_str(), None)
|
||||
.unwrap()
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Return public key.
|
||||
pub fn public(&self) -> AuthorityId {
|
||||
self.pair().public().into()
|
||||
}
|
||||
|
||||
/// Return seed string.
|
||||
pub fn to_seed(&self) -> String {
|
||||
format!("//{}", self)
|
||||
}
|
||||
|
||||
/// Get Keyring from public key.
|
||||
pub fn from_public(who: &AuthorityId) -> Option<Keyring<AuthorityId>> {
|
||||
Self::iter().find(|k| k.public() == *who)
|
||||
}
|
||||
}
|
||||
|
||||
static PRIVATE_KEYS: LazyLock<HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Pair>> =
|
||||
LazyLock::new(|| Keyring::iter().map(|i| (i.clone(), i.pair())).collect());
|
||||
static PUBLIC_KEYS: LazyLock<HashMap<Keyring<ecdsa_crypto::AuthorityId>, ecdsa_crypto::Public>> =
|
||||
LazyLock::new(|| {
|
||||
PRIVATE_KEYS
|
||||
.iter()
|
||||
.map(|(name, pair)| (name.clone(), pezsp_application_crypto::Pair::public(pair)))
|
||||
.collect()
|
||||
});
|
||||
|
||||
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Pair {
|
||||
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
|
||||
k.pair()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa::Pair {
|
||||
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
|
||||
k.pair().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Keyring<ecdsa_crypto::AuthorityId>> for ecdsa_crypto::Public {
|
||||
fn from(k: Keyring<ecdsa_crypto::AuthorityId>) -> Self {
|
||||
(*PUBLIC_KEYS).get(&k).cloned().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new `VoteMessage` from commitment primitives and keyring
|
||||
pub fn signed_vote<Number: BlockNumber>(
|
||||
block_number: Number,
|
||||
payload: Payload,
|
||||
validator_set_id: ValidatorSetId,
|
||||
keyring: &Keyring<ecdsa_crypto::AuthorityId>,
|
||||
) -> VoteMessage<Number, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
|
||||
let commitment = Commitment { validator_set_id, block_number, payload };
|
||||
let signature = keyring.sign(&commitment.encode());
|
||||
VoteMessage { commitment, id: keyring.public(), signature }
|
||||
}
|
||||
|
||||
/// Create a new `DoubleVotingProof` based on given arguments.
|
||||
pub fn generate_double_voting_proof(
|
||||
vote1: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
|
||||
vote2: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
|
||||
) -> DoubleVotingProof<u64, ecdsa_crypto::Public, ecdsa_crypto::Signature> {
|
||||
let first = signed_vote(vote1.0, vote1.1, vote1.2, vote1.3);
|
||||
let second = signed_vote(vote2.0, vote2.1, vote2.2, vote2.3);
|
||||
DoubleVotingProof { first, second }
|
||||
}
|
||||
|
||||
/// Create a new `ForkVotingProof` based on vote & canonical header.
|
||||
pub fn generate_fork_voting_proof<Header: HeaderT<Number = u64>, AncestryProof>(
|
||||
vote: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
|
||||
ancestry_proof: AncestryProof,
|
||||
header: Header,
|
||||
) -> ForkVotingProof<Header, ecdsa_crypto::Public, AncestryProof> {
|
||||
let signed_vote = signed_vote(vote.0, vote.1, vote.2, vote.3);
|
||||
ForkVotingProof { vote: signed_vote, ancestry_proof, header }
|
||||
}
|
||||
|
||||
/// Create a new `ForkVotingProof` based on vote & canonical header.
|
||||
pub fn generate_future_block_voting_proof(
|
||||
vote: (u64, Payload, ValidatorSetId, &Keyring<ecdsa_crypto::AuthorityId>),
|
||||
) -> FutureBlockVotingProof<u64, ecdsa_crypto::Public> {
|
||||
let signed_vote = signed_vote(vote.0, vote.1, vote.2, vote.3);
|
||||
FutureBlockVotingProof { vote: signed_vote }
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for light, 2-phase interactive verification protocol.
|
||||
//!
|
||||
//! Instead of submitting full list of signatures, it's possible to submit first a witness
|
||||
//! form of [SignedCommitment].
|
||||
//! This can later be verified by the client requesting only some (out of all) signatures for
|
||||
//! verification. This allows lowering the data and computation cost of verifying the
|
||||
//! signed commitment.
|
||||
|
||||
use crate::commitment::{Commitment, SignedCommitment};
|
||||
use alloc::vec::Vec;
|
||||
|
||||
/// A light form of [SignedCommitment].
|
||||
///
|
||||
/// This is a light ("witness") form of the signed commitment. Instead of containing full list of
|
||||
/// signatures, which might be heavy and expensive to verify, it only contains a bit vector of
|
||||
/// validators which signed the original [SignedCommitment] and a merkle root of all signatures.
|
||||
///
|
||||
/// This can be used by light clients for 2-phase interactive verification (for instance for
|
||||
/// Ethereum Mainnet), in a commit-reveal like scheme, where first we submit only the signed
|
||||
/// commitment witness and later on, the client picks only some signatures to verify at random.
|
||||
#[derive(Debug, PartialEq, Eq, codec::Encode, codec::Decode)]
|
||||
pub struct SignedCommitmentWitness<TBlockNumber, TSignatureAccumulator> {
|
||||
/// The full content of the commitment.
|
||||
pub commitment: Commitment<TBlockNumber>,
|
||||
|
||||
/// The bit vector of validators who signed the commitment.
|
||||
pub signed_by: Vec<bool>, // TODO [ToDr] Consider replacing with bitvec crate
|
||||
|
||||
/// Either a merkle root of signatures in the original signed commitment or a single aggregated
|
||||
/// BLS signature aggregating all original signatures.
|
||||
pub signature_accumulator: TSignatureAccumulator,
|
||||
}
|
||||
|
||||
impl<TBlockNumber, TSignatureAccumulator>
|
||||
SignedCommitmentWitness<TBlockNumber, TSignatureAccumulator>
|
||||
{
|
||||
/// Convert [SignedCommitment] into [SignedCommitmentWitness].
|
||||
///
|
||||
/// This takes a [SignedCommitment], which contains full signatures
|
||||
/// and converts it into a witness form, which does not contain full signatures,
|
||||
/// only a bit vector indicating which validators have signed the original [SignedCommitment]
|
||||
/// and a merkle root of all signatures.
|
||||
///
|
||||
/// Returns the full list of signatures along with the witness.
|
||||
pub fn from_signed<TSignatureAggregator, TSignature>(
|
||||
signed: SignedCommitment<TBlockNumber, TSignature>,
|
||||
aggregator: TSignatureAggregator,
|
||||
) -> (Self, Vec<Option<TSignature>>)
|
||||
where
|
||||
TSignatureAggregator: FnOnce(&[Option<TSignature>]) -> TSignatureAccumulator,
|
||||
{
|
||||
let SignedCommitment { commitment, signatures } = signed;
|
||||
let signed_by = signatures.iter().map(|s| s.is_some()).collect();
|
||||
let signature_accumulator = aggregator(&signatures);
|
||||
|
||||
(Self { commitment, signed_by, signature_accumulator }, signatures)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use pezsp_core::Pair;
|
||||
use pezsp_crypto_hashing::keccak_256;
|
||||
|
||||
use super::*;
|
||||
use codec::Decode;
|
||||
|
||||
use crate::{ecdsa_crypto::Signature as EcdsaSignature, known_payloads, Payload};
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
use crate::bls_crypto::Signature as BlsSignature;
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
use w3f_bls::{
|
||||
single_pop_aggregator::SignatureAggregatorAssumingPoP, Message, SerializableToBytes,
|
||||
Signed, TinyBLS381,
|
||||
};
|
||||
|
||||
type TestCommitment = Commitment<u128>;
|
||||
|
||||
// Types for ecdsa signed commitment.
|
||||
type TestEcdsaSignedCommitment = SignedCommitment<u128, EcdsaSignature>;
|
||||
type TestEcdsaSignedCommitmentWitness =
|
||||
SignedCommitmentWitness<u128, Vec<Option<EcdsaSignature>>>;
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
#[derive(Clone, Debug, PartialEq, codec::Encode, codec::Decode)]
|
||||
struct EcdsaBlsSignaturePair(EcdsaSignature, BlsSignature);
|
||||
|
||||
// types for commitment containing bls signature along side ecdsa signature
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
type TestBlsSignedCommitment = SignedCommitment<u128, EcdsaBlsSignaturePair>;
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
type TestBlsSignedCommitmentWitness = SignedCommitmentWitness<u128, Vec<u8>>;
|
||||
|
||||
// The mock signatures are equivalent to the ones produced by the BEEFY keystore
|
||||
fn mock_ecdsa_signatures() -> (EcdsaSignature, EcdsaSignature) {
|
||||
let alice = pezsp_core::ecdsa::Pair::from_string("//Alice", None).unwrap();
|
||||
|
||||
let msg = keccak_256(b"This is the first message");
|
||||
let sig1 = alice.sign_prehashed(&msg);
|
||||
|
||||
let msg = keccak_256(b"This is the second message");
|
||||
let sig2 = alice.sign_prehashed(&msg);
|
||||
|
||||
(sig1.into(), sig2.into())
|
||||
}
|
||||
|
||||
// Generates mock aggregatable bls signature for generating test commitment
|
||||
// BLS signatures
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn mock_bls_signatures() -> (BlsSignature, BlsSignature) {
|
||||
let alice = pezsp_core::bls::Pair::from_string("//Alice", None).unwrap();
|
||||
|
||||
let msg = b"This is the first message";
|
||||
let sig1 = alice.sign(msg);
|
||||
|
||||
let msg = b"This is the second message";
|
||||
let sig2 = alice.sign(msg);
|
||||
|
||||
(sig1.into(), sig2.into())
|
||||
}
|
||||
|
||||
fn ecdsa_signed_commitment() -> TestEcdsaSignedCommitment {
|
||||
let payload = Payload::from_single_entry(
|
||||
known_payloads::MMR_ROOT_ID,
|
||||
"Hello World!".as_bytes().to_vec(),
|
||||
);
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let sigs = mock_ecdsa_signatures();
|
||||
|
||||
SignedCommitment { commitment, signatures: vec![None, None, Some(sigs.0), Some(sigs.1)] }
|
||||
}
|
||||
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn ecdsa_and_bls_signed_commitment() -> TestBlsSignedCommitment {
|
||||
let payload = Payload::from_single_entry(
|
||||
known_payloads::MMR_ROOT_ID,
|
||||
"Hello World!".as_bytes().to_vec(),
|
||||
);
|
||||
let commitment: TestCommitment =
|
||||
Commitment { payload, block_number: 5, validator_set_id: 0 };
|
||||
|
||||
let ecdsa_sigs = mock_ecdsa_signatures();
|
||||
let bls_sigs = mock_bls_signatures();
|
||||
|
||||
SignedCommitment {
|
||||
commitment,
|
||||
signatures: vec![
|
||||
None,
|
||||
None,
|
||||
Some(EcdsaBlsSignaturePair(ecdsa_sigs.0, bls_sigs.0)),
|
||||
Some(EcdsaBlsSignaturePair(ecdsa_sigs.1, bls_sigs.1)),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_convert_signed_commitment_to_witness() {
|
||||
// given
|
||||
let signed = ecdsa_signed_commitment();
|
||||
|
||||
// when
|
||||
let (witness, signatures) =
|
||||
TestEcdsaSignedCommitmentWitness::from_signed(signed, |sigs| sigs.to_vec());
|
||||
|
||||
// then
|
||||
assert_eq!(witness.signature_accumulator, signatures);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "bls-experimental")]
|
||||
fn should_convert_dually_signed_commitment_to_witness() {
|
||||
// given
|
||||
let signed = ecdsa_and_bls_signed_commitment();
|
||||
|
||||
// when
|
||||
let (witness, _signatures) =
|
||||
// from signed take a function as the aggregator
|
||||
TestBlsSignedCommitmentWitness::from_signed::<_, _>(signed, |sigs| {
|
||||
// we are going to aggregate the signatures here
|
||||
let mut aggregatedsigs: SignatureAggregatorAssumingPoP<TinyBLS381> =
|
||||
SignatureAggregatorAssumingPoP::new(Message::new(b"", b"mock payload"));
|
||||
|
||||
for sig in sigs {
|
||||
match sig {
|
||||
Some(sig) => {
|
||||
let serialized_sig : Vec<u8> = (*sig.1).to_vec();
|
||||
aggregatedsigs.add_signature(
|
||||
&w3f_bls::Signature::<TinyBLS381>::from_bytes(
|
||||
serialized_sig.as_slice()
|
||||
).unwrap()
|
||||
);
|
||||
},
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
(&aggregatedsigs).signature().to_bytes()
|
||||
});
|
||||
|
||||
// We can't use BlsSignature::try_from because it expected 112Bytes (CP (64) + BLS 48)
|
||||
// single signature while we are having a BLS aggregated signature corresponding to no CP.
|
||||
w3f_bls::Signature::<TinyBLS381>::from_bytes(witness.signature_accumulator.as_slice())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_encode_and_decode_witness() {
|
||||
// Given
|
||||
let signed = ecdsa_signed_commitment();
|
||||
let (witness, _) = TestEcdsaSignedCommitmentWitness::from_signed::<_, _>(
|
||||
signed,
|
||||
|sigs: &[std::option::Option<EcdsaSignature>]| sigs.to_vec(),
|
||||
);
|
||||
|
||||
// When
|
||||
let encoded = codec::Encode::encode(&witness);
|
||||
let decoded = TestEcdsaSignedCommitmentWitness::decode(&mut &*encoded);
|
||||
|
||||
// Then
|
||||
assert_eq!(decoded, Ok(witness));
|
||||
assert_eq!(
|
||||
encoded,
|
||||
array_bytes::hex2bytes_unchecked(
|
||||
"\
|
||||
046d683048656c6c6f20576f726c642105000000000000000000000000000000000000000000000010\
|
||||
0000010110000001558455ad81279df0795cc985580e4fb75d72d948d1107b2ac80a09abed4da8480c\
|
||||
746cc321f2319a5e99a830e314d10dd3cd68ce3dc0c33c86e99bcb7816f9ba01012d6e1f8105c337a8\
|
||||
6cdd9aaacdc496577f3db8c55ef9e6fd48f2c5c05a2274707491635d8ba3df64f324575b7b2a34487b\
|
||||
ca2324b6a0046395a71681be3d0c2a00\
|
||||
"
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "pezsp-consensus"
|
||||
version = "0.32.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Common utilities for building and using consensus engines in bizinikiwi."
|
||||
documentation = "https://docs.rs/pezsp-consensus/"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
async-trait = { workspace = true }
|
||||
futures = { features = ["thread-pool"], workspace = true }
|
||||
log = { workspace = true, default-features = true }
|
||||
pezsp-inherents = { workspace = true, default-features = true }
|
||||
pezsp-runtime = { workspace = true, default-features = true }
|
||||
pezsp-state-machine = { workspace = true, default-features = true }
|
||||
thiserror = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
futures = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
runtime-benchmarks = [
|
||||
"pezsp-inherents/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
"pezsp-state-machine/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,7 @@
|
||||
Common utilities for building and using consensus engines in Bizinikiwi.
|
||||
|
||||
Much of this crate is _unstable_ and thus the API is likely to undergo
|
||||
change. Implementors of traits should not rely on the interfaces to remain
|
||||
the same.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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.
|
||||
|
||||
//! Block announcement validation.
|
||||
|
||||
use crate::BlockStatus;
|
||||
use futures::FutureExt as _;
|
||||
use pezsp_runtime::traits::Block;
|
||||
use std::{error::Error, future::Future, pin::Pin, sync::Arc};
|
||||
|
||||
/// A type which provides access to chain information.
|
||||
pub trait Chain<B: Block> {
|
||||
/// Retrieve the status of the block denoted by the given [`Block::Hash`].
|
||||
fn block_status(&self, hash: B::Hash) -> Result<BlockStatus, Box<dyn Error + Send>>;
|
||||
}
|
||||
|
||||
impl<T: Chain<B>, B: Block> Chain<B> for Arc<T> {
|
||||
fn block_status(&self, hash: B::Hash) -> Result<BlockStatus, Box<dyn Error + Send>> {
|
||||
(&**self).block_status(hash)
|
||||
}
|
||||
}
|
||||
|
||||
/// Result of `BlockAnnounceValidator::validate`.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Validation {
|
||||
/// Valid block announcement.
|
||||
Success {
|
||||
/// Is this the new best block of the node?
|
||||
is_new_best: bool,
|
||||
},
|
||||
/// Invalid block announcement.
|
||||
Failure {
|
||||
/// Should we disconnect from this peer?
|
||||
///
|
||||
/// This should be used if the peer for example send junk to spam us.
|
||||
disconnect: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// Type which checks incoming block announcements.
|
||||
pub trait BlockAnnounceValidator<B: Block> {
|
||||
/// Validate the announced header and its associated data.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// Returning [`Validation::Failure`] will lead to a decrease of the
|
||||
/// peers reputation as it sent us invalid data.
|
||||
///
|
||||
/// The returned future should only resolve to an error if there was an internal error
|
||||
/// validating the block announcement. If the block announcement itself is invalid, this should
|
||||
/// *always* return [`Validation::Failure`].
|
||||
fn validate(
|
||||
&mut self,
|
||||
header: &B::Header,
|
||||
data: &[u8],
|
||||
) -> Pin<Box<dyn Future<Output = Result<Validation, Box<dyn Error + Send>>> + Send>>;
|
||||
}
|
||||
|
||||
/// Default implementation of `BlockAnnounceValidator`.
|
||||
#[derive(Debug)]
|
||||
pub struct DefaultBlockAnnounceValidator;
|
||||
|
||||
impl<B: Block> BlockAnnounceValidator<B> for DefaultBlockAnnounceValidator {
|
||||
fn validate(
|
||||
&mut self,
|
||||
_: &B::Header,
|
||||
data: &[u8],
|
||||
) -> Pin<Box<dyn Future<Output = Result<Validation, Box<dyn Error + Send>>> + Send>> {
|
||||
let is_empty = data.is_empty();
|
||||
|
||||
async move {
|
||||
if !is_empty {
|
||||
log::debug!(
|
||||
target: "sync",
|
||||
"Received unknown data alongside the block announcement.",
|
||||
);
|
||||
Ok(Validation::Failure { disconnect: true })
|
||||
} else {
|
||||
Ok(Validation::Success { is_new_best: false })
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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.
|
||||
|
||||
//! Error types for consensus modules.
|
||||
|
||||
/// Result type alias.
|
||||
pub type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// The error type for consensus-related operations.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
/// Missing state at block with given descriptor.
|
||||
#[error("State unavailable at block {0}")]
|
||||
StateUnavailable(String),
|
||||
/// Intermediate missing.
|
||||
#[error("Missing intermediate")]
|
||||
NoIntermediate,
|
||||
/// Intermediate is of wrong type.
|
||||
#[error("Invalid intermediate")]
|
||||
InvalidIntermediate,
|
||||
/// Error checking signature.
|
||||
#[error("Message signature {0:?} by {1:?} is invalid")]
|
||||
InvalidSignature(Vec<u8>, Vec<u8>),
|
||||
/// Invalid authorities set received from the runtime.
|
||||
#[error("Current state of blockchain has invalid authorities set")]
|
||||
InvalidAuthoritiesSet,
|
||||
/// Justification requirements not met.
|
||||
#[error("Invalid justification")]
|
||||
InvalidJustification,
|
||||
/// The justification provided is outdated and corresponds to a previous set.
|
||||
#[error("Import failed with outdated justification")]
|
||||
OutdatedJustification,
|
||||
/// Error from the client while importing.
|
||||
#[error("Import failed: {0}")]
|
||||
ClientImport(String),
|
||||
/// Error from the client while fetching some data from the chain.
|
||||
#[error("Chain lookup failed: {0}")]
|
||||
ChainLookup(String),
|
||||
/// Signing failed.
|
||||
#[error("Failed to sign: {0}")]
|
||||
CannotSign(String),
|
||||
/// Some other error.
|
||||
#[error(transparent)]
|
||||
Other(#[from] Box<dyn std::error::Error + Sync + Send + 'static>),
|
||||
}
|
||||
@@ -0,0 +1,254 @@
|
||||
// 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.
|
||||
|
||||
//! Common utilities for building and using consensus engines in bizinikiwi.
|
||||
//!
|
||||
//! Much of this crate is _unstable_ and thus the API is likely to undergo
|
||||
//! change. Implementors of traits should not rely on the interfaces to remain
|
||||
//! the same.
|
||||
|
||||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use futures::prelude::*;
|
||||
use pezsp_runtime::{
|
||||
traits::{Block as BlockT, HashingFor},
|
||||
Digest,
|
||||
};
|
||||
use pezsp_state_machine::StorageProof;
|
||||
|
||||
pub mod block_validation;
|
||||
pub mod error;
|
||||
mod select_chain;
|
||||
|
||||
pub use self::error::Error;
|
||||
pub use select_chain::SelectChain;
|
||||
pub use pezsp_inherents::InherentData;
|
||||
pub use pezsp_state_machine::Backend as StateBackend;
|
||||
|
||||
/// Block status.
|
||||
#[derive(Debug, PartialEq, Eq, Clone)]
|
||||
pub enum BlockStatus {
|
||||
/// Added to the import queue.
|
||||
Queued,
|
||||
/// Already in the blockchain and the state is available.
|
||||
InChainWithState,
|
||||
/// In the blockchain, but the state is not available.
|
||||
InChainPruned,
|
||||
/// Block or parent is known to be bad.
|
||||
KnownBad,
|
||||
/// Not in the queue or the blockchain.
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// Block data origin.
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum BlockOrigin {
|
||||
/// Genesis block built into the client.
|
||||
Genesis,
|
||||
/// Block is part of the initial sync with the network.
|
||||
NetworkInitialSync,
|
||||
/// Block was broadcasted on the network.
|
||||
NetworkBroadcast,
|
||||
/// Block that was received from the network and validated in the consensus process.
|
||||
ConsensusBroadcast,
|
||||
/// Block that was collated by this node.
|
||||
Own,
|
||||
/// Block was imported from a file.
|
||||
File,
|
||||
}
|
||||
|
||||
/// Environment for a Consensus instance.
|
||||
///
|
||||
/// Creates proposer instance.
|
||||
pub trait Environment<B: BlockT> {
|
||||
/// The proposer type this creates.
|
||||
type Proposer: Proposer<B> + Send + 'static;
|
||||
/// A future that resolves to the proposer.
|
||||
type CreateProposer: Future<Output = Result<Self::Proposer, Self::Error>>
|
||||
+ Send
|
||||
+ Unpin
|
||||
+ 'static;
|
||||
/// Error which can occur upon creation.
|
||||
type Error: From<Error> + std::error::Error + 'static;
|
||||
|
||||
/// Initialize the proposal logic on top of a specific header. Provide
|
||||
/// the authorities at that header.
|
||||
fn init(&mut self, parent_header: &B::Header) -> Self::CreateProposer;
|
||||
}
|
||||
|
||||
/// A proposal that is created by a [`Proposer`].
|
||||
pub struct Proposal<Block: BlockT, Proof> {
|
||||
/// The block that was build.
|
||||
pub block: Block,
|
||||
/// Proof that was recorded while building the block.
|
||||
pub proof: Proof,
|
||||
/// The storage changes while building this block.
|
||||
pub storage_changes: pezsp_state_machine::StorageChanges<HashingFor<Block>>,
|
||||
}
|
||||
|
||||
/// Error that is returned when [`ProofRecording`] requested to record a proof,
|
||||
/// but no proof was recorded.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
#[error("Proof should be recorded, but no proof was provided.")]
|
||||
pub struct NoProofRecorded;
|
||||
|
||||
/// A trait to express the state of proof recording on type system level.
|
||||
///
|
||||
/// This is used by [`Proposer`] to signal if proof recording is enabled. This can be used by
|
||||
/// downstream users of the [`Proposer`] trait to enforce that proof recording is activated when
|
||||
/// required. The only two implementations of this trait are [`DisableProofRecording`] and
|
||||
/// [`EnableProofRecording`].
|
||||
///
|
||||
/// This trait is sealed and can not be implemented outside of this crate!
|
||||
pub trait ProofRecording: Send + Sync + private::Sealed + 'static {
|
||||
/// The proof type that will be used internally.
|
||||
type Proof: Send + Sync + 'static;
|
||||
/// Is proof recording enabled?
|
||||
const ENABLED: bool;
|
||||
/// Convert the given `storage_proof` into [`Self::Proof`].
|
||||
///
|
||||
/// Internally Bizinikiwi uses `Option<StorageProof>` to express the both states of proof
|
||||
/// recording (for now) and as [`Self::Proof`] is some different type, we need to provide a
|
||||
/// function to convert this value.
|
||||
///
|
||||
/// If the proof recording was requested, but `None` is given, this will return
|
||||
/// `Err(NoProofRecorded)`.
|
||||
fn into_proof(storage_proof: Option<StorageProof>) -> Result<Self::Proof, NoProofRecorded>;
|
||||
}
|
||||
|
||||
/// Express that proof recording is disabled.
|
||||
///
|
||||
/// For more information see [`ProofRecording`].
|
||||
pub struct DisableProofRecording;
|
||||
|
||||
impl ProofRecording for DisableProofRecording {
|
||||
type Proof = ();
|
||||
const ENABLED: bool = false;
|
||||
|
||||
fn into_proof(_: Option<StorageProof>) -> Result<Self::Proof, NoProofRecorded> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Express that proof recording is enabled.
|
||||
///
|
||||
/// For more information see [`ProofRecording`].
|
||||
pub struct EnableProofRecording;
|
||||
|
||||
impl ProofRecording for EnableProofRecording {
|
||||
type Proof = pezsp_state_machine::StorageProof;
|
||||
const ENABLED: bool = true;
|
||||
|
||||
fn into_proof(proof: Option<StorageProof>) -> Result<Self::Proof, NoProofRecorded> {
|
||||
proof.ok_or(NoProofRecorded)
|
||||
}
|
||||
}
|
||||
|
||||
/// Provides `Sealed` trait to prevent implementing trait [`ProofRecording`] outside of this crate.
|
||||
mod private {
|
||||
/// Special trait that prevents the implementation of [`super::ProofRecording`] outside of this
|
||||
/// crate.
|
||||
pub trait Sealed {}
|
||||
|
||||
impl Sealed for super::DisableProofRecording {}
|
||||
impl Sealed for super::EnableProofRecording {}
|
||||
}
|
||||
|
||||
/// Logic for a proposer.
|
||||
///
|
||||
/// This will encapsulate creation and evaluation of proposals at a specific
|
||||
/// block.
|
||||
///
|
||||
/// Proposers are generic over bits of "consensus data" which are engine-specific.
|
||||
pub trait Proposer<B: BlockT> {
|
||||
/// Error type which can occur when proposing or evaluating.
|
||||
type Error: From<Error> + std::error::Error + 'static;
|
||||
/// Future that resolves to a committed proposal with an optional proof.
|
||||
type Proposal: Future<Output = Result<Proposal<B, Self::Proof>, Self::Error>>
|
||||
+ Send
|
||||
+ Unpin
|
||||
+ 'static;
|
||||
/// The supported proof recording by the implementor of this trait. See [`ProofRecording`]
|
||||
/// for more information.
|
||||
type ProofRecording: self::ProofRecording<Proof = Self::Proof> + Send + Sync + 'static;
|
||||
/// The proof type used by [`Self::ProofRecording`].
|
||||
type Proof: Send + Sync + 'static;
|
||||
|
||||
/// Create a proposal.
|
||||
///
|
||||
/// Gets the `inherent_data` and `inherent_digests` as input for the proposal. Additionally
|
||||
/// a maximum duration for building this proposal is given. If building the proposal takes
|
||||
/// longer than this maximum, the proposal will be very likely discarded.
|
||||
///
|
||||
/// If `block_size_limit` is given, the proposer should push transactions until the block size
|
||||
/// limit is hit. Depending on the `finalize_block` implementation of the runtime, it probably
|
||||
/// incorporates other operations (that are happening after the block limit is hit). So,
|
||||
/// when the block size estimation also includes a proof that is recorded alongside the block
|
||||
/// production, the proof can still grow. This means that the `block_size_limit` should not be
|
||||
/// the hard limit of what is actually allowed.
|
||||
///
|
||||
/// # Return
|
||||
///
|
||||
/// Returns a future that resolves to a [`Proposal`] or to [`Error`].
|
||||
fn propose(
|
||||
self,
|
||||
inherent_data: InherentData,
|
||||
inherent_digests: Digest,
|
||||
max_duration: Duration,
|
||||
block_size_limit: Option<usize>,
|
||||
) -> Self::Proposal;
|
||||
}
|
||||
|
||||
/// An oracle for when major synchronization work is being undertaken.
|
||||
///
|
||||
/// Generally, consensus authoring work isn't undertaken while well behind
|
||||
/// the head of the chain.
|
||||
pub trait SyncOracle {
|
||||
/// Whether the synchronization service is undergoing major sync.
|
||||
/// Returns true if so.
|
||||
fn is_major_syncing(&self) -> bool;
|
||||
/// Whether the synchronization service is offline.
|
||||
/// Returns true if so.
|
||||
fn is_offline(&self) -> bool;
|
||||
}
|
||||
|
||||
/// A synchronization oracle for when there is no network.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NoNetwork;
|
||||
|
||||
impl SyncOracle for NoNetwork {
|
||||
fn is_major_syncing(&self) -> bool {
|
||||
false
|
||||
}
|
||||
fn is_offline(&self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> SyncOracle for Arc<T>
|
||||
where
|
||||
T: ?Sized,
|
||||
T: SyncOracle,
|
||||
{
|
||||
fn is_major_syncing(&self) -> bool {
|
||||
T::is_major_syncing(self)
|
||||
}
|
||||
|
||||
fn is_offline(&self) -> bool {
|
||||
T::is_offline(self)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// 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.
|
||||
|
||||
use crate::error::Error;
|
||||
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
|
||||
|
||||
/// The SelectChain trait defines the strategy upon which the head is chosen
|
||||
/// if multiple forks are present for an opaque definition of "best" in the
|
||||
/// specific chain build.
|
||||
///
|
||||
/// The Strategy can be customized for the two use cases of authoring new blocks
|
||||
/// upon the best chain or which fork to finalize. Unless implemented differently
|
||||
/// by default finalization methods fall back to use authoring, so as a minimum
|
||||
/// `_authoring`-functions must be implemented.
|
||||
///
|
||||
/// Any particular user must make explicit, however, whether they intend to finalize
|
||||
/// or author through the using the right function call, as these might differ in
|
||||
/// some implementations.
|
||||
///
|
||||
/// Non-deterministically finalizing chains may only use the `_authoring` functions.
|
||||
#[async_trait::async_trait]
|
||||
pub trait SelectChain<Block: BlockT>: Sync + Send + Clone {
|
||||
/// Get all leaves of the chain, i.e. block hashes that have no children currently.
|
||||
/// Leaves that can never be finalized will not be returned.
|
||||
async fn leaves(&self) -> Result<Vec<<Block as BlockT>::Hash>, Error>;
|
||||
|
||||
/// Among those `leaves` deterministically pick one chain as the generally
|
||||
/// best chain to author new blocks upon and probably (but not necessarily)
|
||||
/// finalize.
|
||||
async fn best_chain(&self) -> Result<<Block as BlockT>::Header, Error>;
|
||||
|
||||
/// Get the best descendent of `base_hash` that we should attempt to
|
||||
/// finalize next, if any. It is valid to return the given `base_hash`
|
||||
/// itself if no better descendent exists.
|
||||
async fn finality_target(
|
||||
&self,
|
||||
base_hash: <Block as BlockT>::Hash,
|
||||
_maybe_max_number: Option<NumberFor<Block>>,
|
||||
) -> Result<<Block as BlockT>::Hash, Error> {
|
||||
Ok(base_hash)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-grandpa"
|
||||
version = "13.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
description = "Primitives for GRANDPA integration, suitable for WASM compilation."
|
||||
documentation = "https://docs.rs/pezsp-consensus-grandpa"
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
finality-grandpa = { features = ["derive-codec"], workspace = true }
|
||||
log = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], optional = true, workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-application-crypto = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-keystore = { optional = true, workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"finality-grandpa/std",
|
||||
"log/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-keystore/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
|
||||
# Serde support without relying on std features.
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"scale-info/serde",
|
||||
"pezsp-application-crypto/serde",
|
||||
"pezsp-core/serde",
|
||||
"pezsp-runtime/serde",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Primitives for GRANDPA integration, suitable for WASM compilation.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,590 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for GRANDPA integration, suitable for WASM compilation.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::Serialize;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Codec, Decode, DecodeWithMemTracking, Encode};
|
||||
use scale_info::TypeInfo;
|
||||
#[cfg(feature = "std")]
|
||||
use pezsp_keystore::KeystorePtr;
|
||||
use pezsp_runtime::{
|
||||
traits::{Header as HeaderT, NumberFor},
|
||||
ConsensusEngineId, OpaqueValue, RuntimeDebug,
|
||||
};
|
||||
|
||||
/// The log target to be used by client code.
|
||||
pub const CLIENT_LOG_TARGET: &str = "grandpa";
|
||||
/// The log target to be used by runtime code.
|
||||
pub const RUNTIME_LOG_TARGET: &str = "runtime::grandpa";
|
||||
|
||||
/// Key type for GRANDPA module.
|
||||
pub const KEY_TYPE: pezsp_core::crypto::KeyTypeId = pezsp_application_crypto::key_types::GRANDPA;
|
||||
|
||||
mod app {
|
||||
use pezsp_application_crypto::{app_crypto, ed25519, key_types::GRANDPA};
|
||||
app_crypto!(ed25519, GRANDPA);
|
||||
}
|
||||
|
||||
pezsp_application_crypto::with_pair! {
|
||||
/// The grandpa crypto scheme defined via the keypair type.
|
||||
pub type AuthorityPair = app::Pair;
|
||||
}
|
||||
|
||||
/// Identity of a Grandpa authority.
|
||||
pub type AuthorityId = app::Public;
|
||||
|
||||
/// Signature for a Grandpa authority.
|
||||
pub type AuthoritySignature = app::Signature;
|
||||
|
||||
/// The `ConsensusEngineId` of GRANDPA.
|
||||
pub const GRANDPA_ENGINE_ID: ConsensusEngineId = *b"FRNK";
|
||||
|
||||
/// The weight of an authority.
|
||||
pub type AuthorityWeight = u64;
|
||||
|
||||
/// The index of an authority.
|
||||
pub type AuthorityIndex = u64;
|
||||
|
||||
/// The monotonic identifier of a GRANDPA set of authorities.
|
||||
pub type SetId = u64;
|
||||
|
||||
/// The round indicator.
|
||||
pub type RoundNumber = u64;
|
||||
|
||||
/// A list of Grandpa authorities with associated weights.
|
||||
pub type AuthorityList = Vec<(AuthorityId, AuthorityWeight)>;
|
||||
|
||||
/// A GRANDPA message for a bizinikiwi chain.
|
||||
pub type Message<Header> =
|
||||
finality_grandpa::Message<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
|
||||
|
||||
/// A signed message.
|
||||
pub type SignedMessage<Header> = finality_grandpa::SignedMessage<
|
||||
<Header as HeaderT>::Hash,
|
||||
<Header as HeaderT>::Number,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// A primary propose message for this chain's block type.
|
||||
pub type PrimaryPropose<Header> =
|
||||
finality_grandpa::PrimaryPropose<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
|
||||
/// A prevote message for this chain's block type.
|
||||
pub type Prevote<Header> =
|
||||
finality_grandpa::Prevote<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
|
||||
/// A precommit message for this chain's block type.
|
||||
pub type Precommit<Header> =
|
||||
finality_grandpa::Precommit<<Header as HeaderT>::Hash, <Header as HeaderT>::Number>;
|
||||
/// A catch up message for this chain's block type.
|
||||
pub type CatchUp<Header> = finality_grandpa::CatchUp<
|
||||
<Header as HeaderT>::Hash,
|
||||
<Header as HeaderT>::Number,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
/// A commit message for this chain's block type.
|
||||
pub type Commit<Header> = finality_grandpa::Commit<
|
||||
<Header as HeaderT>::Hash,
|
||||
<Header as HeaderT>::Number,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// A compact commit message for this chain's block type.
|
||||
pub type CompactCommit<Header> = finality_grandpa::CompactCommit<
|
||||
<Header as HeaderT>::Hash,
|
||||
<Header as HeaderT>::Number,
|
||||
AuthoritySignature,
|
||||
AuthorityId,
|
||||
>;
|
||||
|
||||
/// A GRANDPA justification for block finality, it includes a commit message and
|
||||
/// an ancestry proof including all headers routing all precommit target blocks
|
||||
/// to the commit target block. Due to the current voting strategy the precommit
|
||||
/// targets should be the same as the commit target, since honest voters don't
|
||||
/// vote past authority set change blocks.
|
||||
///
|
||||
/// This is meant to be stored in the db and passed around the network to other
|
||||
/// nodes, and are used by syncing nodes to prove authority set handoffs.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq, TypeInfo)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub struct GrandpaJustification<Header: HeaderT> {
|
||||
pub round: u64,
|
||||
pub commit: Commit<Header>,
|
||||
pub votes_ancestries: Vec<Header>,
|
||||
}
|
||||
|
||||
/// A scheduled change of authority set.
|
||||
#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize))]
|
||||
pub struct ScheduledChange<N> {
|
||||
/// The new authorities after the change, along with their respective weights.
|
||||
pub next_authorities: AuthorityList,
|
||||
/// The number of blocks to delay.
|
||||
pub delay: N,
|
||||
}
|
||||
|
||||
/// An consensus log item for GRANDPA.
|
||||
#[derive(Decode, Encode, PartialEq, Eq, Clone, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize))]
|
||||
pub enum ConsensusLog<N: Codec> {
|
||||
/// Schedule an authority set change.
|
||||
///
|
||||
/// The earliest digest of this type in a single block will be respected,
|
||||
/// provided that there is no `ForcedChange` digest. If there is, then the
|
||||
/// `ForcedChange` will take precedence.
|
||||
///
|
||||
/// No change should be scheduled if one is already and the delay has not
|
||||
/// passed completely.
|
||||
///
|
||||
/// This should be a pure function: i.e. as long as the runtime can interpret
|
||||
/// the digest type it should return the same result regardless of the current
|
||||
/// state.
|
||||
#[codec(index = 1)]
|
||||
ScheduledChange(ScheduledChange<N>),
|
||||
/// Force an authority set change.
|
||||
///
|
||||
/// Forced changes are applied after a delay of _imported_ blocks,
|
||||
/// while pending changes are applied after a delay of _finalized_ blocks.
|
||||
///
|
||||
/// The earliest digest of this type in a single block will be respected,
|
||||
/// with others ignored.
|
||||
///
|
||||
/// No change should be scheduled if one is already and the delay has not
|
||||
/// passed completely.
|
||||
///
|
||||
/// This should be a pure function: i.e. as long as the runtime can interpret
|
||||
/// the digest type it should return the same result regardless of the current
|
||||
/// state.
|
||||
#[codec(index = 2)]
|
||||
ForcedChange(N, ScheduledChange<N>),
|
||||
/// Note that the authority with given index is disabled until the next change.
|
||||
#[codec(index = 3)]
|
||||
OnDisabled(AuthorityIndex),
|
||||
/// A signal to pause the current authority set after the given delay.
|
||||
/// After finalizing the block at _delay_ the authorities should stop voting.
|
||||
#[codec(index = 4)]
|
||||
Pause(N),
|
||||
/// A signal to resume the current authority set after the given delay.
|
||||
/// After authoring the block at _delay_ the authorities should resume voting.
|
||||
#[codec(index = 5)]
|
||||
Resume(N),
|
||||
}
|
||||
|
||||
impl<N: Codec> ConsensusLog<N> {
|
||||
/// Try to cast the log entry as a contained signal.
|
||||
pub fn try_into_change(self) -> Option<ScheduledChange<N>> {
|
||||
match self {
|
||||
ConsensusLog::ScheduledChange(change) => Some(change),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to cast the log entry as a contained forced signal.
|
||||
pub fn try_into_forced_change(self) -> Option<(N, ScheduledChange<N>)> {
|
||||
match self {
|
||||
ConsensusLog::ForcedChange(median, change) => Some((median, change)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to cast the log entry as a contained pause signal.
|
||||
pub fn try_into_pause(self) -> Option<N> {
|
||||
match self {
|
||||
ConsensusLog::Pause(delay) => Some(delay),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to cast the log entry as a contained resume signal.
|
||||
pub fn try_into_resume(self) -> Option<N> {
|
||||
match self {
|
||||
ConsensusLog::Resume(delay) => Some(delay),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proof of voter misbehavior on a given set id. Misbehavior/equivocation in
|
||||
/// GRANDPA happens when a voter votes on the same round (either at prevote or
|
||||
/// precommit stage) for different blocks. Proving is achieved by collecting the
|
||||
/// signed messages of conflicting votes.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, Eq, TypeInfo)]
|
||||
pub struct EquivocationProof<H, N> {
|
||||
set_id: SetId,
|
||||
equivocation: Equivocation<H, N>,
|
||||
}
|
||||
|
||||
impl<H, N> EquivocationProof<H, N> {
|
||||
/// Create a new `EquivocationProof` for the given set id and using the
|
||||
/// given equivocation as proof.
|
||||
pub fn new(set_id: SetId, equivocation: Equivocation<H, N>) -> Self {
|
||||
EquivocationProof { set_id, equivocation }
|
||||
}
|
||||
|
||||
/// Returns the set id at which the equivocation occurred.
|
||||
pub fn set_id(&self) -> SetId {
|
||||
self.set_id
|
||||
}
|
||||
|
||||
/// Returns the round number at which the equivocation occurred.
|
||||
pub fn round(&self) -> RoundNumber {
|
||||
match self.equivocation {
|
||||
Equivocation::Prevote(ref equivocation) => equivocation.round_number,
|
||||
Equivocation::Precommit(ref equivocation) => equivocation.round_number,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the authority id of the equivocator.
|
||||
pub fn offender(&self) -> &AuthorityId {
|
||||
self.equivocation.offender()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper object for GRANDPA equivocation proofs, useful for unifying prevote
|
||||
/// and precommit equivocations under a common type.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, Eq, TypeInfo)]
|
||||
pub enum Equivocation<H, N> {
|
||||
/// Proof of equivocation at prevote stage.
|
||||
Prevote(
|
||||
finality_grandpa::Equivocation<
|
||||
AuthorityId,
|
||||
finality_grandpa::Prevote<H, N>,
|
||||
AuthoritySignature,
|
||||
>,
|
||||
),
|
||||
/// Proof of equivocation at precommit stage.
|
||||
Precommit(
|
||||
finality_grandpa::Equivocation<
|
||||
AuthorityId,
|
||||
finality_grandpa::Precommit<H, N>,
|
||||
AuthoritySignature,
|
||||
>,
|
||||
),
|
||||
}
|
||||
|
||||
impl<H, N>
|
||||
From<
|
||||
finality_grandpa::Equivocation<
|
||||
AuthorityId,
|
||||
finality_grandpa::Prevote<H, N>,
|
||||
AuthoritySignature,
|
||||
>,
|
||||
> for Equivocation<H, N>
|
||||
{
|
||||
fn from(
|
||||
equivocation: finality_grandpa::Equivocation<
|
||||
AuthorityId,
|
||||
finality_grandpa::Prevote<H, N>,
|
||||
AuthoritySignature,
|
||||
>,
|
||||
) -> Self {
|
||||
Equivocation::Prevote(equivocation)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N>
|
||||
From<
|
||||
finality_grandpa::Equivocation<
|
||||
AuthorityId,
|
||||
finality_grandpa::Precommit<H, N>,
|
||||
AuthoritySignature,
|
||||
>,
|
||||
> for Equivocation<H, N>
|
||||
{
|
||||
fn from(
|
||||
equivocation: finality_grandpa::Equivocation<
|
||||
AuthorityId,
|
||||
finality_grandpa::Precommit<H, N>,
|
||||
AuthoritySignature,
|
||||
>,
|
||||
) -> Self {
|
||||
Equivocation::Precommit(equivocation)
|
||||
}
|
||||
}
|
||||
|
||||
impl<H, N> Equivocation<H, N> {
|
||||
/// Returns the authority id of the equivocator.
|
||||
pub fn offender(&self) -> &AuthorityId {
|
||||
match self {
|
||||
Equivocation::Prevote(ref equivocation) => &equivocation.identity,
|
||||
Equivocation::Precommit(ref equivocation) => &equivocation.identity,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the round number when the equivocation happened.
|
||||
pub fn round_number(&self) -> RoundNumber {
|
||||
match self {
|
||||
Equivocation::Prevote(ref equivocation) => equivocation.round_number,
|
||||
Equivocation::Precommit(ref equivocation) => equivocation.round_number,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Verifies the equivocation proof by making sure that both votes target
|
||||
/// different blocks and that its signatures are valid.
|
||||
pub fn check_equivocation_proof<H, N>(report: EquivocationProof<H, N>) -> bool
|
||||
where
|
||||
H: Clone + Encode + PartialEq,
|
||||
N: Clone + Encode + PartialEq,
|
||||
{
|
||||
// NOTE: the bare `Prevote` and `Precommit` types don't share any trait,
|
||||
// this is implemented as a macro to avoid duplication.
|
||||
macro_rules! check {
|
||||
( $equivocation:expr, $message:expr ) => {
|
||||
// if both votes have the same target the equivocation is invalid.
|
||||
if $equivocation.first.0.target_hash == $equivocation.second.0.target_hash &&
|
||||
$equivocation.first.0.target_number == $equivocation.second.0.target_number
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// check signatures on both votes are valid
|
||||
let valid_first = check_message_signature(
|
||||
&$message($equivocation.first.0),
|
||||
&$equivocation.identity,
|
||||
&$equivocation.first.1,
|
||||
$equivocation.round_number,
|
||||
report.set_id,
|
||||
)
|
||||
.is_valid();
|
||||
|
||||
let valid_second = check_message_signature(
|
||||
&$message($equivocation.second.0),
|
||||
&$equivocation.identity,
|
||||
&$equivocation.second.1,
|
||||
$equivocation.round_number,
|
||||
report.set_id,
|
||||
)
|
||||
.is_valid();
|
||||
|
||||
return valid_first && valid_second
|
||||
};
|
||||
}
|
||||
|
||||
match report.equivocation {
|
||||
Equivocation::Prevote(equivocation) => {
|
||||
check!(equivocation, finality_grandpa::Message::Prevote);
|
||||
},
|
||||
Equivocation::Precommit(equivocation) => {
|
||||
check!(equivocation, finality_grandpa::Message::Precommit);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Encode round message localized to a given round and set id.
|
||||
pub fn localized_payload<E: Encode>(round: RoundNumber, set_id: SetId, message: &E) -> Vec<u8> {
|
||||
let mut buf = Vec::new();
|
||||
localized_payload_with_buffer(round, set_id, message, &mut buf);
|
||||
buf
|
||||
}
|
||||
|
||||
/// Encode round message localized to a given round and set id using the given
|
||||
/// buffer. The given buffer will be cleared and the resulting encoded payload
|
||||
/// will always be written to the start of the buffer.
|
||||
pub fn localized_payload_with_buffer<E: Encode>(
|
||||
round: RoundNumber,
|
||||
set_id: SetId,
|
||||
message: &E,
|
||||
buf: &mut Vec<u8>,
|
||||
) {
|
||||
buf.clear();
|
||||
(message, round, set_id).encode_to(buf)
|
||||
}
|
||||
|
||||
/// Result of checking a message signature.
|
||||
#[derive(Clone, Encode, Decode, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "std", derive(Debug))]
|
||||
pub enum SignatureResult {
|
||||
/// Valid signature.
|
||||
Valid,
|
||||
|
||||
/// Invalid signature.
|
||||
Invalid,
|
||||
|
||||
/// Valid signature, but the message was signed in the previous set.
|
||||
OutdatedSet,
|
||||
}
|
||||
|
||||
impl SignatureResult {
|
||||
/// Returns `true` if the signature is valid.
|
||||
pub fn is_valid(&self) -> bool {
|
||||
matches!(self, SignatureResult::Valid)
|
||||
}
|
||||
}
|
||||
|
||||
/// Check a message signature by encoding the message as a localized payload and
|
||||
/// verifying the provided signature using the expected authority id.
|
||||
pub fn check_message_signature<H, N>(
|
||||
message: &finality_grandpa::Message<H, N>,
|
||||
id: &AuthorityId,
|
||||
signature: &AuthoritySignature,
|
||||
round: RoundNumber,
|
||||
set_id: SetId,
|
||||
) -> SignatureResult
|
||||
where
|
||||
H: Encode,
|
||||
N: Encode,
|
||||
{
|
||||
check_message_signature_with_buffer(message, id, signature, round, set_id, &mut Vec::new())
|
||||
}
|
||||
|
||||
/// Check a message signature by encoding the message as a localized payload and
|
||||
/// verifying the provided signature using the expected authority id.
|
||||
/// The encoding necessary to verify the signature will be done using the given
|
||||
/// buffer, the original content of the buffer will be cleared.
|
||||
pub fn check_message_signature_with_buffer<H, N>(
|
||||
message: &finality_grandpa::Message<H, N>,
|
||||
id: &AuthorityId,
|
||||
signature: &AuthoritySignature,
|
||||
round: RoundNumber,
|
||||
set_id: SetId,
|
||||
buf: &mut Vec<u8>,
|
||||
) -> SignatureResult
|
||||
where
|
||||
H: Encode,
|
||||
N: Encode,
|
||||
{
|
||||
use pezsp_application_crypto::RuntimeAppPublic;
|
||||
|
||||
localized_payload_with_buffer(round, set_id, message, buf);
|
||||
|
||||
if id.verify(&buf, signature) {
|
||||
return SignatureResult::Valid;
|
||||
}
|
||||
|
||||
let log_target = if cfg!(feature = "std") { CLIENT_LOG_TARGET } else { RUNTIME_LOG_TARGET };
|
||||
log::debug!(
|
||||
target: log_target,
|
||||
"Bad signature on message from id={id:?} round={round:?} set_id={set_id:?}",
|
||||
);
|
||||
|
||||
// Check if the signature is valid in the previous set.
|
||||
if set_id == 0 {
|
||||
return SignatureResult::Invalid;
|
||||
}
|
||||
|
||||
let prev_set_id = set_id - 1;
|
||||
localized_payload_with_buffer(round, prev_set_id, message, buf);
|
||||
let valid = id.verify(&buf, signature);
|
||||
log::debug!(
|
||||
target: log_target,
|
||||
"Previous set signature check for id={id:?} round={round:?} previous_set={prev_set_id:?} valid={valid:?}"
|
||||
);
|
||||
|
||||
if valid {
|
||||
SignatureResult::OutdatedSet
|
||||
} else {
|
||||
SignatureResult::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
/// Localizes the message to the given set and round and signs the payload.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn sign_message<H, N>(
|
||||
keystore: KeystorePtr,
|
||||
message: finality_grandpa::Message<H, N>,
|
||||
public: AuthorityId,
|
||||
round: RoundNumber,
|
||||
set_id: SetId,
|
||||
) -> Option<finality_grandpa::SignedMessage<H, N, AuthoritySignature, AuthorityId>>
|
||||
where
|
||||
H: Encode,
|
||||
N: Encode,
|
||||
{
|
||||
use pezsp_application_crypto::AppCrypto;
|
||||
|
||||
let encoded = localized_payload(round, set_id, &message);
|
||||
let signature = keystore
|
||||
.ed25519_sign(AuthorityId::ID, public.as_ref(), &encoded[..])
|
||||
.ok()
|
||||
.flatten()?
|
||||
.try_into()
|
||||
.ok()?;
|
||||
|
||||
Some(finality_grandpa::SignedMessage { message, signature, id: public })
|
||||
}
|
||||
|
||||
/// An opaque type used to represent the key ownership proof at the runtime API
|
||||
/// boundary. The inner value is an encoded representation of the actual key
|
||||
/// ownership proof which will be parameterized when defining the runtime. At
|
||||
/// the runtime API boundary this type is unknown and as such we keep this
|
||||
/// opaque representation, implementors of the runtime API will have to make
|
||||
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
|
||||
pub type OpaqueKeyOwnershipProof = OpaqueValue;
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// APIs for integrating the GRANDPA finality gadget into runtimes.
|
||||
/// This should be implemented on the runtime side.
|
||||
///
|
||||
/// This is primarily used for negotiating authority-set changes for the
|
||||
/// gadget. GRANDPA uses a signaling model of changing authority sets:
|
||||
/// changes should be signaled with a delay of N blocks, and then automatically
|
||||
/// applied in the runtime after those N blocks have passed.
|
||||
///
|
||||
/// The consensus protocol will coordinate the handoff externally.
|
||||
#[api_version(3)]
|
||||
pub trait GrandpaApi {
|
||||
/// Get the current GRANDPA authorities and weights. This should not change except
|
||||
/// for when changes are scheduled and the corresponding delay has passed.
|
||||
///
|
||||
/// When called at block B, it will return the set of authorities that should be
|
||||
/// used to finalize descendants of this block (B+1, B+2, ...). The block B itself
|
||||
/// is finalized by the authorities from block B-1.
|
||||
fn grandpa_authorities() -> AuthorityList;
|
||||
|
||||
/// Submits an unsigned extrinsic to report an equivocation. The caller
|
||||
/// must provide the equivocation proof and a key ownership proof
|
||||
/// (should be obtained using `generate_key_ownership_proof`). The
|
||||
/// extrinsic will be unsigned and should only be accepted for local
|
||||
/// authorship (not to be broadcast to the network). This method returns
|
||||
/// `None` when creation of the extrinsic fails, e.g. if equivocation
|
||||
/// reporting is disabled for the given runtime (i.e. this method is
|
||||
/// hardcoded to return `None`). Only useful in an offchain context.
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
equivocation_proof: EquivocationProof<Block::Hash, NumberFor<Block>>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> Option<()>;
|
||||
|
||||
/// Generates a proof of key ownership for the given authority in the
|
||||
/// given set. An example usage of this module is coupled with the
|
||||
/// session historical module to prove that a given authority key is
|
||||
/// tied to a given staking identity during a specific session. Proofs
|
||||
/// of key ownership are necessary for submitting equivocation reports.
|
||||
/// NOTE: even though the API takes a `set_id` as parameter the current
|
||||
/// implementations ignore this parameter and instead rely on this
|
||||
/// method being called at the correct block height, i.e. any point at
|
||||
/// which the given set id is live on-chain. Future implementations will
|
||||
/// instead use indexed data through an offchain worker, not requiring
|
||||
/// older states to be available.
|
||||
fn generate_key_ownership_proof(
|
||||
set_id: SetId,
|
||||
authority_id: AuthorityId,
|
||||
) -> Option<OpaqueKeyOwnershipProof>;
|
||||
|
||||
/// Get current GRANDPA authority set id.
|
||||
fn current_set_id() -> SetId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-pow"
|
||||
version = "0.32.0"
|
||||
authors.workspace = true
|
||||
description = "Primitives for Aura consensus"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-core = { workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "pezsp-api/std", "pezsp-core/std", "pezsp-runtime/std"]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Primitives for Bizinikiwi Proof-of-Work (PoW) consensus.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for Bizinikiwi Proof-of-Work (PoW) consensus.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use codec::Decode;
|
||||
use pezsp_runtime::ConsensusEngineId;
|
||||
|
||||
/// The `ConsensusEngineId` of PoW.
|
||||
pub const POW_ENGINE_ID: ConsensusEngineId = [b'p', b'o', b'w', b'_'];
|
||||
|
||||
/// Type of seal.
|
||||
pub type Seal = Vec<u8>;
|
||||
|
||||
/// Define methods that total difficulty should implement.
|
||||
pub trait TotalDifficulty {
|
||||
fn increment(&mut self, other: Self);
|
||||
}
|
||||
|
||||
impl TotalDifficulty for pezsp_core::U256 {
|
||||
fn increment(&mut self, other: Self) {
|
||||
let ret = self.saturating_add(other);
|
||||
*self = ret;
|
||||
}
|
||||
}
|
||||
|
||||
impl TotalDifficulty for u128 {
|
||||
fn increment(&mut self, other: Self) {
|
||||
let ret = self.saturating_add(other);
|
||||
*self = ret;
|
||||
}
|
||||
}
|
||||
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// API necessary for timestamp-based difficulty adjustment algorithms.
|
||||
pub trait TimestampApi<Moment: Decode> {
|
||||
/// Return the timestamp in the current block.
|
||||
fn timestamp() -> Moment;
|
||||
}
|
||||
|
||||
/// API for those chains that put their difficulty adjustment algorithm directly
|
||||
/// onto runtime. Note that while putting difficulty adjustment algorithm to
|
||||
/// runtime is safe, putting the PoW algorithm on runtime is not.
|
||||
pub trait DifficultyApi<Difficulty: Decode> {
|
||||
/// Return the target difficulty of the next block.
|
||||
fn difficulty() -> Difficulty;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-sassafras"
|
||||
version = "0.3.4-dev"
|
||||
authors.workspace = true
|
||||
description = "Primitives for Sassafras consensus"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository = "https://github.com/pezkuwichain/pezkuwi-sdk/"
|
||||
documentation = "https://docs.rs/pezsp-consensus-sassafras"
|
||||
readme = "README.md"
|
||||
publish = false
|
||||
|
||||
[package.metadata.pezkuwi-sdk]
|
||||
exclude-from-umbrella = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["derive"], optional = true, workspace = true }
|
||||
pezsp-api = { workspace = true }
|
||||
pezsp-application-crypto = { features = [
|
||||
"bandersnatch-experimental",
|
||||
], workspace = true }
|
||||
pezsp-consensus-slots = { workspace = true }
|
||||
pezsp-core = { features = ["bandersnatch-experimental"], workspace = true }
|
||||
pezsp-runtime = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"pezsp-api/std",
|
||||
"pezsp-application-crypto/std",
|
||||
"pezsp-consensus-slots/std",
|
||||
"pezsp-core/std",
|
||||
"pezsp-runtime/std",
|
||||
]
|
||||
|
||||
# Serde support without relying on std features.
|
||||
serde = [
|
||||
"dep:serde",
|
||||
"scale-info/serde",
|
||||
"pezsp-application-crypto/serde",
|
||||
"pezsp-consensus-slots/serde",
|
||||
"pezsp-core/serde",
|
||||
"pezsp-runtime/serde",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezsp-api/runtime-benchmarks",
|
||||
"pezsp-consensus-slots/runtime-benchmarks",
|
||||
"pezsp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,6 @@
|
||||
Primitives for SASSAFRAS.
|
||||
|
||||
- Tracking issue: https://github.com/pezkuwichain/pezkuwi-sdk/issues/95
|
||||
- RFC proposal: https://github.com/polkadot-fellows/RFCs/pull/26
|
||||
|
||||
Depends on `pezsp-core` feature: `bandersnatch-experimental`.
|
||||
@@ -0,0 +1,99 @@
|
||||
// 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.
|
||||
|
||||
//! Sassafras digests structures and helpers.
|
||||
|
||||
use crate::{
|
||||
ticket::TicketClaim, vrf::VrfSignature, AuthorityId, AuthorityIndex, AuthoritySignature,
|
||||
EpochConfiguration, Randomness, Slot, SASSAFRAS_ENGINE_ID,
|
||||
};
|
||||
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use pezsp_runtime::{DigestItem, RuntimeDebug};
|
||||
|
||||
/// Epoch slot claim digest entry.
|
||||
///
|
||||
/// This is mandatory for each block.
|
||||
#[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
pub struct SlotClaim {
|
||||
/// Authority index that claimed the slot.
|
||||
pub authority_idx: AuthorityIndex,
|
||||
/// Corresponding slot number.
|
||||
pub slot: Slot,
|
||||
/// Slot claim VRF signature.
|
||||
pub vrf_signature: VrfSignature,
|
||||
/// Ticket auxiliary information for claim check.
|
||||
pub ticket_claim: Option<TicketClaim>,
|
||||
}
|
||||
|
||||
/// Information about the next epoch.
|
||||
///
|
||||
/// This is mandatory in the first block of each epoch.
|
||||
#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)]
|
||||
pub struct NextEpochDescriptor {
|
||||
/// Randomness value.
|
||||
pub randomness: Randomness,
|
||||
/// Authorities list.
|
||||
pub authorities: Vec<AuthorityId>,
|
||||
/// Epoch configuration.
|
||||
///
|
||||
/// If not present previous epoch parameters are used.
|
||||
pub config: Option<EpochConfiguration>,
|
||||
}
|
||||
|
||||
/// Runtime digest entries.
|
||||
///
|
||||
/// Entries which may be generated by on-chain code.
|
||||
#[derive(Decode, Encode, Clone, PartialEq, Eq)]
|
||||
pub enum ConsensusLog {
|
||||
/// Provides information about the next epoch parameters.
|
||||
#[codec(index = 1)]
|
||||
NextEpochData(NextEpochDescriptor),
|
||||
/// Disable the authority with given index.
|
||||
#[codec(index = 2)]
|
||||
OnDisabled(AuthorityIndex),
|
||||
}
|
||||
|
||||
impl TryFrom<&DigestItem> for SlotClaim {
|
||||
type Error = ();
|
||||
fn try_from(item: &DigestItem) -> Result<Self, Self::Error> {
|
||||
item.pre_runtime_try_to(&SASSAFRAS_ENGINE_ID).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SlotClaim> for DigestItem {
|
||||
fn from(claim: &SlotClaim) -> Self {
|
||||
DigestItem::PreRuntime(SASSAFRAS_ENGINE_ID, claim.encode())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&DigestItem> for AuthoritySignature {
|
||||
type Error = ();
|
||||
fn try_from(item: &DigestItem) -> Result<Self, Self::Error> {
|
||||
item.seal_try_to(&SASSAFRAS_ENGINE_ID).ok_or(())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuthoritySignature> for DigestItem {
|
||||
fn from(signature: &AuthoritySignature) -> Self {
|
||||
DigestItem::Seal(SASSAFRAS_ENGINE_ID, signature.encode())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for Sassafras consensus.
|
||||
|
||||
#![deny(warnings)]
|
||||
#![forbid(unsafe_code, missing_docs, unused_variables, unused_imports)]
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_core::crypto::KeyTypeId;
|
||||
use pezsp_runtime::{ConsensusEngineId, RuntimeDebug};
|
||||
|
||||
pub use pezsp_consensus_slots::{Slot, SlotDuration};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub mod digests;
|
||||
pub mod ticket;
|
||||
pub mod vrf;
|
||||
|
||||
pub use ticket::{
|
||||
ticket_id_threshold, EphemeralPublic, EphemeralSignature, TicketBody, TicketClaim,
|
||||
TicketEnvelope, TicketId,
|
||||
};
|
||||
|
||||
mod app {
|
||||
use pezsp_application_crypto::{app_crypto, bandersnatch, key_types::SASSAFRAS};
|
||||
app_crypto!(bandersnatch, SASSAFRAS);
|
||||
}
|
||||
|
||||
/// Key type identifier.
|
||||
pub const KEY_TYPE: KeyTypeId = pezsp_application_crypto::key_types::SASSAFRAS;
|
||||
|
||||
/// Consensus engine identifier.
|
||||
pub const SASSAFRAS_ENGINE_ID: ConsensusEngineId = *b"SASS";
|
||||
|
||||
/// VRF output length for per-slot randomness.
|
||||
pub const RANDOMNESS_LENGTH: usize = 32;
|
||||
|
||||
/// Index of an authority.
|
||||
pub type AuthorityIndex = u32;
|
||||
|
||||
/// Sassafras authority keypair. Necessarily equivalent to the schnorrkel public key used in
|
||||
/// the main Sassafras module. If that ever changes, then this must, too.
|
||||
#[cfg(feature = "std")]
|
||||
pub type AuthorityPair = app::Pair;
|
||||
|
||||
/// Sassafras authority signature.
|
||||
pub type AuthoritySignature = app::Signature;
|
||||
|
||||
/// Sassafras authority identifier. Necessarily equivalent to the schnorrkel public key used in
|
||||
/// the main Sassafras module. If that ever changes, then this must, too.
|
||||
pub type AuthorityId = app::Public;
|
||||
|
||||
/// Weight of a Sassafras block.
|
||||
/// Primary blocks have a weight of 1 whereas secondary blocks have a weight of 0.
|
||||
pub type SassafrasBlockWeight = u32;
|
||||
|
||||
/// An equivocation proof for multiple block authorships on the same slot (i.e. double vote).
|
||||
pub type EquivocationProof<H> = pezsp_consensus_slots::EquivocationProof<H, AuthorityId>;
|
||||
|
||||
/// Randomness required by some protocol's operations.
|
||||
pub type Randomness = [u8; RANDOMNESS_LENGTH];
|
||||
|
||||
/// Protocol configuration that can be modified on epoch change.
|
||||
///
|
||||
/// Mostly tweaks to the ticketing system parameters.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
MaxEncodedLen,
|
||||
TypeInfo,
|
||||
Default,
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub struct EpochConfiguration {
|
||||
/// Tickets redundancy factor.
|
||||
///
|
||||
/// Expected ratio between epoch's slots and the cumulative number of tickets which can
|
||||
/// be submitted by the set of epoch validators.
|
||||
pub redundancy_factor: u32,
|
||||
/// Tickets max attempts for each validator.
|
||||
///
|
||||
/// Influences the anonymity of block producers. As all published tickets have a public
|
||||
/// attempt number less than `attempts_number` if two tickets share an attempt number
|
||||
/// then they must belong to two different validators, which reduces anonymity late as
|
||||
/// we approach the epoch tail.
|
||||
///
|
||||
/// This anonymity loss already becomes small when `attempts_number = 64` or `128`.
|
||||
pub attempts_number: u32,
|
||||
}
|
||||
|
||||
/// Sassafras epoch information
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, TypeInfo)]
|
||||
pub struct Epoch {
|
||||
/// Epoch index.
|
||||
pub index: u64,
|
||||
/// Starting slot of the epoch.
|
||||
pub start: Slot,
|
||||
/// Number of slots in the epoch.
|
||||
pub length: u32,
|
||||
/// Randomness value.
|
||||
pub randomness: Randomness,
|
||||
/// Authorities list.
|
||||
pub authorities: Vec<AuthorityId>,
|
||||
/// Epoch configuration.
|
||||
pub config: EpochConfiguration,
|
||||
}
|
||||
|
||||
/// An opaque type used to represent the key ownership proof at the runtime API boundary.
|
||||
///
|
||||
/// The inner value is an encoded representation of the actual key ownership proof which will be
|
||||
/// parameterized when defining the runtime. At the runtime API boundary this type is unknown and
|
||||
/// as such we keep this opaque representation, implementors of the runtime API will have to make
|
||||
/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type.
|
||||
#[derive(Decode, Encode, PartialEq, TypeInfo)]
|
||||
#[repr(transparent)]
|
||||
pub struct OpaqueKeyOwnershipProof(Vec<u8>);
|
||||
|
||||
// Runtime API.
|
||||
pezsp_api::decl_runtime_apis! {
|
||||
/// API necessary for block authorship with Sassafras.
|
||||
pub trait SassafrasApi {
|
||||
/// Get ring context to be used for ticket construction and verification.
|
||||
fn ring_context() -> Option<vrf::RingContext>;
|
||||
|
||||
/// Submit next epoch validator tickets via an unsigned extrinsic.
|
||||
/// This method returns `false` when creation of the extrinsics fails.
|
||||
fn submit_tickets_unsigned_extrinsic(tickets: Vec<TicketEnvelope>) -> bool;
|
||||
|
||||
/// Get ticket id associated to the given slot.
|
||||
fn slot_ticket_id(slot: Slot) -> Option<TicketId>;
|
||||
|
||||
/// Get ticket id and data associated to the given slot.
|
||||
fn slot_ticket(slot: Slot) -> Option<(TicketId, TicketBody)>;
|
||||
|
||||
/// Current epoch information.
|
||||
fn current_epoch() -> Epoch;
|
||||
|
||||
/// Next epoch information.
|
||||
fn next_epoch() -> Epoch;
|
||||
|
||||
/// Generates a proof of key ownership for the given authority in the current epoch.
|
||||
///
|
||||
/// An example usage of this module is coupled with the session historical module to prove
|
||||
/// that a given authority key is tied to a given staking identity during a specific
|
||||
/// session.
|
||||
///
|
||||
/// Proofs of key ownership are necessary for submitting equivocation reports.
|
||||
fn generate_key_ownership_proof(authority_id: AuthorityId) -> Option<OpaqueKeyOwnershipProof>;
|
||||
|
||||
/// Submits an unsigned extrinsic to report an equivocation.
|
||||
///
|
||||
/// The caller must provide the equivocation proof and a key ownership proof (should be
|
||||
/// obtained using `generate_key_ownership_proof`). The extrinsic will be unsigned and
|
||||
/// should only be accepted for local authorship (not to be broadcast to the network). This
|
||||
/// method returns `false` when creation of the extrinsic fails.
|
||||
///
|
||||
/// Only useful in an offchain context.
|
||||
fn submit_report_equivocation_unsigned_extrinsic(
|
||||
equivocation_proof: EquivocationProof<Block::Header>,
|
||||
key_owner_proof: OpaqueKeyOwnershipProof,
|
||||
) -> bool;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives related to tickets.
|
||||
|
||||
use crate::vrf::RingVrfSignature;
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
|
||||
pub use pezsp_core::ed25519::{Public as EphemeralPublic, Signature as EphemeralSignature};
|
||||
|
||||
/// Ticket identifier.
|
||||
///
|
||||
/// Its value is the output of a VRF whose inputs cannot be controlled by the
|
||||
/// ticket's creator (refer to [`crate::vrf::ticket_id_input`] parameters).
|
||||
/// Because of this, it is also used as the ticket score to compare against
|
||||
/// the epoch ticket's threshold to decide if the ticket is worth being considered
|
||||
/// for slot assignment (refer to [`ticket_id_threshold`]).
|
||||
pub type TicketId = u128;
|
||||
|
||||
/// Ticket data persisted on-chain.
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
pub struct TicketBody {
|
||||
/// Attempt index.
|
||||
pub attempt_idx: u32,
|
||||
/// Ephemeral public key which gets erased when the ticket is claimed.
|
||||
pub erased_public: EphemeralPublic,
|
||||
/// Ephemeral public key which gets exposed when the ticket is claimed.
|
||||
pub revealed_public: EphemeralPublic,
|
||||
}
|
||||
|
||||
/// Ticket ring vrf signature.
|
||||
pub type TicketSignature = RingVrfSignature;
|
||||
|
||||
/// Ticket envelope used on during submission.
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo,
|
||||
)]
|
||||
pub struct TicketEnvelope {
|
||||
/// Ticket body.
|
||||
pub body: TicketBody,
|
||||
/// Ring signature.
|
||||
pub signature: TicketSignature,
|
||||
}
|
||||
|
||||
/// Ticket claim information filled by the block author.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)]
|
||||
pub struct TicketClaim {
|
||||
/// Signature verified via `TicketBody::erased_public`.
|
||||
pub erased_signature: EphemeralSignature,
|
||||
}
|
||||
|
||||
/// Computes a boundary for [`TicketId`] maximum allowed value for a given epoch.
|
||||
///
|
||||
/// Only ticket identifiers below this threshold should be considered as candidates
|
||||
/// for slot assignment.
|
||||
///
|
||||
/// The value is computed as `TicketId::MAX*(redundancy*slots)/(attempts*validators)`
|
||||
///
|
||||
/// Where:
|
||||
/// - `redundancy`: redundancy factor;
|
||||
/// - `slots`: number of slots in epoch;
|
||||
/// - `attempts`: max number of tickets attempts per validator;
|
||||
/// - `validators`: number of validators in epoch.
|
||||
///
|
||||
/// If `attempts * validators = 0` then we return 0.
|
||||
///
|
||||
/// For details about the formula and implications refer to
|
||||
/// [*probabilities an parameters*](https://research.web3.foundation/Polkadot/protocols/block-production/SASSAFRAS#probabilities-and-parameters)
|
||||
/// paragraph of the w3f introduction to the protocol.
|
||||
// TODO: replace with [RFC-26](https://github.com/polkadot-fellows/RFCs/pull/26)
|
||||
// "Tickets Threshold" paragraph once is merged
|
||||
pub fn ticket_id_threshold(
|
||||
redundancy: u32,
|
||||
slots: u32,
|
||||
attempts: u32,
|
||||
validators: u32,
|
||||
) -> TicketId {
|
||||
let num = redundancy as u64 * slots as u64;
|
||||
let den = attempts as u64 * validators as u64;
|
||||
TicketId::max_value()
|
||||
.checked_div(den.into())
|
||||
.unwrap_or_default()
|
||||
.saturating_mul(num.into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
// This is a trivial example/check which just better explain explains the rationale
|
||||
// behind the threshold.
|
||||
//
|
||||
// After this reading the formula should become obvious.
|
||||
#[test]
|
||||
fn ticket_id_threshold_trivial_check() {
|
||||
// For an epoch with `s` slots we want to accept a number of tickets equal to ~s·r
|
||||
let redundancy = 2;
|
||||
let slots = 1000;
|
||||
let attempts = 100;
|
||||
let validators = 500;
|
||||
|
||||
let threshold = ticket_id_threshold(redundancy, slots, attempts, validators);
|
||||
let threshold = threshold as f64 / TicketId::MAX as f64;
|
||||
|
||||
// We expect that the total number of tickets allowed to be submitted
|
||||
// is slots*redundancy
|
||||
let avt = ((attempts * validators) as f64 * threshold) as u32;
|
||||
assert_eq!(avt, slots * redundancy);
|
||||
|
||||
println!("threshold: {}", threshold);
|
||||
println!("avt = {}", avt);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// 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.
|
||||
|
||||
//! Utilities related to VRF input, pre-output and signatures.
|
||||
|
||||
use crate::{Randomness, TicketBody, TicketId};
|
||||
#[cfg(not(feature = "std"))]
|
||||
use alloc::vec::Vec;
|
||||
use codec::Encode;
|
||||
use pezsp_consensus_slots::Slot;
|
||||
|
||||
pub use pezsp_core::bandersnatch::{
|
||||
ring_vrf::{RingProver, RingVerifier, RingVerifierKey, RingVrfSignature},
|
||||
vrf::{VrfInput, VrfPreOutput, VrfSignData, VrfSignature},
|
||||
};
|
||||
|
||||
/// Ring size (aka authorities count) for Sassafras consensus.
|
||||
pub const RING_SIZE: usize = 1024;
|
||||
|
||||
/// Bandersnatch VRF [`RingContext`] specialization for Sassafras using [`RING_SIZE`].
|
||||
pub type RingContext = pezsp_core::bandersnatch::ring_vrf::RingContext<RING_SIZE>;
|
||||
|
||||
/// Input for slot claim
|
||||
pub fn slot_claim_input(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput {
|
||||
let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()]
|
||||
.concat();
|
||||
VrfInput::new(&v[..])
|
||||
}
|
||||
|
||||
/// Signing-data to claim slot ownership during block production.
|
||||
pub fn slot_claim_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfSignData {
|
||||
let v = [b"sassafras-ticket", randomness.as_slice(), &slot.to_le_bytes(), &epoch.to_le_bytes()]
|
||||
.concat();
|
||||
VrfSignData::new(&v[..], &[])
|
||||
}
|
||||
|
||||
/// VRF input to generate the ticket id.
|
||||
pub fn ticket_id_input(randomness: &Randomness, attempt: u32, epoch: u64) -> VrfInput {
|
||||
let v =
|
||||
[b"sassafras-ticket", randomness.as_slice(), &attempt.to_le_bytes(), &epoch.to_le_bytes()]
|
||||
.concat();
|
||||
VrfInput::new(&v[..])
|
||||
}
|
||||
|
||||
/// Data to be signed via ring-vrf.
|
||||
pub fn ticket_body_sign_data(ticket_body: &TicketBody, ticket_id_input: VrfInput) -> VrfSignData {
|
||||
VrfSignData { vrf_input: ticket_id_input, aux_data: ticket_body.encode() }
|
||||
}
|
||||
|
||||
/// Make ticket-id from the given VRF pre-output.
|
||||
///
|
||||
/// Pre-output should have been obtained from the input directly using the vrf
|
||||
/// secret key or from the vrf signature pre-output.
|
||||
pub fn make_ticket_id(preout: &VrfPreOutput) -> TicketId {
|
||||
let bytes: [u8; 16] = preout.make_bytes()[..16].try_into().unwrap();
|
||||
u128::from_le_bytes(bytes)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
[package]
|
||||
name = "pezsp-consensus-slots"
|
||||
version = "0.32.0"
|
||||
authors.workspace = true
|
||||
description = "Primitives for slots-based consensus"
|
||||
edition.workspace = true
|
||||
license = "Apache-2.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
readme = "README.md"
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
targets = ["x86_64-unknown-linux-gnu"]
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive", "max-encoded-len"], workspace = true }
|
||||
scale-info = { features = ["derive"], workspace = true }
|
||||
serde = { features = ["alloc", "derive"], optional = true, workspace = true }
|
||||
pezsp-timestamp = { workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "scale-info/std", "serde/std", "pezsp-timestamp/std"]
|
||||
|
||||
# Serde support without relying on std features.
|
||||
serde = ["dep:serde", "scale-info/serde"]
|
||||
runtime-benchmarks = ["pezsp-timestamp/runtime-benchmarks"]
|
||||
@@ -0,0 +1,3 @@
|
||||
Primitives for slots-based consensus engines.
|
||||
|
||||
License: Apache-2.0
|
||||
@@ -0,0 +1,198 @@
|
||||
// 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.
|
||||
|
||||
//! Primitives for slots-based consensus engines.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
use codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use pezsp_timestamp::Timestamp;
|
||||
|
||||
/// Unit type wrapper that represents a slot.
|
||||
#[derive(
|
||||
Debug,
|
||||
Encode,
|
||||
MaxEncodedLen,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Eq,
|
||||
Clone,
|
||||
Copy,
|
||||
Default,
|
||||
Ord,
|
||||
Hash,
|
||||
TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[repr(transparent)]
|
||||
pub struct Slot(u64);
|
||||
|
||||
impl core::ops::Deref for Slot {
|
||||
type Target = u64;
|
||||
|
||||
fn deref(&self) -> &u64 {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add for Slot {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: Self) -> Self {
|
||||
Self(self.0 + other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Sub for Slot {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self {
|
||||
Self(self.0 - other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::AddAssign for Slot {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.0 += rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::SubAssign for Slot {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
self.0 -= rhs.0
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add<u64> for Slot {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u64) -> Self {
|
||||
Self(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<u64> + Copy> core::cmp::PartialEq<T> for Slot {
|
||||
fn eq(&self, eq: &T) -> bool {
|
||||
self.0 == (*eq).into()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<u64> + Copy> core::cmp::PartialOrd<T> for Slot {
|
||||
fn partial_cmp(&self, other: &T) -> Option<core::cmp::Ordering> {
|
||||
self.0.partial_cmp(&(*other).into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Slot {
|
||||
/// Create a new slot by calculating it from the given timestamp and slot duration.
|
||||
pub const fn from_timestamp(timestamp: Timestamp, slot_duration: SlotDuration) -> Self {
|
||||
Slot(timestamp.as_millis() / slot_duration.as_millis())
|
||||
}
|
||||
|
||||
/// Timestamp of the start of the slot.
|
||||
///
|
||||
/// Returns `None` if would overflow for given `SlotDuration`.
|
||||
pub fn timestamp(&self, slot_duration: SlotDuration) -> Option<Timestamp> {
|
||||
slot_duration.as_millis().checked_mul(self.0).map(Timestamp::new)
|
||||
}
|
||||
|
||||
/// Saturating addition.
|
||||
pub fn saturating_add<T: Into<u64>>(self, rhs: T) -> Self {
|
||||
Self(self.0.saturating_add(rhs.into()))
|
||||
}
|
||||
|
||||
/// Saturating subtraction.
|
||||
pub fn saturating_sub<T: Into<u64>>(self, rhs: T) -> Self {
|
||||
Self(self.0.saturating_sub(rhs.into()))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl std::fmt::Display for Slot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Slot {
|
||||
fn from(slot: u64) -> Slot {
|
||||
Slot(slot)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Slot> for u64 {
|
||||
fn from(slot: Slot) -> u64 {
|
||||
slot.0
|
||||
}
|
||||
}
|
||||
|
||||
/// A slot duration defined in milliseconds.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
Encode,
|
||||
Decode,
|
||||
MaxEncodedLen,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
PartialEq,
|
||||
Eq,
|
||||
TypeInfo,
|
||||
)]
|
||||
#[repr(transparent)]
|
||||
pub struct SlotDuration(u64);
|
||||
|
||||
impl SlotDuration {
|
||||
/// Initialize from the given milliseconds.
|
||||
pub const fn from_millis(millis: u64) -> Self {
|
||||
Self(millis)
|
||||
}
|
||||
}
|
||||
|
||||
impl SlotDuration {
|
||||
/// Returns `self` as a `u64` representing the duration in milliseconds.
|
||||
pub const fn as_millis(&self) -> u64 {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
impl SlotDuration {
|
||||
/// Returns `self` as [`core::time::Duration`].
|
||||
pub const fn as_duration(&self) -> core::time::Duration {
|
||||
core::time::Duration::from_millis(self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an equivocation proof. An equivocation happens when a validator
|
||||
/// produces more than one block on the same slot. The proof of equivocation
|
||||
/// are the given distinct headers that were signed by the validator and which
|
||||
/// include the slot number.
|
||||
#[derive(Clone, Debug, Decode, DecodeWithMemTracking, Encode, PartialEq, TypeInfo, Eq)]
|
||||
pub struct EquivocationProof<Header, Id> {
|
||||
/// Returns the authority id of the equivocator.
|
||||
pub offender: Id,
|
||||
/// The slot at which the equivocation happened.
|
||||
pub slot: Slot,
|
||||
/// The first header involved in the equivocation.
|
||||
pub first_header: Header,
|
||||
/// The second header involved in the equivocation.
|
||||
pub second_header: Header,
|
||||
}
|
||||
Reference in New Issue
Block a user