feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
[package]
|
||||
name = "pezkuwi-teyrchain-primitives"
|
||||
description = "Types and utilities for creating and working with teyrchains"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version = "6.0.0"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
# note: special care is taken to avoid inclusion of `sp-io` externals when compiling
|
||||
# this crate for WASM. This is critical to avoid forcing all teyrchain WASM into implementing
|
||||
# various unnecessary Substrate-specific endpoints.
|
||||
array-bytes = { workspace = true }
|
||||
bounded-collections = { features = ["serde"], workspace = true }
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
derive_more = { workspace = true, default-features = true }
|
||||
pezkuwi-core-primitives = { workspace = true }
|
||||
scale-info = { features = ["derive", "serde"], workspace = true }
|
||||
sp-core = { features = ["serde"], workspace = true }
|
||||
sp-runtime = { features = ["serde"], workspace = true }
|
||||
sp-weights = { workspace = true }
|
||||
|
||||
# all optional crates.
|
||||
serde = { features = ["alloc", "derive"], workspace = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
wasm-api = []
|
||||
std = [
|
||||
"bounded-collections/std",
|
||||
"codec/std",
|
||||
"pezkuwi-core-primitives/std",
|
||||
"scale-info/std",
|
||||
"serde/std",
|
||||
"sp-core/std",
|
||||
"sp-runtime/std",
|
||||
"sp-weights/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezkuwi-core-primitives/runtime-benchmarks",
|
||||
"sp-runtime/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,55 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Defines primitive types for creating or validating a teyrchain.
|
||||
//!
|
||||
//! When compiled with standard library support, this crate exports a `wasm`
|
||||
//! module that can be used to validate teyrchain WASM.
|
||||
//!
|
||||
//! ## Teyrchain WASM
|
||||
//!
|
||||
//! Pezkuwi teyrchain WASM is in the form of a module which imports a memory
|
||||
//! instance and exports a function `validate_block`.
|
||||
//!
|
||||
//! `validate` accepts as input two `i32` values, representing a pointer/length pair
|
||||
//! respectively, that encodes [`ValidationParams`](primitives::ValidationParams).
|
||||
//!
|
||||
//! `validate` returns an `u64` which is a pointer to an `u8` array and its length.
|
||||
//! The data in the array is expected to be a SCALE encoded
|
||||
//! [`ValidationResult`](primitives::ValidationResult).
|
||||
//!
|
||||
//! ASCII-diagram demonstrating the return data format:
|
||||
//!
|
||||
//! ```ignore
|
||||
//! [pointer][length]
|
||||
//! 32bit 32bit
|
||||
//! ^~~ returned pointer & length
|
||||
//! ```
|
||||
//!
|
||||
//! The wasm-api (enabled only when `std` feature is not enabled and `wasm-api` feature is enabled)
|
||||
//! provides utilities for setting up a teyrchain WASM module in Rust.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
pub mod primitives;
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "wasm-api"))]
|
||||
mod wasm_api;
|
||||
|
||||
#[cfg(all(not(feature = "std"), feature = "wasm-api"))]
|
||||
pub use wasm_api::*;
|
||||
|
||||
extern crate alloc;
|
||||
@@ -0,0 +1,491 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Primitive types which are strictly necessary from a teyrchain-execution point
|
||||
//! of view.
|
||||
|
||||
use alloc::vec::Vec;
|
||||
|
||||
use bounded_collections::{BoundedVec, ConstU32};
|
||||
use codec::{CompactAs, Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
|
||||
use scale_info::TypeInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sp_core::{bytes, RuntimeDebug, TypeId};
|
||||
use sp_runtime::traits::Hash as _;
|
||||
use sp_weights::Weight;
|
||||
|
||||
use pezkuwi_core_primitives::{Hash, OutboundHrmpMessage};
|
||||
|
||||
/// Block number type used by the relay chain.
|
||||
pub use pezkuwi_core_primitives::BlockNumber as RelayChainBlockNumber;
|
||||
|
||||
/// Teyrchain head data included in the chain.
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
derive_more::From,
|
||||
TypeInfo,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Hash, Default))]
|
||||
pub struct HeadData(#[serde(with = "bytes")] pub Vec<u8>);
|
||||
|
||||
impl core::fmt::Debug for HeadData {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "HeadData({})", array_bytes::bytes2hex("0x", &self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl HeadData {
|
||||
/// Returns the hash of this head data.
|
||||
pub fn hash(&self) -> Hash {
|
||||
sp_runtime::traits::BlakeTwo256::hash(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl codec::EncodeLike<HeadData> for alloc::vec::Vec<u8> {}
|
||||
|
||||
/// Teyrchain validation code.
|
||||
#[derive(
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
derive_more::From,
|
||||
TypeInfo,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Hash))]
|
||||
pub struct ValidationCode(#[serde(with = "bytes")] pub Vec<u8>);
|
||||
|
||||
impl core::fmt::Debug for ValidationCode {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "ValidationCode({})", array_bytes::bytes2hex("0x", &self.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl ValidationCode {
|
||||
/// Get the blake2-256 hash of the validation code bytes.
|
||||
pub fn hash(&self) -> ValidationCodeHash {
|
||||
ValidationCodeHash(sp_runtime::traits::BlakeTwo256::hash(&self.0[..]))
|
||||
}
|
||||
}
|
||||
|
||||
/// Unit type wrapper around [`type@Hash`] that represents the blake2-256 hash
|
||||
/// of validation code in particular.
|
||||
///
|
||||
/// This type is produced by [`ValidationCode::hash`].
|
||||
///
|
||||
/// This type makes it easy to enforce that a hash is a validation code hash on the type level.
|
||||
#[derive(
|
||||
Clone,
|
||||
Copy,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Hash,
|
||||
Eq,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
TypeInfo,
|
||||
)]
|
||||
pub struct ValidationCodeHash(Hash);
|
||||
|
||||
impl core::fmt::Display for ValidationCodeHash {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::Debug for ValidationCodeHash {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
write!(f, "{:?}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for ValidationCodeHash {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Hash> for ValidationCodeHash {
|
||||
fn from(hash: Hash) -> ValidationCodeHash {
|
||||
ValidationCodeHash(hash)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for ValidationCodeHash {
|
||||
fn from(hash: [u8; 32]) -> ValidationCodeHash {
|
||||
ValidationCodeHash(hash.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl core::fmt::LowerHex for ValidationCodeHash {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
core::fmt::LowerHex::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Teyrchain block data.
|
||||
///
|
||||
/// Contains everything required to validate para-block, may contain block and witness data.
|
||||
#[derive(PartialEq, Eq, Clone, Encode, Decode, derive_more::From, TypeInfo, RuntimeDebug)]
|
||||
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
|
||||
pub struct BlockData(#[cfg_attr(feature = "std", serde(with = "bytes"))] pub Vec<u8>);
|
||||
|
||||
/// Unique identifier of a teyrchain.
|
||||
#[derive(
|
||||
Clone,
|
||||
CompactAs,
|
||||
Copy,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
Default,
|
||||
Encode,
|
||||
Eq,
|
||||
Hash,
|
||||
MaxEncodedLen,
|
||||
Ord,
|
||||
PartialEq,
|
||||
PartialOrd,
|
||||
serde::Serialize,
|
||||
serde::Deserialize,
|
||||
TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(derive_more::Display))]
|
||||
pub struct Id(u32);
|
||||
|
||||
impl core::fmt::Debug for Id {
|
||||
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl codec::EncodeLike<u32> for Id {}
|
||||
impl codec::EncodeLike<Id> for u32 {}
|
||||
|
||||
impl TypeId for Id {
|
||||
const TYPE_ID: [u8; 4] = *b"para";
|
||||
}
|
||||
|
||||
impl From<Id> for u32 {
|
||||
fn from(x: Id) -> Self {
|
||||
x.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Id {
|
||||
fn from(x: u32) -> Self {
|
||||
Id(x)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "std", feature = "runtime-benchmarks"))]
|
||||
impl From<usize> for Id {
|
||||
fn from(x: usize) -> Self {
|
||||
// can't panic, so need to truncate
|
||||
let x = x.try_into().unwrap_or(u32::MAX);
|
||||
Id(x)
|
||||
}
|
||||
}
|
||||
|
||||
// When we added a second From impl for Id, type inference could no longer
|
||||
// determine which impl should apply for things like `5.into()`. It therefore
|
||||
// raised a bunch of errors in our test code, scattered throughout the
|
||||
// various modules' tests, that there is no impl of `From<i32>` (`i32` being
|
||||
// the default numeric type).
|
||||
//
|
||||
// We can't use `cfg(test)` here, because that configuration directive does not
|
||||
// propagate between crates, which would fail to fix tests in crates other than
|
||||
// this one.
|
||||
//
|
||||
// Instead, let's take advantage of the observation that what really matters for a
|
||||
// ParaId within a test context is that it is unique and constant. I believe that
|
||||
// there is no case where someone does `(-1).into()` anyway, but if they do, it
|
||||
// never matters whether the actual contained ID is `-1` or `4294967295`. Nobody
|
||||
// does arithmetic on a `ParaId`; doing so would be a bug.
|
||||
impl From<i32> for Id {
|
||||
fn from(x: i32) -> Self {
|
||||
Id(x as u32)
|
||||
}
|
||||
}
|
||||
|
||||
// System teyrchain ID is considered `< 2000`.
|
||||
const SYSTEM_INDEX_END: u32 = 1999;
|
||||
const PUBLIC_INDEX_START: u32 = 2000;
|
||||
|
||||
/// The ID of the first publicly registrable teyrchain.
|
||||
pub const LOWEST_PUBLIC_ID: Id = Id(PUBLIC_INDEX_START);
|
||||
|
||||
impl Id {
|
||||
/// Create an `Id`.
|
||||
pub const fn new(id: u32) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if a teyrchain is a system teyrchain or not.
|
||||
pub trait IsSystem {
|
||||
/// Returns `true` if a teyrchain is a system teyrchain, `false` otherwise.
|
||||
fn is_system(&self) -> bool;
|
||||
}
|
||||
|
||||
impl IsSystem for Id {
|
||||
fn is_system(&self) -> bool {
|
||||
self.0 <= SYSTEM_INDEX_END
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Add<u32> for Id {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, other: u32) -> Self {
|
||||
Self(self.0 + other)
|
||||
}
|
||||
}
|
||||
|
||||
impl core::ops::Sub<u32> for Id {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: u32) -> Self {
|
||||
Self(self.0 - other)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Default, Encode, Decode, Eq, PartialEq, Ord, PartialOrd, RuntimeDebug, TypeInfo,
|
||||
)]
|
||||
pub struct Sibling(pub Id);
|
||||
|
||||
impl From<Id> for Sibling {
|
||||
fn from(i: Id) -> Self {
|
||||
Self(i)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Sibling> for Id {
|
||||
fn from(i: Sibling) -> Self {
|
||||
i.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<Id> for Sibling {
|
||||
fn as_ref(&self) -> &Id {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TypeId for Sibling {
|
||||
const TYPE_ID: [u8; 4] = *b"sibl";
|
||||
}
|
||||
|
||||
impl From<Sibling> for u32 {
|
||||
fn from(x: Sibling) -> Self {
|
||||
x.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Sibling {
|
||||
fn from(x: u32) -> Self {
|
||||
Sibling(x.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl IsSystem for Sibling {
|
||||
fn is_system(&self) -> bool {
|
||||
IsSystem::is_system(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
/// A type that uniquely identifies an HRMP channel. An HRMP channel is established between two
|
||||
/// paras. In text, we use the notation `(A, B)` to specify a channel between A and B. The channels
|
||||
/// are unidirectional, meaning that `(A, B)` and `(B, A)` refer to different channels. The
|
||||
/// convention is that we use the first item tuple for the sender and the second for the recipient.
|
||||
/// Only one channel is allowed between two participants in one direction, i.e. there cannot be 2
|
||||
/// different channels identified by `(A, B)`. A channel with the same para id in sender and
|
||||
/// recipient is invalid. That is, however, not enforced.
|
||||
#[derive(
|
||||
Clone,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Encode,
|
||||
Decode,
|
||||
DecodeWithMemTracking,
|
||||
RuntimeDebug,
|
||||
TypeInfo,
|
||||
)]
|
||||
#[cfg_attr(feature = "std", derive(Hash))]
|
||||
pub struct HrmpChannelId {
|
||||
/// The para that acts as the sender in this channel.
|
||||
pub sender: Id,
|
||||
/// The para that acts as the recipient in this channel.
|
||||
pub recipient: Id,
|
||||
}
|
||||
|
||||
impl HrmpChannelId {
|
||||
/// Returns true if the given id corresponds to either the sender or the recipient.
|
||||
pub fn is_participant(&self, id: Id) -> bool {
|
||||
id == self.sender || id == self.recipient
|
||||
}
|
||||
}
|
||||
|
||||
/// A message from a teyrchain to its Relay Chain.
|
||||
pub type UpwardMessage = Vec<u8>;
|
||||
|
||||
/// Something that should be called when a downward message is received.
|
||||
pub trait DmpMessageHandler {
|
||||
/// Handle some incoming DMP messages (note these are individual XCM messages).
|
||||
///
|
||||
/// Also, process messages up to some `max_weight`.
|
||||
fn handle_dmp_messages(
|
||||
iter: impl Iterator<Item = (RelayChainBlockNumber, Vec<u8>)>,
|
||||
max_weight: Weight,
|
||||
) -> Weight;
|
||||
}
|
||||
impl DmpMessageHandler for () {
|
||||
fn handle_dmp_messages(
|
||||
iter: impl Iterator<Item = (RelayChainBlockNumber, Vec<u8>)>,
|
||||
_max_weight: Weight,
|
||||
) -> Weight {
|
||||
iter.for_each(drop);
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// The aggregate XCMP message format.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Eq,
|
||||
PartialEq,
|
||||
Ord,
|
||||
PartialOrd,
|
||||
Encode,
|
||||
Decode,
|
||||
TypeInfo,
|
||||
RuntimeDebug,
|
||||
MaxEncodedLen,
|
||||
)]
|
||||
pub enum XcmpMessageFormat {
|
||||
/// Encoded `VersionedXcm` messages, all concatenated.
|
||||
ConcatenatedVersionedXcm,
|
||||
/// Encoded `Vec<u8>` messages, all concatenated.
|
||||
ConcatenatedEncodedBlob,
|
||||
/// One or more channel control signals; these should be interpreted immediately upon receipt
|
||||
/// from the relay-chain.
|
||||
Signals,
|
||||
/// Double encoded `VersionedXcm` messages, all concatenated.
|
||||
ConcatenatedOpaqueVersionedXcm,
|
||||
}
|
||||
|
||||
/// Something that should be called for each batch of messages received over XCMP.
|
||||
pub trait XcmpMessageHandler {
|
||||
/// Handle some incoming XCMP messages (note these are the big one-per-block aggregate
|
||||
/// messages).
|
||||
///
|
||||
/// Also, process messages up to some `max_weight`.
|
||||
fn handle_xcmp_messages<'a, I: Iterator<Item = (Id, RelayChainBlockNumber, &'a [u8])>>(
|
||||
iter: I,
|
||||
max_weight: Weight,
|
||||
) -> Weight;
|
||||
}
|
||||
impl XcmpMessageHandler for () {
|
||||
fn handle_xcmp_messages<'a, I: Iterator<Item = (Id, RelayChainBlockNumber, &'a [u8])>>(
|
||||
iter: I,
|
||||
_max_weight: Weight,
|
||||
) -> Weight {
|
||||
for _ in iter {}
|
||||
Weight::zero()
|
||||
}
|
||||
}
|
||||
|
||||
/// Validation parameters for evaluating the teyrchain validity function.
|
||||
// TODO: balance downloads (https://github.com/paritytech/polkadot/issues/220)
|
||||
#[derive(PartialEq, Eq, Decode, Clone)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Encode))]
|
||||
pub struct ValidationParams {
|
||||
/// Previous head-data.
|
||||
pub parent_head: HeadData,
|
||||
/// The collation body.
|
||||
pub block_data: BlockData,
|
||||
/// The current relay-chain block number.
|
||||
pub relay_parent_number: RelayChainBlockNumber,
|
||||
/// The relay-chain block's storage root.
|
||||
pub relay_parent_storage_root: Hash,
|
||||
}
|
||||
|
||||
/// Maximum number of HRMP messages allowed per candidate.
|
||||
///
|
||||
/// We also use this as a generous limit, which still prevents possible memory exhaustion, from
|
||||
/// malicious teyrchains that may otherwise return a huge amount of messages in `ValidationResult`.
|
||||
pub const MAX_HORIZONTAL_MESSAGE_NUM: u32 = 16 * 1024;
|
||||
/// Maximum number of UMP messages allowed per candidate.
|
||||
///
|
||||
/// We also use this as a generous limit, which still prevents possible memory exhaustion, from
|
||||
/// malicious teyrchains that may otherwise return a huge amount of messages in `ValidationResult`.
|
||||
pub const MAX_UPWARD_MESSAGE_NUM: u32 = 16 * 1024;
|
||||
|
||||
pub type UpwardMessages = BoundedVec<UpwardMessage, ConstU32<MAX_UPWARD_MESSAGE_NUM>>;
|
||||
|
||||
pub type HorizontalMessages =
|
||||
BoundedVec<OutboundHrmpMessage<Id>, ConstU32<MAX_HORIZONTAL_MESSAGE_NUM>>;
|
||||
|
||||
/// The result of teyrchain validation.
|
||||
// TODO: balance uploads (https://github.com/paritytech/polkadot/issues/220)
|
||||
#[derive(PartialEq, Eq, Clone, Encode)]
|
||||
#[cfg_attr(feature = "std", derive(Debug, Decode))]
|
||||
pub struct ValidationResult {
|
||||
/// New head data that should be included in the relay chain state.
|
||||
pub head_data: HeadData,
|
||||
/// An update to the validation code that should be scheduled in the relay chain.
|
||||
pub new_validation_code: Option<ValidationCode>,
|
||||
/// Upward messages send by the Teyrchain.
|
||||
pub upward_messages: UpwardMessages,
|
||||
/// Outbound horizontal messages sent by the teyrchain.
|
||||
pub horizontal_messages: HorizontalMessages,
|
||||
/// Number of downward messages that were processed by the Teyrchain.
|
||||
///
|
||||
/// It is expected that the Teyrchain processes them from first to last.
|
||||
pub processed_downward_messages: u32,
|
||||
/// The mark which specifies the block number up to which all inbound HRMP messages are
|
||||
/// processed.
|
||||
pub hrmp_watermark: RelayChainBlockNumber,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn para_id_debug() {
|
||||
let id = Id::new(42);
|
||||
assert_eq!(format!("{:?}", id), "42");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Utilities for writing teyrchain WASM.
|
||||
|
||||
/// Load the validation params from memory when implementing a Rust teyrchain.
|
||||
///
|
||||
/// Offset and length must have been provided by the validation
|
||||
/// function's entry point.
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub unsafe fn load_params(params: *const u8, len: usize) -> crate::primitives::ValidationParams {
|
||||
let mut slice = core::slice::from_raw_parts(params, len);
|
||||
|
||||
codec::Decode::decode(&mut slice).expect("Invalid input data")
|
||||
}
|
||||
|
||||
/// Allocate the validation result in memory, getting the return-pointer back.
|
||||
///
|
||||
/// As described in the crate docs, this is a pointer to the appended length
|
||||
/// of the vector.
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub fn write_result(result: &crate::primitives::ValidationResult) -> u64 {
|
||||
sp_core::to_substrate_wasm_fn_return_value(&result)
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
target/
|
||||
Cargo.lock
|
||||
@@ -0,0 +1,4 @@
|
||||
# Test Teyrchains
|
||||
|
||||
Each teyrchain consists of three parts: a `#![no_std]` library with the main execution logic, a WASM crate which wraps
|
||||
this logic, and a collator node.
|
||||
@@ -0,0 +1,33 @@
|
||||
[package]
|
||||
name = "test-teyrchain-adder"
|
||||
description = "Test teyrchain which adds to a number as its state transition"
|
||||
build = "build.rs"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
dlmalloc = { features = ["global"], workspace = true }
|
||||
pezkuwi-teyrchain-primitives = { features = ["wasm-api"], workspace = true }
|
||||
tiny-keccak = { features = ["keccak"], workspace = true }
|
||||
|
||||
# We need to make sure the global allocator is disabled until we have support of full substrate externalities
|
||||
sp-io = { features = ["disable_allocator"], workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
substrate-wasm-builder = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = ["codec/std", "pezkuwi-teyrchain-primitives/std", "sp-io/std"]
|
||||
runtime-benchmarks = [
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"sp-io/runtime-benchmarks",
|
||||
"substrate-wasm-builder/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use substrate_wasm_builder::WasmBuilder;
|
||||
|
||||
fn main() {
|
||||
WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.export_heap_base()
|
||||
.disable_runtime_version_section_check()
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
[package]
|
||||
name = "test-teyrchain-adder-collator"
|
||||
description = "Collator for the adder test teyrchain"
|
||||
publish = false
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "adder-collator"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { features = ["derive"], workspace = true }
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-timer = { workspace = true }
|
||||
log = { workspace = true, default-features = true }
|
||||
|
||||
pezkuwi-cli = { workspace = true, default-features = true }
|
||||
pezkuwi-node-primitives = { workspace = true, default-features = true }
|
||||
pezkuwi-node-subsystem = { workspace = true, default-features = true }
|
||||
pezkuwi-primitives = { workspace = true, default-features = true }
|
||||
pezkuwi-service = { features = [
|
||||
"pezkuwichain-native",
|
||||
], workspace = true, default-features = true }
|
||||
test-teyrchain-adder = { workspace = true }
|
||||
|
||||
sc-cli = { workspace = true, default-features = true }
|
||||
sc-service = { workspace = true, default-features = true }
|
||||
sp-core = { workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezkuwi-node-core-pvf = { features = [
|
||||
"test-utils",
|
||||
], workspace = true, default-features = true }
|
||||
pezkuwi-test-service = { workspace = true }
|
||||
pezkuwi-teyrchain-primitives = { workspace = true, default-features = true }
|
||||
|
||||
sp-keyring = { workspace = true, default-features = true }
|
||||
|
||||
tokio = { features = ["macros"], workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezkuwi-cli/runtime-benchmarks",
|
||||
"pezkuwi-node-core-pvf/runtime-benchmarks",
|
||||
"pezkuwi-node-primitives/runtime-benchmarks",
|
||||
"pezkuwi-node-subsystem/runtime-benchmarks",
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"pezkuwi-service/runtime-benchmarks",
|
||||
"pezkuwi-test-service/runtime-benchmarks",
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"sc-cli/runtime-benchmarks",
|
||||
"sc-service/runtime-benchmarks",
|
||||
"sp-keyring/runtime-benchmarks",
|
||||
"test-teyrchain-adder/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
# How to run this collator
|
||||
|
||||
First, build PezkuwiChain:
|
||||
|
||||
```sh
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
Then start two validators that will run for the relay chain:
|
||||
|
||||
```sh
|
||||
cargo run --release -- -d alice --chain pezkuwichain-local --validator --alice --port 50551
|
||||
cargo run --release -- -d bob --chain pezkuwichain-local --validator --bob --port 50552
|
||||
```
|
||||
|
||||
Next start the collator that will collate for the adder teyrchain:
|
||||
|
||||
```sh
|
||||
cargo run --release -p test-teyrchain-adder-collator -- --tmp --chain pezkuwichain-local --port 50553
|
||||
```
|
||||
|
||||
The last step is to register the teyrchain using `pezkuwi-js`. The teyrchain id is
|
||||
100. The genesis state and the validation code are printed at startup by the collator.
|
||||
|
||||
To do this automatically, run `scripts/adder-collator.sh`.
|
||||
@@ -0,0 +1,117 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Pezkuwi CLI library.
|
||||
|
||||
use clap::Parser;
|
||||
use sc_cli::SubstrateCli;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Sub-commands supported by the collator.
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum Subcommand {
|
||||
/// Export the genesis state of the teyrchain.
|
||||
#[command(name = "export-genesis-state")]
|
||||
ExportGenesisState(ExportGenesisHeadCommand),
|
||||
|
||||
/// Export the genesis wasm of the teyrchain.
|
||||
#[command(name = "export-genesis-wasm")]
|
||||
ExportGenesisWasm(ExportGenesisWasmCommand),
|
||||
}
|
||||
|
||||
/// Command for exporting the genesis head data of the teyrchain
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ExportGenesisHeadCommand {
|
||||
/// Output file name or stdout if unspecified.
|
||||
#[arg()]
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Command for exporting the genesis wasm file.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ExportGenesisWasmCommand {
|
||||
/// Output file name or stdout if unspecified.
|
||||
#[arg()]
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Parser)]
|
||||
#[group(skip)]
|
||||
pub struct RunCmd {
|
||||
#[allow(missing_docs)]
|
||||
#[clap(flatten)]
|
||||
pub base: sc_cli::RunCmd,
|
||||
|
||||
/// Id of the teyrchain this collator collates for.
|
||||
#[arg(long)]
|
||||
pub teyrchain_id: Option<u32>,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub subcommand: Option<Subcommand>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub run: RunCmd,
|
||||
}
|
||||
|
||||
impl SubstrateCli for Cli {
|
||||
fn impl_name() -> String {
|
||||
"Parity Pezkuwi".into()
|
||||
}
|
||||
|
||||
fn impl_version() -> String {
|
||||
"0.0.0".into()
|
||||
}
|
||||
|
||||
fn description() -> String {
|
||||
env!("CARGO_PKG_DESCRIPTION").into()
|
||||
}
|
||||
|
||||
fn author() -> String {
|
||||
env!("CARGO_PKG_AUTHORS").into()
|
||||
}
|
||||
|
||||
fn support_url() -> String {
|
||||
"https://github.com/pezkuwichain/pezkuwi-sdk/issues/new".into()
|
||||
}
|
||||
|
||||
fn copyright_start_year() -> i32 {
|
||||
2017
|
||||
}
|
||||
|
||||
fn executable_name() -> String {
|
||||
"adder-collator".into()
|
||||
}
|
||||
|
||||
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
|
||||
let id = if id.is_empty() { "pezkuwichain" } else { id };
|
||||
Ok(match id {
|
||||
"pezkuwichain-staging" =>
|
||||
Box::new(pezkuwi_service::chain_spec::pezkuwichain_staging_testnet_config()?),
|
||||
"pezkuwichain-local" =>
|
||||
Box::new(pezkuwi_service::chain_spec::pezkuwichain_local_testnet_config()?),
|
||||
"pezkuwichain" => Box::new(pezkuwi_service::chain_spec::pezkuwichain_config()?),
|
||||
path => {
|
||||
let path = std::path::PathBuf::from(path);
|
||||
Box::new(pezkuwi_service::PezkuwichainChainSpec::from_json_file(path)?)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,382 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Collator for the adder test teyrchain.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use futures::channel::oneshot;
|
||||
use futures_timer::Delay;
|
||||
use pezkuwi_node_primitives::{
|
||||
Collation, CollationResult, CollationSecondedSignal, CollatorFn, MaybeCompressedPoV, PoV,
|
||||
Statement,
|
||||
};
|
||||
use pezkuwi_primitives::{CollatorId, CollatorPair};
|
||||
use sp_core::{traits::SpawnNamed, Pair};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use test_teyrchain_adder::{execute, hash_state, BlockData, HeadData};
|
||||
|
||||
/// The amount we add when producing a new block.
|
||||
///
|
||||
/// This is a constant to make tests easily reproducible.
|
||||
const ADD: u64 = 2;
|
||||
|
||||
/// Calculates the head and state for the block with the given `number`.
|
||||
fn calculate_head_and_state_for_number(number: u64) -> (HeadData, u64) {
|
||||
let mut head =
|
||||
HeadData { number: 0, parent_hash: Default::default(), post_state: hash_state(0) };
|
||||
|
||||
let mut state = 0u64;
|
||||
|
||||
while head.number < number {
|
||||
let block = BlockData { state, add: ADD };
|
||||
head = execute(head.hash(), head.clone(), &block).expect("Produces valid block");
|
||||
state = state.wrapping_add(ADD);
|
||||
}
|
||||
|
||||
(head, state)
|
||||
}
|
||||
|
||||
/// The state of the adder teyrchain.
|
||||
struct State {
|
||||
head_to_state: HashMap<Arc<HeadData>, u64>,
|
||||
number_to_head: HashMap<u64, Arc<HeadData>>,
|
||||
/// Block number of the best block.
|
||||
best_block: u64,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Init the genesis state.
|
||||
fn genesis() -> Self {
|
||||
let genesis_state = Arc::new(calculate_head_and_state_for_number(0).0);
|
||||
|
||||
Self {
|
||||
head_to_state: vec![(genesis_state.clone(), 0)].into_iter().collect(),
|
||||
number_to_head: vec![(0, genesis_state)].into_iter().collect(),
|
||||
best_block: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the state and produce a new block based on the given `parent_head`.
|
||||
///
|
||||
/// Returns the new [`BlockData`] and the new [`HeadData`].
|
||||
fn advance(&mut self, parent_head: HeadData) -> (BlockData, HeadData) {
|
||||
self.best_block = parent_head.number;
|
||||
|
||||
let block = BlockData {
|
||||
state: self
|
||||
.head_to_state
|
||||
.get(&parent_head)
|
||||
.copied()
|
||||
.unwrap_or_else(|| calculate_head_and_state_for_number(parent_head.number).1),
|
||||
add: ADD,
|
||||
};
|
||||
|
||||
let new_head =
|
||||
execute(parent_head.hash(), parent_head, &block).expect("Produces valid block");
|
||||
|
||||
let new_head_arc = Arc::new(new_head.clone());
|
||||
self.head_to_state.insert(new_head_arc.clone(), block.state.wrapping_add(ADD));
|
||||
self.number_to_head.insert(new_head.number, new_head_arc);
|
||||
|
||||
(block, new_head)
|
||||
}
|
||||
}
|
||||
|
||||
/// Local collator state so we can compute how fast we are advancing
|
||||
/// per relay parent.
|
||||
#[derive(Default)]
|
||||
pub struct LocalCollatorState {
|
||||
/// First relay block number on which we've built on.
|
||||
first_relay_parent: Option<u32>,
|
||||
/// Last relay block number on which we've built on.
|
||||
last_relay_parent: Option<u32>,
|
||||
}
|
||||
|
||||
impl LocalCollatorState {
|
||||
fn advance(&mut self, new_relay_parent: u32, best_block: u64) {
|
||||
match (self.first_relay_parent, self.last_relay_parent) {
|
||||
(Some(first_relay_parent), Some(last_relay_parent)) => {
|
||||
// Compute the teyrchain velocity when relay parent changes vs our last
|
||||
// recorded relay parent. We do this to only print out the velocity
|
||||
// once per relay parent.
|
||||
if new_relay_parent > last_relay_parent {
|
||||
let building_for = (new_relay_parent - first_relay_parent) as f32;
|
||||
// Round it up, as we don't expect perfect runs in CI.
|
||||
let velocity = (best_block as f32 / building_for).ceil() as u32;
|
||||
|
||||
log::info!("Teyrchain velocity: {:}", velocity);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
|
||||
if self.first_relay_parent.is_none() {
|
||||
self.first_relay_parent = Some(new_relay_parent);
|
||||
}
|
||||
self.last_relay_parent = Some(new_relay_parent);
|
||||
}
|
||||
}
|
||||
|
||||
/// The collator of the adder teyrchain.
|
||||
pub struct Collator {
|
||||
state: Arc<Mutex<State>>,
|
||||
key: CollatorPair,
|
||||
seconded_collations: Arc<AtomicU32>,
|
||||
collator_state: Arc<Mutex<LocalCollatorState>>,
|
||||
}
|
||||
|
||||
impl Collator {
|
||||
/// Create a new collator instance with the state initialized as genesis.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(State::genesis())),
|
||||
key: CollatorPair::generate().0,
|
||||
seconded_collations: Arc::new(AtomicU32::new(0)),
|
||||
collator_state: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the SCALE encoded genesis head of the adder teyrchain.
|
||||
pub fn genesis_head(&self) -> Vec<u8> {
|
||||
self.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.number_to_head
|
||||
.get(&0)
|
||||
.expect("Genesis header exists")
|
||||
.encode()
|
||||
}
|
||||
|
||||
/// Get the validation code of the adder teyrchain.
|
||||
pub fn validation_code(&self) -> &[u8] {
|
||||
test_teyrchain_adder::wasm_binary_unwrap()
|
||||
}
|
||||
|
||||
/// Get the collator key.
|
||||
pub fn collator_key(&self) -> CollatorPair {
|
||||
self.key.clone()
|
||||
}
|
||||
|
||||
/// Get the collator id.
|
||||
pub fn collator_id(&self) -> CollatorId {
|
||||
self.key.public()
|
||||
}
|
||||
|
||||
/// Create the collation function.
|
||||
///
|
||||
/// This collation function can be plugged into the overseer to generate collations for the
|
||||
/// adder teyrchain.
|
||||
pub fn create_collation_function(
|
||||
&self,
|
||||
spawner: impl SpawnNamed + Clone + 'static,
|
||||
) -> CollatorFn {
|
||||
use futures::FutureExt as _;
|
||||
|
||||
let state = self.state.clone();
|
||||
let collator_state = self.collator_state.clone();
|
||||
let seconded_collations = self.seconded_collations.clone();
|
||||
|
||||
Box::new(move |relay_parent, validation_data| {
|
||||
let parent = HeadData::decode(&mut &validation_data.parent_head.0[..])
|
||||
.expect("Decodes parent head");
|
||||
|
||||
collator_state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.advance(validation_data.relay_parent_number, parent.number);
|
||||
|
||||
let (block_data, head_data) = state.lock().unwrap().advance(parent);
|
||||
|
||||
log::info!(
|
||||
"created a new collation on relay-parent({}): {:?}",
|
||||
relay_parent,
|
||||
block_data,
|
||||
);
|
||||
|
||||
let pov = PoV { block_data: block_data.encode().into() };
|
||||
|
||||
let collation = Collation {
|
||||
upward_messages: Default::default(),
|
||||
horizontal_messages: Default::default(),
|
||||
new_validation_code: None,
|
||||
head_data: head_data.encode().into(),
|
||||
proof_of_validity: MaybeCompressedPoV::Raw(pov.clone()),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: validation_data.relay_parent_number,
|
||||
};
|
||||
|
||||
let compressed_pov = pezkuwi_node_primitives::maybe_compress_pov(pov);
|
||||
|
||||
let (result_sender, recv) = oneshot::channel::<CollationSecondedSignal>();
|
||||
let seconded_collations = seconded_collations.clone();
|
||||
spawner.spawn(
|
||||
"adder-collator-seconded",
|
||||
None,
|
||||
async move {
|
||||
if let Ok(res) = recv.await {
|
||||
if !matches!(
|
||||
res.statement.payload(),
|
||||
Statement::Seconded(s) if s.descriptor.pov_hash() == compressed_pov.hash(),
|
||||
) {
|
||||
log::error!(
|
||||
"Seconded statement should match our collation: {:?}",
|
||||
res.statement.payload()
|
||||
);
|
||||
std::process::exit(-1);
|
||||
}
|
||||
|
||||
seconded_collations.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
async move { Some(CollationResult { collation, result_sender: Some(result_sender) }) }
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
/// Wait until `blocks` are built and enacted.
|
||||
pub async fn wait_for_blocks(&self, blocks: u64) {
|
||||
let start_block = self.state.lock().unwrap().best_block;
|
||||
loop {
|
||||
Delay::new(Duration::from_secs(1)).await;
|
||||
|
||||
let current_block = self.state.lock().unwrap().best_block;
|
||||
|
||||
if start_block + blocks <= current_block {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until `seconded` collations of this collator are seconded by a teyrchain validator.
|
||||
///
|
||||
/// The internal counter isn't de-duplicating the collations when counting the number of
|
||||
/// seconded collations. This means when one collation is seconded by X validators, we record X
|
||||
/// seconded messages.
|
||||
pub async fn wait_for_seconded_collations(&self, seconded: u32) {
|
||||
let seconded_collations = self.seconded_collations.clone();
|
||||
loop {
|
||||
Delay::new(Duration::from_secs(1)).await;
|
||||
|
||||
if seconded <= seconded_collations.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use futures::executor::block_on;
|
||||
use pezkuwi_primitives::PersistedValidationData;
|
||||
use pezkuwi_teyrchain_primitives::primitives::{ValidationParams, ValidationResult};
|
||||
|
||||
#[test]
|
||||
fn collator_works() {
|
||||
let spawner = sp_core::testing::TaskExecutor::new();
|
||||
let collator = Collator::new();
|
||||
let collation_function = collator.create_collation_function(spawner);
|
||||
|
||||
for i in 0..5 {
|
||||
let parent_head =
|
||||
collator.state.lock().unwrap().number_to_head.get(&i).unwrap().clone();
|
||||
|
||||
let validation_data = PersistedValidationData {
|
||||
parent_head: parent_head.encode().into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let collation =
|
||||
block_on(collation_function(Default::default(), &validation_data)).unwrap();
|
||||
validate_collation(&collator, (*parent_head).clone(), collation.collation);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_collation(collator: &Collator, parent_head: HeadData, collation: Collation) {
|
||||
use pezkuwi_node_core_pvf::testing::validate_candidate;
|
||||
|
||||
let block_data = match collation.proof_of_validity {
|
||||
MaybeCompressedPoV::Raw(pov) => pov.block_data,
|
||||
MaybeCompressedPoV::Compressed(_) => panic!("Only works with uncompressed povs"),
|
||||
};
|
||||
|
||||
let ret_buf = validate_candidate(
|
||||
collator.validation_code(),
|
||||
&ValidationParams {
|
||||
parent_head: parent_head.encode().into(),
|
||||
block_data,
|
||||
relay_parent_number: 1,
|
||||
relay_parent_storage_root: Default::default(),
|
||||
}
|
||||
.encode(),
|
||||
)
|
||||
.unwrap();
|
||||
let ret = ValidationResult::decode(&mut &ret_buf[..]).unwrap();
|
||||
|
||||
let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();
|
||||
assert_eq!(
|
||||
**collator
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.number_to_head
|
||||
.get(&(parent_head.number + 1))
|
||||
.unwrap(),
|
||||
new_head
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advance_to_state_when_parent_head_is_missing() {
|
||||
let collator = Collator::new();
|
||||
|
||||
let mut head = calculate_head_and_state_for_number(10).0;
|
||||
|
||||
for i in 1..10 {
|
||||
head = collator.state.lock().unwrap().advance(head).1;
|
||||
assert_eq!(10 + i, head.number);
|
||||
}
|
||||
|
||||
let collator = Collator::new();
|
||||
let mut second_head = collator
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.number_to_head
|
||||
.get(&0)
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.clone();
|
||||
|
||||
for _ in 1..20 {
|
||||
second_head = collator.state.lock().unwrap().advance(second_head.clone()).1;
|
||||
}
|
||||
|
||||
assert_eq!(second_head, head);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,142 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Collator for the adder test teyrchain.
|
||||
|
||||
use pezkuwi_cli::{Error, Result};
|
||||
use pezkuwi_node_primitives::CollationGenerationConfig;
|
||||
use pezkuwi_node_subsystem::messages::{CollationGenerationMessage, CollatorProtocolMessage};
|
||||
use pezkuwi_primitives::Id as ParaId;
|
||||
use sc_cli::{Error as SubstrateCliError, SubstrateCli};
|
||||
use sp_core::hexdisplay::HexDisplay;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
io::{self, Write},
|
||||
};
|
||||
use test_teyrchain_adder_collator::Collator;
|
||||
|
||||
/// The teyrchain ID to collate for in case it wasn't set explicitly through CLI.
|
||||
const DEFAULT_PARA_ID: ParaId = ParaId::new(100);
|
||||
|
||||
mod cli;
|
||||
use cli::Cli;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::from_args();
|
||||
|
||||
match cli.subcommand {
|
||||
Some(cli::Subcommand::ExportGenesisState(params)) => {
|
||||
let collator = Collator::new();
|
||||
let output_buf =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.genesis_head())).into_bytes();
|
||||
|
||||
if let Some(output) = params.output {
|
||||
std::fs::write(output, output_buf)?;
|
||||
} else {
|
||||
std::io::stdout().write_all(&output_buf)?;
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
},
|
||||
Some(cli::Subcommand::ExportGenesisWasm(params)) => {
|
||||
let collator = Collator::new();
|
||||
let output_buf =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.validation_code())).into_bytes();
|
||||
|
||||
if let Some(output) = params.output {
|
||||
fs::write(output, output_buf)?;
|
||||
} else {
|
||||
io::stdout().write_all(&output_buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
let runner = cli.create_runner(&cli.run.base).map_err(|e| {
|
||||
SubstrateCliError::Application(
|
||||
Box::new(e) as Box<(dyn 'static + Send + Sync + std::error::Error)>
|
||||
)
|
||||
})?;
|
||||
|
||||
runner.run_node_until_exit(|config| async move {
|
||||
let collator = Collator::new();
|
||||
|
||||
let full_node = pezkuwi_service::build_full(
|
||||
config,
|
||||
pezkuwi_service::NewFullParams {
|
||||
is_teyrchain_node: pezkuwi_service::IsTeyrchainNode::Collator(
|
||||
collator.collator_key(),
|
||||
),
|
||||
enable_beefy: false,
|
||||
force_authoring_backoff: false,
|
||||
telemetry_worker_handle: None,
|
||||
|
||||
// Collators don't spawn PVF workers, so we can disable version checks.
|
||||
node_version: None,
|
||||
secure_validator_mode: false,
|
||||
workers_path: None,
|
||||
workers_names: None,
|
||||
|
||||
overseer_gen: pezkuwi_service::CollatorOverseerGen,
|
||||
overseer_message_channel_capacity_override: None,
|
||||
malus_finality_delay: None,
|
||||
hwbench: None,
|
||||
execute_workers_max_num: None,
|
||||
prepare_workers_hard_max_num: None,
|
||||
prepare_workers_soft_max_num: None,
|
||||
keep_finalized_for: None,
|
||||
invulnerable_ah_collators: HashSet::new(),
|
||||
collator_protocol_hold_off: None,
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut overseer_handle = full_node
|
||||
.overseer_handle
|
||||
.expect("Overseer handle should be initialized for collators");
|
||||
|
||||
let genesis_head_hex =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.genesis_head()));
|
||||
let validation_code_hex =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.validation_code()));
|
||||
|
||||
let para_id = cli.run.teyrchain_id.map(ParaId::from).unwrap_or(DEFAULT_PARA_ID);
|
||||
|
||||
log::info!("Running adder collator for teyrchain id: {}", para_id);
|
||||
log::info!("Genesis state: {}", genesis_head_hex);
|
||||
log::info!("Validation code: {}", validation_code_hex);
|
||||
|
||||
let config = CollationGenerationConfig {
|
||||
key: collator.collator_key(),
|
||||
collator: Some(
|
||||
collator.create_collation_function(full_node.task_manager.spawn_handle()),
|
||||
),
|
||||
para_id,
|
||||
};
|
||||
overseer_handle
|
||||
.send_msg(CollationGenerationMessage::Initialize(config), "Collator")
|
||||
.await;
|
||||
|
||||
overseer_handle
|
||||
.send_msg(CollatorProtocolMessage::CollateOn(para_id), "Collator")
|
||||
.await;
|
||||
|
||||
Ok(full_node.task_manager)
|
||||
})
|
||||
},
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Integration test that ensures that we can build and include teyrchain
|
||||
//! blocks of the adder teyrchain.
|
||||
|
||||
// If this test is failing, make sure to run all tests with the `real-overseer` feature being
|
||||
// enabled.
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn collating_using_adder_collator() {
|
||||
use pezkuwi_primitives::Id as ParaId;
|
||||
use sp_keyring::Sr25519Keyring::*;
|
||||
|
||||
let mut builder = sc_cli::LoggerBuilder::new("");
|
||||
builder.with_colors(false);
|
||||
builder.init().expect("Set up logger");
|
||||
|
||||
let para_id = ParaId::from(100);
|
||||
|
||||
let alice_config = pezkuwi_test_service::node_config(
|
||||
|| {},
|
||||
tokio::runtime::Handle::current(),
|
||||
Alice,
|
||||
Vec::new(),
|
||||
true,
|
||||
);
|
||||
|
||||
let mut workers_path = std::env::current_exe().unwrap();
|
||||
workers_path.pop();
|
||||
workers_path.pop();
|
||||
|
||||
// start alice
|
||||
let alice =
|
||||
pezkuwi_test_service::run_validator_node(alice_config, Some(workers_path.clone())).await;
|
||||
|
||||
let bob_config = pezkuwi_test_service::node_config(
|
||||
|| {},
|
||||
tokio::runtime::Handle::current(),
|
||||
Bob,
|
||||
vec![alice.addr.clone()],
|
||||
true,
|
||||
);
|
||||
|
||||
// start bob
|
||||
let bob = pezkuwi_test_service::run_validator_node(bob_config, Some(workers_path)).await;
|
||||
|
||||
let collator = test_teyrchain_adder_collator::Collator::new();
|
||||
|
||||
// register teyrchain
|
||||
alice
|
||||
.register_teyrchain(para_id, collator.validation_code().to_vec(), collator.genesis_head())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// run the collator node
|
||||
let mut charlie = pezkuwi_test_service::run_collator_node(
|
||||
tokio::runtime::Handle::current(),
|
||||
Charlie,
|
||||
|| {},
|
||||
vec![alice.addr.clone(), bob.addr.clone()],
|
||||
collator.collator_key(),
|
||||
)
|
||||
.await;
|
||||
|
||||
charlie
|
||||
.register_collator(
|
||||
collator.collator_key(),
|
||||
para_id,
|
||||
collator.create_collation_function(charlie.task_manager.spawn_handle()),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait until the teyrchain has 4 blocks produced.
|
||||
collator.wait_for_blocks(4).await;
|
||||
|
||||
// Wait until the collator received `12` seconded statements for its collations.
|
||||
collator.wait_for_seconded_collations(12).await;
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Basic teyrchain that adds a number as part of its state.
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use tiny_keccak::{Hasher as _, Keccak};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod wasm_validation;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[global_allocator]
|
||||
static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
|
||||
|
||||
// Make the WASM binary available.
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
fn keccak256(input: &[u8]) -> [u8; 32] {
|
||||
let mut out = [0u8; 32];
|
||||
let mut keccak256 = Keccak::v256();
|
||||
keccak256.update(input);
|
||||
keccak256.finalize(&mut out);
|
||||
out
|
||||
}
|
||||
|
||||
/// Wasm binary unwrapped. If built with `BUILD_DUMMY_WASM_BINARY`, the function panics.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn wasm_binary_unwrap() -> &'static [u8] {
|
||||
WASM_BINARY.expect(
|
||||
"Development wasm binary is not available. Testing is only \
|
||||
supported with the flag disabled.",
|
||||
)
|
||||
}
|
||||
|
||||
/// Head data for this teyrchain.
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq, Encode, Decode, Debug)]
|
||||
pub struct HeadData {
|
||||
/// Block number
|
||||
pub number: u64,
|
||||
/// parent block keccak256
|
||||
pub parent_hash: [u8; 32],
|
||||
/// hash of post-execution state.
|
||||
pub post_state: [u8; 32],
|
||||
}
|
||||
|
||||
impl HeadData {
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
keccak256(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Block data for this teyrchain.
|
||||
#[derive(Default, Clone, Encode, Decode, Debug)]
|
||||
pub struct BlockData {
|
||||
/// State to begin from.
|
||||
pub state: u64,
|
||||
/// Amount to add (wrapping)
|
||||
pub add: u64,
|
||||
}
|
||||
|
||||
pub fn hash_state(state: u64) -> [u8; 32] {
|
||||
keccak256(state.encode().as_slice())
|
||||
}
|
||||
|
||||
/// Start state mismatched with parent header's state hash.
|
||||
#[derive(Debug)]
|
||||
pub struct StateMismatch;
|
||||
|
||||
/// Execute a block body on top of given parent head, producing new parent head
|
||||
/// if valid.
|
||||
pub fn execute(
|
||||
parent_hash: [u8; 32],
|
||||
parent_head: HeadData,
|
||||
block_data: &BlockData,
|
||||
) -> Result<HeadData, StateMismatch> {
|
||||
assert_eq!(parent_hash, parent_head.hash());
|
||||
|
||||
if hash_state(block_data.state) != parent_head.post_state {
|
||||
return Err(StateMismatch);
|
||||
}
|
||||
|
||||
let new_state = block_data.state.wrapping_add(block_data.add);
|
||||
|
||||
Ok(HeadData { number: parent_head.number + 1, parent_hash, post_state: hash_state(new_state) })
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! WASM validation for adder teyrchain.
|
||||
|
||||
use crate::{BlockData, HeadData};
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use core::panic;
|
||||
use pezkuwi_teyrchain_primitives::primitives::{HeadData as GenericHeadData, ValidationResult};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 {
|
||||
let params = unsafe { pezkuwi_teyrchain_primitives::load_params(params, len) };
|
||||
let parent_head =
|
||||
HeadData::decode(&mut ¶ms.parent_head.0[..]).expect("invalid parent head format.");
|
||||
|
||||
let block_data =
|
||||
BlockData::decode(&mut ¶ms.block_data.0[..]).expect("invalid block data format.");
|
||||
|
||||
let parent_hash = crate::keccak256(¶ms.parent_head.0[..]);
|
||||
|
||||
let new_head = crate::execute(parent_hash, parent_head, &block_data).expect("Executes block");
|
||||
pezkuwi_teyrchain_primitives::write_result(&ValidationResult {
|
||||
head_data: GenericHeadData(new_head.encode()),
|
||||
new_validation_code: None,
|
||||
upward_messages: alloc::vec::Vec::new().try_into().expect("empty vec fits into bounds"),
|
||||
horizontal_messages: alloc::vec::Vec::new().try_into().expect("empty vec fits into bounds"),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: params.relay_parent_number,
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "test-teyrchain-halt"
|
||||
description = "Test teyrchain which executes forever"
|
||||
build = "build.rs"
|
||||
publish = false
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
rustversion = { workspace = true }
|
||||
substrate-wasm-builder = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = []
|
||||
@@ -0,0 +1,37 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use substrate_wasm_builder::WasmBuilder;
|
||||
|
||||
fn main() {
|
||||
WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.export_heap_base()
|
||||
.disable_runtime_version_section_check()
|
||||
.build();
|
||||
|
||||
enable_alloc_error_handler();
|
||||
}
|
||||
|
||||
#[rustversion::before(1.68)]
|
||||
fn enable_alloc_error_handler() {
|
||||
if !cfg!(feature = "std") {
|
||||
println!("cargo:rustc-cfg=enable_alloc_error_handler");
|
||||
}
|
||||
}
|
||||
|
||||
#[rustversion::since(1.68)]
|
||||
fn enable_alloc_error_handler() {}
|
||||
@@ -0,0 +1,52 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Basic teyrchain that executes forever.
|
||||
|
||||
#![no_std]
|
||||
#![cfg_attr(enable_alloc_error_handler, feature(alloc_error_handler))]
|
||||
|
||||
// Make the WASM binary available.
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
/// Wasm binary unwrapped. If built with `BUILD_DUMMY_WASM_BINARY`, the function panics.
|
||||
pub fn wasm_binary_unwrap() -> &'static [u8] {
|
||||
WASM_BINARY.expect(
|
||||
"Development wasm binary is not available. Testing is only \
|
||||
supported with the flag disabled.",
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[panic_handler]
|
||||
pub fn panic(_info: &core::panic::PanicInfo) -> ! {
|
||||
core::arch::wasm32::unreachable();
|
||||
}
|
||||
|
||||
#[cfg(enable_alloc_error_handler)]
|
||||
#[alloc_error_handler]
|
||||
#[no_mangle]
|
||||
pub fn oom(_: core::alloc::Layout) -> ! {
|
||||
core::intrinsics::abort();
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[no_mangle]
|
||||
pub extern "C" fn validate_block(_params: *const u8, _len: usize) -> u64 {
|
||||
loop {}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
[package]
|
||||
name = "test-teyrchain-undying"
|
||||
description = "Test teyrchain for zombienet integration tests"
|
||||
build = "build.rs"
|
||||
publish = false
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[dependencies]
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
dlmalloc = { features = ["global"], workspace = true }
|
||||
log = { workspace = true }
|
||||
pezkuwi-primitives = { workspace = true, default-features = false }
|
||||
pezkuwi-teyrchain-primitives = { features = ["wasm-api"], workspace = true }
|
||||
tiny-keccak = { features = ["keccak"], workspace = true }
|
||||
|
||||
# We need to make sure the global allocator is disabled until we have support of full substrate externalities
|
||||
sp-io = { features = ["disable_allocator"], workspace = true }
|
||||
|
||||
[build-dependencies]
|
||||
substrate-wasm-builder = { workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
default = ["std"]
|
||||
std = [
|
||||
"codec/std",
|
||||
"log/std",
|
||||
"pezkuwi-primitives/std",
|
||||
"pezkuwi-teyrchain-primitives/std",
|
||||
"sp-io/std",
|
||||
]
|
||||
runtime-benchmarks = [
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"sp-io/runtime-benchmarks",
|
||||
"substrate-wasm-builder/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
use substrate_wasm_builder::WasmBuilder;
|
||||
|
||||
fn main() {
|
||||
WasmBuilder::new()
|
||||
.with_current_project()
|
||||
.export_heap_base()
|
||||
.disable_runtime_version_section_check()
|
||||
.build()
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
[package]
|
||||
name = "test-teyrchain-undying-collator"
|
||||
description = "Collator for the undying test teyrchain"
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
version = "1.0.0"
|
||||
authors.workspace = true
|
||||
publish = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "undying-collator"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = { features = ["derive"], workspace = true }
|
||||
codec = { features = ["derive"], workspace = true }
|
||||
futures = { workspace = true }
|
||||
futures-timer = { workspace = true }
|
||||
log = { workspace = true, default-features = true }
|
||||
|
||||
pezkuwi-cli = { workspace = true, default-features = true }
|
||||
pezkuwi-erasure-coding = { workspace = true, default-features = true }
|
||||
pezkuwi-node-primitives = { workspace = true, default-features = true }
|
||||
pezkuwi-node-subsystem = { workspace = true, default-features = true }
|
||||
pezkuwi-primitives = { workspace = true, default-features = true }
|
||||
pezkuwi-service = { features = [
|
||||
"pezkuwichain-native",
|
||||
], workspace = true, default-features = true }
|
||||
test-teyrchain-undying = { workspace = true }
|
||||
|
||||
sc-cli = { workspace = true, default-features = true }
|
||||
sc-client-api = { workspace = true, default-features = true }
|
||||
sc-service = { workspace = true, default-features = true }
|
||||
sp-core = { workspace = true, default-features = true }
|
||||
|
||||
[dev-dependencies]
|
||||
pezkuwi-node-core-pvf = { features = [
|
||||
"test-utils",
|
||||
], workspace = true, default-features = true }
|
||||
pezkuwi-test-service = { workspace = true }
|
||||
pezkuwi-teyrchain-primitives = { workspace = true, default-features = true }
|
||||
|
||||
sp-keyring = { workspace = true, default-features = true }
|
||||
|
||||
tokio = { features = ["macros"], workspace = true, default-features = true }
|
||||
|
||||
[features]
|
||||
runtime-benchmarks = [
|
||||
"pezkuwi-cli/runtime-benchmarks",
|
||||
"pezkuwi-erasure-coding/runtime-benchmarks",
|
||||
"pezkuwi-node-core-pvf/runtime-benchmarks",
|
||||
"pezkuwi-node-primitives/runtime-benchmarks",
|
||||
"pezkuwi-node-subsystem/runtime-benchmarks",
|
||||
"pezkuwi-primitives/runtime-benchmarks",
|
||||
"pezkuwi-service/runtime-benchmarks",
|
||||
"pezkuwi-test-service/runtime-benchmarks",
|
||||
"pezkuwi-teyrchain-primitives/runtime-benchmarks",
|
||||
"sc-cli/runtime-benchmarks",
|
||||
"sc-client-api/runtime-benchmarks",
|
||||
"sc-service/runtime-benchmarks",
|
||||
"sp-keyring/runtime-benchmarks",
|
||||
"test-teyrchain-undying/runtime-benchmarks",
|
||||
]
|
||||
@@ -0,0 +1,156 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Pezkuwi CLI library.
|
||||
|
||||
use clap::Parser;
|
||||
use sc_cli::SubstrateCli;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Sub-commands supported by the collator.
|
||||
#[derive(Debug, Parser)]
|
||||
pub enum Subcommand {
|
||||
/// Export the genesis state of the teyrchain.
|
||||
#[command(name = "export-genesis-state")]
|
||||
ExportGenesisState(ExportGenesisHeadCommand),
|
||||
|
||||
/// Export the genesis wasm of the teyrchain.
|
||||
#[command(name = "export-genesis-wasm")]
|
||||
ExportGenesisWasm(ExportGenesisWasmCommand),
|
||||
}
|
||||
|
||||
/// Command for exporting the genesis head data of the teyrchain
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ExportGenesisHeadCommand {
|
||||
/// Output file name or stdout if unspecified.
|
||||
#[arg()]
|
||||
pub output: Option<PathBuf>,
|
||||
|
||||
/// Id of the teyrchain this collator collates for.
|
||||
#[arg(long, default_value_t = 100)]
|
||||
pub teyrchain_id: u32,
|
||||
|
||||
/// The target raw PoV size in bytes. Minimum value is 64.
|
||||
#[arg(long, default_value_t = 1024)]
|
||||
pub pov_size: usize,
|
||||
|
||||
/// The PVF execution complexity. Actually specifies how many iterations/signatures
|
||||
/// we compute per block.
|
||||
#[arg(long, default_value_t = 1)]
|
||||
pub pvf_complexity: u32,
|
||||
}
|
||||
|
||||
/// Command for exporting the genesis wasm file.
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct ExportGenesisWasmCommand {
|
||||
/// Output file name or stdout if unspecified.
|
||||
#[arg()]
|
||||
pub output: Option<PathBuf>,
|
||||
}
|
||||
|
||||
/// Enum representing different types of malicious behaviors for collators.
|
||||
#[derive(Debug, Parser, Clone, PartialEq, clap::ValueEnum)]
|
||||
pub enum MalusType {
|
||||
/// No malicious behavior.
|
||||
None,
|
||||
/// Submit the same collations to all assigned cores.
|
||||
DuplicateCollations,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Parser)]
|
||||
#[group(skip)]
|
||||
pub struct RunCmd {
|
||||
#[allow(missing_docs)]
|
||||
#[clap(flatten)]
|
||||
pub base: sc_cli::RunCmd,
|
||||
|
||||
/// Id of the teyrchain this collator collates for.
|
||||
#[arg(long, default_value_t = 2000)]
|
||||
pub teyrchain_id: u32,
|
||||
|
||||
/// The target raw PoV size in bytes. Minimum value is 64.
|
||||
#[arg(long, default_value_t = 1024)]
|
||||
pub pov_size: usize,
|
||||
|
||||
/// The PVF execution complexity. Actually specifies how many iterations/signatures
|
||||
/// we compute per block.
|
||||
#[arg(long, default_value_t = 1)]
|
||||
pub pvf_complexity: u32,
|
||||
|
||||
/// Specifies the malicious behavior of the collator.
|
||||
#[arg(long, value_enum, default_value_t = MalusType::None)]
|
||||
pub malus_type: MalusType,
|
||||
|
||||
/// Whether or not the collator should send the experimental ApprovedPeer UMP signal.
|
||||
#[arg(long)]
|
||||
pub experimental_send_approved_peer: bool,
|
||||
}
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[derive(Debug, Parser)]
|
||||
pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub subcommand: Option<Subcommand>,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub run: RunCmd,
|
||||
}
|
||||
|
||||
impl SubstrateCli for Cli {
|
||||
fn impl_name() -> String {
|
||||
"Parity Zombienet/Undying".into()
|
||||
}
|
||||
|
||||
fn impl_version() -> String {
|
||||
env!("CARGO_PKG_VERSION").into()
|
||||
}
|
||||
|
||||
fn description() -> String {
|
||||
env!("CARGO_PKG_DESCRIPTION").into()
|
||||
}
|
||||
|
||||
fn author() -> String {
|
||||
env!("CARGO_PKG_AUTHORS").into()
|
||||
}
|
||||
|
||||
fn support_url() -> String {
|
||||
"https://github.com/pezkuwichain/pezkuwi-sdk/issues/new".into()
|
||||
}
|
||||
|
||||
fn copyright_start_year() -> i32 {
|
||||
2022
|
||||
}
|
||||
|
||||
fn executable_name() -> String {
|
||||
"undying-collator".into()
|
||||
}
|
||||
|
||||
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn sc_service::ChainSpec>, String> {
|
||||
let id = if id.is_empty() { "pezkuwichain" } else { id };
|
||||
Ok(match id {
|
||||
"pezkuwichain-staging" =>
|
||||
Box::new(pezkuwi_service::chain_spec::pezkuwichain_staging_testnet_config()?),
|
||||
"pezkuwichain-local" =>
|
||||
Box::new(pezkuwi_service::chain_spec::pezkuwichain_local_testnet_config()?),
|
||||
"pezkuwichain" => Box::new(pezkuwi_service::chain_spec::pezkuwichain_config()?),
|
||||
path => {
|
||||
let path = std::path::PathBuf::from(path);
|
||||
Box::new(pezkuwi_service::PezkuwichainChainSpec::from_json_file(path)?)
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,747 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Collator for the `Undying` test teyrchain.
|
||||
|
||||
use codec::{Decode, Encode};
|
||||
use futures::{channel::oneshot, StreamExt};
|
||||
use futures_timer::Delay;
|
||||
use pezkuwi_cli::ProvideRuntimeApi;
|
||||
use pezkuwi_node_primitives::{
|
||||
maybe_compress_pov, AvailableData, Collation, CollationResult, CollationSecondedSignal,
|
||||
CollatorFn, MaybeCompressedPoV, PoV, Statement, UpwardMessages,
|
||||
};
|
||||
use pezkuwi_node_subsystem::messages::CollatorProtocolMessage;
|
||||
use pezkuwi_primitives::{
|
||||
CandidateCommitments, CandidateDescriptorV2, CandidateReceiptV2, ClaimQueueOffset, CollatorId,
|
||||
CollatorPair, CoreIndex, Hash, Id as ParaId, OccupiedCoreAssumption,
|
||||
DEFAULT_CLAIM_QUEUE_OFFSET,
|
||||
};
|
||||
use pezkuwi_service::{Handle, NewFull, TeyrchainHost};
|
||||
use sc_client_api::client::BlockchainEvents;
|
||||
use sp_core::Pair;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Arc, Mutex,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use test_teyrchain_undying::{
|
||||
execute, hash_state, BlockData, GraveyardState, HeadData, StateMismatch,
|
||||
};
|
||||
|
||||
pub const LOG_TARGET: &str = "teyrchain::undying-collator";
|
||||
|
||||
/// Default PoV size which also drives state size.
|
||||
const DEFAULT_POV_SIZE: usize = 1000;
|
||||
/// Default PVF time complexity - 1 signature per block.
|
||||
const DEFAULT_PVF_COMPLEXITY: u32 = 1;
|
||||
|
||||
/// Calculates the head and state for the block with the given `number`.
|
||||
fn calculate_head_and_state_for_number(
|
||||
number: u64,
|
||||
graveyard_size: usize,
|
||||
pvf_complexity: u32,
|
||||
experimental_send_approved_peer: bool,
|
||||
) -> Result<(HeadData, GraveyardState), StateMismatch> {
|
||||
let index = 0u64;
|
||||
let mut graveyard = vec![0u8; graveyard_size * graveyard_size];
|
||||
let zombies = 0;
|
||||
let seal = [0u8; 32];
|
||||
let core_selector_number = 0;
|
||||
|
||||
// Ensure a larger compressed PoV.
|
||||
graveyard.iter_mut().enumerate().for_each(|(i, grave)| {
|
||||
*grave = i as u8;
|
||||
});
|
||||
|
||||
let mut state = GraveyardState { index, graveyard, zombies, seal, core_selector_number };
|
||||
let mut head =
|
||||
HeadData { number: 0, parent_hash: Hash::default().into(), post_state: hash_state(&state) };
|
||||
|
||||
while head.number < number {
|
||||
let block = BlockData {
|
||||
state,
|
||||
tombstones: 1_000,
|
||||
iterations: pvf_complexity,
|
||||
experimental_send_approved_peer,
|
||||
};
|
||||
let (new_head, new_state, _) = execute(head.hash(), head.clone(), block)?;
|
||||
head = new_head;
|
||||
state = new_state;
|
||||
}
|
||||
|
||||
Ok((head, state))
|
||||
}
|
||||
|
||||
/// The state of the undying teyrchain.
|
||||
struct State {
|
||||
// We need to keep these around until the including relay chain blocks are finalized.
|
||||
// This is because disputes can trigger reverts up to last finalized block, so we
|
||||
// want that state to collate on older relay chain heads.
|
||||
head_to_state: HashMap<Arc<HeadData>, GraveyardState>,
|
||||
number_to_head: HashMap<u64, Arc<HeadData>>,
|
||||
/// Block number of the best block.
|
||||
best_block: u64,
|
||||
/// PVF time complexity.
|
||||
pvf_complexity: u32,
|
||||
/// Defines the state size (Vec<u8>). Our PoV includes the entire state so this value will
|
||||
/// drive the PoV size.
|
||||
/// Important note: block execution heavily clones this state, so something like 300.000 is
|
||||
/// the max value here, otherwise we'll get OOM during wasm execution.
|
||||
/// TODO: Implement a static state, and use `ballast` to inflate the PoV size. This way
|
||||
/// we can just discard the `ballast` before processing the block.
|
||||
graveyard_size: usize,
|
||||
experimental_send_approved_peer: bool,
|
||||
}
|
||||
|
||||
impl State {
|
||||
/// Init the genesis state.
|
||||
fn genesis(
|
||||
graveyard_size: usize,
|
||||
pvf_complexity: u32,
|
||||
experimental_send_approved_peer: bool,
|
||||
) -> Self {
|
||||
let index = 0u64;
|
||||
let mut graveyard = vec![0u8; graveyard_size * graveyard_size];
|
||||
let zombies = 0;
|
||||
let seal = [0u8; 32];
|
||||
let core_selector_number = 0;
|
||||
|
||||
// Ensure a larger compressed PoV.
|
||||
graveyard.iter_mut().enumerate().for_each(|(i, grave)| {
|
||||
*grave = i as u8;
|
||||
});
|
||||
|
||||
let state = GraveyardState { index, graveyard, zombies, seal, core_selector_number };
|
||||
|
||||
let head_data =
|
||||
HeadData { number: 0, parent_hash: Default::default(), post_state: hash_state(&state) };
|
||||
let head_data = Arc::new(head_data);
|
||||
|
||||
Self {
|
||||
head_to_state: vec![(head_data.clone(), state.clone())].into_iter().collect(),
|
||||
number_to_head: vec![(0, head_data)].into_iter().collect(),
|
||||
best_block: 0,
|
||||
pvf_complexity,
|
||||
graveyard_size,
|
||||
experimental_send_approved_peer,
|
||||
}
|
||||
}
|
||||
|
||||
/// Advance the state and produce a new block based on the given `parent_head`.
|
||||
///
|
||||
/// Returns the new [`BlockData`] and the new [`HeadData`].
|
||||
fn advance(
|
||||
&mut self,
|
||||
parent_head: HeadData,
|
||||
) -> Result<(BlockData, HeadData, UpwardMessages), StateMismatch> {
|
||||
self.best_block = parent_head.number;
|
||||
|
||||
let state = if let Some(state) = self
|
||||
.number_to_head
|
||||
.get(&self.best_block)
|
||||
.and_then(|head_data| self.head_to_state.get(head_data).cloned())
|
||||
{
|
||||
state
|
||||
} else {
|
||||
let (_, state) = calculate_head_and_state_for_number(
|
||||
parent_head.number,
|
||||
self.graveyard_size,
|
||||
self.pvf_complexity,
|
||||
self.experimental_send_approved_peer,
|
||||
)?;
|
||||
state
|
||||
};
|
||||
|
||||
// Start with prev state and transaction to execute (place 1000 tombstones).
|
||||
let block = BlockData {
|
||||
state,
|
||||
tombstones: 1000,
|
||||
iterations: self.pvf_complexity,
|
||||
experimental_send_approved_peer: self.experimental_send_approved_peer,
|
||||
};
|
||||
|
||||
let (new_head, new_state, upward_messages) =
|
||||
execute(parent_head.hash(), parent_head, block.clone())?;
|
||||
|
||||
let new_head_arc = Arc::new(new_head.clone());
|
||||
|
||||
self.head_to_state.insert(new_head_arc.clone(), new_state);
|
||||
self.number_to_head.insert(new_head.number, new_head_arc);
|
||||
|
||||
Ok((block, new_head, upward_messages))
|
||||
}
|
||||
}
|
||||
|
||||
/// The collator of the undying teyrchain.
|
||||
pub struct Collator {
|
||||
state: Arc<Mutex<State>>,
|
||||
key: CollatorPair,
|
||||
seconded_collations: Arc<AtomicU32>,
|
||||
}
|
||||
|
||||
impl Default for Collator {
|
||||
fn default() -> Self {
|
||||
Self::new(DEFAULT_POV_SIZE, DEFAULT_PVF_COMPLEXITY, false)
|
||||
}
|
||||
}
|
||||
|
||||
impl Collator {
|
||||
/// Create a new collator instance with the state initialized from genesis and `pov_size`
|
||||
/// parameter. The same parameter needs to be passed when exporting the genesis state.
|
||||
pub fn new(
|
||||
pov_size: usize,
|
||||
pvf_complexity: u32,
|
||||
experimental_send_approved_peer: bool,
|
||||
) -> Self {
|
||||
let graveyard_size = ((pov_size / std::mem::size_of::<u8>()) as f64).sqrt().ceil() as usize;
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"PoV target size: {} bytes. Graveyard size: ({} x {})",
|
||||
pov_size,
|
||||
graveyard_size,
|
||||
graveyard_size,
|
||||
);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"PVF time complexity: {}",
|
||||
pvf_complexity,
|
||||
);
|
||||
|
||||
Self {
|
||||
state: Arc::new(Mutex::new(State::genesis(
|
||||
graveyard_size,
|
||||
pvf_complexity,
|
||||
experimental_send_approved_peer,
|
||||
))),
|
||||
key: CollatorPair::generate().0,
|
||||
seconded_collations: Arc::new(AtomicU32::new(0)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the SCALE encoded genesis head of the teyrchain.
|
||||
pub fn genesis_head(&self) -> Vec<u8> {
|
||||
self.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.number_to_head
|
||||
.get(&0)
|
||||
.expect("Genesis header exists")
|
||||
.encode()
|
||||
}
|
||||
|
||||
/// Get the validation code of the undying teyrchain.
|
||||
pub fn validation_code(&self) -> &[u8] {
|
||||
test_teyrchain_undying::wasm_binary_unwrap()
|
||||
}
|
||||
|
||||
/// Get the collator key.
|
||||
pub fn collator_key(&self) -> CollatorPair {
|
||||
self.key.clone()
|
||||
}
|
||||
|
||||
/// Get the collator id.
|
||||
pub fn collator_id(&self) -> CollatorId {
|
||||
self.key.public()
|
||||
}
|
||||
|
||||
/// Create the collation function.
|
||||
///
|
||||
/// This collation function can be plugged into the overseer to generate collations for the
|
||||
/// undying teyrchain.
|
||||
pub fn create_collation_function(
|
||||
&self,
|
||||
spawner: impl SpawnNamed + Clone + 'static,
|
||||
) -> CollatorFn {
|
||||
use futures::FutureExt as _;
|
||||
|
||||
let state = self.state.clone();
|
||||
let seconded_collations = self.seconded_collations.clone();
|
||||
|
||||
Box::new(move |relay_parent, validation_data| {
|
||||
let parent = match HeadData::decode(&mut &validation_data.parent_head.0[..]) {
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Requested to build on top of malformed head-data: {:?}",
|
||||
err,
|
||||
);
|
||||
return futures::future::ready(None).boxed();
|
||||
},
|
||||
Ok(p) => p,
|
||||
};
|
||||
|
||||
let (block_data, head_data, upward_messages) =
|
||||
match state.lock().unwrap().advance(parent.clone()) {
|
||||
Err(err) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Unable to build on top of {:?}: {:?}",
|
||||
parent,
|
||||
err,
|
||||
);
|
||||
return futures::future::ready(None).boxed();
|
||||
},
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"created a new collation on relay-parent({}): {:?}",
|
||||
relay_parent,
|
||||
head_data,
|
||||
);
|
||||
|
||||
// The pov is the actually the initial state and the transactions.
|
||||
let pov = PoV { block_data: block_data.encode().into() };
|
||||
|
||||
let collation = Collation {
|
||||
upward_messages,
|
||||
horizontal_messages: Default::default(),
|
||||
new_validation_code: None,
|
||||
head_data: head_data.encode().into(),
|
||||
proof_of_validity: MaybeCompressedPoV::Raw(pov.clone()),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: validation_data.relay_parent_number,
|
||||
};
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Raw PoV size for collation: {} bytes",
|
||||
pov.block_data.0.len(),
|
||||
);
|
||||
let compressed_pov = maybe_compress_pov(pov);
|
||||
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Compressed PoV size for collation: {} bytes",
|
||||
compressed_pov.block_data.0.len(),
|
||||
);
|
||||
|
||||
let (result_sender, recv) = oneshot::channel::<CollationSecondedSignal>();
|
||||
let seconded_collations = seconded_collations.clone();
|
||||
spawner.spawn(
|
||||
"undying-collator-seconded",
|
||||
None,
|
||||
async move {
|
||||
if let Ok(res) = recv.await {
|
||||
if !matches!(
|
||||
res.statement.payload(),
|
||||
Statement::Seconded(s) if s.descriptor.pov_hash() == compressed_pov.hash(),
|
||||
) {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Seconded statement should match our collation: {:?}",
|
||||
res.statement.payload(),
|
||||
);
|
||||
}
|
||||
|
||||
seconded_collations.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
.boxed(),
|
||||
);
|
||||
|
||||
async move { Some(CollationResult { collation, result_sender: Some(result_sender) }) }
|
||||
.boxed()
|
||||
})
|
||||
}
|
||||
|
||||
/// Wait until `blocks` are built and enacted.
|
||||
pub async fn wait_for_blocks(&self, blocks: u64) {
|
||||
let start_block = self.state.lock().unwrap().best_block;
|
||||
loop {
|
||||
Delay::new(Duration::from_secs(1)).await;
|
||||
|
||||
let current_block = self.state.lock().unwrap().best_block;
|
||||
|
||||
if start_block + blocks <= current_block {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait until `seconded` collations of this collator are seconded by a teyrchain validator.
|
||||
///
|
||||
/// The internal counter isn't de-duplicating the collations when counting the number of
|
||||
/// seconded collations. This means when one collation is seconded by X validators, we record X
|
||||
/// seconded messages.
|
||||
pub async fn wait_for_seconded_collations(&self, seconded: u32) {
|
||||
let seconded_collations = self.seconded_collations.clone();
|
||||
loop {
|
||||
Delay::new(Duration::from_secs(1)).await;
|
||||
|
||||
if seconded <= seconded_collations.load(Ordering::Relaxed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn send_same_collations_to_all_assigned_cores(
|
||||
&self,
|
||||
full_node: &NewFull,
|
||||
mut overseer_handle: Handle,
|
||||
para_id: ParaId,
|
||||
) {
|
||||
let client = full_node.client.clone();
|
||||
|
||||
let collation_function =
|
||||
self.create_collation_function(full_node.task_manager.spawn_handle());
|
||||
|
||||
full_node
|
||||
.task_manager
|
||||
.spawn_handle()
|
||||
.spawn("malus-undying-collator", None, async move {
|
||||
// Subscribe to relay chain block import notifications. In each iteration, build a
|
||||
// collation in response to a block import notification and submits it to all cores
|
||||
// assigned to the teyrchain.
|
||||
let mut import_notifications = client.import_notification_stream();
|
||||
|
||||
while let Some(notification) = import_notifications.next().await {
|
||||
let relay_parent = notification.hash;
|
||||
|
||||
// Get the list of cores assigned to the teyrchain.
|
||||
let claim_queue = match client.runtime_api().claim_queue(relay_parent) {
|
||||
Ok(claim_queue) => claim_queue,
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query claim queue runtime API: {error:?}",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let claim_queue_offset = ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET);
|
||||
|
||||
let scheduled_cores: Vec<CoreIndex> = claim_queue
|
||||
.iter()
|
||||
.filter_map(move |(core_index, paras)| {
|
||||
paras.get(claim_queue_offset.0 as usize).and_then(|core_para_id| {
|
||||
(core_para_id == ¶_id).then_some(*core_index)
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
||||
if scheduled_cores.is_empty() {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Scheduled cores is empty.",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if scheduled_cores.len() == 1 {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Malus collator configured with duplicate collations, but only 1 core assigned. \
|
||||
Collator will not do anything malicious.",
|
||||
);
|
||||
}
|
||||
|
||||
// Fetch validation data for the collation.
|
||||
let validation_data = match client.runtime_api().persisted_validation_data(
|
||||
relay_parent,
|
||||
para_id,
|
||||
OccupiedCoreAssumption::Included,
|
||||
) {
|
||||
Ok(Some(validation_data)) => validation_data,
|
||||
Ok(None) => {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Persisted validation data is None.",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query persisted validation data runtime API: {error:?}",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
// Generate the collation.
|
||||
let collation =
|
||||
match collation_function(relay_parent, &validation_data).await {
|
||||
Some(collation) => collation,
|
||||
None => {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Collation result is None.",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
}
|
||||
.collation;
|
||||
|
||||
// Fetch the validation code hash.
|
||||
let validation_code_hash = match client.runtime_api().validation_code_hash(
|
||||
relay_parent,
|
||||
para_id,
|
||||
OccupiedCoreAssumption::Included,
|
||||
) {
|
||||
Ok(Some(validation_code_hash)) => validation_code_hash,
|
||||
Ok(None) => {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Validation code hash is None.",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query validation code hash runtime API: {error:?}",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
// Fetch the session index.
|
||||
let session_index =
|
||||
match client.runtime_api().session_index_for_child(relay_parent) {
|
||||
Ok(session_index) => session_index,
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query session index for child runtime API: {error:?}",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let persisted_validation_data_hash = validation_data.hash();
|
||||
let parent_head_data = validation_data.parent_head.clone();
|
||||
let parent_head_data_hash = validation_data.parent_head.hash();
|
||||
|
||||
// Apply compression to the block data.
|
||||
let pov = {
|
||||
let pov = collation.proof_of_validity.into_compressed();
|
||||
let encoded_size = pov.encoded_size();
|
||||
let max_pov_size = validation_data.max_pov_size as usize;
|
||||
|
||||
// As long as `POV_BOMB_LIMIT` is at least `max_pov_size`, this ensures
|
||||
// that honest collators never produce a PoV which is uncompressed.
|
||||
//
|
||||
// As such, honest collators never produce an uncompressed PoV which starts
|
||||
// with a compression magic number, which would lead validators to
|
||||
// reject the collation.
|
||||
if encoded_size > max_pov_size {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"PoV size {encoded_size} exceeded maximum size of {max_pov_size}",
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
pov
|
||||
};
|
||||
|
||||
let pov_hash = pov.hash();
|
||||
|
||||
// Fetch the session info.
|
||||
let session_info =
|
||||
match client.runtime_api().session_info(relay_parent, session_index) {
|
||||
Ok(Some(session_info)) => session_info,
|
||||
Ok(None) => {
|
||||
log::info!(
|
||||
target: LOG_TARGET,
|
||||
"Session info is None.",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to query session info runtime API: {error:?}",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
|
||||
let n_validators = session_info.validators.len();
|
||||
|
||||
let available_data =
|
||||
AvailableData { validation_data, pov: Arc::new(pov.clone()) };
|
||||
let chunks = match pezkuwi_erasure_coding::obtain_chunks_v1(
|
||||
n_validators,
|
||||
&available_data,
|
||||
) {
|
||||
Ok(chunks) => chunks,
|
||||
Err(error) => {
|
||||
log::error!(
|
||||
target: LOG_TARGET,
|
||||
"Failed to obtain chunks v1: {error:?}",
|
||||
);
|
||||
continue;
|
||||
},
|
||||
};
|
||||
let erasure_root = pezkuwi_erasure_coding::branches(&chunks).root();
|
||||
|
||||
let commitments = CandidateCommitments {
|
||||
upward_messages: collation.upward_messages,
|
||||
horizontal_messages: collation.horizontal_messages,
|
||||
new_validation_code: collation.new_validation_code,
|
||||
head_data: collation.head_data,
|
||||
processed_downward_messages: collation.processed_downward_messages,
|
||||
hrmp_watermark: collation.hrmp_watermark,
|
||||
};
|
||||
|
||||
// Submit the same collation to all assigned cores.
|
||||
for core_index in &scheduled_cores {
|
||||
let candidate_receipt = CandidateReceiptV2 {
|
||||
descriptor: CandidateDescriptorV2::new(
|
||||
para_id,
|
||||
relay_parent,
|
||||
*core_index,
|
||||
session_index,
|
||||
persisted_validation_data_hash,
|
||||
pov_hash,
|
||||
erasure_root,
|
||||
commitments.head_data.hash(),
|
||||
validation_code_hash,
|
||||
),
|
||||
commitments_hash: commitments.hash(),
|
||||
};
|
||||
|
||||
// We cannot use SubmitCollation here because it includes an additional
|
||||
// check for the core index by calling `parse_ump_signals`. This check
|
||||
// enforces that the teyrchain always selects the correct core by comparing
|
||||
// the descriptor and commitments core indexes. To bypass this check, we are
|
||||
// simulating the behavior of SubmitCollation while skipping ump signals
|
||||
// validation.
|
||||
overseer_handle
|
||||
.send_msg(
|
||||
CollatorProtocolMessage::DistributeCollation {
|
||||
candidate_receipt,
|
||||
parent_head_data_hash,
|
||||
pov: pov.clone(),
|
||||
parent_head_data: parent_head_data.clone(),
|
||||
result_sender: None,
|
||||
core_index: *core_index,
|
||||
},
|
||||
"Collator",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
use sp_core::traits::SpawnNamed;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use futures::executor::block_on;
|
||||
use pezkuwi_primitives::{Hash, PersistedValidationData};
|
||||
use pezkuwi_teyrchain_primitives::primitives::{ValidationParams, ValidationResult};
|
||||
|
||||
#[test]
|
||||
fn collator_works() {
|
||||
let spawner = sp_core::testing::TaskExecutor::new();
|
||||
let collator = Collator::new(1_000, 1, false);
|
||||
let collation_function = collator.create_collation_function(spawner);
|
||||
|
||||
for i in 0..5 {
|
||||
let parent_head =
|
||||
collator.state.lock().unwrap().number_to_head.get(&i).unwrap().clone();
|
||||
|
||||
let validation_data = PersistedValidationData {
|
||||
parent_head: parent_head.encode().into(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let collation =
|
||||
block_on(collation_function(Default::default(), &validation_data)).unwrap();
|
||||
validate_collation(&collator, (*parent_head).clone(), collation.collation);
|
||||
}
|
||||
}
|
||||
|
||||
fn validate_collation(collator: &Collator, parent_head: HeadData, collation: Collation) {
|
||||
use pezkuwi_node_core_pvf::testing::validate_candidate;
|
||||
|
||||
let block_data = match collation.proof_of_validity {
|
||||
MaybeCompressedPoV::Raw(pov) => pov.block_data,
|
||||
MaybeCompressedPoV::Compressed(_) => panic!("Only works with uncompressed povs"),
|
||||
};
|
||||
|
||||
let ret_buf = validate_candidate(
|
||||
collator.validation_code(),
|
||||
&ValidationParams {
|
||||
parent_head: parent_head.encode().into(),
|
||||
block_data,
|
||||
relay_parent_number: 1,
|
||||
relay_parent_storage_root: Hash::zero(),
|
||||
}
|
||||
.encode(),
|
||||
)
|
||||
.unwrap();
|
||||
let ret = ValidationResult::decode(&mut &ret_buf[..]).unwrap();
|
||||
|
||||
let new_head = HeadData::decode(&mut &ret.head_data.0[..]).unwrap();
|
||||
assert_eq!(
|
||||
**collator
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.number_to_head
|
||||
.get(&(parent_head.number + 1))
|
||||
.unwrap(),
|
||||
new_head
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advance_to_state_when_parent_head_is_missing() {
|
||||
let collator = Collator::new(1_000, 1, false);
|
||||
let graveyard_size = collator.state.lock().unwrap().graveyard_size;
|
||||
|
||||
let mut head = calculate_head_and_state_for_number(10, graveyard_size, 1, false).unwrap().0;
|
||||
|
||||
for i in 1..10 {
|
||||
head = collator.state.lock().unwrap().advance(head).unwrap().1;
|
||||
assert_eq!(10 + i, head.number);
|
||||
}
|
||||
|
||||
let collator = Collator::new(1_000, 1, false);
|
||||
let mut second_head = collator
|
||||
.state
|
||||
.lock()
|
||||
.unwrap()
|
||||
.number_to_head
|
||||
.get(&0)
|
||||
.cloned()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.clone();
|
||||
|
||||
for _ in 1..20 {
|
||||
second_head = collator.state.lock().unwrap().advance(second_head.clone()).unwrap().1;
|
||||
}
|
||||
|
||||
assert_eq!(second_head, head);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,172 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Collator for the `Undying` test teyrchain.
|
||||
|
||||
use pezkuwi_cli::{Error, Result};
|
||||
use pezkuwi_node_primitives::CollationGenerationConfig;
|
||||
use pezkuwi_node_subsystem::messages::{CollationGenerationMessage, CollatorProtocolMessage};
|
||||
use pezkuwi_primitives::Id as ParaId;
|
||||
use sc_cli::{Error as SubstrateCliError, SubstrateCli};
|
||||
use sp_core::hexdisplay::HexDisplay;
|
||||
use std::{
|
||||
collections::HashSet,
|
||||
fs,
|
||||
io::{self, Write},
|
||||
};
|
||||
use test_teyrchain_undying_collator::Collator;
|
||||
|
||||
mod cli;
|
||||
use cli::{Cli, MalusType};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::from_args();
|
||||
|
||||
match cli.subcommand {
|
||||
Some(cli::Subcommand::ExportGenesisState(params)) => {
|
||||
// `pov_size`, `pvf_complexity` need to match the
|
||||
// ones that we start the collator with.
|
||||
let collator = Collator::new(
|
||||
params.pov_size,
|
||||
params.pvf_complexity,
|
||||
// The value of `experimental_send_approved_peer` doesn't matter because it's not
|
||||
// part of the state.
|
||||
false,
|
||||
);
|
||||
|
||||
let output_buf =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.genesis_head())).into_bytes();
|
||||
|
||||
if let Some(output) = params.output {
|
||||
std::fs::write(output, output_buf)?;
|
||||
} else {
|
||||
std::io::stdout().write_all(&output_buf)?;
|
||||
}
|
||||
|
||||
Ok::<_, Error>(())
|
||||
},
|
||||
Some(cli::Subcommand::ExportGenesisWasm(params)) => {
|
||||
// We pass some dummy values for `pov_size` and `pvf_complexity` as these don't
|
||||
// matter for `wasm` export.
|
||||
let collator = Collator::default();
|
||||
let output_buf =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.validation_code())).into_bytes();
|
||||
|
||||
if let Some(output) = params.output {
|
||||
fs::write(output, output_buf)?;
|
||||
} else {
|
||||
io::stdout().write_all(&output_buf)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
},
|
||||
None => {
|
||||
let runner = cli.create_runner(&cli.run.base).map_err(|e| {
|
||||
SubstrateCliError::Application(
|
||||
Box::new(e) as Box<(dyn 'static + Send + Sync + std::error::Error)>
|
||||
)
|
||||
})?;
|
||||
|
||||
runner.run_node_until_exit(|config| async move {
|
||||
let collator = Collator::new(
|
||||
cli.run.pov_size,
|
||||
cli.run.pvf_complexity,
|
||||
cli.run.experimental_send_approved_peer,
|
||||
);
|
||||
|
||||
let full_node = pezkuwi_service::build_full(
|
||||
config,
|
||||
pezkuwi_service::NewFullParams {
|
||||
is_teyrchain_node: pezkuwi_service::IsTeyrchainNode::Collator(
|
||||
collator.collator_key(),
|
||||
),
|
||||
enable_beefy: false,
|
||||
force_authoring_backoff: false,
|
||||
telemetry_worker_handle: None,
|
||||
|
||||
// Collators don't spawn PVF workers, so we can disable version checks.
|
||||
node_version: None,
|
||||
secure_validator_mode: false,
|
||||
workers_path: None,
|
||||
workers_names: None,
|
||||
|
||||
overseer_gen: pezkuwi_service::CollatorOverseerGen,
|
||||
overseer_message_channel_capacity_override: None,
|
||||
malus_finality_delay: None,
|
||||
hwbench: None,
|
||||
execute_workers_max_num: None,
|
||||
prepare_workers_hard_max_num: None,
|
||||
prepare_workers_soft_max_num: None,
|
||||
keep_finalized_for: None,
|
||||
invulnerable_ah_collators: HashSet::new(),
|
||||
collator_protocol_hold_off: None,
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
let mut overseer_handle = full_node
|
||||
.overseer_handle
|
||||
.clone()
|
||||
.expect("Overseer handle should be initialized for collators");
|
||||
|
||||
let genesis_head_hex =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.genesis_head()));
|
||||
let validation_code_hex =
|
||||
format!("0x{:?}", HexDisplay::from(&collator.validation_code()));
|
||||
|
||||
let para_id = ParaId::from(cli.run.teyrchain_id);
|
||||
|
||||
log::info!("Running `Undying` collator for teyrchain id: {}", para_id);
|
||||
log::info!("Genesis state: {}", genesis_head_hex);
|
||||
log::info!("Validation code: {}", validation_code_hex);
|
||||
|
||||
let config = CollationGenerationConfig {
|
||||
key: collator.collator_key(),
|
||||
// If the collator is malicious, disable the collation function
|
||||
// (set to None) and manually handle collation submission later.
|
||||
collator: if cli.run.malus_type == MalusType::None {
|
||||
Some(
|
||||
collator
|
||||
.create_collation_function(full_node.task_manager.spawn_handle()),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
},
|
||||
para_id,
|
||||
};
|
||||
overseer_handle
|
||||
.send_msg(CollationGenerationMessage::Initialize(config), "Collator")
|
||||
.await;
|
||||
|
||||
overseer_handle
|
||||
.send_msg(CollatorProtocolMessage::CollateOn(para_id), "Collator")
|
||||
.await;
|
||||
|
||||
// If the collator is configured to behave maliciously, simulate the specified
|
||||
// malicious behavior.
|
||||
if cli.run.malus_type == MalusType::DuplicateCollations {
|
||||
collator.send_same_collations_to_all_assigned_cores(
|
||||
&full_node,
|
||||
overseer_handle,
|
||||
para_id,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(full_node.task_manager)
|
||||
})
|
||||
},
|
||||
}?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Integration test that ensures that we can build and include teyrchain
|
||||
//! blocks of the `Undying` teyrchain.
|
||||
|
||||
// If this test is failing, make sure to run all tests with the `real-overseer` feature being
|
||||
// enabled.
|
||||
|
||||
use pezkuwi_node_subsystem::TimeoutExt;
|
||||
use std::time::Duration;
|
||||
|
||||
const TIMEOUT: Duration = Duration::from_secs(120);
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn collating_using_undying_collator() {
|
||||
use pezkuwi_primitives::Id as ParaId;
|
||||
use sp_keyring::Sr25519Keyring::*;
|
||||
|
||||
let mut builder = sc_cli::LoggerBuilder::new("");
|
||||
builder.with_colors(false);
|
||||
builder.init().expect("Set up logger");
|
||||
|
||||
let para_id = ParaId::from(100);
|
||||
|
||||
let alice_config = pezkuwi_test_service::node_config(
|
||||
|| {},
|
||||
tokio::runtime::Handle::current(),
|
||||
Alice,
|
||||
Vec::new(),
|
||||
true,
|
||||
);
|
||||
|
||||
let mut workers_path = std::env::current_exe().unwrap();
|
||||
workers_path.pop();
|
||||
workers_path.pop();
|
||||
|
||||
// start alice
|
||||
let alice =
|
||||
pezkuwi_test_service::run_validator_node(alice_config, Some(workers_path.clone())).await;
|
||||
|
||||
let bob_config = pezkuwi_test_service::node_config(
|
||||
|| {},
|
||||
tokio::runtime::Handle::current(),
|
||||
Bob,
|
||||
vec![alice.addr.clone()],
|
||||
true,
|
||||
);
|
||||
|
||||
// start bob
|
||||
let bob = pezkuwi_test_service::run_validator_node(bob_config, Some(workers_path)).await;
|
||||
|
||||
let collator = test_teyrchain_undying_collator::Collator::new(1_000, 1, false);
|
||||
|
||||
// register teyrchain
|
||||
alice
|
||||
.register_teyrchain(para_id, collator.validation_code().to_vec(), collator.genesis_head())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// run the collator node
|
||||
let mut charlie = pezkuwi_test_service::run_collator_node(
|
||||
tokio::runtime::Handle::current(),
|
||||
Charlie,
|
||||
|| {},
|
||||
vec![alice.addr.clone(), bob.addr.clone()],
|
||||
collator.collator_key(),
|
||||
)
|
||||
.await;
|
||||
|
||||
charlie
|
||||
.register_collator(
|
||||
collator.collator_key(),
|
||||
para_id,
|
||||
collator.create_collation_function(charlie.task_manager.spawn_handle()),
|
||||
)
|
||||
.await;
|
||||
|
||||
// Wait until the teyrchain has 4 blocks produced.
|
||||
collator
|
||||
.wait_for_blocks(4)
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("Timed out waiting for 4 produced blocks");
|
||||
|
||||
// Wait until the collator received `12` seconded statements for its collations.
|
||||
collator
|
||||
.wait_for_seconded_collations(12)
|
||||
.timeout(TIMEOUT)
|
||||
.await
|
||||
.expect("Timed out waiting for 12 seconded collations");
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! Basic teyrchain that adds a number as part of its state.
|
||||
|
||||
#![no_std]
|
||||
|
||||
extern crate alloc;
|
||||
|
||||
use alloc::vec::Vec;
|
||||
use codec::{Decode, Encode};
|
||||
use pezkuwi_primitives::{
|
||||
ClaimQueueOffset, CoreSelector, UMPSignal, DEFAULT_CLAIM_QUEUE_OFFSET, UMP_SEPARATOR,
|
||||
};
|
||||
use pezkuwi_teyrchain_primitives::primitives::UpwardMessages;
|
||||
use tiny_keccak::{Hasher as _, Keccak};
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod wasm_validation;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
#[global_allocator]
|
||||
static ALLOC: dlmalloc::GlobalDlmalloc = dlmalloc::GlobalDlmalloc;
|
||||
const LOG_TARGET: &str = "runtime::undying";
|
||||
|
||||
// Make the WASM binary available.
|
||||
#[cfg(feature = "std")]
|
||||
include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
|
||||
|
||||
fn keccak256(input: &[u8]) -> [u8; 32] {
|
||||
let mut out = [0u8; 32];
|
||||
let mut keccak256 = Keccak::v256();
|
||||
keccak256.update(input);
|
||||
keccak256.finalize(&mut out);
|
||||
out
|
||||
}
|
||||
|
||||
/// Wasm binary unwrapped. If built with `BUILD_DUMMY_WASM_BINARY`, the function panics.
|
||||
#[cfg(feature = "std")]
|
||||
pub fn wasm_binary_unwrap() -> &'static [u8] {
|
||||
WASM_BINARY.expect(
|
||||
"Development wasm binary is not available. Testing is only \
|
||||
supported with the flag disabled.",
|
||||
)
|
||||
}
|
||||
|
||||
/// Head data for this teyrchain.
|
||||
#[derive(Default, Clone, Hash, Eq, PartialEq, Encode, Decode, Debug)]
|
||||
pub struct HeadData {
|
||||
/// Block number
|
||||
pub number: u64,
|
||||
/// parent block keccak256
|
||||
pub parent_hash: [u8; 32],
|
||||
/// hash of post-execution state.
|
||||
pub post_state: [u8; 32],
|
||||
}
|
||||
|
||||
impl HeadData {
|
||||
pub fn hash(&self) -> [u8; 32] {
|
||||
keccak256(&self.encode())
|
||||
}
|
||||
}
|
||||
|
||||
/// Block data for this teyrchain.
|
||||
#[derive(Default, Clone, Encode, Decode, Debug)]
|
||||
pub struct GraveyardState {
|
||||
/// The grave index of the last placed tombstone.
|
||||
pub index: u64,
|
||||
/// We use a matrix where each element represents a grave.
|
||||
/// The unsigned integer tracks the number of tombstones raised on
|
||||
/// each grave.
|
||||
pub graveyard: Vec<u8>,
|
||||
// TODO: Add zombies. All of the graves produce zombies at a regular interval
|
||||
// defined in blocks. The number of zombies produced scales with the tombstones.
|
||||
// This would allow us to have a configurable and reproducible PVF execution time.
|
||||
// However, PVF preparation time will likely rely on prebuild wasm binaries.
|
||||
pub zombies: u64,
|
||||
// Grave seal.
|
||||
pub seal: [u8; 32],
|
||||
// Increasing sequence number for core selector.
|
||||
pub core_selector_number: u8,
|
||||
}
|
||||
|
||||
/// Block data for this teyrchain.
|
||||
#[derive(Default, Clone, Encode, Decode, Debug)]
|
||||
pub struct BlockData {
|
||||
/// The state
|
||||
pub state: GraveyardState,
|
||||
/// The number of tombstones to erect per iteration. For each tombstone placed
|
||||
/// a hash operation is performed as CPU burn.
|
||||
pub tombstones: u64,
|
||||
/// The number of iterations to perform.
|
||||
pub iterations: u32,
|
||||
/// Whether or not to emit the experimental ApprovedPeer UMP signal.
|
||||
pub experimental_send_approved_peer: bool,
|
||||
}
|
||||
|
||||
pub fn hash_state(state: &GraveyardState) -> [u8; 32] {
|
||||
keccak256(state.encode().as_slice())
|
||||
}
|
||||
|
||||
/// Executes all graveyard transactions in the block.
|
||||
pub fn execute_transaction(mut block_data: BlockData) -> GraveyardState {
|
||||
let graveyard_size = block_data.state.graveyard.len();
|
||||
|
||||
for _ in 0..block_data.iterations {
|
||||
for _ in 0..block_data.tombstones {
|
||||
block_data.state.graveyard[block_data.state.index as usize] =
|
||||
block_data.state.graveyard[block_data.state.index as usize].wrapping_add(1);
|
||||
|
||||
block_data.state.index =
|
||||
((block_data.state.index.saturating_add(1)) as usize % graveyard_size) as u64;
|
||||
}
|
||||
// Chain hash the seals and burn CPU.
|
||||
block_data.state.seal = hash_state(&block_data.state);
|
||||
}
|
||||
block_data.state.core_selector_number = block_data.state.core_selector_number.wrapping_add(1);
|
||||
|
||||
block_data.state
|
||||
}
|
||||
|
||||
/// Start state mismatched with parent header's state hash.
|
||||
#[derive(Debug)]
|
||||
pub struct StateMismatch;
|
||||
|
||||
/// Execute a block body on top of given parent head, producing new parent head
|
||||
/// and new state if valid.
|
||||
pub fn execute(
|
||||
parent_hash: [u8; 32],
|
||||
parent_head: HeadData,
|
||||
block_data: BlockData,
|
||||
) -> Result<(HeadData, GraveyardState, UpwardMessages), StateMismatch> {
|
||||
assert_eq!(parent_hash, parent_head.hash());
|
||||
|
||||
if hash_state(&block_data.state) != parent_head.post_state {
|
||||
log::debug!(
|
||||
target: LOG_TARGET,
|
||||
"state has diff vs head: {:?} vs {:?}",
|
||||
hash_state(&block_data.state),
|
||||
parent_head.post_state,
|
||||
);
|
||||
return Err(StateMismatch);
|
||||
}
|
||||
|
||||
let mut upward_messages: UpwardMessages = Default::default();
|
||||
upward_messages.force_push(UMP_SEPARATOR);
|
||||
upward_messages.force_push(
|
||||
UMPSignal::SelectCore(
|
||||
CoreSelector(block_data.state.core_selector_number),
|
||||
ClaimQueueOffset(DEFAULT_CLAIM_QUEUE_OFFSET),
|
||||
)
|
||||
.encode(),
|
||||
);
|
||||
|
||||
if block_data.experimental_send_approved_peer {
|
||||
upward_messages
|
||||
.force_push(UMPSignal::ApprovedPeer(alloc::vec![1, 2, 3].try_into().unwrap()).encode());
|
||||
}
|
||||
|
||||
// We need to clone the block data as the fn will mutate it's state.
|
||||
let new_state = execute_transaction(block_data.clone());
|
||||
|
||||
Ok((
|
||||
HeadData {
|
||||
number: parent_head.number + 1,
|
||||
parent_hash,
|
||||
post_state: hash_state(&new_state),
|
||||
},
|
||||
new_state,
|
||||
upward_messages,
|
||||
))
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// This file is part of Pezkuwi.
|
||||
|
||||
// Pezkuwi is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
|
||||
// Pezkuwi is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
//! WASM validation for the `Undying` teyrchain.
|
||||
|
||||
use crate::{BlockData, HeadData};
|
||||
use codec::{Decode, Encode};
|
||||
use pezkuwi_teyrchain_primitives::primitives::{HeadData as GenericHeadData, ValidationResult};
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn validate_block(params: *const u8, len: usize) -> u64 {
|
||||
let params = unsafe { pezkuwi_teyrchain_primitives::load_params(params, len) };
|
||||
let parent_head =
|
||||
HeadData::decode(&mut ¶ms.parent_head.0[..]).expect("invalid parent head format.");
|
||||
|
||||
let mut block_data =
|
||||
BlockData::decode(&mut ¶ms.block_data.0[..]).expect("invalid block data format.");
|
||||
|
||||
let parent_hash = crate::keccak256(¶ms.parent_head.0[..]);
|
||||
|
||||
let (new_head, _, upward_messages) =
|
||||
crate::execute(parent_hash, parent_head, block_data).expect("Executes block");
|
||||
|
||||
pezkuwi_teyrchain_primitives::write_result(&ValidationResult {
|
||||
head_data: GenericHeadData(new_head.encode()),
|
||||
new_validation_code: None,
|
||||
upward_messages,
|
||||
horizontal_messages: alloc::vec::Vec::new()
|
||||
.try_into()
|
||||
.expect("empty vec fits within bounds"),
|
||||
processed_downward_messages: 0,
|
||||
hrmp_watermark: params.relay_parent_number,
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user