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:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
@@ -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\
"
)
);
}
}
@@ -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,
}