feat: Rebrand Polkadot/Substrate references to PezkuwiChain
This commit systematically rebrands various references from Parity Technologies' Polkadot/Substrate ecosystem to PezkuwiChain within the kurdistan-sdk. Key changes include: - Updated external repository URLs (zombienet-sdk, parity-db, parity-scale-codec, wasm-instrument) to point to pezkuwichain forks. - Modified internal documentation and code comments to reflect PezkuwiChain naming and structure. - Replaced direct references to with or specific paths within the for XCM, Pezkuwi, and other modules. - Cleaned up deprecated issue and PR references in various and files, particularly in and modules. - Adjusted image and logo URLs in documentation to point to PezkuwiChain assets. - Removed or rephrased comments related to external Polkadot/Substrate PRs and issues. This is a significant step towards fully customizing the SDK for the PezkuwiChain ecosystem.
This commit is contained in:
@@ -0,0 +1,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 }
|
||||
@@ -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
|
||||
@@ -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
@@ -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>;
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Bizinikiwi client interfaces.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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(_)),
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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)) = ¤t {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
Bizinikiwi CLI library.
|
||||
|
||||
License: GPL-3.0-or-later WITH Classpath-exception-2.0
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in New Issue
Block a user