feat: Rebrand Polkadot/Substrate references to PezkuwiChain

This commit systematically rebrands various references from Parity Technologies'
Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk.

Key changes include:
- Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks.
- Modified internal documentation and code comments to reflect PezkuwiChain naming and structure.
- Replaced direct references to  with  or specific paths within the  for XCM, Pezkuwi, and other modules.
- Cleaned up deprecated  issue and PR references in various  and  files, particularly in  and  modules.
- Adjusted image and logo URLs in documentation to point to PezkuwiChain assets.
- Removed or rephrased comments related to external Polkadot/Substrate PRs and issues.

This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
2025-12-14 00:04:10 +03:00
parent 286de54384
commit 1c0e57d984
9084 changed files with 997839 additions and 997557 deletions
+23
View File
@@ -0,0 +1,23 @@
[package]
name = "pezsc-allocator"
version = "23.0.0"
authors.workspace = true
edition.workspace = true
license = "Apache-2.0"
homepage.workspace = true
repository.workspace = true
description = "Collection of allocator implementations."
documentation = "https://docs.rs/pezsc-allocator"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
log = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-wasm-interface = { workspace = true, default-features = true }
thiserror = { workspace = true }
+6
View File
@@ -0,0 +1,6 @@
Collection of allocator implementations.
This crate provides the following allocator implementations:
- A freeing-bump allocator: [`FreeingBumpHeapAllocator`](https://docs.rs/pezsc-allocator/latest/sc_allocator/struct.FreeingBumpHeapAllocator.html)
License: Apache-2.0
+33
View File
@@ -0,0 +1,33 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/// The error type used by the allocators.
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum Error {
/// Someone tried to allocate more memory than the allowed maximum per allocation.
#[error("Requested allocation size is too large")]
RequestedAllocationTooLarge,
/// Allocator run out of space.
#[error("Allocator ran out of space")]
AllocatorOutOfSpace,
/// The client passed a memory instance which is smaller than previously observed.
#[error("Shrinking of the underlying memory is observed")]
MemoryShrinked,
/// Some other error occurred.
#[error("Other: {0}")]
Other(&'static str),
}
File diff suppressed because it is too large Load Diff
+61
View File
@@ -0,0 +1,61 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Collection of allocator implementations.
//!
//! This crate provides the following allocator implementations:
//! - A freeing-bump allocator: [`FreeingBumpHeapAllocator`]
#![warn(missing_docs)]
mod error;
mod freeing_bump;
pub use error::Error;
pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator};
/// The size of one wasm page in bytes.
///
/// The wasm memory is divided into pages, meaning the minimum size of a memory is one page.
const PAGE_SIZE: u32 = 65536;
/// The maximum number of wasm pages that can be allocated.
///
/// 4GiB / [`PAGE_SIZE`].
const MAX_WASM_PAGES: u32 = (4u64 * 1024 * 1024 * 1024 / PAGE_SIZE as u64) as u32;
/// Grants access to the memory for the allocator.
///
/// Memory of wasm is allocated in pages. A page has a constant size of 64KiB. The maximum allowed
/// memory size as defined in the wasm specification is 4GiB (65536 pages).
pub trait Memory {
/// Run the given closure `run` and grant it write access to the raw memory.
fn with_access_mut<R>(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R;
/// Run the given closure `run` and grant it read access to the raw memory.
fn with_access<R>(&self, run: impl FnOnce(&[u8]) -> R) -> R;
/// Grow the memory by `additional` pages.
fn grow(&mut self, additional: u32) -> Result<(), ()>;
/// Returns the current number of pages this memory has allocated.
fn pages(&self) -> u32;
/// Returns the maximum number of pages this memory is allowed to allocate.
///
/// The returned number needs to be smaller or equal to `65536`. The returned number needs to be
/// bigger or equal to [`Self::pages`].
///
/// If `None` is returned, there is no maximum (besides the maximum defined in the wasm spec).
fn max_pages(&self) -> Option<u32>;
}
+54
View File
@@ -0,0 +1,54 @@
[package]
name = "pezsc-client-api"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Bizinikiwi client interfaces."
documentation = "https://docs.rs/pezsc-client-api"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true }
fnv = { workspace = true }
futures = { workspace = true }
log = { workspace = true, default-features = true }
parking_lot = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
pezsc-executor = { workspace = true, default-features = true }
pezsc-transaction-pool-api = { workspace = true, default-features = true }
pezsc-utils = { workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-consensus = { workspace = true, default-features = true }
pezsp-core = { workspace = true }
pezsp-database = { workspace = true, default-features = true }
pezsp-externalities = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true }
pezsp-state-machine = { workspace = true, default-features = true }
pezsp-storage = { workspace = true, default-features = true }
pezsp-trie = { workspace = true, default-features = true }
[dev-dependencies]
bizinikiwi-test-runtime = { workspace = true }
[features]
runtime-benchmarks = [
"pezsc-executor/runtime-benchmarks",
"pezsc-transaction-pool-api/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-state-machine/runtime-benchmarks",
"pezsp-trie/runtime-benchmarks",
"bizinikiwi-test-runtime/runtime-benchmarks",
]
+3
View File
@@ -0,0 +1,3 @@
Bizinikiwi client interfaces.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
+684
View File
@@ -0,0 +1,684 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Bizinikiwi Client data backend
use std::collections::HashSet;
use parking_lot::RwLock;
use pezsp_api::CallContext;
use pezsp_consensus::BlockOrigin;
use pezsp_core::offchain::OffchainStorage;
use pezsp_runtime::{
traits::{Block as BlockT, HashingFor, NumberFor},
Justification, Justifications, StateVersion, Storage,
};
use pezsp_state_machine::{
backend::AsTrieBackend, ChildStorageCollection, IndexOperation, IterArgs,
OffchainChangesCollection, StorageCollection, StorageIterator,
};
use pezsp_storage::{ChildInfo, StorageData, StorageKey};
pub use pezsp_trie::MerkleValue;
use crate::{blockchain::Backend as BlockchainBackend, UsageInfo};
pub use pezsp_state_machine::{Backend as StateBackend, BackendTransaction, KeyValueStates};
/// Extracts the state backend type for the given backend.
pub type StateBackendFor<B, Block> = <B as Backend<Block>>::State;
/// Describes which block import notification stream should be notified.
#[derive(Debug, Clone, Copy)]
pub enum ImportNotificationAction {
/// Notify only when the node has synced to the tip or there is a re-org.
RecentBlock,
/// Notify for every single block no matter what the sync state is.
EveryBlock,
/// Both block import notifications above should be fired.
Both,
/// No block import notification should be fired.
None,
}
/// Import operation summary.
///
/// Contains information about the block that just got imported,
/// including storage changes, reorged blocks, etc.
pub struct ImportSummary<Block: BlockT> {
/// Block hash of the imported block.
pub hash: Block::Hash,
/// Import origin.
pub origin: BlockOrigin,
/// Header of the imported block.
pub header: Block::Header,
/// Is this block a new best block.
pub is_new_best: bool,
/// Optional storage changes.
pub storage_changes: Option<(StorageCollection, ChildStorageCollection)>,
/// Tree route from old best to new best.
///
/// If `None`, there was no re-org while importing.
pub tree_route: Option<pezsp_blockchain::TreeRoute<Block>>,
/// What notify action to take for this import.
pub import_notification_action: ImportNotificationAction,
}
/// A stale block.
#[derive(Clone, Debug)]
pub struct StaleBlock<Block: BlockT> {
/// The hash of this block.
pub hash: Block::Hash,
/// Is this a head?
pub is_head: bool,
}
/// Finalization operation summary.
///
/// Contains information about the block that just got finalized,
/// including tree heads that became stale at the moment of finalization.
pub struct FinalizeSummary<Block: BlockT> {
/// Last finalized block header.
pub header: Block::Header,
/// Blocks that were finalized.
///
/// The last entry is the one that has been explicitly finalized.
pub finalized: Vec<Block::Hash>,
/// Blocks that became stale during this finalization operation.
pub stale_blocks: Vec<StaleBlock<Block>>,
}
/// Import operation wrapper.
pub struct ClientImportOperation<Block: BlockT, B: Backend<Block>> {
/// DB Operation.
pub op: B::BlockImportOperation,
/// Summary of imported block.
pub notify_imported: Option<ImportSummary<Block>>,
/// Summary of finalized block.
pub notify_finalized: Option<FinalizeSummary<Block>>,
}
/// Helper function to apply auxiliary data insertion into an operation.
pub fn apply_aux<'a, 'b: 'a, 'c: 'a, B, Block, D, I>(
operation: &mut ClientImportOperation<Block, B>,
insert: I,
delete: D,
) -> pezsp_blockchain::Result<()>
where
Block: BlockT,
B: Backend<Block>,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
{
operation.op.insert_aux(
insert
.into_iter()
.map(|(k, v)| (k.to_vec(), Some(v.to_vec())))
.chain(delete.into_iter().map(|k| (k.to_vec(), None))),
)
}
/// State of a new block.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NewBlockState {
/// Normal block.
Normal,
/// New best block.
Best,
/// Newly finalized block (implicitly best).
Final,
}
impl NewBlockState {
/// Whether this block is the new best block.
pub fn is_best(self) -> bool {
match self {
NewBlockState::Best | NewBlockState::Final => true,
NewBlockState::Normal => false,
}
}
/// Whether this block is considered final.
pub fn is_final(self) -> bool {
match self {
NewBlockState::Final => true,
NewBlockState::Best | NewBlockState::Normal => false,
}
}
}
/// Block insertion operation.
///
/// Keeps hold if the inserted block state and data.
pub trait BlockImportOperation<Block: BlockT> {
/// Associated state backend type.
type State: StateBackend<HashingFor<Block>>;
/// Returns pending state.
///
/// Returns None for backends with locally-unavailable state data.
fn state(&self) -> pezsp_blockchain::Result<Option<&Self::State>>;
/// Append block data to the transaction.
fn set_block_data(
&mut self,
header: Block::Header,
body: Option<Vec<Block::Extrinsic>>,
indexed_body: Option<Vec<Vec<u8>>>,
justifications: Option<Justifications>,
state: NewBlockState,
) -> pezsp_blockchain::Result<()>;
/// Inject storage data into the database.
fn update_db_storage(
&mut self,
update: BackendTransaction<HashingFor<Block>>,
) -> pezsp_blockchain::Result<()>;
/// Set genesis state. If `commit` is `false` the state is saved in memory, but is not written
/// to the database.
fn set_genesis_state(
&mut self,
storage: Storage,
commit: bool,
state_version: StateVersion,
) -> pezsp_blockchain::Result<Block::Hash>;
/// Inject storage data into the database replacing any existing data.
fn reset_storage(
&mut self,
storage: Storage,
state_version: StateVersion,
) -> pezsp_blockchain::Result<Block::Hash>;
/// Set storage changes.
fn update_storage(
&mut self,
update: StorageCollection,
child_update: ChildStorageCollection,
) -> pezsp_blockchain::Result<()>;
/// Write offchain storage changes to the database.
fn update_offchain_storage(
&mut self,
_offchain_update: OffchainChangesCollection,
) -> pezsp_blockchain::Result<()> {
Ok(())
}
/// Insert auxiliary keys.
///
/// Values are `None` if should be deleted.
fn insert_aux<I>(&mut self, ops: I) -> pezsp_blockchain::Result<()>
where
I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>;
/// Mark a block as finalized, if multiple blocks are finalized in the same operation then they
/// must be marked in ascending order.
fn mark_finalized(
&mut self,
hash: Block::Hash,
justification: Option<Justification>,
) -> pezsp_blockchain::Result<()>;
/// Mark a block as new head. If both block import and set head are specified, set head
/// overrides block import's best block rule.
fn mark_head(&mut self, hash: Block::Hash) -> pezsp_blockchain::Result<()>;
/// Add a transaction index operation.
fn update_transaction_index(&mut self, index: Vec<IndexOperation>)
-> pezsp_blockchain::Result<()>;
/// Configure whether to create a block gap if newly imported block is missing parent
fn set_create_gap(&mut self, create_gap: bool);
}
/// Interface for performing operations on the backend.
pub trait LockImportRun<Block: BlockT, B: Backend<Block>> {
/// Lock the import lock, and run operations inside.
fn lock_import_and_run<R, Err, F>(&self, f: F) -> Result<R, Err>
where
F: FnOnce(&mut ClientImportOperation<Block, B>) -> Result<R, Err>,
Err: From<pezsp_blockchain::Error>;
}
/// Finalize Facilities
pub trait Finalizer<Block: BlockT, B: Backend<Block>> {
/// Mark all blocks up to given as finalized in operation.
///
/// If `justification` is provided it is stored with the given finalized
/// block (any other finalized blocks are left unjustified).
///
/// If the block being finalized is on a different fork from the current
/// best block the finalized block is set as best, this might be slightly
/// inaccurate (i.e. outdated). Usages that require determining an accurate
/// best block should use `SelectChain` instead of the client.
fn apply_finality(
&self,
operation: &mut ClientImportOperation<Block, B>,
block: Block::Hash,
justification: Option<Justification>,
notify: bool,
) -> pezsp_blockchain::Result<()>;
/// Finalize a block.
///
/// This will implicitly finalize all blocks up to it and
/// fire finality notifications.
///
/// If the block being finalized is on a different fork from the current
/// best block, the finalized block is set as best. This might be slightly
/// inaccurate (i.e. outdated). Usages that require determining an accurate
/// best block should use `SelectChain` instead of the client.
///
/// Pass a flag to indicate whether finality notifications should be propagated.
/// This is usually tied to some synchronization state, where we don't send notifications
/// while performing major synchronization work.
fn finalize_block(
&self,
block: Block::Hash,
justification: Option<Justification>,
notify: bool,
) -> pezsp_blockchain::Result<()>;
}
/// Provides access to an auxiliary database.
///
/// This is a simple global database not aware of forks. Can be used for storing auxiliary
/// information like total block weight/difficulty for fork resolution purposes as a common use
/// case.
pub trait AuxStore {
/// Insert auxiliary data into key-value store.
///
/// Deletions occur after insertions.
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
insert: I,
delete: D,
) -> pezsp_blockchain::Result<()>;
/// Query auxiliary data from key-value store.
fn get_aux(&self, key: &[u8]) -> pezsp_blockchain::Result<Option<Vec<u8>>>;
}
/// An `Iterator` that iterates keys in a given block under a prefix.
pub struct KeysIter<State, Block>
where
State: StateBackend<HashingFor<Block>>,
Block: BlockT,
{
inner: <State as StateBackend<HashingFor<Block>>>::RawIter,
state: State,
}
impl<State, Block> KeysIter<State, Block>
where
State: StateBackend<HashingFor<Block>>,
Block: BlockT,
{
/// Create a new iterator over storage keys.
pub fn new(
state: State,
prefix: Option<&StorageKey>,
start_at: Option<&StorageKey>,
) -> Result<Self, State::Error> {
let mut args = IterArgs::default();
args.prefix = prefix.as_ref().map(|prefix| prefix.0.as_slice());
args.start_at = start_at.as_ref().map(|start_at| start_at.0.as_slice());
args.start_at_exclusive = true;
Ok(Self { inner: state.raw_iter(args)?, state })
}
/// Create a new iterator over a child storage's keys.
pub fn new_child(
state: State,
child_info: ChildInfo,
prefix: Option<&StorageKey>,
start_at: Option<&StorageKey>,
) -> Result<Self, State::Error> {
let mut args = IterArgs::default();
args.prefix = prefix.as_ref().map(|prefix| prefix.0.as_slice());
args.start_at = start_at.as_ref().map(|start_at| start_at.0.as_slice());
args.child_info = Some(child_info);
args.start_at_exclusive = true;
Ok(Self { inner: state.raw_iter(args)?, state })
}
}
impl<State, Block> Iterator for KeysIter<State, Block>
where
Block: BlockT,
State: StateBackend<HashingFor<Block>>,
{
type Item = StorageKey;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next_key(&self.state)?.ok().map(StorageKey)
}
}
/// An `Iterator` that iterates keys and values in a given block under a prefix.
pub struct PairsIter<State, Block>
where
State: StateBackend<HashingFor<Block>>,
Block: BlockT,
{
inner: <State as StateBackend<HashingFor<Block>>>::RawIter,
state: State,
}
impl<State, Block> Iterator for PairsIter<State, Block>
where
Block: BlockT,
State: StateBackend<HashingFor<Block>>,
{
type Item = (StorageKey, StorageData);
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next_pair(&self.state)?
.ok()
.map(|(key, value)| (StorageKey(key), StorageData(value)))
}
}
impl<State, Block> PairsIter<State, Block>
where
State: StateBackend<HashingFor<Block>>,
Block: BlockT,
{
/// Create a new iterator over storage key and value pairs.
pub fn new(
state: State,
prefix: Option<&StorageKey>,
start_at: Option<&StorageKey>,
) -> Result<Self, State::Error> {
let mut args = IterArgs::default();
args.prefix = prefix.as_ref().map(|prefix| prefix.0.as_slice());
args.start_at = start_at.as_ref().map(|start_at| start_at.0.as_slice());
args.start_at_exclusive = true;
Ok(Self { inner: state.raw_iter(args)?, state })
}
}
/// Provides access to storage primitives
pub trait StorageProvider<Block: BlockT, B: Backend<Block>> {
/// Given a block's `Hash` and a key, return the value under the key in that block.
fn storage(
&self,
hash: Block::Hash,
key: &StorageKey,
) -> pezsp_blockchain::Result<Option<StorageData>>;
/// Given a block's `Hash` and a key, return the value under the hash in that block.
fn storage_hash(
&self,
hash: Block::Hash,
key: &StorageKey,
) -> pezsp_blockchain::Result<Option<Block::Hash>>;
/// Given a block's `Hash` and a key prefix, returns a `KeysIter` iterates matching storage
/// keys in that block.
fn storage_keys(
&self,
hash: Block::Hash,
prefix: Option<&StorageKey>,
start_key: Option<&StorageKey>,
) -> pezsp_blockchain::Result<KeysIter<B::State, Block>>;
/// Given a block's `Hash` and a key prefix, returns an iterator over the storage keys and
/// values in that block.
fn storage_pairs(
&self,
hash: <Block as BlockT>::Hash,
prefix: Option<&StorageKey>,
start_key: Option<&StorageKey>,
) -> pezsp_blockchain::Result<PairsIter<B::State, Block>>;
/// Given a block's `Hash`, a key and a child storage key, return the value under the key in
/// that block.
fn child_storage(
&self,
hash: Block::Hash,
child_info: &ChildInfo,
key: &StorageKey,
) -> pezsp_blockchain::Result<Option<StorageData>>;
/// Given a block's `Hash` and a key `prefix` and a child storage key,
/// returns a `KeysIter` that iterates matching storage keys in that block.
fn child_storage_keys(
&self,
hash: Block::Hash,
child_info: ChildInfo,
prefix: Option<&StorageKey>,
start_key: Option<&StorageKey>,
) -> pezsp_blockchain::Result<KeysIter<B::State, Block>>;
/// Given a block's `Hash`, a key and a child storage key, return the hash under the key in that
/// block.
fn child_storage_hash(
&self,
hash: Block::Hash,
child_info: &ChildInfo,
key: &StorageKey,
) -> pezsp_blockchain::Result<Option<Block::Hash>>;
/// Given a block's `Hash` and a key, return the closest merkle value.
fn closest_merkle_value(
&self,
hash: Block::Hash,
key: &StorageKey,
) -> pezsp_blockchain::Result<Option<MerkleValue<Block::Hash>>>;
/// Given a block's `Hash`, a key and a child storage key, return the closest merkle value.
fn child_closest_merkle_value(
&self,
hash: Block::Hash,
child_info: &ChildInfo,
key: &StorageKey,
) -> pezsp_blockchain::Result<Option<MerkleValue<Block::Hash>>>;
}
/// Specify the desired trie cache context when calling [`Backend::state_at`].
///
/// This is used to determine the size of the local trie cache.
#[derive(Debug, Clone, Copy)]
pub enum TrieCacheContext {
/// This is used when calling [`Backend::state_at`] in a trusted context.
///
/// A trusted context is for example the building or importing of a block.
/// In this case the local trie cache can grow unlimited and all the cached data
/// will be propagated back to the shared trie cache. It is safe to let the local
/// cache grow to hold the entire data, because importing and building blocks is
/// bounded by the block size limit.
Trusted,
/// This is used when calling [`Backend::state_at`] in from untrusted context.
///
/// The local trie cache will be bounded by its preconfigured size.
Untrusted,
}
impl From<CallContext> for TrieCacheContext {
fn from(call_context: CallContext) -> Self {
match call_context {
CallContext::Onchain => TrieCacheContext::Trusted,
CallContext::Offchain => TrieCacheContext::Untrusted,
}
}
}
/// Client backend.
///
/// Manages the data layer.
///
/// # State Pruning
///
/// While an object from `state_at` is alive, the state
/// should not be pruned. The backend should internally reference-count
/// its state objects.
///
/// The same applies for live `BlockImportOperation`s: while an import operation building on a
/// parent `P` is alive, the state for `P` should not be pruned.
///
/// # Block Pruning
///
/// Users can pin blocks in memory by calling `pin_block`. When
/// a block would be pruned, its value is kept in an in-memory cache
/// until it is unpinned via `unpin_block`.
///
/// While a block is pinned, its state is also preserved.
///
/// The backend should internally reference count the number of pin / unpin calls.
pub trait Backend<Block: BlockT>: AuxStore + Send + Sync {
/// Associated block insertion operation type.
type BlockImportOperation: BlockImportOperation<Block, State = Self::State>;
/// Associated blockchain backend type.
type Blockchain: BlockchainBackend<Block>;
/// Associated state backend type.
type State: StateBackend<HashingFor<Block>>
+ Send
+ AsTrieBackend<
HashingFor<Block>,
TrieBackendStorage = <Self::State as StateBackend<HashingFor<Block>>>::TrieBackendStorage,
>;
/// Offchain workers local storage.
type OffchainStorage: OffchainStorage;
/// Begin a new block insertion transaction with given parent block id.
///
/// When constructing the genesis, this is called with all-zero hash.
fn begin_operation(&self) -> pezsp_blockchain::Result<Self::BlockImportOperation>;
/// Note an operation to contain state transition.
fn begin_state_operation(
&self,
operation: &mut Self::BlockImportOperation,
block: Block::Hash,
) -> pezsp_blockchain::Result<()>;
/// Commit block insertion.
fn commit_operation(
&self,
transaction: Self::BlockImportOperation,
) -> pezsp_blockchain::Result<()>;
/// Finalize block with given `hash`.
///
/// This should only be called if the parent of the given block has been finalized.
fn finalize_block(
&self,
hash: Block::Hash,
justification: Option<Justification>,
) -> pezsp_blockchain::Result<()>;
/// Append justification to the block with the given `hash`.
///
/// This should only be called for blocks that are already finalized.
fn append_justification(
&self,
hash: Block::Hash,
justification: Justification,
) -> pezsp_blockchain::Result<()>;
/// Returns reference to blockchain backend.
fn blockchain(&self) -> &Self::Blockchain;
/// Returns current usage statistics.
fn usage_info(&self) -> Option<UsageInfo>;
/// Returns a handle to offchain storage.
fn offchain_storage(&self) -> Option<Self::OffchainStorage>;
/// Pin the block to keep body, justification and state available after pruning.
/// Number of pins are reference counted. Users need to make sure to perform
/// one call to [`Self::unpin_block`] per call to [`Self::pin_block`].
fn pin_block(&self, hash: Block::Hash) -> pezsp_blockchain::Result<()>;
/// Unpin the block to allow pruning.
fn unpin_block(&self, hash: Block::Hash);
/// Returns true if state for given block is available.
fn have_state_at(&self, hash: Block::Hash, _number: NumberFor<Block>) -> bool {
self.state_at(hash, TrieCacheContext::Untrusted).is_ok()
}
/// Returns state backend with post-state of given block.
fn state_at(
&self,
hash: Block::Hash,
trie_cache_context: TrieCacheContext,
) -> pezsp_blockchain::Result<Self::State>;
/// Attempts to revert the chain by `n` blocks. If `revert_finalized` is set it will attempt to
/// revert past any finalized block, this is unsafe and can potentially leave the node in an
/// inconsistent state. All blocks higher than the best block are also reverted and not counting
/// towards `n`.
///
/// Returns the number of blocks that were successfully reverted and the list of finalized
/// blocks that has been reverted.
fn revert(
&self,
n: NumberFor<Block>,
revert_finalized: bool,
) -> pezsp_blockchain::Result<(NumberFor<Block>, HashSet<Block::Hash>)>;
/// Discard non-best, unfinalized leaf block.
fn remove_leaf_block(&self, hash: Block::Hash) -> pezsp_blockchain::Result<()>;
/// Insert auxiliary data into key-value store.
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
insert: I,
delete: D,
) -> pezsp_blockchain::Result<()> {
AuxStore::insert_aux(self, insert, delete)
}
/// Query auxiliary data from key-value store.
fn get_aux(&self, key: &[u8]) -> pezsp_blockchain::Result<Option<Vec<u8>>> {
AuxStore::get_aux(self, key)
}
/// Gain access to the import lock around this backend.
///
/// _Note_ Backend isn't expected to acquire the lock by itself ever. Rather
/// the using components should acquire and hold the lock whenever they do
/// something that the import of a block would interfere with, e.g. importing
/// a new block or calculating the best head.
fn get_import_lock(&self) -> &RwLock<()>;
/// Tells whether the backend requires full-sync mode.
fn requires_full_sync(&self) -> bool;
}
/// Mark for all Backend implementations, that are making use of state data, stored locally.
pub trait LocalBackend<Block: BlockT>: Backend<Block> {}
@@ -0,0 +1,95 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! A method call executor interface.
use pezsc_executor::{RuntimeVersion, RuntimeVersionOf};
use pezsp_core::traits::CallContext;
use pezsp_externalities::Extensions;
use pezsp_runtime::traits::{Block as BlockT, HashingFor};
use pezsp_state_machine::{OverlayedChanges, StorageProof};
use std::cell::RefCell;
use crate::execution_extensions::ExecutionExtensions;
use pezsp_api::ProofRecorder;
/// Executor Provider
pub trait ExecutorProvider<Block: BlockT> {
/// executor instance
type Executor: CallExecutor<Block>;
/// Get call executor reference.
fn executor(&self) -> &Self::Executor;
/// Get a reference to the execution extensions.
fn execution_extensions(&self) -> &ExecutionExtensions<Block>;
}
/// Method call executor.
pub trait CallExecutor<B: BlockT>: RuntimeVersionOf {
/// Externalities error type.
type Error: pezsp_state_machine::Error;
/// The backend used by the node.
type Backend: crate::backend::Backend<B>;
/// Returns the [`ExecutionExtensions`].
fn execution_extensions(&self) -> &ExecutionExtensions<B>;
/// Execute a call to a contract on top of state in a block of given hash.
///
/// No changes are made.
fn call(
&self,
at_hash: B::Hash,
method: &str,
call_data: &[u8],
context: CallContext,
) -> Result<Vec<u8>, pezsp_blockchain::Error>;
/// Execute a contextual call on top of state in a block of a given hash.
///
/// No changes are made.
/// Before executing the method, passed header is installed as the current header
/// of the execution context.
fn contextual_call(
&self,
at_hash: B::Hash,
method: &str,
call_data: &[u8],
changes: &RefCell<OverlayedChanges<HashingFor<B>>>,
proof_recorder: &Option<ProofRecorder<B>>,
call_context: CallContext,
extensions: &RefCell<Extensions>,
) -> pezsp_blockchain::Result<Vec<u8>>;
/// Extract RuntimeVersion of given block
///
/// No changes are made.
fn runtime_version(&self, at_hash: B::Hash) -> Result<RuntimeVersion, pezsp_blockchain::Error>;
/// Prove the execution of the given `method`.
///
/// No changes are made.
fn prove_execution(
&self,
at_hash: B::Hash,
method: &str,
call_data: &[u8],
) -> Result<(Vec<u8>, StorageProof), pezsp_blockchain::Error>;
}
+475
View File
@@ -0,0 +1,475 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! A set of APIs supported by the client along with their primitives.
use pezsp_consensus::BlockOrigin;
use pezsp_core::storage::StorageKey;
use pezsp_runtime::{
generic::SignedBlock,
traits::{Block as BlockT, NumberFor},
Justifications,
};
use std::{
collections::HashSet,
fmt::{self, Debug},
sync::Arc,
};
use crate::{
blockchain::Info, notifications::StorageEventStream, FinalizeSummary, ImportSummary, StaleBlock,
};
use pezsc_transaction_pool_api::ChainEvent;
use pezsc_utils::mpsc::{TracingUnboundedReceiver, TracingUnboundedSender};
use pezsp_blockchain;
/// Type that implements `futures::Stream` of block import events.
pub type ImportNotifications<Block> = TracingUnboundedReceiver<BlockImportNotification<Block>>;
/// A stream of block finality notifications.
pub type FinalityNotifications<Block> = TracingUnboundedReceiver<FinalityNotification<Block>>;
/// Expected hashes of blocks at given heights.
///
/// This may be used as chain spec extension to set trusted checkpoints, i.e.
/// the client will refuse to import a block with a different hash at the given
/// height.
pub type ForkBlocks<Block> = Option<Vec<(NumberFor<Block>, <Block as BlockT>::Hash)>>;
/// Known bad block hashes.
///
/// This may be used as chain spec extension to filter out known, unwanted forks.
pub type BadBlocks<Block> = Option<HashSet<<Block as BlockT>::Hash>>;
/// Figure out the block type for a given type (for now, just a `Client`).
pub trait BlockOf {
/// The type of the block.
type Type: BlockT;
}
/// A source of blockchain events.
pub trait BlockchainEvents<Block: BlockT> {
/// Get block import event stream.
///
/// Not guaranteed to be fired for every imported block. Use
/// `every_import_notification_stream()` if you want a notification of every imported block
/// regardless.
///
/// The events for this notification stream are emitted:
/// - During initial sync process: if there is a re-org while importing blocks. See
/// [here](https://github.com/pezkuwichain/kurdistan-sdk/issues/60#issuecomment-694091901) for the
/// rationale behind this.
/// - After initial sync process: on every imported block, regardless of whether it is
/// the new best block or not, causes a re-org or not.
fn import_notification_stream(&self) -> ImportNotifications<Block>;
/// Get a stream of every imported block.
fn every_import_notification_stream(&self) -> ImportNotifications<Block>;
/// Get a stream of finality notifications. Not guaranteed to be fired for every
/// finalized block.
fn finality_notification_stream(&self) -> FinalityNotifications<Block>;
/// Get storage changes event stream.
///
/// Passing `None` as `filter_keys` subscribes to all storage changes.
fn storage_changes_notification_stream(
&self,
filter_keys: Option<&[StorageKey]>,
child_filter_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
) -> pezsp_blockchain::Result<StorageEventStream<Block::Hash>>;
}
/// List of operations to be performed on storage aux data.
/// First tuple element is the encoded data key.
/// Second tuple element is the encoded optional data to write.
/// If `None`, the key and the associated data are deleted from storage.
pub type AuxDataOperations = Vec<(Vec<u8>, Option<Vec<u8>>)>;
/// Callback invoked before committing the operations created during block import.
/// This gives the opportunity to perform auxiliary pre-commit actions and optionally
/// enqueue further storage write operations to be atomically performed on commit.
pub type OnImportAction<Block> =
Box<dyn (Fn(&BlockImportNotification<Block>) -> AuxDataOperations) + Send>;
/// Callback invoked before committing the operations created during block finalization.
/// This gives the opportunity to perform auxiliary pre-commit actions and optionally
/// enqueue further storage write operations to be atomically performed on commit.
pub type OnFinalityAction<Block> =
Box<dyn (Fn(&FinalityNotification<Block>) -> AuxDataOperations) + Send>;
/// Interface to perform auxiliary actions before committing a block import or
/// finality operation.
pub trait PreCommitActions<Block: BlockT> {
/// Actions to be performed on block import.
fn register_import_action(&self, op: OnImportAction<Block>);
/// Actions to be performed on block finalization.
fn register_finality_action(&self, op: OnFinalityAction<Block>);
}
/// Interface for fetching block data.
pub trait BlockBackend<Block: BlockT> {
/// Get block body by ID. Returns `None` if the body is not stored.
fn block_body(
&self,
hash: Block::Hash,
) -> pezsp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>>;
/// Get all indexed transactions for a block,
/// including renewed transactions.
///
/// Note that this will only fetch transactions
/// that are indexed by the runtime with `storage_index_transaction`.
fn block_indexed_body(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<Vec<Vec<u8>>>>;
/// Get full block by hash.
fn block(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<SignedBlock<Block>>>;
/// Get block status by block hash.
fn block_status(&self, hash: Block::Hash) -> pezsp_blockchain::Result<pezsp_consensus::BlockStatus>;
/// Get block justifications for the block with the given hash.
fn justifications(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<Justifications>>;
/// Get block hash by number.
fn block_hash(&self, number: NumberFor<Block>) -> pezsp_blockchain::Result<Option<Block::Hash>>;
/// Get single indexed transaction by content hash.
///
/// Note that this will only fetch transactions
/// that are indexed by the runtime with `storage_index_transaction`.
fn indexed_transaction(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<Vec<u8>>>;
/// Check if transaction index exists.
fn has_indexed_transaction(&self, hash: Block::Hash) -> pezsp_blockchain::Result<bool> {
Ok(self.indexed_transaction(hash)?.is_some())
}
/// Tells whether the current client configuration requires full-sync mode.
fn requires_full_sync(&self) -> bool;
}
/// Provide a list of potential uncle headers for a given block.
pub trait ProvideUncles<Block: BlockT> {
/// Gets the uncles of the block with `target_hash` going back `max_generation` ancestors.
fn uncles(
&self,
target_hash: Block::Hash,
max_generation: NumberFor<Block>,
) -> pezsp_blockchain::Result<Vec<Block::Header>>;
}
/// Client info
#[derive(Debug, Clone)]
pub struct ClientInfo<Block: BlockT> {
/// Best block hash.
pub chain: Info<Block>,
/// Usage info, if backend supports this.
pub usage: Option<UsageInfo>,
}
/// A wrapper to store the size of some memory.
#[derive(Default, Clone, Debug, Copy)]
pub struct MemorySize(usize);
impl MemorySize {
/// Creates `Self` from the given `bytes` size.
pub fn from_bytes(bytes: usize) -> Self {
Self(bytes)
}
/// Returns the memory size as bytes.
pub fn as_bytes(self) -> usize {
self.0
}
}
impl fmt::Display for MemorySize {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.0 < 1024 {
write!(f, "{} bytes", self.0)
} else if self.0 < 1024 * 1024 {
write!(f, "{:.2} KiB", self.0 as f64 / 1024f64)
} else if self.0 < 1024 * 1024 * 1024 {
write!(f, "{:.2} MiB", self.0 as f64 / (1024f64 * 1024f64))
} else {
write!(f, "{:.2} GiB", self.0 as f64 / (1024f64 * 1024f64 * 1024f64))
}
}
}
/// Memory statistics for client instance.
#[derive(Default, Clone, Debug)]
pub struct MemoryInfo {
/// Size of state cache.
pub state_cache: MemorySize,
/// Size of backend database cache.
pub database_cache: MemorySize,
}
/// I/O statistics for client instance.
#[derive(Default, Clone, Debug)]
pub struct IoInfo {
/// Number of transactions.
pub transactions: u64,
/// Total bytes read from disk.
pub bytes_read: u64,
/// Total bytes written to disk.
pub bytes_written: u64,
/// Total key writes to disk.
pub writes: u64,
/// Total key reads from disk.
pub reads: u64,
/// Average size of the transaction.
pub average_transaction_size: u64,
/// State reads (keys)
pub state_reads: u64,
/// State reads (keys) from cache.
pub state_reads_cache: u64,
/// State reads (keys)
pub state_writes: u64,
/// State write (keys) already cached.
pub state_writes_cache: u64,
/// State write (trie nodes) to backend db.
pub state_writes_nodes: u64,
}
/// Usage statistics for running client instance.
///
/// Returning backend determines the scope of these stats,
/// but usually it is either from service start or from previous
/// gathering of the statistics.
#[derive(Default, Clone, Debug)]
pub struct UsageInfo {
/// Memory statistics.
pub memory: MemoryInfo,
/// I/O statistics.
pub io: IoInfo,
}
impl fmt::Display for UsageInfo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"caches: ({} state, {} db overlay), \
i/o: ({} tx, {} write, {} read, {} avg tx, {}/{} key cache reads/total, {} trie nodes writes)",
self.memory.state_cache,
self.memory.database_cache,
self.io.transactions,
self.io.bytes_written,
self.io.bytes_read,
self.io.average_transaction_size,
self.io.state_reads_cache,
self.io.state_reads,
self.io.state_writes_nodes,
)
}
}
/// Sends a message to the pinning-worker once dropped to unpin a block in the backend.
pub struct UnpinHandleInner<Block: BlockT> {
/// Hash of the block pinned by this handle
hash: Block::Hash,
unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
}
impl<Block: BlockT> Debug for UnpinHandleInner<Block> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("UnpinHandleInner").field("pinned_block", &self.hash).finish()
}
}
impl<Block: BlockT> UnpinHandleInner<Block> {
/// Create a new [`UnpinHandleInner`]
pub fn new(
hash: Block::Hash,
unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
) -> Self {
Self { hash, unpin_worker_sender }
}
}
impl<Block: BlockT> Drop for UnpinHandleInner<Block> {
fn drop(&mut self) {
if let Err(err) =
self.unpin_worker_sender.unbounded_send(UnpinWorkerMessage::Unpin(self.hash))
{
log::debug!(target: "db", "Unable to unpin block with hash: {}, error: {:?}", self.hash, err);
};
}
}
/// Message that signals notification-based pinning actions to the pinning-worker.
///
/// When the notification is dropped, an `Unpin` message should be sent to the worker.
#[derive(Debug)]
pub enum UnpinWorkerMessage<Block: BlockT> {
/// Should be sent when a import or finality notification is created.
AnnouncePin(Block::Hash),
/// Should be sent when a import or finality notification is dropped.
Unpin(Block::Hash),
}
/// Keeps a specific block pinned while the handle is alive.
/// Once the last handle instance for a given block is dropped, the
/// block is unpinned in the [`Backend`](crate::backend::Backend::unpin_block).
#[derive(Debug, Clone)]
pub struct UnpinHandle<Block: BlockT>(Arc<UnpinHandleInner<Block>>);
impl<Block: BlockT> UnpinHandle<Block> {
/// Create a new [`UnpinHandle`]
pub fn new(
hash: Block::Hash,
unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
) -> UnpinHandle<Block> {
UnpinHandle(Arc::new(UnpinHandleInner::new(hash, unpin_worker_sender)))
}
/// Hash of the block this handle is unpinning on drop
pub fn hash(&self) -> Block::Hash {
self.0.hash
}
}
/// Summary of an imported block
#[derive(Clone, Debug)]
pub struct BlockImportNotification<Block: BlockT> {
/// Imported block header hash.
pub hash: Block::Hash,
/// Imported block origin.
pub origin: BlockOrigin,
/// Imported block header.
pub header: Block::Header,
/// Is this the new best block.
pub is_new_best: bool,
/// Tree route from old best to new best parent.
///
/// If `None`, there was no re-org while importing.
pub tree_route: Option<Arc<pezsp_blockchain::TreeRoute<Block>>>,
/// Handle to unpin the block this notification is for
unpin_handle: UnpinHandle<Block>,
}
impl<Block: BlockT> BlockImportNotification<Block> {
/// Create new notification
pub fn new(
hash: Block::Hash,
origin: BlockOrigin,
header: Block::Header,
is_new_best: bool,
tree_route: Option<Arc<pezsp_blockchain::TreeRoute<Block>>>,
unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
) -> Self {
Self {
hash,
origin,
header,
is_new_best,
tree_route,
unpin_handle: UnpinHandle::new(hash, unpin_worker_sender),
}
}
/// Consume this notification and extract the unpin handle.
///
/// Note: Only use this if you want to keep the block pinned in the backend.
pub fn into_unpin_handle(self) -> UnpinHandle<Block> {
self.unpin_handle
}
}
/// Summary of a finalized block.
#[derive(Clone, Debug)]
pub struct FinalityNotification<Block: BlockT> {
/// Finalized block header hash.
pub hash: Block::Hash,
/// Finalized block header.
pub header: Block::Header,
/// Path from the old finalized to new finalized parent (implicitly finalized blocks).
///
/// This maps to the range `(old_finalized, new_finalized)`.
pub tree_route: Arc<[Block::Hash]>,
/// Stale blocks.
pub stale_blocks: Arc<[Arc<StaleBlock<Block>>]>,
/// Handle to unpin the block this notification is for
unpin_handle: UnpinHandle<Block>,
}
impl<B: BlockT> TryFrom<BlockImportNotification<B>> for ChainEvent<B> {
type Error = ();
fn try_from(n: BlockImportNotification<B>) -> Result<Self, ()> {
if n.is_new_best {
Ok(Self::NewBestBlock { hash: n.hash, tree_route: n.tree_route })
} else {
Err(())
}
}
}
impl<B: BlockT> From<FinalityNotification<B>> for ChainEvent<B> {
fn from(n: FinalityNotification<B>) -> Self {
Self::Finalized { hash: n.hash, tree_route: n.tree_route }
}
}
impl<Block: BlockT> FinalityNotification<Block> {
/// Create finality notification from finality summary.
pub fn from_summary(
mut summary: FinalizeSummary<Block>,
unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
) -> FinalityNotification<Block> {
let hash = summary.finalized.pop().unwrap_or_default();
FinalityNotification {
hash,
header: summary.header,
tree_route: Arc::from(summary.finalized),
stale_blocks: Arc::from(
summary.stale_blocks.into_iter().map(Arc::from).collect::<Vec<_>>(),
),
unpin_handle: UnpinHandle::new(hash, unpin_worker_sender),
}
}
/// Consume this notification and extract the unpin handle.
///
/// Note: Only use this if you want to keep the block pinned in the backend.
pub fn into_unpin_handle(self) -> UnpinHandle<Block> {
self.unpin_handle
}
}
impl<Block: BlockT> BlockImportNotification<Block> {
/// Create finality notification from finality summary.
pub fn from_summary(
summary: ImportSummary<Block>,
unpin_worker_sender: TracingUnboundedSender<UnpinWorkerMessage<Block>>,
) -> BlockImportNotification<Block> {
let hash = summary.hash;
BlockImportNotification {
hash,
origin: summary.origin,
header: summary.header,
is_new_best: summary.is_new_best,
tree_route: summary.tree_route.map(Arc::new),
unpin_handle: UnpinHandle::new(hash, unpin_worker_sender),
}
}
}
@@ -0,0 +1,130 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Execution extensions for runtime calls.
//!
//! This module is responsible for defining the execution
//! strategy for the runtime calls and provide the right `Externalities`
//! extensions to support APIs for particular execution context & capabilities.
use parking_lot::RwLock;
use pezsp_core::traits::{ReadRuntimeVersion, ReadRuntimeVersionExt};
use pezsp_externalities::{Extension, Extensions};
use pezsp_runtime::traits::{Block as BlockT, NumberFor};
use std::{marker::PhantomData, sync::Arc};
/// Generate the starting set of [`Extensions`].
///
/// These [`Extensions`] are passed to the environment a runtime is executed in.
pub trait ExtensionsFactory<Block: BlockT>: Send + Sync {
/// Create [`Extensions`] for the given input.
///
/// - `block_hash`: The hash of the block in the context that extensions will be used.
/// - `block_number`: The number of the block in the context that extensions will be used.
fn extensions_for(&self, block_hash: Block::Hash, block_number: NumberFor<Block>)
-> Extensions;
}
impl<Block: BlockT> ExtensionsFactory<Block> for () {
fn extensions_for(&self, _: Block::Hash, _: NumberFor<Block>) -> Extensions {
Extensions::new()
}
}
impl<Block: BlockT, T: ExtensionsFactory<Block>> ExtensionsFactory<Block> for Vec<T> {
fn extensions_for(
&self,
block_hash: Block::Hash,
block_number: NumberFor<Block>,
) -> Extensions {
let mut exts = Extensions::new();
exts.extend(self.iter().map(|e| e.extensions_for(block_hash, block_number)));
exts
}
}
/// An [`ExtensionsFactory`] that registers an [`Extension`] before a certain block.
pub struct ExtensionBeforeBlock<Block: BlockT, Ext> {
before: NumberFor<Block>,
_marker: PhantomData<fn(Ext) -> Ext>,
}
impl<Block: BlockT, Ext> ExtensionBeforeBlock<Block, Ext> {
/// Create the extension factory.
///
/// - `before`: The block number until the extension should be registered.
pub fn new(before: NumberFor<Block>) -> Self {
Self { before, _marker: PhantomData }
}
}
impl<Block: BlockT, Ext: Default + Extension> ExtensionsFactory<Block>
for ExtensionBeforeBlock<Block, Ext>
{
fn extensions_for(&self, _: Block::Hash, block_number: NumberFor<Block>) -> Extensions {
let mut exts = Extensions::new();
if block_number < self.before {
exts.register(Ext::default());
}
exts
}
}
/// A producer of execution extensions for offchain calls.
///
/// This crate aggregates extensions available for the offchain calls
/// and is responsible for producing a correct `Extensions` object.
pub struct ExecutionExtensions<Block: BlockT> {
extensions_factory: RwLock<Box<dyn ExtensionsFactory<Block>>>,
read_runtime_version: Arc<dyn ReadRuntimeVersion>,
}
impl<Block: BlockT> ExecutionExtensions<Block> {
/// Create new `ExecutionExtensions` given an `extensions_factory`.
pub fn new(
extensions_factory: Option<Box<dyn ExtensionsFactory<Block>>>,
read_runtime_version: Arc<dyn ReadRuntimeVersion>,
) -> Self {
Self {
extensions_factory: extensions_factory
.map(RwLock::new)
.unwrap_or_else(|| RwLock::new(Box::new(()))),
read_runtime_version,
}
}
/// Set the new extensions_factory
pub fn set_extensions_factory(&self, maker: impl ExtensionsFactory<Block> + 'static) {
*self.extensions_factory.write() = Box::new(maker);
}
/// Produces default extensions based on the input parameters.
pub fn extensions(
&self,
block_hash: Block::Hash,
block_number: NumberFor<Block>,
) -> Extensions {
let mut extensions =
self.extensions_factory.read().extensions_for(block_hash, block_number);
extensions.register(ReadRuntimeVersionExt::new(self.read_runtime_version.clone()));
extensions
}
}
+874
View File
@@ -0,0 +1,874 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! In memory client backend
use parking_lot::RwLock;
use pezsp_blockchain::{CachedHeaderMetadata, HeaderMetadata};
use pezsp_core::{
offchain::storage::InMemOffchainStorage as OffchainStorage, storage::well_known_keys,
};
use pezsp_runtime::{
generic::BlockId,
traits::{Block as BlockT, HashingFor, Header as HeaderT, NumberFor, Zero},
Justification, Justifications, StateVersion, Storage,
};
use pezsp_state_machine::{
Backend as StateBackend, BackendTransaction, ChildStorageCollection, InMemoryBackend,
IndexOperation, StorageCollection,
};
use std::{
collections::{HashMap, HashSet},
ptr,
sync::Arc,
};
use crate::{
backend::{self, NewBlockState},
blockchain::{self, BlockStatus, HeaderBackend},
leaves::LeafSet,
TrieCacheContext, UsageInfo,
};
struct PendingBlock<B: BlockT> {
block: StoredBlock<B>,
state: NewBlockState,
}
#[derive(PartialEq, Eq, Clone)]
enum StoredBlock<B: BlockT> {
Header(B::Header, Option<Justifications>),
Full(B, Option<Justifications>),
}
impl<B: BlockT> StoredBlock<B> {
fn new(
header: B::Header,
body: Option<Vec<B::Extrinsic>>,
just: Option<Justifications>,
) -> Self {
match body {
Some(body) => StoredBlock::Full(B::new(header, body), just),
None => StoredBlock::Header(header, just),
}
}
fn header(&self) -> &B::Header {
match *self {
StoredBlock::Header(ref h, _) => h,
StoredBlock::Full(ref b, _) => b.header(),
}
}
fn justifications(&self) -> Option<&Justifications> {
match *self {
StoredBlock::Header(_, ref j) | StoredBlock::Full(_, ref j) => j.as_ref(),
}
}
fn extrinsics(&self) -> Option<&[B::Extrinsic]> {
match *self {
StoredBlock::Header(_, _) => None,
StoredBlock::Full(ref b, _) => Some(b.extrinsics()),
}
}
fn into_inner(self) -> (B::Header, Option<Vec<B::Extrinsic>>, Option<Justifications>) {
match self {
StoredBlock::Header(header, just) => (header, None, just),
StoredBlock::Full(block, just) => {
let (header, body) = block.deconstruct();
(header, Some(body), just)
},
}
}
}
#[derive(Clone)]
struct BlockchainStorage<Block: BlockT> {
blocks: HashMap<Block::Hash, StoredBlock<Block>>,
hashes: HashMap<NumberFor<Block>, Block::Hash>,
best_hash: Block::Hash,
best_number: NumberFor<Block>,
finalized_hash: Block::Hash,
finalized_number: NumberFor<Block>,
genesis_hash: Block::Hash,
header_cht_roots: HashMap<NumberFor<Block>, Block::Hash>,
leaves: LeafSet<Block::Hash, NumberFor<Block>>,
aux: HashMap<Vec<u8>, Vec<u8>>,
}
/// In-memory blockchain. Supports concurrent reads.
#[derive(Clone)]
pub struct Blockchain<Block: BlockT> {
storage: Arc<RwLock<BlockchainStorage<Block>>>,
}
impl<Block: BlockT> Default for Blockchain<Block> {
fn default() -> Self {
Self::new()
}
}
impl<Block: BlockT> Blockchain<Block> {
/// Get header hash of given block.
pub fn id(&self, id: BlockId<Block>) -> Option<Block::Hash> {
match id {
BlockId::Hash(h) => Some(h),
BlockId::Number(n) => self.storage.read().hashes.get(&n).cloned(),
}
}
/// Create new in-memory blockchain storage.
pub fn new() -> Blockchain<Block> {
let storage = Arc::new(RwLock::new(BlockchainStorage {
blocks: HashMap::new(),
hashes: HashMap::new(),
best_hash: Default::default(),
best_number: Zero::zero(),
finalized_hash: Default::default(),
finalized_number: Zero::zero(),
genesis_hash: Default::default(),
header_cht_roots: HashMap::new(),
leaves: LeafSet::new(),
aux: HashMap::new(),
}));
Blockchain { storage }
}
/// Insert a block header and associated data.
pub fn insert(
&self,
hash: Block::Hash,
header: <Block as BlockT>::Header,
justifications: Option<Justifications>,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
new_state: NewBlockState,
) -> pezsp_blockchain::Result<()> {
let number = *header.number();
if new_state.is_best() {
self.apply_head(&header)?;
}
{
let mut storage = self.storage.write();
storage.leaves.import(hash, number, *header.parent_hash());
storage.blocks.insert(hash, StoredBlock::new(header, body, justifications));
if let NewBlockState::Final = new_state {
storage.finalized_hash = hash;
storage.finalized_number = number;
}
if number == Zero::zero() {
storage.genesis_hash = hash;
}
}
Ok(())
}
/// Get total number of blocks.
pub fn blocks_count(&self) -> usize {
self.storage.read().blocks.len()
}
/// Compare this blockchain with another in-mem blockchain
pub fn equals_to(&self, other: &Self) -> bool {
// Check ptr equality first to avoid double read locks.
if ptr::eq(self, other) {
return true;
}
self.canon_equals_to(other) && self.storage.read().blocks == other.storage.read().blocks
}
/// Compare canonical chain to other canonical chain.
pub fn canon_equals_to(&self, other: &Self) -> bool {
// Check ptr equality first to avoid double read locks.
if ptr::eq(self, other) {
return true;
}
let this = self.storage.read();
let other = other.storage.read();
this.hashes == other.hashes &&
this.best_hash == other.best_hash &&
this.best_number == other.best_number &&
this.genesis_hash == other.genesis_hash
}
/// Insert header CHT root.
pub fn insert_cht_root(&self, block: NumberFor<Block>, cht_root: Block::Hash) {
self.storage.write().header_cht_roots.insert(block, cht_root);
}
/// Set an existing block as head.
pub fn set_head(&self, hash: Block::Hash) -> pezsp_blockchain::Result<()> {
let header = self
.header(hash)?
.ok_or_else(|| pezsp_blockchain::Error::UnknownBlock(format!("{}", hash)))?;
self.apply_head(&header)
}
fn apply_head(&self, header: &<Block as BlockT>::Header) -> pezsp_blockchain::Result<()> {
let hash = header.hash();
let number = header.number();
// Note: this may lock storage, so it must happen before obtaining storage
// write lock.
let best_tree_route = {
let best_hash = self.storage.read().best_hash;
if &best_hash == header.parent_hash() {
None
} else {
let route = pezsp_blockchain::tree_route(self, best_hash, *header.parent_hash())?;
Some(route)
}
};
let mut storage = self.storage.write();
if let Some(tree_route) = best_tree_route {
// apply retraction and enaction when reorganizing up to parent hash
let enacted = tree_route.enacted();
for entry in enacted {
storage.hashes.insert(entry.number, entry.hash);
}
for entry in tree_route.retracted().iter().skip(enacted.len()) {
storage.hashes.remove(&entry.number);
}
}
storage.best_hash = hash;
storage.best_number = *number;
storage.hashes.insert(*number, hash);
Ok(())
}
fn finalize_header(
&self,
block: Block::Hash,
justification: Option<Justification>,
) -> pezsp_blockchain::Result<()> {
let mut storage = self.storage.write();
storage.finalized_hash = block;
if justification.is_some() {
let block = storage
.blocks
.get_mut(&block)
.expect("hash was fetched from a block in the db; qed");
let block_justifications = match block {
StoredBlock::Header(_, ref mut j) | StoredBlock::Full(_, ref mut j) => j,
};
*block_justifications = justification.map(Justifications::from);
}
Ok(())
}
fn append_justification(
&self,
hash: Block::Hash,
justification: Justification,
) -> pezsp_blockchain::Result<()> {
let mut storage = self.storage.write();
let block = storage
.blocks
.get_mut(&hash)
.expect("hash was fetched from a block in the db; qed");
let block_justifications = match block {
StoredBlock::Header(_, ref mut j) | StoredBlock::Full(_, ref mut j) => j,
};
if let Some(stored_justifications) = block_justifications {
if !stored_justifications.append(justification) {
return Err(pezsp_blockchain::Error::BadJustification(
"Duplicate consensus engine ID".into(),
));
}
} else {
*block_justifications = Some(Justifications::from(justification));
};
Ok(())
}
fn write_aux(&self, ops: Vec<(Vec<u8>, Option<Vec<u8>>)>) {
let mut storage = self.storage.write();
for (k, v) in ops {
match v {
Some(v) => storage.aux.insert(k, v),
None => storage.aux.remove(&k),
};
}
}
}
impl<Block: BlockT> HeaderBackend<Block> for Blockchain<Block> {
fn header(
&self,
hash: Block::Hash,
) -> pezsp_blockchain::Result<Option<<Block as BlockT>::Header>> {
Ok(self.storage.read().blocks.get(&hash).map(|b| b.header().clone()))
}
fn info(&self) -> blockchain::Info<Block> {
let storage = self.storage.read();
blockchain::Info {
best_hash: storage.best_hash,
best_number: storage.best_number,
genesis_hash: storage.genesis_hash,
finalized_hash: storage.finalized_hash,
finalized_number: storage.finalized_number,
finalized_state: if storage.finalized_hash != Default::default() {
Some((storage.finalized_hash, storage.finalized_number))
} else {
None
},
number_leaves: storage.leaves.count(),
block_gap: None,
}
}
fn status(&self, hash: Block::Hash) -> pezsp_blockchain::Result<BlockStatus> {
match self.storage.read().blocks.contains_key(&hash) {
true => Ok(BlockStatus::InChain),
false => Ok(BlockStatus::Unknown),
}
}
fn number(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<NumberFor<Block>>> {
Ok(self.storage.read().blocks.get(&hash).map(|b| *b.header().number()))
}
fn hash(
&self,
number: <<Block as BlockT>::Header as HeaderT>::Number,
) -> pezsp_blockchain::Result<Option<Block::Hash>> {
Ok(self.id(BlockId::Number(number)))
}
}
impl<Block: BlockT> HeaderMetadata<Block> for Blockchain<Block> {
type Error = pezsp_blockchain::Error;
fn header_metadata(
&self,
hash: Block::Hash,
) -> Result<CachedHeaderMetadata<Block>, Self::Error> {
self.header(hash)?
.map(|header| CachedHeaderMetadata::from(&header))
.ok_or_else(|| {
pezsp_blockchain::Error::UnknownBlock(format!("header not found: {}", hash))
})
}
fn insert_header_metadata(&self, _hash: Block::Hash, _metadata: CachedHeaderMetadata<Block>) {
// No need to implement.
}
fn remove_header_metadata(&self, _hash: Block::Hash) {
// No need to implement.
}
}
impl<Block: BlockT> blockchain::Backend<Block> for Blockchain<Block> {
fn body(
&self,
hash: Block::Hash,
) -> pezsp_blockchain::Result<Option<Vec<<Block as BlockT>::Extrinsic>>> {
Ok(self
.storage
.read()
.blocks
.get(&hash)
.and_then(|b| b.extrinsics().map(|x| x.to_vec())))
}
fn justifications(&self, hash: Block::Hash) -> pezsp_blockchain::Result<Option<Justifications>> {
Ok(self.storage.read().blocks.get(&hash).and_then(|b| b.justifications().cloned()))
}
fn last_finalized(&self) -> pezsp_blockchain::Result<Block::Hash> {
Ok(self.storage.read().finalized_hash)
}
fn leaves(&self) -> pezsp_blockchain::Result<Vec<Block::Hash>> {
Ok(self.storage.read().leaves.hashes())
}
fn children(&self, _parent_hash: Block::Hash) -> pezsp_blockchain::Result<Vec<Block::Hash>> {
unimplemented!()
}
fn indexed_transaction(&self, _hash: Block::Hash) -> pezsp_blockchain::Result<Option<Vec<u8>>> {
unimplemented!("Not supported by the in-mem backend.")
}
fn block_indexed_body(
&self,
_hash: Block::Hash,
) -> pezsp_blockchain::Result<Option<Vec<Vec<u8>>>> {
unimplemented!("Not supported by the in-mem backend.")
}
}
impl<Block: BlockT> backend::AuxStore for Blockchain<Block> {
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
insert: I,
delete: D,
) -> pezsp_blockchain::Result<()> {
let mut storage = self.storage.write();
for (k, v) in insert {
storage.aux.insert(k.to_vec(), v.to_vec());
}
for k in delete {
storage.aux.remove(*k);
}
Ok(())
}
fn get_aux(&self, key: &[u8]) -> pezsp_blockchain::Result<Option<Vec<u8>>> {
Ok(self.storage.read().aux.get(key).cloned())
}
}
/// In-memory operation.
pub struct BlockImportOperation<Block: BlockT> {
pending_block: Option<PendingBlock<Block>>,
old_state: InMemoryBackend<HashingFor<Block>>,
new_state: Option<BackendTransaction<HashingFor<Block>>>,
aux: Vec<(Vec<u8>, Option<Vec<u8>>)>,
finalized_blocks: Vec<(Block::Hash, Option<Justification>)>,
set_head: Option<Block::Hash>,
}
impl<Block: BlockT> BlockImportOperation<Block> {
fn apply_storage(
&mut self,
storage: Storage,
commit: bool,
state_version: StateVersion,
) -> pezsp_blockchain::Result<Block::Hash> {
check_genesis_storage(&storage)?;
let child_delta = storage.children_default.values().map(|child_content| {
(
&child_content.child_info,
child_content.data.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))),
)
});
let (root, transaction) = self.old_state.full_storage_root(
storage.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))),
child_delta,
state_version,
);
if commit {
self.new_state = Some(transaction);
}
Ok(root)
}
}
impl<Block: BlockT> backend::BlockImportOperation<Block> for BlockImportOperation<Block> {
type State = InMemoryBackend<HashingFor<Block>>;
fn state(&self) -> pezsp_blockchain::Result<Option<&Self::State>> {
Ok(Some(&self.old_state))
}
fn set_block_data(
&mut self,
header: <Block as BlockT>::Header,
body: Option<Vec<<Block as BlockT>::Extrinsic>>,
_indexed_body: Option<Vec<Vec<u8>>>,
justifications: Option<Justifications>,
state: NewBlockState,
) -> pezsp_blockchain::Result<()> {
assert!(self.pending_block.is_none(), "Only one block per operation is allowed");
self.pending_block =
Some(PendingBlock { block: StoredBlock::new(header, body, justifications), state });
Ok(())
}
fn update_db_storage(
&mut self,
update: BackendTransaction<HashingFor<Block>>,
) -> pezsp_blockchain::Result<()> {
self.new_state = Some(update);
Ok(())
}
fn set_genesis_state(
&mut self,
storage: Storage,
commit: bool,
state_version: StateVersion,
) -> pezsp_blockchain::Result<Block::Hash> {
self.apply_storage(storage, commit, state_version)
}
fn reset_storage(
&mut self,
storage: Storage,
state_version: StateVersion,
) -> pezsp_blockchain::Result<Block::Hash> {
self.apply_storage(storage, true, state_version)
}
fn insert_aux<I>(&mut self, ops: I) -> pezsp_blockchain::Result<()>
where
I: IntoIterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
{
self.aux.append(&mut ops.into_iter().collect());
Ok(())
}
fn update_storage(
&mut self,
_update: StorageCollection,
_child_update: ChildStorageCollection,
) -> pezsp_blockchain::Result<()> {
Ok(())
}
fn mark_finalized(
&mut self,
hash: Block::Hash,
justification: Option<Justification>,
) -> pezsp_blockchain::Result<()> {
self.finalized_blocks.push((hash, justification));
Ok(())
}
fn mark_head(&mut self, hash: Block::Hash) -> pezsp_blockchain::Result<()> {
assert!(self.pending_block.is_none(), "Only one set block per operation is allowed");
self.set_head = Some(hash);
Ok(())
}
fn update_transaction_index(
&mut self,
_index: Vec<IndexOperation>,
) -> pezsp_blockchain::Result<()> {
Ok(())
}
fn set_create_gap(&mut self, _create_gap: bool) {}
}
/// In-memory backend. Keeps all states and blocks in memory.
///
/// > **Warning**: Doesn't support all the features necessary for a proper database. Only use this
/// > struct for testing purposes. Do **NOT** use in production.
pub struct Backend<Block: BlockT> {
states: RwLock<HashMap<Block::Hash, InMemoryBackend<HashingFor<Block>>>>,
blockchain: Blockchain<Block>,
import_lock: RwLock<()>,
pinned_blocks: RwLock<HashMap<Block::Hash, i64>>,
}
impl<Block: BlockT> Backend<Block> {
/// Create a new instance of in-mem backend.
///
/// # Warning
///
/// For testing purposes only!
pub fn new() -> Self {
Backend {
states: RwLock::new(HashMap::new()),
blockchain: Blockchain::new(),
import_lock: Default::default(),
pinned_blocks: Default::default(),
}
}
/// Return the number of references active for a pinned block.
///
/// # Warning
///
/// For testing purposes only!
pub fn pin_refs(&self, hash: &<Block as BlockT>::Hash) -> Option<i64> {
let blocks = self.pinned_blocks.read();
blocks.get(hash).map(|value| *value)
}
}
impl<Block: BlockT> backend::AuxStore for Backend<Block> {
fn insert_aux<
'a,
'b: 'a,
'c: 'a,
I: IntoIterator<Item = &'a (&'c [u8], &'c [u8])>,
D: IntoIterator<Item = &'a &'b [u8]>,
>(
&self,
insert: I,
delete: D,
) -> pezsp_blockchain::Result<()> {
self.blockchain.insert_aux(insert, delete)
}
fn get_aux(&self, key: &[u8]) -> pezsp_blockchain::Result<Option<Vec<u8>>> {
self.blockchain.get_aux(key)
}
}
impl<Block: BlockT> backend::Backend<Block> for Backend<Block> {
type BlockImportOperation = BlockImportOperation<Block>;
type Blockchain = Blockchain<Block>;
type State = InMemoryBackend<HashingFor<Block>>;
type OffchainStorage = OffchainStorage;
fn begin_operation(&self) -> pezsp_blockchain::Result<Self::BlockImportOperation> {
let old_state = self.state_at(Default::default(), TrieCacheContext::Untrusted)?;
Ok(BlockImportOperation {
pending_block: None,
old_state,
new_state: None,
aux: Default::default(),
finalized_blocks: Default::default(),
set_head: None,
})
}
fn begin_state_operation(
&self,
operation: &mut Self::BlockImportOperation,
block: Block::Hash,
) -> pezsp_blockchain::Result<()> {
operation.old_state = self.state_at(block, TrieCacheContext::Untrusted)?;
Ok(())
}
fn commit_operation(&self, operation: Self::BlockImportOperation) -> pezsp_blockchain::Result<()> {
if !operation.finalized_blocks.is_empty() {
for (block, justification) in operation.finalized_blocks {
self.blockchain.finalize_header(block, justification)?;
}
}
if let Some(pending_block) = operation.pending_block {
let old_state = &operation.old_state;
let (header, body, justification) = pending_block.block.into_inner();
let hash = header.hash();
let new_state = match operation.new_state {
Some(state) => old_state.update_backend(*header.state_root(), state),
None => old_state.clone(),
};
self.states.write().insert(hash, new_state);
self.blockchain.insert(hash, header, justification, body, pending_block.state)?;
}
if !operation.aux.is_empty() {
self.blockchain.write_aux(operation.aux);
}
if let Some(set_head) = operation.set_head {
self.blockchain.set_head(set_head)?;
}
Ok(())
}
fn finalize_block(
&self,
hash: Block::Hash,
justification: Option<Justification>,
) -> pezsp_blockchain::Result<()> {
self.blockchain.finalize_header(hash, justification)
}
fn append_justification(
&self,
hash: Block::Hash,
justification: Justification,
) -> pezsp_blockchain::Result<()> {
self.blockchain.append_justification(hash, justification)
}
fn blockchain(&self) -> &Self::Blockchain {
&self.blockchain
}
fn usage_info(&self) -> Option<UsageInfo> {
None
}
fn offchain_storage(&self) -> Option<Self::OffchainStorage> {
None
}
fn state_at(
&self,
hash: Block::Hash,
_trie_cache_context: TrieCacheContext,
) -> pezsp_blockchain::Result<Self::State> {
if hash == Default::default() {
return Ok(Self::State::default());
}
self.states
.read()
.get(&hash)
.cloned()
.ok_or_else(|| pezsp_blockchain::Error::UnknownBlock(format!("{}", hash)))
}
fn revert(
&self,
_n: NumberFor<Block>,
_revert_finalized: bool,
) -> pezsp_blockchain::Result<(NumberFor<Block>, HashSet<Block::Hash>)> {
Ok((Zero::zero(), HashSet::new()))
}
fn remove_leaf_block(&self, _hash: Block::Hash) -> pezsp_blockchain::Result<()> {
Ok(())
}
fn get_import_lock(&self) -> &RwLock<()> {
&self.import_lock
}
fn requires_full_sync(&self) -> bool {
false
}
fn pin_block(&self, hash: <Block as BlockT>::Hash) -> blockchain::Result<()> {
let mut blocks = self.pinned_blocks.write();
*blocks.entry(hash).or_default() += 1;
Ok(())
}
fn unpin_block(&self, hash: <Block as BlockT>::Hash) {
let mut blocks = self.pinned_blocks.write();
blocks.entry(hash).and_modify(|counter| *counter -= 1).or_insert(-1);
}
}
impl<Block: BlockT> backend::LocalBackend<Block> for Backend<Block> {}
/// Check that genesis storage is valid.
pub fn check_genesis_storage(storage: &Storage) -> pezsp_blockchain::Result<()> {
if storage.top.iter().any(|(k, _)| well_known_keys::is_child_storage_key(k)) {
return Err(pezsp_blockchain::Error::InvalidState);
}
if storage
.children_default
.keys()
.any(|child_key| !well_known_keys::is_child_storage_key(child_key))
{
return Err(pezsp_blockchain::Error::InvalidState);
}
Ok(())
}
#[cfg(test)]
mod tests {
use crate::{in_mem::Blockchain, NewBlockState};
use pezsp_blockchain::Backend;
use pezsp_runtime::{traits::Header as HeaderT, ConsensusEngineId, Justifications};
use bizinikiwi_test_runtime::{Block, Header, H256};
pub const ID1: ConsensusEngineId = *b"TST1";
pub const ID2: ConsensusEngineId = *b"TST2";
fn header(number: u64) -> Header {
let parent_hash = match number {
0 => Default::default(),
_ => header(number - 1).hash(),
};
Header::new(
number,
H256::from_low_u64_be(0),
H256::from_low_u64_be(0),
parent_hash,
Default::default(),
)
}
fn test_blockchain() -> Blockchain<Block> {
let blockchain = Blockchain::<Block>::new();
let just0 = Some(Justifications::from((ID1, vec![0])));
let just1 = Some(Justifications::from((ID1, vec![1])));
let just2 = None;
let just3 = Some(Justifications::from((ID1, vec![3])));
blockchain
.insert(header(0).hash(), header(0), just0, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(1).hash(), header(1), just1, None, NewBlockState::Final)
.unwrap();
blockchain
.insert(header(2).hash(), header(2), just2, None, NewBlockState::Best)
.unwrap();
blockchain
.insert(header(3).hash(), header(3), just3, None, NewBlockState::Final)
.unwrap();
blockchain
}
#[test]
fn append_and_retrieve_justifications() {
let blockchain = test_blockchain();
let last_finalized = blockchain.last_finalized().unwrap();
blockchain.append_justification(last_finalized, (ID2, vec![4])).unwrap();
let justifications = {
let mut just = Justifications::from((ID1, vec![3]));
just.append((ID2, vec![4]));
just
};
assert_eq!(blockchain.justifications(last_finalized).unwrap(), Some(justifications));
}
#[test]
fn store_duplicate_justifications_is_forbidden() {
let blockchain = test_blockchain();
let last_finalized = blockchain.last_finalized().unwrap();
blockchain.append_justification(last_finalized, (ID2, vec![0])).unwrap();
assert!(matches!(
blockchain.append_justification(last_finalized, (ID2, vec![1])),
Err(pezsp_blockchain::Error::BadJustification(_)),
));
}
}
+436
View File
@@ -0,0 +1,436 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Helper for managing the set of available leaves in the chain for DB implementations.
use codec::{Decode, Encode};
use pezsp_blockchain::{Error, Result};
use pezsp_database::{Database, Transaction};
use pezsp_runtime::traits::AtLeast32Bit;
use std::{cmp::Reverse, collections::BTreeMap};
type DbHash = pezsp_core::H256;
#[derive(Debug, Clone, PartialEq, Eq)]
struct LeafSetItem<H, N> {
hash: H,
number: Reverse<N>,
}
/// Inserted and removed leaves after an import action.
pub struct ImportOutcome<H, N> {
inserted: LeafSetItem<H, N>,
removed: Option<H>,
}
/// Inserted and removed leaves after a remove action.
pub struct RemoveOutcome<H, N> {
inserted: Option<H>,
removed: LeafSetItem<H, N>,
}
/// Removed leaves after a finalization action.
pub struct FinalizationOutcome<I, H, N>
where
I: Iterator<Item = (N, H)>,
{
removed: I,
}
impl<I, H: Ord, N: Ord> FinalizationOutcome<I, H, N>
where
I: Iterator<Item = (N, H)>,
{
/// Constructor
pub fn new(new_displaced: I) -> Self {
FinalizationOutcome { removed: new_displaced }
}
}
/// list of leaf hashes ordered by number (descending).
/// stored in memory for fast access.
/// this allows very fast checking and modification of active leaves.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LeafSet<H, N> {
storage: BTreeMap<Reverse<N>, Vec<H>>,
}
impl<H, N> LeafSet<H, N>
where
H: Clone + PartialEq + Decode + Encode,
N: std::fmt::Debug + Copy + AtLeast32Bit + Decode + Encode,
{
/// Construct a new, blank leaf set.
pub fn new() -> Self {
Self { storage: BTreeMap::new() }
}
/// Read the leaf list from the DB, using given prefix for keys.
pub fn read_from_db(db: &dyn Database<DbHash>, column: u32, prefix: &[u8]) -> Result<Self> {
let mut storage = BTreeMap::new();
match db.get(column, prefix) {
Some(leaves) => {
let vals: Vec<_> = match Decode::decode(&mut leaves.as_ref()) {
Ok(vals) => vals,
Err(_) => return Err(Error::Backend("Error decoding leaves".into())),
};
for (number, hashes) in vals.into_iter() {
storage.insert(Reverse(number), hashes);
}
},
None => {},
}
Ok(Self { storage })
}
/// Update the leaf list on import.
pub fn import(&mut self, hash: H, number: N, parent_hash: H) -> ImportOutcome<H, N> {
let number = Reverse(number);
let removed = if number.0 != N::zero() {
let parent_number = Reverse(number.0 - N::one());
self.remove_leaf(&parent_number, &parent_hash).then(|| parent_hash)
} else {
None
};
self.insert_leaf(number, hash.clone());
ImportOutcome { inserted: LeafSetItem { hash, number }, removed }
}
/// Update the leaf list on removal.
///
/// Note that the leaves set structure doesn't have the information to decide if the
/// leaf we're removing is the last children of the parent. Follows that this method requires
/// the caller to check this condition and optionally pass the `parent_hash` if `hash` is
/// its last child.
///
/// Returns `None` if no modifications are applied.
pub fn remove(
&mut self,
hash: H,
number: N,
parent_hash: Option<H>,
) -> Option<RemoveOutcome<H, N>> {
let number = Reverse(number);
if !self.remove_leaf(&number, &hash) {
return None;
}
let inserted = parent_hash.and_then(|parent_hash| {
if number.0 != N::zero() {
let parent_number = Reverse(number.0 - N::one());
self.insert_leaf(parent_number, parent_hash.clone());
Some(parent_hash)
} else {
None
}
});
Some(RemoveOutcome { inserted, removed: LeafSetItem { hash, number } })
}
/// Remove all leaves displaced by the last block finalization.
pub fn remove_displaced_leaves<I>(&mut self, displaced_leaves: FinalizationOutcome<I, H, N>)
where
I: Iterator<Item = (N, H)>,
{
for (number, hash) in displaced_leaves.removed {
self.remove_leaf(&Reverse(number), &hash);
}
}
/// Undo all pending operations.
///
/// This returns an `Undo` struct, where any
/// `Displaced` objects that have returned by previous method calls
/// should be passed to via the appropriate methods. Otherwise,
/// the on-disk state may get out of sync with in-memory state.
pub fn undo(&mut self) -> Undo<'_, H, N> {
Undo { inner: self }
}
/// Revert to the given block height by dropping all leaves in the leaf set
/// with a block number higher than the target.
///
/// Returns the removed leaves.
pub fn revert(&mut self, best_hash: H, best_number: N) -> impl Iterator<Item = (H, N)> {
let items = self
.storage
.iter()
.flat_map(|(number, hashes)| hashes.iter().map(move |h| (h.clone(), number.0)))
.collect::<Vec<_>>();
for (hash, number) in &items {
if *number > best_number {
assert!(
self.remove_leaf(&Reverse(*number), &hash),
"item comes from an iterator over storage; qed",
);
}
}
let best_number_rev = Reverse(best_number);
let leaves_contains_best = self
.storage
.get(&best_number_rev)
.map_or(false, |hashes| hashes.contains(&best_hash));
// We need to make sure that the best block exists in the leaf set as
// this is an invariant of regular block import.
if !leaves_contains_best {
self.insert_leaf(best_number_rev, best_hash.clone());
}
items.into_iter().filter(move |(_, n)| *n > best_number)
}
/// Returns an iterator over all hashes in the leaf set
/// ordered by their block number descending.
pub fn hashes(&self) -> Vec<H> {
self.storage.iter().flat_map(|(_, hashes)| hashes.iter()).cloned().collect()
}
/// Number of known leaves.
pub fn count(&self) -> usize {
self.storage.values().map(|level| level.len()).sum()
}
/// Write the leaf list to the database transaction.
pub fn prepare_transaction(
&mut self,
tx: &mut Transaction<DbHash>,
column: u32,
prefix: &[u8],
) {
let leaves: Vec<_> = self.storage.iter().map(|(n, h)| (n.0, h.clone())).collect();
tx.set_from_vec(column, prefix, leaves.encode());
}
/// Check if given block is a leaf.
pub fn contains(&self, number: N, hash: H) -> bool {
self.storage
.get(&Reverse(number))
.map_or(false, |hashes| hashes.contains(&hash))
}
fn insert_leaf(&mut self, number: Reverse<N>, hash: H) {
self.storage.entry(number).or_insert_with(Vec::new).push(hash);
}
// Returns true if this leaf was contained, false otherwise.
fn remove_leaf(&mut self, number: &Reverse<N>, hash: &H) -> bool {
let mut empty = false;
let removed = self.storage.get_mut(number).map_or(false, |leaves| {
let mut found = false;
leaves.retain(|h| {
if h == hash {
found = true;
false
} else {
true
}
});
if leaves.is_empty() {
empty = true
}
found
});
if removed && empty {
self.storage.remove(number);
}
removed
}
/// Returns the highest leaf and all hashes associated to it.
pub fn highest_leaf(&self) -> Option<(N, &[H])> {
self.storage.iter().next().map(|(k, v)| (k.0, &v[..]))
}
}
/// Helper for undoing operations.
pub struct Undo<'a, H: 'a, N: 'a> {
inner: &'a mut LeafSet<H, N>,
}
impl<'a, H: 'a, N: 'a> Undo<'a, H, N>
where
H: Clone + PartialEq + Decode + Encode,
N: std::fmt::Debug + Copy + AtLeast32Bit + Decode + Encode,
{
/// Undo an imported block by providing the import operation outcome.
/// No additional operations should be performed between import and undo.
pub fn undo_import(&mut self, outcome: ImportOutcome<H, N>) {
if let Some(removed_hash) = outcome.removed {
let removed_number = Reverse(outcome.inserted.number.0 - N::one());
self.inner.insert_leaf(removed_number, removed_hash);
}
self.inner.remove_leaf(&outcome.inserted.number, &outcome.inserted.hash);
}
/// Undo a removed block by providing the displaced leaf.
/// No additional operations should be performed between remove and undo.
pub fn undo_remove(&mut self, outcome: RemoveOutcome<H, N>) {
if let Some(inserted_hash) = outcome.inserted {
let inserted_number = Reverse(outcome.removed.number.0 - N::one());
self.inner.remove_leaf(&inserted_number, &inserted_hash);
}
self.inner.insert_leaf(outcome.removed.number, outcome.removed.hash);
}
/// Undo a finalization operation by providing the displaced leaves.
/// No additional operations should be performed between finalization and undo.
pub fn undo_finalization<I>(&mut self, outcome: FinalizationOutcome<I, H, N>)
where
I: Iterator<Item = (N, H)>,
{
for (number, hash) in outcome.removed {
self.inner.storage.entry(Reverse(number)).or_default().push(hash);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
#[test]
fn import_works() {
let mut set = LeafSet::new();
set.import(0u32, 0u32, 0u32);
set.import(1_1, 1, 0);
set.import(2_1, 2, 1_1);
set.import(3_1, 3, 2_1);
assert_eq!(set.count(), 1);
assert!(set.contains(3, 3_1));
assert!(!set.contains(2, 2_1));
assert!(!set.contains(1, 1_1));
assert!(!set.contains(0, 0));
set.import(2_2, 2, 1_1);
set.import(1_2, 1, 0);
set.import(2_3, 2, 1_2);
assert_eq!(set.count(), 3);
assert!(set.contains(3, 3_1));
assert!(set.contains(2, 2_2));
assert!(set.contains(2, 2_3));
// Finally test the undo feature
let outcome = set.import(2_4, 2, 1_1);
assert_eq!(outcome.inserted.hash, 2_4);
assert_eq!(outcome.removed, None);
assert_eq!(set.count(), 4);
assert!(set.contains(2, 2_4));
set.undo().undo_import(outcome);
assert_eq!(set.count(), 3);
assert!(set.contains(3, 3_1));
assert!(set.contains(2, 2_2));
assert!(set.contains(2, 2_3));
let outcome = set.import(3_2, 3, 2_3);
assert_eq!(outcome.inserted.hash, 3_2);
assert_eq!(outcome.removed, Some(2_3));
assert_eq!(set.count(), 3);
assert!(set.contains(3, 3_2));
set.undo().undo_import(outcome);
assert_eq!(set.count(), 3);
assert!(set.contains(3, 3_1));
assert!(set.contains(2, 2_2));
assert!(set.contains(2, 2_3));
}
#[test]
fn removal_works() {
let mut set = LeafSet::new();
set.import(10_1u32, 10u32, 0u32);
set.import(11_1, 11, 10_1);
set.import(11_2, 11, 10_1);
set.import(12_1, 12, 11_1);
let outcome = set.remove(12_1, 12, Some(11_1)).unwrap();
assert_eq!(outcome.removed.hash, 12_1);
assert_eq!(outcome.inserted, Some(11_1));
assert_eq!(set.count(), 2);
assert!(set.contains(11, 11_1));
assert!(set.contains(11, 11_2));
let outcome = set.remove(11_1, 11, None).unwrap();
assert_eq!(outcome.removed.hash, 11_1);
assert_eq!(outcome.inserted, None);
assert_eq!(set.count(), 1);
assert!(set.contains(11, 11_2));
let outcome = set.remove(11_2, 11, Some(10_1)).unwrap();
assert_eq!(outcome.removed.hash, 11_2);
assert_eq!(outcome.inserted, Some(10_1));
assert_eq!(set.count(), 1);
assert!(set.contains(10, 10_1));
set.undo().undo_remove(outcome);
assert_eq!(set.count(), 1);
assert!(set.contains(11, 11_2));
}
#[test]
fn flush_to_disk() {
const PREFIX: &[u8] = b"abcdefg";
let db = Arc::new(pezsp_database::MemDb::default());
let mut set = LeafSet::new();
set.import(0u32, 0u32, 0u32);
set.import(1_1, 1, 0);
set.import(2_1, 2, 1_1);
set.import(3_1, 3, 2_1);
let mut tx = Transaction::new();
set.prepare_transaction(&mut tx, 0, PREFIX);
db.commit(tx).unwrap();
let set2 = LeafSet::read_from_db(&*db, 0, PREFIX).unwrap();
assert_eq!(set, set2);
}
#[test]
fn two_leaves_same_height_can_be_included() {
let mut set = LeafSet::new();
set.import(1_1u32, 10u32, 0u32);
set.import(1_2, 10, 0);
assert!(set.storage.contains_key(&Reverse(10)));
assert!(set.contains(10, 1_1));
assert!(set.contains(10, 1_2));
assert!(!set.contains(10, 1_3));
}
}
+90
View File
@@ -0,0 +1,90 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Bizinikiwi client interfaces.
#![warn(missing_docs)]
pub mod backend;
pub mod call_executor;
pub mod client;
pub mod execution_extensions;
pub mod in_mem;
pub mod leaves;
pub mod notifications;
pub mod proof_provider;
pub use backend::*;
pub use call_executor::*;
pub use client::*;
pub use notifications::*;
pub use proof_provider::*;
pub use pezsp_blockchain as blockchain;
pub use pezsp_blockchain::HeaderBackend;
pub use pezsp_state_machine::{CompactProof, StorageProof};
pub use pezsp_storage::{ChildInfo, PrefixedStorageKey, StorageData, StorageKey};
/// Usage Information Provider interface
pub trait UsageProvider<Block: pezsp_runtime::traits::Block> {
/// Get usage info about current client.
fn usage_info(&self) -> ClientInfo<Block>;
}
/// Utility methods for the client.
pub mod utils {
use pezsp_blockchain::{Error, HeaderBackend, HeaderMetadata};
use pezsp_runtime::traits::Block as BlockT;
/// Returns a function for checking block ancestry, the returned function will
/// return `true` if the given hash (second parameter) is a descendent of the
/// base (first parameter). If the `current` parameter is defined, it should
/// represent the current block `hash` and its `parent hash`, if given the
/// function that's returned will assume that `hash` isn't part of the local DB
/// yet, and all searches in the DB will instead reference the parent.
pub fn is_descendent_of<Block: BlockT, T>(
client: &T,
current: Option<(Block::Hash, Block::Hash)>,
) -> impl Fn(&Block::Hash, &Block::Hash) -> Result<bool, Error> + '_
where
T: HeaderBackend<Block> + HeaderMetadata<Block, Error = Error>,
{
move |base, hash| {
if base == hash {
return Ok(false);
}
let mut hash = hash;
if let Some((current_hash, current_parent_hash)) = &current {
if base == current_hash {
return Ok(false);
}
if hash == current_hash {
if base == current_parent_hash {
return Ok(true);
} else {
hash = current_parent_hash;
}
}
}
let ancestor = pezsp_blockchain::lowest_common_ancestor(client, *hash, *base)?;
Ok(ancestor.hash == *base)
}
}
}
+153
View File
@@ -0,0 +1,153 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Storage notifications
use std::{
collections::{HashMap, HashSet},
pin::Pin,
sync::Arc,
task::Poll,
};
use futures::Stream;
use prometheus_endpoint::Registry as PrometheusRegistry;
use pezsc_utils::pubsub::{Hub, Receiver};
use pezsp_core::storage::{StorageData, StorageKey};
use pezsp_runtime::traits::Block as BlockT;
mod registry;
use registry::Registry;
#[cfg(test)]
mod tests;
/// A type of a message delivered to the subscribers
#[derive(Debug)]
pub struct StorageNotification<Hash> {
/// The hash of the block
pub block: Hash,
/// The set of changes
pub changes: StorageChangeSet,
}
/// Storage change set
#[derive(Debug)]
pub struct StorageChangeSet {
changes: Arc<[(StorageKey, Option<StorageData>)]>,
child_changes: Arc<[(StorageKey, Vec<(StorageKey, Option<StorageData>)>)]>,
filter: Keys,
child_filters: ChildKeys,
}
/// Manages storage listeners.
#[derive(Debug)]
pub struct StorageNotifications<Block: BlockT>(Hub<StorageNotification<Block::Hash>, Registry>);
/// Type that implements `futures::Stream` of storage change events.
pub struct StorageEventStream<H>(Receiver<StorageNotification<H>, Registry>);
type Keys = Option<HashSet<StorageKey>>;
type ChildKeys = Option<HashMap<StorageKey, Option<HashSet<StorageKey>>>>;
impl StorageChangeSet {
/// Convert the change set into iterator over storage items.
pub fn iter(
&self,
) -> impl Iterator<Item = (Option<&StorageKey>, &StorageKey, Option<&StorageData>)> + '_ {
let top = self
.changes
.iter()
.filter(move |&(key, _)| match self.filter {
Some(ref filter) => filter.contains(key),
None => true,
})
.map(move |(k, v)| (None, k, v.as_ref()));
let children = self
.child_changes
.iter()
.filter_map(move |(sk, changes)| {
self.child_filters.as_ref().and_then(|cf| {
cf.get(sk).map(|filter| {
changes
.iter()
.filter(move |&(key, _)| match filter {
Some(ref filter) => filter.contains(key),
None => true,
})
.map(move |(k, v)| (Some(sk), k, v.as_ref()))
})
})
})
.flatten();
top.chain(children)
}
}
impl<H> Stream for StorageEventStream<H> {
type Item = StorageNotification<H>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Option<Self::Item>> {
Stream::poll_next(Pin::new(&mut self.get_mut().0), cx)
}
}
impl<Block: BlockT> StorageNotifications<Block> {
/// Initialize a new StorageNotifications
/// optionally pass a prometheus registry to send subscriber metrics to
pub fn new(prometheus_registry: Option<PrometheusRegistry>) -> Self {
let registry = Registry::new(prometheus_registry);
let hub = Hub::new_with_registry("mpsc_storage_notification_items", registry);
StorageNotifications(hub)
}
/// Trigger notification to all listeners.
///
/// Note the changes are going to be filtered by listener's filter key.
/// In fact no event might be sent if clients are not interested in the changes.
pub fn trigger(
&self,
hash: &Block::Hash,
changeset: impl Iterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
child_changeset: impl Iterator<
Item = (Vec<u8>, impl Iterator<Item = (Vec<u8>, Option<Vec<u8>>)>),
>,
) {
self.0.send((hash, changeset, child_changeset))
}
/// Start listening for particular storage keys.
pub fn listen(
&self,
filter_keys: Option<&[StorageKey]>,
filter_child_keys: Option<&[(StorageKey, Option<Vec<StorageKey>>)]>,
) -> StorageEventStream<Block::Hash> {
let receiver = self
.0
.subscribe(registry::SubscribeOp { filter_keys, filter_child_keys }, 100_000);
StorageEventStream(receiver)
}
}
@@ -0,0 +1,365 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use pezsp_core::hexdisplay::HexDisplay;
use fnv::{FnvHashMap, FnvHashSet};
use prometheus_endpoint::{register, CounterVec, Opts, U64};
use pezsc_utils::{
id_sequence::SeqID as SubscriberId,
pubsub::{Dispatch, Subscribe, Unsubscribe},
};
type SubscribersGauge = CounterVec<U64>;
/// A command to subscribe with the specified filters.
///
/// Used by the implementation of [`Subscribe<Op>`] trait for [`Registry].
pub(super) struct SubscribeOp<'a> {
pub filter_keys: Option<&'a [StorageKey]>,
pub filter_child_keys: Option<&'a [(StorageKey, Option<Vec<StorageKey>>)]>,
}
#[derive(Debug, Default)]
pub(super) struct Registry {
pub(super) metrics: Option<SubscribersGauge>,
pub(super) wildcard_listeners: FnvHashSet<SubscriberId>,
pub(super) listeners: HashMap<StorageKey, FnvHashSet<SubscriberId>>,
pub(super) child_listeners: HashMap<
StorageKey,
(HashMap<StorageKey, FnvHashSet<SubscriberId>>, FnvHashSet<SubscriberId>),
>,
pub(super) sinks: FnvHashMap<SubscriberId, SubscriberSink>,
}
#[derive(Debug)]
pub(super) struct SubscriberSink {
subs_id: SubscriberId,
keys: Keys,
child_keys: ChildKeys,
was_triggered: bool,
}
impl Drop for SubscriberSink {
fn drop(&mut self) {
if !self.was_triggered {
log::trace!(
target: "storage_notifications",
"Listener was never triggered: id={}, keys={:?}, child_keys={:?}",
self.subs_id,
PrintKeys(&self.keys),
PrintChildKeys(&self.child_keys),
);
}
}
}
impl SubscriberSink {
fn new(subs_id: SubscriberId, keys: Keys, child_keys: ChildKeys) -> Self {
Self { subs_id, keys, child_keys, was_triggered: false }
}
}
impl Registry {
pub(super) fn new(prometheus_registry: Option<PrometheusRegistry>) -> Self {
let metrics = prometheus_registry.and_then(|r| {
CounterVec::new(
Opts::new(
"bizinikiwi_storage_notification_subscribers",
"Number of subscribers in storage notification sytem",
),
&["action"], // added | removed
)
.and_then(|g| register(g, &r))
.ok()
});
Registry { metrics, ..Default::default() }
}
}
impl Unsubscribe for Registry {
fn unsubscribe(&mut self, subs_id: SubscriberId) {
self.remove_subscriber(subs_id);
}
}
impl<'a> Subscribe<SubscribeOp<'a>> for Registry {
fn subscribe(&mut self, subs_op: SubscribeOp<'a>, subs_id: SubscriberId) {
let SubscribeOp { filter_keys, filter_child_keys } = subs_op;
let keys = Self::listen_from(
subs_id,
filter_keys.as_ref(),
&mut self.listeners,
&mut self.wildcard_listeners,
);
let child_keys = filter_child_keys.map(|filter_child_keys| {
filter_child_keys
.iter()
.map(|(c_key, o_keys)| {
let (c_listeners, c_wildcards) =
self.child_listeners.entry(c_key.clone()).or_default();
(
c_key.clone(),
Self::listen_from(
subs_id,
o_keys.as_ref(),
&mut *c_listeners,
&mut *c_wildcards,
),
)
})
.collect()
});
if let Some(m) = self.metrics.as_ref() {
m.with_label_values(&["added"]).inc();
}
if self
.sinks
.insert(subs_id, SubscriberSink::new(subs_id, keys, child_keys))
.is_some()
{
log::warn!("The `subscribe`-method has been passed a non-unique subs_id (in `sc-client-api::notifications`)");
}
}
}
impl<'a, Hash, CS, CCS, CCSI> Dispatch<(&'a Hash, CS, CCS)> for Registry
where
Hash: Clone,
CS: Iterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
CCS: Iterator<Item = (Vec<u8>, CCSI)>,
CCSI: Iterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
{
type Item = StorageNotification<Hash>;
type Ret = ();
fn dispatch<F>(&mut self, message: (&'a Hash, CS, CCS), dispatch: F) -> Self::Ret
where
F: FnMut(&SubscriberId, Self::Item),
{
let (hash, changeset, child_changeset) = message;
self.trigger(hash, changeset, child_changeset, dispatch);
}
}
impl Registry {
pub(super) fn trigger<Hash, F>(
&mut self,
hash: &Hash,
changeset: impl Iterator<Item = (Vec<u8>, Option<Vec<u8>>)>,
child_changeset: impl Iterator<
Item = (Vec<u8>, impl Iterator<Item = (Vec<u8>, Option<Vec<u8>>)>),
>,
mut dispatch: F,
) where
Hash: Clone,
F: FnMut(&SubscriberId, StorageNotification<Hash>),
{
let has_wildcard = !self.wildcard_listeners.is_empty();
// early exit if no listeners
if !has_wildcard && self.listeners.is_empty() && self.child_listeners.is_empty() {
return;
}
let mut subscribers = self.wildcard_listeners.clone();
let mut changes = Vec::new();
let mut child_changes = Vec::new();
// Collect subscribers and changes
for (k, v) in changeset {
let k = StorageKey(k);
let listeners = self.listeners.get(&k);
if let Some(listeners) = listeners {
subscribers.extend(listeners.iter());
}
if has_wildcard || listeners.is_some() {
changes.push((k, v.map(StorageData)));
}
}
for (sk, changeset) in child_changeset {
let sk = StorageKey(sk);
if let Some((cl, cw)) = self.child_listeners.get(&sk) {
let mut changes = Vec::new();
for (k, v) in changeset {
let k = StorageKey(k);
let listeners = cl.get(&k);
if let Some(listeners) = listeners {
subscribers.extend(listeners.iter());
}
subscribers.extend(cw.iter());
if !cw.is_empty() || listeners.is_some() {
changes.push((k, v.map(StorageData)));
}
}
if !changes.is_empty() {
child_changes.push((sk, changes));
}
}
}
// Don't send empty notifications
if changes.is_empty() && child_changes.is_empty() {
return;
}
let changes = Arc::<[_]>::from(changes);
let child_changes = Arc::<[_]>::from(child_changes);
// Trigger the events
self.sinks.iter_mut().for_each(|(subs_id, sink)| {
if subscribers.contains(subs_id) {
sink.was_triggered = true;
let storage_change_set = StorageChangeSet {
changes: changes.clone(),
child_changes: child_changes.clone(),
filter: sink.keys.clone(),
child_filters: sink.child_keys.clone(),
};
let notification =
StorageNotification { block: hash.clone(), changes: storage_change_set };
dispatch(subs_id, notification);
}
});
}
}
impl Registry {
fn remove_subscriber(&mut self, subscriber: SubscriberId) -> Option<(Keys, ChildKeys)> {
let sink = self.sinks.remove(&subscriber)?;
Self::remove_subscriber_from(
subscriber,
&sink.keys,
&mut self.listeners,
&mut self.wildcard_listeners,
);
if let Some(child_filters) = &sink.child_keys {
for (c_key, filters) in child_filters {
if let Some((listeners, wildcards)) = self.child_listeners.get_mut(c_key) {
Self::remove_subscriber_from(
subscriber,
filters,
&mut *listeners,
&mut *wildcards,
);
if listeners.is_empty() && wildcards.is_empty() {
self.child_listeners.remove(c_key);
}
}
}
}
if let Some(m) = self.metrics.as_ref() {
m.with_label_values(&["removed"]).inc();
}
Some((sink.keys.clone(), sink.child_keys.clone()))
}
fn remove_subscriber_from(
subscriber: SubscriberId,
filters: &Keys,
listeners: &mut HashMap<StorageKey, FnvHashSet<SubscriberId>>,
wildcards: &mut FnvHashSet<SubscriberId>,
) {
match filters {
None => {
wildcards.remove(&subscriber);
},
Some(filters) =>
for key in filters.iter() {
let remove_key = match listeners.get_mut(key) {
Some(ref mut set) => {
set.remove(&subscriber);
set.is_empty()
},
None => false,
};
if remove_key {
listeners.remove(key);
}
},
}
}
fn listen_from(
current_id: SubscriberId,
filter_keys: Option<impl AsRef<[StorageKey]>>,
listeners: &mut HashMap<StorageKey, FnvHashSet<SubscriberId>>,
wildcards: &mut FnvHashSet<SubscriberId>,
) -> Keys {
match filter_keys {
None => {
wildcards.insert(current_id);
None
},
Some(keys) => Some(
keys.as_ref()
.iter()
.map(|key| {
listeners.entry(key.clone()).or_default().insert(current_id);
key.clone()
})
.collect(),
),
}
}
}
pub(super) struct PrintKeys<'a>(pub &'a Keys);
impl<'a> std::fmt::Debug for PrintKeys<'a> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(keys) = self.0 {
fmt.debug_list().entries(keys.iter().map(HexDisplay::from)).finish()
} else {
write!(fmt, "None")
}
}
}
pub(super) struct PrintChildKeys<'a>(pub &'a ChildKeys);
impl<'a> std::fmt::Debug for PrintChildKeys<'a> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
if let Some(map) = self.0 {
fmt.debug_map()
.entries(map.iter().map(|(key, values)| (HexDisplay::from(key), PrintKeys(values))))
.finish()
} else {
write!(fmt, "None")
}
}
}
@@ -0,0 +1,221 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use super::*;
use pezsp_runtime::testing::{Block as RawBlock, TestXt, H256 as Hash};
use std::iter::{empty, Empty};
type TestChangeSet = (
Vec<(StorageKey, Option<StorageData>)>,
Vec<(StorageKey, Vec<(StorageKey, Option<StorageData>)>)>,
);
impl From<TestChangeSet> for StorageChangeSet {
fn from(changes: TestChangeSet) -> Self {
// warning hardcoded child trie wildcard to test upon
let child_filters = Some(
[(StorageKey(vec![4]), None), (StorageKey(vec![5]), None)]
.iter()
.cloned()
.collect(),
);
StorageChangeSet {
changes: From::from(changes.0),
child_changes: From::from(changes.1),
filter: None,
child_filters,
}
}
}
impl PartialEq for StorageChangeSet {
fn eq(&self, other: &Self) -> bool {
self.iter().eq(other.iter())
}
}
type Block = RawBlock<TestXt<bizinikiwi_test_runtime::RuntimeCall, ()>>;
#[test]
fn triggering_change_should_notify_wildcard_listeners() {
// given
let notifications = StorageNotifications::<Block>::new(None);
let child_filter = [(StorageKey(vec![4]), None)];
let mut recv =
futures::executor::block_on_stream(notifications.listen(None, Some(&child_filter[..])));
// when
let changeset = vec![(vec![2], Some(vec![3])), (vec![3], None)];
let c_changeset_1 = vec![(vec![5], Some(vec![4])), (vec![6], None)];
let c_changeset = vec![(vec![4], c_changeset_1)];
notifications.trigger(
&Hash::from_low_u64_be(1),
changeset.into_iter(),
c_changeset.into_iter().map(|(a, b)| (a, b.into_iter())),
);
// then
assert_eq!(
recv.next().map(StorageNotification::into_fields).unwrap(),
(
Hash::from_low_u64_be(1),
(
vec![
(StorageKey(vec![2]), Some(StorageData(vec![3]))),
(StorageKey(vec![3]), None),
],
vec![(
StorageKey(vec![4]),
vec![
(StorageKey(vec![5]), Some(StorageData(vec![4]))),
(StorageKey(vec![6]), None),
]
)]
)
.into()
)
);
}
#[test]
fn should_only_notify_interested_listeners() {
// given
let notifications = StorageNotifications::<Block>::new(None);
let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))];
let mut recv1 = futures::executor::block_on_stream(
notifications.listen(Some(&[StorageKey(vec![1])]), None),
);
let mut recv2 = futures::executor::block_on_stream(
notifications.listen(Some(&[StorageKey(vec![2])]), None),
);
let mut recv3 =
futures::executor::block_on_stream(notifications.listen(Some(&[]), Some(&child_filter)));
// when
let changeset = vec![(vec![2], Some(vec![3])), (vec![1], None)];
let c_changeset_1 = vec![(vec![5], Some(vec![4])), (vec![6], None)];
let c_changeset = vec![(vec![4], c_changeset_1)];
notifications.trigger(
&Hash::from_low_u64_be(1),
changeset.into_iter(),
c_changeset.into_iter().map(|(a, b)| (a, b.into_iter())),
);
// then
assert_eq!(
recv1.next().map(StorageNotification::into_fields).unwrap(),
(Hash::from_low_u64_be(1), (vec![(StorageKey(vec![1]), None),], vec![]).into())
);
assert_eq!(
recv2.next().map(StorageNotification::into_fields).unwrap(),
(
Hash::from_low_u64_be(1),
(vec![(StorageKey(vec![2]), Some(StorageData(vec![3]))),], vec![]).into()
)
);
assert_eq!(
recv3.next().map(StorageNotification::into_fields).unwrap(),
(
Hash::from_low_u64_be(1),
(
vec![],
vec![(
StorageKey(vec![4]),
vec![(StorageKey(vec![5]), Some(StorageData(vec![4])))]
),]
)
.into()
)
);
}
#[test]
fn should_cleanup_subscribers_if_dropped() {
// given
let notifications = StorageNotifications::<Block>::new(None);
{
let child_filter = [(StorageKey(vec![4]), Some(vec![StorageKey(vec![5])]))];
let _recv1 = futures::executor::block_on_stream(
notifications.listen(Some(&[StorageKey(vec![1])]), None),
);
let _recv2 = futures::executor::block_on_stream(
notifications.listen(Some(&[StorageKey(vec![2])]), None),
);
let _recv3 = futures::executor::block_on_stream(notifications.listen(None, None));
let _recv4 =
futures::executor::block_on_stream(notifications.listen(None, Some(&child_filter)));
assert_eq!(notifications.map_registry(|r| r.listeners.len()), 2);
assert_eq!(notifications.map_registry(|r| r.wildcard_listeners.len()), 2);
assert_eq!(notifications.map_registry(|r| r.child_listeners.len()), 1);
}
// when
let changeset = vec![(vec![2], Some(vec![3])), (vec![1], None)];
let c_changeset = empty::<(_, Empty<_>)>();
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset);
// then
assert_eq!(notifications.map_registry(|r| r.listeners.len()), 0);
assert_eq!(notifications.map_registry(|r| r.wildcard_listeners.len()), 0);
assert_eq!(notifications.map_registry(|r| r.child_listeners.len()), 0);
}
#[test]
fn should_cleanup_subscriber_if_stream_is_dropped() {
let notifications = StorageNotifications::<Block>::new(None);
let stream = notifications.listen(None, None);
assert_eq!(notifications.map_registry(|r| r.sinks.len()), 1);
std::mem::drop(stream);
assert_eq!(notifications.map_registry(|r| r.sinks.len()), 0);
}
#[test]
fn should_not_send_empty_notifications() {
// given
let mut recv = {
let notifications = StorageNotifications::<Block>::new(None);
let recv = futures::executor::block_on_stream(notifications.listen(None, None));
// when
let changeset = vec![];
let c_changeset = empty::<(_, Empty<_>)>();
notifications.trigger(&Hash::from_low_u64_be(1), changeset.into_iter(), c_changeset);
recv
};
// then
assert_eq!(recv.next().map(StorageNotification::into_fields), None);
}
impl<B: BlockT> StorageNotifications<B> {
fn map_registry<MapF, Ret>(&self, map: MapF) -> Ret
where
MapF: FnOnce(&Registry) -> Ret,
{
self.0.map_registry_for_tests(map)
}
}
impl<H> StorageNotification<H> {
fn into_fields(self) -> (H, StorageChangeSet) {
let Self { block, changes } = self;
(block, changes)
}
}
@@ -0,0 +1,93 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Proof utilities
use crate::{CompactProof, StorageProof};
use pezsp_runtime::traits::Block as BlockT;
use pezsp_state_machine::{KeyValueStates, KeyValueStorageLevel};
use pezsp_storage::ChildInfo;
/// Interface for providing block proving utilities.
pub trait ProofProvider<Block: BlockT> {
/// Reads storage value at a given block + key, returning read proof.
fn read_proof(
&self,
hash: Block::Hash,
keys: &mut dyn Iterator<Item = &[u8]>,
) -> pezsp_blockchain::Result<StorageProof>;
/// Reads child storage value at a given block + storage_key + key, returning
/// read proof.
fn read_child_proof(
&self,
hash: Block::Hash,
child_info: &ChildInfo,
keys: &mut dyn Iterator<Item = &[u8]>,
) -> pezsp_blockchain::Result<StorageProof>;
/// Execute a call to a contract on top of state in a block of given hash
/// AND returning execution proof.
///
/// No changes are made.
fn execution_proof(
&self,
hash: Block::Hash,
method: &str,
call_data: &[u8],
) -> pezsp_blockchain::Result<(Vec<u8>, StorageProof)>;
/// Given a `Hash` iterate over all storage values starting at `start_keys`.
/// Last `start_keys` element contains last accessed key value.
/// With multiple `start_keys`, first `start_keys` element is
/// the current storage key of of the last accessed child trie.
/// at last level the value to start at exclusively.
/// Proofs is build until size limit is reached and always include at
/// least one key following `start_keys`.
/// Returns combined proof and the numbers of collected keys.
fn read_proof_collection(
&self,
hash: Block::Hash,
start_keys: &[Vec<u8>],
size_limit: usize,
) -> pezsp_blockchain::Result<(CompactProof, u32)>;
/// Given a `Hash` iterate over all storage values starting at `start_key`.
/// Returns collected keys and values.
/// Returns the collected keys values content of the top trie followed by the
/// collected keys values of child tries.
/// Only child tries with their root part of the collected content or
/// related to `start_key` are attached.
/// For each collected state a boolean indicates if state reach
/// end.
fn storage_collection(
&self,
hash: Block::Hash,
start_key: &[Vec<u8>],
size_limit: usize,
) -> pezsp_blockchain::Result<Vec<(KeyValueStorageLevel, bool)>>;
/// Verify read storage proof for a set of keys.
/// Returns collected key-value pairs and the nested state
/// depth of current iteration or 0 if completed.
fn verify_range_proof(
&self,
root: Block::Hash,
proof: CompactProof,
start_keys: &[Vec<u8>],
) -> pezsp_blockchain::Result<(KeyValueStates, usize)>;
}
@@ -0,0 +1,65 @@
[package]
name = "pezsc-authority-discovery"
version = "0.34.0"
authors.workspace = true
edition.workspace = true
build = "build.rs"
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Bizinikiwi authority discovery."
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
async-trait = { workspace = true }
codec = { workspace = true }
futures = { workspace = true }
futures-timer = { workspace = true }
ip_network = { workspace = true }
linked_hash_set = { workspace = true }
log = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
prost = { workspace = true }
rand = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-network-types = { workspace = true, default-features = true }
pezsc-service.workspace = true
serde.workspace = true
serde_json.workspace = true
pezsp-api = { workspace = true, default-features = true }
pezsp-authority-discovery = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-keystore = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
thiserror = { workspace = true }
tokio.workspace = true
[dev-dependencies]
hex.workspace = true
quickcheck = { workspace = true }
pezsp-tracing = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
tempfile.workspace = true
[build-dependencies]
prost-build = { workspace = true }
[features]
runtime-benchmarks = [
"pezsc-client-api/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsc-service/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-authority-discovery/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,9 @@
# Bizinikiwi authority discovery
This crate enables Bizinikiwi authorities to discover and directly connect to
other authorities. It is split into two components the [`Worker`] and the
[`Service`].
See [`Worker`] and [`Service`] for more documentation.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,29 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
fn main() {
prost_build::compile_protos(
&[
"src/worker/schema/dht-v1.proto",
"src/worker/schema/dht-v2.proto",
"src/worker/schema/dht-v3.proto",
],
&["src/worker/schema"],
)
.unwrap();
}
@@ -0,0 +1,87 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Authority discovery errors.
/// AuthorityDiscovery Result.
pub type Result<T> = std::result::Result<T, Error>;
/// Error type for the authority discovery module.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error("Received dht value found event with records with different keys.")]
ReceivingDhtValueFoundEventWithDifferentKeys,
#[error("Received dht value found event with no records.")]
ReceivingDhtValueFoundEventWithNoRecords,
#[error("Failed to verify a dht payload with the given signature.")]
VerifyingDhtPayload,
#[error("Failed to hash the authority id to be used as a dht key.")]
HashingAuthorityId(#[from] pezsc_network_types::multihash::Error),
#[error("Failed calling into the Bizinikiwi runtime: {0}")]
CallingRuntime(#[from] pezsp_blockchain::Error),
#[error("Received a dht record with a key that does not match any in-flight awaited keys.")]
ReceivingUnexpectedRecord,
#[error("Failed to encode a protobuf payload.")]
EncodingProto(#[from] prost::EncodeError),
#[error("Failed to decode a protobuf payload.")]
DecodingProto(#[from] prost::DecodeError),
#[error("Failed to encode or decode scale payload.")]
EncodingDecodingScale(#[from] codec::Error),
#[error("Failed to encode or decode AddrCache.")]
EncodingDecodingAddrCache(String),
#[error("Failed to parse a libp2p multi address.")]
ParsingMultiaddress(#[from] pezsc_network::multiaddr::ParseError),
#[error("Failed to parse a libp2p key: {0}")]
ParsingLibp2pIdentity(String),
#[error("Failed to sign: {0}.")]
CannotSign(String),
#[error("Failed to register Prometheus metric.")]
Prometheus(#[from] prometheus_endpoint::PrometheusError),
#[error("Received authority record that contains addresses with multiple peer ids")]
ReceivingDhtValueFoundEventWithDifferentPeerIds,
#[error("Received authority record without any addresses having a peer id")]
ReceivingDhtValueFoundEventWithNoPeerIds,
#[error("Received authority record without a valid signature for the remote peer id.")]
MissingPeerIdSignature,
#[error("Unable to fetch best block.")]
BestBlockFetchingError,
#[error("Publisher not present.")]
MissingPublisher,
#[error("Unknown authority.")]
UnknownAuthority,
}
@@ -0,0 +1,76 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use futures::{future::FutureExt, ready, stream::Stream};
use futures_timer::Delay;
use std::{
pin::Pin,
task::{Context, Poll},
time::Duration,
};
/// Exponentially increasing interval
///
/// Doubles interval duration on each tick until the configured maximum is reached.
pub struct ExpIncInterval {
start: Duration,
max: Duration,
next: Duration,
delay: Delay,
}
impl ExpIncInterval {
/// Create a new [`ExpIncInterval`].
pub fn new(start: Duration, max: Duration) -> Self {
let delay = Delay::new(start);
Self { start, max, next: start * 2, delay }
}
/// Fast forward the exponentially increasing interval to the configured maximum, if not already
/// set.
pub fn set_to_max(&mut self) {
if self.next == self.max {
return;
}
self.next = self.max;
self.delay = Delay::new(self.next);
}
/// Rewind the exponentially increasing interval to the configured start, if not already set.
pub fn set_to_start(&mut self) {
if self.next == self.start * 2 {
return;
}
self.next = self.start * 2;
self.delay = Delay::new(self.start);
}
}
impl Stream for ExpIncInterval {
type Item = ();
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll<Option<Self::Item>> {
ready!(self.delay.poll_unpin(cx));
self.delay = Delay::new(self.next);
self.next = std::cmp::min(self.max, self.next * 2);
Poll::Ready(Some(()))
}
}
@@ -0,0 +1,190 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![warn(missing_docs)]
#![recursion_limit = "1024"]
//! Bizinikiwi authority discovery.
//!
//! This crate enables Bizinikiwi authorities to discover and directly connect to
//! other authorities. It is split into two components the [`Worker`] and the
//! [`Service`].
//!
//! See [`Worker`] and [`Service`] for more documentation.
pub use crate::{
error::Error,
service::Service,
worker::{AuthorityDiscovery, NetworkProvider, Role, Worker},
};
use std::{collections::HashSet, path::PathBuf, sync::Arc, time::Duration};
use futures::{
channel::{mpsc, oneshot},
Stream,
};
use pezsc_network::{event::DhtEvent, Multiaddr};
use pezsc_network_types::PeerId;
use pezsp_authority_discovery::AuthorityId;
use pezsp_blockchain::HeaderBackend;
use pezsp_core::traits::SpawnNamed;
use pezsp_runtime::traits::Block as BlockT;
mod error;
mod interval;
mod service;
mod worker;
#[cfg(test)]
mod tests;
/// Configuration of [`Worker`].
pub struct WorkerConfig {
/// The maximum interval in which the node will publish its own address on the DHT.
///
/// By default this is set to 1 hour.
pub max_publish_interval: Duration,
/// Interval at which the keystore is queried. If the keys have changed, unconditionally
/// re-publish its addresses on the DHT.
///
/// By default this is set to 1 minute.
pub keystore_refresh_interval: Duration,
/// The maximum interval in which the node will query the DHT for new entries.
///
/// By default this is set to 10 minutes.
pub max_query_interval: Duration,
/// If `false`, the node won't publish on the DHT multiaddresses that contain non-global
/// IP addresses (such as 10.0.0.1).
///
/// Recommended: `false` for live chains, and `true` for local chains or for testing.
///
/// Defaults to `true` to avoid the surprise factor.
pub publish_non_global_ips: bool,
/// Public addresses set by the node operator to always publish first in the authority
/// discovery DHT record.
pub public_addresses: Vec<Multiaddr>,
/// Reject authority discovery records that are not signed by their network identity (PeerId)
///
/// Defaults to `false` to provide compatibility with old versions
pub strict_record_validation: bool,
/// The directory of where the persisted AddrCache file is located,
/// optional since NetworkConfiguration's `net_config_path` field
/// is optional. If None, we won't persist the AddrCache at all.
pub persisted_cache_directory: Option<PathBuf>,
}
impl Default for WorkerConfig {
fn default() -> Self {
Self {
// Kademlia's default time-to-live for Dht records is 36h, republishing records every
// 24h through libp2p-kad. Given that a node could restart at any point in time, one can
// not depend on the republishing process, thus publishing own external addresses should
// happen on an interval < 36h.
max_publish_interval: Duration::from_secs(1 * 60 * 60),
keystore_refresh_interval: Duration::from_secs(60),
// External addresses of remote authorities can change at any given point in time. The
// interval on which to trigger new queries for the current and next authorities is a
// trade off between efficiency and performance.
//
// Querying 700 [`AuthorityId`]s takes ~8m on the Kusama DHT (16th Nov 2020) when
// comparing `authority_discovery_authority_addresses_requested_total` and
// `authority_discovery_dht_event_received`.
max_query_interval: Duration::from_secs(10 * 60),
publish_non_global_ips: true,
public_addresses: Vec::new(),
strict_record_validation: false,
persisted_cache_directory: None,
}
}
}
/// Create a new authority discovery [`Worker`] and [`Service`].
///
/// See the struct documentation of each for more details.
pub fn new_worker_and_service<Client, Block, DhtEventStream>(
client: Arc<Client>,
network: Arc<dyn NetworkProvider>,
dht_event_rx: DhtEventStream,
role: Role,
prometheus_registry: Option<prometheus_endpoint::Registry>,
spawner: impl SpawnNamed + 'static,
) -> (Worker<Client, Block, DhtEventStream>, Service)
where
Block: BlockT + Unpin + 'static,
Client: AuthorityDiscovery<Block> + Send + Sync + 'static + HeaderBackend<Block>,
DhtEventStream: Stream<Item = DhtEvent> + Unpin,
{
new_worker_and_service_with_config(
Default::default(),
client,
network,
dht_event_rx,
role,
prometheus_registry,
spawner,
)
}
/// Same as [`new_worker_and_service`] but with support for providing the `config`.
///
/// When in doubt use [`new_worker_and_service`] as it will use the default configuration.
pub fn new_worker_and_service_with_config<Client, Block, DhtEventStream>(
config: WorkerConfig,
client: Arc<Client>,
network: Arc<dyn NetworkProvider>,
dht_event_rx: DhtEventStream,
role: Role,
prometheus_registry: Option<prometheus_endpoint::Registry>,
spawner: impl SpawnNamed + 'static,
) -> (Worker<Client, Block, DhtEventStream>, Service)
where
Block: BlockT + Unpin + 'static,
Client: AuthorityDiscovery<Block> + 'static,
DhtEventStream: Stream<Item = DhtEvent> + Unpin,
{
let (to_worker, from_service) = mpsc::channel(0);
let worker = Worker::new(
from_service,
client,
network,
dht_event_rx,
role,
prometheus_registry,
config,
spawner,
);
let service = Service::new(to_worker);
(worker, service)
}
/// Message send from the [`Service`] to the [`Worker`].
pub(crate) enum ServicetoWorkerMsg {
/// See [`Service::get_addresses_by_authority_id`].
GetAddressesByAuthorityId(AuthorityId, oneshot::Sender<Option<HashSet<Multiaddr>>>),
/// See [`Service::get_authority_ids_by_peer_id`].
GetAuthorityIdsByPeerId(PeerId, oneshot::Sender<Option<HashSet<AuthorityId>>>),
}
@@ -0,0 +1,95 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use std::{collections::HashSet, fmt::Debug};
use crate::ServicetoWorkerMsg;
use futures::{
channel::{mpsc, oneshot},
SinkExt,
};
use pezsc_network::Multiaddr;
use pezsc_network_types::PeerId;
use pezsp_authority_discovery::AuthorityId;
/// Service to interact with the [`crate::Worker`].
#[derive(Clone)]
pub struct Service {
to_worker: mpsc::Sender<ServicetoWorkerMsg>,
}
impl Debug for Service {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("AuthorityDiscoveryService").finish()
}
}
/// A [`Service`] allows to interact with a [`crate::Worker`], e.g. by querying the
/// [`crate::Worker`]'s local address cache for a given [`AuthorityId`].
impl Service {
pub(crate) fn new(to_worker: mpsc::Sender<ServicetoWorkerMsg>) -> Self {
Self { to_worker }
}
/// Get the addresses for the given [`AuthorityId`] from the local address
/// cache.
///
/// Returns `None` if no entry was present or connection to the
/// [`crate::Worker`] failed.
///
/// Note: [`Multiaddr`]s returned always include a [`PeerId`] via a
/// [`pezsc_network_types::multiaddr::Protocol::P2p`] component. Equality of
/// [`PeerId`]s across [`Multiaddr`]s returned by a single call is not
/// enforced today, given that there are still authorities out there
/// publishing the addresses of their sentry nodes on the DHT. In the future
/// this guarantee can be provided.
pub async fn get_addresses_by_authority_id(
&mut self,
authority: AuthorityId,
) -> Option<HashSet<Multiaddr>> {
let (tx, rx) = oneshot::channel();
self.to_worker
.send(ServicetoWorkerMsg::GetAddressesByAuthorityId(authority, tx))
.await
.ok()?;
rx.await.ok().flatten()
}
/// Get the [`AuthorityId`] for the given [`PeerId`] from the local address
/// cache.
///
/// Returns `None` if no entry was present or connection to the
/// [`crate::Worker`] failed.
pub async fn get_authority_ids_by_peer_id(
&mut self,
peer_id: PeerId,
) -> Option<HashSet<AuthorityId>> {
let (tx, rx) = oneshot::channel();
self.to_worker
.send(ServicetoWorkerMsg::GetAuthorityIdsByPeerId(peer_id, tx))
.await
.ok()?;
rx.await.ok().flatten()
}
}
@@ -0,0 +1,168 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
new_worker_and_service_with_config,
worker::{
tests::{TestApi, TestNetwork},
AddrCache, Role,
},
WorkerConfig,
};
use futures::{channel::mpsc::channel, executor::LocalPool, task::LocalSpawn};
use pezsc_network_types::ed25519;
use std::{collections::HashSet, sync::Arc};
use pezsc_network::{multiaddr::Protocol, Multiaddr, PeerId};
use pezsp_authority_discovery::AuthorityId;
use pezsp_core::{crypto::key_types, testing::TaskExecutor, traits::SpawnNamed};
use pezsp_keystore::{testing::MemoryKeystore, Keystore};
pub(super) fn create_spawner() -> Box<dyn SpawnNamed> {
Box::new(TaskExecutor::new())
}
pub(super) fn test_config(path_buf: Option<std::path::PathBuf>) -> WorkerConfig {
WorkerConfig { persisted_cache_directory: path_buf, ..Default::default() }
}
#[tokio::test]
async fn get_addresses_and_authority_id() {
let (_dht_event_tx, dht_event_rx) = channel(0);
let network: Arc<TestNetwork> = Arc::new(Default::default());
let mut pool = LocalPool::new();
let key_store = MemoryKeystore::new();
let remote_authority_id: AuthorityId = pool.run_until(async {
key_store
.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)
.unwrap()
.into()
});
let remote_peer_id = PeerId::random();
let remote_addr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
.parse::<Multiaddr>()
.unwrap()
.with(Protocol::P2p(remote_peer_id.into()));
let test_api = Arc::new(TestApi { authorities: vec![] });
let tempdir = tempfile::tempdir().unwrap();
let path = tempdir.path().to_path_buf();
let (mut worker, mut service) = new_worker_and_service_with_config(
test_config(Some(path)),
test_api,
network.clone(),
Box::pin(dht_event_rx),
Role::PublishAndDiscover(key_store.into()),
None,
create_spawner(),
);
worker.inject_addresses(remote_authority_id.clone(), vec![remote_addr.clone()]);
pool.spawner().spawn_local_obj(Box::pin(worker.run()).into()).unwrap();
pool.run_until(async {
assert_eq!(
Some(HashSet::from([remote_addr])),
service.get_addresses_by_authority_id(remote_authority_id.clone()).await,
);
assert_eq!(
Some(HashSet::from([remote_authority_id])),
service.get_authority_ids_by_peer_id(remote_peer_id.into()).await,
);
});
}
#[tokio::test]
async fn cryptos_are_compatible() {
use pezsp_core::crypto::Pair;
let libp2p_keypair = ed25519::Keypair::generate();
let libp2p_public = libp2p_keypair.public();
let pezsp_core_secret =
{ pezsp_core::ed25519::Pair::from_seed_slice(&libp2p_keypair.secret().as_ref()).unwrap() };
let pezsp_core_public = pezsp_core_secret.public();
let message = b"we are more powerful than not to be better";
let libp2p_signature = libp2p_keypair.sign(message);
let pezsp_core_signature = pezsp_core_secret.sign(message); // no error expected...
assert!(pezsp_core::ed25519::Pair::verify(
&pezsp_core::ed25519::Signature::try_from(libp2p_signature.as_slice()).unwrap(),
message,
&pezsp_core_public
));
assert!(libp2p_public.verify(message, pezsp_core_signature.as_ref()));
}
#[tokio::test]
async fn when_addr_cache_is_persisted_with_authority_ids_then_when_worker_is_created_it_loads_the_persisted_cache(
) {
// ARRANGE
let (_dht_event_tx, dht_event_rx) = channel(0);
let mut pool = LocalPool::new();
let key_store = MemoryKeystore::new();
let remote_authority_id: AuthorityId = pool.run_until(async {
key_store
.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)
.unwrap()
.into()
});
let remote_peer_id = PeerId::random();
let remote_addr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
.parse::<Multiaddr>()
.unwrap()
.with(Protocol::P2p(remote_peer_id.into()));
let tempdir = tempfile::tempdir().unwrap();
let cache_path = tempdir.path().to_path_buf();
// persist the remote_authority_id and remote_addr in the cache
{
let mut addr_cache = AddrCache::default();
addr_cache.insert(remote_authority_id.clone(), vec![remote_addr.clone()]);
let path_to_save = cache_path.join(crate::worker::ADDR_CACHE_FILE_NAME);
addr_cache.serialize_and_persist(&path_to_save);
}
let (_, from_service) = futures::channel::mpsc::channel(0);
// ACT
// Create a worker with the persisted cache
let worker = crate::worker::Worker::new(
from_service,
Arc::new(TestApi { authorities: vec![] }),
Arc::new(TestNetwork::default()),
Box::pin(dht_event_rx),
Role::PublishAndDiscover(key_store.into()),
None,
test_config(Some(cache_path)),
create_spawner(),
);
// ASSERT
assert!(worker.contains_authority(&remote_authority_id));
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,675 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error::Error;
use log::{info, warn};
use pezsc_network::{multiaddr::Protocol, Multiaddr};
use pezsc_network_types::PeerId;
use serde::{Deserialize, Serialize};
use pezsp_authority_discovery::AuthorityId;
use pezsp_runtime::DeserializeOwned;
use std::{
collections::{hash_map::Entry, HashMap, HashSet},
fs::File,
io::{self, BufReader, Write},
path::Path,
};
/// Cache for [`AuthorityId`] -> [`HashSet<Multiaddr>`] and [`PeerId`] -> [`HashSet<AuthorityId>`]
/// mappings.
#[derive(Default, Clone, PartialEq, Debug)]
pub(crate) struct AddrCache {
/// The addresses found in `authority_id_to_addresses` are guaranteed to always match
/// the peerids found in `peer_id_to_authority_ids`. In other words, these two hashmaps
/// are similar to a bi-directional map.
///
/// Since we may store the mapping across several sessions, a single
/// `PeerId` might correspond to multiple `AuthorityId`s. However,
/// it's not expected that a single `AuthorityId` can have multiple `PeerId`s.
authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>>,
}
impl Serialize for AddrCache {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
SerializeAddrCache::from(self.clone()).serialize(serializer)
}
}
impl<'de> Deserialize<'de> for AddrCache {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
SerializeAddrCache::deserialize(deserializer).map(Into::into)
}
}
/// A storage and serialization time optimized version of `AddrCache`
/// which contains the bare minimum info to reconstruct the AddrCache. We
/// rely on the fact that the `peer_id_to_authority_ids` can be reconstructed from
/// the `authority_id_to_addresses` field.
///
/// Benchmarks show that this is about 2x faster to serialize and about 4x faster to deserialize
/// compared to the full `AddrCache`.
///
/// Storage wise it is about half the size of the full `AddrCache`.
///
/// This is used to persist the `AddrCache` to disk and load it back.
///
/// AddrCache impl of Serialize and Deserialize "piggybacks" on this struct.
#[derive(Serialize, Deserialize)]
struct SerializeAddrCache {
authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
}
impl From<SerializeAddrCache> for AddrCache {
fn from(value: SerializeAddrCache) -> Self {
let mut peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>> = HashMap::new();
for (authority_id, addresses) in &value.authority_id_to_addresses {
for peer_id in addresses_to_peer_ids(addresses) {
peer_id_to_authority_ids
.entry(peer_id)
.or_insert_with(HashSet::new)
.insert(authority_id.clone());
}
}
AddrCache {
authority_id_to_addresses: value.authority_id_to_addresses,
peer_id_to_authority_ids,
}
}
}
impl From<AddrCache> for SerializeAddrCache {
fn from(value: AddrCache) -> Self {
Self { authority_id_to_addresses: value.authority_id_to_addresses }
}
}
fn write_to_file(path: impl AsRef<Path>, contents: &str) -> io::Result<()> {
let path = path.as_ref();
let mut file = File::create(path)?;
file.write_all(contents.as_bytes())?;
file.flush()?;
Ok(())
}
impl TryFrom<&Path> for AddrCache {
type Error = Error;
fn try_from(path: &Path) -> Result<Self, Self::Error> {
// Try to load from the cache file if it exists and is valid.
load_from_file::<AddrCache>(&path).map_err(|e| {
Error::EncodingDecodingAddrCache(format!(
"Failed to load AddrCache from file: {}, error: {:?}",
path.display(),
e
))
})
}
}
impl AddrCache {
pub fn new() -> Self {
AddrCache::default()
}
fn serialize(&self) -> Option<String> {
serde_json::to_string_pretty(self).inspect_err(|e| {
warn!(target: super::LOG_TARGET, "Failed to serialize AddrCache to JSON: {} => skip persisting it.", e);
}).ok()
}
fn persist(path: impl AsRef<Path>, serialized_cache: String) {
match write_to_file(path.as_ref(), &serialized_cache) {
Err(err) => {
warn!(target: super::LOG_TARGET, "Failed to persist AddrCache on disk at path: {}, error: {}", path.as_ref().display(), err);
},
Ok(_) => {
info!(target: super::LOG_TARGET, "Successfully persisted AddrCache on disk");
},
}
}
pub fn serialize_and_persist(&self, path: impl AsRef<Path>) {
let Some(serialized) = self.serialize() else { return };
Self::persist(path, serialized);
}
/// Inserts the given [`AuthorityId`] and [`Vec<Multiaddr>`] pair for future lookups by
/// [`AuthorityId`] or [`PeerId`].
pub fn insert(&mut self, authority_id: AuthorityId, addresses: Vec<Multiaddr>) {
let addresses = addresses.into_iter().collect::<HashSet<_>>();
let peer_ids = addresses_to_peer_ids(&addresses);
if peer_ids.is_empty() {
log::debug!(
target: super::LOG_TARGET,
"Authority({:?}) provides no addresses or addresses without peer ids. Adresses: {:?}",
authority_id,
addresses,
);
return;
} else if peer_ids.len() > 1 {
log::warn!(
target: super::LOG_TARGET,
"Authority({:?}) can be reached through multiple peer ids: {:?}",
authority_id,
peer_ids
);
}
log::debug!(
target: super::LOG_TARGET,
"Found addresses for authority {authority_id:?}: {addresses:?}",
);
let old_addresses = self.authority_id_to_addresses.insert(authority_id.clone(), addresses);
let old_peer_ids = addresses_to_peer_ids(&old_addresses.unwrap_or_default());
// Add the new peer ids
peer_ids.difference(&old_peer_ids).for_each(|new_peer_id| {
self.peer_id_to_authority_ids
.entry(*new_peer_id)
.or_default()
.insert(authority_id.clone());
});
// Remove the old peer ids
self.remove_authority_id_from_peer_ids(&authority_id, old_peer_ids.difference(&peer_ids));
}
/// Remove the given `authority_id` from the `peer_id` to `authority_ids` mapping.
///
/// If a `peer_id` doesn't have any `authority_id` assigned anymore, it is removed.
fn remove_authority_id_from_peer_ids<'a>(
&mut self,
authority_id: &AuthorityId,
peer_ids: impl Iterator<Item = &'a PeerId>,
) {
peer_ids.for_each(|peer_id| {
if let Entry::Occupied(mut e) = self.peer_id_to_authority_ids.entry(*peer_id) {
e.get_mut().remove(authority_id);
// If there are no more entries, remove the peer id.
if e.get().is_empty() {
e.remove();
}
}
})
}
/// Returns the number of authority IDs in the cache.
pub fn num_authority_ids(&self) -> usize {
self.authority_id_to_addresses.len()
}
/// Returns the addresses for the given [`AuthorityId`].
pub fn get_addresses_by_authority_id(
&self,
authority_id: &AuthorityId,
) -> Option<&HashSet<Multiaddr>> {
self.authority_id_to_addresses.get(authority_id)
}
/// Returns the [`AuthorityId`]s for the given [`PeerId`].
///
/// As the authority id can change between sessions, one [`PeerId`] can be mapped to
/// multiple authority ids.
pub fn get_authority_ids_by_peer_id(&self, peer_id: &PeerId) -> Option<&HashSet<AuthorityId>> {
self.peer_id_to_authority_ids.get(peer_id)
}
/// Removes all [`PeerId`]s and [`Multiaddr`]s from the cache that are not related to the given
/// [`AuthorityId`]s.
pub fn retain_ids(&mut self, authority_ids: &[AuthorityId]) {
// The below logic could be replaced by `BtreeMap::drain_filter` once it stabilized.
let authority_ids_to_remove = self
.authority_id_to_addresses
.iter()
.filter(|(id, _addresses)| !authority_ids.contains(id))
.map(|entry| entry.0)
.cloned()
.collect::<Vec<AuthorityId>>();
for authority_id_to_remove in authority_ids_to_remove {
// Remove other entries from `self.authority_id_to_addresses`.
let addresses = if let Some(addresses) =
self.authority_id_to_addresses.remove(&authority_id_to_remove)
{
addresses
} else {
continue;
};
self.remove_authority_id_from_peer_ids(
&authority_id_to_remove,
addresses_to_peer_ids(&addresses).iter(),
);
}
}
}
fn peer_id_from_multiaddr(addr: &Multiaddr) -> Option<PeerId> {
addr.iter().last().and_then(|protocol| {
if let Protocol::P2p(multihash) = protocol {
PeerId::from_multihash(multihash).ok()
} else {
None
}
})
}
fn addresses_to_peer_ids(addresses: &HashSet<Multiaddr>) -> HashSet<PeerId> {
addresses.iter().filter_map(peer_id_from_multiaddr).collect::<HashSet<_>>()
}
fn load_from_file<T: DeserializeOwned>(path: impl AsRef<Path>) -> io::Result<T> {
let file = File::open(path)?;
let reader = BufReader::new(file);
serde_json::from_reader(reader).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
#[cfg(test)]
mod tests {
use std::{
thread::sleep,
time::{Duration, Instant},
};
use super::*;
use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult};
use pezsc_network_types::multihash::{Code, Multihash};
use pezsp_authority_discovery::{AuthorityId, AuthorityPair};
use pezsp_core::crypto::Pair;
#[derive(Clone, Debug)]
struct TestAuthorityId(AuthorityId);
impl Arbitrary for TestAuthorityId {
fn arbitrary(g: &mut Gen) -> Self {
let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
TestAuthorityId(AuthorityPair::from_seed_slice(&seed).unwrap().public())
}
}
#[derive(Clone, Debug)]
struct TestMultiaddr(Multiaddr);
impl Arbitrary for TestMultiaddr {
fn arbitrary(g: &mut Gen) -> Self {
let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
let peer_id =
PeerId::from_multihash(Multihash::wrap(Code::Sha2_256.into(), &seed).unwrap())
.unwrap();
let multiaddr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
.parse::<Multiaddr>()
.unwrap()
.with(Protocol::P2p(peer_id.into()));
TestMultiaddr(multiaddr)
}
}
#[derive(Clone, Debug)]
struct TestMultiaddrsSamePeerCombo(Multiaddr, Multiaddr);
impl Arbitrary for TestMultiaddrsSamePeerCombo {
fn arbitrary(g: &mut Gen) -> Self {
let seed = (0..32).map(|_| u8::arbitrary(g)).collect::<Vec<_>>();
let peer_id =
PeerId::from_multihash(Multihash::wrap(Code::Sha2_256.into(), &seed).unwrap())
.unwrap();
let multiaddr1 = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333"
.parse::<Multiaddr>()
.unwrap()
.with(Protocol::P2p(peer_id.into()));
let multiaddr2 = "/ip6/2002:db8:0:0:0:0:0:2/tcp/30133"
.parse::<Multiaddr>()
.unwrap()
.with(Protocol::P2p(peer_id.into()));
TestMultiaddrsSamePeerCombo(multiaddr1, multiaddr2)
}
}
#[test]
fn retains_only_entries_of_provided_authority_ids() {
fn property(
first: (TestAuthorityId, TestMultiaddr),
second: (TestAuthorityId, TestMultiaddr),
third: (TestAuthorityId, TestMultiaddr),
) -> TestResult {
let first: (AuthorityId, Multiaddr) = ((first.0).0, (first.1).0);
let second: (AuthorityId, Multiaddr) = ((second.0).0, (second.1).0);
let third: (AuthorityId, Multiaddr) = ((third.0).0, (third.1).0);
let mut cache = AddrCache::new();
cache.insert(first.0.clone(), vec![first.1.clone()]);
cache.insert(second.0.clone(), vec![second.1.clone()]);
cache.insert(third.0.clone(), vec![third.1.clone()]);
assert_eq!(
Some(&HashSet::from([third.1.clone()])),
cache.get_addresses_by_authority_id(&third.0),
"Expect `get_addresses_by_authority_id` to return addresses of third authority.",
);
assert_eq!(
Some(&HashSet::from([third.0.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
"Expect `get_authority_id_by_peer_id` to return `AuthorityId` of third authority.",
);
cache.retain_ids(&vec![first.0.clone(), second.0]);
assert_eq!(
None,
cache.get_addresses_by_authority_id(&third.0),
"Expect `get_addresses_by_authority_id` to not return `None` for third authority.",
);
assert_eq!(
None,
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&third.1).unwrap()),
"Expect `get_authority_id_by_peer_id` to return `None` for third authority.",
);
TestResult::passed()
}
QuickCheck::new()
.max_tests(10)
.quickcheck(property as fn(_, _, _) -> TestResult)
}
#[test]
fn test_from_to_serializable() {
let serializable = SerializeAddrCache::from(AddrCache::sample());
let roundtripped = AddrCache::from(serializable);
assert_eq!(roundtripped, AddrCache::sample())
}
#[test]
fn keeps_consistency_between_authority_id_and_peer_id() {
fn property(
authority1: TestAuthorityId,
authority2: TestAuthorityId,
multiaddr1: TestMultiaddr,
multiaddr2: TestMultiaddr,
multiaddr3: TestMultiaddrsSamePeerCombo,
) -> TestResult {
let authority1 = authority1.0;
let authority2 = authority2.0;
let multiaddr1 = multiaddr1.0;
let multiaddr2 = multiaddr2.0;
let TestMultiaddrsSamePeerCombo(multiaddr3, multiaddr4) = multiaddr3;
let mut cache = AddrCache::new();
cache.insert(authority1.clone(), vec![multiaddr1.clone()]);
cache.insert(
authority1.clone(),
vec![multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()],
);
assert_eq!(
None,
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr1).unwrap())
);
assert_eq!(
Some(&HashSet::from([authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
);
assert_eq!(
Some(&HashSet::from([authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
);
assert_eq!(
Some(&HashSet::from([authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr4).unwrap())
);
cache.insert(authority2.clone(), vec![multiaddr2.clone()]);
assert_eq!(
Some(&HashSet::from([authority2.clone(), authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
);
assert_eq!(
Some(&HashSet::from([authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
);
assert_eq!(cache.get_addresses_by_authority_id(&authority1).unwrap().len(), 3);
cache.insert(authority2.clone(), vec![multiaddr2.clone(), multiaddr3.clone()]);
assert_eq!(
Some(&HashSet::from([authority2.clone(), authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr2).unwrap())
);
assert_eq!(
Some(&HashSet::from([authority2.clone(), authority1.clone()])),
cache.get_authority_ids_by_peer_id(&peer_id_from_multiaddr(&multiaddr3).unwrap())
);
assert_eq!(
&HashSet::from([multiaddr2.clone(), multiaddr3.clone(), multiaddr4.clone()]),
cache.get_addresses_by_authority_id(&authority1).unwrap(),
);
TestResult::passed()
}
QuickCheck::new()
.max_tests(10)
.quickcheck(property as fn(_, _, _, _, _) -> TestResult)
}
/// As the runtime gives us the current + next authority ids, it can happen that some
/// authority changed its session keys. Changing the sessions keys leads to having two
/// authority ids that map to the same `PeerId` & addresses.
#[test]
fn adding_two_authority_ids_for_the_same_peer_id() {
let mut addr_cache = AddrCache::new();
let peer_id = PeerId::random();
let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into()));
let authority_id0 = AuthorityPair::generate().0.public();
let authority_id1 = AuthorityPair::generate().0.public();
addr_cache.insert(authority_id0.clone(), vec![addr.clone()]);
addr_cache.insert(authority_id1.clone(), vec![addr.clone()]);
assert_eq!(2, addr_cache.num_authority_ids());
assert_eq!(
&HashSet::from([addr.clone()]),
addr_cache.get_addresses_by_authority_id(&authority_id0).unwrap()
);
assert_eq!(
&HashSet::from([addr]),
addr_cache.get_addresses_by_authority_id(&authority_id1).unwrap()
);
}
impl AddrCache {
pub fn sample() -> Self {
let mut addr_cache = AddrCache::new();
let peer_id = PeerId::from_multihash(
Multihash::wrap(Code::Sha2_256.into(), &[0xab; 32]).unwrap(),
)
.unwrap();
let addr = Multiaddr::empty().with(Protocol::P2p(peer_id.into()));
let authority_id0 = AuthorityPair::from_seed(&[0xaa; 32]).public();
let authority_id1 = AuthorityPair::from_seed(&[0xbb; 32]).public();
addr_cache.insert(authority_id0.clone(), vec![addr.clone()]);
addr_cache.insert(authority_id1.clone(), vec![addr.clone()]);
addr_cache
}
}
#[test]
fn serde_json() {
let sample = || AddrCache::sample();
let serializable = AddrCache::from(sample());
let json = serde_json::to_string(&serializable).expect("Serialization should not fail");
let deserialized = serde_json::from_str::<AddrCache>(&json).unwrap();
let from_serializable = AddrCache::try_from(deserialized).unwrap();
assert_eq!(sample(), from_serializable);
}
#[test]
fn deserialize_from_json() {
let json = r#"
{
"authority_id_to_addresses": {
"5FjfMGrqw9ck5XZaPVTKm2RE5cbwoVUfXvSGZY7KCUEFtdr7": [
"/p2p/QmZtnFaddFtzGNT8BxdHVbQrhSFdq1pWxud5z4fA4kxfDt"
],
"5DiQDBQvjFkmUF3C8a7ape5rpRPoajmMj44Q9CTGPfVBaa6U": [
"/p2p/QmZtnFaddFtzGNT8BxdHVbQrhSFdq1pWxud5z4fA4kxfDt"
]
}
}
"#;
let deserialized = serde_json::from_str::<AddrCache>(json).unwrap();
assert_eq!(deserialized, AddrCache::sample())
}
fn serialize_and_write_to_file<T: Serialize>(
path: impl AsRef<Path>,
contents: &T,
) -> io::Result<()> {
let serialized = serde_json::to_string_pretty(contents).unwrap();
write_to_file(path, &serialized)
}
#[test]
fn test_load_cache_from_disc() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("cache.json");
let sample = AddrCache::sample();
assert_eq!(sample.num_authority_ids(), 2);
serialize_and_write_to_file(&path, &sample).unwrap();
sleep(Duration::from_millis(10)); // Ensure file is written before loading
let cache = AddrCache::try_from(path.as_path()).unwrap();
assert_eq!(cache.num_authority_ids(), 2);
}
fn create_cache(authority_id_count: u64, multiaddr_per_authority_count: u64) -> AddrCache {
let mut addr_cache = AddrCache::new();
for i in 0..authority_id_count {
let seed = &mut [0xab as u8; 32];
let i_bytes = i.to_le_bytes();
seed[0..8].copy_from_slice(&i_bytes);
let authority_id = AuthorityPair::from_seed(seed).public();
let multi_addresses = (0..multiaddr_per_authority_count)
.map(|j| {
let mut digest = [0xab; 32];
let j_bytes = j.to_le_bytes();
digest[0..8].copy_from_slice(&j_bytes);
let peer_id = PeerId::from_multihash(
Multihash::wrap(Code::Sha2_256.into(), &digest).unwrap(),
)
.unwrap();
Multiaddr::empty().with(Protocol::P2p(peer_id.into()))
})
.collect::<Vec<_>>();
assert_eq!(multi_addresses.len(), multiaddr_per_authority_count as usize);
addr_cache.insert(authority_id.clone(), multi_addresses);
}
assert_eq!(addr_cache.authority_id_to_addresses.len(), authority_id_count as usize);
addr_cache
}
/// This test is ignored by default as it takes a long time to run.
#[test]
#[ignore]
fn addr_cache_measure_serde_performance() {
let addr_cache = create_cache(1000, 5);
/// A replica of `AddrCache` that is serializable and deserializable
/// without any optimizations.
#[derive(Default, Clone, PartialEq, Debug, Serialize, Deserialize)]
pub(crate) struct NaiveSerdeAddrCache {
authority_id_to_addresses: HashMap<AuthorityId, HashSet<Multiaddr>>,
peer_id_to_authority_ids: HashMap<PeerId, HashSet<AuthorityId>>,
}
impl From<AddrCache> for NaiveSerdeAddrCache {
fn from(value: AddrCache) -> Self {
Self {
authority_id_to_addresses: value.authority_id_to_addresses,
peer_id_to_authority_ids: value.peer_id_to_authority_ids,
}
}
}
let naive = NaiveSerdeAddrCache::from(addr_cache.clone());
let storage_optimized = addr_cache.clone();
fn measure_clone<T: Clone>(data: &T) -> Duration {
let start = Instant::now();
let _ = data.clone();
start.elapsed()
}
fn measure_serialize<T: Serialize>(data: &T) -> (Duration, String) {
let start = Instant::now();
let json = serde_json::to_string_pretty(data).unwrap();
(start.elapsed(), json)
}
fn measure_deserialize<T: DeserializeOwned>(json: String) -> (Duration, T) {
let start = Instant::now();
let value = serde_json::from_str(&json).unwrap();
(start.elapsed(), value)
}
let serialize_naive = measure_serialize(&naive);
let serialize_storage_optimized = measure_serialize(&storage_optimized);
println!("CLONE: Naive took: {} ms", measure_clone(&naive).as_millis());
println!(
"CLONE: Storage optimized took: {} ms",
measure_clone(&storage_optimized).as_millis()
);
println!("SERIALIZE: Naive took: {} ms", serialize_naive.0.as_millis());
println!(
"SERIALIZE: Storage optimized took: {} ms",
serialize_storage_optimized.0.as_millis()
);
let deserialize_naive = measure_deserialize::<NaiveSerdeAddrCache>(serialize_naive.1);
let deserialize_storage_optimized =
measure_deserialize::<AddrCache>(serialize_storage_optimized.1);
println!("DESERIALIZE: Naive took: {} ms", deserialize_naive.0.as_millis());
println!(
"DESERIALIZE: Storage optimized took: {} ms",
deserialize_storage_optimized.0.as_millis()
);
assert_eq!(deserialize_naive.1, naive);
assert_eq!(deserialize_storage_optimized.1, storage_optimized);
}
}
@@ -0,0 +1,14 @@
syntax = "proto3";
package authority_discovery_v1;
// First we need to serialize the addresses in order to be able to sign them.
message AuthorityAddresses {
repeated bytes addresses = 1;
}
// Then we need to serialize addresses and signature to send them over the wire.
message SignedAuthorityAddresses {
bytes addresses = 1;
bytes signature = 2;
}
@@ -0,0 +1,23 @@
syntax = "proto3";
package authority_discovery_v2;
// First we need to serialize the addresses in order to be able to sign them.
message AuthorityRecord {
// Possibly multiple `MultiAddress`es through which the node can be
repeated bytes addresses = 1;
}
message PeerSignature {
bytes signature = 1;
bytes public_key = 2;
}
// Then we need to serialize the authority record and signature to send them over the wire.
message SignedAuthorityRecord {
bytes record = 1;
bytes auth_signature = 2;
// Even if there are multiple `record.addresses`, all of them have the same peer id.
// Old versions are missing this field. It is optional in order to provide compatibility both ways.
PeerSignature peer_signature = 3;
}
@@ -0,0 +1,31 @@
syntax = "proto3";
package authority_discovery_v3;
// First we need to serialize the addresses in order to be able to sign them.
message AuthorityRecord {
// Possibly multiple `MultiAddress`es through which the node can be reached.
repeated bytes addresses = 1;
// Information about the creation time of the record
TimestampInfo creation_time = 2;
}
message PeerSignature {
bytes signature = 1;
bytes public_key = 2;
}
// Information regarding the creation data of the record
message TimestampInfo {
// Time since UNIX_EPOCH in nanoseconds, scale encoded
bytes timestamp = 1;
}
// Then we need to serialize the authority record and signature to send them over the wire.
message SignedAuthorityRecord {
bytes record = 1;
bytes auth_signature = 2;
// Even if there are multiple `record.addresses`, all of them have the same peer id.
// Old versions are missing this field. It is optional in order to provide compatibility both ways.
PeerSignature peer_signature = 3;
}
@@ -0,0 +1,171 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
mod schema_v1 {
include!(concat!(env!("OUT_DIR"), "/authority_discovery_v1.rs"));
}
mod schema_v2 {
include!(concat!(env!("OUT_DIR"), "/authority_discovery_v2.rs"));
}
use super::*;
use codec::Encode;
use prost::Message;
use pezsc_network::{Multiaddr, PeerId};
use pezsc_network_types::ed25519::Keypair;
#[test]
fn v2_decodes_v1() {
let peer_id = PeerId::random();
let multiaddress: Multiaddr =
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
let vec_addresses = vec![multiaddress.to_vec()];
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
let addresses_v1 = schema_v1::AuthorityAddresses { addresses: vec_addresses.clone() };
let mut vec_addresses_v1 = vec![];
addresses_v1.encode(&mut vec_addresses_v1).unwrap();
let signed_addresses_v1 = schema_v1::SignedAuthorityAddresses {
addresses: vec_addresses_v1.clone(),
signature: vec_auth_signature.clone(),
};
let mut vec_signed_addresses_v1 = vec![];
signed_addresses_v1.encode(&mut vec_signed_addresses_v1).unwrap();
let signed_record_v2_decoded =
SignedAuthorityRecord::decode(vec_signed_addresses_v1.as_slice()).unwrap();
assert_eq!(&signed_record_v2_decoded.record, &vec_addresses_v1);
assert_eq!(&signed_record_v2_decoded.auth_signature, &vec_auth_signature);
assert_eq!(&signed_record_v2_decoded.peer_signature, &None);
let record_v2_decoded = AuthorityRecord::decode(vec_addresses_v1.as_slice()).unwrap();
assert_eq!(&record_v2_decoded.addresses, &vec_addresses);
}
#[test]
fn v1_decodes_v2() {
let peer_secret = Keypair::generate();
let peer_public = peer_secret.public();
let peer_id = peer_public.to_peer_id();
let multiaddress: Multiaddr =
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
let vec_addresses = vec![multiaddress.to_vec()];
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec();
let record_v2 = schema_v2::AuthorityRecord { addresses: vec_addresses.clone() };
let mut vec_record_v2 = vec![];
record_v2.encode(&mut vec_record_v2).unwrap();
let vec_peer_public = peer_public.to_bytes().to_vec();
let peer_signature_v2 =
PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature };
let signed_record_v2 = SignedAuthorityRecord {
record: vec_record_v2.clone(),
auth_signature: vec_auth_signature.clone(),
peer_signature: Some(peer_signature_v2.clone()),
};
let mut vec_signed_record_v2 = vec![];
signed_record_v2.encode(&mut vec_signed_record_v2).unwrap();
let signed_addresses_v1_decoded =
schema_v1::SignedAuthorityAddresses::decode(vec_signed_record_v2.as_slice()).unwrap();
assert_eq!(&signed_addresses_v1_decoded.addresses, &vec_record_v2);
assert_eq!(&signed_addresses_v1_decoded.signature, &vec_auth_signature);
let addresses_v2_decoded =
schema_v2::AuthorityRecord::decode(vec_record_v2.as_slice()).unwrap();
assert_eq!(&addresses_v2_decoded.addresses, &vec_addresses);
}
#[test]
fn v1_decodes_v3() {
let peer_secret = Keypair::generate();
let peer_public = peer_secret.public();
let peer_id = peer_public.to_peer_id();
let multiaddress: Multiaddr =
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
let vec_addresses = vec![multiaddress.to_vec()];
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec();
let record_v3 = AuthorityRecord {
addresses: vec_addresses.clone(),
creation_time: Some(TimestampInfo { timestamp: Encode::encode(&55) }),
};
let mut vec_record_v3 = vec![];
record_v3.encode(&mut vec_record_v3).unwrap();
let vec_peer_public = peer_public.to_bytes().to_vec();
let peer_signature_v3 =
PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature };
let signed_record_v3 = SignedAuthorityRecord {
record: vec_record_v3.clone(),
auth_signature: vec_auth_signature.clone(),
peer_signature: Some(peer_signature_v3.clone()),
};
let mut vec_signed_record_v3 = vec![];
signed_record_v3.encode(&mut vec_signed_record_v3).unwrap();
let signed_addresses_v1_decoded =
schema_v1::SignedAuthorityAddresses::decode(vec_signed_record_v3.as_slice()).unwrap();
assert_eq!(&signed_addresses_v1_decoded.addresses, &vec_record_v3);
assert_eq!(&signed_addresses_v1_decoded.signature, &vec_auth_signature);
let addresses_v2_decoded =
schema_v2::AuthorityRecord::decode(vec_record_v3.as_slice()).unwrap();
assert_eq!(&addresses_v2_decoded.addresses, &vec_addresses);
}
#[test]
fn v3_decodes_v2() {
let peer_secret = Keypair::generate();
let peer_public = peer_secret.public();
let peer_id = peer_public.to_peer_id();
let multiaddress: Multiaddr =
format!("/ip4/127.0.0.1/tcp/3003/p2p/{}", peer_id).parse().unwrap();
let vec_addresses = vec![multiaddress.to_vec()];
let vec_auth_signature = b"Totally valid signature, I promise!".to_vec();
let vec_peer_signature = b"Surprisingly hard to crack crypto".to_vec();
let record_v2 = schema_v2::AuthorityRecord { addresses: vec_addresses.clone() };
let mut vec_record_v2 = vec![];
record_v2.encode(&mut vec_record_v2).unwrap();
let vec_peer_public = peer_public.to_bytes().to_vec();
let peer_signature_v2 =
schema_v2::PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature };
let signed_record_v2 = schema_v2::SignedAuthorityRecord {
record: vec_record_v2.clone(),
auth_signature: vec_auth_signature.clone(),
peer_signature: Some(peer_signature_v2.clone()),
};
let mut vec_signed_record_v2 = vec![];
signed_record_v2.encode(&mut vec_signed_record_v2).unwrap();
let signed_addresses_v3_decoded =
SignedAuthorityRecord::decode(vec_signed_record_v2.as_slice()).unwrap();
assert_eq!(&signed_addresses_v3_decoded.record, &vec_record_v2);
assert_eq!(&signed_addresses_v3_decoded.auth_signature, &vec_auth_signature);
let addresses_v3_decoded = AuthorityRecord::decode(vec_record_v2.as_slice()).unwrap();
assert_eq!(&addresses_v3_decoded.addresses, &vec_addresses);
assert_eq!(&addresses_v3_decoded.creation_time, &None);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
[package]
name = "pezsc-basic-authorship"
version = "0.34.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Basic implementation of block-authoring logic."
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { workspace = true, default-features = true }
futures = { workspace = true }
log = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
pezsc-block-builder = { workspace = true, default-features = true }
pezsc-proposer-metrics = { workspace = true, default-features = true }
pezsc-telemetry = { workspace = true, default-features = true }
pezsc-transaction-pool-api = { workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-consensus = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-inherents = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-trie = { workspace = true, default-features = true }
[dev-dependencies]
parking_lot = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-transaction-pool = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
[features]
runtime-benchmarks = [
"pezsc-block-builder/runtime-benchmarks",
"pezsc-client-api/runtime-benchmarks",
"pezsc-transaction-pool-api/runtime-benchmarks",
"pezsc-transaction-pool/runtime-benchmarks",
"pezsp-api/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus/runtime-benchmarks",
"pezsp-inherents/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-trie/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,31 @@
Basic implementation of block-authoring logic.
# Example
```rust
// The first step is to create a `ProposerFactory`.
let mut proposer_factory = ProposerFactory::new(client.clone(), txpool.clone(), None);
// From this factory, we create a `Proposer`.
let proposer = proposer_factory.init(
&client.header(client.chain_info().genesis_hash).unwrap().unwrap(),
);
// The proposer is created asynchronously.
let proposer = futures::executor::block_on(proposer).unwrap();
// This `Proposer` allows us to create a block proposition.
// The proposer will grab transactions from the transaction pool, and put them into the block.
let future = proposer.propose(
Default::default(),
Default::default(),
Duration::from_secs(2),
);
// We wait until the proposition is performed.
let block = futures::executor::block_on(future).unwrap();
println!("Generated block: {:?}", block.block);
```
License: GPL-3.0-or-later WITH Classpath-exception-2.0
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,78 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Basic implementation of block-authoring logic.
//!
//! # Example
//!
//! ```
//! # use pezsc_basic_authorship::ProposerFactory;
//! # use pezsp_consensus::{Environment, Proposer};
//! # use pezsp_runtime::generic::BlockId;
//! # use std::{sync::Arc, time::Duration};
//! # use bizinikiwi_test_runtime_client::{
//! # runtime::Transfer, Sr25519Keyring,
//! # DefaultTestClientBuilderExt, TestClientBuilderExt,
//! # };
//! # use pezsc_transaction_pool::{BasicPool, FullChainApi};
//! # let client = Arc::new(bizinikiwi_test_runtime_client::new());
//! # let spawner = pezsp_core::testing::TaskExecutor::new();
//! # let txpool = Arc::from(BasicPool::new_full(
//! # Default::default(),
//! # true.into(),
//! # None,
//! # spawner.clone(),
//! # client.clone(),
//! # ));
//! // The first step is to create a `ProposerFactory`.
//! let mut proposer_factory = ProposerFactory::new(
//! spawner,
//! client.clone(),
//! txpool.clone(),
//! None,
//! None,
//! );
//!
//! // From this factory, we create a `Proposer`.
//! let proposer = proposer_factory.init(
//! &client.header(client.chain_info().genesis_hash).unwrap().unwrap(),
//! );
//!
//! // The proposer is created asynchronously.
//! let proposer = futures::executor::block_on(proposer).unwrap();
//!
//! // This `Proposer` allows us to create a block proposition.
//! // The proposer will grab transactions from the transaction pool, and put them into the block.
//! let future = Proposer::propose(
//! proposer,
//! Default::default(),
//! Default::default(),
//! Duration::from_secs(2),
//! None,
//! );
//!
//! // We wait until the proposition is performed.
//! let block = futures::executor::block_on(future).unwrap();
//! println!("Generated block: {:?}", block.block);
//! ```
mod basic_authorship;
pub use crate::basic_authorship::{
ProposeArgs, Proposer, ProposerFactory, DEFAULT_BLOCK_SIZE_LIMIT,
};
@@ -0,0 +1,42 @@
[package]
name = "pezsc-block-builder"
version = "0.33.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Bizinikiwi block builder"
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { features = ["derive"], workspace = true, default-features = true }
pezsp-api = { workspace = true, default-features = true }
pezsp-block-builder = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-inherents = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-trie = { workspace = true, default-features = true }
[dev-dependencies]
pezsp-state-machine = { workspace = true, default-features = true }
bizinikiwi-test-runtime-client = { workspace = true }
[features]
runtime-benchmarks = [
"pezsp-api/runtime-benchmarks",
"pezsp-block-builder/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-inherents/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-state-machine/runtime-benchmarks",
"pezsp-trie/runtime-benchmarks",
"bizinikiwi-test-runtime-client/runtime-benchmarks",
]
@@ -0,0 +1,9 @@
Bizinikiwi block builder
This crate provides the [`BlockBuilder`] utility and the corresponding runtime api
[`BlockBuilder`](https://docs.rs/pezsc-block-builder/latest/sc_block_builder/struct.BlockBuilder.html).Error
The block builder utility is used in the node as an abstraction over the runtime api to
initialize a block, to push extrinsics and to finalize a block.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
+465
View File
@@ -0,0 +1,465 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Bizinikiwi block builder
//!
//! This crate provides the [`BlockBuilder`] utility and the corresponding runtime api
//! [`BlockBuilder`](pezsp_block_builder::BlockBuilder).
//!
//! The block builder utility is used in the node as an abstraction over the runtime api to
//! initialize a block, to push extrinsics and to finalize a block.
#![warn(missing_docs)]
use codec::Encode;
use pezsp_api::{
ApiExt, ApiRef, CallApiAt, Core, ProofRecorder, ProvideRuntimeApi, StorageChanges,
StorageProof, TransactionOutcome,
};
use pezsp_blockchain::{ApplyExtrinsicFailed, Error, HeaderBackend};
use pezsp_core::traits::CallContext;
use pezsp_runtime::{
legacy,
traits::{Block as BlockT, Hash, HashingFor, Header as HeaderT, NumberFor, One},
Digest, ExtrinsicInclusionMode,
};
use std::marker::PhantomData;
pub use pezsp_block_builder::BlockBuilder as BlockBuilderApi;
use pezsp_trie::proof_size_extension::ProofSizeExt;
/// A builder for creating an instance of [`BlockBuilder`].
pub struct BlockBuilderBuilder<'a, B, C> {
call_api_at: &'a C,
_phantom: PhantomData<B>,
}
impl<'a, B, C> BlockBuilderBuilder<'a, B, C>
where
B: BlockT,
{
/// Create a new instance of the builder.
///
/// `call_api_at`: Something that implements [`CallApiAt`].
pub fn new(call_api_at: &'a C) -> Self {
Self { call_api_at, _phantom: PhantomData }
}
/// Specify the parent block to build on top of.
pub fn on_parent_block(self, parent_block: B::Hash) -> BlockBuilderBuilderStage1<'a, B, C> {
BlockBuilderBuilderStage1 { call_api_at: self.call_api_at, parent_block }
}
}
/// The second stage of the [`BlockBuilderBuilder`].
///
/// This type can not be instantiated directly. To get an instance of it
/// [`BlockBuilderBuilder::new`] needs to be used.
pub struct BlockBuilderBuilderStage1<'a, B: BlockT, C> {
call_api_at: &'a C,
parent_block: B::Hash,
}
impl<'a, B, C> BlockBuilderBuilderStage1<'a, B, C>
where
B: BlockT,
{
/// Fetch the parent block number from the given `header_backend`.
///
/// The parent block number is used to initialize the block number of the new block.
///
/// Returns an error if the parent block specified in
/// [`on_parent_block`](BlockBuilderBuilder::on_parent_block) does not exist.
pub fn fetch_parent_block_number<H: HeaderBackend<B>>(
self,
header_backend: &H,
) -> Result<BlockBuilderBuilderStage2<'a, B, C>, Error> {
let parent_number = header_backend.number(self.parent_block)?.ok_or_else(|| {
Error::Backend(format!(
"Could not fetch block number for block: {:?}",
self.parent_block
))
})?;
Ok(BlockBuilderBuilderStage2 {
call_api_at: self.call_api_at,
proof_recorder: None,
inherent_digests: Default::default(),
parent_block: self.parent_block,
parent_number,
})
}
/// Provide the block number for the parent block directly.
///
/// The parent block is specified in [`on_parent_block`](BlockBuilderBuilder::on_parent_block).
/// The parent block number is used to initialize the block number of the new block.
pub fn with_parent_block_number(
self,
parent_number: NumberFor<B>,
) -> BlockBuilderBuilderStage2<'a, B, C> {
BlockBuilderBuilderStage2 {
call_api_at: self.call_api_at,
proof_recorder: None,
inherent_digests: Default::default(),
parent_block: self.parent_block,
parent_number,
}
}
}
/// The second stage of the [`BlockBuilderBuilder`].
///
/// This type can not be instantiated directly. To get an instance of it
/// [`BlockBuilderBuilder::new`] needs to be used.
pub struct BlockBuilderBuilderStage2<'a, B: BlockT, C> {
call_api_at: &'a C,
proof_recorder: Option<ProofRecorder<B>>,
inherent_digests: Digest,
parent_block: B::Hash,
parent_number: NumberFor<B>,
}
impl<'a, B: BlockT, C> BlockBuilderBuilderStage2<'a, B, C> {
/// Enable proof recording for the block builder.
pub fn enable_proof_recording(mut self) -> Self {
self.proof_recorder = Some(Default::default());
self
}
/// Enable/disable proof recording for the block builder.
pub fn with_proof_recording(mut self, enable: bool) -> Self {
self.proof_recorder = enable.then(|| Default::default());
self
}
/// Enable/disable proof recording for the block builder using the given proof recorder.
pub fn with_proof_recorder(mut self, proof_recorder: Option<ProofRecorder<B>>) -> Self {
self.proof_recorder = proof_recorder;
self
}
/// Build the block with the given inherent digests.
pub fn with_inherent_digests(mut self, inherent_digests: Digest) -> Self {
self.inherent_digests = inherent_digests;
self
}
/// Create the instance of the [`BlockBuilder`].
pub fn build(self) -> Result<BlockBuilder<'a, B, C>, Error>
where
C: CallApiAt<B> + ProvideRuntimeApi<B>,
C::Api: BlockBuilderApi<B>,
{
BlockBuilder::new(
self.call_api_at,
self.parent_block,
self.parent_number,
self.proof_recorder,
self.inherent_digests,
)
}
}
/// A block that was build by [`BlockBuilder`] plus some additional data.
///
/// This additional data includes the `storage_changes`, these changes can be applied to the
/// backend to get the state of the block. Furthermore an optional `proof` is included which
/// can be used to proof that the build block contains the expected data. The `proof` will
/// only be set when proof recording was activated.
pub struct BuiltBlock<Block: BlockT> {
/// The actual block that was build.
pub block: Block,
/// The changes that need to be applied to the backend to get the state of the build block.
pub storage_changes: StorageChanges<Block>,
/// An optional proof that was recorded while building the block.
pub proof: Option<StorageProof>,
}
impl<Block: BlockT> BuiltBlock<Block> {
/// Convert into the inner values.
pub fn into_inner(self) -> (Block, StorageChanges<Block>, Option<StorageProof>) {
(self.block, self.storage_changes, self.proof)
}
}
/// Utility for building new (valid) blocks from a stream of extrinsics.
pub struct BlockBuilder<'a, Block: BlockT, C: ProvideRuntimeApi<Block> + 'a> {
extrinsics: Vec<Block::Extrinsic>,
api: ApiRef<'a, C::Api>,
call_api_at: &'a C,
/// Version of the [`BlockBuilderApi`] runtime API.
version: u32,
parent_hash: Block::Hash,
/// The estimated size of the block header.
estimated_header_size: usize,
extrinsic_inclusion_mode: ExtrinsicInclusionMode,
}
impl<'a, Block, C> BlockBuilder<'a, Block, C>
where
Block: BlockT,
C: CallApiAt<Block> + ProvideRuntimeApi<Block> + 'a,
C::Api: BlockBuilderApi<Block>,
{
/// Create a new instance of builder based on the given `parent_hash` and `parent_number`.
///
/// While proof recording is enabled, all accessed trie nodes are saved.
/// These recorded trie nodes can be used by a third party to prove the
/// output of this block builder without having access to the full storage.
fn new(
call_api_at: &'a C,
parent_hash: Block::Hash,
parent_number: NumberFor<Block>,
proof_recorder: Option<ProofRecorder<Block>>,
inherent_digests: Digest,
) -> Result<Self, Error> {
let header = <<Block as BlockT>::Header as HeaderT>::new(
parent_number + One::one(),
Default::default(),
Default::default(),
parent_hash,
inherent_digests,
);
let estimated_header_size = header.encoded_size();
let mut api = call_api_at.runtime_api();
if let Some(recorder) = proof_recorder {
api.record_proof_with_recorder(recorder.clone());
api.register_extension(ProofSizeExt::new(recorder));
}
api.set_call_context(CallContext::Onchain);
let core_version = api
.api_version::<dyn Core<Block>>(parent_hash)?
.ok_or_else(|| Error::VersionInvalid("Core".to_string()))?;
let extrinsic_inclusion_mode = if core_version >= 5 {
api.initialize_block(parent_hash, &header)?
} else {
#[allow(deprecated)]
api.initialize_block_before_version_5(parent_hash, &header)?;
ExtrinsicInclusionMode::AllExtrinsics
};
let bb_version = api
.api_version::<dyn BlockBuilderApi<Block>>(parent_hash)?
.ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?;
Ok(Self {
parent_hash,
extrinsics: Vec::new(),
api,
version: bb_version,
estimated_header_size,
call_api_at,
extrinsic_inclusion_mode,
})
}
/// The extrinsic inclusion mode of the runtime for this block.
pub fn extrinsic_inclusion_mode(&self) -> ExtrinsicInclusionMode {
self.extrinsic_inclusion_mode
}
/// Push onto the block's list of extrinsics.
///
/// This will ensure the extrinsic can be validly executed (by executing it).
pub fn push(&mut self, xt: <Block as BlockT>::Extrinsic) -> Result<(), Error> {
let parent_hash = self.parent_hash;
let extrinsics = &mut self.extrinsics;
let version = self.version;
self.api.execute_in_transaction(|api| {
let res = if version < 6 {
#[allow(deprecated)]
api.apply_extrinsic_before_version_6(parent_hash, xt.clone())
.map(legacy::byte_sized_error::convert_to_latest)
} else {
api.apply_extrinsic(parent_hash, xt.clone())
};
match res {
Ok(Ok(_)) => {
extrinsics.push(xt);
TransactionOutcome::Commit(Ok(()))
},
Ok(Err(tx_validity)) => TransactionOutcome::Rollback(Err(
ApplyExtrinsicFailed::Validity(tx_validity).into(),
)),
Err(e) => TransactionOutcome::Rollback(Err(Error::from(e))),
}
})
}
/// Consume the builder to build a valid `Block` containing all pushed extrinsics.
///
/// Returns the build `Block`, the changes to the storage and an optional `StorageProof`
/// supplied by `self.api`, combined as [`BuiltBlock`].
/// The storage proof will be `Some(_)` when proof recording was enabled.
pub fn build(mut self) -> Result<BuiltBlock<Block>, Error> {
let header = self.api.finalize_block(self.parent_hash)?;
debug_assert_eq!(
header.extrinsics_root().clone(),
HashingFor::<Block>::ordered_trie_root(
self.extrinsics.iter().map(Encode::encode).collect(),
self.api.version(self.parent_hash)?.extrinsics_root_state_version(),
),
);
let proof = self.api.extract_proof();
let state = self.call_api_at.state_at(self.parent_hash)?;
let storage_changes = self
.api
.into_storage_changes(&state, self.parent_hash)
.map_err(pezsp_blockchain::Error::StorageChanges)?;
Ok(BuiltBlock {
block: <Block as BlockT>::new(header, self.extrinsics),
storage_changes,
proof,
})
}
/// Create the inherents for the block.
///
/// Returns the inherents created by the runtime or an error if something failed.
pub fn create_inherents(
&mut self,
inherent_data: pezsp_inherents::InherentData,
) -> Result<Vec<Block::Extrinsic>, Error> {
let parent_hash = self.parent_hash;
self.api
.execute_in_transaction(move |api| {
// `create_inherents` should not change any state, to ensure this we always rollback
// the transaction.
TransactionOutcome::Rollback(api.inherent_extrinsics(parent_hash, inherent_data))
})
.map_err(|e| Error::Application(Box::new(e)))
}
/// Estimate the size of the block in the current state.
///
/// If `include_proof` is `true`, the estimated size of the storage proof will be added
/// to the estimation.
pub fn estimate_block_size(&self, include_proof: bool) -> usize {
let size = self.estimated_header_size + self.extrinsics.encoded_size();
if include_proof {
size + self.api.proof_recorder().map(|pr| pr.estimate_encoded_size()).unwrap_or(0)
} else {
size
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsp_blockchain::HeaderBackend;
use pezsp_core::Blake2Hasher;
use pezsp_state_machine::Backend;
use bizinikiwi_test_runtime_client::{
runtime::ExtrinsicBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt,
};
#[test]
fn block_building_storage_proof_does_not_include_runtime_by_default() {
let builder = bizinikiwi_test_runtime_client::TestClientBuilder::new();
let client = builder.build();
let genesis_hash = client.info().best_hash;
let block = BlockBuilderBuilder::new(&client)
.on_parent_block(genesis_hash)
.with_parent_block_number(0)
.enable_proof_recording()
.build()
.unwrap()
.build()
.unwrap();
let proof = block.proof.expect("Proof is build on request");
let genesis_state_root = client.header(genesis_hash).unwrap().unwrap().state_root;
let backend =
pezsp_state_machine::create_proof_check_backend::<Blake2Hasher>(genesis_state_root, proof)
.unwrap();
assert!(backend
.storage(&pezsp_core::storage::well_known_keys::CODE)
.unwrap_err()
.contains("Database missing expected key"),);
}
#[test]
fn failing_extrinsic_rolls_back_changes_in_storage_proof() {
let builder = bizinikiwi_test_runtime_client::TestClientBuilder::new();
let client = builder.build();
let genesis_hash = client.info().best_hash;
let mut block_builder = BlockBuilderBuilder::new(&client)
.on_parent_block(genesis_hash)
.with_parent_block_number(0)
.enable_proof_recording()
.build()
.unwrap();
block_builder.push(ExtrinsicBuilder::new_read_and_panic(8).build()).unwrap_err();
let block = block_builder.build().unwrap();
let proof_with_panic = block.proof.expect("Proof is build on request").encoded_size();
let mut block_builder = BlockBuilderBuilder::new(&client)
.on_parent_block(genesis_hash)
.with_parent_block_number(0)
.enable_proof_recording()
.build()
.unwrap();
block_builder.push(ExtrinsicBuilder::new_read(8).build()).unwrap();
let block = block_builder.build().unwrap();
let proof_without_panic = block.proof.expect("Proof is build on request").encoded_size();
let block = BlockBuilderBuilder::new(&client)
.on_parent_block(genesis_hash)
.with_parent_block_number(0)
.enable_proof_recording()
.build()
.unwrap()
.build()
.unwrap();
let proof_empty_block = block.proof.expect("Proof is build on request").encoded_size();
// Ensure that we rolled back the changes of the panicked transaction.
assert!(proof_without_panic > proof_with_panic);
assert!(proof_without_panic > proof_empty_block);
assert_eq!(proof_empty_block, proof_with_panic);
}
}
+61
View File
@@ -0,0 +1,61 @@
[package]
name = "pezsc-chain-spec"
version = "28.0.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Bizinikiwi chain configurations."
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
array-bytes = { workspace = true, default-features = true }
clap = { features = ["derive"], optional = true, workspace = true }
codec = { features = ["derive"], workspace = true }
docify = { workspace = true }
memmap2 = { workspace = true }
pezsc-chain-spec-derive = { workspace = true, default-features = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-executor = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-telemetry = { workspace = true, default-features = true }
serde = { features = ["derive"], workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-crypto-hashing = { workspace = true, default-features = true }
pezsp-genesis-builder = { workspace = true, default-features = true }
pezsp-io = { workspace = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-state-machine = { workspace = true, default-features = true }
pezsp-tracing = { workspace = true, default-features = true }
[dev-dependencies]
pretty_assertions = { workspace = true }
regex = { workspace = true }
pezsp-application-crypto = { features = ["serde"], workspace = true }
pezsp-consensus-babe = { features = ["serde"], workspace = true }
pezsp-keyring = { workspace = true, default-features = true }
bizinikiwi-test-runtime = { workspace = true }
[features]
runtime-benchmarks = [
"pezsc-client-api/runtime-benchmarks",
"pezsc-executor/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-consensus-babe/runtime-benchmarks",
"pezsp-genesis-builder/runtime-benchmarks",
"pezsp-io/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-state-machine/runtime-benchmarks",
"bizinikiwi-test-runtime/runtime-benchmarks",
]
+6
View File
@@ -0,0 +1,6 @@
Bizinikiwi chain configurations.
This crate contains structs and utilities to declare a runtime-specific configuration file (a.k.a chain spec).
Refer to crate documentation for details.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
@@ -0,0 +1,24 @@
[package]
name = "pezsc-chain-spec-derive"
version = "11.0.0"
authors.workspace = true
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
description = "Macros to derive chain spec extension traits implementation."
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[lib]
proc-macro = true
[dependencies]
proc-macro-crate = { workspace = true }
proc-macro2 = { workspace = true }
quote = { workspace = true }
syn = { workspace = true }
@@ -0,0 +1,225 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use proc_macro2::{Span, TokenStream};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{DeriveInput, Error, Ident, Path};
const CRATE_NAME: &str = "sc-chain-spec";
const ATTRIBUTE_NAME: &str = "forks";
/// Implements `Extension's` `Group` accessor.
///
/// The struct that derives this implementation will be usable within the `ChainSpec` file.
/// The derive implements a by-type accessor method.
pub fn extension_derive(ast: &DeriveInput) -> proc_macro::TokenStream {
derive(ast, |crate_name, name, generics: &syn::Generics, field_names, field_types, fields| {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let forks = fields
.named
.iter()
.find_map(|f| {
if f.attrs.iter().any(|attr| attr.path().is_ident(ATTRIBUTE_NAME)) {
let typ = &f.ty;
Some(quote! { #typ })
} else {
None
}
})
.unwrap_or_else(|| quote! { #crate_name::NoExtension });
quote! {
impl #impl_generics #crate_name::Extension for #name #ty_generics #where_clause {
type Forks = #forks;
fn get<T: 'static>(&self) -> Option<&T> {
use std::any::{Any, TypeId};
match TypeId::of::<T>() {
#( x if x == TypeId::of::<#field_types>() => <dyn Any>::downcast_ref(&self.#field_names) ),*,
_ => None,
}
}
fn get_any(&self, t: std::any::TypeId) -> &dyn std::any::Any {
use std::any::{Any, TypeId};
match t {
#( x if x == TypeId::of::<#field_types>() => &self.#field_names ),*,
_ => self,
}
}
fn get_any_mut(&mut self, t: std::any::TypeId) -> &mut dyn std::any::Any {
use std::any::{Any, TypeId};
match t {
#( x if x == TypeId::of::<#field_types>() => &mut self.#field_names ),*,
_ => self,
}
}
}
}
})
}
/// Implements required traits and creates `Fork` structs for `ChainSpec` custom parameter group.
pub fn group_derive(ast: &DeriveInput) -> proc_macro::TokenStream {
derive(ast, |crate_name, name, generics: &syn::Generics, field_names, field_types, _fields| {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
let fork_name = Ident::new(&format!("{}Fork", name), Span::call_site());
let fork_fields = generate_fork_fields(crate_name, &field_names, &field_types);
let to_fork = generate_base_to_fork(&fork_name, &field_names);
let combine_with = generate_combine_with(&field_names);
let to_base = generate_fork_to_base(name, &field_names);
let serde_crate_name = match proc_macro_crate::crate_name("serde") {
Ok(FoundCrate::Itself) => Ident::new("serde", Span::call_site()),
Ok(FoundCrate::Name(name)) => Ident::new(&name, Span::call_site()),
Err(e) => {
let err =
Error::new(Span::call_site(), &format!("Could not find `serde` crate: {}", e))
.to_compile_error();
return quote!( #err );
},
};
quote! {
#[derive(
Debug,
Clone,
PartialEq,
#serde_crate_name::Serialize,
#serde_crate_name::Deserialize,
ChainSpecExtension,
)]
pub struct #fork_name #ty_generics #where_clause {
#fork_fields
}
impl #impl_generics #crate_name::Group for #name #ty_generics #where_clause {
type Fork = #fork_name #ty_generics;
fn to_fork(self) -> Self::Fork {
use #crate_name::Group;
#to_fork
}
}
impl #impl_generics #crate_name::Fork for #fork_name #ty_generics #where_clause {
type Base = #name #ty_generics;
fn combine_with(&mut self, other: Self) {
use #crate_name::Fork;
#combine_with
}
fn to_base(self) -> Option<Self::Base> {
use #crate_name::Fork;
#to_base
}
}
}
})
}
pub fn derive(
ast: &DeriveInput,
derive: impl Fn(
&Path,
&Ident,
&syn::Generics,
Vec<&Ident>,
Vec<&syn::Type>,
&syn::FieldsNamed,
) -> TokenStream,
) -> proc_macro::TokenStream {
let err = || {
let err = Error::new(
Span::call_site(),
"ChainSpecGroup is only available for structs with named fields.",
)
.to_compile_error();
quote!( #err ).into()
};
let data = match &ast.data {
syn::Data::Struct(ref data) => data,
_ => return err(),
};
let fields = match &data.fields {
syn::Fields::Named(ref named) => named,
_ => return err(),
};
let name = &ast.ident;
let crate_path = match crate_name(CRATE_NAME) {
Ok(FoundCrate::Itself) => CRATE_NAME.replace("-", "_"),
Ok(FoundCrate::Name(chain_spec_name)) => chain_spec_name,
Err(e) => match crate_name("pezkuwi-sdk") {
Ok(FoundCrate::Name(sdk)) => format!("{sdk}::{CRATE_NAME}").replace("-", "_"),
_ => {
return Error::new(Span::call_site(), &e).to_compile_error().into();
},
},
};
let crate_path =
syn::parse_str::<Path>(&crate_path).expect("crate_name returns valid path; qed");
let field_names = fields.named.iter().flat_map(|x| x.ident.as_ref()).collect::<Vec<_>>();
let field_types = fields.named.iter().map(|x| &x.ty).collect::<Vec<_>>();
derive(&crate_path, name, &ast.generics, field_names, field_types, fields).into()
}
fn generate_fork_fields(crate_path: &Path, names: &[&Ident], types: &[&syn::Type]) -> TokenStream {
let crate_path = std::iter::repeat(crate_path);
quote! {
#( pub #names: Option<<#types as #crate_path::Group>::Fork>, )*
}
}
fn generate_base_to_fork(fork_name: &Ident, names: &[&Ident]) -> TokenStream {
let names2 = names.to_vec();
quote! {
#fork_name {
#( #names: Some(self.#names2.to_fork()), )*
}
}
}
fn generate_combine_with(names: &[&Ident]) -> TokenStream {
let names2 = names.to_vec();
quote! {
#( self.#names.combine_with(other.#names2); )*
}
}
fn generate_fork_to_base(fork: &Ident, names: &[&Ident]) -> TokenStream {
let names2 = names.to_vec();
quote! {
Some(#fork {
#( #names: self.#names2?.to_base()?, )*
})
}
}
@@ -0,0 +1,39 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Macros to derive chain spec extension traits implementation.
mod impls;
use proc_macro::TokenStream;
#[proc_macro_derive(ChainSpecGroup)]
pub fn group_derive(input: TokenStream) -> TokenStream {
match syn::parse(input) {
Ok(ast) => impls::group_derive(&ast),
Err(e) => e.to_compile_error().into(),
}
}
#[proc_macro_derive(ChainSpecExtension, attributes(forks))]
pub fn extensions_derive(input: TokenStream) -> TokenStream {
match syn::parse(input) {
Ok(ast) => impls::extension_derive(&ast),
Err(e) => e.to_compile_error().into(),
}
}
@@ -0,0 +1,128 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
"config": {
"babe": {
"authorities": [
[
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
1
],
[
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1
],
[
"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
1
]
],
"epochConfig": {
"allowed_slots": "PrimaryAndSecondaryPlainSlots",
"c": [
3,
10
]
}
},
"balances": {
"balances": [
[
"5D34dL5prEUaGNQtPPZ3yN5Y6BnkfXunKXXz6fo7ZJbLwRRH",
100000000000000000
],
[
"5GBNeWRhZc2jXu7D55rBimKYDk8PGk8itRYFTPfC8RJLKG5o",
100000000000000000
],
[
"5Dfis6XL8J2P6JHUnUtArnFWndn62SydeP8ee8sG2ky9nfm9",
100000000000000000
],
[
"5F4H97f7nQovyrbiq4ZetaaviNwThSVcFobcA5aGab6167dK",
100000000000000000
],
[
"5DiDShBWa1fQx6gLzpf3SFBhMinCoyvHM1BWjPNsmXS8hkrW",
100000000000000000
],
[
"5EFb84yH9tpcFuiKUcsmdoF7xeeY3ajG1ZLQimxQoFt9HMKR",
100000000000000000
],
[
"5DZLHESsfGrJ5YzT3HuRPXsSNb589xQ4Unubh1mYLodzKdVY",
100000000000000000
],
[
"5GHJzqvG6tXnngCpG7B12qjUvbo5e4e9z8Xjidk3CQZHxTPZ",
100000000000000000
],
[
"5CUnSsgAyLND3bxxnfNhgWXSe9Wn676JzLpGLgyJv858qhoX",
100000000000000000
],
[
"5CVKn7HAZW1Ky4r7Vkgsr7VEW88C2sHgUNDiwHY9Ct2hjU8q",
100000000000000000
],
[
"5H673aukQ4PeDe1U2nuv1bi32xDEziimh3PZz7hDdYUB7TNz",
100000000000000000
],
[
"5HTe9L15LJryjUAt1jZXZCBPnzbbGnpvFwbjE3NwCWaAqovf",
100000000000000000
],
[
"5D7LFzGpMwHPyDBavkRbWSKWTtJhCaPPZ379wWLT23bJwXJz",
100000000000000000
],
[
"5CLepMARnEgtVR1EkUuJVUvKh97gzergpSxUU3yKGx1v6EwC",
100000000000000000
],
[
"5Chb2UhfvZpmjjEziHbFbotM4quX32ZscRV6QJBt1rUKzz51",
100000000000000000
],
[
"5HmRp3i3ZZk7xsAvbi8hyXVP6whSMnBJGebVC4FsiZVhx52e",
100000000000000000
],
[
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
100000000000000000
],
[
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
100000000000000000
],
[
"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
100000000000000000
]
]
},
"bizinikiwiTest": {
"authorities": [
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y"
]
},
"system": {}
},
"code": "0x0"
}
}
}
@@ -0,0 +1,54 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"raw": {
"top": {
"0x00771836bebdd29870ff246d305c578c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
"0x00771836bebdd29870ff246d305c578c5e0621c4869aa60c02be9adcc98a0d1d": "0x0cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22",
"0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
"0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x0cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48010000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe220100000000000000",
"0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000",
"0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x0cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48010000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe220100000000000000",
"0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x03000000000000000a0000000000000001",
"0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
"0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01",
"0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545",
"0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746bb1bdbcacd6ac9340000000000000000": "0x4545454545454545454545454545454545454545454545454545454545454545",
"0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da901cae4e3edfbb32c91ed3f01ab964f4eeeab50338d8e5176d3141802d7b010a55dadcd5f23cf8aaafa724627e967e90e": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da91b614bd4a126f2d5d294e9a8af9da25248d7e931307afb4b68d8d565d4c66e00d856c6d65f5fed6bb82dcfb60e936c67": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94b21aff9fe1e8b2fc4b0775b8cbeff28ba8e2c7594dd74730f3ca835e95455d199261897edc9735d602ea29615e2b10b": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95786a2916fcb81e1bd5dcd81e0d2452884617f575372edb5a36d85c04cdf2e4699f96fe33eb5f94a28c041b88e398d0c": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95b8542d9672c7b7e779cc7c1e6b605691c2115d06120ea2bee32dd601d02f36367564e7ddf84ae2717ca3f097459652e": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da996c30bdbfab640838e6b6d3c33ab4adb4211b79e34ee8072eab506edd4b93a7b85a14c9a05e5cdd056d98e7dbca87730": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99dc65b1339ec388fbf2ca0cdef51253512c6cfd663203ea16968594f24690338befd906856c4d2f4ef32dad578dba20c": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99e6eb5abd62f5fd54793da91a47e6af6125d57171ff9241f07acaa1bb6a6103517965cf2cd00e643b27e7599ebccba70": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d0052993b6f3bd0544fd1f5e4125b9fbde3e789ecd53431fe5c06c12b72137153496dace35c695b5f4d7b41f7ed5763b": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d6b7e9a5f12bc571053265dade10d3b4b606fc73f57f03cdb4c932d475ab426043e429cecc2ffff0d2672b0df8398c48": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e1a35f56ee295d39287cbffcfc60c4b346f136b564e1fad55031404dd84e5cd3fa76bfe7cc7599b39d38fd06663bbc0a": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e2c1dc507e2035edbbd8776c440d870460c57f0008067cc01c5ff9eb2e2f9b3a94299a915a91198bd1021a6c55596f57": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9eca0e653a94f4080f6311b4e7b6934eb2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ee8bf7ef90fc56a8aa3b90b344c599550c29b161e27ff8ba45bf6bad4711f326fc506a8803453a4d7e3158e993495f10": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f5d6f1c082fe63eec7a71fcad00f4a892e3d43b7b0d04e776e69e7be35247cecdac65504c579195731eaf64b7940966e": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9fbf0818841edf110e05228a6379763c4fc3c37459d9bdc61f58a5ebc01e9e2305a19d390c0543dc733861ec3cf1de01f": "0x000000000000000000000000010000000000000000008a5d784563010000000000000000000000000000000000000000000000000000000000000080",
"0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x0000",
"0x3a636f6465": "0x0",
"0x3a65787472696e7369635f696e646578": "0x00000000",
"0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100",
"0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x00003ef1ee275e1a",
"0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000"
},
"childrenDefault": {}
}
}
}
@@ -0,0 +1,35 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
"patch": {
"balances": {
"balances": [
[
"5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty",
1000000000000000
],
[
"5FLSigC9HGRKVhB9FiEo4Y3koPsNmBmLJbpXg2mp1hXcS59Y",
1000000000000000
]
]
},
"bizinikiwiTest": {
"authorities": [
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
"5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL"
]
}
},
"code": "0x0"
}
}
}
@@ -0,0 +1,32 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"runtimeGenesis": {
"patch": {
"babe": {
"epochConfig": {
"allowed_slots": "PrimaryAndSecondaryPlainSlots",
"c": [
7,
10
]
}
},
"bizinikiwiTest": {
"authorities": [
"5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL",
"5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"
]
}
},
"code": "0x0"
}
}
}
@@ -0,0 +1,33 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"raw": {
"top": {
"0x00771836bebdd29870ff246d305c578c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
"0x00771836bebdd29870ff246d305c578c5e0621c4869aa60c02be9adcc98a0d1d": "0x081cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07cd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d",
"0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
"0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000",
"0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x07000000000000000a0000000000000001",
"0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000",
"0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01",
"0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545",
"0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746bb1bdbcacd6ac9340000000000000000": "0x4545454545454545454545454545454545454545454545454545454545454545",
"0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01",
"0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x0000",
"0x3a636f6465": "0x0",
"0x3a65787472696e7369635f696e646578": "0x00000000",
"0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0100",
"0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0000000000000000",
"0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000"
},
"childrenDefault": {}
}
}
}
@@ -0,0 +1,30 @@
{
"name": "Flaming Fir",
"id": "flaming-fir",
"properties": {
"tokenDecimals": 15,
"tokenSymbol": "FIR"
},
"bootNodes": [
"/ip4/35.246.224.91/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV",
"/ip4/35.246.224.91/tcp/30334/ws/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV",
"/ip4/35.246.210.11/tcp/30333/p2p/QmWv9Ww7znzgLFyCzf21SR6tUKXrmHCZH9KhebeH4gyE9f",
"/ip4/35.246.210.11/tcp/30334/ws/p2p/QmWv9Ww7znzgLFyCzf21SR6tUKXrmHCZH9KhebeH4gyE9f",
"/ip4/35.198.110.45/tcp/30333/p2p/QmTtcYKJho9vFmqtMA548QBSmLbmwAkBSiEKK3kWKfb6bJ",
"/ip4/35.198.110.45/tcp/30334/ws/p2p/QmTtcYKJho9vFmqtMA548QBSmLbmwAkBSiEKK3kWKfb6bJ",
"/ip4/35.198.114.154/tcp/30333/p2p/QmQJmDorK9c8KjMF5PdWiH2WGUXyzJtgTeJ55S5gggdju6",
"/ip4/35.198.114.154/tcp/30334/ws/p2p/QmQJmDorK9c8KjMF5PdWiH2WGUXyzJtgTeJ55S5gggdju6"
],
"telemetryEndpoints": [
["wss://telemetry.pezkuwichain.io/submit/", 0]
],
"protocolId": "fir",
"genesis": {
"raw": [
{
"0xb2029f8665aac509629f2d28cea790a3": "0x10f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c26633919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f437800299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d655633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde787932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d129becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe96993326e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f91066e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"
},
{}
]
}
}
@@ -0,0 +1,31 @@
{
"name": "Flaming Fir",
"id": "flaming-fir",
"properties": {
"tokenDecimals": 15,
"tokenSymbol": "FIR"
},
"bootNodes": [
"/ip4/35.246.224.91/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV",
"/ip4/35.246.224.91/tcp/30334/ws/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV",
"/ip4/35.246.210.11/tcp/30333/p2p/QmWv9Ww7znzgLFyCzf21SR6tUKXrmHCZH9KhebeH4gyE9f",
"/ip4/35.246.210.11/tcp/30334/ws/p2p/QmWv9Ww7znzgLFyCzf21SR6tUKXrmHCZH9KhebeH4gyE9f",
"/ip4/35.198.110.45/tcp/30333/p2p/QmTtcYKJho9vFmqtMA548QBSmLbmwAkBSiEKK3kWKfb6bJ",
"/ip4/35.198.110.45/tcp/30334/ws/p2p/QmTtcYKJho9vFmqtMA548QBSmLbmwAkBSiEKK3kWKfb6bJ",
"/ip4/35.198.114.154/tcp/30333/p2p/QmQJmDorK9c8KjMF5PdWiH2WGUXyzJtgTeJ55S5gggdju6",
"/ip4/35.198.114.154/tcp/30334/ws/p2p/QmQJmDorK9c8KjMF5PdWiH2WGUXyzJtgTeJ55S5gggdju6"
],
"telemetryEndpoints": [
["wss://telemetry.pezkuwichain.io/submit/", 0]
],
"protocolId": "fir",
"myProperty": "Test Extension",
"genesis": {
"raw": [
{
"0xb2029f8665aac509629f2d28cea790a3": "0x10f26cdb14b5aec7b2789fd5ca80f979cef3761897ae1f37ffb3e154cbcc1c26633919132b851ef0fd2dae42a7e734fe547af5a6b809006100f48944d7fae8e8ef00299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f437800299981a2b92f878baaf5dbeba5c18d4e70f2a1fcd9c61b32ea18daf38f4378547ff0ab649283a7ae01dbc2eb73932eba2fb09075e9485ff369082a2ff38d655633b70b80a6c8bb16270f82cca6d56b27ed7b76c8fd5af2986a25a4788ce440482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a482a3389a6cf42d8ed83888cfd920fec738ea30f97e44699ada7323f08c3380a68655684472b743e456907b398d3a44c113f189e56d1bbfd55e889e295dfde787932cff431e748892fa48e10c63c17d30f80ca42e4de3921e641249cd7fa3c2f482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e482dbd7297a39fa145c570552249c2ca9dd47e281f0c500c971b59c9dcdcd82e9c7a2ee14e565db0c69f78c7b4cd839fbf52b607d867e9e9c5a79042898a0d129becad03e6dcac03cee07edebca5475314861492cdfc96a2144a67bbe96993326e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f91066e7e4eb42cbd2e0ab4cae8708ce5509580b8c04d11f6758dbf686d50fe9f9106"
},
{}
]
}
}
@@ -0,0 +1,18 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"raw": {
"top": {
"0x3a636f6465": "0x010101"
},
"childrenDefault": {}
}
}
}
@@ -0,0 +1,19 @@
{
"name": "TestName",
"id": "test_id",
"chainType": "Local",
"bootNodes": [],
"telemetryEndpoints": null,
"protocolId": null,
"properties": null,
"codeSubstitutes": {},
"genesis": {
"raw": {
"top": {
"0x3a636f6465": "0x010101"
},
"childrenDefault": {}
}
},
"code": "0x060708"
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,433 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Chain Spec extensions helpers.
use std::{
any::{Any, TypeId},
fmt::Debug,
};
use std::collections::BTreeMap;
use serde::{de::DeserializeOwned, Deserialize, Serialize};
/// A `ChainSpec` extension.
///
/// This trait is implemented automatically by `ChainSpecGroup` macro.
pub trait Group: Clone + Sized {
/// An associated type containing fork definition.
type Fork: Fork<Base = Self>;
/// Convert to fork type.
fn to_fork(self) -> Self::Fork;
}
/// A `ChainSpec` extension fork definition.
///
/// Basically should look the same as `Group`, but
/// all parameters are optional. This allows changing
/// only one parameter as part of the fork.
/// The forks can be combined (summed up) to specify
/// a complete set of parameters
pub trait Fork: Serialize + DeserializeOwned + Clone + Sized {
/// A base `Group` type.
type Base: Group<Fork = Self>;
/// Combine with another struct.
///
/// All parameters set in `other` should override the
/// ones in the current struct.
fn combine_with(&mut self, other: Self);
/// Attempt to convert to the base type if all parameters are set.
fn to_base(self) -> Option<Self::Base>;
}
macro_rules! impl_trivial {
() => {};
($A : ty) => {
impl_trivial!($A ,);
};
($A : ty , $( $B : ty ),*) => {
impl_trivial!($( $B ),*);
impl Group for $A {
type Fork = $A;
fn to_fork(self) -> Self::Fork {
self
}
}
impl Fork for $A {
type Base = $A;
fn combine_with(&mut self, other: Self) {
*self = other;
}
fn to_base(self) -> Option<Self::Base> {
Some(self)
}
}
}
}
impl_trivial!((), u8, u16, u32, u64, usize, String, Vec<u8>);
impl<T: Group> Group for Option<T> {
type Fork = Option<T::Fork>;
fn to_fork(self) -> Self::Fork {
self.map(|a| a.to_fork())
}
}
impl<T: Fork> Fork for Option<T> {
type Base = Option<T::Base>;
fn combine_with(&mut self, other: Self) {
*self = match (self.take(), other) {
(Some(mut a), Some(b)) => {
a.combine_with(b);
Some(a)
},
(a, b) => a.or(b),
};
}
fn to_base(self) -> Option<Self::Base> {
self.map(|x| x.to_base())
}
}
/// A collection of `ChainSpec` extensions.
///
/// This type can be passed around and allows the core
/// modules to request a strongly-typed, but optional configuration.
pub trait Extension: Serialize + DeserializeOwned + Clone {
type Forks: IsForks;
/// Get an extension of specific type.
fn get<T: 'static>(&self) -> Option<&T>;
/// Get an extension of specific type as reference to `Any`.
fn get_any(&self, t: TypeId) -> &dyn Any;
/// Get an extension of specific type as mutable reference to `Any`.
fn get_any_mut(&mut self, t: TypeId) -> &mut dyn Any;
/// Get forkable extensions of specific type.
fn forks<BlockNumber, T>(&self) -> Option<Forks<BlockNumber, T>>
where
BlockNumber: Ord + Clone + 'static,
T: Group + 'static,
<Self::Forks as IsForks>::Extension: Extension,
<<Self::Forks as IsForks>::Extension as Group>::Fork: Extension,
{
self.get::<Forks<BlockNumber, <Self::Forks as IsForks>::Extension>>()?
.for_type()
}
}
impl Extension for crate::NoExtension {
type Forks = Self;
fn get<T: 'static>(&self) -> Option<&T> {
None
}
fn get_any(&self, _t: TypeId) -> &dyn Any {
self
}
fn get_any_mut(&mut self, _: TypeId) -> &mut dyn Any {
self
}
}
pub trait IsForks {
type BlockNumber: Ord + 'static;
type Extension: Group + 'static;
}
impl IsForks for Option<()> {
type BlockNumber = u64;
type Extension = Self;
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
#[serde(deny_unknown_fields)]
pub struct Forks<BlockNumber: Ord, T: Group> {
forks: BTreeMap<BlockNumber, T::Fork>,
#[serde(flatten)]
base: T,
}
impl<B: Ord, T: Group + Default> Default for Forks<B, T> {
fn default() -> Self {
Self { base: Default::default(), forks: Default::default() }
}
}
impl<B: Ord, T: Group> Forks<B, T>
where
T::Fork: Debug,
{
/// Create new fork definition given the base and the forks.
pub fn new(base: T, forks: BTreeMap<B, T::Fork>) -> Self {
Self { base, forks }
}
/// Return a set of parameters for `Group` including all forks up to `block` (inclusive).
pub fn at_block(&self, block: B) -> T {
let mut start = self.base.clone().to_fork();
for (_, fork) in self.forks.range(..=block) {
start.combine_with(fork.clone());
}
start
.to_base()
.expect("We start from the `base` object, so it's always fully initialized; qed")
}
}
impl<B, T> IsForks for Forks<B, T>
where
B: Ord + 'static,
T: Group + 'static,
{
type BlockNumber = B;
type Extension = T;
}
impl<B: Ord + Clone, T: Group + Extension> Forks<B, T>
where
T::Fork: Extension,
{
/// Get forks definition for a subset of this extension.
///
/// Returns the `Forks` struct, but limited to a particular type
/// within the extension.
pub fn for_type<X>(&self) -> Option<Forks<B, X>>
where
X: Group + 'static,
{
let base = self.base.get::<X>()?.clone();
let forks = self
.forks
.iter()
.filter_map(|(k, v)| Some((k.clone(), v.get::<Option<X::Fork>>()?.clone()?)))
.collect();
Some(Forks { base, forks })
}
}
impl<B, E> Extension for Forks<B, E>
where
B: Serialize + DeserializeOwned + Ord + Clone + 'static,
E: Extension + Group + 'static,
{
type Forks = Self;
fn get<T: 'static>(&self) -> Option<&T> {
if TypeId::of::<T>() == TypeId::of::<E>() {
<dyn Any>::downcast_ref(&self.base)
} else {
self.base.get()
}
}
fn get_any(&self, t: TypeId) -> &dyn Any {
if t == TypeId::of::<E>() {
&self.base
} else {
self.base.get_any(t)
}
}
fn get_any_mut(&mut self, t: TypeId) -> &mut dyn Any {
if t == TypeId::of::<E>() {
&mut self.base
} else {
self.base.get_any_mut(t)
}
}
fn forks<BlockNumber, T>(&self) -> Option<Forks<BlockNumber, T>>
where
BlockNumber: Ord + Clone + 'static,
T: Group + 'static,
<Self::Forks as IsForks>::Extension: Extension,
<<Self::Forks as IsForks>::Extension as Group>::Fork: Extension,
{
if TypeId::of::<BlockNumber>() == TypeId::of::<B>() {
<dyn Any>::downcast_ref(&self.for_type::<T>()?).cloned()
} else {
self.get::<Forks<BlockNumber, <Self::Forks as IsForks>::Extension>>()?
.for_type()
}
}
}
/// A subset of the `Extension` trait that only allows for querying extensions.
pub trait GetExtension {
/// Get an extension of specific type.
fn get_any(&self, t: TypeId) -> &dyn Any;
/// Get an extension of specific type with mutable access.
fn get_any_mut(&mut self, t: TypeId) -> &mut dyn Any;
}
impl<E: Extension> GetExtension for E {
fn get_any(&self, t: TypeId) -> &dyn Any {
Extension::get_any(self, t)
}
fn get_any_mut(&mut self, t: TypeId) -> &mut dyn Any {
Extension::get_any_mut(self, t)
}
}
/// Helper function that queries an extension by type from `GetExtension` trait object.
pub fn get_extension<T: 'static>(e: &dyn GetExtension) -> Option<&T> {
<dyn Any>::downcast_ref(GetExtension::get_any(e, TypeId::of::<T>()))
}
/// Helper function that queries an extension by type from `GetExtension` trait object.
pub fn get_extension_mut<T: 'static>(e: &mut dyn GetExtension) -> Option<&mut T> {
<dyn Any>::downcast_mut(GetExtension::get_any_mut(e, TypeId::of::<T>()))
}
#[cfg(test)]
mod tests {
use super::*;
use pezsc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup};
// Make the proc macro work for tests and doc tests.
use crate as pezsc_chain_spec;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup)]
#[serde(deny_unknown_fields)]
pub struct Extension1 {
pub test: u64,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup)]
#[serde(deny_unknown_fields)]
pub struct Extension2 {
pub test: u8,
}
#[derive(
Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecGroup, ChainSpecExtension,
)]
#[serde(deny_unknown_fields)]
pub struct Extensions {
pub ext1: Extension1,
pub ext2: Extension2,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ChainSpecExtension)]
#[serde(deny_unknown_fields)]
pub struct Ext2 {
#[serde(flatten)]
ext1: Extension1,
#[forks]
forkable: Forks<u64, Extensions>,
}
#[test]
fn forks_should_work_correctly() {
use super::Extension as _;
// We first need to deserialize into a `Value` because of the following bug:
// https://github.com/serde-rs/json/issues/505
let ext_val: serde_json::Value = serde_json::from_str(
r#"
{
"test": 11,
"forkable": {
"ext1": {
"test": 15
},
"ext2": {
"test": 123
},
"forks": {
"1": {
"ext1": { "test": 5 }
},
"2": {
"ext2": { "test": 5 }
},
"5": {
"ext2": { "test": 1 }
}
}
}
}
"#,
)
.unwrap();
let ext: Ext2 = serde_json::from_value(ext_val).unwrap();
assert_eq!(ext.get::<Extension1>(), Some(&Extension1 { test: 11 }));
// get forks definition
let forks = ext.get::<Forks<u64, Extensions>>().unwrap();
assert_eq!(
forks.at_block(0),
Extensions { ext1: Extension1 { test: 15 }, ext2: Extension2 { test: 123 } }
);
assert_eq!(
forks.at_block(1),
Extensions { ext1: Extension1 { test: 5 }, ext2: Extension2 { test: 123 } }
);
assert_eq!(
forks.at_block(2),
Extensions { ext1: Extension1 { test: 5 }, ext2: Extension2 { test: 5 } }
);
assert_eq!(
forks.at_block(4),
Extensions { ext1: Extension1 { test: 5 }, ext2: Extension2 { test: 5 } }
);
assert_eq!(
forks.at_block(5),
Extensions { ext1: Extension1 { test: 5 }, ext2: Extension2 { test: 1 } }
);
assert_eq!(
forks.at_block(10),
Extensions { ext1: Extension1 { test: 5 }, ext2: Extension2 { test: 1 } }
);
assert!(forks.at_block(10).get::<Extension2>().is_some());
// filter forks for `Extension2`
let ext2 = forks.for_type::<Extension2>().unwrap();
assert_eq!(ext2.at_block(0), Extension2 { test: 123 });
assert_eq!(ext2.at_block(2), Extension2 { test: 5 });
assert_eq!(ext2.at_block(10), Extension2 { test: 1 });
// make sure that it can return forks correctly
let ext2_2 = forks.forks::<u64, Extension2>().unwrap();
assert_eq!(ext2, ext2_2);
// also ext should be able to return forks correctly:
let ext2_3 = ext.forks::<u64, Extension2>().unwrap();
assert_eq!(ext2_2, ext2_3);
}
}
@@ -0,0 +1,148 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Tool for creating the genesis block.
use std::{marker::PhantomData, sync::Arc};
use codec::Encode;
use pezsc_client_api::{backend::Backend, BlockImportOperation};
use pezsc_executor::RuntimeVersionOf;
use pezsp_core::storage::{well_known_keys, StateVersion, Storage};
use pezsp_runtime::{
traits::{Block as BlockT, Hash as HashT, HashingFor, Header as HeaderT, Zero},
BuildStorage,
};
/// Return the state version given the genesis storage and executor.
pub fn resolve_state_version_from_wasm<E, H>(
storage: &Storage,
executor: &E,
) -> pezsp_blockchain::Result<StateVersion>
where
E: RuntimeVersionOf,
H: HashT,
{
if let Some(wasm) = storage.top.get(well_known_keys::CODE) {
let mut ext = pezsp_state_machine::BasicExternalities::new_empty(); // just to read runtime version.
let code_fetcher = pezsp_core::traits::WrappedRuntimeCode(wasm.as_slice().into());
let runtime_code = pezsp_core::traits::RuntimeCode {
code_fetcher: &code_fetcher,
heap_pages: None,
hash: <H as HashT>::hash(wasm).encode(),
};
let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code)
.map_err(|e| pezsp_blockchain::Error::VersionInvalid(e.to_string()))?;
Ok(runtime_version.state_version())
} else {
Err(pezsp_blockchain::Error::VersionInvalid(
"Runtime missing from initial storage, could not read state version.".to_string(),
))
}
}
/// Create a genesis block, given the initial storage.
pub fn construct_genesis_block<Block: BlockT>(
state_root: Block::Hash,
state_version: StateVersion,
) -> Block {
let extrinsics_root = <<<Block as BlockT>::Header as HeaderT>::Hashing as HashT>::trie_root(
Vec::new(),
state_version,
);
Block::new(
<<Block as BlockT>::Header as HeaderT>::new(
Zero::zero(),
extrinsics_root,
state_root,
Default::default(),
Default::default(),
),
Default::default(),
)
}
/// Trait for building the genesis block.
pub trait BuildGenesisBlock<Block: BlockT> {
/// The import operation used to import the genesis block into the backend.
type BlockImportOperation;
/// Returns the built genesis block along with the block import operation
/// after setting the genesis storage.
fn build_genesis_block(self) -> pezsp_blockchain::Result<(Block, Self::BlockImportOperation)>;
}
/// Default genesis block builder in Bizinikiwi.
pub struct GenesisBlockBuilder<Block: BlockT, B, E> {
genesis_storage: Storage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
_phantom: PhantomData<Block>,
}
impl<Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf> GenesisBlockBuilder<Block, B, E> {
/// Constructs a new instance of [`GenesisBlockBuilder`].
pub fn new(
build_genesis_storage: &dyn BuildStorage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
) -> pezsp_blockchain::Result<Self> {
let genesis_storage =
build_genesis_storage.build_storage().map_err(pezsp_blockchain::Error::Storage)?;
Self::new_with_storage(genesis_storage, commit_genesis_state, backend, executor)
}
/// Constructs a new instance of [`GenesisBlockBuilder`] using provided storage.
pub fn new_with_storage(
genesis_storage: Storage,
commit_genesis_state: bool,
backend: Arc<B>,
executor: E,
) -> pezsp_blockchain::Result<Self> {
Ok(Self {
genesis_storage,
commit_genesis_state,
backend,
executor,
_phantom: PhantomData::<Block>,
})
}
}
impl<Block: BlockT, B: Backend<Block>, E: RuntimeVersionOf> BuildGenesisBlock<Block>
for GenesisBlockBuilder<Block, B, E>
{
type BlockImportOperation = <B as Backend<Block>>::BlockImportOperation;
fn build_genesis_block(self) -> pezsp_blockchain::Result<(Block, Self::BlockImportOperation)> {
let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self;
let genesis_state_version =
resolve_state_version_from_wasm::<_, HashingFor<Block>>(&genesis_storage, &executor)?;
let mut op = backend.begin_operation()?;
let state_root =
op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?;
let genesis_block = construct_genesis_block::<Block>(state_root, genesis_state_version);
Ok((genesis_block, op))
}
}
@@ -0,0 +1,253 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! A helper module for calling the GenesisBuilder API from arbitrary runtime wasm blobs.
use codec::{Decode, Encode};
pub use pezsc_executor::pezsp_wasm_interface::HostFunctions;
use pezsc_executor::{error::Result, WasmExecutor};
use serde_json::{from_slice, Value};
use pezsp_core::{
storage::Storage,
traits::{CallContext, CodeExecutor, Externalities, FetchRuntimeCode, RuntimeCode},
};
use pezsp_genesis_builder::{PresetId, Result as BuildResult};
pub use pezsp_genesis_builder::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET};
use pezsp_state_machine::BasicExternalities;
use std::borrow::Cow;
/// A utility that facilitates calling the GenesisBuilder API from the runtime wasm code blob.
///
/// `EHF` type allows to specify the extended host function required for building runtime's genesis
/// config. The type will be combined with default `pezsp_io::BizinikiwiHostFunctions`.
pub struct GenesisConfigBuilderRuntimeCaller<'a, EHF = ()>
where
EHF: HostFunctions,
{
code: Cow<'a, [u8]>,
code_hash: Vec<u8>,
executor: WasmExecutor<(pezsp_io::BizinikiwiHostFunctions, EHF)>,
}
impl<'a, EHF> FetchRuntimeCode for GenesisConfigBuilderRuntimeCaller<'a, EHF>
where
EHF: HostFunctions,
{
fn fetch_runtime_code(&self) -> Option<Cow<'_, [u8]>> {
Some(self.code.as_ref().into())
}
}
impl<'a, EHF> GenesisConfigBuilderRuntimeCaller<'a, EHF>
where
EHF: HostFunctions,
{
/// Creates new instance using the provided code blob.
///
/// This code is later referred to as `runtime`.
pub fn new(code: &'a [u8]) -> Self {
GenesisConfigBuilderRuntimeCaller {
code: code.into(),
code_hash: pezsp_crypto_hashing::blake2_256(code).to_vec(),
executor: WasmExecutor::<(pezsp_io::BizinikiwiHostFunctions, EHF)>::builder()
.with_allow_missing_host_functions(true)
.build(),
}
}
fn call(&self, ext: &mut dyn Externalities, method: &str, data: &[u8]) -> Result<Vec<u8>> {
self.executor
.call(
ext,
&RuntimeCode { heap_pages: None, code_fetcher: self, hash: self.code_hash.clone() },
method,
data,
CallContext::Offchain,
)
.0
}
/// Returns a json representation of the default `RuntimeGenesisConfig` provided by the
/// `runtime`.
///
/// Calls [`GenesisBuilder::get_preset`](pezsp_genesis_builder::GenesisBuilder::get_preset) in the
/// `runtime` with `None` argument.
pub fn get_default_config(&self) -> core::result::Result<Value, String> {
self.get_named_preset(None)
}
/// Returns a JSON blob representation of the builtin `GenesisConfig` identified by `id`.
///
/// Calls [`GenesisBuilder::get_preset`](pezsp_genesis_builder::GenesisBuilder::get_preset)
/// provided by the `runtime`.
pub fn get_named_preset(&self, id: Option<&String>) -> core::result::Result<Value, String> {
let mut t = BasicExternalities::new_empty();
let call_result = self
.call(&mut t, "GenesisBuilder_get_preset", &id.encode())
.map_err(|e| format!("wasm call error {e}"))?;
let named_preset = Option::<Vec<u8>>::decode(&mut &call_result[..])
.map_err(|e| format!("scale codec error: {e}"))?;
if let Some(named_preset) = named_preset {
Ok(from_slice(&named_preset[..]).expect("returned value is json. qed."))
} else {
Err(format!("The preset with name {id:?} is not available."))
}
}
/// Calls [`pezsp_genesis_builder::GenesisBuilder::build_state`] provided by runtime.
pub fn get_storage_for_config(&self, config: Value) -> core::result::Result<Storage, String> {
let mut ext = BasicExternalities::new_empty();
let json_pretty_str = serde_json::to_string_pretty(&config)
.map_err(|e| format!("json to string failed: {e}"))?;
let call_result = self
.call(&mut ext, "GenesisBuilder_build_state", &json_pretty_str.encode())
.map_err(|e| format!("wasm call error {e}"))?;
BuildResult::decode(&mut &call_result[..])
.map_err(|e| format!("scale codec error: {e}"))?
.map_err(|e| format!("{e} for blob:\n{}", json_pretty_str))?;
Ok(ext.into_storages())
}
/// Creates the genesis state by patching the default `RuntimeGenesisConfig`.
///
/// This function generates the `RuntimeGenesisConfig` for the runtime by applying a provided
/// JSON patch. The patch modifies the default `RuntimeGenesisConfig` allowing customization of
/// the specific keys. The resulting `RuntimeGenesisConfig` is then deserialized from the
/// patched JSON representation and stored in the storage.
///
/// If the provided JSON patch is incorrect or the deserialization fails the error will be
/// returned.
///
/// The patching process modifies the default `RuntimeGenesisConfig` according to the following
/// rules:
/// 1. Existing keys in the default configuration will be overridden by the corresponding values
/// in the patch (also applies to `null` values).
/// 2. If a key exists in the patch but not in the default configuration, it will be added to
/// the resulting `RuntimeGenesisConfig`.
///
/// Please note that the patch may contain full `RuntimeGenesisConfig`.
pub fn get_storage_for_patch(&self, patch: Value) -> core::result::Result<Storage, String> {
let mut config = self.get_default_config()?;
crate::json_patch::merge(&mut config, patch);
self.get_storage_for_config(config)
}
pub fn get_storage_for_named_preset(
&self,
name: Option<&String>,
) -> core::result::Result<Storage, String> {
self.get_storage_for_patch(self.get_named_preset(name)?)
}
pub fn preset_names(&self) -> core::result::Result<Vec<PresetId>, String> {
let mut t = BasicExternalities::new_empty();
let call_result = self
.call(&mut t, "GenesisBuilder_preset_names", &vec![])
.map_err(|e| format!("wasm call error {e}"))?;
let preset_names = Vec::<PresetId>::decode(&mut &call_result[..])
.map_err(|e| format!("scale codec error: {e}"))?;
Ok(preset_names)
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::{from_str, json};
pub use pezsp_consensus_babe::{AllowedSlots, BabeEpochConfiguration};
pub use pezsp_genesis_builder::PresetId;
#[test]
fn list_presets_works() {
pezsp_tracing::try_init_simple();
let presets =
<GenesisConfigBuilderRuntimeCaller>::new(bizinikiwi_test_runtime::wasm_binary_unwrap())
.preset_names()
.unwrap();
assert_eq!(presets, vec![PresetId::from("foobar"), PresetId::from("staging"),]);
}
#[test]
fn get_default_config_works() {
let config =
<GenesisConfigBuilderRuntimeCaller>::new(bizinikiwi_test_runtime::wasm_binary_unwrap())
.get_default_config()
.unwrap();
let expected = r#"{"babe": {"authorities": [], "epochConfig": {"allowed_slots": "PrimaryAndSecondaryVRFSlots", "c": [1, 4]}}, "balances": {"balances": [], "devAccounts": null}, "bizinikiwiTest": {"authorities": []}, "system": {}}"#;
assert_eq!(from_str::<Value>(expected).unwrap(), config);
}
#[test]
fn get_named_preset_works() {
pezsp_tracing::try_init_simple();
let config =
<GenesisConfigBuilderRuntimeCaller>::new(bizinikiwi_test_runtime::wasm_binary_unwrap())
.get_named_preset(Some(&"foobar".to_string()))
.unwrap();
let expected = r#"{"foo":"bar"}"#;
assert_eq!(from_str::<Value>(expected).unwrap(), config);
}
#[test]
fn get_storage_for_patch_works() {
let patch = json!({
"babe": {
"epochConfig": {
"c": [
69,
696
],
"allowed_slots": "PrimaryAndSecondaryPlainSlots"
}
},
});
let storage =
<GenesisConfigBuilderRuntimeCaller>::new(bizinikiwi_test_runtime::wasm_binary_unwrap())
.get_storage_for_patch(patch)
.unwrap();
//Babe|Authorities
let value: Vec<u8> = storage
.top
.get(
&array_bytes::hex2bytes(
"1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef",
)
.unwrap(),
)
.unwrap()
.clone();
assert_eq!(
BabeEpochConfiguration::decode(&mut &value[..]).unwrap(),
BabeEpochConfiguration {
c: (69, 696),
allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots
}
);
}
}
@@ -0,0 +1,198 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! A helper module providing json patching functions.
use serde_json::Value;
/// Recursively merges two JSON objects, `a` and `b`, into a single object.
///
/// If a key exists in both objects, the value from `b` will override the value from `a` (also if
/// value in `b` is `null`).
/// If a key exists only in `b` and not in `a`, it will be added to `a`.
/// No keys will be removed from `a`.
///
/// # Arguments
///
/// * `a` - A mutable reference to the target JSON object to merge into.
/// * `b` - The JSON object to merge with `a`.
pub fn merge(a: &mut Value, b: Value) {
match (a, b) {
(Value::Object(a), Value::Object(b)) =>
for (k, v) in b {
merge(a.entry(k).or_insert(Value::Null), v);
},
(a, b) => *a = b,
};
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test1_simple_merge() {
let mut j1 = json!({ "a":123 });
merge(&mut j1, json!({ "b":256 }));
assert_eq!(j1, json!({ "a":123, "b":256 }));
}
#[test]
fn test2_patch_simple_merge_nested() {
let mut j1 = json!({
"a": {
"name": "xxx",
"value": 123
},
"b": { "c" : { "inner_name": "yyy" } }
});
let j2 = json!({
"a": {
"keys": ["a", "b", "c" ]
}
});
merge(&mut j1, j2);
assert_eq!(
j1,
json!({"a":{"keys":["a","b","c"],"name":"xxx","value":123}, "b": { "c" : { "inner_name": "yyy" } }})
);
}
#[test]
fn test3_patch_overrides_existing_keys() {
let mut j1 = json!({
"a": {
"name": "xxx",
"value": 123,
"keys": ["d"]
}
});
let j2 = json!({
"a": {
"keys": ["a", "b", "c" ]
}
});
merge(&mut j1, j2);
assert_eq!(j1, json!({"a":{"keys":["a","b","c"],"name":"xxx","value":123}}));
}
#[test]
fn test4_patch_overrides_existing_keys() {
let mut j1 = json!({
"a": {
"name": "xxx",
"value": 123,
"b" : {
"inner_name": "yyy"
}
}
});
let j2 = json!({
"a": {
"name": "new_name",
"b" : {
"inner_name": "inner_new_name"
}
}
});
merge(&mut j1, j2);
assert_eq!(
j1,
json!({ "a": {"name":"new_name", "value":123, "b" : { "inner_name": "inner_new_name" }} })
);
}
#[test]
fn test5_patch_overrides_existing_nested_keys() {
let mut j1 = json!({
"a": {
"name": "xxx",
"value": 123,
"b": {
"c": {
"d": {
"name": "yyy",
"value": 256
}
}
}
},
});
let j2 = json!({
"a": {
"value": 456,
"b": {
"c": {
"d": {
"name": "new_name"
}
}
}
}
});
merge(&mut j1, j2);
assert_eq!(
j1,
json!({ "a": {"name":"xxx", "value":456, "b": { "c": { "d": { "name": "new_name", "value": 256 }}}}})
);
}
#[test]
fn test6_patch_does_not_remove_keys_if_null() {
let mut j1 = json!({
"a": {
"name": "xxx",
"value": 123,
"enum_variant_1": {
"name": "yyy",
}
},
});
let j2 = json!({
"a": {
"value": 456,
"enum_variant_1": null,
"enum_variant_2": 32,
}
});
merge(&mut j1, j2);
assert_eq!(
j1,
json!({
"a": {
"name":"xxx",
"value":456,
"enum_variant_1": null,
"enum_variant_2": 32
}
})
);
}
}
+433
View File
@@ -0,0 +1,433 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! This crate includes structs and utilities for defining configuration files (known as chain
//! specification) for both runtime and node.
//!
//! # Intro: Chain Specification
//!
//! The chain specification comprises parameters and settings that define the properties and an
//! initial state of a chain. Users typically interact with the JSON representation of the chain
//! spec. Internally, the chain spec is embodied by the [`GenericChainSpec`] struct, and specific
//! properties can be accessed using the [`ChainSpec`] trait.
//!
//! In summary, although not restricted to, the primary role of the chain spec is to provide a list
//! of well-known boot nodes for the blockchain network and the means for initializing the genesis
//! storage. This initialization is necessary for creating a genesis block upon which subsequent
//! blocks are built. When the node is launched for the first time, it reads the chain spec,
//! initializes the genesis block, and establishes connections with the boot nodes.
//!
//! The JSON chain spec is divided into two main logical sections:
//! - one section details general chain properties,
//! - second explicitly or indirectly defines the genesis storage, which, in turn, determines the
//! genesis hash of the chain,
//!
//! The chain specification consists of the following fields:
//!
//! <table>
//! <thead>
//! <tr>
//! <th>Chain spec key</th>
//! <th>Description</th>
//! </tr>
//! </thead>
//! <tbody>
//! <tr>
//! <td>name</td>
//! <td>The human readable name of the chain.</td>
//! </tr>
//! <tr>
//! <td>id</td>
//! <td>The id of the chain.</td>
//! </tr>
//! <tr>
//! <td>chainType</td>
//! <td>The chain type of this chain
//! (refer to
//! <a href="enum.ChainType.html" title="enum pezsc_chain_spec::ChainType">
//! <code>ChainType</code>
//! </a>).
//! </td>
//! </tr>
//! <tr>
//! <td>bootNodes</td>
//! <td>A list of
//! <a href="https://github.com/multiformats/multiaddr">multi addresses</a>
//! that belong to boot nodes of the chain.</td>
//! </tr>
//! <tr>
//! <td>telemetryEndpoints</td>
//! <td>Optional list of <code>multi address, verbosity</code> of telemetry endpoints. The
//! verbosity goes from 0 to 9. With 0 being the mode with the lowest verbosity.</td>
//! </tr>
//! <tr>
//! <td>protocolId</td>
//! <td>Optional networking protocol id that identifies the chain.</td>
//! </tr>
//! <tr>
//! <td>forkId</td>
//! <td>Optional fork id. Should most likely be left empty. Can be used to signal a fork on
//! the network level when two chains have the same genesis hash.</td>
//! </tr>
//! <tr>
//! <td>properties</td>
//! <td>Custom properties. Shall be provided in the form of
//! <code>key</code>-<code>value</code> json object.
//! </td>
//! </tr>
//! <tr>
//! <td>consensusEngine</td>
//! <td>Deprecated field. Should be ignored.</td>
//! </tr>
//! <tr>
//! <td>codeSubstitutes</td>
//! <td>Optional map of <code>block_number</code> to <code>wasm_code</code>. More details in
//! material to follow.</td>
//! </tr>
//! <tr>
//! <td>genesis</td>
//! <td>Defines the initial state of the runtime. More details in material to follow.</td>
//! </tr>
//! </tbody>
//! </table>
//!
//! # `genesis`: Initial Runtime State
//!
//! All nodes in the network must build subsequent blocks upon exactly the same genesis block.
//!
//! The information configured in the `genesis` section of a chain specification is used to build
//! the genesis storage, which is essential for creating the genesis block, since the block header
//! includes the storage root hash.
//!
//! The `genesis` key of the chain specification definition describes the
//! initial state of the runtime. For example, it may contain:
//! - an initial list of funded accounts,
//! - the administrative account that controls the sudo key,
//! - an initial authorities set for consensus, etc.
//!
//! As the compiled WASM blob of the runtime code is stored in the chain's state, the initial
//! runtime must also be provided within the chain specification.
//!
//! # `chain-spec` formats
//!
//! In essence, the most important formats of genesis initial state in chain specification files
//! are:
//!
//! <table>
//! <thead>
//! <tr>
//! <th>Format</th>
//! <th>Description</th>
//! </tr>
//! </thead>
//! <tbody>
//! <tr>
//! <td>
//! <code>full config</code>
//! </td>
//! <td>A JSON object that provides an explicit and comprehensive representation of the
//! <code>RuntimeGenesisConfig</code> struct, which is generated by <a
//! href="../pezframe_support_procedural/macro.construct_runtime.html"
//! ><code>pezkuwi_sdk_frame::runtime::prelude::construct_runtime</code></a> macro (<a
//! href="../bizinikiwi_test_runtime/struct.RuntimeGenesisConfig.html#"
//! >example of generated struct</a>). Must contain *all* the keys of
//! the genesis config, no defaults will be used.
//!
//! This format explicitly provides the code of the runtime.
//! </td></tr>
//! <tr>
//! <td>
//! <code>patch</code>
//! </td>
//! <td>A JSON object that offers a partial representation of the
//! <code>RuntimeGenesisConfig</code> provided by the runtime. It contains a patch, which is
//! essentially a list of key-value pairs to customize in the default runtime's
//! <code>RuntimeGenesisConfig</code>: `full = default + patch`. Please note that `default`
//! `RuntimeGenesisConfig` may not be functional.
//! This format explicitly provides the code of the runtime.
//! </td></tr>
//! <tr>
//! <td>
//! <code>raw</code>
//! </td>
//! <td>A JSON object with two fields: <code>top</code> and <code>children_default</code>.
//! Each field is a map of <code>key => value</code> pairs representing entries in a genesis storage
//! trie. The runtime code is one of such entries.</td>
//! </tr>
//! </tbody>
//! </table>
//!
//! The main purpose of the `RuntimeGenesisConfig` patch is to:
//! - minimize the maintenance effort when RuntimeGenesisConfig is changed in the future (e.g. new
//! pallets added to the runtime or pallet's genesis config changed),
//! - increase the readability - it only contains the relevant fields,
//! - allow to apply numerous changes in distinct domains (e.g. for zombienet).
//!
//! For production or long-lasting blockchains, using the `raw` format in the chain specification is
//! recommended. Only the `raw` format guarantees that storage root hash will remain unchanged when
//! the `RuntimeGenesisConfig` format changes due to software upgrade.
//!
//! JSON examples in the [following section](#json-chain-specification-example) illustrate the `raw`
//! `patch` and full genesis fields.
//!
//! # From Initial State to Raw Genesis.
//!
//! To generate a raw genesis storage from the JSON representation of the runtime genesis config,
//! the node needs to interact with the runtime.
//!
//! This interaction involves passing the runtime genesis config JSON blob to the runtime using the
//! [`pezsp_genesis_builder::GenesisBuilder::build_state`] function. During this operation, the
//! runtime converts the JSON representation of the genesis config into [`pezsp_io::storage`] items. It
//! is a crucial step for computing the storage root hash, which is a key component in determining
//! the genesis hash.
//!
//! Consequently, the runtime must support the [`pezsp_genesis_builder::GenesisBuilder`] API to
//! utilize either `patch` or `full` formats.
//!
//! This entire process is encapsulated within the implementation of the [`BuildStorage`] trait,
//! which can be accessed through the [`ChainSpec::as_storage_builder`] method. There is an
//! intermediate internal helper that facilitates this interaction,
//! [`GenesisConfigBuilderRuntimeCaller`], which serves as a straightforward wrapper for
//! [`pezsc_executor::WasmExecutor`].
//!
//! In case of `raw` genesis state the node does not interact with the runtime regarding the
//! computation of initial state.
//!
//! The plain and `raw` chain specification JSON blobs can be found in
//! [JSON examples](#json-chain-specification-example) section.
//!
//! # Optional Code Mapping
//!
//! Optional map of `block_number` to `wasm_code`.
//!
//! The given `wasm_code` will be used to substitute the on-chain wasm code starting with the
//! given block number until the `spec_version` on-chain changes. The given `wasm_code` should
//! be as close as possible to the on-chain wasm code. A substitute should be used to fix a bug
//! that cannot be fixed with a runtime upgrade, if for example the runtime is constantly
//! panicking. Introducing new runtime APIs isn't supported, because the node
//! will read the runtime version from the on-chain wasm code.
//!
//! Use this functionality only when there is no other way around it, and only patch the problematic
//! bug; the rest should be done with an on-chain runtime upgrade.
//!
//! # Building a Chain Specification
//!
//! The [`ChainSpecBuilder`] should be used to create an instance of a chain specification. Its API
//! allows configuration of all fields of the chain spec. To generate a JSON representation of the
//! specification, use [`ChainSpec::as_json`].
//!
//! The sample code to generate a chain spec is as follows:
#![doc = docify::embed!("src/chain_spec.rs", build_chain_spec_with_patch_works)]
//! # JSON chain specification example
//!
//! The following are the plain and `raw` versions of the chain specification JSON files, resulting
//! from executing of the above [example](#building-a-chain-specification):
//! ```ignore
#![doc = include_str!("../res/bizinikiwi_test_runtime_from_patch.json")]
//! ```
//! ```ignore
#![doc = include_str!("../res/bizinikiwi_test_runtime_from_patch_raw.json")]
//! ```
//! The following example shows the plain full config version of chain spec:
//! ```ignore
#![doc = include_str!("../res/bizinikiwi_test_runtime_from_config.json")]
//! ```
//! The [`ChainSpec`] trait represents the API to access values defined in the JSON chain specification.
//!
//!
//! # Custom Chain Spec Extensions
//!
//! The basic chain spec type containing all required parameters is [`GenericChainSpec`]. It can be
//! extended with additional options containing configuration specific to your chain. Usually, the
//! extension will be a combination of types exposed by Bizinikiwi core modules.
//!
//! To allow the core modules to retrieve their configuration from your extension, you should use
//! `ChainSpecExtension` macro exposed by this crate.
//! ```rust
//! use std::collections::HashMap;
//! use pezsc_chain_spec::{GenericChainSpec, ChainSpecExtension};
//!
//! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecExtension)]
//! pub struct MyExtension {
//! pub known_blocks: HashMap<u64, String>,
//! }
//!
//! pub type MyChainSpec = GenericChainSpec<MyExtension>;
//! ```
//! Some parameters may require different values depending on the current blockchain height (a.k.a.
//! forks). You can use the [`ChainSpecGroup`](macro@ChainSpecGroup) macro and the provided [`Forks`]
//! structure to add such parameters to your chain spec. This will allow overriding a single
//! parameter starting at a specific block number.
//! ```rust
//! use pezsc_chain_spec::{Forks, ChainSpecGroup, ChainSpecExtension, GenericChainSpec};
//!
//! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)]
//! pub struct ClientParams {
//! max_block_size: usize,
//! max_extrinsic_size: usize,
//! }
//!
//! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup)]
//! pub struct PoolParams {
//! max_transaction_size: usize,
//! }
//!
//! #[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ChainSpecGroup, ChainSpecExtension)]
//! pub struct Extension {
//! pub client: ClientParams,
//! pub pool: PoolParams,
//! }
//!
//! pub type BlockNumber = u64;
//!
//! /// A chain spec supporting forkable `ClientParams`.
//! pub type MyChainSpec1 = GenericChainSpec<Forks<BlockNumber, ClientParams>>;
//!
//! /// A chain spec supporting forkable `Extension`.
//! pub type MyChainSpec2 = GenericChainSpec<Forks<BlockNumber, Extension>>;
//! ```
//! It's also possible to have a set of parameters that are allowed to change with block numbers
//! (i.e., they are forkable), and another set that is not subject to changes. This can also be
//! achieved by declaring an extension that contains [`Forks`] within it.
//! ```rust
//! use serde::{Serialize, Deserialize};
//! use pezsc_chain_spec::{Forks, GenericChainSpec, ChainSpecGroup, ChainSpecExtension};
//!
//! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)]
//! pub struct ClientParams {
//! max_block_size: usize,
//! max_extrinsic_size: usize,
//! }
//!
//! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecGroup)]
//! pub struct PoolParams {
//! max_transaction_size: usize,
//! }
//!
//! #[derive(Clone, Debug, Serialize, Deserialize, ChainSpecExtension)]
//! pub struct Extension {
//! pub client: ClientParams,
//! #[forks]
//! pub pool: Forks<u64, PoolParams>,
//! }
//!
//! pub type MyChainSpec = GenericChainSpec<Extension>;
//! ```
//! The chain spec can be extended with other fields that are opaque to the default chain spec.
//! Specific node implementations will need to be able to deserialize these extensions.
mod chain_spec;
mod extension;
mod genesis_block;
mod genesis_config_builder;
pub mod json_patch;
pub use self::{
chain_spec::{
set_code_substitute_in_json_chain_spec, update_code_in_json_chain_spec,
ChainSpec as GenericChainSpec, ChainSpecBuilder, NoExtension,
},
extension::{get_extension, get_extension_mut, Extension, Fork, Forks, GetExtension, Group},
genesis_block::{
construct_genesis_block, resolve_state_version_from_wasm, BuildGenesisBlock,
GenesisBlockBuilder,
},
genesis_config_builder::{
GenesisConfigBuilderRuntimeCaller, DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET,
},
json_patch::merge as json_merge,
};
pub use pezsc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup};
use pezsc_network::config::MultiaddrWithPeerId;
use pezsc_telemetry::TelemetryEndpoints;
use pezsp_core::storage::Storage;
use pezsp_runtime::BuildStorage;
/// The type of chain.
///
/// This can be used by tools to determine the type of chain for displaying
/// additional information or enabling additional features.
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq, Clone)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
pub enum ChainType {
/// A development chain that runs mainly on one node.
Development,
/// A local chain that runs locally on multiple nodes for testing purposes.
Local,
/// A live chain.
Live,
/// Some custom chain type.
#[cfg_attr(feature = "clap", clap(skip))]
Custom(String),
}
impl Default for ChainType {
fn default() -> Self {
Self::Live
}
}
/// Arbitrary properties defined in chain spec as a JSON object
pub type Properties = serde_json::map::Map<String, serde_json::Value>;
/// Common interface of a chain specification.
pub trait ChainSpec: BuildStorage + Send + Sync {
/// Spec name.
fn name(&self) -> &str;
/// Spec id.
fn id(&self) -> &str;
/// Type of the chain.
fn chain_type(&self) -> ChainType;
/// A list of bootnode addresses.
fn boot_nodes(&self) -> &[MultiaddrWithPeerId];
/// Telemetry endpoints (if any)
fn telemetry_endpoints(&self) -> &Option<TelemetryEndpoints>;
/// Network protocol id.
fn protocol_id(&self) -> Option<&str>;
/// Optional network fork identifier. `None` by default.
fn fork_id(&self) -> Option<&str>;
/// Additional loosely-typed properties of the chain.
///
/// Returns an empty JSON object if 'properties' not defined in config
fn properties(&self) -> Properties;
/// Returns a reference to the defined chain spec extensions.
fn extensions(&self) -> &dyn GetExtension;
/// Returns a mutable reference to the defined chain spec extensions.
fn extensions_mut(&mut self) -> &mut dyn GetExtension;
/// Add a bootnode to the list.
fn add_boot_node(&mut self, addr: MultiaddrWithPeerId);
/// Return spec as JSON.
fn as_json(&self, raw: bool) -> Result<String, String>;
/// Return StorageBuilder for this spec.
fn as_storage_builder(&self) -> &dyn BuildStorage;
/// Returns a cloned `Box<dyn ChainSpec>`.
fn cloned_box(&self) -> Box<dyn ChainSpec>;
/// Set the storage that should be used by this chain spec.
///
/// This will be used as storage at genesis.
fn set_storage(&mut self, storage: Storage);
/// Returns code substitutes that should be used for the on chain wasm.
fn code_substitutes(&self) -> std::collections::BTreeMap<String, Vec<u8>>;
}
impl std::fmt::Debug for dyn ChainSpec {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "ChainSpec(name = {:?}, id = {:?})", self.name(), self.id())
}
}
+79
View File
@@ -0,0 +1,79 @@
[package]
name = "pezsc-cli"
version = "0.36.0"
authors.workspace = true
description = "Bizinikiwi CLI interface."
edition.workspace = true
license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
homepage.workspace = true
repository.workspace = true
readme = "README.md"
[lints]
workspace = true
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
array-bytes = { workspace = true, default-features = true }
bip39 = { workspace = true, default-features = true, features = ["rand"] }
chrono = { workspace = true }
clap = { features = ["derive", "string", "wrap_help"], workspace = true }
codec = { workspace = true, default-features = true }
fdlimit = { workspace = true }
futures = { workspace = true }
itertools = { workspace = true }
libp2p-identity = { features = ["ed25519", "peerid"], workspace = true }
log = { workspace = true, default-features = true }
names = { workspace = true }
rand = { workspace = true, default-features = true }
regex = { workspace = true }
rpassword = { workspace = true }
pezsc-client-api = { workspace = true, default-features = true }
pezsc-client-db = { workspace = true, default-features = false }
pezsc-keystore = { workspace = true, default-features = true }
pezsc-mixnet = { workspace = true, default-features = true }
pezsc-network = { workspace = true, default-features = true }
pezsc-service = { workspace = true, default-features = false }
pezsc-telemetry = { workspace = true, default-features = true }
pezsc-tracing = { workspace = true, default-features = true }
pezsc-transaction-pool = { workspace = true, default-features = true }
pezsc-utils = { workspace = true, default-features = true }
serde = { workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
pezsp-blockchain = { workspace = true, default-features = true }
pezsp-core = { workspace = true, default-features = true }
pezsp-keyring = { workspace = true, default-features = true }
pezsp-keystore = { workspace = true, default-features = true }
pezsp-panic-handler = { workspace = true, default-features = true }
pezsp-runtime = { workspace = true, default-features = true }
pezsp-version = { workspace = true, default-features = true }
thiserror = { workspace = true }
tokio = { features = [
"parking_lot",
"rt-multi-thread",
"signal",
], workspace = true, default-features = true }
[dev-dependencies]
futures-timer = { workspace = true }
pezsp-tracing = { workspace = true, default-features = true }
tempfile = { workspace = true }
[features]
default = ["rocksdb"]
rocksdb = ["pezsc-client-db/rocksdb"]
runtime-benchmarks = [
"pezsc-client-api/runtime-benchmarks",
"pezsc-client-db/runtime-benchmarks",
"pezsc-mixnet/runtime-benchmarks",
"pezsc-network/runtime-benchmarks",
"pezsc-service/runtime-benchmarks",
"pezsc-tracing/runtime-benchmarks",
"pezsc-transaction-pool/runtime-benchmarks",
"pezsp-blockchain/runtime-benchmarks",
"pezsp-keyring/runtime-benchmarks",
"pezsp-runtime/runtime-benchmarks",
"pezsp-version/runtime-benchmarks",
]
+3
View File
@@ -0,0 +1,3 @@
Bizinikiwi CLI library.
License: GPL-3.0-or-later WITH Classpath-exception-2.0
+331
View File
@@ -0,0 +1,331 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Definitions of [`ValueEnum`] types.
use clap::ValueEnum;
use std::str::FromStr;
/// The instantiation strategy to use in compiled mode.
#[derive(Debug, Clone, Copy, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum WasmtimeInstantiationStrategy {
/// Pool the instances to avoid initializing everything from scratch
/// on each instantiation. Use copy-on-write memory when possible.
PoolingCopyOnWrite,
/// Recreate the instance from scratch on every instantiation.
/// Use copy-on-write memory when possible.
RecreateInstanceCopyOnWrite,
/// Pool the instances to avoid initializing everything from scratch
/// on each instantiation.
Pooling,
/// Recreate the instance from scratch on every instantiation. Very slow.
RecreateInstance,
}
/// The default [`WasmtimeInstantiationStrategy`].
pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy =
WasmtimeInstantiationStrategy::PoolingCopyOnWrite;
/// How to execute Wasm runtime code.
#[derive(Debug, Clone, Copy, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum WasmExecutionMethod {
/// Uses an interpreter which now is deprecated.
#[clap(name = "interpreted-i-know-what-i-do")]
Interpreted,
/// Uses a compiled runtime.
Compiled,
}
impl std::fmt::Display for WasmExecutionMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Interpreted => write!(f, "Interpreted"),
Self::Compiled => write!(f, "Compiled"),
}
}
}
/// Converts the execution method and instantiation strategy command line arguments
/// into an execution method which can be used internally.
pub fn execution_method_from_cli(
execution_method: WasmExecutionMethod,
instantiation_strategy: WasmtimeInstantiationStrategy,
) -> pezsc_service::config::WasmExecutionMethod {
if let WasmExecutionMethod::Interpreted = execution_method {
log::warn!(
"`interpreted-i-know-what-i-do` is deprecated and will be removed in the future. Defaults to `compiled` execution mode."
);
}
pezsc_service::config::WasmExecutionMethod::Compiled {
instantiation_strategy: match instantiation_strategy {
WasmtimeInstantiationStrategy::PoolingCopyOnWrite =>
pezsc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite,
WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite =>
pezsc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite,
WasmtimeInstantiationStrategy::Pooling =>
pezsc_service::config::WasmtimeInstantiationStrategy::Pooling,
WasmtimeInstantiationStrategy::RecreateInstance =>
pezsc_service::config::WasmtimeInstantiationStrategy::RecreateInstance,
},
}
}
/// The default [`WasmExecutionMethod`].
pub const DEFAULT_WASM_EXECUTION_METHOD: WasmExecutionMethod = WasmExecutionMethod::Compiled;
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum TracingReceiver {
/// Output the tracing records using the log.
Log,
}
impl Into<pezsc_tracing::TracingReceiver> for TracingReceiver {
fn into(self) -> pezsc_tracing::TracingReceiver {
match self {
TracingReceiver::Log => pezsc_tracing::TracingReceiver::Log,
}
}
}
/// The type of the node key.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum NodeKeyType {
/// Use ed25519.
Ed25519,
}
/// The crypto scheme to use.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum CryptoScheme {
/// Use ed25519.
Ed25519,
/// Use sr25519.
Sr25519,
/// Use ecdsa.
Ecdsa,
}
/// The type of the output format.
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum OutputType {
/// Output as json.
Json,
/// Output as text.
Text,
}
/// How to execute blocks
#[derive(Debug, Copy, Clone, PartialEq, Eq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum ExecutionStrategy {
/// Execute with native build (if available, WebAssembly otherwise).
Native,
/// Only execute with the WebAssembly build.
Wasm,
/// Execute with both native (where available) and WebAssembly builds.
Both,
/// Execute with the native build if possible; if it fails, then execute with WebAssembly.
NativeElseWasm,
}
/// Available RPC methods.
#[allow(missing_docs)]
#[derive(Debug, Copy, Clone, PartialEq, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum RpcMethods {
/// Expose every RPC method only when RPC is listening on `localhost`,
/// otherwise serve only safe RPC methods.
Auto,
/// Allow only a safe subset of RPC methods.
Safe,
/// Expose every RPC method (even potentially unsafe ones).
Unsafe,
}
impl FromStr for RpcMethods {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"safe" => Ok(RpcMethods::Safe),
"unsafe" => Ok(RpcMethods::Unsafe),
"auto" => Ok(RpcMethods::Auto),
invalid => Err(format!("Invalid rpc methods {invalid}")),
}
}
}
impl Into<pezsc_service::config::RpcMethods> for RpcMethods {
fn into(self) -> pezsc_service::config::RpcMethods {
match self {
RpcMethods::Auto => pezsc_service::config::RpcMethods::Auto,
RpcMethods::Safe => pezsc_service::config::RpcMethods::Safe,
RpcMethods::Unsafe => pezsc_service::config::RpcMethods::Unsafe,
}
}
}
/// CORS setting
///
/// The type is introduced to overcome `Option<Option<T>>` handling of `clap`.
#[derive(Clone, Debug)]
pub enum Cors {
/// All hosts allowed.
All,
/// Only hosts on the list are allowed.
List(Vec<String>),
}
impl From<Cors> for Option<Vec<String>> {
fn from(cors: Cors) -> Self {
match cors {
Cors::All => None,
Cors::List(list) => Some(list),
}
}
}
impl FromStr for Cors {
type Err = crate::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut is_all = false;
let mut origins = Vec::new();
for part in s.split(',') {
match part {
"all" | "*" => {
is_all = true;
break;
},
other => origins.push(other.to_owned()),
}
}
if is_all {
Ok(Cors::All)
} else {
Ok(Cors::List(origins))
}
}
}
/// Database backend
#[derive(Debug, Clone, PartialEq, Copy, clap::ValueEnum)]
#[value(rename_all = "lower")]
pub enum Database {
/// Facebooks RocksDB
#[cfg(feature = "rocksdb")]
RocksDb,
/// ParityDb. <https://github.com/paritytech/parity-db/>
ParityDb,
/// Detect whether there is an existing database. Use it, if there is, if not, create new
/// instance of ParityDb
Auto,
/// ParityDb. <https://github.com/paritytech/parity-db/>
#[value(name = "paritydb-experimental")]
ParityDbDeprecated,
}
impl Database {
/// Returns all the variants of this enum to be shown in the cli.
pub const fn variants() -> &'static [&'static str] {
&[
#[cfg(feature = "rocksdb")]
"rocksdb",
"paritydb",
"paritydb-experimental",
"auto",
]
}
}
/// Whether off-chain workers are enabled.
#[allow(missing_docs)]
#[derive(Debug, Clone, ValueEnum)]
#[value(rename_all = "kebab-case")]
pub enum OffchainWorkerEnabled {
/// Always have offchain worker enabled.
Always,
/// Never enable the offchain worker.
Never,
/// Only enable the offchain worker when running as a validator (or collator, if this is a
/// teyrchain node).
WhenAuthority,
}
/// Syncing mode.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
#[value(rename_all = "kebab-case")]
pub enum SyncMode {
/// Full sync. Download and verify all blocks.
Full,
/// Download blocks without executing them. Download latest state with proofs.
Fast,
/// Download blocks without executing them. Download latest state without proofs.
FastUnsafe,
/// Prove finality and download the latest state.
Warp,
}
impl Into<pezsc_network::config::SyncMode> for SyncMode {
fn into(self) -> pezsc_network::config::SyncMode {
match self {
SyncMode::Full => pezsc_network::config::SyncMode::Full,
SyncMode::Fast => pezsc_network::config::SyncMode::LightState {
skip_proofs: false,
storage_chain_mode: false,
},
SyncMode::FastUnsafe => pezsc_network::config::SyncMode::LightState {
skip_proofs: true,
storage_chain_mode: false,
},
SyncMode::Warp => pezsc_network::config::SyncMode::Warp,
}
}
}
/// Network backend type.
#[derive(Debug, Clone, Copy, ValueEnum, PartialEq)]
#[value(rename_all = "lower")]
pub enum NetworkBackendType {
/// Use libp2p for P2P networking.
Libp2p,
/// Use litep2p for P2P networking.
Litep2p,
}
impl Into<pezsc_network::config::NetworkBackendType> for NetworkBackendType {
fn into(self) -> pezsc_network::config::NetworkBackendType {
match self {
Self::Libp2p => pezsc_network::config::NetworkBackendType::Libp2p,
Self::Litep2p => pezsc_network::config::NetworkBackendType::Litep2p,
}
}
}
@@ -0,0 +1,91 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{NodeKeyParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use log::info;
use pezsc_network::config::build_multiaddr;
use pezsc_service::{
config::{MultiaddrWithPeerId, NetworkConfiguration},
ChainSpec,
};
use std::io::Write;
/// The `build-spec` command used to build a specification.
#[derive(Debug, Clone, Parser)]
pub struct BuildSpecCmd {
/// Force raw genesis storage output.
#[arg(long)]
pub raw: bool,
/// Disable adding the default bootnode to the specification.
/// By default the `/ip4/127.0.0.1/tcp/30333/p2p/NODE_PEER_ID` bootnode is added to the
/// specification when no bootnode exists.
#[arg(long)]
pub disable_default_bootnode: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub node_key_params: NodeKeyParams,
}
impl BuildSpecCmd {
/// Run the build-spec command
pub fn run(
&self,
mut spec: Box<dyn ChainSpec>,
network_config: NetworkConfiguration,
) -> error::Result<()> {
info!("Building chain spec");
let raw_output = self.raw;
if spec.boot_nodes().is_empty() && !self.disable_default_bootnode {
let keys = network_config.node_key.into_keypair()?;
let peer_id = keys.public().to_peer_id();
let addr = MultiaddrWithPeerId {
multiaddr: build_multiaddr![Ip4([127, 0, 0, 1]), Tcp(30333u16)],
peer_id: peer_id.into(),
};
spec.add_boot_node(addr)
}
let json = pezsc_service::chain_ops::build_spec(&*spec, raw_output)?;
if std::io::stdout().write_all(json.as_bytes()).is_err() {
let _ = std::io::stderr().write_all(b"Error writing to stdout\n");
}
Ok(())
}
}
impl CliConfiguration for BuildSpecCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn node_key_params(&self) -> Option<&NodeKeyParams> {
Some(&self.node_key_params)
}
}
@@ -0,0 +1,103 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{CliConfiguration, DatabaseParams, PruningParams, Result as CliResult, SharedParams};
use codec::{Decode, Encode};
use pezsc_client_api::{backend::Backend as BackendT, blockchain::HeaderBackend};
use pezsp_blockchain::Info;
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, io};
/// The `chain-info` subcommand used to output db meta columns information.
#[derive(Debug, Clone, clap::Parser)]
pub struct ChainInfoCmd {
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
/// Serializable `chain-info` subcommand output.
#[derive(Clone, Eq, PartialEq, Debug, Encode, Decode, serde::Serialize)]
struct ChainInfo<B: BlockT> {
/// Best block hash.
best_hash: B::Hash,
/// Best block number.
best_number: <<B as BlockT>::Header as HeaderT>::Number,
/// Genesis block hash.
genesis_hash: B::Hash,
/// The head of the finalized chain.
finalized_hash: B::Hash,
/// Last finalized block number.
finalized_number: <<B as BlockT>::Header as HeaderT>::Number,
}
impl<B: BlockT> From<Info<B>> for ChainInfo<B> {
fn from(info: Info<B>) -> Self {
ChainInfo::<B> {
best_hash: info.best_hash,
best_number: info.best_number,
genesis_hash: info.genesis_hash,
finalized_hash: info.finalized_hash,
finalized_number: info.finalized_number,
}
}
}
impl ChainInfoCmd {
/// Run the `chain-info` subcommand
pub fn run<B>(&self, config: &pezsc_service::Configuration) -> CliResult<()>
where
B: BlockT,
{
let db_config = pezsc_client_db::DatabaseSettings {
trie_cache_maximum_size: config.trie_cache_maximum_size,
state_pruning: config.state_pruning.clone(),
source: config.database.clone(),
blocks_pruning: config.blocks_pruning,
metrics_registry: None,
};
let backend = pezsc_service::new_db_backend::<B>(db_config)?;
let info: ChainInfo<B> = backend.blockchain().info().into();
let mut out = io::stdout();
serde_json::to_writer_pretty(&mut out, &info)
.map_err(|e| format!("Error writing JSON: {}", e))?;
Ok(())
}
}
impl CliConfiguration for ChainInfoCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,76 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{BlockNumberOrHash, ImportParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use pezsc_client_api::{BlockBackend, HeaderBackend};
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, str::FromStr, sync::Arc};
/// The `check-block` command used to validate blocks.
#[derive(Debug, Clone, Parser)]
pub struct CheckBlockCmd {
/// Block hash or number.
#[arg(value_name = "HASH or NUMBER")]
pub input: BlockNumberOrHash,
/// The default number of 64KB pages to ever allocate for Wasm execution.
/// Don't alter this unless you know what you're doing.
#[arg(long, value_name = "COUNT")]
pub default_heap_pages: Option<u32>,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub import_params: ImportParams,
}
impl CheckBlockCmd {
/// Run the check-block command
pub async fn run<B, C, IQ>(&self, client: Arc<C>, import_queue: IQ) -> error::Result<()>
where
B: BlockT + for<'de> serde::Deserialize<'de>,
C: BlockBackend<B> + HeaderBackend<B> + Send + Sync + 'static,
IQ: pezsc_service::ImportQueue<B> + 'static,
<B::Hash as FromStr>::Err: Debug,
<<B::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
let start = std::time::Instant::now();
pezsc_service::chain_ops::check_block(client, import_queue, self.input.parse()?).await?;
println!("Completed in {} ms.", start.elapsed().as_millis());
Ok(())
}
}
impl CliConfiguration for CheckBlockCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
}
@@ -0,0 +1,107 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{DatabaseParams, GenericNumber, PruningParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use log::info;
use pezsc_client_api::{BlockBackend, HeaderBackend, UsageProvider};
use pezsc_service::{chain_ops::export_blocks, config::DatabaseSource};
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, fs, io, path::PathBuf, str::FromStr, sync::Arc};
/// The `export-blocks` command used to export blocks.
#[derive(Debug, Clone, Parser)]
pub struct ExportBlocksCmd {
/// Output file name or stdout if unspecified.
#[arg()]
pub output: Option<PathBuf>,
/// Specify starting block number.
/// Default is 1.
#[arg(long, value_name = "BLOCK")]
pub from: Option<GenericNumber>,
/// Specify last block number.
/// Default is best block.
#[arg(long, value_name = "BLOCK")]
pub to: Option<GenericNumber>,
/// Use binary output rather than JSON.
#[arg(long)]
pub binary: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
impl ExportBlocksCmd {
/// Run the export-blocks command
pub async fn run<B, C>(
&self,
client: Arc<C>,
database_config: DatabaseSource,
) -> error::Result<()>
where
B: BlockT,
C: HeaderBackend<B> + BlockBackend<B> + UsageProvider<B> + 'static,
<<B::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
if let Some(path) = database_config.path() {
info!("DB path: {}", path.display());
}
let from = self.from.as_ref().and_then(|f| f.parse().ok()).unwrap_or(1u32);
let to = self.to.as_ref().and_then(|t| t.parse().ok());
let binary = self.binary;
let file: Box<dyn io::Write> = match &self.output {
Some(filename) => Box::new(fs::File::create(filename)?),
None => Box::new(io::stdout()),
};
export_blocks(client, file, from.into(), to, binary).await.map_err(Into::into)
}
}
impl CliConfiguration for ExportBlocksCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,68 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error::Result;
use clap::Parser;
use pezsc_service::{chain_ops, ChainSpec};
use std::{
fs,
io::{self, Write},
path::PathBuf,
};
/// Export a chain-spec to a JSON file in plain or in raw storage format.
///
/// Nodes that expose this command usually have embedded runtimes WASM blobs with
/// genesis config presets which can be referenced via `--chain <id>` . The logic for
/// loading the chain spec into memory based on an `id` is specific to each
/// node and is a prerequisite to enable this command.
///
/// Same functionality can be achieved currently via
/// [`crate::commands::build_spec_cmd::BuildSpecCmd`] but we recommend
/// `export-chain-spec` in its stead. `build-spec` is known
/// to be a legacy mix of exporting chain specs to JSON files or
/// converting them to raw, which will be better
/// represented under `export-chain-spec`.
#[derive(Debug, Clone, Parser)]
pub struct ExportChainSpecCmd {
/// The chain spec identifier to export.
#[arg(long, default_value = "local")]
pub chain: String,
/// `chain-spec` JSON file path. If omitted, prints to stdout.
#[arg(long)]
pub output: Option<PathBuf>,
/// Export in raw genesis storage format.
#[arg(long)]
pub raw: bool,
}
impl ExportChainSpecCmd {
/// Run the export-chain-spec command
pub fn run(&self, spec: Box<dyn ChainSpec>) -> Result<()> {
let json = chain_ops::build_spec(spec.as_ref(), self.raw)?;
if let Some(ref path) = self.output {
fs::write(path, json)?;
println!("Exported chain spec to {}", path.display());
} else {
io::stdout().write_all(json.as_bytes()).map_err(|e| format!("{}", e))?;
}
Ok(())
}
}
@@ -0,0 +1,95 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{BlockNumberOrHash, DatabaseParams, PruningParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use log::info;
use pezsc_client_api::{HeaderBackend, StorageProvider, UsageProvider};
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT};
use std::{fmt::Debug, io::Write, str::FromStr, sync::Arc};
/// The `export-state` command used to export the state of a given block into
/// a chain spec.
#[derive(Debug, Clone, Parser)]
pub struct ExportStateCmd {
/// Block hash or number.
#[arg(value_name = "HASH or NUMBER")]
pub input: Option<BlockNumberOrHash>,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
impl ExportStateCmd {
/// Run the `export-state` command
pub async fn run<B, BA, C>(
&self,
client: Arc<C>,
mut input_spec: Box<dyn pezsc_service::ChainSpec>,
) -> error::Result<()>
where
B: BlockT,
C: UsageProvider<B> + StorageProvider<B, BA> + HeaderBackend<B>,
BA: pezsc_client_api::backend::Backend<B>,
<B::Hash as FromStr>::Err: Debug,
<<B::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
info!("Exporting raw state...");
let block_id = self.input.as_ref().map(|b| b.parse()).transpose()?;
let hash = match block_id {
Some(id) => client.expect_block_hash_from_id(&id)?,
None => client.usage_info().chain.best_hash,
};
let raw_state = pezsc_service::chain_ops::export_raw_state(client, hash)?;
input_spec.set_storage(raw_state);
info!("Generating new chain spec...");
let json = pezsc_service::chain_ops::build_spec(&*input_spec, true)?;
if std::io::stdout().write_all(json.as_bytes()).is_err() {
let _ = std::io::stderr().write_all(b"Error writing to stdout\n");
}
Ok(())
}
}
impl CliConfiguration for ExportStateCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,87 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `generate` subcommand
use crate::{
utils::print_from_uri, with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams,
NetworkSchemeFlag, OutputTypeFlag,
};
use bip39::Mnemonic;
use clap::Parser;
use itertools::Itertools;
/// The `generate` command
#[derive(Debug, Clone, Parser)]
#[command(name = "generate", about = "Generate a random account")]
pub struct GenerateCmd {
/// The number of words in the phrase to generate. One of 12 (default), 15, 18, 21 and 24.
#[arg(short = 'w', long, value_name = "WORDS")]
words: Option<usize>,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl GenerateCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let words = match self.words {
Some(words_count) if [12, 15, 18, 21, 24].contains(&words_count) => Ok(words_count),
Some(_) => Err(Error::Input(
"Invalid number of words given for phrase: must be 12/15/18/21/24".into(),
)),
None => Ok(12),
}?;
let mnemonic = Mnemonic::generate(words)
.map_err(|e| Error::Input(format!("Mnemonic generation failed: {e}").into()))?;
let password = self.keystore_params.read_password()?;
let output = self.output_scheme.output_type;
let phrase = mnemonic.words().join(" ");
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(&phrase, password, self.network_scheme.network, output)
);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn generate() {
let generate = GenerateCmd::parse_from(&["generate", "--password", "12345"]);
assert!(generate.run().is_ok())
}
}
@@ -0,0 +1,187 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `generate-node-key` subcommand
use crate::{build_network_key_dir_or_default, Error, NODE_KEY_ED25519_FILE};
use clap::{Args, Parser};
use libp2p_identity::{ed25519, Keypair};
use pezsc_service::BasePath;
use std::{
fs,
io::{self, Write},
path::PathBuf,
};
/// Common arguments accross all generate key commands, subkey and node.
#[derive(Debug, Args, Clone)]
pub struct GenerateKeyCmdCommon {
/// Name of file to save secret key to.
/// If not given, the secret key is printed to stdout.
#[arg(long)]
file: Option<PathBuf>,
/// The output is in raw binary format.
/// If not given, the output is written as an hex encoded string.
#[arg(long)]
bin: bool,
}
/// The `generate-node-key` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "generate-node-key",
about = "Generate a random node key, write it to a file or stdout \
and write the corresponding peer-id to stderr"
)]
pub struct GenerateNodeKeyCmd {
#[clap(flatten)]
pub common: GenerateKeyCmdCommon,
/// Specify the chain specification.
///
/// It can be any of the predefined chains like dev, local, staging, pezkuwi, kusama.
#[arg(long, value_name = "CHAIN_SPEC")]
pub chain: Option<String>,
/// A directory where the key should be saved. If a key already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["file", "default_base_path"])]
base_path: Option<PathBuf>,
/// Save the key in the default directory. If a key already
/// exists in the directory, it won't be overwritten.
#[arg(long, conflicts_with_all = ["base_path", "file"])]
default_base_path: bool,
}
impl GenerateKeyCmdCommon {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
generate_key(&self.file, self.bin, None, &None, false, None)
}
}
impl GenerateNodeKeyCmd {
/// Run the command
pub fn run(&self, chain_spec_id: &str, executable_name: &String) -> Result<(), Error> {
generate_key(
&self.common.file,
self.common.bin,
Some(chain_spec_id),
&self.base_path,
self.default_base_path,
Some(executable_name),
)
}
}
// Utility function for generating a key based on the provided CLI arguments
//
// `file` - Name of file to save secret key to
// `bin`
fn generate_key(
file: &Option<PathBuf>,
bin: bool,
chain_spec_id: Option<&str>,
base_path: &Option<PathBuf>,
default_base_path: bool,
executable_name: Option<&String>,
) -> Result<(), Error> {
let keypair = ed25519::Keypair::generate();
let secret = keypair.secret();
let file_data = if bin {
secret.as_ref().to_owned()
} else {
array_bytes::bytes2hex("", secret).into_bytes()
};
match (file, base_path, default_base_path) {
(Some(file), None, false) => fs::write(file, file_data)?,
(None, Some(_), false) | (None, None, true) => {
let network_path = build_network_key_dir_or_default(
base_path.clone().map(BasePath::new),
chain_spec_id.unwrap_or_default(),
executable_name.ok_or(Error::Input("Executable name not provided".into()))?,
);
fs::create_dir_all(network_path.as_path())?;
let key_path = network_path.join(NODE_KEY_ED25519_FILE);
if key_path.exists() {
eprintln!("Skip generation, a key already exists in {:?}", key_path);
return Err(Error::KeyAlreadyExistsInPath(key_path));
} else {
eprintln!("Generating key in {:?}", key_path);
fs::write(key_path, file_data)?
}
},
(None, None, false) => io::stdout().lock().write_all(&file_data)?,
(_, _, _) => {
// This should not happen, arguments are marked as mutually exclusive.
return Err(Error::Input("Mutually exclusive arguments provided".into()));
},
}
eprintln!("{}", Keypair::from(keypair).public().to_peer_id());
Ok(())
}
#[cfg(test)]
pub mod tests {
use crate::DEFAULT_NETWORK_CONFIG_PATH;
use super::*;
use std::io::Read;
use tempfile::Builder;
#[test]
fn generate_node_key() {
let mut file = Builder::new().prefix("keyfile").tempfile().unwrap();
let file_path = file.path().display().to_string();
let generate = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", &file_path]);
assert!(generate.run("test", &String::from("test")).is_ok());
let mut buf = String::new();
assert!(file.read_to_string(&mut buf).is_ok());
assert!(array_bytes::hex2bytes(&buf).is_ok());
}
#[test]
fn generate_node_key_base_path() {
let base_dir = Builder::new().prefix("keyfile").tempdir().unwrap();
let key_path = base_dir
.path()
.join("chains/test_id/")
.join(DEFAULT_NETWORK_CONFIG_PATH)
.join(NODE_KEY_ED25519_FILE);
let base_path = base_dir.path().display().to_string();
let generate =
GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--base-path", &base_path]);
assert!(generate.run("test_id", &String::from("test")).is_ok());
let buf = fs::read_to_string(key_path.as_path()).unwrap();
assert!(array_bytes::hex2bytes(&buf).is_ok());
assert!(generate.run("test_id", &String::from("test")).is_err());
let new_buf = fs::read_to_string(key_path).unwrap();
assert_eq!(
array_bytes::hex2bytes(&new_buf).unwrap(),
array_bytes::hex2bytes(&buf).unwrap()
);
}
}
@@ -0,0 +1,88 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{ImportParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use pezsc_client_api::HeaderBackend;
use pezsc_service::chain_ops::import_blocks;
use pezsp_runtime::traits::Block as BlockT;
use std::{
fmt::Debug,
fs,
io::{self, Read},
path::PathBuf,
sync::Arc,
};
/// The `import-blocks` command used to import blocks.
#[derive(Debug, Parser)]
pub struct ImportBlocksCmd {
/// Input file or stdin if unspecified.
#[arg()]
pub input: Option<PathBuf>,
/// The default number of 64KB pages to ever allocate for Wasm execution.
/// Don't alter this unless you know what you're doing.
#[arg(long, value_name = "COUNT")]
pub default_heap_pages: Option<u32>,
/// Try importing blocks from binary format rather than JSON.
#[arg(long)]
pub binary: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub import_params: ImportParams,
}
impl ImportBlocksCmd {
/// Run the import-blocks command
pub async fn run<B, C, IQ>(&self, client: Arc<C>, import_queue: IQ) -> error::Result<()>
where
C: HeaderBackend<B> + Send + Sync + 'static,
B: BlockT + for<'de> serde::Deserialize<'de>,
IQ: pezsc_service::ImportQueue<B> + 'static,
{
let file: Box<dyn Read + Send> = match &self.input {
Some(filename) => Box::new(fs::File::open(filename)?),
None => Box::new(io::stdin()),
};
import_blocks(client, import_queue, file, false, self.binary)
.await
.map_err(Into::into)
}
}
impl CliConfiguration for ImportBlocksCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
}
@@ -0,0 +1,166 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `insert` subcommand
use crate::{
utils, with_crypto_scheme, CryptoScheme, Error, KeystoreParams, SharedParams, BizinikiwiCli,
};
use clap::Parser;
use pezsc_keystore::LocalKeystore;
use pezsc_service::config::{BasePath, KeystoreConfig};
use pezsp_core::crypto::{KeyTypeId, SecretString};
use pezsp_keystore::KeystorePtr;
/// The `insert` command
#[derive(Debug, Clone, Parser)]
#[command(name = "insert", about = "Insert a key to the keystore of a node.")]
pub struct InsertKeyCmd {
/// The secret key URI.
/// If the value is a file, the file content is used as URI.
/// If not given, you will be prompted for the URI.
#[arg(long)]
suri: Option<String>,
/// Key type, examples: "gran", or "imon".
#[arg(long)]
key_type: String,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
/// The cryptography scheme that should be used to generate the key out of the given URI.
#[arg(long, value_name = "SCHEME", value_enum, ignore_case = true)]
pub scheme: CryptoScheme,
}
impl InsertKeyCmd {
/// Run the command
pub fn run<C: BizinikiwiCli>(&self, cli: &C) -> Result<(), Error> {
let suri = utils::read_uri(self.suri.as_ref())?;
let base_path = self
.shared_params
.base_path()?
.unwrap_or_else(|| BasePath::from_project("", "", &C::executable_name()));
let chain_id = self.shared_params.chain_id(self.shared_params.is_dev());
let chain_spec = cli.load_spec(&chain_id)?;
let config_dir = base_path.config_dir(chain_spec.id());
let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? {
KeystoreConfig::Path { path, password } => {
let public = with_crypto_scheme!(self.scheme, to_vec(&suri, password.clone()))?;
let keystore: KeystorePtr = LocalKeystore::open(path, password)?.into();
(keystore, public)
},
_ => unreachable!("keystore_config always returns path and password; qed"),
};
let key_type =
KeyTypeId::try_from(self.key_type.as_str()).map_err(|_| Error::KeyTypeInvalid)?;
keystore
.insert(key_type, &suri, &public[..])
.map_err(|_| Error::KeystoreOperation)?;
Ok(())
}
}
fn to_vec<P: pezsp_core::Pair>(uri: &str, pass: Option<SecretString>) -> Result<Vec<u8>, Error> {
let p = utils::pair_from_suri::<P>(uri, pass)?;
Ok(p.public().as_ref().to_vec())
}
#[cfg(test)]
mod tests {
use super::*;
use pezsc_service::{ChainSpec, ChainType, GenericChainSpec, NoExtension};
use pezsp_core::{sr25519::Pair, ByteArray, Pair as _};
use pezsp_keystore::Keystore;
use tempfile::TempDir;
struct Cli;
impl BizinikiwiCli for Cli {
fn impl_name() -> String {
"test".into()
}
fn impl_version() -> String {
"2.0".into()
}
fn description() -> String {
"test".into()
}
fn support_url() -> String {
"test.test".into()
}
fn copyright_start_year() -> i32 {
2021
}
fn author() -> String {
"test".into()
}
fn load_spec(&self, _: &str) -> std::result::Result<Box<dyn ChainSpec>, String> {
let builder =
GenericChainSpec::<NoExtension, ()>::builder(Default::default(), NoExtension::None);
Ok(Box::new(
builder
.with_name("test")
.with_id("test_id")
.with_chain_type(ChainType::Development)
.with_genesis_config_patch(Default::default())
.build(),
))
}
}
#[test]
fn insert_with_custom_base_path() {
let path = TempDir::new().unwrap();
let path_str = format!("{}", path.path().display());
let (key, uri, _) = Pair::generate_with_phrase(None);
let inspect = InsertKeyCmd::parse_from(&[
"insert-key",
"-d",
&path_str,
"--key-type",
"test",
"--suri",
&uri,
"--scheme=sr25519",
]);
assert!(inspect.run(&Cli).is_ok());
let keystore =
LocalKeystore::open(path.path().join("chains").join("test_id").join("keystore"), None)
.unwrap();
assert!(keystore.has_keys(&[(key.public().to_raw_vec(), KeyTypeId(*b"test"))]));
}
}
@@ -0,0 +1,264 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `inspect` subcommand
use crate::{
utils::{self, print_from_public, print_from_uri},
with_crypto_scheme, CryptoSchemeFlag, Error, KeystoreParams, NetworkSchemeFlag, OutputTypeFlag,
};
use clap::Parser;
use pezsp_core::crypto::{ExposeSecret, SecretString, SecretUri, Ss58Codec};
use std::str::FromStr;
/// The `inspect` command
#[derive(Debug, Parser)]
#[command(
name = "inspect",
about = "Gets a public key and a SS58 address from the provided Secret URI"
)]
pub struct InspectKeyCmd {
/// A Key URI to be inspected. May be a secret seed, secret URI
/// (with derivation paths and password), SS58, public URI or a hex encoded public key.
/// If it is a hex encoded public key, `--public` needs to be given as argument.
/// If the given value is a file, the file content will be used
/// as URI.
/// If omitted, you will be prompted for the URI.
uri: Option<String>,
/// Is the given `uri` a hex encoded public key?
#[arg(long)]
public: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
/// Expect that `--uri` has the given public key/account-id.
/// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the
/// `uri` without any derivation applied. However, if `uri` has a password or there is one
/// given by `--password`, it will be used to decrypt `uri` before comparing the public
/// key/account-id.
/// If there is no derivation in `--uri`, the public key will be checked against the public key
/// of `--uri` directly.
#[arg(long, conflicts_with = "public")]
pub expect_public: Option<String>,
}
impl InspectKeyCmd {
/// Run the command
pub fn run(&self) -> Result<(), Error> {
let uri = utils::read_uri(self.uri.as_ref())?;
let password = self.keystore_params.read_password()?;
if self.public {
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_public(
&uri,
self.network_scheme.network,
self.output_scheme.output_type,
)
)?;
} else {
if let Some(ref expect_public) = self.expect_public {
with_crypto_scheme!(
self.crypto_scheme.scheme,
expect_public_from_phrase(expect_public, &uri, password.as_ref())
)?;
}
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(
&uri,
password,
self.network_scheme.network,
self.output_scheme.output_type,
)
);
}
Ok(())
}
}
/// Checks that `expect_public` is the public key of `suri`.
///
/// If `suri` has any derivations, `expect_public` is checked against the public key of the "bare"
/// `suri`, i.e. without any derivations.
///
/// Returns an error if the public key does not match.
fn expect_public_from_phrase<Pair: pezsp_core::Pair>(
expect_public: &str,
suri: &str,
password: Option<&SecretString>,
) -> Result<(), Error> {
let secret_uri = SecretUri::from_str(suri).map_err(|e| format!("{:?}", e))?;
let expected_public = if let Some(public) = expect_public.strip_prefix("0x") {
let hex_public = array_bytes::hex2bytes(public)
.map_err(|_| format!("Invalid expected public key hex: `{}`", expect_public))?;
Pair::Public::try_from(&hex_public)
.map_err(|_| format!("Invalid expected public key: `{}`", expect_public))?
} else {
Pair::Public::from_string_with_version(expect_public)
.map_err(|_| format!("Invalid expected account id: `{}`", expect_public))?
.0
};
let pair = Pair::from_string_with_seed(
secret_uri.phrase.expose_secret().as_str(),
password
.or_else(|| secret_uri.password.as_ref())
.map(|p| p.expose_secret().as_str()),
)
.map_err(|_| format!("Invalid secret uri: {}", suri))?
.0;
if pair.public() == expected_public {
Ok(())
} else {
Err(format!("Expected public ({}) key does not match.", expect_public).into())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsp_core::crypto::{ByteArray, Pair};
use pezsp_runtime::traits::IdentifyAccount;
#[test]
fn inspect() {
let words =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", words, "--password", "12345"]);
assert!(inspect.run().is_ok());
let inspect = InspectKeyCmd::parse_from(&["inspect-key", seed]);
assert!(inspect.run().is_ok());
}
#[test]
fn inspect_public_key() {
let public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let inspect = InspectKeyCmd::parse_from(&["inspect-key", "--public", public]);
assert!(inspect.run().is_ok());
}
#[test]
fn inspect_with_expected_public_key() {
let check_cmd = |seed, expected_public, success| {
let inspect = InspectKeyCmd::parse_from(&[
"inspect-key",
"--expect-public",
expected_public,
seed,
]);
let res = inspect.run();
if success {
assert!(res.is_ok());
} else {
assert!(res.unwrap_err().to_string().contains(&format!(
"Expected public ({}) key does not match.",
expected_public
)));
}
};
let seed =
"remember fiber forum demise paper uniform squirrel feel access exclude casual effort";
let invalid_public = "0x12e76e0ae8ce41b6516cce52b3f23a08dcb4cfeed53c6ee8f5eb9f7367341069";
let valid_public = pezsp_core::sr25519::Pair::from_string_with_seed(seed, None)
.expect("Valid")
.0
.public();
let valid_public_hex = array_bytes::bytes2hex("0x", valid_public.as_slice());
let valid_accountid = format!("{}", valid_public.into_account());
// It should fail with the invalid public key
check_cmd(seed, invalid_public, false);
// It should work with the valid public key & account id
check_cmd(seed, &valid_public_hex, true);
check_cmd(seed, &valid_accountid, true);
let password = "test12245";
let seed_with_password = format!("{}///{}", seed, password);
let valid_public_with_password =
pezsp_core::sr25519::Pair::from_string_with_seed(&seed_with_password, Some(password))
.expect("Valid")
.0
.public();
let valid_public_hex_with_password =
array_bytes::bytes2hex("0x", valid_public_with_password.as_slice());
let valid_accountid_with_password =
format!("{}", &valid_public_with_password.into_account());
// Only the public key that corresponds to the seed with password should be accepted.
check_cmd(&seed_with_password, &valid_public_hex, false);
check_cmd(&seed_with_password, &valid_accountid, false);
check_cmd(&seed_with_password, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password, &valid_accountid_with_password, true);
let seed_with_password_and_derivation = format!("{}//test//account///{}", seed, password);
let valid_public_with_password_and_derivation =
pezsp_core::sr25519::Pair::from_string_with_seed(
&seed_with_password_and_derivation,
Some(password),
)
.expect("Valid")
.0
.public();
let valid_public_hex_with_password_and_derivation =
array_bytes::bytes2hex("0x", valid_public_with_password_and_derivation.as_slice());
// They should still be valid, because we check the base secret key.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex_with_password, true);
check_cmd(&seed_with_password_and_derivation, &valid_accountid_with_password, true);
// And these should be invalid.
check_cmd(&seed_with_password_and_derivation, &valid_public_hex, false);
check_cmd(&seed_with_password_and_derivation, &valid_accountid, false);
// The public of the derived account should fail.
check_cmd(
&seed_with_password_and_derivation,
&valid_public_hex_with_password_and_derivation,
false,
);
}
}
@@ -0,0 +1,98 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `inspect-node-key` subcommand
use crate::Error;
use clap::Parser;
use libp2p_identity::Keypair;
use std::{
fs,
io::{self, Read},
path::PathBuf,
};
/// The `inspect-node-key` command
#[derive(Debug, Parser)]
#[command(
name = "inspect-node-key",
about = "Load a node key from a file or stdin and print the corresponding peer-id."
)]
pub struct InspectNodeKeyCmd {
/// Name of file to read the secret key from.
/// If not given, the secret key is read from stdin (up to EOF).
#[arg(long)]
file: Option<PathBuf>,
/// The input is in raw binary format.
/// If not given, the input is read as an hex encoded string.
#[arg(long)]
bin: bool,
/// This argument is deprecated and has no effect for this command.
#[deprecated(note = "Network identifier is not used for node-key inspection")]
#[arg(short = 'n', long = "network", value_name = "NETWORK", ignore_case = true)]
pub network_scheme: Option<String>,
}
impl InspectNodeKeyCmd {
/// runs the command
pub fn run(&self) -> Result<(), Error> {
let mut file_data = match &self.file {
Some(file) => fs::read(&file)?,
None => {
let mut buf = Vec::with_capacity(64);
io::stdin().lock().read_to_end(&mut buf)?;
buf
},
};
if !self.bin {
// With hex input, give to the user a bit of tolerance about whitespaces
let keyhex = String::from_utf8_lossy(&file_data);
file_data = array_bytes::hex2bytes(keyhex.trim())
.map_err(|_| "failed to decode secret as hex")?;
}
let keypair =
Keypair::ed25519_from_bytes(&mut file_data).map_err(|_| "Bad node key file")?;
println!("{}", keypair.public().to_peer_id());
Ok(())
}
}
#[cfg(test)]
mod tests {
use crate::commands::generate_node_key::GenerateNodeKeyCmd;
use super::*;
#[test]
fn inspect_node_key() {
let path = tempfile::tempdir().unwrap().into_path().join("node-id").into_os_string();
let path = path.to_str().unwrap();
let cmd = GenerateNodeKeyCmd::parse_from(&["generate-node-key", "--file", path]);
assert!(cmd.run("test", &String::from("test")).is_ok());
let cmd = InspectNodeKeyCmd::parse_from(&["inspect-node-key", "--file", path]);
assert!(cmd.run().is_ok());
}
}
+61
View File
@@ -0,0 +1,61 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Key related CLI utilities
use super::{
generate::GenerateCmd, generate_node_key::GenerateNodeKeyCmd, insert_key::InsertKeyCmd,
inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
};
use crate::{Error, BizinikiwiCli};
/// Key utilities for the cli.
#[derive(Debug, clap::Subcommand)]
pub enum KeySubcommand {
/// Generate a random node key, write it to a file or stdout and write the
/// corresponding peer-id to stderr
GenerateNodeKey(GenerateNodeKeyCmd),
/// Generate a random account
Generate(GenerateCmd),
/// Gets a public key and a SS58 address from the provided Secret URI
Inspect(InspectKeyCmd),
/// Load a node key from a file or stdin and print the corresponding peer-id
InspectNodeKey(InspectNodeKeyCmd),
/// Insert a key to the keystore of a node.
Insert(InsertKeyCmd),
}
impl KeySubcommand {
/// run the key subcommands
pub fn run<C: BizinikiwiCli>(&self, cli: &C) -> Result<(), Error> {
match self {
KeySubcommand::GenerateNodeKey(cmd) => {
let chain_spec = cli.load_spec(cmd.chain.as_deref().unwrap_or(""))?;
cmd.run(chain_spec.id(), &C::executable_name())
},
KeySubcommand::Generate(cmd) => cmd.run(),
KeySubcommand::Inspect(cmd) => cmd.run(),
KeySubcommand::Insert(cmd) => cmd.run(cli),
KeySubcommand::InspectNodeKey(cmd) => cmd.run(),
}
}
}
+51
View File
@@ -0,0 +1,51 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Various subcommands that can be included in a bizinikiwi-based chain's CLI.
mod build_spec_cmd;
mod chain_info_cmd;
mod check_block_cmd;
mod export_blocks_cmd;
mod export_chain_spec_cmd;
mod export_state_cmd;
mod generate;
mod generate_node_key;
mod import_blocks_cmd;
mod insert_key;
mod inspect_key;
mod inspect_node_key;
mod key;
mod purge_chain_cmd;
mod revert_cmd;
mod run_cmd;
mod sign;
mod test;
pub mod utils;
mod vanity;
mod verify;
pub use self::{
build_spec_cmd::BuildSpecCmd, chain_info_cmd::ChainInfoCmd, check_block_cmd::CheckBlockCmd,
export_blocks_cmd::ExportBlocksCmd, export_chain_spec_cmd::ExportChainSpecCmd,
export_state_cmd::ExportStateCmd, generate::GenerateCmd,
generate_node_key::GenerateKeyCmdCommon, import_blocks_cmd::ImportBlocksCmd,
insert_key::InsertKeyCmd, inspect_key::InspectKeyCmd, inspect_node_key::InspectNodeKeyCmd,
key::KeySubcommand, purge_chain_cmd::PurgeChainCmd, revert_cmd::RevertCmd, run_cmd::RunCmd,
sign::SignCmd, vanity::VanityCmd, verify::VerifyCmd,
};
@@ -0,0 +1,94 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{DatabaseParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use pezsc_service::DatabaseSource;
use std::{
fmt::Debug,
fs,
io::{self, Write},
};
/// The `purge-chain` command used to remove the whole chain.
#[derive(Debug, Clone, Parser)]
pub struct PurgeChainCmd {
/// Skip interactive prompt by answering yes automatically.
#[arg(short = 'y')]
pub yes: bool,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
impl PurgeChainCmd {
/// Run the purge command
pub fn run(&self, database_config: DatabaseSource) -> error::Result<()> {
let db_path = database_config.path().and_then(|p| p.parent()).ok_or_else(|| {
error::Error::Input("Cannot purge custom database implementation".into())
})?;
if !self.yes {
print!("Are you sure to remove {:?}? [y/N]: ", &db_path);
io::stdout().flush().expect("failed to flush stdout");
let mut input = String::new();
io::stdin().read_line(&mut input)?;
let input = input.trim();
match input.chars().next() {
Some('y') | Some('Y') => {},
_ => {
println!("Aborted");
return Ok(());
},
}
}
match fs::remove_dir_all(&db_path) {
Ok(_) => {
println!("{:?} removed.", &db_path);
Ok(())
},
Err(ref err) if err.kind() == io::ErrorKind::NotFound => {
eprintln!("{:?} did not exist.", &db_path);
Ok(())
},
Err(err) => Result::Err(err.into()),
}
}
}
impl CliConfiguration for PurgeChainCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,90 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error,
params::{DatabaseParams, GenericNumber, PruningParams, SharedParams},
CliConfiguration,
};
use clap::Parser;
use pezsc_client_api::{Backend, UsageProvider};
use pezsc_service::chain_ops::revert_chain;
use pezsp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor};
use std::{fmt::Debug, str::FromStr, sync::Arc};
/// The `revert` command used revert the chain to a previous state.
#[derive(Debug, Parser)]
pub struct RevertCmd {
/// Number of blocks to revert.
#[arg(default_value = "256")]
pub num: GenericNumber,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
}
/// Revert handler for auxiliary data (e.g. consensus).
type AuxRevertHandler<C, BA, B> =
Box<dyn FnOnce(Arc<C>, Arc<BA>, NumberFor<B>) -> error::Result<()>>;
impl RevertCmd {
/// Run the revert command
pub async fn run<B, BA, C>(
&self,
client: Arc<C>,
backend: Arc<BA>,
aux_revert: Option<AuxRevertHandler<C, BA, B>>,
) -> error::Result<()>
where
B: BlockT,
BA: Backend<B>,
C: UsageProvider<B>,
<<<B as BlockT>::Header as HeaderT>::Number as FromStr>::Err: Debug,
{
let blocks = self.num.parse()?;
if let Some(aux_revert) = aux_revert {
aux_revert(client.clone(), backend.clone(), blocks)?;
}
revert_chain(client, backend, blocks)?;
Ok(())
}
}
impl CliConfiguration for RevertCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn pruning_params(&self) -> Option<&PruningParams> {
Some(&self.pruning_params)
}
fn database_params(&self) -> Option<&DatabaseParams> {
Some(&self.database_params)
}
}
@@ -0,0 +1,421 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
error::{Error, Result},
params::{
ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, RpcEndpoint,
SharedParams, TransactionPoolParams,
},
CliConfiguration, PrometheusParams, RpcParams, RuntimeParams, TelemetryParams,
};
use clap::Parser;
use regex::Regex;
use pezsc_service::{
config::{
BasePath, IpNetwork, PrometheusConfig, RpcBatchRequestConfig, TransactionPoolOptions,
},
ChainSpec, Role,
};
use pezsc_telemetry::TelemetryEndpoints;
use std::num::NonZeroU32;
/// The `run` command used to run a node.
#[derive(Debug, Clone, Parser)]
pub struct RunCmd {
/// Enable validator mode.
///
/// The node will be started with the authority role and actively
/// participate in any consensus task that it can (e.g. depending on
/// availability of local keys).
#[arg(long)]
pub validator: bool,
/// Disable GRANDPA.
///
/// Disables voter when running in validator mode, otherwise disable the GRANDPA
/// observer.
#[arg(long)]
pub no_grandpa: bool,
/// The human-readable name for this node.
///
/// It's used as network node name.
#[arg(long, value_name = "NAME")]
pub name: Option<String>,
#[allow(missing_docs)]
#[clap(flatten)]
pub rpc_params: RpcParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub telemetry_params: TelemetryParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub prometheus_params: PrometheusParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub runtime_params: RuntimeParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub offchain_worker_params: OffchainWorkerParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub shared_params: SharedParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub import_params: ImportParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub network_params: NetworkParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub pool_config: TransactionPoolParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
/// Shortcut for `--name Alice --validator`.
///
/// Session keys for `Alice` are added to keystore.
#[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])]
pub alice: bool,
/// Shortcut for `--name Bob --validator`.
///
/// Session keys for `Bob` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "charlie", "dave", "eve", "ferdie", "one", "two"])]
pub bob: bool,
/// Shortcut for `--name Charlie --validator`.
///
/// Session keys for `Charlie` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "dave", "eve", "ferdie", "one", "two"])]
pub charlie: bool,
/// Shortcut for `--name Dave --validator`.
///
/// Session keys for `Dave` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "eve", "ferdie", "one", "two"])]
pub dave: bool,
/// Shortcut for `--name Eve --validator`.
///
/// Session keys for `Eve` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "ferdie", "one", "two"])]
pub eve: bool,
/// Shortcut for `--name Ferdie --validator`.
///
/// Session keys for `Ferdie` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "one", "two"])]
pub ferdie: bool,
/// Shortcut for `--name One --validator`.
///
/// Session keys for `One` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "two"])]
pub one: bool,
/// Shortcut for `--name Two --validator`.
///
/// Session keys for `Two` are added to keystore.
#[arg(long, conflicts_with_all = &["alice", "bob", "charlie", "dave", "eve", "ferdie", "one"])]
pub two: bool,
/// Enable authoring even when offline.
#[arg(long)]
pub force_authoring: bool,
/// Run a temporary node.
///
/// A temporary directory will be created to store the configuration and will be deleted
/// at the end of the process.
///
/// Note: the directory is random per process execution. This directory is used as base path
/// which includes: database, node key and keystore.
///
/// When `--dev` is given and no explicit `--base-path`, this option is implied.
#[arg(long, conflicts_with = "base_path")]
pub tmp: bool,
}
impl RunCmd {
/// Get the `Sr25519Keyring` matching one of the flag.
pub fn get_keyring(&self) -> Option<pezsp_keyring::Sr25519Keyring> {
use pezsp_keyring::Sr25519Keyring::*;
if self.alice {
Some(Alice)
} else if self.bob {
Some(Bob)
} else if self.charlie {
Some(Charlie)
} else if self.dave {
Some(Dave)
} else if self.eve {
Some(Eve)
} else if self.ferdie {
Some(Ferdie)
} else if self.one {
Some(One)
} else if self.two {
Some(Two)
} else {
None
}
}
}
impl CliConfiguration for RunCmd {
fn shared_params(&self) -> &SharedParams {
&self.shared_params
}
fn import_params(&self) -> Option<&ImportParams> {
Some(&self.import_params)
}
fn network_params(&self) -> Option<&NetworkParams> {
Some(&self.network_params)
}
fn keystore_params(&self) -> Option<&KeystoreParams> {
Some(&self.keystore_params)
}
fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
Some(&self.offchain_worker_params)
}
fn node_name(&self) -> Result<String> {
let name: String = match (self.name.as_ref(), self.get_keyring()) {
(Some(name), _) => name.to_string(),
(_, Some(keyring)) => keyring.to_string(),
(None, None) => crate::generate_node_name(),
};
is_node_name_valid(&name).map_err(|msg| {
Error::Input(format!(
"Invalid node name '{}'. Reason: {}. If unsure, use none.",
name, msg
))
})?;
Ok(name)
}
fn dev_key_seed(&self, is_dev: bool) -> Result<Option<String>> {
Ok(self.get_keyring().map(|a| format!("//{}", a)).or_else(|| {
if is_dev {
Some("//Alice".into())
} else {
None
}
}))
}
fn telemetry_endpoints(
&self,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<TelemetryEndpoints>> {
let params = &self.telemetry_params;
Ok(if params.no_telemetry {
None
} else if !params.telemetry_endpoints.is_empty() {
Some(
TelemetryEndpoints::new(params.telemetry_endpoints.clone())
.map_err(|e| e.to_string())?,
)
} else {
chain_spec.telemetry_endpoints().clone()
})
}
fn role(&self, is_dev: bool) -> Result<Role> {
let keyring = self.get_keyring();
let is_authority = self.validator || is_dev || keyring.is_some();
Ok(if is_authority { Role::Authority } else { Role::Full })
}
fn force_authoring(&self) -> Result<bool> {
// Imply forced authoring on --dev
Ok(self.shared_params.dev || self.force_authoring)
}
fn prometheus_config(
&self,
default_listen_port: u16,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<PrometheusConfig>> {
Ok(self
.prometheus_params
.prometheus_config(default_listen_port, chain_spec.id().to_string()))
}
fn disable_grandpa(&self) -> Result<bool> {
Ok(self.no_grandpa)
}
fn rpc_max_connections(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_connections)
}
fn rpc_cors(&self, is_dev: bool) -> Result<Option<Vec<String>>> {
self.rpc_params.rpc_cors(is_dev)
}
fn rpc_addr(&self, default_listen_port: u16) -> Result<Option<Vec<RpcEndpoint>>> {
self.rpc_params.rpc_addr(self.is_dev()?, self.validator, default_listen_port)
}
fn rpc_methods(&self) -> Result<pezsc_service::config::RpcMethods> {
Ok(self.rpc_params.rpc_methods.into())
}
fn rpc_max_request_size(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_request_size)
}
fn rpc_max_response_size(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_response_size)
}
fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_max_subscriptions_per_connection)
}
fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
Ok(self.rpc_params.rpc_message_buffer_capacity_per_connection)
}
fn rpc_batch_config(&self) -> Result<RpcBatchRequestConfig> {
self.rpc_params.rpc_batch_config()
}
fn rpc_rate_limit(&self) -> Result<Option<NonZeroU32>> {
Ok(self.rpc_params.rpc_rate_limit)
}
fn rpc_rate_limit_whitelisted_ips(&self) -> Result<Vec<IpNetwork>> {
Ok(self.rpc_params.rpc_rate_limit_whitelisted_ips.clone())
}
fn rpc_rate_limit_trust_proxy_headers(&self) -> Result<bool> {
Ok(self.rpc_params.rpc_rate_limit_trust_proxy_headers)
}
fn transaction_pool(&self, is_dev: bool) -> Result<TransactionPoolOptions> {
Ok(self.pool_config.transaction_pool(is_dev))
}
fn max_runtime_instances(&self) -> Result<Option<usize>> {
Ok(Some(self.runtime_params.max_runtime_instances))
}
fn runtime_cache_size(&self) -> Result<u8> {
Ok(self.runtime_params.runtime_cache_size)
}
fn base_path(&self) -> Result<Option<BasePath>> {
Ok(if self.tmp {
Some(BasePath::new_temp_dir()?)
} else {
match self.shared_params().base_path()? {
Some(r) => Some(r),
// If `dev` is enabled, we use the temp base path.
None if self.shared_params().is_dev() => Some(BasePath::new_temp_dir()?),
None => None,
}
})
}
}
/// Check whether a node name is considered as valid.
pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> {
let name = _name.to_string();
if name.is_empty() {
return Err("Node name cannot be empty");
}
if name.chars().count() >= crate::NODE_NAME_MAX_LENGTH {
return Err("Node name too long");
}
let invalid_chars = r"[\\.@]";
let re = Regex::new(invalid_chars).unwrap();
if re.is_match(&name) {
return Err("Node name should not contain invalid chars such as '.' and '@'");
}
let invalid_patterns = r"^https?:";
let re = Regex::new(invalid_patterns).unwrap();
if re.is_match(&name) {
return Err("Node name should not contain urls");
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tests_node_name_good() {
assert!(is_node_name_valid("short name").is_ok());
assert!(is_node_name_valid("www").is_ok());
assert!(is_node_name_valid("aawww").is_ok());
assert!(is_node_name_valid("wwwaa").is_ok());
assert!(is_node_name_valid("www aa").is_ok());
}
#[test]
fn tests_node_name_bad() {
assert!(is_node_name_valid("").is_err());
assert!(is_node_name_valid(
"very very long names are really not very cool for the ui at all, really they're not"
)
.is_err());
assert!(is_node_name_valid("Dots.not.Ok").is_err());
// NOTE: the urls below don't include a domain otherwise
// they'd get filtered for including a `.`
assert!(is_node_name_valid("http://visitme").is_err());
assert!(is_node_name_valid("http:/visitme").is_err());
assert!(is_node_name_valid("http:visitme").is_err());
assert!(is_node_name_valid("https://visitme").is_err());
assert!(is_node_name_valid("https:/visitme").is_err());
assert!(is_node_name_valid("https:visitme").is_err());
assert!(is_node_name_valid("www.visit.me").is_err());
assert!(is_node_name_valid("www.visit").is_err());
assert!(is_node_name_valid("hello\\world").is_err());
assert!(is_node_name_valid("visit.www").is_err());
assert!(is_node_name_valid("email@domain").is_err());
}
}
+126
View File
@@ -0,0 +1,126 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Implementation of the `sign` subcommand
use crate::{
error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams,
};
use array_bytes::bytes2hex;
use clap::Parser;
use pezsp_core::crypto::SecretString;
use std::io::{BufRead, Write};
/// The `sign` command
#[derive(Debug, Clone, Parser)]
#[command(name = "sign", about = "Sign a message, with a given (secret) key")]
pub struct SignCmd {
/// The secret key URI.
/// If the value is a file, the file content is used as URI.
/// If not given, you will be prompted for the URI.
#[arg(long)]
suri: Option<String>,
#[allow(missing_docs)]
#[clap(flatten)]
pub message_params: MessageParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub keystore_params: KeystoreParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl SignCmd {
/// Run the command
pub fn run(&self) -> error::Result<()> {
let sig = self.sign(|| std::io::stdin().lock())?;
std::io::stdout().lock().write_all(sig.as_bytes())?;
Ok(())
}
/// Sign a message.
///
/// The message can either be provided as immediate argument via CLI or otherwise read from the
/// reader created by `create_reader`. The reader will only be created in case that the message
/// is not passed as immediate.
pub(crate) fn sign<F, R>(&self, create_reader: F) -> error::Result<String>
where
R: BufRead,
F: FnOnce() -> R,
{
let message = self.message_params.message_from(create_reader)?;
let suri = utils::read_uri(self.suri.as_ref())?;
let password = self.keystore_params.read_password()?;
with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))
}
}
fn sign<P: pezsp_core::Pair>(
suri: &str,
password: Option<SecretString>,
message: Vec<u8>,
) -> error::Result<String> {
let pair = utils::pair_from_suri::<P>(suri, password)?;
Ok(bytes2hex("0x", pair.sign(&message).as_ref()))
}
#[cfg(test)]
mod test {
use super::*;
const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
#[test]
fn sign_arg() {
let cmd = SignCmd::parse_from(&[
"sign",
"--suri",
&SEED,
"--message",
&SEED,
"--password",
"12345",
"--hex",
]);
let sig = cmd.sign(|| std::io::stdin().lock()).expect("Must sign");
assert!(sig.starts_with("0x"), "Signature must start with 0x");
assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
}
#[test]
fn sign_stdin() {
let cmd = SignCmd::parse_from(&[
"sign",
"--suri",
SEED,
"--message",
&SEED,
"--password",
"12345",
]);
let sig = cmd.sign(|| SEED.as_bytes()).expect("Must sign");
assert!(sig.starts_with("0x"), "Signature must start with 0x");
assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex");
}
}
@@ -0,0 +1,21 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Integration tests for subkey commands.
mod sig_verify;
@@ -0,0 +1,152 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
#![cfg(test)]
//! Integration test that the `sign` and `verify` sub-commands work together.
use crate::*;
use clap::Parser;
const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a";
const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
const BOB: &str = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty";
/// Sign a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument.
fn sign(msg: &str, hex: bool, stdin: bool) -> String {
sign_raw(msg.as_bytes(), hex, stdin)
}
/// Sign a raw message which can be `hex` and passed either via `stdin` or as an argument.
fn sign_raw(msg: &[u8], hex: bool, stdin: bool) -> String {
let mut args = vec!["sign", "--suri", SEED];
if !stdin {
args.push("--message");
args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg"));
}
if hex {
args.push("--hex");
}
let cmd = SignCmd::parse_from(&args);
cmd.sign(|| msg).expect("Static data is good; Must sign; qed")
}
/// Verify a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument.
fn verify(msg: &str, hex: bool, stdin: bool, who: &str, sig: &str) -> bool {
verify_raw(msg.as_bytes(), hex, stdin, who, sig)
}
/// Verify a raw message which can be `hex` and passed either via `stdin` or as an argument.
fn verify_raw(msg: &[u8], hex: bool, stdin: bool, who: &str, sig: &str) -> bool {
let mut args = vec!["verify", sig, who];
if !stdin {
args.push("--message");
args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg"));
}
if hex {
args.push("--hex");
}
let cmd = VerifyCmd::parse_from(&args);
cmd.verify(|| msg).is_ok()
}
/// Test that sig/verify works with UTF-8 bytes passed as arg.
#[test]
fn sig_verify_arg_utf8_work() {
let sig = sign("Something", false, false);
assert!(verify("Something", false, false, ALICE, &sig));
assert!(!verify("Something", false, false, BOB, &sig));
assert!(!verify("Wrong", false, false, ALICE, &sig));
assert!(!verify("Not hex", true, false, ALICE, &sig));
assert!(!verify("0x1234", true, false, ALICE, &sig));
assert!(!verify("Wrong", false, false, BOB, &sig));
assert!(!verify("Not hex", true, false, BOB, &sig));
assert!(!verify("0x1234", true, false, BOB, &sig));
}
/// Test that sig/verify works with UTF-8 bytes passed via stdin.
#[test]
fn sig_verify_stdin_utf8_work() {
let sig = sign("Something", false, true);
assert!(verify("Something", false, true, ALICE, &sig));
assert!(!verify("Something", false, true, BOB, &sig));
assert!(!verify("Wrong", false, true, ALICE, &sig));
assert!(!verify("Not hex", true, true, ALICE, &sig));
assert!(!verify("0x1234", true, true, ALICE, &sig));
assert!(!verify("Wrong", false, true, BOB, &sig));
assert!(!verify("Not hex", true, true, BOB, &sig));
assert!(!verify("0x1234", true, true, BOB, &sig));
}
/// Test that sig/verify works with hex bytes passed as arg.
#[test]
fn sig_verify_arg_hex_work() {
let sig = sign("0xaabbcc", true, false);
assert!(verify("0xaabbcc", true, false, ALICE, &sig));
assert!(verify("aabBcc", true, false, ALICE, &sig));
assert!(verify("0xaAbbCC", true, false, ALICE, &sig));
assert!(!verify("0xaabbcc", true, false, BOB, &sig));
assert!(!verify("0xaabbcc", false, false, ALICE, &sig));
}
/// Test that sig/verify works with hex bytes passed via stdin.
#[test]
fn sig_verify_stdin_hex_work() {
let sig = sign("0xaabbcc", true, true);
assert!(verify("0xaabbcc", true, true, ALICE, &sig));
assert!(verify("aabBcc", true, true, ALICE, &sig));
assert!(verify("0xaAbbCC", true, true, ALICE, &sig));
assert!(!verify("0xaabbcc", true, true, BOB, &sig));
assert!(!verify("0xaabbcc", false, true, ALICE, &sig));
}
/// Test that sig/verify works with random bytes.
#[test]
fn sig_verify_stdin_non_utf8_work() {
use rand::RngCore;
let mut rng = rand::thread_rng();
for _ in 0..100 {
let mut raw = [0u8; 32];
rng.fill_bytes(&mut raw);
let sig = sign_raw(&raw, false, true);
assert!(verify_raw(&raw, false, true, ALICE, &sig));
assert!(!verify_raw(&raw, false, true, BOB, &sig));
}
}
/// Test that sig/verify works with invalid UTF-8 bytes.
#[test]
fn sig_verify_stdin_invalid_utf8_work() {
let raw = vec![192u8, 193];
assert!(String::from_utf8(raw.clone()).is_err(), "Must be invalid UTF-8");
let sig = sign_raw(&raw, false, true);
assert!(verify_raw(&raw, false, true, ALICE, &sig));
assert!(!verify_raw(&raw, false, true, BOB, &sig));
}
+301
View File
@@ -0,0 +1,301 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! subcommand utilities
use crate::{
error::{self, Error},
OutputType,
};
use serde_json::json;
use pezsp_core::{
crypto::{
unwrap_or_default_ss58_version, ExposeSecret, SecretString, Ss58AddressFormat, Ss58Codec,
Zeroize,
},
hexdisplay::HexDisplay,
Pair,
};
use pezsp_runtime::{traits::IdentifyAccount, MultiSigner};
use std::path::PathBuf;
/// Public key type for Runtime
pub type PublicFor<P> = <P as pezsp_core::Pair>::Public;
/// Seed type for Runtime
pub type SeedFor<P> = <P as pezsp_core::Pair>::Seed;
/// helper method to fetch uri from `Option<String>` either as a file or read from stdin
pub fn read_uri(uri: Option<&String>) -> error::Result<String> {
let uri = if let Some(uri) = uri {
let file = PathBuf::from(&uri);
if file.is_file() {
std::fs::read_to_string(uri)?.trim_end().to_owned()
} else {
uri.into()
}
} else {
rpassword::prompt_password("URI: ")?
};
Ok(uri)
}
/// Try to parse given `uri` and print relevant information.
///
/// 1. Try to construct the `Pair` while using `uri` as input for [`pezsp_core::Pair::from_phrase`].
///
/// 2. Try to construct the `Pair` while using `uri` as input for
/// [`pezsp_core::Pair::from_string_with_seed`].
///
/// 3. Try to construct the `Pair::Public` while using `uri` as input for
/// [`pezsp_core::crypto::Ss58Codec::from_string_with_version`].
pub fn print_from_uri<Pair>(
uri: &str,
password: Option<SecretString>,
network_override: Option<Ss58AddressFormat>,
output: OutputType,
) where
Pair: pezsp_core::Pair,
Pair::Public: Into<MultiSigner>,
{
let password = password.as_ref().map(|s| s.expose_secret().as_str());
let network_id = String::from(unwrap_or_default_ss58_version(network_override));
if let Ok((pair, seed)) = Pair::from_phrase(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
match output {
OutputType::Json => {
let json = json!({
"secretPhrase": uri,
"networkId": network_id,
"secretSeed": format_seed::<Pair>(seed),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
},
OutputType::Text => {
println!(
"Secret phrase: {}\n \
Network ID: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
network_id,
format_seed::<Pair>(seed),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override),
);
},
}
} else if let Ok((pair, seed)) = Pair::from_string_with_seed(uri, password) {
let public_key = pair.public();
let network_override = unwrap_or_default_ss58_version(network_override);
match output {
OutputType::Json => {
let json = json!({
"secretKeyUri": uri,
"networkId": network_id,
"secretSeed": if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
"publicKey": format_public_key::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"accountId": format_account_id::<Pair>(public_key),
"ss58Address": pair.public().into().into_account().to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
},
OutputType::Text => {
println!(
"Secret Key URI `{}` is account:\n \
Network ID: {}\n \
Secret seed: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
network_id,
if let Some(seed) = seed { format_seed::<Pair>(seed) } else { "n/a".into() },
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
pair.public().into().into_account().to_ss58check_with_version(network_override),
);
},
}
} else if let Ok((public_key, network)) = Pair::Public::from_string_with_version(uri) {
let network_override = network_override.unwrap_or(network);
match output {
OutputType::Json => {
let json = json!({
"publicKeyUri": uri,
"networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override),
});
println!(
"{}",
serde_json::to_string_pretty(&json).expect("Json pretty print failed")
);
},
OutputType::Text => {
println!(
"Public Key URI `{}` is account:\n \
Network ID/Version: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
uri,
String::from(network_override),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override),
);
},
}
} else {
println!("Invalid phrase/URI given");
}
}
/// Try to parse given `public` as hex encoded public key and print relevant information.
pub fn print_from_public<Pair>(
public_str: &str,
network_override: Option<Ss58AddressFormat>,
output: OutputType,
) -> Result<(), Error>
where
Pair: pezsp_core::Pair,
Pair::Public: Into<MultiSigner>,
{
let public = array_bytes::hex2bytes(public_str)?;
let public_key = Pair::Public::try_from(&public)
.map_err(|_| "Failed to construct public key from given hex")?;
let network_override = unwrap_or_default_ss58_version(network_override);
match output {
OutputType::Json => {
let json = json!({
"networkId": String::from(network_override),
"publicKey": format_public_key::<Pair>(public_key.clone()),
"accountId": format_account_id::<Pair>(public_key.clone()),
"ss58PublicKey": public_key.to_ss58check_with_version(network_override),
"ss58Address": public_key.to_ss58check_with_version(network_override),
});
println!("{}", serde_json::to_string_pretty(&json).expect("Json pretty print failed"));
},
OutputType::Text => {
println!(
"Network ID/Version: {}\n \
Public key (hex): {}\n \
Account ID: {}\n \
Public key (SS58): {}\n \
SS58 Address: {}",
String::from(network_override),
format_public_key::<Pair>(public_key.clone()),
format_account_id::<Pair>(public_key.clone()),
public_key.to_ss58check_with_version(network_override),
public_key.to_ss58check_with_version(network_override),
);
},
}
Ok(())
}
/// generate a pair from suri
pub fn pair_from_suri<P: Pair>(suri: &str, password: Option<SecretString>) -> Result<P, Error> {
let result = if let Some(pass) = password {
let mut pass_str = pass.expose_secret().clone();
let pair = P::from_string(suri, Some(&pass_str));
pass_str.zeroize();
pair
} else {
P::from_string(suri, None)
};
Ok(result.map_err(|err| format!("Invalid phrase {:?}", err))?)
}
/// formats seed as hex
pub fn format_seed<P: pezsp_core::Pair>(seed: SeedFor<P>) -> String {
format!("0x{}", HexDisplay::from(&seed.as_ref()))
}
/// formats public key as hex
fn format_public_key<P: pezsp_core::Pair>(public_key: PublicFor<P>) -> String {
format!("0x{}", HexDisplay::from(&public_key.as_ref()))
}
/// formats public key as accountId as hex
fn format_account_id<P: pezsp_core::Pair>(public_key: PublicFor<P>) -> String
where
PublicFor<P>: Into<MultiSigner>,
{
format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref()))
}
/// Allows for calling $method with appropriate crypto impl.
#[macro_export]
macro_rules! with_crypto_scheme {
(
$scheme:expr,
$method:ident ( $($params:expr),* $(,)?) $(,)?
) => {
$crate::with_crypto_scheme!($scheme, $method<>($($params),*))
};
(
$scheme:expr,
$method:ident<$($generics:ty),*>( $( $params:expr ),* $(,)?) $(,)?
) => {
match $scheme {
$crate::CryptoScheme::Ecdsa => {
$method::<pezsp_core::ecdsa::Pair, $($generics),*>($($params),*)
}
$crate::CryptoScheme::Sr25519 => {
$method::<pezsp_core::sr25519::Pair, $($generics),*>($($params),*)
}
$crate::CryptoScheme::Ed25519 => {
$method::<pezsp_core::ed25519::Pair, $($generics),*>($($params),*)
}
}
};
}
@@ -0,0 +1,226 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! implementation of the `vanity` subcommand
use crate::{
error, utils, with_crypto_scheme, CryptoSchemeFlag, NetworkSchemeFlag, OutputTypeFlag,
};
use clap::Parser;
use rand::{rngs::OsRng, RngCore};
use pezsp_core::crypto::{unwrap_or_default_ss58_version, Ss58AddressFormat, Ss58Codec};
use pezsp_runtime::traits::IdentifyAccount;
use utils::print_from_uri;
/// The `vanity` command
#[derive(Debug, Clone, Parser)]
#[command(name = "vanity", about = "Generate a seed that provides a vanity address")]
pub struct VanityCmd {
/// Desired pattern
#[arg(long, value_parser = assert_non_empty_string)]
pattern: String,
#[allow(missing_docs)]
#[clap(flatten)]
network_scheme: NetworkSchemeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
output_scheme: OutputTypeFlag,
#[allow(missing_docs)]
#[clap(flatten)]
crypto_scheme: CryptoSchemeFlag,
}
impl VanityCmd {
/// Run the command
pub fn run(&self) -> error::Result<()> {
let formatted_seed = with_crypto_scheme!(
self.crypto_scheme.scheme,
generate_key(
&self.pattern,
unwrap_or_default_ss58_version(self.network_scheme.network)
),
)?;
with_crypto_scheme!(
self.crypto_scheme.scheme,
print_from_uri(
&formatted_seed,
None,
self.network_scheme.network,
self.output_scheme.output_type,
),
);
Ok(())
}
}
/// genertae a key based on given pattern
fn generate_key<Pair>(
desired: &str,
network_override: Ss58AddressFormat,
) -> Result<String, &'static str>
where
Pair: pezsp_core::Pair,
Pair::Public: IdentifyAccount,
<Pair::Public as IdentifyAccount>::AccountId: Ss58Codec,
{
println!("Generating key containing pattern '{}'", desired);
let top = 45 + (desired.len() * 48);
let mut best = 0;
let mut seed = Pair::Seed::default();
let mut done = 0;
loop {
if done % 100000 == 0 {
OsRng.fill_bytes(seed.as_mut());
} else {
next_seed(seed.as_mut());
}
let p = Pair::from_seed(&seed);
let ss58 = p.public().into_account().to_ss58check_with_version(network_override);
let score = calculate_score(desired, &ss58);
if score > best || desired.len() < 2 {
best = score;
if best >= top {
println!("best: {} == top: {}", best, top);
return Ok(utils::format_seed::<Pair>(seed.clone()));
}
}
done += 1;
if done % good_waypoint(done) == 0 {
println!("{} keys searched; best is {}/{} complete", done, best, top);
}
}
}
fn good_waypoint(done: u64) -> u64 {
match done {
0..=1_000_000 => 100_000,
1_000_001..=10_000_000 => 1_000_000,
10_000_001..=100_000_000 => 10_000_000,
100_000_001.. => 100_000_000,
}
}
fn next_seed(seed: &mut [u8]) {
for s in seed {
match s {
255 => {
*s = 0;
},
_ => {
*s += 1;
break;
},
}
}
}
/// Calculate the score of a key based on the desired
/// input.
fn calculate_score(_desired: &str, key: &str) -> usize {
for truncate in 0.._desired.len() {
let snip_size = _desired.len() - truncate;
let truncated = &_desired[0..snip_size];
if let Some(pos) = key.find(truncated) {
return (47 - pos) + (snip_size * 48);
}
}
0
}
/// checks that `pattern` is non-empty
fn assert_non_empty_string(pattern: &str) -> Result<String, &'static str> {
if pattern.is_empty() {
Err("Pattern must not be empty")
} else {
Ok(pattern.to_string())
}
}
#[cfg(test)]
mod tests {
use super::*;
use pezsp_core::{
crypto::{default_ss58_version, Ss58AddressFormatRegistry, Ss58Codec},
sr25519, Pair,
};
#[test]
fn vanity() {
let vanity = VanityCmd::parse_from(&["vanity", "--pattern", "j"]);
assert!(vanity.run().is_ok());
}
#[test]
fn test_generation_with_single_char() {
let seed = generate_key::<sr25519::Pair>("ab", default_ss58_version()).unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.unwrap()
.public()
.to_ss58check()
.contains("ab"));
}
#[test]
fn generate_key_respects_network_override() {
let seed =
generate_key::<sr25519::Pair>("ab", Ss58AddressFormatRegistry::PezkuwiAccount.into())
.unwrap();
assert!(sr25519::Pair::from_seed_slice(&array_bytes::hex2bytes_unchecked(&seed))
.unwrap()
.public()
.to_ss58check_with_version(Ss58AddressFormatRegistry::PezkuwiAccount.into())
.contains("ab"));
}
#[test]
fn test_score_1_char_100() {
let score = calculate_score("j", "5jolkadotwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
assert_eq!(score, 94);
}
#[test]
fn test_score_100() {
let score = calculate_score("Pezkuwi", "5PezkuwiwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim");
assert_eq!(score, 430);
}
#[test]
fn test_score_50_2() {
// 50% for the position + 50% for the size
assert_eq!(
calculate_score("Pezkuwi", "5PolkXXXXwHY5k9GpdTgpqs9xjuNvtv8EcwCFpEeyEf3KHim"),
238
);
}
#[test]
fn test_score_0() {
assert_eq!(
calculate_score("Pezkuwi", "5GUWv4bLCchGUHJrzULXnh4JgXsMpTKRnjuXTY7Qo1Kh9uYK"),
0
);
}
}
@@ -0,0 +1,137 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! implementation of the `verify` subcommand
use crate::{error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag};
use clap::Parser;
use pezsp_core::crypto::{ByteArray, Ss58Codec};
use std::io::BufRead;
/// The `verify` command
#[derive(Debug, Clone, Parser)]
#[command(
name = "verify",
about = "Verify a signature for a message, provided on STDIN, with a given (public or secret) key"
)]
pub struct VerifyCmd {
/// Signature, hex-encoded.
sig: String,
/// The public or secret key URI.
/// If the value is a file, the file content is used as URI.
/// If not given, you will be prompted for the URI.
uri: Option<String>,
#[allow(missing_docs)]
#[clap(flatten)]
pub message_params: MessageParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub crypto_scheme: CryptoSchemeFlag,
}
impl VerifyCmd {
/// Run the command
pub fn run(&self) -> error::Result<()> {
self.verify(|| std::io::stdin().lock())
}
/// Verify a signature for a message.
///
/// The message can either be provided as immediate argument via CLI or otherwise read from the
/// reader created by `create_reader`. The reader will only be created in case that the message
/// is not passed as immediate.
pub(crate) fn verify<F, R>(&self, create_reader: F) -> error::Result<()>
where
R: BufRead,
F: FnOnce() -> R,
{
let message = self.message_params.message_from(create_reader)?;
let sig_data = array_bytes::hex2bytes(&self.sig)?;
let uri = utils::read_uri(self.uri.as_ref())?;
let uri = if let Some(uri) = uri.strip_prefix("0x") { uri } else { &uri };
with_crypto_scheme!(self.crypto_scheme.scheme, verify(sig_data, message, uri))
}
}
fn verify<Pair>(sig_data: Vec<u8>, message: Vec<u8>, uri: &str) -> error::Result<()>
where
Pair: pezsp_core::Pair,
Pair::Signature: for<'a> TryFrom<&'a [u8]>,
{
let signature =
Pair::Signature::try_from(&sig_data).map_err(|_| error::Error::SignatureFormatInvalid)?;
let pubkey = if let Ok(pubkey_vec) = array_bytes::hex2bytes(uri) {
Pair::Public::from_slice(pubkey_vec.as_slice())
.map_err(|_| error::Error::KeyFormatInvalid)?
} else {
Pair::Public::from_string(uri)?
};
if Pair::verify(&signature, &message, &pubkey) {
println!("Signature verifies correctly.");
} else {
return Err(error::Error::SignatureInvalid);
}
Ok(())
}
#[cfg(test)]
mod test {
use super::*;
const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY";
const SIG1: &str = "0x4eb25a2285a82374888880af0024eb30c3a21ce086eae3862888d345af607f0ad6fb081312f11730932564f24a9f8ebcee2d46861413ae61307eca58db2c3e81";
const SIG2: &str = "0x026342225155056ea797118c1c8c8b3cc002aa2020c36f4217fa3c302783a572ad3dcd38c231cbaf86cadb93984d329c963ceac0685cc1ee4c1ed50fa443a68f";
// Verify work with `--message` argument.
#[test]
fn verify_immediate() {
let cmd = VerifyCmd::parse_from(&["verify", SIG1, ALICE, "--message", "test message"]);
assert!(cmd.run().is_ok(), "Alice' signature should verify");
}
// Verify work without `--message` argument.
#[test]
fn verify_stdin() {
let cmd = VerifyCmd::parse_from(&["verify", SIG1, ALICE]);
let message = "test message";
assert!(cmd.verify(|| message.as_bytes()).is_ok(), "Alice' signature should verify");
}
// Verify work with `--message` argument for hex message.
#[test]
fn verify_immediate_hex() {
let cmd = VerifyCmd::parse_from(&["verify", SIG2, ALICE, "--message", "0xaabbcc", "--hex"]);
assert!(cmd.run().is_ok(), "Alice' signature should verify");
}
// Verify work without `--message` argument for hex message.
#[test]
fn verify_stdin_hex() {
let cmd = VerifyCmd::parse_from(&["verify", SIG2, ALICE, "--hex"]);
assert!(cmd.verify(|| "0xaabbcc".as_bytes()).is_ok());
assert!(cmd.verify(|| "aabbcc".as_bytes()).is_ok());
assert!(cmd.verify(|| "0xaABBcC".as_bytes()).is_ok());
}
}
+730
View File
@@ -0,0 +1,730 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Configuration trait for a CLI based on bizinikiwi
use crate::{
arg_enums::Database, error::Result, DatabaseParams, ImportParams, KeystoreParams,
NetworkParams, NodeKeyParams, OffchainWorkerParams, PruningParams, RpcEndpoint, SharedParams,
BizinikiwiCli,
};
use log::warn;
use names::{Generator, Name};
use pezsc_service::{
config::{
BasePath, Configuration, DatabaseSource, ExecutorConfiguration, IpNetwork, KeystoreConfig,
NetworkConfiguration, NodeKeyConfig, OffchainWorkerConfig, PrometheusConfig, PruningMode,
Role, RpcBatchRequestConfig, RpcConfiguration, RpcMethods, TelemetryEndpoints,
TransactionPoolOptions, WasmExecutionMethod,
},
BlocksPruning, ChainSpec, TracingReceiver,
};
use pezsc_tracing::logging::LoggerBuilder;
use std::{num::NonZeroU32, path::PathBuf};
/// The maximum number of characters for a node name.
pub(crate) const NODE_NAME_MAX_LENGTH: usize = 64;
/// Default sub directory to store network config.
pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &str = "network";
/// The recommended open file descriptor limit to be configured for the process.
const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000;
/// The default port.
pub const RPC_DEFAULT_PORT: u16 = 9944;
/// The default max number of subscriptions per connection.
pub const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024;
/// The default max request size in MB.
pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15;
/// The default max response size in MB.
pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15;
/// The default concurrent connection limit.
pub const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 100;
/// The default number of messages the RPC server
/// is allowed to keep in memory per connection.
pub const RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN: u32 = 64;
/// Default configuration values used by Bizinikiwi
///
/// These values will be used by [`CliConfiguration`] to set
/// default values for e.g. the listen port or the RPC port.
pub trait DefaultConfigurationValues {
/// The port Bizinikiwi should listen on for p2p connections.
///
/// By default this is `30333`.
fn p2p_listen_port() -> u16 {
30333
}
/// The port Bizinikiwi should listen on for JSON-RPC connections.
///
/// By default this is `9944`.
fn rpc_listen_port() -> u16 {
RPC_DEFAULT_PORT
}
/// The port Bizinikiwi should listen on for prometheus connections.
///
/// By default this is `9615`.
fn prometheus_listen_port() -> u16 {
9615
}
}
impl DefaultConfigurationValues for () {}
/// A trait that allows converting an object to a Configuration
pub trait CliConfiguration<DCV: DefaultConfigurationValues = ()>: Sized {
/// Get the SharedParams for this object
fn shared_params(&self) -> &SharedParams;
/// Get the ImportParams for this object
fn import_params(&self) -> Option<&ImportParams> {
None
}
/// Get the PruningParams for this object
fn pruning_params(&self) -> Option<&PruningParams> {
self.import_params().map(|x| &x.pruning_params)
}
/// Get the KeystoreParams for this object
fn keystore_params(&self) -> Option<&KeystoreParams> {
None
}
/// Get the NetworkParams for this object
fn network_params(&self) -> Option<&NetworkParams> {
None
}
/// Get a reference to `OffchainWorkerParams` for this object.
fn offchain_worker_params(&self) -> Option<&OffchainWorkerParams> {
None
}
/// Get the NodeKeyParams for this object
fn node_key_params(&self) -> Option<&NodeKeyParams> {
self.network_params().map(|x| &x.node_key_params)
}
/// Get the DatabaseParams for this object
fn database_params(&self) -> Option<&DatabaseParams> {
self.import_params().map(|x| &x.database_params)
}
/// Get the base path of the configuration (if any)
///
/// By default this is retrieved from `SharedParams`.
fn base_path(&self) -> Result<Option<BasePath>> {
self.shared_params().base_path()
}
/// Returns `true` if the node is for development or not
///
/// By default this is retrieved from `SharedParams`.
fn is_dev(&self) -> Result<bool> {
Ok(self.shared_params().is_dev())
}
/// Gets the role
///
/// By default this is `Role::Full`.
fn role(&self, _is_dev: bool) -> Result<Role> {
Ok(Role::Full)
}
/// Get the transaction pool options
///
/// By default this is `TransactionPoolOptions::default()`.
fn transaction_pool(&self, _is_dev: bool) -> Result<TransactionPoolOptions> {
Ok(Default::default())
}
/// Get the network configuration
///
/// By default this is retrieved from `NetworkParams` if it is available otherwise it creates
/// a default `NetworkConfiguration` based on `node_name`, `client_id`, `node_key` and
/// `net_config_dir`.
fn network_config(
&self,
chain_spec: &Box<dyn ChainSpec>,
is_dev: bool,
is_validator: bool,
net_config_dir: PathBuf,
client_id: &str,
node_name: &str,
node_key: NodeKeyConfig,
default_listen_port: u16,
) -> Result<NetworkConfiguration> {
let network_config = if let Some(network_params) = self.network_params() {
network_params.network_config(
chain_spec,
is_dev,
is_validator,
Some(net_config_dir),
client_id,
node_name,
node_key,
default_listen_port,
)
} else {
NetworkConfiguration::new(node_name, client_id, node_key, Some(net_config_dir))
};
// TODO: Return error here in the next release:
// https://github.com/pezkuwichain/pezkuwi-sdk/issues/139
// if is_validator && network_config.public_addresses.is_empty() {}
Ok(network_config)
}
/// Get the keystore configuration.
///
/// By default this is retrieved from `KeystoreParams` if it is available. Otherwise it uses
/// `KeystoreConfig::InMemory`.
fn keystore_config(&self, config_dir: &PathBuf) -> Result<KeystoreConfig> {
self.keystore_params()
.map(|x| x.keystore_config(config_dir))
.unwrap_or_else(|| Ok(KeystoreConfig::InMemory))
}
/// Get the database cache size.
///
/// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`.
fn database_cache_size(&self) -> Result<Option<usize>> {
Ok(self.database_params().map(|x| x.database_cache_size()).unwrap_or_default())
}
/// Get the database backend variant.
///
/// By default this is retrieved from `DatabaseParams` if it is available. Otherwise its `None`.
fn database(&self) -> Result<Option<Database>> {
Ok(self.database_params().and_then(|x| x.database()))
}
/// Get the database configuration object for the parameters provided
fn database_config(
&self,
base_path: &PathBuf,
cache_size: usize,
database: Database,
) -> Result<DatabaseSource> {
let role_dir = "full";
let rocksdb_path = base_path.join("db").join(role_dir);
let paritydb_path = base_path.join("paritydb").join(role_dir);
Ok(match database {
#[cfg(feature = "rocksdb")]
Database::RocksDb => DatabaseSource::RocksDb { path: rocksdb_path, cache_size },
Database::ParityDb => DatabaseSource::ParityDb { path: paritydb_path },
Database::ParityDbDeprecated => {
eprintln!(
"WARNING: \"paritydb-experimental\" database setting is deprecated and will be removed in future releases. \
Please update your setup to use the new value: \"paritydb\"."
);
DatabaseSource::ParityDb { path: paritydb_path }
},
Database::Auto => DatabaseSource::Auto { paritydb_path, rocksdb_path, cache_size },
})
}
/// Get the trie cache maximum size.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its `0`.
/// If `None` is returned the trie cache is disabled.
fn trie_cache_maximum_size(&self) -> Result<Option<usize>> {
Ok(self.import_params().map(|x| x.trie_cache_maximum_size()).unwrap_or_default())
}
/// Get if we should warm up the trie cache.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its `None`.
fn warm_up_trie_cache(&self) -> Result<Option<pezsc_service::config::TrieCacheWarmUpStrategy>> {
Ok(self
.import_params()
.map(|x| x.warm_up_trie_cache().map(|x| x.into()))
.unwrap_or_default())
}
/// Get the state pruning mode.
///
/// By default this is retrieved from `PruningMode` if it is available. Otherwise its
/// `PruningMode::default()`.
fn state_pruning(&self) -> Result<Option<PruningMode>> {
self.pruning_params()
.map(|x| x.state_pruning())
.unwrap_or_else(|| Ok(Default::default()))
}
/// Get the block pruning mode.
///
/// By default this is retrieved from `block_pruning` if it is available. Otherwise its
/// `BlocksPruning::KeepFinalized`.
fn blocks_pruning(&self) -> Result<BlocksPruning> {
self.pruning_params()
.map(|x| x.blocks_pruning())
.unwrap_or_else(|| Ok(BlocksPruning::KeepFinalized))
}
/// Get the chain ID (string).
///
/// By default this is retrieved from `SharedParams`.
fn chain_id(&self, is_dev: bool) -> Result<String> {
Ok(self.shared_params().chain_id(is_dev))
}
/// Get the name of the node.
///
/// By default a random name is generated.
fn node_name(&self) -> Result<String> {
Ok(generate_node_name())
}
/// Get the WASM execution method.
///
/// By default this is retrieved from `ImportParams` if it is available. Otherwise its
/// `WasmExecutionMethod::default()`.
fn wasm_method(&self) -> Result<WasmExecutionMethod> {
Ok(self.import_params().map(|x| x.wasm_method()).unwrap_or_default())
}
/// Get the path where WASM overrides live.
///
/// By default this is `None`.
fn wasm_runtime_overrides(&self) -> Option<PathBuf> {
self.import_params().map(|x| x.wasm_runtime_overrides()).unwrap_or_default()
}
/// Get the RPC address.
fn rpc_addr(&self, _default_listen_port: u16) -> Result<Option<Vec<RpcEndpoint>>> {
Ok(None)
}
/// Returns the RPC method set to expose.
///
/// By default this is `RpcMethods::Auto` (unsafe RPCs are denied iff
/// `rpc_external` returns true, respectively).
fn rpc_methods(&self) -> Result<RpcMethods> {
Ok(Default::default())
}
/// Get the maximum number of RPC server connections.
fn rpc_max_connections(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_CONNECTIONS)
}
/// Get the RPC cors (`None` if disabled)
///
/// By default this is `Some(Vec::new())`.
fn rpc_cors(&self, _is_dev: bool) -> Result<Option<Vec<String>>> {
Ok(Some(Vec::new()))
}
/// Get maximum RPC request payload size.
fn rpc_max_request_size(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_REQUEST_SIZE_MB)
}
/// Get maximum RPC response payload size.
fn rpc_max_response_size(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)
}
/// Get maximum number of subscriptions per connection.
fn rpc_max_subscriptions_per_connection(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MAX_SUBS_PER_CONN)
}
/// The number of messages the RPC server is allowed to keep in memory per connection.
fn rpc_buffer_capacity_per_connection(&self) -> Result<u32> {
Ok(RPC_DEFAULT_MESSAGE_CAPACITY_PER_CONN)
}
/// RPC server batch request configuration.
fn rpc_batch_config(&self) -> Result<RpcBatchRequestConfig> {
Ok(RpcBatchRequestConfig::Unlimited)
}
/// RPC rate limit configuration.
fn rpc_rate_limit(&self) -> Result<Option<NonZeroU32>> {
Ok(None)
}
/// RPC rate limit whitelisted ip addresses.
fn rpc_rate_limit_whitelisted_ips(&self) -> Result<Vec<IpNetwork>> {
Ok(vec![])
}
/// RPC rate limit trust proxy headers.
fn rpc_rate_limit_trust_proxy_headers(&self) -> Result<bool> {
Ok(false)
}
/// Get the prometheus configuration (`None` if disabled)
///
/// By default this is `None`.
fn prometheus_config(
&self,
_default_listen_port: u16,
_chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<PrometheusConfig>> {
Ok(None)
}
/// Get the telemetry endpoints (if any)
///
/// By default this is retrieved from the chain spec loaded by `load_spec`.
fn telemetry_endpoints(
&self,
chain_spec: &Box<dyn ChainSpec>,
) -> Result<Option<TelemetryEndpoints>> {
Ok(chain_spec.telemetry_endpoints().clone())
}
/// Get the default value for heap pages
///
/// By default this is `None`.
fn default_heap_pages(&self) -> Result<Option<u64>> {
Ok(None)
}
/// Returns an offchain worker config wrapped in `Ok(_)`
///
/// By default offchain workers are disabled.
fn offchain_worker(&self, role: &Role) -> Result<OffchainWorkerConfig> {
self.offchain_worker_params()
.map(|x| x.offchain_worker(role))
.unwrap_or_else(|| Ok(OffchainWorkerConfig::default()))
}
/// Returns `Ok(true)` if authoring should be forced
///
/// By default this is `false`.
fn force_authoring(&self) -> Result<bool> {
Ok(Default::default())
}
/// Returns `Ok(true)` if grandpa should be disabled
///
/// By default this is `false`.
fn disable_grandpa(&self) -> Result<bool> {
Ok(Default::default())
}
/// Get the development key seed from the current object
///
/// By default this is `None`.
fn dev_key_seed(&self, _is_dev: bool) -> Result<Option<String>> {
Ok(Default::default())
}
/// Get the tracing targets from the current object (if any)
///
/// By default this is retrieved from [`SharedParams`] if it is available. Otherwise its
/// `None`.
fn tracing_targets(&self) -> Result<Option<String>> {
Ok(self.shared_params().tracing_targets())
}
/// Get the TracingReceiver value from the current object
///
/// By default this is retrieved from [`SharedParams`] if it is available. Otherwise its
/// `TracingReceiver::default()`.
fn tracing_receiver(&self) -> Result<TracingReceiver> {
Ok(self.shared_params().tracing_receiver())
}
/// Get the node key from the current object
///
/// By default this is retrieved from `NodeKeyParams` if it is available. Otherwise its
/// `NodeKeyConfig::default()`.
fn node_key(&self, net_config_dir: &PathBuf) -> Result<NodeKeyConfig> {
let is_dev = self.is_dev()?;
let role = self.role(is_dev)?;
self.node_key_params()
.map(|x| x.node_key(net_config_dir, role, is_dev))
.unwrap_or_else(|| Ok(Default::default()))
}
/// Get maximum runtime instances
///
/// By default this is `None`.
fn max_runtime_instances(&self) -> Result<Option<usize>> {
Ok(Default::default())
}
/// Get maximum different runtimes in cache
///
/// By default this is `2`.
fn runtime_cache_size(&self) -> Result<u8> {
Ok(2)
}
/// Activate or not the automatic announcing of blocks after import
///
/// By default this is `false`.
fn announce_block(&self) -> Result<bool> {
Ok(true)
}
/// Create a Configuration object from the current object
fn create_configuration<C: BizinikiwiCli>(
&self,
cli: &C,
tokio_handle: tokio::runtime::Handle,
) -> Result<Configuration> {
let is_dev = self.is_dev()?;
let chain_id = self.chain_id(is_dev)?;
let chain_spec = cli.load_spec(&chain_id)?;
let base_path = base_path_or_default(self.base_path()?, &C::executable_name());
let config_dir = build_config_dir(&base_path, chain_spec.id());
let net_config_dir = build_net_config_dir(&config_dir);
let client_id = C::client_id();
let database_cache_size = self.database_cache_size()?.unwrap_or(1024);
let database = self.database()?.unwrap_or(
#[cfg(feature = "rocksdb")]
{
Database::RocksDb
},
#[cfg(not(feature = "rocksdb"))]
{
Database::ParityDb
},
);
let node_key = self.node_key(&net_config_dir)?;
let role = self.role(is_dev)?;
let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8);
let is_validator = role.is_authority();
let keystore = self.keystore_config(&config_dir)?;
let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?;
let runtime_cache_size = self.runtime_cache_size()?;
let rpc_addrs: Option<Vec<pezsc_service::config::RpcEndpoint>> = self
.rpc_addr(DCV::rpc_listen_port())?
.map(|addrs| addrs.into_iter().map(Into::into).collect());
Ok(Configuration {
impl_name: C::impl_name(),
impl_version: C::impl_version(),
tokio_handle,
transaction_pool: self.transaction_pool(is_dev)?,
network: self.network_config(
&chain_spec,
is_dev,
is_validator,
net_config_dir,
client_id.as_str(),
self.node_name()?.as_str(),
node_key,
DCV::p2p_listen_port(),
)?,
keystore,
database: self.database_config(&config_dir, database_cache_size, database)?,
data_path: config_dir,
trie_cache_maximum_size: self.trie_cache_maximum_size()?,
warm_up_trie_cache: self.warm_up_trie_cache()?,
state_pruning: self.state_pruning()?,
blocks_pruning: self.blocks_pruning()?,
executor: ExecutorConfiguration {
wasm_method: self.wasm_method()?,
default_heap_pages: self.default_heap_pages()?,
max_runtime_instances,
runtime_cache_size,
},
wasm_runtime_overrides: self.wasm_runtime_overrides(),
rpc: RpcConfiguration {
addr: rpc_addrs,
methods: self.rpc_methods()?,
max_connections: self.rpc_max_connections()?,
cors: self.rpc_cors(is_dev)?,
max_request_size: self.rpc_max_request_size()?,
max_response_size: self.rpc_max_response_size()?,
id_provider: None,
max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?,
port: DCV::rpc_listen_port(),
message_buffer_capacity: self.rpc_buffer_capacity_per_connection()?,
batch_config: self.rpc_batch_config()?,
rate_limit: self.rpc_rate_limit()?,
rate_limit_whitelisted_ips: self.rpc_rate_limit_whitelisted_ips()?,
rate_limit_trust_proxy_headers: self.rpc_rate_limit_trust_proxy_headers()?,
request_logger_limit: if is_dev { 1024 * 1024 } else { 1024 },
},
prometheus_config: self
.prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?,
telemetry_endpoints,
offchain_worker: self.offchain_worker(&role)?,
force_authoring: self.force_authoring()?,
disable_grandpa: self.disable_grandpa()?,
dev_key_seed: self.dev_key_seed(is_dev)?,
tracing_targets: self.tracing_targets()?,
tracing_receiver: self.tracing_receiver()?,
chain_spec,
announce_block: self.announce_block()?,
role,
base_path,
})
}
/// Get the filters for the logging.
///
/// This should be a list of comma-separated values.
/// Example: `foo=trace,bar=debug,baz=info`
///
/// By default this is retrieved from `SharedParams`.
fn log_filters(&self) -> Result<String> {
Ok(self.shared_params().log_filters().join(","))
}
/// Should the detailed log output be enabled.
fn detailed_log_output(&self) -> Result<bool> {
Ok(self.shared_params().detailed_log_output())
}
/// Is log reloading enabled?
fn enable_log_reloading(&self) -> Result<bool> {
Ok(self.shared_params().enable_log_reloading())
}
/// Should the log color output be disabled?
fn disable_log_color(&self) -> Result<bool> {
Ok(self.shared_params().disable_log_color())
}
/// Initialize bizinikiwi. This must be done only once per process.
///
/// This method:
///
/// 1. Sets the panic handler
/// 2. Optionally customize logger/profiling
/// 2. Initializes the logger
/// 3. Raises the FD limit
///
/// The `logger_hook` closure is executed before the logger is constructed
/// and initialized. It is useful for setting up a custom profiler.
///
/// Example:
/// ```
/// use pezsc_tracing::{SpanDatum, TraceEvent};
/// struct TestProfiler;
///
/// impl pezsc_tracing::TraceHandler for TestProfiler {
/// fn handle_span(&self, sd: &SpanDatum) {}
/// fn handle_event(&self, _event: &TraceEvent) {}
/// };
///
/// fn logger_hook() -> impl FnOnce(&mut pezsc_cli::LoggerBuilder, &pezsc_service::Configuration) -> () {
/// |logger_builder, config| {
/// logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
/// }
/// }
/// ```
fn init<F>(&self, support_url: &String, impl_version: &String, logger_hook: F) -> Result<()>
where
F: FnOnce(&mut LoggerBuilder),
{
pezsp_panic_handler::set(support_url, impl_version);
let mut logger = LoggerBuilder::new(self.log_filters()?);
logger
.with_log_reloading(self.enable_log_reloading()?)
.with_detailed_output(self.detailed_log_output()?);
if let Some(tracing_targets) = self.tracing_targets()? {
let tracing_receiver = self.tracing_receiver()?;
logger.with_profiling(tracing_receiver, tracing_targets);
}
if self.disable_log_color()? {
logger.with_colors(false);
}
// Call hook for custom profiling setup.
logger_hook(&mut logger);
logger.init()?;
match fdlimit::raise_fd_limit() {
Ok(fdlimit::Outcome::LimitRaised { to, .. }) => {
if to < RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT {
warn!(
"Low open file descriptor limit configured for the process. \
Current value: {:?}, recommended value: {:?}.",
to, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
);
}
},
Ok(fdlimit::Outcome::Unsupported) => {
// Unsupported platform (non-Linux)
},
Err(error) => {
warn!(
"Failed to configure file descriptor limit for the process: \
{}, recommended value: {:?}.",
error, RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT,
);
},
}
Ok(())
}
}
/// Generate a valid random name for the node
pub fn generate_node_name() -> String {
loop {
let node_name = Generator::with_naming(Name::Numbered)
.next()
.expect("RNG is available on all supported platforms; qed");
let count = node_name.chars().count();
if count < NODE_NAME_MAX_LENGTH {
return node_name;
}
}
}
/// Returns the value of `base_path` or the default_path if it is None
pub(crate) fn base_path_or_default(
base_path: Option<BasePath>,
executable_name: &String,
) -> BasePath {
base_path.unwrap_or_else(|| BasePath::from_project("", "", executable_name))
}
/// Returns the default path for configuration directory based on the chain_spec
pub(crate) fn build_config_dir(base_path: &BasePath, chain_spec_id: &str) -> PathBuf {
base_path.config_dir(chain_spec_id)
}
/// Returns the default path for the network configuration inside the configuration dir
pub(crate) fn build_net_config_dir(config_dir: &PathBuf) -> PathBuf {
config_dir.join(DEFAULT_NETWORK_CONFIG_PATH)
}
/// Returns the default path for the network directory starting from the provided base_path
/// or from the default base_path.
pub(crate) fn build_network_key_dir_or_default(
base_path: Option<BasePath>,
chain_spec_id: &str,
executable_name: &String,
) -> PathBuf {
let config_dir =
build_config_dir(&base_path_or_default(base_path, executable_name), chain_spec_id);
build_net_config_dir(&config_dir)
}
+121
View File
@@ -0,0 +1,121 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Initialization errors.
use std::path::PathBuf;
use pezsp_core::crypto;
/// Result type alias for the CLI.
pub type Result<T> = std::result::Result<T, Error>;
/// Error type for the CLI.
#[derive(Debug, thiserror::Error)]
#[allow(missing_docs)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
Cli(#[from] clap::Error),
#[error(transparent)]
Service(#[from] pezsc_service::Error),
#[error(transparent)]
Client(#[from] pezsp_blockchain::Error),
#[error(transparent)]
Codec(#[from] codec::Error),
#[error("Invalid input: {0}")]
Input(String),
#[error("Invalid listen multiaddress")]
InvalidListenMultiaddress,
#[error("Invalid URI; expecting either a secret URI or a public URI.")]
InvalidUri(crypto::PublicError),
#[error("Signature is an invalid format.")]
SignatureFormatInvalid,
#[error("Key is an invalid format.")]
KeyFormatInvalid,
#[error("Unknown key type, must be a known 4-character sequence")]
KeyTypeInvalid,
#[error("Signature verification failed")]
SignatureInvalid,
#[error("Key store operation failed")]
KeystoreOperation,
#[error("Key storage issue encountered")]
KeyStorage(#[from] pezsc_keystore::Error),
#[error("Invalid hexadecimal string data, {0:?}")]
HexDataConversion(array_bytes::Error),
/// Application specific error chain sequence forwarder.
#[error(transparent)]
Application(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error(transparent)]
GlobalLoggerError(#[from] pezsc_tracing::logging::Error),
#[error(
"Starting an authorithy without network key in {0}.
\n This is not a safe operation because other authorities in the network may depend on your node having a stable identity.
\n Otherwise these other authorities may not being able to reach you.
\n If it is the first time running your node you could use one of the following methods:
\n 1. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --base-path <YOUR_BASE_PATH>
\n 2. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --file <YOUR_PATH_TO_NODE_KEY>
\n 3. [Preferred] Separately generate the key with: <NODE_BINARY> key generate-node-key --default-base-path
\n 4. [Unsafe] Pass --unsafe-force-node-key-generation and make sure you remove it for subsequent node restarts"
)]
NetworkKeyNotFound(PathBuf),
#[error("A network key already exists in path {0}")]
KeyAlreadyExistsInPath(PathBuf),
}
impl From<&str> for Error {
fn from(s: &str) -> Error {
Error::Input(s.to_string())
}
}
impl From<String> for Error {
fn from(s: String) -> Error {
Error::Input(s)
}
}
impl From<crypto::PublicError> for Error {
fn from(e: crypto::PublicError) -> Error {
Error::InvalidUri(e)
}
}
impl From<array_bytes::Error> for Error {
fn from(e: array_bytes::Error) -> Error {
Error::HexDataConversion(e)
}
}
+253
View File
@@ -0,0 +1,253 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Bizinikiwi CLI library.
//!
//! To see a full list of commands available, see [`commands`].
#![warn(missing_docs)]
#![warn(unused_extern_crates)]
#![warn(unused_imports)]
use clap::{CommandFactory, FromArgMatches, Parser};
use log::warn;
use pezsc_service::Configuration;
pub mod arg_enums;
pub mod commands;
mod config;
mod error;
mod params;
mod runner;
mod signals;
pub use arg_enums::*;
pub use clap;
pub use commands::*;
pub use config::*;
pub use error::*;
pub use params::*;
pub use runner::*;
pub use pezsc_service::{ChainSpec, Role};
pub use pezsc_tracing::logging::LoggerBuilder;
pub use signals::Signals;
pub use pezsp_version::RuntimeVersion;
/// Bizinikiwi client CLI
///
/// This trait needs to be implemented on the root CLI struct of the application. It will provide
/// the implementation `name`, `version`, `executable name`, `description`, `author`, `support_url`,
/// `copyright start year` and most importantly: how to load the chain spec.
pub trait BizinikiwiCli: Sized {
/// Implementation name.
fn impl_name() -> String;
/// Implementation version.
///
/// By default, it will look like this:
///
/// `2.0.0-b950f731c`
///
/// Where the hash is the short hash of the commit in the Git repository.
fn impl_version() -> String;
/// Executable file name.
///
/// Extracts the file name from `std::env::current_exe()`.
/// Resorts to the env var `CARGO_PKG_NAME` in case of Error.
fn executable_name() -> String {
std::env::current_exe()
.ok()
.and_then(|e| e.file_name().map(|s| s.to_os_string()))
.and_then(|w| w.into_string().ok())
.unwrap_or_else(|| env!("CARGO_PKG_NAME").into())
}
/// Executable file description.
fn description() -> String;
/// Executable file author.
fn author() -> String;
/// Support URL.
fn support_url() -> String;
/// Copyright starting year (x-current year)
fn copyright_start_year() -> i32;
/// Chain spec factory
fn load_spec(&self, id: &str) -> std::result::Result<Box<dyn ChainSpec>, String>;
/// Helper function used to parse the command line arguments. This is the equivalent of
/// [`clap::Parser::parse()`].
///
/// To allow running the node without subcommand, it also sets a few more settings:
/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
/// [`clap::Command::subcommand_negates_reqs`].
///
/// Creates `Self` from the command line arguments. Print the
/// error message and quit the program in case of failure.
fn from_args() -> Self
where
Self: Parser + Sized,
{
<Self as BizinikiwiCli>::from_iter(&mut std::env::args_os())
}
/// Helper function used to parse the command line arguments. This is the equivalent of
/// [`clap::Parser::parse_from`].
///
/// To allow running the node without subcommand, it also sets a few more settings:
/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
/// [`clap::Command::subcommand_negates_reqs`].
///
/// Creates `Self` from any iterator over arguments.
/// Print the error message and quit the program in case of failure.
fn from_iter<I>(iter: I) -> Self
where
Self: Parser + Sized,
I: IntoIterator,
I::Item: Into<std::ffi::OsString> + Clone,
{
let app = <Self as CommandFactory>::command();
let app = Self::setup_command(app);
let matches = app.try_get_matches_from(iter).unwrap_or_else(|e| e.exit());
<Self as FromArgMatches>::from_arg_matches(&matches).unwrap_or_else(|e| e.exit())
}
/// Helper function used to parse the command line arguments. This is the equivalent of
/// [`clap::Parser::try_parse_from`]
///
/// To allow running the node without subcommand, it also sets a few more settings:
/// [`clap::Command::propagate_version`], [`clap::Command::args_conflicts_with_subcommands`],
/// [`clap::Command::subcommand_negates_reqs`].
///
/// Creates `Self` from any iterator over arguments.
/// Print the error message and quit the program in case of failure.
///
/// **NOTE:** This method WILL NOT exit when `--help` or `--version` (or short versions) are
/// used. It will return a [`clap::Error`], where the [`clap::Error::kind`] is a
/// [`clap::error::ErrorKind::DisplayHelp`] or [`clap::error::ErrorKind::DisplayVersion`]
/// respectively. You must call [`clap::Error::exit`] or perform a [`std::process::exit`].
fn try_from_iter<I>(iter: I) -> clap::error::Result<Self>
where
Self: Parser + Sized,
I: IntoIterator,
I::Item: Into<std::ffi::OsString> + Clone,
{
let app = <Self as CommandFactory>::command();
let app = Self::setup_command(app);
let matches = app.try_get_matches_from(iter)?;
<Self as FromArgMatches>::from_arg_matches(&matches)
}
/// Returns the client ID: `{impl_name}/v{impl_version}`
fn client_id() -> String {
format!("{}/v{}", Self::impl_name(), Self::impl_version())
}
/// Only create a Configuration for the command provided in argument
fn create_configuration<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
&self,
command: &T,
tokio_handle: tokio::runtime::Handle,
) -> error::Result<Configuration> {
command.create_configuration(self, tokio_handle)
}
/// Create a runner for the command provided in argument. This will create a Configuration and
/// a tokio runtime
fn create_runner<T: CliConfiguration<DVC>, DVC: DefaultConfigurationValues>(
&self,
command: &T,
) -> Result<Runner<Self>> {
self.create_runner_with_logger_hook(command, |_, _| {})
}
/// Create a runner for the command provided in argument. The `logger_hook` can be used to setup
/// a custom profiler or update the logger configuration before it is initialized.
///
/// Example:
/// ```
/// use pezsc_tracing::{SpanDatum, TraceEvent};
/// struct TestProfiler;
///
/// impl pezsc_tracing::TraceHandler for TestProfiler {
/// fn handle_span(&self, sd: &SpanDatum) {}
/// fn handle_event(&self, _event: &TraceEvent) {}
/// };
///
/// fn logger_hook() -> impl FnOnce(&mut pezsc_cli::LoggerBuilder, &pezsc_service::Configuration) -> () {
/// |logger_builder, config| {
/// logger_builder.with_custom_profiling(Box::new(TestProfiler{}));
/// }
/// }
/// ```
fn create_runner_with_logger_hook<
T: CliConfiguration<DVC>,
DVC: DefaultConfigurationValues,
F,
>(
&self,
command: &T,
logger_hook: F,
) -> Result<Runner<Self>>
where
F: FnOnce(&mut LoggerBuilder, &Configuration),
{
let tokio_runtime = build_runtime()?;
// `capture` needs to be called in a tokio context.
// Also capture them as early as possible.
let signals = tokio_runtime.block_on(async { Signals::capture() })?;
let config = command.create_configuration(self, tokio_runtime.handle().clone())?;
command.init(&Self::support_url(), &Self::impl_version(), |logger_builder| {
logger_hook(logger_builder, &config)
})?;
Runner::new(config, tokio_runtime, signals)
}
/// Augments a `clap::Command` with standard metadata like name, version, author, description,
/// etc.
///
/// This is used internally in `from_iter`, `try_from_iter` and can be used externally
/// to manually set up a command with Bizinikiwi CLI defaults.
fn setup_command(mut cmd: clap::Command) -> clap::Command {
let mut full_version = Self::impl_version();
full_version.push('\n');
cmd = cmd
.name(Self::executable_name())
.version(full_version)
.author(Self::author())
.about(Self::description())
.long_about(Self::description())
.after_help(format!("Support: {}", Self::support_url()))
.propagate_version(true)
.args_conflicts_with_subcommands(true)
.subcommand_negates_reqs(true);
cmd
}
}
@@ -0,0 +1,44 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::arg_enums::Database;
use clap::Args;
/// Parameters for database
#[derive(Debug, Clone, PartialEq, Args)]
pub struct DatabaseParams {
/// Select database backend to use.
#[arg(long, alias = "db", value_name = "DB", ignore_case = true, value_enum)]
pub database: Option<Database>,
/// Limit the memory the database cache can use.
#[arg(long = "db-cache", value_name = "MiB")]
pub database_cache_size: Option<usize>,
}
impl DatabaseParams {
/// Database backend
pub fn database(&self) -> Option<Database> {
self.database
}
/// Limit the memory the database cache can use.
pub fn database_cache_size(&self) -> Option<usize> {
self.database_cache_size
}
}
@@ -0,0 +1,204 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
arg_enums::{
ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy,
DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD,
},
params::{DatabaseParams, PruningParams},
};
use clap::{Args, ValueEnum};
use std::path::PathBuf;
/// Parameters for block import.
#[derive(Debug, Clone, Args)]
pub struct ImportParams {
#[allow(missing_docs)]
#[clap(flatten)]
pub pruning_params: PruningParams,
#[allow(missing_docs)]
#[clap(flatten)]
pub database_params: DatabaseParams,
/// Method for executing Wasm runtime code.
#[arg(
long = "wasm-execution",
value_name = "METHOD",
value_enum,
ignore_case = true,
default_value_t = DEFAULT_WASM_EXECUTION_METHOD,
)]
pub wasm_method: WasmExecutionMethod,
/// The WASM instantiation method to use.
///
/// Only has an effect when `wasm-execution` is set to `compiled`.
/// The copy-on-write strategies are only supported on Linux.
/// If the copy-on-write variant of a strategy is unsupported
/// the executor will fall back to the non-CoW equivalent.
/// The fastest (and the default) strategy available is `pooling-copy-on-write`.
/// The `legacy-instance-reuse` strategy is deprecated and will
/// be removed in the future. It should only be used in case of
/// issues with the default instantiation strategy.
#[arg(
long,
value_name = "STRATEGY",
default_value_t = DEFAULT_WASMTIME_INSTANTIATION_STRATEGY,
value_enum,
)]
pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy,
/// Specify the path where local WASM runtimes are stored.
///
/// These runtimes will override on-chain runtimes when the version matches.
#[arg(long, value_name = "PATH")]
pub wasm_runtime_overrides: Option<PathBuf>,
#[allow(missing_docs)]
#[clap(flatten)]
pub execution_strategies: ExecutionStrategiesParams,
/// Specify the state cache size.
///
/// Providing `0` will disable the cache.
#[arg(long, value_name = "Bytes", default_value_t = 1024 * 1024 * 1024)]
pub trie_cache_size: usize,
/// Warm up the trie cache.
///
/// No warmup if flag is not present. Using flag without value chooses non-blocking warmup.
#[arg(long, value_name = "STRATEGY", value_enum, num_args = 0..=1, default_missing_value = "non-blocking")]
pub warm_up_trie_cache: Option<TrieCacheWarmUpStrategy>,
}
/// Warmup strategy for the trie cache.
#[derive(Debug, Clone, Copy, ValueEnum)]
pub enum TrieCacheWarmUpStrategy {
/// Warm up the cache in a non-blocking way.
#[clap(name = "non-blocking")]
NonBlocking,
/// Warm up the cache in a blocking way (not recommended for production use).
///
/// When enabled, the trie cache warm-up will block the node startup until complete.
/// This is not recommended for production use as it can significantly delay node startup.
/// Only enable this option for testing or debugging purposes.
#[clap(name = "blocking")]
Blocking,
}
impl From<TrieCacheWarmUpStrategy> for pezsc_service::config::TrieCacheWarmUpStrategy {
fn from(strategy: TrieCacheWarmUpStrategy) -> Self {
match strategy {
TrieCacheWarmUpStrategy::NonBlocking =>
pezsc_service::config::TrieCacheWarmUpStrategy::NonBlocking,
TrieCacheWarmUpStrategy::Blocking =>
pezsc_service::config::TrieCacheWarmUpStrategy::Blocking,
}
}
}
impl ImportParams {
/// Specify the trie cache maximum size.
pub fn trie_cache_maximum_size(&self) -> Option<usize> {
if self.trie_cache_size == 0 {
None
} else {
Some(self.trie_cache_size)
}
}
/// Specify if we should warm up the trie cache.
pub fn warm_up_trie_cache(&self) -> Option<TrieCacheWarmUpStrategy> {
self.warm_up_trie_cache
}
/// Get the WASM execution method from the parameters
pub fn wasm_method(&self) -> pezsc_service::config::WasmExecutionMethod {
self.execution_strategies.check_usage_and_print_deprecation_warning();
crate::execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy)
}
/// Enable overriding on-chain WASM with locally-stored WASM
/// by specifying the path where local WASM is stored.
pub fn wasm_runtime_overrides(&self) -> Option<PathBuf> {
self.wasm_runtime_overrides.clone()
}
}
/// Execution strategies parameters.
#[derive(Debug, Clone, Args)]
pub struct ExecutionStrategiesParams {
/// Runtime execution strategy for importing blocks during initial sync.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_syncing: Option<ExecutionStrategy>,
/// Runtime execution strategy for general block import (including locally authored blocks).
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_import_block: Option<ExecutionStrategy>,
/// Runtime execution strategy for constructing blocks.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_block_construction: Option<ExecutionStrategy>,
/// Runtime execution strategy for offchain workers.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_offchain_worker: Option<ExecutionStrategy>,
/// Runtime execution strategy when not syncing, importing or constructing blocks.
#[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)]
pub execution_other: Option<ExecutionStrategy>,
/// The execution strategy that should be used by all execution contexts.
#[arg(
long,
value_name = "STRATEGY",
value_enum,
ignore_case = true,
conflicts_with_all = &[
"execution_other",
"execution_offchain_worker",
"execution_block_construction",
"execution_import_block",
"execution_syncing",
]
)]
pub execution: Option<ExecutionStrategy>,
}
impl ExecutionStrategiesParams {
/// Check if one of the parameters is still passed and print a warning if so.
fn check_usage_and_print_deprecation_warning(&self) {
for (param, name) in [
(&self.execution_syncing, "execution-syncing"),
(&self.execution_import_block, "execution-import-block"),
(&self.execution_block_construction, "execution-block-construction"),
(&self.execution_offchain_worker, "execution-offchain-worker"),
(&self.execution_other, "execution-other"),
(&self.execution, "execution"),
] {
if param.is_some() {
eprintln!(
"CLI parameter `--{name}` has no effect anymore and will be removed in the future!"
);
}
}
}
}
@@ -0,0 +1,103 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{error, error::Result};
use clap::Args;
use pezsc_service::config::KeystoreConfig;
use pezsp_core::crypto::SecretString;
use std::{
fs,
path::{Path, PathBuf},
};
/// default sub directory for the key store
const DEFAULT_KEYSTORE_CONFIG_PATH: &str = "keystore";
/// Parameters of the keystore
#[derive(Debug, Clone, Args)]
pub struct KeystoreParams {
/// Specify custom keystore path.
#[arg(long, value_name = "PATH")]
pub keystore_path: Option<PathBuf>,
/// Use interactive shell for entering the password used by the keystore.
#[arg(long, conflicts_with_all = &["password", "password_filename"])]
pub password_interactive: bool,
/// Password used by the keystore.
///
/// This allows appending an extra user-defined secret to the seed.
#[arg(
long,
value_parser = secret_string_from_str,
conflicts_with_all = &["password_interactive", "password_filename"]
)]
pub password: Option<SecretString>,
/// File that contains the password used by the keystore.
#[arg(
long,
value_name = "PATH",
conflicts_with_all = &["password_interactive", "password"]
)]
pub password_filename: Option<PathBuf>,
}
/// Parse a secret string, returning a displayable error.
pub fn secret_string_from_str(s: &str) -> std::result::Result<SecretString, String> {
std::str::FromStr::from_str(s).map_err(|_| "Could not get SecretString".to_string())
}
impl KeystoreParams {
/// Get the keystore configuration for the parameters
pub fn keystore_config(&self, config_dir: &Path) -> Result<KeystoreConfig> {
let password = if self.password_interactive {
Some(SecretString::new(input_keystore_password()?))
} else if let Some(ref file) = self.password_filename {
let password = fs::read_to_string(file).map_err(|e| format!("{}", e))?;
Some(SecretString::new(password))
} else {
self.password.clone()
};
let path = self
.keystore_path
.clone()
.unwrap_or_else(|| config_dir.join(DEFAULT_KEYSTORE_CONFIG_PATH));
Ok(KeystoreConfig::Path { path, password })
}
/// helper method to fetch password from `KeyParams` or read from stdin
pub fn read_password(&self) -> error::Result<Option<SecretString>> {
let (password_interactive, password) = (self.password_interactive, self.password.clone());
let pass = if password_interactive {
let password = rpassword::prompt_password("Key password: ")?;
Some(SecretString::new(password))
} else {
password
};
Ok(pass)
}
}
fn input_keystore_password() -> Result<String> {
rpassword::prompt_password("Keystore password: ").map_err(|e| format!("{:?}", e).into())
}
@@ -0,0 +1,120 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Params to configure how a message should be passed into a command.
use crate::error::Error;
use array_bytes::{hex2bytes, hex_bytes2hex_str};
use clap::Args;
use std::io::BufRead;
/// Params to configure how a message should be passed into a command.
#[derive(Debug, Clone, Args)]
pub struct MessageParams {
/// Message to process. Will be read from STDIN otherwise.
/// The message is assumed to be raw bytes per default. Use `--hex` for hex input. Can
/// optionally be prefixed with `0x` in the hex case.
#[arg(long)]
message: Option<String>,
/// The message is hex-encoded data.
#[arg(long)]
hex: bool,
}
impl MessageParams {
/// Produces the message by either using its immediate value or reading from stdin.
///
/// This function should only be called once and the result cached.
pub(crate) fn message_from<F, R>(&self, create_reader: F) -> Result<Vec<u8>, Error>
where
R: BufRead,
F: FnOnce() -> R,
{
let raw = match &self.message {
Some(raw) => raw.as_bytes().to_vec(),
None => {
let mut raw = vec![];
create_reader().read_to_end(&mut raw)?;
raw
},
};
if self.hex {
hex2bytes(hex_bytes2hex_str(&raw)?).map_err(Into::into)
} else {
Ok(raw)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
/// Test that decoding an immediate message works.
#[test]
fn message_decode_immediate() {
for (name, input, hex, output) in test_closures() {
println!("Testing: immediate_{}", name);
let params = MessageParams { message: Some(input.into()), hex };
let message = params.message_from(|| std::io::stdin().lock());
match output {
Some(output) => {
let message = message.expect(&format!("{}: should decode but did not", name));
assert_eq!(message, output, "{}: decoded a wrong message", name);
},
None => {
message.err().expect(&format!("{}: should not decode but did", name));
},
}
}
}
/// Test that decoding a message from a stream works.
#[test]
fn message_decode_stream() {
for (name, input, hex, output) in test_closures() {
println!("Testing: stream_{}", name);
let params = MessageParams { message: None, hex };
let message = params.message_from(|| input.as_bytes());
match output {
Some(output) => {
let message = message.expect(&format!("{}: should decode but did not", name));
assert_eq!(message, output, "{}: decoded a wrong message", name);
},
None => {
message.err().expect(&format!("{}: should not decode but did", name));
},
}
}
}
/// Returns (test_name, input, hex, output).
fn test_closures() -> Vec<(&'static str, &'static str, bool, Option<&'static [u8]>)> {
vec![
("decode_no_hex_works", "Hello this is not hex", false, Some(b"Hello this is not hex")),
("decode_no_hex_with_hex_string_works", "0xffffffff", false, Some(b"0xffffffff")),
("decode_hex_works", "0x00112233", true, Some(&[0, 17, 34, 51])),
("decode_hex_without_prefix_works", "00112233", true, Some(&[0, 17, 34, 51])),
("decode_hex_uppercase_works", "0xaAbbCCDd", true, Some(&[170, 187, 204, 221])),
("decode_hex_wrong_len_errors", "0x0011223", true, None),
]
}
}
@@ -0,0 +1,67 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use pezsp_core::H256;
use std::str::FromStr;
fn parse_kx_secret(s: &str) -> Result<pezsc_mixnet::KxSecret, String> {
H256::from_str(s).map(H256::to_fixed_bytes).map_err(|err| err.to_string())
}
/// Parameters used to create the mixnet configuration.
#[derive(Debug, Clone, Args)]
pub struct MixnetParams {
/// Enable the mixnet service.
///
/// This will make the mixnet RPC methods available. If the node is running as a validator, it
/// will also attempt to register and operate as a mixnode.
#[arg(long)]
pub mixnet: bool,
/// The mixnet key-exchange secret to use in session 0.
///
/// Should be 64 hex characters, giving a 32-byte secret.
///
/// WARNING: Secrets provided as command-line arguments are easily exposed. Use of this option
/// should be limited to development and testing.
#[arg(long, value_name = "SECRET", value_parser = parse_kx_secret)]
pub mixnet_session_0_kx_secret: Option<pezsc_mixnet::KxSecret>,
}
impl MixnetParams {
/// Returns the mixnet configuration, or `None` if the mixnet is disabled.
pub fn config(&self, is_authority: bool) -> Option<pezsc_mixnet::Config> {
self.mixnet.then(|| {
let mut config = pezsc_mixnet::Config {
core: pezsc_mixnet::CoreConfig {
session_0_kx_secret: self.mixnet_session_0_kx_secret,
..Default::default()
},
..Default::default()
};
if !is_authority {
// Only authorities can be mixnodes; don't attempt to register
config.bizinikiwi.register = false;
// Only mixnodes need to allow connections from non-mixnodes
config.bizinikiwi.num_gateway_slots = 0;
}
config
})
}
}
+203
View File
@@ -0,0 +1,203 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
mod database_params;
mod import_params;
mod keystore_params;
mod message_params;
mod mixnet_params;
mod network_params;
mod node_key_params;
mod offchain_worker_params;
mod prometheus_params;
mod pruning_params;
mod rpc_params;
mod runtime_params;
mod shared_params;
mod telemetry_params;
mod transaction_pool_params;
use crate::arg_enums::{CryptoScheme, OutputType};
use clap::Args;
use pezsc_service::config::{IpNetwork, RpcBatchRequestConfig};
use pezsp_core::crypto::{Ss58AddressFormat, Ss58AddressFormatRegistry};
use pezsp_runtime::{
generic::BlockId,
traits::{Block as BlockT, NumberFor},
};
use std::{fmt::Debug, str::FromStr};
pub use crate::params::{
database_params::*, import_params::*, keystore_params::*, message_params::*, mixnet_params::*,
network_params::*, node_key_params::*, offchain_worker_params::*, prometheus_params::*,
pruning_params::*, rpc_params::*, runtime_params::*, shared_params::*, telemetry_params::*,
transaction_pool_params::*,
};
/// Parse Ss58AddressFormat
pub fn parse_ss58_address_format(x: &str) -> Result<Ss58AddressFormat, String> {
match Ss58AddressFormatRegistry::try_from(x) {
Ok(format_registry) => Ok(format_registry.into()),
Err(_) => Err(format!(
"Unable to parse variant. Known variants: {:?}",
Ss58AddressFormat::all_names()
)),
}
}
/// Wrapper type of `String` that holds an unsigned integer of arbitrary size, formatted as a
/// decimal.
#[derive(Debug, Clone)]
pub struct GenericNumber(String);
impl FromStr for GenericNumber {
type Err = String;
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
if let Some(pos) = block_number.chars().position(|d| !d.is_digit(10)) {
Err(format!("Expected block number, found illegal digit at position: {}", pos))
} else {
Ok(Self(block_number.to_owned()))
}
}
}
impl GenericNumber {
/// Wrapper on top of `std::str::parse<N>` but with `Error` as a `String`
///
/// See `https://doc.rust-lang.org/std/primitive.str.html#method.parse` for more elaborate
/// documentation.
pub fn parse<N>(&self) -> Result<N, String>
where
N: FromStr,
N::Err: std::fmt::Debug,
{
FromStr::from_str(&self.0).map_err(|e| format!("Failed to parse block number: {:?}", e))
}
}
/// Wrapper type that is either a `Hash` or the number of a `Block`.
#[derive(Debug, Clone)]
pub struct BlockNumberOrHash(String);
impl FromStr for BlockNumberOrHash {
type Err = String;
fn from_str(block_number: &str) -> Result<Self, Self::Err> {
if let Some(rest) = block_number.strip_prefix("0x") {
if let Some(pos) = rest.chars().position(|c| !c.is_ascii_hexdigit()) {
Err(format!(
"Expected block hash, found illegal hex character at position: {}",
2 + pos,
))
} else {
Ok(Self(block_number.into()))
}
} else {
GenericNumber::from_str(block_number).map(|v| Self(v.0))
}
}
}
impl BlockNumberOrHash {
/// Parse the inner value as `BlockId`.
pub fn parse<B: BlockT>(&self) -> Result<BlockId<B>, String>
where
<B::Hash as FromStr>::Err: std::fmt::Debug,
NumberFor<B>: FromStr,
<NumberFor<B> as FromStr>::Err: std::fmt::Debug,
{
if self.0.starts_with("0x") {
Ok(BlockId::Hash(
FromStr::from_str(&self.0[2..])
.map_err(|e| format!("Failed to parse block hash: {:?}", e))?,
))
} else {
GenericNumber(self.0.clone()).parse().map(BlockId::Number)
}
}
}
/// Optional flag for specifying crypto algorithm
#[derive(Debug, Clone, Args)]
pub struct CryptoSchemeFlag {
/// cryptography scheme
#[arg(long, value_name = "SCHEME", value_enum, ignore_case = true, default_value_t = CryptoScheme::Sr25519)]
pub scheme: CryptoScheme,
}
/// Optional flag for specifying output type
#[derive(Debug, Clone, Args)]
pub struct OutputTypeFlag {
/// output format
#[arg(long, value_name = "FORMAT", value_enum, ignore_case = true, default_value_t = OutputType::Text)]
pub output_type: OutputType,
}
/// Optional flag for specifying network scheme
#[derive(Debug, Clone, Args)]
pub struct NetworkSchemeFlag {
/// network address format
#[arg(
short = 'n',
long,
value_name = "NETWORK",
ignore_case = true,
value_parser = parse_ss58_address_format,
)]
pub network: Option<Ss58AddressFormat>,
}
#[cfg(test)]
mod tests {
use super::*;
type Header = pezsp_runtime::generic::Header<u32, pezsp_runtime::traits::BlakeTwo256>;
type Block = pezsp_runtime::generic::Block<Header, pezsp_runtime::OpaqueExtrinsic>;
#[test]
fn parse_block_number() {
let block_number_or_hash = BlockNumberOrHash::from_str("1234").unwrap();
let parsed = block_number_or_hash.parse::<Block>().unwrap();
assert_eq!(BlockId::Number(1234), parsed);
}
#[test]
fn parse_block_hash() {
let hash = pezsp_core::H256::default();
let hash_str = format!("{:?}", hash);
let block_number_or_hash = BlockNumberOrHash::from_str(&hash_str).unwrap();
let parsed = block_number_or_hash.parse::<Block>().unwrap();
assert_eq!(BlockId::Hash(hash), parsed);
}
#[test]
fn parse_block_hash_fails() {
assert_eq!(
"Expected block hash, found illegal hex character at position: 2",
BlockNumberOrHash::from_str("0xHello").unwrap_err(),
);
}
#[test]
fn parse_block_number_fails() {
assert_eq!(
"Expected block number, found illegal digit at position: 3",
BlockNumberOrHash::from_str("345Hello").unwrap_err(),
);
}
}
@@ -0,0 +1,342 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::{
arg_enums::{NetworkBackendType, SyncMode},
params::node_key_params::NodeKeyParams,
};
use clap::Args;
use pezsc_network::{
config::{
NetworkConfiguration, NodeKeyConfig, NonReservedPeerMode, SetConfig, TransportConfig,
DEFAULT_IDLE_CONNECTION_TIMEOUT,
},
multiaddr::Protocol,
};
use pezsc_service::{
config::{Multiaddr, MultiaddrWithPeerId},
ChainSpec, ChainType,
};
use std::{borrow::Cow, num::NonZeroUsize, path::PathBuf};
/// Parameters used to create the network configuration.
#[derive(Debug, Clone, Args)]
pub struct NetworkParams {
/// Specify a list of bootnodes.
#[arg(long, value_name = "ADDR", num_args = 1..)]
pub bootnodes: Vec<MultiaddrWithPeerId>,
/// Specify a list of reserved node addresses.
#[arg(long, value_name = "ADDR", num_args = 1..)]
pub reserved_nodes: Vec<MultiaddrWithPeerId>,
/// Whether to only synchronize the chain with reserved nodes.
///
/// Also disables automatic peer discovery.
/// TCP connections might still be established with non-reserved nodes.
/// In particular, if you are a validator your node might still connect to other
/// validator nodes and collator nodes regardless of whether they are defined as
/// reserved nodes.
#[arg(long)]
pub reserved_only: bool,
/// Public address that other nodes will use to connect to this node.
///
/// This can be used if there's a proxy in front of this node.
#[arg(long, value_name = "PUBLIC_ADDR", num_args = 1..)]
pub public_addr: Vec<Multiaddr>,
/// Listen on this multiaddress.
///
/// By default:
/// If `--validator` is passed: `/ip4/0.0.0.0/tcp/<port>` and `/ip6/[::]/tcp/<port>`.
/// Otherwise: `/ip4/0.0.0.0/tcp/<port>/ws` and `/ip6/[::]/tcp/<port>/ws`.
#[arg(long, value_name = "LISTEN_ADDR", num_args = 1..)]
pub listen_addr: Vec<Multiaddr>,
/// Specify p2p protocol TCP port.
#[arg(long, value_name = "PORT", conflicts_with_all = &[ "listen_addr" ])]
pub port: Option<u16>,
/// Always forbid connecting to private IPv4/IPv6 addresses.
///
/// The option doesn't apply to addresses passed with `--reserved-nodes` or
/// `--bootnodes`. Enabled by default for chains marked as "live" in their chain
/// specifications.
///
/// Address allocation for private networks is specified by
/// [RFC1918](https://tools.ietf.org/html/rfc1918)).
#[arg(long, alias = "no-private-ipv4", conflicts_with_all = &["allow_private_ip"])]
pub no_private_ip: bool,
/// Always accept connecting to private IPv4/IPv6 addresses.
///
/// Enabled by default for chains marked as "local" in their chain specifications,
/// or when `--dev` is passed.
///
/// Address allocation for private networks is specified by
/// [RFC1918](https://tools.ietf.org/html/rfc1918)).
#[arg(long, alias = "allow-private-ipv4", conflicts_with_all = &["no_private_ip"])]
pub allow_private_ip: bool,
/// Number of outgoing connections we're trying to maintain.
#[arg(long, value_name = "COUNT", default_value_t = 8)]
pub out_peers: u32,
/// Maximum number of inbound full nodes peers.
#[arg(long, value_name = "COUNT", default_value_t = 32)]
pub in_peers: u32,
/// Maximum number of inbound light nodes peers.
#[arg(long, value_name = "COUNT", default_value_t = 100)]
pub in_peers_light: u32,
/// Disable mDNS discovery (default: true).
///
/// By default, the network will use mDNS to discover other nodes on the
/// local network. This disables it. Automatically implied when using --dev.
#[arg(long)]
pub no_mdns: bool,
/// Maximum number of peers from which to ask for the same blocks in parallel.
///
/// This allows downloading announced blocks from multiple peers.
/// Decrease to save traffic and risk increased latency.
#[arg(long, value_name = "COUNT", default_value_t = 5)]
pub max_parallel_downloads: u32,
#[allow(missing_docs)]
#[clap(flatten)]
pub node_key_params: NodeKeyParams,
/// Enable peer discovery on local networks.
///
/// By default this option is `true` for `--dev` or when the chain type is
/// `Local`/`Development` and false otherwise.
#[arg(long)]
pub discover_local: bool,
/// Require iterative Kademlia DHT queries to use disjoint paths.
///
/// Disjoint paths increase resiliency in the presence of potentially adversarial nodes.
///
/// See the S/Kademlia paper for more information on the high level design as well as its
/// security improvements.
#[arg(long)]
pub kademlia_disjoint_query_paths: bool,
/// Kademlia replication factor.
///
/// Determines to how many closest peers a record is replicated to.
///
/// Discovery mechanism requires successful replication to all
/// `kademlia_replication_factor` peers to consider record successfully put.
#[arg(long, default_value = "20")]
pub kademlia_replication_factor: NonZeroUsize,
/// Join the IPFS network and serve transactions over bitswap protocol.
#[arg(long)]
pub ipfs_server: bool,
/// Blockchain syncing mode.
#[arg(
long,
value_enum,
value_name = "SYNC_MODE",
default_value_t = SyncMode::Full,
ignore_case = true,
verbatim_doc_comment
)]
pub sync: SyncMode,
/// Maximum number of blocks per request.
///
/// Try reducing this number from the default value if you have a slow network connection
/// and observe block requests timing out.
#[arg(long, value_name = "COUNT", default_value_t = 64)]
pub max_blocks_per_request: u32,
/// Network backend used for P2P networking.
///
/// Litep2p is a lightweight alternative to libp2p, that is designed to be more
/// efficient and easier to use. At the same time, litep2p brings performance
/// improvements and reduces the CPU usage significantly.
///
/// Libp2p is the old network backend, that may still be used for compatibility
/// reasons until the whole ecosystem is migrated to litep2p.
#[arg(
long,
value_enum,
value_name = "NETWORK_BACKEND",
default_value_t = NetworkBackendType::Litep2p,
ignore_case = true,
verbatim_doc_comment
)]
pub network_backend: NetworkBackendType,
}
impl NetworkParams {
/// Fill the given `NetworkConfiguration` by looking at the cli parameters.
pub fn network_config(
&self,
chain_spec: &Box<dyn ChainSpec>,
is_dev: bool,
is_validator: bool,
net_config_path: Option<PathBuf>,
client_id: &str,
node_name: &str,
node_key: NodeKeyConfig,
default_listen_port: u16,
) -> NetworkConfiguration {
let port = self.port.unwrap_or(default_listen_port);
let listen_addresses = if self.listen_addr.is_empty() {
if is_validator || is_dev {
vec![
Multiaddr::empty()
.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
.with(Protocol::Tcp(port)),
Multiaddr::empty()
.with(Protocol::Ip4([0, 0, 0, 0].into()))
.with(Protocol::Tcp(port)),
]
} else {
vec![
Multiaddr::empty()
.with(Protocol::Ip6([0, 0, 0, 0, 0, 0, 0, 0].into()))
.with(Protocol::Tcp(port))
.with(Protocol::Ws(Cow::Borrowed("/"))),
Multiaddr::empty()
.with(Protocol::Ip4([0, 0, 0, 0].into()))
.with(Protocol::Tcp(port))
.with(Protocol::Ws(Cow::Borrowed("/"))),
]
}
} else {
self.listen_addr.clone()
};
let public_addresses = self.public_addr.clone();
let mut boot_nodes = chain_spec.boot_nodes().to_vec();
boot_nodes.extend(self.bootnodes.clone());
let chain_type = chain_spec.chain_type();
// Activate if the user explicitly requested local discovery, `--dev` is given or the
// chain type is `Local`/`Development`
let allow_non_globals_in_dht =
self.discover_local ||
is_dev || matches!(chain_type, ChainType::Local | ChainType::Development);
let allow_private_ip = match (self.allow_private_ip, self.no_private_ip) {
(true, true) => unreachable!("`*_private_ip` flags are mutually exclusive; qed"),
(true, false) => true,
(false, true) => false,
(false, false) =>
is_dev || matches!(chain_type, ChainType::Local | ChainType::Development),
};
NetworkConfiguration {
boot_nodes,
net_config_path,
default_peers_set: SetConfig {
in_peers: self.in_peers + self.in_peers_light,
out_peers: self.out_peers,
reserved_nodes: self.reserved_nodes.clone(),
non_reserved_mode: if self.reserved_only {
NonReservedPeerMode::Deny
} else {
NonReservedPeerMode::Accept
},
},
default_peers_set_num_full: self.in_peers + self.out_peers,
listen_addresses,
public_addresses,
node_key,
node_name: node_name.to_string(),
client_version: client_id.to_string(),
transport: TransportConfig::Normal {
enable_mdns: !is_dev && !self.no_mdns,
allow_private_ip,
},
idle_connection_timeout: DEFAULT_IDLE_CONNECTION_TIMEOUT,
max_parallel_downloads: self.max_parallel_downloads,
max_blocks_per_request: self.max_blocks_per_request,
min_peers_to_start_warp_sync: None,
enable_dht_random_walk: !self.reserved_only,
allow_non_globals_in_dht,
kademlia_disjoint_query_paths: self.kademlia_disjoint_query_paths,
kademlia_replication_factor: self.kademlia_replication_factor,
ipfs_server: self.ipfs_server,
sync_mode: self.sync.into(),
network_backend: self.network_backend.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser)]
struct Cli {
#[clap(flatten)]
network_params: NetworkParams,
}
#[test]
fn reserved_nodes_multiple_values_and_occurrences() {
let params = Cli::try_parse_from([
"",
"--reserved-nodes",
"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
"--reserved-nodes",
"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS",
])
.expect("Parses network params");
let expected = vec![
MultiaddrWithPeerId::try_from(
"/ip4/0.0.0.0/tcp/501/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
.to_string(),
)
.unwrap(),
MultiaddrWithPeerId::try_from(
"/ip4/0.0.0.0/tcp/502/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
.to_string(),
)
.unwrap(),
MultiaddrWithPeerId::try_from(
"/ip4/0.0.0.0/tcp/503/p2p/12D3KooWEBo1HUPQJwiBmM5kSeg4XgiVxEArArQdDarYEsGxMfbS"
.to_string(),
)
.unwrap(),
];
assert_eq!(expected, params.network_params.reserved_nodes);
}
#[test]
fn sync_ignores_case() {
let params = Cli::try_parse_from(["", "--sync", "wArP"]).expect("Parses network params");
assert_eq!(SyncMode::Warp, params.network_params.sync);
}
}
@@ -0,0 +1,268 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use pezsc_network::config::{ed25519, NodeKeyConfig};
use pezsc_service::Role;
use pezsp_core::H256;
use std::{path::PathBuf, str::FromStr};
use crate::{arg_enums::NodeKeyType, error, Error};
/// The file name of the node's Ed25519 secret key inside the chain-specific
/// network config directory, if neither `--node-key` nor `--node-key-file`
/// is specified in combination with `--node-key-type=ed25519`.
pub(crate) const NODE_KEY_ED25519_FILE: &str = "secret_ed25519";
/// Parameters used to create the `NodeKeyConfig`, which determines the keypair
/// used for libp2p networking.
#[derive(Debug, Clone, Args)]
pub struct NodeKeyParams {
/// Secret key to use for p2p networking.
///
/// The value is a string that is parsed according to the choice of
/// `--node-key-type` as follows:
///
/// - `ed25519`: the value is parsed as a hex-encoded Ed25519 32 byte secret key (64 hex
/// chars)
///
/// The value of this option takes precedence over `--node-key-file`.
///
/// WARNING: Secrets provided as command-line arguments are easily exposed.
/// Use of this option should be limited to development and testing. To use
/// an externally managed secret key, use `--node-key-file` instead.
#[arg(long, value_name = "KEY")]
pub node_key: Option<String>,
/// Crypto primitive to use for p2p networking.
///
/// The secret key of the node is obtained as follows:
///
/// - If the `--node-key` option is given, the value is parsed as a secret key according to the
/// type. See the documentation for `--node-key`.
///
/// - If the `--node-key-file` option is given, the secret key is read from the specified file.
/// See the documentation for `--node-key-file`.
///
/// - Otherwise, the secret key is read from a file with a predetermined, type-specific name
/// from the chain-specific network config directory inside the base directory specified by
/// `--base-dir`. If this file does not exist, it is created with a newly generated secret
/// key of the chosen type.
///
/// The node's secret key determines the corresponding public key and hence the
/// node's peer ID in the context of libp2p.
#[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)]
pub node_key_type: NodeKeyType,
/// File from which to read the node's secret key to use for p2p networking.
///
/// The contents of the file are parsed according to the choice of `--node-key-type`
/// as follows:
///
/// - `ed25519`: the file must contain an unencoded 32 byte or hex encoded Ed25519 secret key.
///
/// If the file does not exist, it is created with a newly generated secret key of
/// the chosen type.
#[arg(long, value_name = "FILE")]
pub node_key_file: Option<PathBuf>,
/// Forces key generation if node-key-file file does not exist.
///
/// This is an unsafe feature for production networks, because as an active authority
/// other authorities may depend on your node having a stable identity and they might
/// not being able to reach you if your identity changes after entering the active set.
///
/// For minimal node downtime if no custom `node-key-file` argument is provided
/// the network-key is usually persisted accross nodes restarts,
/// in the `network` folder from directory provided in `--base-path`
///
/// Warning!! If you ever run the node with this argument, make sure
/// you remove it for the subsequent restarts.
#[arg(long)]
pub unsafe_force_node_key_generation: bool,
}
impl NodeKeyParams {
/// Create a `NodeKeyConfig` from the given `NodeKeyParams` in the context
/// of an optional network config storage directory.
pub fn node_key(
&self,
net_config_dir: &PathBuf,
role: Role,
is_dev: bool,
) -> error::Result<NodeKeyConfig> {
Ok(match self.node_key_type {
NodeKeyType::Ed25519 => {
let secret = if let Some(node_key) = self.node_key.as_ref() {
parse_ed25519_secret(node_key)?
} else {
let key_path = self
.node_key_file
.clone()
.unwrap_or_else(|| net_config_dir.join(NODE_KEY_ED25519_FILE));
if !self.unsafe_force_node_key_generation &&
role.is_authority() &&
!is_dev && !key_path.exists()
{
return Err(Error::NetworkKeyNotFound(key_path));
}
pezsc_network::config::Secret::File(key_path)
};
NodeKeyConfig::Ed25519(secret)
},
})
}
}
/// Create an error caused by an invalid node key argument.
fn invalid_node_key(e: impl std::fmt::Display) -> error::Error {
error::Error::Input(format!("Invalid node key: {}", e))
}
/// Parse a Ed25519 secret key from a hex string into a `pezsc_network::Secret`.
fn parse_ed25519_secret(hex: &str) -> error::Result<pezsc_network::config::Ed25519Secret> {
H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| {
ed25519::SecretKey::try_from_bytes(bytes)
.map(pezsc_network::config::Secret::Input)
.map_err(invalid_node_key)
})
}
#[cfg(test)]
mod tests {
use super::*;
use clap::ValueEnum;
use pezsc_network::config::ed25519;
use std::fs::{self, File};
use tempfile::TempDir;
#[test]
fn test_node_key_config_input() {
fn secret_input(net_config_dir: &PathBuf) -> error::Result<()> {
NodeKeyType::value_variants().iter().try_for_each(|t| {
let node_key_type = *t;
let sk = match node_key_type {
NodeKeyType::Ed25519 => ed25519::SecretKey::generate().as_ref().to_vec(),
};
let params = NodeKeyParams {
node_key_type,
node_key: Some(format!("{:x}", H256::from_slice(sk.as_ref()))),
node_key_file: None,
unsafe_force_node_key_generation: false,
};
params.node_key(net_config_dir, Role::Authority, false).and_then(|c| match c {
NodeKeyConfig::Ed25519(pezsc_network::config::Secret::Input(ref ski))
if node_key_type == NodeKeyType::Ed25519 && &sk[..] == ski.as_ref() =>
Ok(()),
_ => Err(error::Error::Input("Unexpected node key config".into())),
})
})
}
assert!(secret_input(&PathBuf::from_str("x").unwrap()).is_ok());
}
#[test]
fn test_node_key_config_file() {
fn check_key(file: PathBuf, key: &ed25519::SecretKey) {
let params = NodeKeyParams {
node_key_type: NodeKeyType::Ed25519,
node_key: None,
node_key_file: Some(file),
unsafe_force_node_key_generation: false,
};
let node_key = params
.node_key(&PathBuf::from("not-used"), Role::Authority, false)
.expect("Creates node key config")
.into_keypair()
.expect("Creates node key pair");
if node_key.secret().as_ref() != key.as_ref() {
panic!("Invalid key")
}
}
let tmp = tempfile::Builder::new().prefix("alice").tempdir().expect("Creates tempfile");
let file = tmp.path().join("mysecret").to_path_buf();
let key = ed25519::SecretKey::generate();
fs::write(&file, array_bytes::bytes2hex("", key.as_ref())).expect("Writes secret key");
check_key(file.clone(), &key);
fs::write(&file, &key).expect("Writes secret key");
check_key(file.clone(), &key);
}
#[test]
fn test_node_key_config_default() {
fn with_def_params<F>(f: F, unsafe_force_node_key_generation: bool) -> error::Result<()>
where
F: Fn(NodeKeyParams) -> error::Result<()>,
{
NodeKeyType::value_variants().iter().try_for_each(|t| {
let node_key_type = *t;
f(NodeKeyParams {
node_key_type,
node_key: None,
node_key_file: None,
unsafe_force_node_key_generation,
})
})
}
fn some_config_dir(
net_config_dir: &PathBuf,
unsafe_force_node_key_generation: bool,
role: Role,
is_dev: bool,
) -> error::Result<()> {
with_def_params(
|params| {
let dir = PathBuf::from(net_config_dir.clone());
let typ = params.node_key_type;
params.node_key(net_config_dir, role, is_dev).and_then(move |c| match c {
NodeKeyConfig::Ed25519(pezsc_network::config::Secret::File(ref f))
if typ == NodeKeyType::Ed25519 &&
f == &dir.join(NODE_KEY_ED25519_FILE) =>
Ok(()),
_ => Err(error::Error::Input("Unexpected node key config".into())),
})
},
unsafe_force_node_key_generation,
)
}
assert!(some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Full, false).is_ok());
assert!(
some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, true).is_ok()
);
assert!(
some_config_dir(&PathBuf::from_str("x").unwrap(), true, Role::Authority, false).is_ok()
);
assert!(matches!(
some_config_dir(&PathBuf::from_str("x").unwrap(), false, Role::Authority, false),
Err(Error::NetworkKeyNotFound(_))
));
let tempdir = TempDir::new().unwrap();
let _file = File::create(tempdir.path().join(NODE_KEY_ED25519_FILE)).unwrap();
assert!(some_config_dir(&tempdir.path().into(), false, Role::Authority, false).is_ok());
}
}
@@ -0,0 +1,65 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
//! Offchain worker related configuration parameters.
//!
//! A subset of configuration parameters which are relevant to
//! the inner working of offchain workers. The usage is solely
//! targeted at handling input parameter parsing providing
//! a reasonable abstraction.
use clap::{ArgAction, Args};
use pezsc_network::config::Role;
use pezsc_service::config::OffchainWorkerConfig;
use crate::{error, OffchainWorkerEnabled};
/// Offchain worker related parameters.
#[derive(Debug, Clone, Args)]
pub struct OffchainWorkerParams {
/// Execute offchain workers on every block.
#[arg(
long = "offchain-worker",
value_name = "ENABLED",
value_enum,
ignore_case = true,
default_value_t = OffchainWorkerEnabled::WhenAuthority
)]
pub enabled: OffchainWorkerEnabled,
/// Enable offchain indexing API.
///
/// Allows the runtime to write directly to offchain workers DB during block import.
#[arg(long = "enable-offchain-indexing", value_name = "ENABLE_OFFCHAIN_INDEXING", default_value_t = false, action = ArgAction::Set)]
pub indexing_enabled: bool,
}
impl OffchainWorkerParams {
/// Load spec to `Configuration` from `OffchainWorkerParams` and spec factory.
pub fn offchain_worker(&self, role: &Role) -> error::Result<OffchainWorkerConfig> {
let enabled = match (&self.enabled, role) {
(OffchainWorkerEnabled::WhenAuthority, Role::Authority { .. }) => true,
(OffchainWorkerEnabled::Always, _) => true,
(OffchainWorkerEnabled::Never, _) => false,
(OffchainWorkerEnabled::WhenAuthority, _) => false,
};
let indexing_enabled = self.indexing_enabled;
Ok(OffchainWorkerConfig { enabled, indexing_enabled })
}
}
@@ -0,0 +1,63 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use clap::Args;
use pezsc_service::config::PrometheusConfig;
use std::net::{Ipv4Addr, SocketAddr};
/// Parameters used to config prometheus.
#[derive(Debug, Clone, Args)]
pub struct PrometheusParams {
/// Specify Prometheus exporter TCP Port.
#[arg(long, value_name = "PORT")]
pub prometheus_port: Option<u16>,
/// Expose Prometheus exporter on all interfaces.
///
/// Default is local.
#[arg(long)]
pub prometheus_external: bool,
/// Do not expose a Prometheus exporter endpoint.
///
/// Prometheus metric endpoint is enabled by default.
#[arg(long)]
pub no_prometheus: bool,
}
impl PrometheusParams {
/// Creates [`PrometheusConfig`].
pub fn prometheus_config(
&self,
default_listen_port: u16,
chain_id: String,
) -> Option<PrometheusConfig> {
if self.no_prometheus {
None
} else {
let interface =
if self.prometheus_external { Ipv4Addr::UNSPECIFIED } else { Ipv4Addr::LOCALHOST };
Some(PrometheusConfig::new_with_default_registry(
SocketAddr::new(
interface.into(),
self.prometheus_port.unwrap_or(default_listen_port),
),
chain_id,
))
}
}
}
@@ -0,0 +1,165 @@
// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
// This program 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.
// This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
use crate::error;
use clap::Args;
use pezsc_service::{BlocksPruning, PruningMode};
/// Parameters to define the pruning mode
#[derive(Debug, Clone, Args)]
pub struct PruningParams {
/// Specify the state pruning mode.
///
/// This mode specifies when the block's state (ie, storage)
/// should be pruned (ie, removed) from the database.
/// This setting can only be set on the first creation of the database. Every subsequent run
/// will load the pruning mode from the database and will error if the stored mode doesn't
/// match this CLI value. It is fine to drop this CLI flag for subsequent runs. The only
/// exception is that `NUMBER` can change between subsequent runs (increasing it will not
/// lead to restoring pruned state).
///
/// Possible values:
///
/// - archive: Keep the data of all blocks.
///
/// - archive-canonical: Keep only the data of finalized blocks.
///
/// - NUMBER: Keep the data of the last NUMBER of finalized blocks.
///
/// [default: 256]
#[arg(alias = "pruning", long, value_name = "PRUNING_MODE")]
pub state_pruning: Option<DatabasePruningMode>,
/// Specify the blocks pruning mode.
///
/// This mode specifies when the block's body (including justifications)
/// should be pruned (ie, removed) from the database.
///
/// Possible values:
///
/// - archive: Keep the data of all blocks.
///
/// - archive-canonical: Keep only the data of finalized blocks.
///
/// - NUMBER: Keep the data of the last NUMBER of finalized blocks.
#[arg(
alias = "keep-blocks",
long,
value_name = "PRUNING_MODE",
default_value = "archive-canonical"
)]
pub blocks_pruning: DatabasePruningMode,
}
impl PruningParams {
/// Get the pruning value from the parameters
pub fn state_pruning(&self) -> error::Result<Option<PruningMode>> {
Ok(self.state_pruning.map(|v| v.into()))
}
/// Get the block pruning value from the parameters
pub fn blocks_pruning(&self) -> error::Result<BlocksPruning> {
Ok(self.blocks_pruning.into())
}
}
/// Specifies the pruning mode of the database.
///
/// This specifies when the block's data (either state via `--state-pruning`
/// or body via `--blocks-pruning`) should be pruned (ie, removed) from
/// the database.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DatabasePruningMode {
/// Keep the data of all blocks.
Archive,
/// Keep only the data of finalized blocks.
ArchiveCanonical,
/// Keep the data of the last number of finalized blocks.
Custom(u32),
}
impl std::str::FromStr for DatabasePruningMode {
type Err = String;
fn from_str(input: &str) -> Result<Self, Self::Err> {
match input {
"archive" => Ok(Self::Archive),
"archive-canonical" => Ok(Self::ArchiveCanonical),
bc => bc
.parse()
.map_err(|_| "Invalid pruning mode specified".to_string())
.map(Self::Custom),
}
}
}
impl Into<PruningMode> for DatabasePruningMode {
fn into(self) -> PruningMode {
match self {
DatabasePruningMode::Archive => PruningMode::ArchiveAll,
DatabasePruningMode::ArchiveCanonical => PruningMode::ArchiveCanonical,
DatabasePruningMode::Custom(n) => PruningMode::blocks_pruning(n),
}
}
}
impl Into<BlocksPruning> for DatabasePruningMode {
fn into(self) -> BlocksPruning {
match self {
DatabasePruningMode::Archive => BlocksPruning::KeepAll,
DatabasePruningMode::ArchiveCanonical => BlocksPruning::KeepFinalized,
DatabasePruningMode::Custom(n) => BlocksPruning::Some(n),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use clap::Parser;
#[derive(Parser)]
struct Cli {
#[clap(flatten)]
pruning: PruningParams,
}
#[test]
fn pruning_params_parse_works() {
let Cli { pruning } =
Cli::parse_from(["", "--state-pruning=1000", "--blocks-pruning=1000"]);
assert!(matches!(pruning.state_pruning, Some(DatabasePruningMode::Custom(1000))));
assert!(matches!(pruning.blocks_pruning, DatabasePruningMode::Custom(1000)));
let Cli { pruning } =
Cli::parse_from(["", "--state-pruning=archive", "--blocks-pruning=archive"]);
assert!(matches!(dbg!(pruning.state_pruning), Some(DatabasePruningMode::Archive)));
assert!(matches!(pruning.blocks_pruning, DatabasePruningMode::Archive));
let Cli { pruning } = Cli::parse_from([
"",
"--state-pruning=archive-canonical",
"--blocks-pruning=archive-canonical",
]);
assert!(matches!(dbg!(pruning.state_pruning), Some(DatabasePruningMode::ArchiveCanonical)));
assert!(matches!(pruning.blocks_pruning, DatabasePruningMode::ArchiveCanonical));
}
}

Some files were not shown because too many files have changed in this diff Show More