feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit e4778b4576
6838 changed files with 1847450 additions and 0 deletions
+308
View File
@@ -0,0 +1,308 @@
[package]
name = "pezkuwi-service"
rust-version = "1.60"
version = "7.0.1"
authors.workspace = true
edition.workspace = true
license.workspace = true
description = "Utils to tie different Pezkuwi components together and allow instantiation of a node."
homepage.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
# Substrate Client
mmr-gadget = { workspace = true, default-features = true }
sc-authority-discovery = { workspace = true, default-features = true }
sc-basic-authorship = { workspace = true, default-features = true }
sc-chain-spec = { workspace = true, default-features = true }
sc-client-api = { workspace = true, default-features = true }
sc-consensus = { workspace = true, default-features = true }
sc-consensus-babe = { workspace = true, default-features = true }
sc-consensus-beefy = { workspace = true, default-features = true }
sc-consensus-grandpa = { workspace = true, default-features = true }
sc-consensus-slots = { workspace = true, default-features = true }
sc-executor = { workspace = true, default-features = true }
sc-keystore = { workspace = true, default-features = true }
sc-network = { workspace = true, default-features = true }
sc-network-sync = { workspace = true, default-features = true }
sc-offchain = { workspace = true, default-features = true }
sc-service = { workspace = true }
sc-sync-state-rpc = { workspace = true, default-features = true }
sc-sysinfo = { workspace = true, default-features = true }
sc-telemetry = { workspace = true, default-features = true }
sc-transaction-pool = { workspace = true, default-features = true }
sc-transaction-pool-api = { workspace = true, default-features = true }
sp-genesis-builder = { workspace = true, default-features = true }
sp-mmr-primitives = { workspace = true, default-features = true }
# Substrate Primitives
pallet-transaction-payment = { workspace = true, default-features = true }
sp-api = { workspace = true, default-features = true }
sp-authority-discovery = { workspace = true, default-features = true }
sp-block-builder = { workspace = true, default-features = true }
sp-blockchain = { workspace = true, default-features = true }
sp-consensus = { workspace = true, default-features = true }
sp-consensus-babe = { workspace = true, default-features = true }
sp-consensus-beefy = { workspace = true, default-features = true }
sp-consensus-grandpa = { workspace = true, default-features = true }
sp-core = { workspace = true, default-features = true }
sp-inherents = { workspace = true, default-features = true }
sp-io = { workspace = true, default-features = true }
sp-keyring = { workspace = true, default-features = true }
sp-offchain = { workspace = true, default-features = true }
sp-runtime = { workspace = true, default-features = true }
sp-session = { workspace = true, default-features = true }
sp-timestamp = { workspace = true, default-features = true }
sp-transaction-pool = { workspace = true, default-features = true }
sp-version = { workspace = true, default-features = true }
sp-weights = { workspace = true, default-features = true }
# Substrate Pallets
frame-metadata-hash-extension = { optional = true, workspace = true, default-features = true }
frame-system = { workspace = true, default-features = true }
pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true }
# Substrate Other
frame-benchmarking = { workspace = true, default-features = true }
frame-benchmarking-cli = { workspace = true, default-features = true }
frame-system-rpc-runtime-api = { workspace = true, default-features = true }
prometheus-endpoint = { workspace = true, default-features = true }
# External Crates
async-trait = { workspace = true }
codec = { workspace = true, default-features = true }
futures = { workspace = true }
gum = { workspace = true, default-features = true }
is_executable = { workspace = true }
kvdb = { workspace = true }
kvdb-rocksdb = { optional = true, workspace = true }
log = { workspace = true, default-features = true }
parity-db = { optional = true, workspace = true }
parking_lot = { workspace = true, default-features = true }
serde = { features = ["derive"], workspace = true, default-features = true }
serde_json = { workspace = true, default-features = true }
thiserror = { workspace = true }
# Pezkuwi
pezkuwi-core-primitives = { workspace = true, default-features = true }
pezkuwi-node-core-teyrchains-inherent = { workspace = true, default-features = true }
pezkuwi-node-network-protocol = { workspace = true, default-features = true }
pezkuwi-node-primitives = { workspace = true, default-features = true }
pezkuwi-node-subsystem = { workspace = true, default-features = true }
pezkuwi-node-subsystem-types = { workspace = true, default-features = true }
pezkuwi-node-subsystem-util = { workspace = true, default-features = true }
pezkuwi-overseer = { workspace = true, default-features = true }
pezkuwi-primitives = { workspace = true, default-features = true }
pezkuwi-rpc = { workspace = true, default-features = true }
pezkuwi-runtime-teyrchains = { workspace = true, default-features = true }
# Pezkuwi Runtime Constants
pezkuwichain-runtime-constants = { optional = true, workspace = true, default-features = true }
zagros-runtime-constants = { optional = true, workspace = true, default-features = true }
# Pezkuwi Runtimes
pezkuwichain-runtime = { optional = true, workspace = true }
zagros-runtime = { optional = true, workspace = true, default-features = true }
# Pezkuwi Subsystems
pezkuwi-approval-distribution = { optional = true, workspace = true, default-features = true }
pezkuwi-availability-bitfield-distribution = { optional = true, workspace = true, default-features = true }
pezkuwi-availability-distribution = { optional = true, workspace = true, default-features = true }
pezkuwi-availability-recovery = { optional = true, workspace = true, default-features = true }
pezkuwi-collator-protocol = { optional = true, workspace = true, default-features = true }
pezkuwi-dispute-distribution = { optional = true, workspace = true, default-features = true }
pezkuwi-gossip-support = { optional = true, workspace = true, default-features = true }
pezkuwi-network-bridge = { optional = true, workspace = true, default-features = true }
pezkuwi-node-collation-generation = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-approval-voting = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-approval-voting-parallel = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-av-store = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-backing = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-bitfield-signing = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-candidate-validation = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-chain-api = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-chain-selection = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-dispute-coordinator = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-prospective-teyrchains = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-provisioner = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-pvf = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-pvf-checker = { optional = true, workspace = true, default-features = true }
pezkuwi-node-core-runtime-api = { optional = true, workspace = true, default-features = true }
pezkuwi-statement-distribution = { optional = true, workspace = true, default-features = true }
xcm = { workspace = true, default-features = true }
xcm-runtime-apis = { workspace = true, default-features = true }
[dev-dependencies]
assert_matches = { workspace = true }
pezkuwi-node-subsystem-test-helpers = { workspace = true }
pezkuwi-primitives-test-helpers = { workspace = true }
pezkuwi-test-client = { workspace = true }
sp-tracing = { workspace = true }
tempfile = { workspace = true }
[features]
default = ["db", "full-node"]
db = ["sc-service/rocksdb"]
full-node = [
"kvdb-rocksdb",
"parity-db",
"pezkuwi-approval-distribution",
"pezkuwi-availability-bitfield-distribution",
"pezkuwi-availability-distribution",
"pezkuwi-availability-recovery",
"pezkuwi-collator-protocol",
"pezkuwi-dispute-distribution",
"pezkuwi-gossip-support",
"pezkuwi-network-bridge",
"pezkuwi-node-collation-generation",
"pezkuwi-node-core-approval-voting",
"pezkuwi-node-core-approval-voting-parallel",
"pezkuwi-node-core-av-store",
"pezkuwi-node-core-backing",
"pezkuwi-node-core-bitfield-signing",
"pezkuwi-node-core-candidate-validation",
"pezkuwi-node-core-chain-api",
"pezkuwi-node-core-chain-selection",
"pezkuwi-node-core-dispute-coordinator",
"pezkuwi-node-core-prospective-teyrchains",
"pezkuwi-node-core-provisioner",
"pezkuwi-node-core-pvf",
"pezkuwi-node-core-pvf-checker",
"pezkuwi-node-core-runtime-api",
"pezkuwi-statement-distribution",
]
# Configure the native runtimes to use.
zagros-native = [
"frame-metadata-hash-extension",
"zagros-runtime",
"zagros-runtime-constants",
]
pezkuwichain-native = [
"frame-metadata-hash-extension",
"pezkuwichain-runtime",
"pezkuwichain-runtime-constants",
]
# Generate the metadata hash needed for CheckMetadataHash
# in the test runtimes.
metadata-hash = [
"pezkuwichain-runtime?/metadata-hash",
"zagros-runtime?/metadata-hash",
]
runtime-benchmarks = [
"frame-benchmarking-cli/runtime-benchmarks",
"frame-benchmarking/runtime-benchmarks",
"frame-metadata-hash-extension?/runtime-benchmarks",
"frame-system-rpc-runtime-api/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"gum/runtime-benchmarks",
"mmr-gadget/runtime-benchmarks",
"pallet-transaction-payment-rpc-runtime-api/runtime-benchmarks",
"pallet-transaction-payment/runtime-benchmarks",
"pezkuwi-approval-distribution?/runtime-benchmarks",
"pezkuwi-availability-bitfield-distribution?/runtime-benchmarks",
"pezkuwi-availability-distribution?/runtime-benchmarks",
"pezkuwi-availability-recovery?/runtime-benchmarks",
"pezkuwi-collator-protocol?/runtime-benchmarks",
"pezkuwi-core-primitives/runtime-benchmarks",
"pezkuwi-dispute-distribution?/runtime-benchmarks",
"pezkuwi-gossip-support?/runtime-benchmarks",
"pezkuwi-network-bridge?/runtime-benchmarks",
"pezkuwi-node-collation-generation?/runtime-benchmarks",
"pezkuwi-node-core-approval-voting-parallel?/runtime-benchmarks",
"pezkuwi-node-core-approval-voting?/runtime-benchmarks",
"pezkuwi-node-core-av-store?/runtime-benchmarks",
"pezkuwi-node-core-backing?/runtime-benchmarks",
"pezkuwi-node-core-bitfield-signing?/runtime-benchmarks",
"pezkuwi-node-core-candidate-validation?/runtime-benchmarks",
"pezkuwi-node-core-chain-api?/runtime-benchmarks",
"pezkuwi-node-core-chain-selection?/runtime-benchmarks",
"pezkuwi-node-core-dispute-coordinator?/runtime-benchmarks",
"pezkuwi-node-core-prospective-teyrchains?/runtime-benchmarks",
"pezkuwi-node-core-provisioner?/runtime-benchmarks",
"pezkuwi-node-core-pvf-checker?/runtime-benchmarks",
"pezkuwi-node-core-pvf?/runtime-benchmarks",
"pezkuwi-node-core-runtime-api?/runtime-benchmarks",
"pezkuwi-node-core-teyrchains-inherent/runtime-benchmarks",
"pezkuwi-node-network-protocol/runtime-benchmarks",
"pezkuwi-node-primitives/runtime-benchmarks",
"pezkuwi-node-subsystem-test-helpers/runtime-benchmarks",
"pezkuwi-node-subsystem-types/runtime-benchmarks",
"pezkuwi-node-subsystem-util/runtime-benchmarks",
"pezkuwi-node-subsystem/runtime-benchmarks",
"pezkuwi-overseer/runtime-benchmarks",
"pezkuwi-primitives-test-helpers/runtime-benchmarks",
"pezkuwi-primitives/runtime-benchmarks",
"pezkuwi-rpc/runtime-benchmarks",
"pezkuwi-runtime-teyrchains/runtime-benchmarks",
"pezkuwi-statement-distribution?/runtime-benchmarks",
"pezkuwi-test-client/runtime-benchmarks",
"pezkuwichain-runtime-constants?/runtime-benchmarks",
"pezkuwichain-runtime?/runtime-benchmarks",
"sc-authority-discovery/runtime-benchmarks",
"sc-basic-authorship/runtime-benchmarks",
"sc-chain-spec/runtime-benchmarks",
"sc-client-api/runtime-benchmarks",
"sc-consensus-babe/runtime-benchmarks",
"sc-consensus-beefy/runtime-benchmarks",
"sc-consensus-grandpa/runtime-benchmarks",
"sc-consensus-slots/runtime-benchmarks",
"sc-consensus/runtime-benchmarks",
"sc-executor/runtime-benchmarks",
"sc-network-sync/runtime-benchmarks",
"sc-network/runtime-benchmarks",
"sc-offchain/runtime-benchmarks",
"sc-service/runtime-benchmarks",
"sc-sync-state-rpc/runtime-benchmarks",
"sc-sysinfo/runtime-benchmarks",
"sc-transaction-pool-api/runtime-benchmarks",
"sc-transaction-pool/runtime-benchmarks",
"sp-api/runtime-benchmarks",
"sp-authority-discovery/runtime-benchmarks",
"sp-block-builder/runtime-benchmarks",
"sp-blockchain/runtime-benchmarks",
"sp-consensus-babe/runtime-benchmarks",
"sp-consensus-beefy/runtime-benchmarks",
"sp-consensus-grandpa/runtime-benchmarks",
"sp-consensus/runtime-benchmarks",
"sp-genesis-builder/runtime-benchmarks",
"sp-inherents/runtime-benchmarks",
"sp-io/runtime-benchmarks",
"sp-keyring/runtime-benchmarks",
"sp-mmr-primitives/runtime-benchmarks",
"sp-offchain/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
"sp-session/runtime-benchmarks",
"sp-timestamp/runtime-benchmarks",
"sp-transaction-pool/runtime-benchmarks",
"sp-version/runtime-benchmarks",
"xcm-runtime-apis/runtime-benchmarks",
"xcm/runtime-benchmarks",
"zagros-runtime-constants?/runtime-benchmarks",
"zagros-runtime?/runtime-benchmarks",
]
try-runtime = [
"frame-system/try-runtime",
"pallet-transaction-payment/try-runtime",
"pezkuwi-runtime-teyrchains/try-runtime",
"pezkuwichain-runtime?/try-runtime",
"sp-runtime/try-runtime",
"zagros-runtime?/try-runtime",
]
fast-runtime = [
"pezkuwichain-runtime?/fast-runtime",
"zagros-runtime?/fast-runtime",
]
malus = ["full-node"]
runtime-metrics = [
"pezkuwi-runtime-teyrchains/runtime-metrics",
"pezkuwichain-runtime?/runtime-metrics",
"zagros-runtime?/runtime-metrics",
]
@@ -0,0 +1 @@
!/*.json
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+281
View File
@@ -0,0 +1,281 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Code related to benchmarking a node.
use pezkuwi_primitives::AccountId;
use sc_client_api::UsageProvider;
use sp_keyring::Sr25519Keyring;
use sp_runtime::OpaqueExtrinsic;
use crate::*;
macro_rules! identify_chain {
(
$chain:expr,
$nonce:ident,
$current_block:ident,
$period:ident,
$genesis:ident,
$signer:ident,
$generic_code:expr $(,)*
) => {
match $chain {
Chain::Pezkuwi => Err("Pezkuwi runtimes are currently not supported"),
Chain::Kusama => Err("Kusama runtimes are currently not supported"),
Chain::Pezkuwichain => {
#[cfg(feature = "pezkuwichain-native")]
{
use pezkuwichain_runtime as runtime;
let call = $generic_code;
Ok(pezkuwichain_sign_call(
call,
$nonce,
$current_block,
$period,
$genesis,
$signer,
))
}
#[cfg(not(feature = "pezkuwichain-native"))]
{
Err("`pezkuwichain-native` feature not enabled")
}
},
Chain::Zagros => {
#[cfg(feature = "zagros-native")]
{
use zagros_runtime as runtime;
let call = $generic_code;
Ok(zagros_sign_call(call, $nonce, $current_block, $period, $genesis, $signer))
}
#[cfg(not(feature = "zagros-native"))]
{
Err("`zagros-native` feature not enabled")
}
},
Chain::Unknown => {
let _ = $nonce;
let _ = $current_block;
let _ = $period;
let _ = $genesis;
let _ = $signer;
Err("Unknown chain")
},
}
};
}
/// Generates `Balances::TransferKeepAlive` extrinsics for the benchmarks.
///
/// Note: Should only be used for benchmarking.
pub struct TransferKeepAliveBuilder {
client: Arc<FullClient>,
dest: AccountId,
chain: Chain,
}
impl TransferKeepAliveBuilder {
/// Creates a new [`Self`] from the given client and the arguments for the extrinsics.
pub fn new(client: Arc<FullClient>, dest: AccountId, chain: Chain) -> Self {
Self { client, dest, chain }
}
}
impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder {
fn pallet(&self) -> &str {
"balances"
}
fn extrinsic(&self) -> &str {
"transfer_keep_alive"
}
fn build(&self, nonce: u32) -> std::result::Result<OpaqueExtrinsic, &'static str> {
let signer = Sr25519Keyring::Bob.pair();
// We apply the extrinsic directly, so let's take some random period.
let period = 128;
let genesis = self.client.usage_info().chain.best_hash;
let current_block = 0;
let _dest = self.dest.clone();
identify_chain! {
self.chain,
nonce,
current_block,
period,
genesis,
signer,
{
runtime::RuntimeCall::Balances(runtime::BalancesCall::transfer_keep_alive {
dest: _dest.into(),
value: runtime::ExistentialDeposit::get(),
})
},
}
}
}
#[cfg(feature = "zagros-native")]
fn zagros_sign_call(
call: zagros_runtime::RuntimeCall,
nonce: u32,
current_block: u64,
period: u64,
genesis: sp_core::H256,
acc: sp_core::sr25519::Pair,
) -> OpaqueExtrinsic {
use codec::Encode;
use sp_core::Pair;
use zagros_runtime as runtime;
let tx_ext: runtime::TxExtension = (
frame_system::AuthorizeCall::<runtime::Runtime>::new(),
frame_system::CheckNonZeroSender::<runtime::Runtime>::new(),
frame_system::CheckSpecVersion::<runtime::Runtime>::new(),
frame_system::CheckTxVersion::<runtime::Runtime>::new(),
frame_system::CheckGenesis::<runtime::Runtime>::new(),
frame_system::CheckMortality::<runtime::Runtime>::from(sp_runtime::generic::Era::mortal(
period,
current_block,
)),
frame_system::CheckNonce::<runtime::Runtime>::from(nonce),
frame_system::CheckWeight::<runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0),
frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false),
frame_system::WeightReclaim::<runtime::Runtime>::new(),
)
.into();
let payload = runtime::SignedPayload::from_raw(
call.clone(),
tx_ext.clone(),
(
(),
(),
runtime::VERSION.spec_version,
runtime::VERSION.transaction_version,
genesis,
genesis,
(),
(),
(),
None,
(),
),
);
let signature = payload.using_encoded(|p| acc.sign(p));
runtime::UncheckedExtrinsic::new_signed(
call,
sp_runtime::AccountId32::from(acc.public()).into(),
pezkuwi_core_primitives::Signature::Sr25519(signature),
tx_ext,
)
.into()
}
#[cfg(feature = "pezkuwichain-native")]
fn pezkuwichain_sign_call(
call: pezkuwichain_runtime::RuntimeCall,
nonce: u32,
current_block: u64,
period: u64,
genesis: sp_core::H256,
acc: sp_core::sr25519::Pair,
) -> OpaqueExtrinsic {
use codec::Encode;
use pezkuwichain_runtime as runtime;
use sp_core::Pair;
let tx_ext: runtime::TxExtension = (
frame_system::AuthorizeCall::<runtime::Runtime>::new(),
frame_system::CheckNonZeroSender::<runtime::Runtime>::new(),
frame_system::CheckSpecVersion::<runtime::Runtime>::new(),
frame_system::CheckTxVersion::<runtime::Runtime>::new(),
frame_system::CheckGenesis::<runtime::Runtime>::new(),
frame_system::CheckMortality::<runtime::Runtime>::from(sp_runtime::generic::Era::mortal(
period,
current_block,
)),
frame_system::CheckNonce::<runtime::Runtime>::from(nonce),
frame_system::CheckWeight::<runtime::Runtime>::new(),
pallet_transaction_payment::ChargeTransactionPayment::<runtime::Runtime>::from(0),
frame_metadata_hash_extension::CheckMetadataHash::<runtime::Runtime>::new(false),
frame_system::WeightReclaim::<runtime::Runtime>::new(),
)
.into();
let payload = runtime::SignedPayload::from_raw(
call.clone(),
tx_ext.clone(),
(
(),
(),
runtime::VERSION.spec_version,
runtime::VERSION.transaction_version,
genesis,
genesis,
(),
(),
(),
None,
(),
),
);
let signature = payload.using_encoded(|p| acc.sign(p));
runtime::UncheckedExtrinsic::new_signed(
call,
sp_runtime::AccountId32::from(acc.public()).into(),
pezkuwi_core_primitives::Signature::Sr25519(signature),
tx_ext,
)
.into()
}
/// Generates inherent data for benchmarking Pezkuwi, Kusama, Zagros and Pezkuwichain.
///
/// Not to be used outside of benchmarking since it returns mocked values.
pub fn benchmark_inherent_data(
header: pezkuwi_core_primitives::Header,
) -> std::result::Result<sp_inherents::InherentData, sp_inherents::Error> {
use sp_inherents::InherentDataProvider;
let mut inherent_data = sp_inherents::InherentData::new();
// Assume that all runtimes have the `timestamp` pallet.
let d = std::time::Duration::from_millis(0);
let timestamp = sp_timestamp::InherentDataProvider::new(d.into());
futures::executor::block_on(timestamp.provide_inherent_data(&mut inherent_data))?;
let para_data = pezkuwi_primitives::InherentData {
bitfields: Vec::new(),
backed_candidates: Vec::new(),
disputes: Vec::new(),
parent_header: header,
};
inherent_data.put_data(pezkuwi_primitives::TEYRCHAINS_INHERENT_IDENTIFIER, &para_data)?;
Ok(inherent_data)
}
+878
View File
@@ -0,0 +1,878 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Pezkuwi service builder.
#![cfg(feature = "full-node")]
mod partial;
use partial::PezkuwiPartialComponents;
pub(crate) use partial::{new_partial, new_partial_basics};
use crate::{
grandpa_support, open_database,
overseer::{ExtendedOverseerGenArgs, OverseerGen, OverseerGenArgs},
relay_chain_selection::SelectRelayChain,
teyrchains_db, workers, Chain, Error, FullBackend, FullClient, IdentifyVariant,
IsTeyrchainNode, GRANDPA_JUSTIFICATION_PERIOD, KEEP_FINALIZED_FOR_LIVE_NETWORKS,
};
use frame_benchmarking_cli::SUBSTRATE_REFERENCE_HARDWARE;
use gum::info;
use mmr_gadget::MmrGadget;
use pezkuwi_availability_recovery::FETCH_CHUNKS_THRESHOLD;
use pezkuwi_node_core_approval_voting::Config as ApprovalVotingConfig;
use pezkuwi_node_core_av_store::Config as AvailabilityConfig;
use pezkuwi_node_core_candidate_validation::Config as CandidateValidationConfig;
use pezkuwi_node_core_chain_selection::{
self as chain_selection_subsystem, Config as ChainSelectionConfig,
};
use pezkuwi_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig;
use pezkuwi_node_network_protocol::{
peer_set::{PeerSet, PeerSetProtocolNames},
request_response::{IncomingRequest, ReqProtocolNames},
};
use pezkuwi_node_subsystem_types::DefaultSubsystemClient;
use pezkuwi_overseer::{Handle, OverseerConnector};
use pezkuwi_primitives::Block;
use sc_client_api::Backend;
use sc_network::config::FullNetworkConfiguration;
use sc_network_sync::WarpSyncConfig;
use sc_service::{Configuration, RpcHandlers, TaskManager};
use sc_sysinfo::Metric;
use sc_telemetry::TelemetryWorkerHandle;
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
use sp_consensus_beefy::ecdsa_crypto;
use sp_runtime::traits::Block as BlockT;
use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Duration,
};
/// Pezkuwi node service initialization parameters.
pub struct NewFullParams<OverseerGenerator: OverseerGen> {
pub is_teyrchain_node: IsTeyrchainNode,
pub enable_beefy: bool,
/// Whether to enable the block authoring backoff on production networks
/// where it isn't enabled by default.
pub force_authoring_backoff: bool,
pub telemetry_worker_handle: Option<TelemetryWorkerHandle>,
/// The version of the node. TESTING ONLY: `None` can be passed to skip the node/worker version
/// check, both on startup and in the workers.
pub node_version: Option<String>,
/// Whether the node is attempting to run as a secure validator.
pub secure_validator_mode: bool,
/// An optional path to a directory containing the workers.
pub workers_path: Option<std::path::PathBuf>,
/// Optional custom names for the prepare and execute workers.
pub workers_names: Option<(String, String)>,
/// An optional number of the maximum number of pvf execute workers.
pub execute_workers_max_num: Option<usize>,
/// An optional maximum number of pvf workers that can be spawned in the pvf prepare pool for
/// tasks with the priority below critical.
pub prepare_workers_soft_max_num: Option<usize>,
/// An optional absolute number of pvf workers that can be spawned in the pvf prepare pool.
pub prepare_workers_hard_max_num: Option<usize>,
/// How long finalized data should be kept in the availability store (in hours)
pub keep_finalized_for: Option<u32>,
pub overseer_gen: OverseerGenerator,
pub overseer_message_channel_capacity_override: Option<usize>,
#[allow(dead_code)]
pub malus_finality_delay: Option<u32>,
pub hwbench: Option<sc_sysinfo::HwBench>,
/// Set of invulnerable AH collator `PeerId`s
pub invulnerable_ah_collators: HashSet<pezkuwi_node_network_protocol::PeerId>,
/// Override for `HOLD_OFF_DURATION` constant .
pub collator_protocol_hold_off: Option<Duration>,
}
/// Completely built pezkuwi node service.
pub struct NewFull {
pub task_manager: TaskManager,
pub client: Arc<FullClient>,
pub overseer_handle: Option<Handle>,
pub network: Arc<dyn sc_network::service::traits::NetworkService>,
pub sync_service: Arc<sc_network_sync::SyncingService<Block>>,
pub rpc_handlers: RpcHandlers,
pub backend: Arc<FullBackend>,
}
pub struct PezkuwiServiceBuilder<OverseerGenerator, Network>
where
OverseerGenerator: OverseerGen,
Network: sc_network::NetworkBackend<Block, <Block as BlockT>::Hash>,
{
config: Configuration,
params: NewFullParams<OverseerGenerator>,
overseer_connector: OverseerConnector,
partial_components: PezkuwiPartialComponents<SelectRelayChain<FullBackend>>,
net_config: FullNetworkConfiguration<Block, <Block as BlockT>::Hash, Network>,
}
impl<OverseerGenerator, Network> PezkuwiServiceBuilder<OverseerGenerator, Network>
where
OverseerGenerator: OverseerGen,
Network: sc_network::NetworkBackend<Block, <Block as BlockT>::Hash>,
{
/// Create new pezkuwi service builder.
pub fn new(
mut config: Configuration,
params: NewFullParams<OverseerGenerator>,
) -> Result<PezkuwiServiceBuilder<OverseerGenerator, Network>, Error> {
let basics = new_partial_basics(&mut config, params.telemetry_worker_handle.clone())?;
let prometheus_registry = config.prometheus_registry().cloned();
let overseer_connector = OverseerConnector::default();
let overseer_handle = Handle::new(overseer_connector.handle());
let auth_or_collator = config.role.is_authority() || params.is_teyrchain_node.is_collator();
let select_chain = if auth_or_collator {
let metrics = pezkuwi_node_subsystem_util::metrics::Metrics::register(
prometheus_registry.as_ref(),
)?;
SelectRelayChain::new_with_overseer(
basics.backend.clone(),
overseer_handle.clone(),
metrics,
Some(basics.task_manager.spawn_handle()),
)
} else {
SelectRelayChain::new_longest_chain(basics.backend.clone())
};
let partial_components =
new_partial::<SelectRelayChain<_>>(&mut config, basics, select_chain)?;
let net_config = sc_network::config::FullNetworkConfiguration::<_, _, Network>::new(
&config.network,
config.prometheus_config.as_ref().map(|cfg| cfg.registry.clone()),
);
Ok(PezkuwiServiceBuilder {
config,
params,
overseer_connector,
partial_components,
net_config,
})
}
/// Get the genesis hash of the pezkuwi service being built.
pub fn genesis_hash(&self) -> <Block as BlockT>::Hash {
self.partial_components.client.chain_info().genesis_hash
}
/// Add extra request-response protocol to the pezkuwi service.
pub fn add_extra_request_response_protocol(
&mut self,
config: Network::RequestResponseProtocolConfig,
) {
self.net_config.add_request_response_protocol(config);
}
/// Build pezkuwi service.
pub fn build(self) -> Result<NewFull, Error> {
let Self {
config,
params:
NewFullParams {
is_teyrchain_node,
enable_beefy,
force_authoring_backoff,
telemetry_worker_handle: _,
node_version,
secure_validator_mode,
workers_path,
workers_names,
overseer_gen,
overseer_message_channel_capacity_override,
malus_finality_delay: _malus_finality_delay,
hwbench,
execute_workers_max_num,
prepare_workers_soft_max_num,
prepare_workers_hard_max_num,
keep_finalized_for,
invulnerable_ah_collators,
collator_protocol_hold_off,
},
overseer_connector,
partial_components:
sc_service::PartialComponents::<_, _, SelectRelayChain<_>, _, _, _> {
client,
backend,
mut task_manager,
keystore_container,
select_chain,
import_queue,
transaction_pool,
other:
(rpc_extensions_builder, import_setup, rpc_setup, slot_duration, mut telemetry),
},
mut net_config,
} = self;
let role = config.role;
let auth_or_collator = config.role.is_authority() || is_teyrchain_node.is_collator();
let is_offchain_indexing_enabled = config.offchain_worker.indexing_enabled;
let force_authoring = config.force_authoring;
let disable_grandpa = config.disable_grandpa;
let name = config.network.node_name.clone();
let backoff_authoring_blocks = if !force_authoring_backoff &&
(config.chain_spec.is_pezkuwi() || config.chain_spec.is_kusama())
{
// the block authoring backoff is disabled by default on production networks
None
} else {
let mut backoff = sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging::default();
if config.chain_spec.is_pezkuwichain() ||
config.chain_spec.is_versi() ||
config.chain_spec.is_dev()
{
// on testnets that are in flux (like pezkuwichain or versi), finality has stalled
// sometimes due to operational issues and it's annoying to slow down block
// production to 1 block per hour.
backoff.max_interval = 10;
}
Some(backoff)
};
let shared_voter_state = rpc_setup;
let auth_disc_publish_non_global_ips = config.network.allow_non_globals_in_dht;
let auth_disc_public_addresses = config.network.public_addresses.clone();
let genesis_hash = client.chain_info().genesis_hash;
let peer_store_handle = net_config.peer_store_handle();
let prometheus_registry = config.prometheus_registry().cloned();
let metrics = Network::register_notification_metrics(
config.prometheus_config.as_ref().map(|cfg| &cfg.registry),
);
// Note: GrandPa is pushed before the Pezkuwi-specific protocols. This doesn't change
// anything in terms of behaviour, but makes the logs more consistent with the other
// Substrate nodes.
let grandpa_protocol_name =
sc_consensus_grandpa::protocol_standard_name(&genesis_hash, &config.chain_spec);
let (grandpa_protocol_config, grandpa_notification_service) =
sc_consensus_grandpa::grandpa_peers_set_config::<_, Network>(
grandpa_protocol_name.clone(),
metrics.clone(),
Arc::clone(&peer_store_handle),
);
net_config.add_notification_protocol(grandpa_protocol_config);
let beefy_gossip_proto_name =
sc_consensus_beefy::gossip_protocol_name(&genesis_hash, config.chain_spec.fork_id());
// `beefy_on_demand_justifications_handler` is given to `beefy-gadget` task to be run,
// while `beefy_req_resp_cfg` is added to `config.network.request_response_protocols`.
let (beefy_on_demand_justifications_handler, beefy_req_resp_cfg) =
sc_consensus_beefy::communication::request_response::BeefyJustifsRequestHandler::new::<
_,
Network,
>(
&genesis_hash,
config.chain_spec.fork_id(),
client.clone(),
prometheus_registry.clone(),
);
let beefy_notification_service = match enable_beefy {
false => None,
true => {
let (beefy_notification_config, beefy_notification_service) =
sc_consensus_beefy::communication::beefy_peers_set_config::<_, Network>(
beefy_gossip_proto_name.clone(),
metrics.clone(),
Arc::clone(&peer_store_handle),
);
net_config.add_notification_protocol(beefy_notification_config);
net_config.add_request_response_protocol(beefy_req_resp_cfg);
Some(beefy_notification_service)
},
};
// validation/collation protocols are enabled only if `Overseer` is enabled
let peerset_protocol_names =
PeerSetProtocolNames::new(genesis_hash, config.chain_spec.fork_id());
// If this is a validator or running alongside a teyrchain node, we need to enable the
// networking protocols.
//
// Collators and teyrchain full nodes require the collator and validator networking to send
// collations and to be able to recover PoVs.
let notification_services = if role.is_authority() ||
is_teyrchain_node.is_running_alongside_teyrchain_node()
{
use pezkuwi_network_bridge::{peer_sets_info, IsAuthority};
let is_authority = if role.is_authority() { IsAuthority::Yes } else { IsAuthority::No };
peer_sets_info::<_, Network>(
is_authority,
&peerset_protocol_names,
metrics.clone(),
Arc::clone(&peer_store_handle),
)
.into_iter()
.map(|(config, (peerset, service))| {
net_config.add_notification_protocol(config);
(peerset, service)
})
.collect::<HashMap<PeerSet, Box<dyn sc_network::NotificationService>>>()
} else {
std::collections::HashMap::new()
};
let req_protocol_names = ReqProtocolNames::new(&genesis_hash, config.chain_spec.fork_id());
let (collation_req_v1_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (collation_req_v2_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (available_data_req_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (pov_req_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (chunk_req_v1_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (chunk_req_v2_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let grandpa_hard_forks = if config.chain_spec.is_kusama() {
grandpa_support::kusama_hard_forks()
} else {
Vec::new()
};
let warp_sync = Arc::new(sc_consensus_grandpa::warp_proof::NetworkProvider::new(
backend.clone(),
import_setup.1.shared_authority_set().clone(),
grandpa_hard_forks,
));
let ext_overseer_args = if is_teyrchain_node.is_running_alongside_teyrchain_node() {
None
} else {
let teyrchains_db = open_database(&config.database)?;
let candidate_validation_config = if role.is_authority() {
let (prep_worker_path, exec_worker_path) = workers::determine_workers_paths(
workers_path,
workers_names,
node_version.clone(),
)?;
log::info!("🚀 Using prepare-worker binary at: {:?}", prep_worker_path);
log::info!("🚀 Using execute-worker binary at: {:?}", exec_worker_path);
Some(CandidateValidationConfig {
artifacts_cache_path: config
.database
.path()
.ok_or(Error::DatabasePathRequired)?
.join("pvf-artifacts"),
node_version,
secure_validator_mode,
prep_worker_path,
exec_worker_path,
// Default execution workers is 4 because we have 8 cores on the reference
// hardware, and this accounts for 50% of that cpu capacity.
pvf_execute_workers_max_num: execute_workers_max_num.unwrap_or(4),
pvf_prepare_workers_soft_max_num: prepare_workers_soft_max_num.unwrap_or(1),
pvf_prepare_workers_hard_max_num: prepare_workers_hard_max_num.unwrap_or(2),
})
} else {
None
};
let (candidate_req_v2_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let (dispute_req_receiver, cfg) =
IncomingRequest::get_config_receiver::<_, Network>(&req_protocol_names);
net_config.add_request_response_protocol(cfg);
let approval_voting_config = ApprovalVotingConfig {
col_approval_data: teyrchains_db::REAL_COLUMNS.col_approval_data,
slot_duration_millis: slot_duration.as_millis() as u64,
};
let dispute_coordinator_config = DisputeCoordinatorConfig {
col_dispute_data: teyrchains_db::REAL_COLUMNS.col_dispute_coordinator_data,
};
let chain_selection_config = ChainSelectionConfig {
col_data: teyrchains_db::REAL_COLUMNS.col_chain_selection_data,
stagnant_check_interval: Default::default(),
stagnant_check_mode: chain_selection_subsystem::StagnantCheckMode::PruneOnly,
};
// Kusama + testnets get a higher threshold, we are conservative on Pezkuwi for now.
let fetch_chunks_threshold =
if config.chain_spec.is_pezkuwi() { None } else { Some(FETCH_CHUNKS_THRESHOLD) };
let availability_config = AvailabilityConfig {
col_data: teyrchains_db::REAL_COLUMNS.col_availability_data,
col_meta: teyrchains_db::REAL_COLUMNS.col_availability_meta,
keep_finalized_for: if matches!(
config.chain_spec.identify_chain(),
Chain::Pezkuwichain
) {
keep_finalized_for.unwrap_or(1)
} else {
KEEP_FINALIZED_FOR_LIVE_NETWORKS
},
};
Some(ExtendedOverseerGenArgs {
keystore: keystore_container.local_keystore(),
teyrchains_db,
candidate_validation_config,
availability_config,
pov_req_receiver,
chunk_req_v1_receiver,
chunk_req_v2_receiver,
candidate_req_v2_receiver,
approval_voting_config,
dispute_req_receiver,
dispute_coordinator_config,
chain_selection_config,
fetch_chunks_threshold,
invulnerable_ah_collators,
collator_protocol_hold_off,
})
};
let (network, system_rpc_tx, tx_handler_controller, sync_service) =
sc_service::build_network(sc_service::BuildNetworkParams {
config: &config,
net_config,
client: client.clone(),
transaction_pool: transaction_pool.clone(),
spawn_handle: task_manager.spawn_handle(),
import_queue,
block_announce_validator_builder: None,
warp_sync_config: Some(WarpSyncConfig::WithProvider(warp_sync)),
block_relay: None,
metrics,
})?;
if config.offchain_worker.enabled {
use futures::FutureExt;
task_manager.spawn_handle().spawn(
"offchain-workers-runner",
"offchain-work",
sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions {
runtime_api_provider: client.clone(),
keystore: Some(keystore_container.keystore()),
offchain_db: backend.offchain_storage(),
transaction_pool: Some(OffchainTransactionPoolFactory::new(
transaction_pool.clone(),
)),
network_provider: Arc::new(network.clone()),
is_validator: role.is_authority(),
enable_http_requests: false,
custom_extensions: move |_| vec![],
})?
.run(client.clone(), task_manager.spawn_handle())
.boxed(),
);
}
let network_config = config.network.clone();
let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams {
config,
backend: backend.clone(),
client: client.clone(),
keystore: keystore_container.keystore(),
network: network.clone(),
sync_service: sync_service.clone(),
rpc_builder: Box::new(rpc_extensions_builder),
transaction_pool: transaction_pool.clone(),
task_manager: &mut task_manager,
system_rpc_tx,
tx_handler_controller,
telemetry: telemetry.as_mut(),
tracing_execute_block: None,
})?;
if let Some(hwbench) = hwbench {
sc_sysinfo::print_hwbench(&hwbench);
match SUBSTRATE_REFERENCE_HARDWARE.check_hardware(&hwbench, role.is_authority()) {
Err(err) if role.is_authority() => {
if err
.0
.iter()
.any(|failure| matches!(failure.metric, Metric::Blake2256Parallel { .. }))
{
log::warn!(
"⚠️ Starting January 2025 the hardware will fail the minimal physical CPU cores requirements {} for role 'Authority',\n\
find out more when this will become mandatory at:\n\
https://wiki.network.pezkuwichain.io/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
}
if err
.0
.iter()
.any(|failure| !matches!(failure.metric, Metric::Blake2256Parallel { .. }))
{
log::warn!(
"⚠️ The hardware does not meet the minimal requirements {} for role 'Authority' find out more at:\n\
https://wiki.network.pezkuwichain.io/docs/maintain-guides-how-to-validate-polkadot#reference-hardware",
err
);
}
},
_ => {},
}
if let Some(ref mut telemetry) = telemetry {
let telemetry_handle = telemetry.handle();
task_manager.spawn_handle().spawn(
"telemetry_hwbench",
None,
sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench),
);
}
}
let (block_import, link_half, babe_link, beefy_links) = import_setup;
let overseer_client = client.clone();
let spawner = task_manager.spawn_handle();
let authority_discovery_service =
// We need the authority discovery if this node is either a validator or running alongside a teyrchain node.
// Teyrchains node require the authority discovery for finding relay chain validators for sending
// their PoVs or recovering PoVs.
if role.is_authority() || is_teyrchain_node.is_running_alongside_teyrchain_node() {
use futures::StreamExt;
use sc_network::{Event, NetworkEventStream};
let authority_discovery_role = if role.is_authority() {
sc_authority_discovery::Role::PublishAndDiscover(keystore_container.keystore())
} else {
// don't publish our addresses when we're not an authority (collator, cumulus, ..)
sc_authority_discovery::Role::Discover
};
let dht_event_stream =
network.event_stream("authority-discovery").filter_map(|e| async move {
match e {
Event::Dht(e) => Some(e),
_ => None,
}
});
let (worker, service) = sc_authority_discovery::new_worker_and_service_with_config(
sc_authority_discovery::WorkerConfig {
publish_non_global_ips: auth_disc_publish_non_global_ips,
public_addresses: auth_disc_public_addresses,
// Require that authority discovery records are signed.
strict_record_validation: true,
persisted_cache_directory: network_config.net_config_path,
..Default::default()
},
client.clone(),
Arc::new(network.clone()),
Box::pin(dht_event_stream),
authority_discovery_role,
prometheus_registry.clone(),
task_manager.spawn_handle(),
);
task_manager.spawn_handle().spawn(
"authority-discovery-worker",
Some("authority-discovery"),
Box::pin(worker.run()),
);
Some(service)
} else {
None
};
let runtime_client = Arc::new(DefaultSubsystemClient::new(
overseer_client.clone(),
OffchainTransactionPoolFactory::new(transaction_pool.clone()),
));
let overseer_handle = if let Some(authority_discovery_service) = authority_discovery_service
{
let (overseer, overseer_handle) = overseer_gen
.generate::<sc_service::SpawnTaskHandle, DefaultSubsystemClient<FullClient>>(
overseer_connector,
OverseerGenArgs {
runtime_client,
network_service: network.clone(),
sync_service: sync_service.clone(),
authority_discovery_service,
collation_req_v1_receiver,
collation_req_v2_receiver,
available_data_req_receiver,
registry: prometheus_registry.as_ref(),
spawner,
is_teyrchain_node,
overseer_message_channel_capacity_override,
req_protocol_names,
peerset_protocol_names,
notification_services,
},
ext_overseer_args,
)
.map_err(|e| {
gum::error!("Failed to init overseer: {}", e);
e
})?;
let handle = Handle::new(overseer_handle.clone());
{
let handle = handle.clone();
task_manager.spawn_essential_handle().spawn_blocking(
"overseer",
None,
Box::pin(async move {
use futures::{pin_mut, select, FutureExt};
let forward = pezkuwi_overseer::forward_events(overseer_client, handle);
let forward = forward.fuse();
let overseer_fut = overseer.run().fuse();
pin_mut!(overseer_fut);
pin_mut!(forward);
select! {
() = forward => (),
() = overseer_fut => (),
complete => (),
}
}),
);
}
Some(handle)
} else {
assert!(
!auth_or_collator,
"Precondition congruence (false) is guaranteed by manual checking. qed"
);
None
};
if role.is_authority() {
let proposer = sc_basic_authorship::ProposerFactory::new(
task_manager.spawn_handle(),
client.clone(),
transaction_pool.clone(),
prometheus_registry.as_ref(),
telemetry.as_ref().map(|x| x.handle()),
);
let client_clone = client.clone();
let overseer_handle =
overseer_handle.as_ref().ok_or(Error::AuthoritiesRequireRealOverseer)?.clone();
let slot_duration = babe_link.config().slot_duration();
let babe_config = sc_consensus_babe::BabeParams {
keystore: keystore_container.keystore(),
client: client.clone(),
select_chain,
block_import,
env: proposer,
sync_oracle: sync_service.clone(),
justification_sync_link: sync_service.clone(),
create_inherent_data_providers: move |parent, ()| {
let client_clone = client_clone.clone();
let overseer_handle = overseer_handle.clone();
async move {
let teyrchain =
pezkuwi_node_core_teyrchains_inherent::TeyrchainsInherentDataProvider::new(
client_clone,
overseer_handle,
parent,
);
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot =
sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp,
slot_duration,
);
Ok((slot, timestamp, teyrchain))
}
},
force_authoring,
backoff_authoring_blocks,
babe_link,
block_proposal_slot_portion: sc_consensus_babe::SlotProportion::new(2f32 / 3f32),
max_block_proposal_slot_portion: None,
telemetry: telemetry.as_ref().map(|x| x.handle()),
};
let babe = sc_consensus_babe::start_babe(babe_config)?;
task_manager.spawn_essential_handle().spawn_blocking("babe", None, babe);
}
// if the node isn't actively participating in consensus then it doesn't
// need a keystore, regardless of which protocol we use below.
let keystore_opt =
if role.is_authority() { Some(keystore_container.keystore()) } else { None };
// beefy is enabled if its notification service exists
if let Some(notification_service) = beefy_notification_service {
let justifications_protocol_name =
beefy_on_demand_justifications_handler.protocol_name();
let network_params = sc_consensus_beefy::BeefyNetworkParams {
network: Arc::new(network.clone()),
sync: sync_service.clone(),
gossip_protocol_name: beefy_gossip_proto_name,
justifications_protocol_name,
notification_service,
_phantom: core::marker::PhantomData::<Block>,
};
let payload_provider = sp_consensus_beefy::mmr::MmrRootProvider::new(client.clone());
let beefy_params = sc_consensus_beefy::BeefyParams {
client: client.clone(),
backend: backend.clone(),
payload_provider,
runtime: client.clone(),
key_store: keystore_opt.clone(),
network_params,
min_block_delta: 8,
prometheus_registry: prometheus_registry.clone(),
links: beefy_links,
on_demand_justifications_handler: beefy_on_demand_justifications_handler,
is_authority: role.is_authority(),
};
let gadget = sc_consensus_beefy::start_beefy_gadget::<
_,
_,
_,
_,
_,
_,
_,
ecdsa_crypto::AuthorityId,
>(beefy_params);
// BEEFY is part of consensus, if it fails we'll bring the node down with it to make
// sure it is noticed.
task_manager
.spawn_essential_handle()
.spawn_blocking("beefy-gadget", None, gadget);
}
// When offchain indexing is enabled, MMR gadget should also run.
if is_offchain_indexing_enabled {
task_manager.spawn_essential_handle().spawn_blocking(
"mmr-gadget",
None,
MmrGadget::start(
client.clone(),
backend.clone(),
sp_mmr_primitives::INDEXING_PREFIX.to_vec(),
),
);
}
let config = sc_consensus_grandpa::Config {
// FIXME substrate#1578 make this available through chainspec
// Grandpa performance can be improved a bit by tuning this parameter, see:
// https://github.com/paritytech/polkadot/issues/5464
gossip_duration: Duration::from_millis(1000),
justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD,
name: Some(name),
observer_enabled: false,
keystore: keystore_opt,
local_role: role,
telemetry: telemetry.as_ref().map(|x| x.handle()),
protocol_name: grandpa_protocol_name,
};
let enable_grandpa = !disable_grandpa;
if enable_grandpa {
// start the full GRANDPA voter
// NOTE: unlike in substrate we are currently running the full
// GRANDPA voter protocol for all full nodes (regardless of whether
// they're validators or not). at this point the full voter should
// provide better guarantees of block and vote data availability than
// the observer.
let mut voting_rules_builder = sc_consensus_grandpa::VotingRulesBuilder::default();
#[cfg(not(feature = "malus"))]
let _malus_finality_delay = None;
if let Some(delay) = _malus_finality_delay {
info!(?delay, "Enabling malus finality delay",);
voting_rules_builder =
voting_rules_builder.add(sc_consensus_grandpa::BeforeBestBlockBy(delay));
};
let grandpa_config = sc_consensus_grandpa::GrandpaParams {
config,
link: link_half,
network: network.clone(),
sync: sync_service.clone(),
voting_rule: voting_rules_builder.build(),
prometheus_registry: prometheus_registry.clone(),
shared_voter_state,
telemetry: telemetry.as_ref().map(|x| x.handle()),
notification_service: grandpa_notification_service,
offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(
transaction_pool.clone(),
),
};
task_manager.spawn_essential_handle().spawn_blocking(
"grandpa-voter",
None,
sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?,
);
}
Ok(NewFull {
task_manager,
client,
overseer_handle,
network,
sync_service,
rpc_handlers,
backend,
})
}
}
/// Create a new full node of arbitrary runtime and executor.
///
/// This is an advanced feature and not recommended for general use. Generally, `build_full` is
/// a better choice.
///
/// `workers_path` is used to get the path to the directory where auxiliary worker binaries reside.
/// If not specified, the main binary's directory is searched first, then `/usr/lib/pezkuwi` is
/// searched. If the path points to an executable rather then directory, that executable is used
/// both as preparation and execution worker (supposed to be used for tests only).
pub fn new_full<
OverseerGenerator: OverseerGen,
Network: sc_network::NetworkBackend<Block, <Block as BlockT>::Hash>,
>(
config: Configuration,
params: NewFullParams<OverseerGenerator>,
) -> Result<NewFull, Error> {
PezkuwiServiceBuilder::<OverseerGenerator, Network>::new(config, params)?.build()
}
+284
View File
@@ -0,0 +1,284 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Pezkuwi service partial builder.
#![cfg(feature = "full-node")]
use crate::{
fake_runtime_api::RuntimeApi, grandpa_support, relay_chain_selection, Error, FullBackend,
FullClient, IdentifyVariant, GRANDPA_JUSTIFICATION_PERIOD,
};
use pezkuwi_primitives::Block;
use sc_consensus_grandpa::FinalityProofProvider as GrandpaFinalityProofProvider;
use sc_executor::{HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY};
use sc_service::{Configuration, Error as SubstrateServiceError, KeystoreContainer, TaskManager};
use sc_telemetry::{Telemetry, TelemetryWorker, TelemetryWorkerHandle};
use sc_transaction_pool_api::OffchainTransactionPoolFactory;
use sp_consensus::SelectChain;
use sp_consensus_babe::inherents::BabeCreateInherentDataProviders;
use sp_consensus_beefy::ecdsa_crypto;
use std::sync::Arc;
type FullSelectChain = relay_chain_selection::SelectRelayChain<FullBackend>;
type FullGrandpaBlockImport<ChainSelection = FullSelectChain> =
sc_consensus_grandpa::GrandpaBlockImport<FullBackend, Block, FullClient, ChainSelection>;
type FullBeefyBlockImport<InnerBlockImport, AuthorityId> =
sc_consensus_beefy::import::BeefyBlockImport<
Block,
FullBackend,
FullClient,
InnerBlockImport,
AuthorityId,
>;
pub(crate) type PezkuwiPartialComponents<ChainSelection> = sc_service::PartialComponents<
FullClient,
FullBackend,
ChainSelection,
sc_consensus::DefaultImportQueue<Block>,
sc_transaction_pool::TransactionPoolHandle<Block, FullClient>,
(
Box<
dyn Fn(
pezkuwi_rpc::SubscriptionTaskExecutor,
) -> Result<pezkuwi_rpc::RpcExtension, SubstrateServiceError>,
>,
(
sc_consensus_babe::BabeBlockImport<
Block,
FullClient,
FullBeefyBlockImport<
FullGrandpaBlockImport<ChainSelection>,
ecdsa_crypto::AuthorityId,
>,
BabeCreateInherentDataProviders<Block>,
ChainSelection,
>,
sc_consensus_grandpa::LinkHalf<Block, FullClient, ChainSelection>,
sc_consensus_babe::BabeLink<Block>,
sc_consensus_beefy::BeefyVoterLinks<Block, ecdsa_crypto::AuthorityId>,
),
sc_consensus_grandpa::SharedVoterState,
sp_consensus_babe::SlotDuration,
Option<Telemetry>,
),
>;
pub(crate) struct Basics {
pub(crate) task_manager: TaskManager,
pub(crate) client: Arc<FullClient>,
pub(crate) backend: Arc<FullBackend>,
pub(crate) keystore_container: KeystoreContainer,
pub(crate) telemetry: Option<Telemetry>,
}
pub(crate) fn new_partial_basics(
config: &mut Configuration,
telemetry_worker_handle: Option<TelemetryWorkerHandle>,
) -> Result<Basics, Error> {
let telemetry = config
.telemetry_endpoints
.clone()
.filter(|x| !x.is_empty())
.map(move |endpoints| -> Result<_, sc_telemetry::Error> {
let (worker, mut worker_handle) = if let Some(worker_handle) = telemetry_worker_handle {
(None, worker_handle)
} else {
let worker = TelemetryWorker::new(16)?;
let worker_handle = worker.handle();
(Some(worker), worker_handle)
};
let telemetry = worker_handle.new_telemetry(endpoints);
Ok((worker, telemetry))
})
.transpose()?;
let heap_pages = config
.executor
.default_heap_pages
.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| HeapAllocStrategy::Static { extra_pages: h as _ });
let executor = WasmExecutor::builder()
.with_execution_method(config.executor.wasm_method)
.with_onchain_heap_alloc_strategy(heap_pages)
.with_offchain_heap_alloc_strategy(heap_pages)
.with_max_runtime_instances(config.executor.max_runtime_instances)
.with_runtime_cache_size(config.executor.runtime_cache_size)
.build();
let (client, backend, keystore_container, task_manager) =
sc_service::new_full_parts::<Block, RuntimeApi, _>(
&config,
telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()),
executor,
)?;
let client = Arc::new(client);
let telemetry = telemetry.map(|(worker, telemetry)| {
if let Some(worker) = worker {
task_manager.spawn_handle().spawn(
"telemetry",
Some("telemetry"),
Box::pin(worker.run()),
);
}
telemetry
});
Ok(Basics { task_manager, client, backend, keystore_container, telemetry })
}
pub(crate) fn new_partial<ChainSelection>(
config: &mut Configuration,
Basics { task_manager, backend, client, keystore_container, telemetry }: Basics,
select_chain: ChainSelection,
) -> Result<PezkuwiPartialComponents<ChainSelection>, Error>
where
ChainSelection: 'static + SelectChain<Block>,
{
let transaction_pool = Arc::from(
sc_transaction_pool::Builder::new(
task_manager.spawn_essential_handle(),
client.clone(),
config.role.is_authority().into(),
)
.with_options(config.transaction_pool.clone())
.with_prometheus(config.prometheus_registry())
.build(),
);
let grandpa_hard_forks = if config.chain_spec.is_kusama() {
grandpa_support::kusama_hard_forks()
} else {
Vec::new()
};
let (grandpa_block_import, grandpa_link) =
sc_consensus_grandpa::block_import_with_authority_set_hard_forks(
client.clone(),
GRANDPA_JUSTIFICATION_PERIOD,
&client.clone(),
select_chain.clone(),
grandpa_hard_forks,
telemetry.as_ref().map(|x| x.handle()),
)?;
let justification_import = grandpa_block_import.clone();
let (beefy_block_import, beefy_voter_links, beefy_rpc_links) =
sc_consensus_beefy::beefy_block_import_and_links(
grandpa_block_import,
backend.clone(),
client.clone(),
config.prometheus_registry().cloned(),
);
let babe_config = sc_consensus_babe::configuration(&*client)?;
let slot_duration = babe_config.slot_duration();
let (block_import, babe_link) = sc_consensus_babe::block_import(
babe_config.clone(),
beefy_block_import,
client.clone(),
Arc::new(move |_, _| async move {
let timestamp = sp_timestamp::InherentDataProvider::from_system_time();
let slot = sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration(
*timestamp,
slot_duration,
);
Ok((slot, timestamp))
}) as BabeCreateInherentDataProviders<Block>,
select_chain.clone(),
OffchainTransactionPoolFactory::new(transaction_pool.clone()),
)?;
let (import_queue, babe_worker_handle) =
sc_consensus_babe::import_queue(sc_consensus_babe::ImportQueueParams {
link: babe_link.clone(),
block_import: block_import.clone(),
justification_import: Some(Box::new(justification_import)),
client: client.clone(),
slot_duration,
spawner: &task_manager.spawn_essential_handle(),
registry: config.prometheus_registry(),
telemetry: telemetry.as_ref().map(|x| x.handle()),
})?;
let justification_stream = grandpa_link.justification_stream();
let shared_authority_set = grandpa_link.shared_authority_set().clone();
let shared_voter_state = sc_consensus_grandpa::SharedVoterState::empty();
let finality_proof_provider = GrandpaFinalityProofProvider::new_for_service(
backend.clone(),
Some(shared_authority_set.clone()),
);
let import_setup = (block_import, grandpa_link, babe_link, beefy_voter_links);
let rpc_setup = shared_voter_state.clone();
let rpc_extensions_builder = {
let client = client.clone();
let keystore = keystore_container.keystore();
let transaction_pool = transaction_pool.clone();
let select_chain = select_chain.clone();
let chain_spec = config.chain_spec.cloned_box();
let backend = backend.clone();
move |subscription_executor: pezkuwi_rpc::SubscriptionTaskExecutor|
-> Result<pezkuwi_rpc::RpcExtension, sc_service::Error> {
let deps = pezkuwi_rpc::FullDeps {
client: client.clone(),
pool: transaction_pool.clone(),
select_chain: select_chain.clone(),
chain_spec: chain_spec.cloned_box(),
babe: pezkuwi_rpc::BabeDeps {
babe_worker_handle: babe_worker_handle.clone(),
keystore: keystore.clone(),
},
grandpa: pezkuwi_rpc::GrandpaDeps {
shared_voter_state: shared_voter_state.clone(),
shared_authority_set: shared_authority_set.clone(),
justification_stream: justification_stream.clone(),
subscription_executor: subscription_executor.clone(),
finality_provider: finality_proof_provider.clone(),
},
beefy: pezkuwi_rpc::BeefyDeps::<ecdsa_crypto::AuthorityId> {
beefy_finality_proof_stream: beefy_rpc_links.from_voter_justif_stream.clone(),
beefy_best_block_stream: beefy_rpc_links.from_voter_best_beefy_stream.clone(),
subscription_executor,
},
backend: backend.clone(),
};
pezkuwi_rpc::create_full(deps).map_err(Into::into)
}
};
Ok(sc_service::PartialComponents {
client,
backend,
task_manager,
keystore_container,
select_chain,
import_queue,
transaction_pool,
other: (
Box::new(rpc_extensions_builder),
import_setup,
rpc_setup,
slot_duration,
telemetry,
),
})
}
+256
View File
@@ -0,0 +1,256 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Pezkuwi chain configurations.
#[cfg(feature = "pezkuwichain-native")]
use pezkuwichain_runtime as pezkuwichain;
use sc_chain_spec::ChainSpecExtension;
#[cfg(any(feature = "zagros-native", feature = "pezkuwichain-native"))]
use sc_chain_spec::ChainType;
#[cfg(any(feature = "zagros-native", feature = "pezkuwichain-native"))]
use sc_telemetry::TelemetryEndpoints;
use serde::{Deserialize, Serialize};
#[cfg(feature = "zagros-native")]
use zagros_runtime as zagros;
#[cfg(feature = "zagros-native")]
const ZAGROS_STAGING_TELEMETRY_URL: &str = "wss://telemetry.pezkuwichain.io/submit/";
#[cfg(feature = "pezkuwichain-native")]
const PEZKUWICHAIN_STAGING_TELEMETRY_URL: &str = "wss://telemetry.pezkuwichain.io/submit/";
#[cfg(feature = "pezkuwichain-native")]
const VERSI_STAGING_TELEMETRY_URL: &str = "wss://telemetry.pezkuwichain.io/submit/";
#[cfg(any(feature = "zagros-native", feature = "pezkuwichain-native"))]
const DEFAULT_PROTOCOL_ID: &str = "hez";
/// Node `ChainSpec` extensions.
///
/// Additional parameters for some Substrate core modules,
/// customizable from the chain spec.
#[derive(Default, Clone, Serialize, Deserialize, ChainSpecExtension)]
#[serde(rename_all = "camelCase")]
pub struct Extensions {
/// Block numbers with known hashes.
pub fork_blocks: sc_client_api::ForkBlocks<pezkuwi_primitives::Block>,
/// Known bad block hashes.
pub bad_blocks: sc_client_api::BadBlocks<pezkuwi_primitives::Block>,
/// The light sync state.
///
/// This value will be set by the `sync-state rpc` implementation.
pub light_sync_state: sc_sync_state_rpc::LightSyncStateExtension,
}
// Generic chain spec, in case when we don't have the native runtime.
pub type GenericChainSpec = sc_service::GenericChainSpec<Extensions>;
/// The `ChainSpec` parameterized for the zagros runtime.
#[cfg(feature = "zagros-native")]
pub type ZagrosChainSpec = sc_service::GenericChainSpec<Extensions>;
/// The `ChainSpec` parameterized for the zagros runtime.
// Dummy chain spec, but that is fine when we don't have the native runtime.
#[cfg(not(feature = "zagros-native"))]
pub type ZagrosChainSpec = GenericChainSpec;
/// The `ChainSpec` parameterized for the pezkuwichain runtime.
#[cfg(feature = "pezkuwichain-native")]
pub type PezkuwichainChainSpec = sc_service::GenericChainSpec<Extensions>;
/// The `ChainSpec` parameterized for the pezkuwichain runtime.
// Dummy chain spec, but that is fine when we don't have the native runtime.
#[cfg(not(feature = "pezkuwichain-native"))]
pub type PezkuwichainChainSpec = GenericChainSpec;
pub fn pezkuwi_config() -> Result<GenericChainSpec, String> {
GenericChainSpec::from_json_bytes(&include_bytes!("../chain-specs/pezkuwi.json")[..])
}
pub fn kusama_config() -> Result<GenericChainSpec, String> {
GenericChainSpec::from_json_bytes(&include_bytes!("../chain-specs/kusama.json")[..])
}
pub fn zagros_config() -> Result<ZagrosChainSpec, String> {
ZagrosChainSpec::from_json_bytes(&include_bytes!("../chain-specs/zagros.json")[..])
}
pub fn paseo_config() -> Result<GenericChainSpec, String> {
GenericChainSpec::from_json_bytes(&include_bytes!("../chain-specs/paseo.json")[..])
}
pub fn pezkuwichain_config() -> Result<PezkuwichainChainSpec, String> {
PezkuwichainChainSpec::from_json_bytes(&include_bytes!("../chain-specs/pezkuwichain.json")[..])
}
/// Zagros staging testnet config.
#[cfg(feature = "zagros-native")]
pub fn zagros_staging_testnet_config() -> Result<ZagrosChainSpec, String> {
Ok(ZagrosChainSpec::builder(
zagros::WASM_BINARY.ok_or("Zagros development wasm not available")?,
Default::default(),
)
.with_name("Zagros Staging Testnet")
.with_id("zagros_staging_testnet")
.with_chain_type(ChainType::Live)
.with_genesis_config_preset_name("staging_testnet")
.with_telemetry_endpoints(
TelemetryEndpoints::new(vec![(ZAGROS_STAGING_TELEMETRY_URL.to_string(), 0)])
.expect("Zagros Staging telemetry url is valid; qed"),
)
.with_protocol_id(DEFAULT_PROTOCOL_ID)
.build())
}
/// Pezkuwichain staging testnet config.
#[cfg(feature = "pezkuwichain-native")]
pub fn pezkuwichain_staging_testnet_config() -> Result<PezkuwichainChainSpec, String> {
Ok(PezkuwichainChainSpec::builder(
pezkuwichain::WASM_BINARY.ok_or("Pezkuwichain development wasm not available")?,
Default::default(),
)
.with_name("Pezkuwichain Staging Testnet")
.with_id("pezkuwichain_staging_testnet")
.with_chain_type(ChainType::Live)
.with_genesis_config_preset_name("staging_testnet")
.with_telemetry_endpoints(
TelemetryEndpoints::new(vec![(PEZKUWICHAIN_STAGING_TELEMETRY_URL.to_string(), 0)])
.expect("Pezkuwichain Staging telemetry url is valid; qed"),
)
.with_protocol_id(DEFAULT_PROTOCOL_ID)
.build())
}
pub fn versi_chain_spec_properties() -> serde_json::map::Map<String, serde_json::Value> {
serde_json::json!({
"ss58Format": 42,
"tokenDecimals": 12,
"tokenSymbol": "VRS",
})
.as_object()
.expect("Map given; qed")
.clone()
}
/// Versi staging testnet config.
#[cfg(feature = "pezkuwichain-native")]
pub fn versi_staging_testnet_config() -> Result<PezkuwichainChainSpec, String> {
Ok(PezkuwichainChainSpec::builder(
pezkuwichain::WASM_BINARY.ok_or("Versi development wasm not available")?,
Default::default(),
)
.with_name("Versi Staging Testnet")
.with_id("versi_staging_testnet")
.with_chain_type(ChainType::Live)
.with_genesis_config_preset_name("staging_testnet")
.with_telemetry_endpoints(
TelemetryEndpoints::new(vec![(VERSI_STAGING_TELEMETRY_URL.to_string(), 0)])
.expect("Versi Staging telemetry url is valid; qed"),
)
.with_protocol_id("versi")
.with_properties(versi_chain_spec_properties())
.build())
}
/// Zagros development config (single validator Alice)
#[cfg(feature = "zagros-native")]
pub fn zagros_development_config() -> Result<ZagrosChainSpec, String> {
Ok(ZagrosChainSpec::builder(
zagros::WASM_BINARY.ok_or("Zagros development wasm not available")?,
Default::default(),
)
.with_name("Development")
.with_id("zagros_dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
.with_protocol_id(DEFAULT_PROTOCOL_ID)
.build())
}
/// Pezkuwichain development config (single validator Alice)
#[cfg(feature = "pezkuwichain-native")]
pub fn pezkuwichain_development_config() -> Result<PezkuwichainChainSpec, String> {
Ok(PezkuwichainChainSpec::builder(
pezkuwichain::WASM_BINARY.ok_or("Pezkuwichain development wasm not available")?,
Default::default(),
)
.with_name("Development")
.with_id("pezkuwichain_dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
.with_protocol_id(DEFAULT_PROTOCOL_ID)
.build())
}
/// `Versi` development config (single validator Alice)
#[cfg(feature = "pezkuwichain-native")]
pub fn versi_development_config() -> Result<PezkuwichainChainSpec, String> {
Ok(PezkuwichainChainSpec::builder(
pezkuwichain::WASM_BINARY.ok_or("Versi development wasm not available")?,
Default::default(),
)
.with_name("Development")
.with_id("versi_dev")
.with_chain_type(ChainType::Development)
.with_genesis_config_preset_name(sp_genesis_builder::DEV_RUNTIME_PRESET)
.with_protocol_id("versi")
.build())
}
/// Zagros local testnet config (multivalidator Alice + Bob)
#[cfg(feature = "zagros-native")]
pub fn zagros_local_testnet_config() -> Result<ZagrosChainSpec, String> {
Ok(ZagrosChainSpec::builder(
zagros::fast_runtime_binary::WASM_BINARY.ok_or("Zagros development wasm not available")?,
Default::default(),
)
.with_name("Zagros Local Testnet")
.with_id("zagros_local_testnet")
.with_chain_type(ChainType::Local)
.with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)
.with_protocol_id(DEFAULT_PROTOCOL_ID)
.build())
}
/// Pezkuwichain local testnet config (multivalidator Alice + Bob)
#[cfg(feature = "pezkuwichain-native")]
pub fn pezkuwichain_local_testnet_config() -> Result<PezkuwichainChainSpec, String> {
Ok(PezkuwichainChainSpec::builder(
pezkuwichain::fast_runtime_binary::WASM_BINARY
.ok_or("Pezkuwichain development wasm not available")?,
Default::default(),
)
.with_name("Pezkuwichain Local Testnet")
.with_id("pezkuwichain_local_testnet")
.with_chain_type(ChainType::Local)
.with_genesis_config_preset_name(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET)
.with_protocol_id(DEFAULT_PROTOCOL_ID)
.build())
}
/// `Versi` local testnet config (multivalidator Alice + Bob + Charlie + Dave)
#[cfg(feature = "pezkuwichain-native")]
pub fn versi_local_testnet_config() -> Result<PezkuwichainChainSpec, String> {
Ok(PezkuwichainChainSpec::builder(
pezkuwichain::WASM_BINARY
.ok_or("Pezkuwichain development wasm (used for versi) not available")?,
Default::default(),
)
.with_name("Versi Local Testnet")
.with_id("versi_local_testnet")
.with_chain_type(ChainType::Local)
.with_genesis_config_preset_name("versi_local_testnet")
.with_protocol_id("versi")
.build())
}
@@ -0,0 +1,457 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Provides "fake" runtime API implementations
//!
//! These are used to provide a type that implements these runtime APIs without requiring to import
//! the native runtimes.
use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo};
use pezkuwi_primitives::{
runtime_api, slashing, AccountId, AuthorityDiscoveryId, Balance, Block, BlockNumber,
CandidateCommitments, CandidateEvent, CandidateHash,
CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, DisputeState,
ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage,
InboundHrmpMessage, Nonce, OccupiedCoreAssumption, PersistedValidationData, PvfCheckStatement,
ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash,
ValidatorId, ValidatorIndex, ValidatorSignature,
};
use sp_consensus_beefy::ecdsa_crypto::{AuthorityId as BeefyId, Signature as BeefySignature};
use sp_consensus_grandpa::AuthorityId as GrandpaId;
use sp_core::OpaqueMetadata;
use sp_runtime::{
traits::Block as BlockT,
transaction_validity::{TransactionSource, TransactionValidity},
ApplyExtrinsicResult,
};
use sp_version::RuntimeVersion;
use sp_weights::Weight;
use std::collections::BTreeMap;
use xcm::{
Version as XcmVersion, VersionedAssetId, VersionedAssets, VersionedLocation, VersionedXcm,
};
sp_api::decl_runtime_apis! {
/// This runtime API is only implemented for the test runtime!
pub trait GetLastTimestamp {
/// Returns the last timestamp of a runtime.
fn get_last_timestamp() -> u64;
}
}
#[allow(dead_code)]
struct Runtime;
sp_api::impl_runtime_apis! {
impl sp_api::Core<Block> for Runtime {
fn version() -> RuntimeVersion {
unimplemented!()
}
fn execute_block(_: <Block as BlockT>::LazyBlock) {
unimplemented!()
}
fn initialize_block(_: &<Block as BlockT>::Header) -> sp_runtime::ExtrinsicInclusionMode {
unimplemented!()
}
}
impl sp_api::Metadata<Block> for Runtime {
fn metadata() -> OpaqueMetadata {
unimplemented!()
}
fn metadata_at_version(_: u32) -> Option<OpaqueMetadata> {
unimplemented!()
}
fn metadata_versions() -> Vec<u32> {
unimplemented!()
}
}
impl sp_block_builder::BlockBuilder<Block> for Runtime {
fn apply_extrinsic(_: <Block as BlockT>::Extrinsic) -> ApplyExtrinsicResult {
unimplemented!()
}
fn finalize_block() -> <Block as BlockT>::Header {
unimplemented!()
}
fn inherent_extrinsics(_: sp_inherents::InherentData) -> Vec<<Block as BlockT>::Extrinsic> {
unimplemented!()
}
fn check_inherents(
_: <Block as BlockT>::LazyBlock,
_: sp_inherents::InherentData,
) -> sp_inherents::CheckInherentsResult {
unimplemented!()
}
}
impl sp_transaction_pool::runtime_api::TaggedTransactionQueue<Block> for Runtime {
fn validate_transaction(
_: TransactionSource,
_: <Block as BlockT>::Extrinsic,
_: <Block as BlockT>::Hash,
) -> TransactionValidity {
unimplemented!()
}
}
impl sp_offchain::OffchainWorkerApi<Block> for Runtime {
fn offchain_worker(_: &<Block as BlockT>::Header) {
unimplemented!()
}
}
impl runtime_api::TeyrchainHost<Block> for Runtime {
fn validators() -> Vec<ValidatorId> {
unimplemented!()
}
fn validator_groups() -> (Vec<Vec<ValidatorIndex>>, GroupRotationInfo<BlockNumber>) {
unimplemented!()
}
fn availability_cores() -> Vec<CoreState<Hash, BlockNumber>> {
unimplemented!()
}
fn persisted_validation_data(_: ParaId, _: OccupiedCoreAssumption)
-> Option<PersistedValidationData<Hash, BlockNumber>> {
unimplemented!()
}
fn assumed_validation_data(
_: ParaId,
_: Hash,
) -> Option<(PersistedValidationData<Hash, BlockNumber>, ValidationCodeHash)> {
unimplemented!()
}
fn check_validation_outputs(
_: ParaId,
_: CandidateCommitments,
) -> bool {
unimplemented!()
}
fn session_index_for_child() -> SessionIndex {
unimplemented!()
}
fn validation_code(_: ParaId, _: OccupiedCoreAssumption)
-> Option<ValidationCode> {
unimplemented!()
}
fn candidate_pending_availability(_: ParaId) -> Option<CommittedCandidateReceipt<Hash>> {
unimplemented!()
}
fn candidate_events() -> Vec<CandidateEvent<Hash>> {
unimplemented!()
}
fn session_info(_: SessionIndex) -> Option<SessionInfo> {
unimplemented!()
}
fn session_executor_params(_: SessionIndex) -> Option<ExecutorParams> {
unimplemented!()
}
fn dmq_contents(_: ParaId) -> Vec<InboundDownwardMessage<BlockNumber>> {
unimplemented!()
}
fn inbound_hrmp_channels_contents(
_: ParaId
) -> BTreeMap<ParaId, Vec<InboundHrmpMessage<BlockNumber>>> {
unimplemented!()
}
fn validation_code_by_hash(_: ValidationCodeHash) -> Option<ValidationCode> {
unimplemented!()
}
fn on_chain_votes() -> Option<ScrapedOnChainVotes<Hash>> {
unimplemented!()
}
fn submit_pvf_check_statement(
_: PvfCheckStatement,
_: ValidatorSignature,
) {
unimplemented!()
}
fn pvfs_require_precheck() -> Vec<ValidationCodeHash> {
unimplemented!()
}
fn validation_code_hash(_: ParaId, _: OccupiedCoreAssumption)
-> Option<ValidationCodeHash>
{
unimplemented!()
}
fn disputes() -> Vec<(SessionIndex, CandidateHash, DisputeState<BlockNumber>)> {
unimplemented!()
}
fn unapplied_slashes(
) -> Vec<(SessionIndex, CandidateHash, slashing::LegacyPendingSlashes)> {
unimplemented!()
}
fn key_ownership_proof(
_: ValidatorId,
) -> Option<slashing::OpaqueKeyOwnershipProof> {
unimplemented!()
}
fn submit_report_dispute_lost(
_: slashing::DisputeProof,
_: slashing::OpaqueKeyOwnershipProof,
) -> Option<()> {
unimplemented!()
}
}
impl sp_consensus_beefy::BeefyApi<Block, BeefyId> for Runtime {
fn beefy_genesis() -> Option<BlockNumber> {
unimplemented!()
}
fn validator_set() -> Option<sp_consensus_beefy::ValidatorSet<BeefyId>> {
unimplemented!()
}
fn submit_report_double_voting_unsigned_extrinsic(
_: sp_consensus_beefy::DoubleVotingProof<
BlockNumber,
BeefyId,
BeefySignature,
>,
_: sp_consensus_beefy::OpaqueKeyOwnershipProof,
) -> Option<()> {
unimplemented!()
}
fn submit_report_fork_voting_unsigned_extrinsic(
_: sp_consensus_beefy::ForkVotingProof<
<Block as BlockT>::Header,
BeefyId,
sp_runtime::OpaqueValue
>,
_: sp_consensus_beefy::OpaqueKeyOwnershipProof,
) -> Option<()> {
unimplemented!()
}
fn submit_report_future_block_voting_unsigned_extrinsic(
_: sp_consensus_beefy::FutureBlockVotingProof<BlockNumber, BeefyId>,
_: sp_consensus_beefy::OpaqueKeyOwnershipProof,
) -> Option<()> {
unimplemented!()
}
fn generate_key_ownership_proof(
_: sp_consensus_beefy::ValidatorSetId,
_: BeefyId,
) -> Option<sp_consensus_beefy::OpaqueKeyOwnershipProof> {
unimplemented!()
}
}
impl sp_mmr_primitives::MmrApi<Block, Hash, BlockNumber> for Runtime {
fn mmr_root() -> Result<Hash, sp_mmr_primitives::Error> {
unimplemented!()
}
fn mmr_leaf_count() -> Result<sp_mmr_primitives::LeafIndex, sp_mmr_primitives::Error> {
unimplemented!()
}
fn generate_proof(
_: Vec<BlockNumber>,
_: Option<BlockNumber>,
) -> Result<(Vec<sp_mmr_primitives::EncodableOpaqueLeaf>, sp_mmr_primitives::LeafProof<Hash>), sp_mmr_primitives::Error> {
unimplemented!()
}
fn generate_ancestry_proof(
_: BlockNumber,
_: Option<BlockNumber>,
) -> Result<sp_mmr_primitives::AncestryProof<Hash>, sp_mmr_primitives::Error> {
unimplemented!()
}
fn verify_proof(_: Vec<sp_mmr_primitives::EncodableOpaqueLeaf>, _: sp_mmr_primitives::LeafProof<Hash>)
-> Result<(), sp_mmr_primitives::Error>
{
unimplemented!()
}
fn verify_proof_stateless(
_: Hash,
_: Vec<sp_mmr_primitives::EncodableOpaqueLeaf>,
_: sp_mmr_primitives::LeafProof<Hash>
) -> Result<(), sp_mmr_primitives::Error> {
unimplemented!()
}
}
impl sp_consensus_grandpa::GrandpaApi<Block> for Runtime {
fn grandpa_authorities() -> Vec<(GrandpaId, u64)> {
unimplemented!()
}
fn current_set_id() -> sp_consensus_grandpa::SetId {
unimplemented!()
}
fn submit_report_equivocation_unsigned_extrinsic(
_: sp_consensus_grandpa::EquivocationProof<
<Block as BlockT>::Hash,
sp_runtime::traits::NumberFor<Block>,
>,
_: sp_consensus_grandpa::OpaqueKeyOwnershipProof,
) -> Option<()> {
unimplemented!()
}
fn generate_key_ownership_proof(
_: sp_consensus_grandpa::SetId,
_: sp_consensus_grandpa::AuthorityId,
) -> Option<sp_consensus_grandpa::OpaqueKeyOwnershipProof> {
unimplemented!()
}
}
impl sp_consensus_babe::BabeApi<Block> for Runtime {
fn configuration() -> sp_consensus_babe::BabeConfiguration {
unimplemented!()
}
fn current_epoch_start() -> sp_consensus_babe::Slot {
unimplemented!()
}
fn current_epoch() -> sp_consensus_babe::Epoch {
unimplemented!()
}
fn next_epoch() -> sp_consensus_babe::Epoch {
unimplemented!()
}
fn generate_key_ownership_proof(
_: sp_consensus_babe::Slot,
_: sp_consensus_babe::AuthorityId,
) -> Option<sp_consensus_babe::OpaqueKeyOwnershipProof> {
unimplemented!()
}
fn submit_report_equivocation_unsigned_extrinsic(
_: sp_consensus_babe::EquivocationProof<<Block as BlockT>::Header>,
_: sp_consensus_babe::OpaqueKeyOwnershipProof,
) -> Option<()> {
unimplemented!()
}
}
impl sp_authority_discovery::AuthorityDiscoveryApi<Block> for Runtime {
fn authorities() -> Vec<AuthorityDiscoveryId> {
unimplemented!()
}
}
impl sp_session::SessionKeys<Block> for Runtime {
fn generate_session_keys(_: Option<Vec<u8>>) -> Vec<u8> {
unimplemented!()
}
fn decode_session_keys(
_: Vec<u8>,
) -> Option<Vec<(Vec<u8>, sp_core::crypto::KeyTypeId)>> {
unimplemented!()
}
}
impl frame_system_rpc_runtime_api::AccountNonceApi<Block, AccountId, Nonce> for Runtime {
fn account_nonce(_: AccountId) -> Nonce {
unimplemented!()
}
}
impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi<
Block,
Balance,
> for Runtime {
fn query_info(_: <Block as BlockT>::Extrinsic, _: u32) -> RuntimeDispatchInfo<Balance> {
unimplemented!()
}
fn query_fee_details(_: <Block as BlockT>::Extrinsic, _: u32) -> FeeDetails<Balance> {
unimplemented!()
}
fn query_weight_to_fee(_: Weight) -> Balance {
unimplemented!()
}
fn query_length_to_fee(_: u32) -> Balance {
unimplemented!()
}
}
impl crate::fake_runtime_api::GetLastTimestamp<Block> for Runtime {
fn get_last_timestamp() -> u64 {
unimplemented!()
}
}
impl xcm_runtime_apis::fees::XcmPaymentApi<Block> for Runtime {
fn query_acceptable_payment_assets(_: xcm::Version) -> Result<Vec<VersionedAssetId>, xcm_runtime_apis::fees::Error> {
unimplemented!()
}
fn query_weight_to_asset_fee(_: Weight, _: VersionedAssetId) -> Result<u128, xcm_runtime_apis::fees::Error> {
unimplemented!()
}
fn query_xcm_weight(_: VersionedXcm<()>) -> Result<Weight, xcm_runtime_apis::fees::Error> {
unimplemented!()
}
fn query_delivery_fees(_: VersionedLocation, _: VersionedXcm<()>, _: VersionedAssetId) -> Result<VersionedAssets, xcm_runtime_apis::fees::Error> {
unimplemented!()
}
}
impl xcm_runtime_apis::dry_run::DryRunApi<Block, (), (), ()> for Runtime {
fn dry_run_call(_: (), _: (), _: XcmVersion) -> Result<xcm_runtime_apis::dry_run::CallDryRunEffects<()>, xcm_runtime_apis::dry_run::Error> {
unimplemented!()
}
fn dry_run_xcm(_: VersionedLocation, _: VersionedXcm<()>) -> Result<xcm_runtime_apis::dry_run::XcmDryRunEffects<()>, xcm_runtime_apis::dry_run::Error> {
unimplemented!()
}
}
}
+165
View File
@@ -0,0 +1,165 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Pezkuwi-specific GRANDPA integration utilities.
use sp_runtime::traits::{Block as BlockT, Header as _, NumberFor};
use crate::HeaderProvider;
#[cfg(feature = "full-node")]
use pezkuwi_primitives::{Block, Hash};
/// Returns the block hash of the block at the given `target_number` by walking
/// backwards from the given `current_header`.
pub(super) fn walk_backwards_to_target_block<Block, HP>(
backend: &HP,
target_number: NumberFor<Block>,
current_header: &Block::Header,
) -> Result<(Block::Hash, NumberFor<Block>), sp_blockchain::Error>
where
Block: BlockT,
HP: HeaderProvider<Block>,
{
let mut target_hash = current_header.hash();
let mut target_header = current_header.clone();
loop {
if *target_header.number() < target_number {
unreachable!(
"we are traversing backwards from a known block; \
blocks are stored contiguously; \
qed"
);
}
if *target_header.number() == target_number {
return Ok((target_hash, target_number));
}
target_hash = *target_header.parent_hash();
target_header = backend
.header(target_hash)?
.expect("Header known to exist due to the existence of one of its descendants; qed");
}
}
/// GRANDPA hard forks due to borked migration of session keys after a runtime
/// upgrade (at #1491596), the signaled authority set changes were invalid
/// (blank keys) and were impossible to finalize. The authorities for these
/// intermediary pending changes are replaced with a static list comprised of
/// w3f validators and randomly selected validators from the latest session (at
/// #1500988).
#[cfg(feature = "full-node")]
pub(crate) fn kusama_hard_forks() -> Vec<sc_consensus_grandpa::AuthoritySetHardFork<Block>> {
use sp_core::crypto::Ss58Codec;
use std::str::FromStr;
let forks = vec![
(623, "01e94e1e7e9cf07b3b0bf4e1717fce7448e5563901c2ef2e3b8e9ecaeba088b1", 1492283),
(624, "ddc4323c5e8966844dfaa87e0c2f74ef6b43115f17bf8e4ff38845a62d02b9a9", 1492436),
(625, "38ba115b296663e424e32d7b1655cd795719cef4fd7d579271a6d01086cf1628", 1492586),
(626, "f3172b6b8497c10fc772f5dada4eeb1f4c4919c97de9de2e1a439444d5a057ff", 1492955),
(627, "b26526aea299e9d24af29fdacd5cf4751a663d24894e3d0a37833aa14c58424a", 1493338),
(628, "3980d024327d53b8d01ef0d198a052cd058dd579508d8ed6283fe3614e0a3694", 1493913),
(629, "31f22997a786c25ee677786373368cae6fd501fd1bc4b212b8e267235c88179d", 1495083),
(630, "1c65eb250cf54b466c64f1a4003d1415a7ee275e49615450c0e0525179857eef", 1497404),
(631, "9e44116467cc9d7e224e36487bf2cf571698cae16b25f54a7430f1278331fdd8", 1498598),
];
let authorities = vec![
"CwjLJ1zPWK5Ao9WChAFp7rWGEgN3AyXXjTRPrqgm5WwBpoS",
"Dp8FHpZTzvoKXztkfrUAkF6xNf6sjVU5ZLZ29NEGUazouou",
"DtK7YfkhNWU6wEPF1dShsFdhtosVAuJPLkoGhKhG1r5LjKq",
"FLnHYBuoyThzqJ45tdb8P6yMLdocM7ir27Pg1AnpYoygm1K",
"FWEfJ5UMghr52UopgYjawAg6hQg3ztbQek75pfeRtLVi8pB",
"ECoLHAu7HKWGTB9od82HAtequYj6hvNHigkGSB9g3ApxAwB",
"GL1Tg3Uppo8GYL9NjKj4dWKcS6tW98REop9G5hpu7HgFwTa",
"ExnjU5LZMktrgtQBE3An6FsQfvaKG1ukxPqwhJydgdgarmY",
"CagLpgCBu5qJqYF2tpFX6BnU4yHvMGSjc7r3Ed1jY3tMbQt",
"DsrtmMsD4ijh3n4uodxPoiW9NZ7v7no5wVvPVj8fL1dfrWB",
"HQB4EctrVR68ozZDyBiRJzLRAEGh1YKgCkAsFjJcegL9RQA",
"H2YTYbXTFkDY1cGnv164ecnDT3hsD2bQXtyiDbcQuXcQZUV",
"H5WL8jXmbkCoEcLfvqJkbLUeGrDFsJiMXkhhRWn3joct1tE",
"DpB37GDrJDYcmg2df2eqsrPKMay1u8hyZ6sQi2FuUiUeNLu",
"FR8yjKRA9MTjvFGK8kfzrdC23Fr6xd7rfBvZXSjAsmuxURE",
"DxHPty3B9fpj3duu6Gc6gCSCAvsydJHJEY5G3oVYT8S5BYJ",
"DbVKC8ZJjevrhqSnZyJMMvmPL7oPPL4ed1roxawYnHVgyin",
"DVJV81kab2J6oTyRJ9T3NCwW2DSrysbWCssvMcE6cwZHnAd",
"Fg4rDAyzoVzf39Zo8JFPo4W314ntNWNwm3shr4xKe8M1fJg",
"GUaNcnAruMVxHGTs7gGpSUpigRJboQYQBBQyPohkFcP6NMH",
"J4BMGF4W9yWiJz4pkhQW73X6QMGpKUzmPppVnqzBCqw5dQq",
"E1cR61L1tdDEop4WdWVqcq1H1x6VqsDpSHvFyUeC41uruVJ",
"GoWLzBsj1f23YtdDpyntnvN1LwXKhF5TEeZvBeTVxofgWGR",
"CwHwmbogSwtRbrkajVBNubPvWmHBGU4bhMido54M9CjuKZD",
"FLT63y9oVXJnyiWMAL4RvWxsQx21Vymw9961Z7NRFmSG7rw",
"FoQ2y6JuHuHTG4rHFL3f2hCxfJMvtrq8wwPWdv8tsdkcyA8",
"D7QQKqqs8ocGorRA12h4QoBSHDia1DkHeXT4eMfjWQ483QH",
"J6z7FP35F9DiiU985bhkDTS3WxyeTBeoo9MtLdLoD3GiWPj",
"EjapydCK25AagodRbDECavHAy8yQY1tmeRhwUXhVWx4cFPv",
"H8admATcRkGCrF1dTDDBCjQDsYjMkuPaN9YwR2mSCj4DWMQ",
"FtHMRU1fxsoswJjBvyCGvECepC7gP2X77QbNpyikYSqqR6k",
"DzY5gwr45GVRUFzRMmeg8iffpqYF47nm3XbJhmjG97FijaE",
"D3HKWAihSUmg8HrfeFrftSwNK7no261yA9RNr3LUUdsuzuJ",
"D82DwwGJGTcSvtB3SmNrZejnSertbPzpkYvDUp3ibScL3ne",
"FTPxLXLQvMDQYFA6VqNLGwWPKhemMYP791XVj8TmDpFuV3b",
"FzGfKmS7N8Z1tvCBU5JH1eBXZQ9pCtRNoMUnNVv38wZNq72",
"GDfm1MyLAQ7Rh8YPtF6FtMweV4hz91zzeDy2sSABNNqAbmg",
"DiVQbq7sozeKp7PXPM1HLFc2m7ih8oepKLRK99oBY3QZak1",
"HErWh7D2RzrjWWB2fTJfcAejD9MJpadeWWZM2Wnk7LiNWfG",
"Es4DbDauYZYyRJbr6VxrhdcM1iufP9GtdBYf3YtSEvdwNyb",
"EBgXT6FaVo4WsN2LmfnB2jnpDFf4zay3E492RGSn6v1tY99",
"Dr9Zg4fxZurexParztL9SezFeHsPwdP8uGgULeRMbk8DDHJ",
"JEnSTZJpLh91cSryptj57RtFxq9xXqf4U5wBH3qoP91ZZhN",
"DqtRkrmtPANa8wrYR7Ce2LxJxk2iNFtiCxv1cXbx54uqdTN",
"GaxmF53xbuTFKopVEseWiaCTa8fC6f99n4YfW8MGPSPYX3s",
"EiCesgkAaighBKMpwFSAUdvwE4mRjBjNmmd5fP6d4FG8DAx",
"HVbwWGUx7kCgUGap1Mfcs37g6JAZ5qsfsM7TsDRcSqvfxmd",
"G45bc8Ajrd6YSXav77gQwjjGoAsR2qiGd1aLzkMy7o1RLwd",
"Cqix2rD93Mdf7ytg8tBavAig2TvhXPgPZ2mejQvkq7qgRPq",
"GpodE2S5dPeVjzHB4Drm8R9rEwcQPtwAspXqCVz1ooFWf5K",
"CwfmfRmzPKLj3ntSCejuVwYmQ1F9iZWY4meQrAVoJ2G8Kce",
"Fhp5NPvutRCJ4Gx3G8vCYGaveGcU3KgTwfrn5Zr8sLSgwVx",
"GeYRRPkyi23wSF3cJGjq82117fKJZUbWsAGimUnzb5RPbB1",
"DzCJ4y5oT611dfKQwbBDVbtCfENTdMCjb4KGMU3Mq6nyUMu",
];
let authorities = authorities
.into_iter()
.map(|address| {
(
sp_consensus_grandpa::AuthorityId::from_ss58check(address)
.expect("hard fork authority addresses are static and they should be carefully defined; qed."),
1,
)
})
.collect::<Vec<_>>();
forks
.into_iter()
.map(|(set_id, hash, number)| {
let hash = Hash::from_str(hash)
.expect("hard fork hashes are static and they should be carefully defined; qed.");
sc_consensus_grandpa::AuthoritySetHardFork {
set_id,
block: (hash, number),
authorities: authorities.clone(),
last_finalized: None,
}
})
.collect()
}
+532
View File
@@ -0,0 +1,532 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Pezkuwi service. Specialized wrapper over substrate service.
#![deny(unused_results)]
pub mod benchmarking;
pub mod chain_spec;
mod fake_runtime_api;
mod grandpa_support;
mod relay_chain_selection;
mod teyrchains_db;
#[cfg(feature = "full-node")]
pub mod builder;
#[cfg(feature = "full-node")]
pub mod overseer;
#[cfg(feature = "full-node")]
pub mod workers;
#[cfg(feature = "full-node")]
pub use crate::builder::{new_full, NewFull, NewFullParams};
#[cfg(feature = "full-node")]
pub use self::overseer::{
CollatorOverseerGen, ExtendedOverseerGenArgs, OverseerGen, OverseerGenArgs,
ValidatorOverseerGen,
};
#[cfg(test)]
mod tests;
#[cfg(feature = "full-node")]
use crate::builder::{new_partial, new_partial_basics};
#[cfg(feature = "full-node")]
use {
pezkuwi_node_core_approval_voting as approval_voting_subsystem,
pezkuwi_node_core_av_store::Error as AvailabilityError,
pezkuwi_node_core_chain_selection as chain_selection_subsystem,
};
use pezkuwi_node_subsystem_util::database::Database;
use pezkuwi_overseer::SpawnGlue;
#[cfg(feature = "full-node")]
pub use {
pezkuwi_overseer::{Handle, Overseer, OverseerConnector, OverseerHandle},
pezkuwi_primitives::runtime_api::TeyrchainHost,
relay_chain_selection::SelectRelayChain,
sc_client_api::AuxStore,
sp_authority_discovery::AuthorityDiscoveryApi,
sp_blockchain::{HeaderBackend, HeaderMetadata},
sp_consensus_babe::BabeApi,
};
use std::{path::PathBuf, sync::Arc};
use prometheus_endpoint::Registry;
use sc_service::SpawnTaskHandle;
pub use chain_spec::{GenericChainSpec, PezkuwichainChainSpec, ZagrosChainSpec};
pub use pezkuwi_primitives::{Block, BlockId, BlockNumber, CollatorPair, Hash, Id as ParaId};
pub use sc_client_api::{Backend, CallExecutor};
pub use sc_consensus::{BlockImport, LongestChain};
pub use sc_executor::NativeExecutionDispatch;
use sc_executor::WasmExecutor;
pub use sc_service::{
config::{DatabaseSource, PrometheusConfig},
ChainSpec, Configuration, Error as SubstrateServiceError, PruningMode, Role, TFullBackend,
TFullCallExecutor, TFullClient, TaskManager, TransactionPoolOptions,
};
pub use sp_api::{ApiRef, ConstructRuntimeApi, Core as CoreApi, ProvideRuntimeApi};
pub use sp_consensus::{Proposal, SelectChain};
pub use sp_runtime::{
generic,
traits::{self as runtime_traits, BlakeTwo256, Block as BlockT, Header as HeaderT, NumberFor},
};
#[cfg(feature = "pezkuwichain-native")]
pub use {pezkuwichain_runtime, pezkuwichain_runtime_constants};
#[cfg(feature = "zagros-native")]
pub use {zagros_runtime, zagros_runtime_constants};
pub use fake_runtime_api::{GetLastTimestamp, RuntimeApi};
#[cfg(feature = "full-node")]
pub type FullBackend = sc_service::TFullBackend<Block>;
#[cfg(feature = "full-node")]
pub type FullClient = sc_service::TFullClient<
Block,
RuntimeApi,
WasmExecutor<(sp_io::SubstrateHostFunctions, frame_benchmarking::benchmarking::HostFunctions)>,
>;
/// The minimum period of blocks on which justifications will be
/// imported and generated.
const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512;
/// The number of hours to keep finalized data in the availability store for live networks.
const KEEP_FINALIZED_FOR_LIVE_NETWORKS: u32 = 25;
/// Provides the header and block number for a hash.
///
/// Decouples `sc_client_api::Backend` and `sp_blockchain::HeaderBackend`.
pub trait HeaderProvider<Block, Error = sp_blockchain::Error>: Send + Sync + 'static
where
Block: BlockT,
Error: std::fmt::Debug + Send + Sync + 'static,
{
/// Obtain the header for a hash.
fn header(
&self,
hash: <Block as BlockT>::Hash,
) -> Result<Option<<Block as BlockT>::Header>, Error>;
/// Obtain the block number for a hash.
fn number(
&self,
hash: <Block as BlockT>::Hash,
) -> Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>, Error>;
}
impl<Block, T> HeaderProvider<Block> for T
where
Block: BlockT,
T: sp_blockchain::HeaderBackend<Block> + 'static,
{
fn header(
&self,
hash: Block::Hash,
) -> sp_blockchain::Result<Option<<Block as BlockT>::Header>> {
<Self as sp_blockchain::HeaderBackend<Block>>::header(self, hash)
}
fn number(
&self,
hash: Block::Hash,
) -> sp_blockchain::Result<Option<<<Block as BlockT>::Header as HeaderT>::Number>> {
<Self as sp_blockchain::HeaderBackend<Block>>::number(self, hash)
}
}
/// Decoupling the provider.
///
/// Mandated since `trait HeaderProvider` can only be
/// implemented once for a generic `T`.
pub trait HeaderProviderProvider<Block>: Send + Sync + 'static
where
Block: BlockT,
{
type Provider: HeaderProvider<Block> + 'static;
fn header_provider(&self) -> &Self::Provider;
}
impl<Block, T> HeaderProviderProvider<Block> for T
where
Block: BlockT,
T: sc_client_api::Backend<Block> + 'static,
{
type Provider = <T as sc_client_api::Backend<Block>>::Blockchain;
fn header_provider(&self) -> &Self::Provider {
self.blockchain()
}
}
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error(transparent)]
Io(#[from] std::io::Error),
#[error(transparent)]
AddrFormatInvalid(#[from] std::net::AddrParseError),
#[error(transparent)]
Sub(#[from] SubstrateServiceError),
#[error(transparent)]
Blockchain(#[from] sp_blockchain::Error),
#[error(transparent)]
Consensus(#[from] sp_consensus::Error),
#[error("Failed to create an overseer")]
Overseer(#[from] pezkuwi_overseer::SubsystemError),
#[error(transparent)]
Prometheus(#[from] prometheus_endpoint::PrometheusError),
#[error(transparent)]
Telemetry(#[from] sc_telemetry::Error),
#[cfg(feature = "full-node")]
#[error(transparent)]
Availability(#[from] AvailabilityError),
#[error("Authorities require the real overseer implementation")]
AuthoritiesRequireRealOverseer,
#[cfg(feature = "full-node")]
#[error("Creating a custom database is required for validators")]
DatabasePathRequired,
#[cfg(feature = "full-node")]
#[error("Expected at least one of pezkuwi, kusama, zagros or pezkuwichain runtime feature")]
NoRuntime,
#[cfg(feature = "full-node")]
#[error("Worker binaries not executable, prepare binary: {prep_worker_path:?}, execute binary: {exec_worker_path:?}")]
InvalidWorkerBinaries { prep_worker_path: PathBuf, exec_worker_path: PathBuf },
#[cfg(feature = "full-node")]
#[error("Worker binaries could not be found, make sure pezkuwi was built and installed correctly. Please see the readme for the latest instructions (https://github.com/paritytech/polkadot-sdk/tree/master/polkadot). If you ran with `cargo run`, please run `cargo build` first. Searched given workers path ({given_workers_path:?}), pezkuwi binary path ({current_exe_path:?}), and lib path (/usr/lib/pezkuwi), workers names: {workers_names:?}")]
MissingWorkerBinaries {
given_workers_path: Option<PathBuf>,
current_exe_path: PathBuf,
workers_names: Option<(String, String)>,
},
#[cfg(feature = "full-node")]
#[error("Version of worker binary ({worker_version}) is different from node version ({node_version}), worker_path: {worker_path}. If you ran with `cargo run`, please run `cargo build` first, otherwise try to `cargo clean`. TESTING ONLY: this check can be disabled with --disable-worker-version-check")]
WorkerBinaryVersionMismatch {
worker_version: String,
node_version: String,
worker_path: PathBuf,
},
}
/// Identifies the variant of the chain.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Chain {
/// Pezkuwi.
Pezkuwi,
/// Kusama.
Kusama,
/// Pezkuwichain or one of its derivations.
Pezkuwichain,
/// Zagros.
Zagros,
/// Unknown chain?
Unknown,
}
/// Can be called for a `Configuration` to identify which network the configuration targets.
pub trait IdentifyVariant {
/// Returns if this is a configuration for the `Pezkuwi` network.
fn is_pezkuwi(&self) -> bool;
/// Returns if this is a configuration for the `Kusama` network.
fn is_kusama(&self) -> bool;
/// Returns if this is a configuration for the `Zagros` network.
fn is_zagros(&self) -> bool;
/// Returns if this is a configuration for the `Pezkuwichain` network.
fn is_pezkuwichain(&self) -> bool;
/// Returns if this is a configuration for the `Versi` test network.
fn is_versi(&self) -> bool;
/// Returns true if this configuration is for a development network.
fn is_dev(&self) -> bool;
/// Identifies the variant of the chain.
fn identify_chain(&self) -> Chain;
}
impl IdentifyVariant for Box<dyn ChainSpec> {
fn is_pezkuwi(&self) -> bool {
self.id().starts_with("pezkuwi") || self.id().starts_with("hez")
}
fn is_kusama(&self) -> bool {
self.id().starts_with("kusama") || self.id().starts_with("ksm")
}
fn is_zagros(&self) -> bool {
self.id().starts_with("zagros") || self.id().starts_with("wnd")
}
fn is_pezkuwichain(&self) -> bool {
self.id().starts_with("pezkuwichain") || self.id().starts_with("rco")
}
fn is_versi(&self) -> bool {
self.id().starts_with("versi") || self.id().starts_with("vrs")
}
fn is_dev(&self) -> bool {
self.id().ends_with("dev")
}
fn identify_chain(&self) -> Chain {
if self.is_pezkuwi() {
Chain::Pezkuwi
} else if self.is_kusama() {
Chain::Kusama
} else if self.is_zagros() {
Chain::Zagros
} else if self.is_pezkuwichain() || self.is_versi() {
Chain::Pezkuwichain
} else {
Chain::Unknown
}
}
}
#[cfg(feature = "full-node")]
pub fn open_database(db_source: &DatabaseSource) -> Result<Arc<dyn Database>, Error> {
let teyrchains_db = match db_source {
DatabaseSource::RocksDb { path, .. } => teyrchains_db::open_creating_rocksdb(
path.clone(),
teyrchains_db::CacheSizes::default(),
)?,
DatabaseSource::ParityDb { path, .. } => teyrchains_db::open_creating_paritydb(
path.parent().ok_or(Error::DatabasePathRequired)?.into(),
teyrchains_db::CacheSizes::default(),
)?,
DatabaseSource::Auto { paritydb_path, rocksdb_path, .. } => {
if paritydb_path.is_dir() && paritydb_path.exists() {
teyrchains_db::open_creating_paritydb(
paritydb_path.parent().ok_or(Error::DatabasePathRequired)?.into(),
teyrchains_db::CacheSizes::default(),
)?
} else {
teyrchains_db::open_creating_rocksdb(
rocksdb_path.clone(),
teyrchains_db::CacheSizes::default(),
)?
}
},
DatabaseSource::Custom { .. } => {
unimplemented!("No pezkuwi subsystem db for custom source.");
},
};
Ok(teyrchains_db)
}
/// Is this node running as in-process node for a teyrchain node?
#[cfg(feature = "full-node")]
#[derive(Clone)]
pub enum IsTeyrchainNode {
/// This node is running as in-process node for a teyrchain collator.
Collator(CollatorPair),
/// This node is running as in-process node for a teyrchain full node.
FullNode,
/// This node is not running as in-process node for a teyrchain node, aka a normal relay chain
/// node.
No,
}
#[cfg(feature = "full-node")]
impl std::fmt::Debug for IsTeyrchainNode {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
use sp_core::Pair;
match self {
IsTeyrchainNode::Collator(pair) => write!(fmt, "Collator({})", pair.public()),
IsTeyrchainNode::FullNode => write!(fmt, "FullNode"),
IsTeyrchainNode::No => write!(fmt, "No"),
}
}
}
#[cfg(feature = "full-node")]
impl IsTeyrchainNode {
/// Is this running alongside a collator?
fn is_collator(&self) -> bool {
matches!(self, Self::Collator(_))
}
/// Is this running alongside a full node?
fn is_full_node(&self) -> bool {
matches!(self, Self::FullNode)
}
/// Is this node running alongside a relay chain node?
fn is_running_alongside_teyrchain_node(&self) -> bool {
self.is_collator() || self.is_full_node()
}
}
#[cfg(feature = "full-node")]
macro_rules! chain_ops {
($config:expr, $telemetry_worker_handle:expr) => {{
let telemetry_worker_handle = $telemetry_worker_handle;
let mut config = $config;
let basics = new_partial_basics(config, telemetry_worker_handle)?;
use ::sc_consensus::LongestChain;
// use the longest chain selection, since there is no overseer available
let chain_selection = LongestChain::new(basics.backend.clone());
let sc_service::PartialComponents { client, backend, import_queue, task_manager, .. } =
new_partial::<LongestChain<_, Block>>(&mut config, basics, chain_selection)?;
Ok((client, backend, import_queue, task_manager))
}};
}
/// Builds a new object suitable for chain operations.
#[cfg(feature = "full-node")]
pub fn new_chain_ops(
config: &mut Configuration,
) -> Result<(Arc<FullClient>, Arc<FullBackend>, sc_consensus::BasicQueue<Block>, TaskManager), Error>
{
config.keystore = sc_service::config::KeystoreConfig::InMemory;
if config.chain_spec.is_pezkuwichain() || config.chain_spec.is_versi() {
chain_ops!(config, None)
} else if config.chain_spec.is_kusama() {
chain_ops!(config, None)
} else if config.chain_spec.is_zagros() {
return chain_ops!(config, None);
} else {
chain_ops!(config, None)
}
}
/// Build a full node.
///
/// The actual "flavor", aka if it will use `Pezkuwi`, `Pezkuwichain` or `Kusama` is determined
/// based on [`IdentifyVariant`] using the chain spec.
#[cfg(feature = "full-node")]
pub fn build_full<OverseerGenerator: OverseerGen>(
config: Configuration,
mut params: NewFullParams<OverseerGenerator>,
) -> Result<NewFull, Error> {
let is_pezkuwi = config.chain_spec.is_pezkuwi();
params.overseer_message_channel_capacity_override =
params.overseer_message_channel_capacity_override.map(move |capacity| {
if is_pezkuwi {
gum::warn!("Channel capacity should _never_ be tampered with on pezkuwi!");
}
capacity
});
match config.network.network_backend {
sc_network::config::NetworkBackendType::Libp2p =>
new_full::<_, sc_network::NetworkWorker<Block, Hash>>(config, params),
sc_network::config::NetworkBackendType::Litep2p =>
new_full::<_, sc_network::Litep2pNetworkBackend>(config, params),
}
}
/// Reverts the node state down to at most the last finalized block.
///
/// In particular this reverts:
/// - `ApprovalVotingSubsystem` data in the teyrchains-db;
/// - `ChainSelectionSubsystem` data in the teyrchains-db;
/// - Low level Babe and Grandpa consensus data.
#[cfg(feature = "full-node")]
pub fn revert_backend(
client: Arc<FullClient>,
backend: Arc<FullBackend>,
blocks: BlockNumber,
config: Configuration,
task_handle: SpawnTaskHandle,
) -> Result<(), Error> {
let best_number = client.info().best_number;
let finalized = client.info().finalized_number;
let revertible = blocks.min(best_number - finalized);
if revertible == 0 {
return Ok(());
}
let number = best_number - revertible;
let hash = client.block_hash_from_id(&BlockId::Number(number))?.ok_or(
sp_blockchain::Error::Backend(format!(
"Unexpected hash lookup failure for block number: {}",
number
)),
)?;
let teyrchains_db = open_database(&config.database)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))?;
revert_approval_voting(teyrchains_db.clone(), hash, task_handle)?;
revert_chain_selection(teyrchains_db, hash)?;
// Revert Substrate consensus related components
sc_consensus_babe::revert(client.clone(), backend, blocks)?;
sc_consensus_grandpa::revert(client, blocks)?;
Ok(())
}
fn revert_chain_selection(db: Arc<dyn Database>, hash: Hash) -> sp_blockchain::Result<()> {
let config = chain_selection_subsystem::Config {
col_data: teyrchains_db::REAL_COLUMNS.col_chain_selection_data,
stagnant_check_interval: chain_selection_subsystem::StagnantCheckInterval::never(),
stagnant_check_mode: chain_selection_subsystem::StagnantCheckMode::PruneOnly,
};
let chain_selection = chain_selection_subsystem::ChainSelectionSubsystem::new(config, db);
chain_selection
.revert_to(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))
}
fn revert_approval_voting(
db: Arc<dyn Database>,
hash: Hash,
task_handle: SpawnTaskHandle,
) -> sp_blockchain::Result<()> {
let config = approval_voting_subsystem::Config {
col_approval_data: teyrchains_db::REAL_COLUMNS.col_approval_data,
slot_duration_millis: Default::default(),
};
let approval_voting = approval_voting_subsystem::ApprovalVotingSubsystem::with_config(
config,
db,
Arc::new(sc_keystore::LocalKeystore::in_memory()),
Box::new(sp_consensus::NoNetwork),
approval_voting_subsystem::Metrics::default(),
Arc::new(SpawnGlue(task_handle)),
);
approval_voting
.revert_to(hash)
.map_err(|err| sp_blockchain::Error::Backend(err.to_string()))
}
+572
View File
@@ -0,0 +1,572 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use super::{Error, IsTeyrchainNode, Registry};
use pezkuwi_node_subsystem_types::{ChainApiBackend, RuntimeApiSubsystemClient};
use pezkuwi_overseer::{DummySubsystem, InitializedOverseerBuilder, SubsystemError};
use sp_core::traits::SpawnNamed;
use pezkuwi_availability_distribution::IncomingRequestReceivers;
use pezkuwi_node_core_approval_voting::Config as ApprovalVotingConfig;
use pezkuwi_node_core_av_store::Config as AvailabilityConfig;
use pezkuwi_node_core_candidate_validation::Config as CandidateValidationConfig;
use pezkuwi_node_core_chain_selection::Config as ChainSelectionConfig;
use pezkuwi_node_core_dispute_coordinator::Config as DisputeCoordinatorConfig;
use pezkuwi_node_network_protocol::{
peer_set::{PeerSet, PeerSetProtocolNames},
request_response::{
v1 as request_v1, v2 as request_v2, IncomingRequestReceiver, ReqProtocolNames,
},
};
#[cfg(any(feature = "malus", test))]
pub use pezkuwi_overseer::{dummy::dummy_overseer_builder, HeadSupportsTeyrchains};
use pezkuwi_overseer::{
metrics::Metrics as OverseerMetrics, MetricsTrait, Overseer, OverseerConnector, OverseerHandle,
SpawnGlue,
};
use parking_lot::Mutex;
use sc_authority_discovery::Service as AuthorityDiscoveryService;
use sc_client_api::AuxStore;
use sc_keystore::LocalKeystore;
use sc_network::{NetworkStateInfo, NotificationService};
use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Duration,
};
pub use pezkuwi_approval_distribution::ApprovalDistribution as ApprovalDistributionSubsystem;
pub use pezkuwi_availability_bitfield_distribution::BitfieldDistribution as BitfieldDistributionSubsystem;
pub use pezkuwi_availability_distribution::AvailabilityDistributionSubsystem;
pub use pezkuwi_availability_recovery::AvailabilityRecoverySubsystem;
pub use pezkuwi_collator_protocol::{CollatorProtocolSubsystem, ProtocolSide};
pub use pezkuwi_dispute_distribution::DisputeDistributionSubsystem;
pub use pezkuwi_gossip_support::GossipSupport as GossipSupportSubsystem;
pub use pezkuwi_network_bridge::{
Metrics as NetworkBridgeMetrics, NetworkBridgeRx as NetworkBridgeRxSubsystem,
NetworkBridgeTx as NetworkBridgeTxSubsystem,
};
pub use pezkuwi_node_collation_generation::CollationGenerationSubsystem;
pub use pezkuwi_node_core_approval_voting::ApprovalVotingSubsystem;
pub use pezkuwi_node_core_approval_voting_parallel::{
ApprovalVotingParallelSubsystem, Metrics as ApprovalVotingParallelMetrics,
};
pub use pezkuwi_node_core_av_store::AvailabilityStoreSubsystem;
pub use pezkuwi_node_core_backing::CandidateBackingSubsystem;
pub use pezkuwi_node_core_bitfield_signing::BitfieldSigningSubsystem;
pub use pezkuwi_node_core_candidate_validation::CandidateValidationSubsystem;
pub use pezkuwi_node_core_chain_api::ChainApiSubsystem;
pub use pezkuwi_node_core_chain_selection::ChainSelectionSubsystem;
pub use pezkuwi_node_core_dispute_coordinator::DisputeCoordinatorSubsystem;
pub use pezkuwi_node_core_prospective_teyrchains::ProspectiveTeyrchainsSubsystem;
pub use pezkuwi_node_core_provisioner::ProvisionerSubsystem;
pub use pezkuwi_node_core_pvf_checker::PvfCheckerSubsystem;
pub use pezkuwi_node_core_runtime_api::RuntimeApiSubsystem;
pub use pezkuwi_statement_distribution::StatementDistributionSubsystem;
/// Arguments passed for overseer construction.
pub struct OverseerGenArgs<'a, Spawner, RuntimeClient>
where
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
/// Runtime client generic, providing the `ProvideRuntimeApi` trait besides others.
pub runtime_client: Arc<RuntimeClient>,
/// Underlying network service implementation.
pub network_service: Arc<dyn sc_network::service::traits::NetworkService>,
/// Underlying syncing service implementation.
pub sync_service: Arc<dyn sp_consensus::SyncOracle + Send + Sync>,
/// Underlying authority discovery service.
pub authority_discovery_service: AuthorityDiscoveryService,
/// Collations request receiver for network protocol v1.
pub collation_req_v1_receiver: IncomingRequestReceiver<request_v1::CollationFetchingRequest>,
/// Collations request receiver for network protocol v2.
pub collation_req_v2_receiver: IncomingRequestReceiver<request_v2::CollationFetchingRequest>,
/// Receiver for available data requests.
pub available_data_req_receiver:
IncomingRequestReceiver<request_v1::AvailableDataFetchingRequest>,
/// Prometheus registry, commonly used for production systems, less so for test.
pub registry: Option<&'a Registry>,
/// Task spawner to be used throughout the overseer and the APIs it provides.
pub spawner: Spawner,
/// Determines the behavior of the collator.
pub is_teyrchain_node: IsTeyrchainNode,
/// Overseer channel capacity override.
pub overseer_message_channel_capacity_override: Option<usize>,
/// Request-response protocol names source.
pub req_protocol_names: ReqProtocolNames,
/// `PeerSet` protocol names to protocols mapping.
pub peerset_protocol_names: PeerSetProtocolNames,
/// Notification services for validation/collation protocols.
pub notification_services: HashMap<PeerSet, Box<dyn NotificationService>>,
}
pub struct ExtendedOverseerGenArgs {
/// The keystore to use for i.e. validator keys.
pub keystore: Arc<LocalKeystore>,
/// The underlying key value store for the teyrchains.
pub teyrchains_db: Arc<dyn pezkuwi_node_subsystem_util::database::Database>,
/// Configuration for the candidate validation subsystem.
pub candidate_validation_config: Option<CandidateValidationConfig>,
/// Configuration for the availability store subsystem.
pub availability_config: AvailabilityConfig,
/// POV request receiver.
pub pov_req_receiver: IncomingRequestReceiver<request_v1::PoVFetchingRequest>,
/// Erasure chunk request v1 receiver.
pub chunk_req_v1_receiver: IncomingRequestReceiver<request_v1::ChunkFetchingRequest>,
/// Erasure chunk request v2 receiver.
pub chunk_req_v2_receiver: IncomingRequestReceiver<request_v2::ChunkFetchingRequest>,
/// Receiver for incoming candidate requests.
pub candidate_req_v2_receiver: IncomingRequestReceiver<request_v2::AttestedCandidateRequest>,
/// Configuration for the approval voting subsystem.
pub approval_voting_config: ApprovalVotingConfig,
/// Receiver for incoming disputes.
pub dispute_req_receiver: IncomingRequestReceiver<request_v1::DisputeRequest>,
/// Configuration for the dispute coordinator subsystem.
pub dispute_coordinator_config: DisputeCoordinatorConfig,
/// Configuration for the chain selection subsystem.
pub chain_selection_config: ChainSelectionConfig,
/// Optional availability recovery fetch chunks threshold. If PoV size size is lower
/// than the value put in here we always try to recovery availability from backers.
/// The presence of this parameter here is needed to have different values per chain.
pub fetch_chunks_threshold: Option<usize>,
/// Set of invulnerable AH collator `PeerId`s
pub invulnerable_ah_collators: HashSet<pezkuwi_node_network_protocol::PeerId>,
/// Override for `HOLD_OFF_DURATION` constant .
pub collator_protocol_hold_off: Option<Duration>,
}
/// Obtain a prepared validator `Overseer`, that is initialized with all default values.
pub fn validator_overseer_builder<Spawner, RuntimeClient>(
OverseerGenArgs {
runtime_client,
network_service,
sync_service,
authority_discovery_service,
collation_req_v1_receiver: _,
collation_req_v2_receiver: _,
available_data_req_receiver,
registry,
spawner,
is_teyrchain_node,
overseer_message_channel_capacity_override,
req_protocol_names,
peerset_protocol_names,
notification_services,
}: OverseerGenArgs<Spawner, RuntimeClient>,
ExtendedOverseerGenArgs {
keystore,
teyrchains_db,
candidate_validation_config,
availability_config,
pov_req_receiver,
chunk_req_v1_receiver,
chunk_req_v2_receiver,
candidate_req_v2_receiver,
approval_voting_config,
dispute_req_receiver,
dispute_coordinator_config,
chain_selection_config,
fetch_chunks_threshold,
invulnerable_ah_collators,
collator_protocol_hold_off,
}: ExtendedOverseerGenArgs,
) -> Result<
InitializedOverseerBuilder<
SpawnGlue<Spawner>,
Arc<RuntimeClient>,
CandidateValidationSubsystem,
PvfCheckerSubsystem,
CandidateBackingSubsystem,
StatementDistributionSubsystem,
AvailabilityDistributionSubsystem,
AvailabilityRecoverySubsystem,
BitfieldSigningSubsystem,
BitfieldDistributionSubsystem,
ProvisionerSubsystem,
RuntimeApiSubsystem<RuntimeClient>,
AvailabilityStoreSubsystem,
NetworkBridgeRxSubsystem<
Arc<dyn sc_network::service::traits::NetworkService>,
AuthorityDiscoveryService,
>,
NetworkBridgeTxSubsystem<
Arc<dyn sc_network::service::traits::NetworkService>,
AuthorityDiscoveryService,
>,
ChainApiSubsystem<RuntimeClient>,
DummySubsystem,
CollatorProtocolSubsystem,
DummySubsystem,
DummySubsystem,
ApprovalVotingParallelSubsystem,
GossipSupportSubsystem<AuthorityDiscoveryService>,
DisputeCoordinatorSubsystem,
DisputeDistributionSubsystem<AuthorityDiscoveryService>,
ChainSelectionSubsystem,
ProspectiveTeyrchainsSubsystem,
>,
Error,
>
where
RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
use pezkuwi_node_subsystem_util::metrics::Metrics;
let metrics = <OverseerMetrics as MetricsTrait>::register(registry)?;
let notification_sinks = Arc::new(Mutex::new(HashMap::new()));
let spawner = SpawnGlue(spawner);
let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?;
let approval_voting_parallel_metrics: ApprovalVotingParallelMetrics =
Metrics::register(registry)?;
let builder = Overseer::builder()
.network_bridge_tx(NetworkBridgeTxSubsystem::new(
network_service.clone(),
authority_discovery_service.clone(),
network_bridge_metrics.clone(),
req_protocol_names.clone(),
peerset_protocol_names.clone(),
notification_sinks.clone(),
))
.network_bridge_rx(NetworkBridgeRxSubsystem::new(
network_service.clone(),
authority_discovery_service.clone(),
Box::new(sync_service.clone()),
network_bridge_metrics,
peerset_protocol_names,
notification_services,
notification_sinks,
))
.availability_distribution(AvailabilityDistributionSubsystem::new(
keystore.clone(),
IncomingRequestReceivers {
pov_req_receiver,
chunk_req_v1_receiver,
chunk_req_v2_receiver,
},
req_protocol_names.clone(),
Metrics::register(registry)?,
))
.availability_recovery(AvailabilityRecoverySubsystem::for_validator(
fetch_chunks_threshold,
available_data_req_receiver,
&req_protocol_names,
Metrics::register(registry)?,
))
.availability_store(AvailabilityStoreSubsystem::new(
teyrchains_db.clone(),
availability_config,
Box::new(sync_service.clone()),
Metrics::register(registry)?,
))
.bitfield_distribution(BitfieldDistributionSubsystem::new(Metrics::register(registry)?))
.bitfield_signing(BitfieldSigningSubsystem::new(
keystore.clone(),
Metrics::register(registry)?,
))
.candidate_backing(CandidateBackingSubsystem::new(
keystore.clone(),
Metrics::register(registry)?,
))
.candidate_validation(CandidateValidationSubsystem::with_config(
candidate_validation_config,
keystore.clone(),
Metrics::register(registry)?, // candidate-validation metrics
Metrics::register(registry)?, // validation host metrics
))
.pvf_checker(PvfCheckerSubsystem::new(keystore.clone(), Metrics::register(registry)?))
.chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?))
.collation_generation(DummySubsystem)
.collator_protocol({
let side = match is_teyrchain_node {
IsTeyrchainNode::Collator(_) | IsTeyrchainNode::FullNode =>
return Err(Error::Overseer(SubsystemError::Context(
"build validator overseer for teyrchain node".to_owned(),
))),
IsTeyrchainNode::No => ProtocolSide::Validator {
keystore: keystore.clone(),
eviction_policy: Default::default(),
metrics: Metrics::register(registry)?,
invulnerables: invulnerable_ah_collators,
collator_protocol_hold_off,
},
};
CollatorProtocolSubsystem::new(side)
})
.provisioner(ProvisionerSubsystem::new(Metrics::register(registry)?))
.runtime_api(RuntimeApiSubsystem::new(
runtime_client.clone(),
Metrics::register(registry)?,
spawner.clone(),
))
.statement_distribution(StatementDistributionSubsystem::new(
keystore.clone(),
candidate_req_v2_receiver,
Metrics::register(registry)?,
))
.approval_distribution(DummySubsystem)
.approval_voting(DummySubsystem)
.approval_voting_parallel(ApprovalVotingParallelSubsystem::with_config(
approval_voting_config,
teyrchains_db.clone(),
keystore.clone(),
Box::new(sync_service.clone()),
approval_voting_parallel_metrics,
spawner.clone(),
overseer_message_channel_capacity_override,
))
.gossip_support(GossipSupportSubsystem::new(
keystore.clone(),
authority_discovery_service.clone(),
Metrics::register(registry)?,
))
.dispute_coordinator(DisputeCoordinatorSubsystem::new(
teyrchains_db.clone(),
dispute_coordinator_config,
keystore.clone(),
Metrics::register(registry)?,
))
.dispute_distribution(DisputeDistributionSubsystem::new(
keystore.clone(),
dispute_req_receiver,
authority_discovery_service.clone(),
Metrics::register(registry)?,
))
.chain_selection(ChainSelectionSubsystem::new(chain_selection_config, teyrchains_db))
.prospective_teyrchains(ProspectiveTeyrchainsSubsystem::new(Metrics::register(registry)?))
.activation_external_listeners(Default::default())
.active_leaves(Default::default())
.supports_teyrchains(runtime_client)
.metrics(metrics)
.spawner(spawner);
let builder = if let Some(capacity) = overseer_message_channel_capacity_override {
builder.message_channel_capacity(capacity)
} else {
builder
};
Ok(builder)
}
/// Obtain a prepared collator `Overseer`, that is initialized with all default values.
pub fn collator_overseer_builder<Spawner, RuntimeClient>(
OverseerGenArgs {
runtime_client,
network_service,
sync_service,
authority_discovery_service,
collation_req_v1_receiver: _,
collation_req_v2_receiver,
available_data_req_receiver,
registry,
spawner,
is_teyrchain_node,
overseer_message_channel_capacity_override,
req_protocol_names,
peerset_protocol_names,
notification_services,
}: OverseerGenArgs<Spawner, RuntimeClient>,
) -> Result<
InitializedOverseerBuilder<
SpawnGlue<Spawner>,
Arc<RuntimeClient>,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
AvailabilityRecoverySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
RuntimeApiSubsystem<RuntimeClient>,
DummySubsystem,
NetworkBridgeRxSubsystem<
Arc<dyn sc_network::service::traits::NetworkService>,
AuthorityDiscoveryService,
>,
NetworkBridgeTxSubsystem<
Arc<dyn sc_network::service::traits::NetworkService>,
AuthorityDiscoveryService,
>,
ChainApiSubsystem<RuntimeClient>,
CollationGenerationSubsystem,
CollatorProtocolSubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
DummySubsystem,
>,
Error,
>
where
Spawner: 'static + SpawnNamed + Clone + Unpin,
RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static,
{
use pezkuwi_node_subsystem_util::metrics::Metrics;
let notification_sinks = Arc::new(Mutex::new(HashMap::new()));
let spawner = SpawnGlue(spawner);
let network_bridge_metrics: NetworkBridgeMetrics = Metrics::register(registry)?;
let builder = Overseer::builder()
.network_bridge_tx(NetworkBridgeTxSubsystem::new(
network_service.clone(),
authority_discovery_service.clone(),
network_bridge_metrics.clone(),
req_protocol_names.clone(),
peerset_protocol_names.clone(),
notification_sinks.clone(),
))
.network_bridge_rx(NetworkBridgeRxSubsystem::new(
network_service.clone(),
authority_discovery_service.clone(),
Box::new(sync_service.clone()),
network_bridge_metrics,
peerset_protocol_names,
notification_services,
notification_sinks,
))
.availability_distribution(DummySubsystem)
.availability_recovery(AvailabilityRecoverySubsystem::for_collator(
None,
available_data_req_receiver,
&req_protocol_names,
Metrics::register(registry)?,
))
.availability_store(DummySubsystem)
.bitfield_distribution(DummySubsystem)
.bitfield_signing(DummySubsystem)
.candidate_backing(DummySubsystem)
.candidate_validation(DummySubsystem)
.pvf_checker(DummySubsystem)
.chain_api(ChainApiSubsystem::new(runtime_client.clone(), Metrics::register(registry)?))
.collation_generation(CollationGenerationSubsystem::new(Metrics::register(registry)?))
.collator_protocol({
let side = match is_teyrchain_node {
IsTeyrchainNode::No =>
return Err(Error::Overseer(SubsystemError::Context(
"build teyrchain node overseer for validator".to_owned(),
))),
IsTeyrchainNode::Collator(collator_pair) => ProtocolSide::Collator {
peer_id: network_service.local_peer_id(),
collator_pair,
request_receiver_v2: collation_req_v2_receiver,
metrics: Metrics::register(registry)?,
},
IsTeyrchainNode::FullNode => ProtocolSide::None,
};
CollatorProtocolSubsystem::new(side)
})
.provisioner(DummySubsystem)
.runtime_api(RuntimeApiSubsystem::new(
runtime_client.clone(),
Metrics::register(registry)?,
spawner.clone(),
))
.statement_distribution(DummySubsystem)
.approval_distribution(DummySubsystem)
.approval_voting(DummySubsystem)
.approval_voting_parallel(DummySubsystem)
.gossip_support(DummySubsystem)
.dispute_coordinator(DummySubsystem)
.dispute_distribution(DummySubsystem)
.chain_selection(DummySubsystem)
.prospective_teyrchains(DummySubsystem)
.activation_external_listeners(Default::default())
.active_leaves(Default::default())
.supports_teyrchains(runtime_client)
.metrics(Metrics::register(registry)?)
.spawner(spawner);
let builder = if let Some(capacity) = overseer_message_channel_capacity_override {
builder.message_channel_capacity(capacity)
} else {
builder
};
Ok(builder)
}
/// Trait for the `fn` generating the overseer.
pub trait OverseerGen {
/// Overwrite the full generation of the overseer, including the subsystems.
fn generate<Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<Spawner, RuntimeClient>,
ext_args: Option<ExtendedOverseerGenArgs>,
) -> Result<(Overseer<SpawnGlue<Spawner>, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static,
Spawner: 'static + SpawnNamed + Clone + Unpin;
// It would be nice to make `create_subsystems` part of this trait,
// but the amount of generic arguments that would be required as
// as consequence make this rather annoying to implement and use.
}
/// The regular set of subsystems.
pub struct ValidatorOverseerGen;
impl OverseerGen for ValidatorOverseerGen {
fn generate<Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<Spawner, RuntimeClient>,
ext_args: Option<ExtendedOverseerGenArgs>,
) -> Result<(Overseer<SpawnGlue<Spawner>, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
let ext_args = ext_args.ok_or(Error::Overseer(SubsystemError::Context(
"create validator overseer as mandatory extended arguments were not provided"
.to_owned(),
)))?;
validator_overseer_builder(args, ext_args)?
.build_with_connector(connector)
.map_err(|e| e.into())
}
}
/// Reduced set of subsystems, to use in collator and collator's full node.
pub struct CollatorOverseerGen;
impl OverseerGen for CollatorOverseerGen {
fn generate<Spawner, RuntimeClient>(
&self,
connector: OverseerConnector,
args: OverseerGenArgs<Spawner, RuntimeClient>,
_ext_args: Option<ExtendedOverseerGenArgs>,
) -> Result<(Overseer<SpawnGlue<Spawner>, Arc<RuntimeClient>>, OverseerHandle), Error>
where
RuntimeClient: RuntimeApiSubsystemClient + ChainApiBackend + AuxStore + 'static,
Spawner: 'static + SpawnNamed + Clone + Unpin,
{
collator_overseer_builder(args)?
.build_with_connector(connector)
.map_err(|e| e.into())
}
}
@@ -0,0 +1,618 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! A [`SelectChain`] implementation designed for relay chains.
//!
//! This uses information about teyrchains to inform GRANDPA and BABE
//! about blocks which are safe to build on and blocks which are safe to
//! finalize.
//!
//! To learn more about chain-selection rules for Relay Chains, please see the
//! documentation on [chain-selection][chain-selection-guide]
//! in the implementers' guide.
//!
//! This is mostly a wrapper around a subsystem which implements the
//! chain-selection rule, which leaves the code to be very simple.
//!
//! However, this does apply the further finality constraints to the best
//! leaf returned from the chain selection subsystem by calling into other
//! subsystems which yield information about approvals and disputes.
//!
//! [chain-selection-guide]: https://docs.pezkuwichain.io/sdk/book/protocol-chain-selection.html
#![cfg(feature = "full-node")]
use super::{HeaderProvider, HeaderProviderProvider};
use futures::channel::oneshot;
use pezkuwi_node_primitives::MAX_FINALITY_LAG as PRIMITIVES_MAX_FINALITY_LAG;
use pezkuwi_node_subsystem::messages::{
ApprovalVotingParallelMessage, ChainSelectionMessage, DisputeCoordinatorMessage,
HighestApprovedAncestorBlock,
};
use pezkuwi_node_subsystem_util::metrics::{self, prometheus};
use pezkuwi_overseer::{AllMessages, Handle, PriorityLevel};
use pezkuwi_primitives::{Block as PezkuwiBlock, BlockNumber, Hash, Header as PezkuwiHeader};
use sp_consensus::{Error as ConsensusError, SelectChain};
use std::sync::Arc;
pub use sc_service::SpawnTaskHandle;
/// The maximum amount of unfinalized blocks we are willing to allow due to approval checking
/// or disputes.
///
/// This is a safety net that should be removed at some point in the future.
// In sync with `MAX_HEADS_LOOK_BACK` in `approval-voting`
// and `MAX_BATCH_SCRAPE_ANCESTORS` in `dispute-coordinator`.
const MAX_FINALITY_LAG: pezkuwi_primitives::BlockNumber = PRIMITIVES_MAX_FINALITY_LAG;
const LOG_TARGET: &str = "teyrchain::chain-selection";
/// Prometheus metrics for chain-selection.
#[derive(Debug, Default, Clone)]
pub struct Metrics(Option<MetricsInner>);
#[derive(Debug, Clone)]
struct MetricsInner {
approval_checking_finality_lag: prometheus::Gauge<prometheus::U64>,
disputes_finality_lag: prometheus::Gauge<prometheus::U64>,
}
impl metrics::Metrics for Metrics {
fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
let metrics = MetricsInner {
approval_checking_finality_lag: prometheus::register(
prometheus::Gauge::with_opts(
prometheus::Opts::new(
"pezkuwi_teyrchain_approval_checking_finality_lag",
"How far behind the head of the chain the Approval Checking protocol wants to vote",
)
)?,
registry,
)?,
disputes_finality_lag: prometheus::register(
prometheus::Gauge::with_opts(
prometheus::Opts::new(
"pezkuwi_teyrchain_disputes_finality_lag",
"How far behind the head of the chain the Disputes protocol wants to vote",
)
)?,
registry,
)?,
};
Ok(Metrics(Some(metrics)))
}
}
impl Metrics {
fn note_approval_checking_finality_lag(&self, lag: BlockNumber) {
if let Some(ref metrics) = self.0 {
metrics.approval_checking_finality_lag.set(lag as _);
}
}
fn note_disputes_finality_lag(&self, lag: BlockNumber) {
if let Some(ref metrics) = self.0 {
metrics.disputes_finality_lag.set(lag as _);
}
}
}
/// Determines whether the chain is a relay chain
/// and hence has to take approval votes and disputes
/// into account.
enum IsDisputesAwareWithOverseer<B: sc_client_api::Backend<PezkuwiBlock>> {
Yes(SelectRelayChainInner<B, Handle>),
No,
}
impl<B> Clone for IsDisputesAwareWithOverseer<B>
where
B: sc_client_api::Backend<PezkuwiBlock>,
SelectRelayChainInner<B, Handle>: Clone,
{
fn clone(&self) -> Self {
match self {
Self::Yes(ref inner) => Self::Yes(inner.clone()),
Self::No => Self::No,
}
}
}
/// A chain-selection implementation which provides safety for relay chains.
pub struct SelectRelayChain<B: sc_client_api::Backend<PezkuwiBlock>> {
longest_chain: sc_consensus::LongestChain<B, PezkuwiBlock>,
selection: IsDisputesAwareWithOverseer<B>,
}
impl<B> Clone for SelectRelayChain<B>
where
B: sc_client_api::Backend<PezkuwiBlock>,
SelectRelayChainInner<B, Handle>: Clone,
{
fn clone(&self) -> Self {
Self { longest_chain: self.longest_chain.clone(), selection: self.selection.clone() }
}
}
impl<B> SelectRelayChain<B>
where
B: sc_client_api::Backend<PezkuwiBlock> + 'static,
{
/// Use the plain longest chain algorithm exclusively.
pub fn new_longest_chain(backend: Arc<B>) -> Self {
gum::debug!(target: LOG_TARGET, "Using {} chain selection algorithm", "longest");
Self {
longest_chain: sc_consensus::LongestChain::new(backend.clone()),
selection: IsDisputesAwareWithOverseer::No,
}
}
/// Create a new [`SelectRelayChain`] wrapping the given chain backend
/// and a handle to the overseer.
pub fn new_with_overseer(
backend: Arc<B>,
overseer: Handle,
metrics: Metrics,
spawn_handle: Option<SpawnTaskHandle>,
) -> Self {
gum::debug!(target: LOG_TARGET, "Using dispute aware relay-chain selection algorithm",);
SelectRelayChain {
longest_chain: sc_consensus::LongestChain::new(backend.clone()),
selection: IsDisputesAwareWithOverseer::Yes(SelectRelayChainInner::new(
backend,
overseer,
metrics,
spawn_handle,
)),
}
}
/// Allow access to the inner chain, for usage during the node setup.
pub fn as_longest_chain(&self) -> &sc_consensus::LongestChain<B, PezkuwiBlock> {
&self.longest_chain
}
}
#[async_trait::async_trait]
impl<B> SelectChain<PezkuwiBlock> for SelectRelayChain<B>
where
B: sc_client_api::Backend<PezkuwiBlock> + 'static,
{
async fn leaves(&self) -> Result<Vec<Hash>, ConsensusError> {
match self.selection {
IsDisputesAwareWithOverseer::Yes(ref selection) => selection.leaves().await,
IsDisputesAwareWithOverseer::No => self.longest_chain.leaves().await,
}
}
async fn best_chain(&self) -> Result<PezkuwiHeader, ConsensusError> {
match self.selection {
IsDisputesAwareWithOverseer::Yes(ref selection) => selection.best_chain().await,
IsDisputesAwareWithOverseer::No => self.longest_chain.best_chain().await,
}
}
async fn finality_target(
&self,
target_hash: Hash,
maybe_max_number: Option<BlockNumber>,
) -> Result<Hash, ConsensusError> {
if let IsDisputesAwareWithOverseer::Yes(ref selection) = self.selection {
selection
.finality_target_with_longest_chain(target_hash, maybe_max_number)
.await
} else {
self.longest_chain.finality_target(target_hash, maybe_max_number).await
}
}
}
/// A chain-selection implementation which provides safety for relay chains
/// but does not handle situations where the overseer is not yet connected.
pub struct SelectRelayChainInner<B, OH> {
backend: Arc<B>,
overseer: OH,
metrics: Metrics,
spawn_handle: Option<SpawnTaskHandle>,
}
impl<B, OH> SelectRelayChainInner<B, OH>
where
B: HeaderProviderProvider<PezkuwiBlock>,
OH: OverseerHandleT + OverseerHandleWithPriorityT,
{
/// Create a new [`SelectRelayChainInner`] wrapping the given chain backend
/// and a handle to the overseer.
pub fn new(
backend: Arc<B>,
overseer: OH,
metrics: Metrics,
spawn_handle: Option<SpawnTaskHandle>,
) -> Self {
SelectRelayChainInner { backend, overseer, metrics, spawn_handle }
}
fn block_header(&self, hash: Hash) -> Result<PezkuwiHeader, ConsensusError> {
match HeaderProvider::header(self.backend.header_provider(), hash) {
Ok(Some(header)) => Ok(header),
Ok(None) =>
Err(ConsensusError::ChainLookup(format!("Missing header with hash {:?}", hash,))),
Err(e) => Err(ConsensusError::ChainLookup(format!(
"Lookup failed for header with hash {:?}: {:?}",
hash, e,
))),
}
}
fn block_number(&self, hash: Hash) -> Result<BlockNumber, ConsensusError> {
match HeaderProvider::number(self.backend.header_provider(), hash) {
Ok(Some(number)) => Ok(number),
Ok(None) =>
Err(ConsensusError::ChainLookup(format!("Missing number with hash {:?}", hash,))),
Err(e) => Err(ConsensusError::ChainLookup(format!(
"Lookup failed for number with hash {:?}: {:?}",
hash, e,
))),
}
}
}
impl<B, OH> Clone for SelectRelayChainInner<B, OH>
where
B: HeaderProviderProvider<PezkuwiBlock> + Send + Sync,
OH: OverseerHandleT + OverseerHandleWithPriorityT,
{
fn clone(&self) -> Self {
SelectRelayChainInner {
backend: self.backend.clone(),
overseer: self.overseer.clone(),
metrics: self.metrics.clone(),
spawn_handle: self.spawn_handle.clone(),
}
}
}
#[derive(thiserror::Error, Debug)]
enum Error {
// Oneshot for requesting leaves from chain selection got canceled - check errors in that
// subsystem.
#[error("Request for leaves from chain selection got canceled")]
LeavesCanceled(oneshot::Canceled),
#[error("Request for leaves from chain selection got canceled")]
BestLeafContainingCanceled(oneshot::Canceled),
// Requesting recent disputes oneshot got canceled.
#[error("Request for determining the undisputed chain from DisputeCoordinator got canceled")]
DetermineUndisputedChainCanceled(oneshot::Canceled),
#[error("Request approved ancestor from approval voting got canceled")]
ApprovedAncestorCanceled(oneshot::Canceled),
/// Chain selection returned empty leaves.
#[error("ChainSelection returned no leaves")]
EmptyLeaves,
}
/// Decoupling trait for the overseer handle.
///
/// Required for testing purposes.
#[async_trait::async_trait]
pub trait OverseerHandleT: Clone + Send + Sync {
async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, origin: &'static str);
}
/// Trait for the overseer handle that allows sending messages with the specified priority level.
#[async_trait::async_trait]
pub trait OverseerHandleWithPriorityT: Clone + Send + Sync {
async fn send_msg_with_priority<M: Send + Into<AllMessages>>(
&mut self,
msg: M,
origin: &'static str,
priority: PriorityLevel,
);
}
#[async_trait::async_trait]
impl OverseerHandleT for Handle {
async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, origin: &'static str) {
Handle::send_msg(self, msg, origin).await
}
}
#[async_trait::async_trait]
impl OverseerHandleWithPriorityT for Handle {
async fn send_msg_with_priority<M: Send + Into<AllMessages>>(
&mut self,
msg: M,
origin: &'static str,
priority: PriorityLevel,
) {
Handle::send_msg_with_priority(self, msg, origin, priority).await
}
}
impl<B, OH> SelectRelayChainInner<B, OH>
where
B: HeaderProviderProvider<PezkuwiBlock>,
OH: OverseerHandleT + OverseerHandleWithPriorityT + 'static,
{
/// Get all leaves of the chain, i.e. block hashes that are suitable to
/// build upon and have no suitable children.
async fn leaves(&self) -> Result<Vec<Hash>, ConsensusError> {
let (tx, rx) = oneshot::channel();
self.overseer
.clone()
.send_msg(ChainSelectionMessage::Leaves(tx), std::any::type_name::<Self>())
.await;
let leaves = rx
.await
.map_err(Error::LeavesCanceled)
.map_err(|e| ConsensusError::Other(Box::new(e)))?;
gum::trace!(target: LOG_TARGET, ?leaves, "Chain selection leaves");
Ok(leaves)
}
/// Among all leaves, pick the one which is the best chain to build upon.
async fn best_chain(&self) -> Result<PezkuwiHeader, ConsensusError> {
// The Chain Selection subsystem is supposed to treat the finalized
// block as the best leaf in the case that there are no viable
// leaves, so this should not happen in practice.
let best_leaf = *self
.leaves()
.await?
.first()
.ok_or_else(|| ConsensusError::Other(Box::new(Error::EmptyLeaves)))?;
gum::trace!(target: LOG_TARGET, ?best_leaf, "Best chain");
self.block_header(best_leaf)
}
/// Get the best descendant of `target_hash` that we should attempt to
/// finalize next, if any. It is valid to return the `target_hash` if
/// no better block exists.
///
/// This will search all leaves to find the best one containing the
/// given target hash, and then constrain to the given block number.
///
/// It will also constrain the chain to only chains which are fully
/// approved, and chains which contain no disputes.
pub(crate) async fn finality_target_with_longest_chain(
&self,
target_hash: Hash,
maybe_max_number: Option<BlockNumber>,
) -> Result<Hash, ConsensusError> {
let mut overseer = self.overseer.clone();
let subchain_head = {
let (tx, rx) = oneshot::channel();
overseer
.send_msg(
ChainSelectionMessage::BestLeafContaining(target_hash, tx),
std::any::type_name::<Self>(),
)
.await;
let best = rx
.await
.map_err(Error::BestLeafContainingCanceled)
.map_err(|e| ConsensusError::Other(Box::new(e)))?;
gum::trace!(target: LOG_TARGET, ?best, "Best leaf containing");
match best {
// No viable leaves containing the block.
None => return Ok(target_hash),
Some(best) => best,
}
};
let target_number = self.block_number(target_hash)?;
// 1. Constrain the leaf according to `maybe_max_number`.
let subchain_head = match maybe_max_number {
None => subchain_head,
Some(max) => {
if max <= target_number {
if max < target_number {
gum::warn!(
LOG_TARGET,
max_number = max,
target_number,
"`finality_target` max number is less than target number",
);
}
return Ok(target_hash);
}
// find the current number.
let subchain_header = self.block_header(subchain_head)?;
if subchain_header.number <= max {
gum::trace!(target: LOG_TARGET, ?subchain_head, "Constrained sub-chain head",);
subchain_head
} else {
let (ancestor_hash, _) =
crate::grandpa_support::walk_backwards_to_target_block(
self.backend.header_provider(),
max,
&subchain_header,
)
.map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?;
gum::trace!(
target: LOG_TARGET,
?ancestor_hash,
"Grandpa walk backwards sub-chain head"
);
ancestor_hash
}
},
};
let initial_leaf = subchain_head;
let initial_leaf_number = self.block_number(initial_leaf)?;
// 2. Constrain according to `ApprovedAncestor`.
let (subchain_head, subchain_number, subchain_block_descriptions) = {
let (tx, rx) = oneshot::channel();
overseer
.send_msg_with_priority(
ApprovalVotingParallelMessage::ApprovedAncestor(
subchain_head,
target_number,
tx,
),
std::any::type_name::<Self>(),
PriorityLevel::High,
)
.await;
match rx
.await
.map_err(Error::ApprovedAncestorCanceled)
.map_err(|e| ConsensusError::Other(Box::new(e)))?
{
// No approved ancestors means target hash is maximal vote.
None => (target_hash, target_number, Vec::new()),
Some(HighestApprovedAncestorBlock { number, hash, descriptions }) =>
(hash, number, descriptions),
}
};
gum::trace!(target: LOG_TARGET, ?subchain_head, "Ancestor approval restriction applied",);
let lag = initial_leaf_number.saturating_sub(subchain_number);
self.metrics.note_approval_checking_finality_lag(lag);
// Messages sent to `approval-distribution` are known to have high `ToF`, we need to spawn a
// task for sending the message to not block here and delay finality.
if let Some(spawn_handle) = &self.spawn_handle {
let mut overseer_handle = self.overseer.clone();
let lag_update_task = async move {
overseer_handle
.send_msg_with_priority(
ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag),
std::any::type_name::<Self>(),
PriorityLevel::High,
)
.await;
};
spawn_handle.spawn(
"approval-checking-lag-update",
Some("relay-chain-selection"),
Box::pin(lag_update_task),
);
}
let (lag, subchain_head) = {
// Prevent sending flawed data to the dispute-coordinator.
if Some(subchain_block_descriptions.len() as _) !=
subchain_number.checked_sub(target_number)
{
gum::error!(
LOG_TARGET,
present_block_descriptions = subchain_block_descriptions.len(),
target_number,
subchain_number,
"Mismatch of anticipated block descriptions and block number difference.",
);
return Ok(target_hash);
}
// 3. Constrain according to disputes:
let (tx, rx) = oneshot::channel();
overseer
.send_msg_with_priority(
DisputeCoordinatorMessage::DetermineUndisputedChain {
base: (target_number, target_hash),
block_descriptions: subchain_block_descriptions,
tx,
},
std::any::type_name::<Self>(),
PriorityLevel::High,
)
.await;
// Try to fetch response from `dispute-coordinator`. If an error occurs we just log it
// and return `target_hash` as maximal vote. It is safer to contain this error here
// and not push it up the stack to cause additional issues in GRANDPA/BABE.
let (lag, subchain_head) =
match rx.await.map_err(Error::DetermineUndisputedChainCanceled) {
// If request succeeded we will receive (block number, block hash).
Ok((subchain_number, subchain_head)) => {
// The total lag accounting for disputes.
let lag_disputes = initial_leaf_number.saturating_sub(subchain_number);
self.metrics.note_disputes_finality_lag(lag_disputes);
(lag_disputes, subchain_head)
},
Err(e) => {
gum::error!(
target: LOG_TARGET,
error = ?e,
"Call to `DetermineUndisputedChain` failed",
);
// We need to return a sane finality target. But, we are unable to ensure we
// are not finalizing something that is being disputed or has been concluded
// as invalid. We will be conservative here and not vote for finality above
// the ancestor passed in.
return Ok(target_hash);
},
};
(lag, subchain_head)
};
gum::trace!(
target: LOG_TARGET,
?subchain_head,
"Disputed blocks in ancestry restriction applied",
);
// 4. Apply the maximum safeguard to the finality lag.
if lag > MAX_FINALITY_LAG {
// We need to constrain our vote as a safety net to
// ensure the network continues to finalize.
let safe_target = initial_leaf_number - MAX_FINALITY_LAG;
if safe_target <= target_number {
gum::warn!(target: LOG_TARGET, ?target_hash, "Safeguard enforced finalization");
// Minimal vote needs to be on the target number.
Ok(target_hash)
} else {
// Otherwise we're looking for a descendant.
let initial_leaf_header = self.block_header(initial_leaf)?;
let (forced_target, _) = crate::grandpa_support::walk_backwards_to_target_block(
self.backend.header_provider(),
safe_target,
&initial_leaf_header,
)
.map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?;
gum::warn!(
target: LOG_TARGET,
?forced_target,
"Safeguard enforced finalization of child"
);
Ok(forced_target)
}
} else {
Ok(subchain_head)
}
}
}
+785
View File
@@ -0,0 +1,785 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
use super::{relay_chain_selection::*, *};
use futures::channel::oneshot::Receiver;
use pezkuwi_node_primitives::approval::v2::VrfSignature;
use pezkuwi_node_subsystem::messages::{AllMessages, BlockDescription};
use pezkuwi_node_subsystem_util::TimeoutExt;
use pezkuwi_overseer::{HighPriority, PriorityLevel};
use pezkuwi_test_client::Sr25519Keyring;
use sp_consensus_babe::{
digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest},
VrfTranscript,
};
use sp_core::{crypto::VrfSecret, testing::TaskExecutor};
use sp_runtime::{testing::*, DigestItem};
use std::{
collections::{BTreeMap, HashMap, HashSet},
iter::IntoIterator,
};
use assert_matches::assert_matches;
use std::{sync::Arc, time::Duration};
use futures::{channel::oneshot, prelude::*};
use pezkuwi_node_subsystem::messages::{
ApprovalVotingParallelMessage, ChainSelectionMessage, DisputeCoordinatorMessage,
HighestApprovedAncestorBlock,
};
use pezkuwi_primitives::{Block, BlockNumber, Hash, Header};
use pezkuwi_node_subsystem_test_helpers::TestSubsystemSender;
use pezkuwi_overseer::{SubsystemContext, SubsystemSender};
type VirtualOverseer =
pezkuwi_node_subsystem_test_helpers::TestSubsystemContextHandle<ApprovalVotingParallelMessage>;
#[async_trait::async_trait]
impl OverseerHandleT for TestSubsystemSender {
async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, _origin: &'static str) {
TestSubsystemSender::send_message(self, msg.into()).await;
}
}
#[async_trait::async_trait]
impl OverseerHandleWithPriorityT for TestSubsystemSender {
async fn send_msg_with_priority<M: Send + Into<AllMessages>>(
&mut self,
msg: M,
_origin: &'static str,
priority: PriorityLevel,
) {
match priority {
PriorityLevel::High => {
TestSubsystemSender::send_message_with_priority::<HighPriority>(self, msg.into())
.await;
},
PriorityLevel::Normal => {
TestSubsystemSender::send_message(self, msg.into()).await;
},
}
}
}
struct TestHarness {
virtual_overseer: VirtualOverseer,
case_vars: CaseVars,
/// The result of `fn finality_target` will be injected into the
/// harness scope via this channel.
finality_target_rx: Receiver<Option<Hash>>,
}
fn test_harness<T: Future<Output = VirtualOverseer>>(
case_vars: CaseVars,
test: impl FnOnce(TestHarness) -> T,
) {
sp_tracing::init_for_tests();
let pool = TaskExecutor::new();
let (mut context, virtual_overseer) =
pezkuwi_node_subsystem_test_helpers::make_subsystem_context(pool);
let (finality_target_tx, finality_target_rx) = oneshot::channel::<Option<Hash>>();
let select_relay_chain = SelectRelayChainInner::<TestChainStorage, TestSubsystemSender>::new(
Arc::new(case_vars.chain.clone()),
context.sender().clone(),
Default::default(),
None,
);
let target_hash = case_vars.target_block;
let selection_process = async move {
let best = select_relay_chain
.finality_target_with_longest_chain(target_hash, None)
.await
.unwrap();
finality_target_tx.send(Some(best)).unwrap();
()
};
let test_fut = test(TestHarness { virtual_overseer, case_vars, finality_target_rx });
futures::pin_mut!(test_fut);
futures::pin_mut!(selection_process);
futures::executor::block_on(future::join(
async move {
let _overseer = test_fut.await;
},
selection_process,
));
}
async fn overseer_recv(overseer: &mut VirtualOverseer) -> AllMessages {
let msg = overseer_recv_with_timeout(overseer, TIMEOUT)
.await
.expect(&format!("{:?} is enough to receive messages.", TIMEOUT));
gum::trace!("Received message:\n{:?}", &msg);
msg
}
async fn overseer_recv_with_timeout(
overseer: &mut VirtualOverseer,
timeout: Duration,
) -> Option<AllMessages> {
gum::trace!("Waiting for message...");
overseer.recv().timeout(timeout).await
}
const TIMEOUT: Duration = Duration::from_millis(2000);
// used for generating assignments where the validity of the VRF doesn't matter.
fn garbage_vrf_signature() -> VrfSignature {
let transcript = VrfTranscript::new(b"test-garbage", &[]);
Sr25519Keyring::Alice.pair().vrf_sign(&transcript.into())
}
/// Representation of a local representation
/// to extract information for finalization target
/// extraction.
#[derive(Debug, Default, Clone)]
struct TestChainStorage {
blocks_by_hash: HashMap<Hash, Header>,
blocks_at_height: BTreeMap<u32, Vec<Hash>>,
disputed_blocks: HashSet<Hash>,
approved_blocks: HashSet<Hash>,
heads: HashSet<Hash>,
}
impl TestChainStorage {
/// Fill the [`HighestApprovedAncestor`] structure with mostly
/// correct data.
pub fn highest_approved_ancestors(
&self,
minimum_block_number: BlockNumber,
leaf: Hash,
) -> Option<HighestApprovedAncestorBlock> {
let mut descriptions = Vec::new();
let mut block_hash = leaf;
let mut highest_approved_ancestor = None;
while let Some(block) = self.blocks_by_hash.get(&block_hash) {
if minimum_block_number >= block.number {
break;
}
if !self.approved_blocks.contains(&block_hash) {
highest_approved_ancestor = None;
descriptions.clear();
} else {
if highest_approved_ancestor.is_none() {
highest_approved_ancestor = Some((block_hash, block.number));
}
descriptions.push(BlockDescription {
session: 1 as _, // dummy, not checked
block_hash,
candidates: vec![], // not relevant for any test cases
});
}
block_hash = *block.parent_hash();
}
highest_approved_ancestor.map(|(hash, number)| HighestApprovedAncestorBlock {
hash,
number,
descriptions: descriptions.into_iter().rev().collect(),
})
}
/// Traverse backwards from leave down to block number.
fn undisputed_chain(
&self,
base_blocknumber: BlockNumber,
highest_approved_block_hash: Hash,
) -> Option<Hash> {
if self.disputed_blocks.is_empty() {
return Some(highest_approved_block_hash);
}
let mut undisputed_chain = Some(highest_approved_block_hash);
let mut block_hash = highest_approved_block_hash;
while let Some(block) = self.blocks_by_hash.get(&block_hash) {
let next = block.parent_hash();
if self.disputed_blocks.contains(&block_hash) {
undisputed_chain = Some(*next);
}
if block.number() == &base_blocknumber {
break;
}
block_hash = *next;
}
undisputed_chain
}
}
impl HeaderProvider<Block> for TestChainStorage {
fn header(&self, hash: Hash) -> sp_blockchain::Result<Option<Header>> {
Ok(self.blocks_by_hash.get(&hash).cloned())
}
fn number(&self, hash: Hash) -> sp_blockchain::Result<Option<BlockNumber>> {
self.header(hash).map(|opt| opt.map(|h| h.number))
}
}
impl HeaderProviderProvider<Block> for TestChainStorage {
type Provider = Self;
fn header_provider(&self) -> &Self {
self
}
}
#[derive(Debug, Clone)]
struct ChainBuilder(pub TestChainStorage);
impl ChainBuilder {
const GENESIS_HASH: Hash = Hash::repeat_byte(0xff);
const GENESIS_PARENT_HASH: Hash = Hash::repeat_byte(0x00);
pub fn new() -> Self {
let mut builder = Self(TestChainStorage::default());
let _ = builder.add_block_inner(Self::GENESIS_HASH, Self::GENESIS_PARENT_HASH, 0);
builder
}
pub fn add_block(&mut self, hash: Hash, parent_hash: Hash, number: u32) -> &mut Self {
assert!(number != 0, "cannot add duplicate genesis block");
assert!(hash != Self::GENESIS_HASH, "cannot add block with genesis hash");
assert!(
parent_hash != Self::GENESIS_PARENT_HASH,
"cannot add block with genesis parent hash"
);
assert!(self.0.blocks_by_hash.len() < u8::MAX.into());
self.add_block_inner(hash, parent_hash, number)
}
fn add_block_inner(&mut self, hash: Hash, parent_hash: Hash, number: u32) -> &mut Self {
let header = ChainBuilder::make_header(parent_hash, number);
assert!(
self.0.blocks_by_hash.insert(hash, header).is_none(),
"block with hash {:?} already exists",
hash,
);
self.0.blocks_at_height.entry(number).or_insert_with(Vec::new).push(hash);
self
}
pub fn fast_forward_approved(
&mut self,
branch_tag: u8,
parent: Hash,
block_number: BlockNumber,
) -> Hash {
let block = self.fast_forward(branch_tag, parent, block_number);
let _ = self.0.approved_blocks.insert(block);
block
}
/// Add a relay chain block that contains a disputed teyrchain block.
/// For simplicity this is not modeled explicitly.
pub fn fast_forward_disputed(
&mut self,
branch_tag: u8,
parent: Hash,
block_number: BlockNumber,
) -> Hash {
let block = self.fast_forward_approved(branch_tag, parent, block_number);
let _ = self.0.disputed_blocks.insert(block);
block
}
pub fn fast_forward(
&mut self,
branch_tag: u8,
parent: Hash,
block_number: BlockNumber,
) -> Hash {
let hash = Hash::repeat_byte((block_number as u8 | branch_tag) as u8);
let _ = self.add_block(hash, parent, block_number);
hash
}
pub fn set_heads(&mut self, heads: impl IntoIterator<Item = Hash>) {
self.0.heads = heads.into_iter().collect();
}
pub fn init(self) -> TestChainStorage {
self.0
}
fn make_header(parent_hash: Hash, number: u32) -> Header {
let digest = {
let mut digest = Digest::default();
let vrf_signature = garbage_vrf_signature();
digest.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
SecondaryVRFPreDigest {
authority_index: 0,
slot: 1.into(), // slot, unused
vrf_signature,
},
)));
digest
};
Header {
digest,
extrinsics_root: Default::default(),
number,
state_root: Default::default(),
parent_hash,
}
}
}
/// Generalized sequence of the test, based on
/// the messages being sent out by the `fn finality_target`
/// Depends on a particular `target_hash`
/// that is passed to `finality_target` block number.
async fn test_skeleton(
chain: &TestChainStorage,
virtual_overseer: &mut VirtualOverseer,
target_block_hash: Hash,
best_chain_containing_block: Option<Hash>,
highest_approved_ancestor_block: Option<HighestApprovedAncestorBlock>,
undisputed_chain: Option<Hash>,
) {
let undisputed_chain = undisputed_chain.map(|x| (chain.number(x).unwrap().unwrap(), x));
gum::trace!("best leaf response: {:?}", undisputed_chain);
assert_matches!(
overseer_recv(
virtual_overseer
).await,
AllMessages::ChainSelection(ChainSelectionMessage::BestLeafContaining(
target_hash,
tx,
))
=> {
assert_eq!(target_block_hash, target_hash, "TestIntegrity: target hashes always match. qed");
tx.send(best_chain_containing_block).unwrap();
}
);
if best_chain_containing_block.is_none() {
return;
}
gum::trace!("approved ancestor response: {:?}", undisputed_chain);
assert_matches!(
overseer_recv(
virtual_overseer
).await,
AllMessages::ApprovalVotingParallel(ApprovalVotingParallelMessage::ApprovedAncestor(_block_hash, _block_number, tx))
=> {
tx.send(highest_approved_ancestor_block.clone()).unwrap();
}
);
gum::trace!("determine undisputed chain response: {:?}", undisputed_chain);
let target_block_number = chain.number(target_block_hash).unwrap().unwrap();
assert_matches!(
overseer_recv(
virtual_overseer
).await,
AllMessages::DisputeCoordinator(
DisputeCoordinatorMessage::DetermineUndisputedChain {
base: _,
block_descriptions: _,
tx,
}
) => {
tx.send(undisputed_chain.unwrap_or((target_block_number, target_block_hash))).unwrap();
});
// Check that ApprovedAncestor and DetermineUndisputedChain are sent with high priority.
assert_eq!(virtual_overseer.message_counter.with_high_priority(), 2);
}
/// Straight forward test case, where the test is not
/// for integrity, but for different block relation structures.
fn run_specialized_test_w_harness<F: FnOnce() -> CaseVars>(case_var_provider: F) {
test_harness(case_var_provider(), |test_harness| async move {
let TestHarness {
mut virtual_overseer,
finality_target_rx,
case_vars:
CaseVars {
chain,
target_block,
best_chain_containing_block,
highest_approved_ancestor_block,
undisputed_chain,
expected_finality_target_result,
},
..
} = test_harness;
// Verify test integrity: the provided highest approved
// ancestor must match the chain derived one.
let highest_approved_ancestor_w_desc = best_chain_containing_block
.and_then(|best_chain_containing_block| {
chain.blocks_by_hash.get(&target_block).map(|target_block_header| {
let target_blocknumber = target_block_header.number;
let highest_approved_ancestor_w_desc = chain.highest_approved_ancestors(
target_blocknumber,
best_chain_containing_block,
);
if let (
Some(highest_approved_ancestor_w_desc),
Some(highest_approved_ancestor_block),
) = (&highest_approved_ancestor_w_desc, highest_approved_ancestor_block)
{
assert_eq!(
highest_approved_ancestor_block, highest_approved_ancestor_w_desc.hash,
"TestCaseIntegrity: Provided and expected approved ancestor hash mismatch: {:?} vs {:?}",
highest_approved_ancestor_block, highest_approved_ancestor_w_desc.hash,
);
let expected = chain
.undisputed_chain(target_blocknumber, highest_approved_ancestor_block);
assert_eq!(
expected, undisputed_chain,
"TestCaseIntegrity: Provided and anticipated undisputed chain mismatch: {:?} vs {:?}",
undisputed_chain, expected,
);
}
highest_approved_ancestor_w_desc
})
})
.flatten();
test_skeleton(
&chain,
&mut virtual_overseer,
target_block,
best_chain_containing_block,
highest_approved_ancestor_w_desc,
undisputed_chain,
)
.await;
assert_matches!(finality_target_rx.await,
Ok(
finality_target_val,
) => assert_eq!(expected_finality_target_result, finality_target_val));
virtual_overseer
});
}
/// All variables relevant for a test case.
#[derive(Clone, Debug)]
struct CaseVars {
/// Chain test _case_ definition.
chain: TestChainStorage,
/// The target block to be finalized.
target_block: Hash,
/// Response to the `target_block` request, must be a chain-head.
/// `None` if no such chain exists.
best_chain_containing_block: Option<Hash>,
/// Resulting best estimate, before considering
/// the disputed state of blocks.
highest_approved_ancestor_block: Option<Hash>,
/// Equal to the previous, unless there are disputes.
/// The backtracked version of this must _never_
/// contain a disputed block.
undisputed_chain: Option<Hash>,
/// The returned value by `fn finality_target`.
expected_finality_target_result: Option<Hash>,
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2 --- 0xA3 --- 0xA4(!avail) --- 0xA5(!avail)
/// \
/// `- 0xB2
/// ```
fn chain_undisputed() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
let a3 = builder.fast_forward_approved(0xA0, a2, 3);
let a4 = builder.fast_forward(0xA0, a3, 4);
let a5 = builder.fast_forward(0xA0, a4, 5);
let b1 = builder.fast_forward_approved(0xB0, a1, 2);
let b2 = builder.fast_forward_approved(0xB0, b1, 3);
let b3 = builder.fast_forward_approved(0xB0, b2, 4);
builder.set_heads(vec![a5, b3]);
CaseVars {
chain: builder.init(),
target_block: a1,
best_chain_containing_block: Some(a5),
highest_approved_ancestor_block: Some(a3),
undisputed_chain: Some(a3),
expected_finality_target_result: Some(a3),
}
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2 --- 0xA3(disputed) --- 0xA4(!avail) --- 0xA5(!avail)
/// \
/// `- 0xB2
/// ```
fn chain_0() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
let a3 = builder.fast_forward_disputed(0xA0, a2, 3);
let a4 = builder.fast_forward(0xA0, a3, 4);
let a5 = builder.fast_forward(0xA0, a4, 5);
let b1 = builder.fast_forward_approved(0xB0, a1, 2);
let b2 = builder.fast_forward_approved(0xB0, b1, 3);
let b3 = builder.fast_forward_approved(0xB0, b2, 4);
builder.set_heads(vec![a5, b3]);
CaseVars {
chain: builder.init(),
target_block: a1,
best_chain_containing_block: Some(a5),
highest_approved_ancestor_block: Some(a3),
undisputed_chain: Some(a2),
expected_finality_target_result: Some(a2),
}
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2(disputed) --- 0xA3
/// \
/// `- 0xB2 --- 0xB3(!available)
/// ```
fn chain_1() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_disputed(0xA0, a1, 2);
let a3 = builder.fast_forward_approved(0xA0, a2, 3);
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
let b3 = builder.fast_forward(0xB0, b2, 3);
builder.set_heads(vec![a3, b3]);
CaseVars {
chain: builder.init(),
target_block: a1,
best_chain_containing_block: Some(b3),
highest_approved_ancestor_block: Some(b2),
undisputed_chain: Some(b2),
expected_finality_target_result: Some(b2),
}
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2(disputed) --- 0xA3
/// \
/// `- 0xB2 --- 0xB3
/// ```
fn chain_2() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_disputed(0xA0, a1, 2);
let a3 = builder.fast_forward_approved(0xA0, a2, 3);
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
builder.set_heads(vec![a3, b3]);
CaseVars {
chain: builder.init(),
target_block: a3,
best_chain_containing_block: Some(a3),
highest_approved_ancestor_block: Some(a3),
undisputed_chain: Some(a1),
expected_finality_target_result: Some(a1),
}
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2 --- 0xA3(disputed)
/// \
/// `- 0xB2 --- 0xB3
/// ```
fn chain_3() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
let a3 = builder.fast_forward_disputed(0xA0, a2, 3);
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
builder.set_heads(vec![a3, b3]);
CaseVars {
chain: builder.init(),
target_block: a2,
best_chain_containing_block: Some(a3),
highest_approved_ancestor_block: Some(a3),
undisputed_chain: Some(a2),
expected_finality_target_result: Some(a2),
}
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2 --- 0xA3(disputed)
/// \
/// `- 0xB2 --- 0xB3
///
/// ? --- NEX(does_not_exist)
/// ```
fn chain_4() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
let a3 = builder.fast_forward_disputed(0xA0, a2, 3);
let b2 = builder.fast_forward_approved(0xB0, a1, 2);
let b3 = builder.fast_forward_approved(0xB0, b2, 3);
builder.set_heads(vec![a3, b3]);
let does_not_exist = Hash::repeat_byte(0xCC);
CaseVars {
chain: builder.init(),
target_block: does_not_exist,
best_chain_containing_block: None,
highest_approved_ancestor_block: None,
undisputed_chain: None,
expected_finality_target_result: Some(does_not_exist),
}
}
/// ```raw
/// genesis -- 0xA1 --- 0xA2
/// ```
fn chain_5() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let a1 = builder.fast_forward_approved(0xA0, head, 1);
let a2 = builder.fast_forward_approved(0xA0, a1, 2);
builder.set_heads(vec![a2]);
CaseVars {
chain: builder.init(),
target_block: a2,
best_chain_containing_block: Some(a2),
highest_approved_ancestor_block: Some(a2),
undisputed_chain: Some(a2),
expected_finality_target_result: Some(a2),
}
}
/// ```raw
/// genesis -- 0xB2 -- 0xD2 -- .. -- 0xD8 -- 0xC8(unapproved) -- .. -- 0xCF(unapproved)
/// ```
fn chain_6() -> CaseVars {
let head: Hash = ChainBuilder::GENESIS_HASH;
let mut builder = ChainBuilder::new();
let b1 = builder.fast_forward_approved(0xB0, head, 1);
let mut previous = b1;
let mut approved = b1;
for block_number in 2_u32..16 {
if block_number <= 8 {
previous = builder.fast_forward_approved(0xD0, previous, block_number as _);
approved = previous;
} else {
previous = builder.fast_forward(0xA0, previous, block_number as _);
}
}
let leaf = previous;
builder.set_heads(vec![leaf]);
let chain = builder.init();
gum::trace!(highest_approved = ?chain.highest_approved_ancestors(1, leaf));
gum::trace!(undisputed = ?chain.undisputed_chain(1, approved));
CaseVars {
chain,
target_block: b1,
best_chain_containing_block: Some(leaf),
highest_approved_ancestor_block: Some(approved),
undisputed_chain: Some(approved),
expected_finality_target_result: Some(approved),
}
}
#[test]
fn chain_sel_undisputed() {
run_specialized_test_w_harness(chain_undisputed);
}
#[test]
fn chain_sel_0() {
run_specialized_test_w_harness(chain_0);
}
#[test]
fn chain_sel_1() {
run_specialized_test_w_harness(chain_1);
}
#[test]
fn chain_sel_2() {
run_specialized_test_w_harness(chain_2);
}
#[test]
fn chain_sel_3() {
run_specialized_test_w_harness(chain_3);
}
#[test]
fn chain_sel_4_target_hash_value_not_contained() {
run_specialized_test_w_harness(chain_4);
}
#[test]
fn chain_sel_5_best_is_target_hash() {
run_specialized_test_w_harness(chain_5);
}
#[test]
fn chain_sel_6_approval_lag() {
run_specialized_test_w_harness(chain_6);
}
@@ -0,0 +1,175 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//! A `RocksDB` instance for storing teyrchain data; availability data, and approvals.
#[cfg(feature = "full-node")]
use {
pezkuwi_node_subsystem_util::database::Database, std::io, std::path::PathBuf, std::sync::Arc,
};
#[cfg(feature = "full-node")]
mod upgrade;
const LOG_TARGET: &str = "teyrchain::db";
/// Column configuration per version.
#[cfg(any(test, feature = "full-node"))]
pub(crate) mod columns {
pub mod v0 {
pub const NUM_COLUMNS: u32 = 3;
}
pub mod v1 {
pub const NUM_COLUMNS: u32 = 5;
}
pub mod v2 {
pub const NUM_COLUMNS: u32 = 6;
#[cfg(test)]
pub const COL_SESSION_WINDOW_DATA: u32 = 5;
}
// Version 4 only changed structures in approval voting, so we can re-export the v4 definitions.
pub mod v3 {
pub use super::v4::{NUM_COLUMNS, ORDERED_COL};
}
pub mod v4 {
pub const NUM_COLUMNS: u32 = 5;
pub const COL_AVAILABILITY_DATA: u32 = 0;
pub const COL_AVAILABILITY_META: u32 = 1;
pub const COL_APPROVAL_DATA: u32 = 2;
pub const COL_CHAIN_SELECTION_DATA: u32 = 3;
pub const COL_DISPUTE_COORDINATOR_DATA: u32 = 4;
pub const ORDERED_COL: &[u32] =
&[COL_AVAILABILITY_META, COL_CHAIN_SELECTION_DATA, COL_DISPUTE_COORDINATOR_DATA];
}
}
/// Columns used by different subsystems.
#[cfg(any(test, feature = "full-node"))]
#[derive(Debug, Clone)]
pub struct ColumnsConfig {
/// The column used by the av-store for data.
pub col_availability_data: u32,
/// The column used by the av-store for meta information.
pub col_availability_meta: u32,
/// The column used by approval voting for data.
pub col_approval_data: u32,
/// The column used by chain selection for data.
pub col_chain_selection_data: u32,
/// The column used by dispute coordinator for data.
pub col_dispute_coordinator_data: u32,
}
/// The real columns used by the teyrchains DB.
#[cfg(any(test, feature = "full-node"))]
pub const REAL_COLUMNS: ColumnsConfig = ColumnsConfig {
col_availability_data: columns::v4::COL_AVAILABILITY_DATA,
col_availability_meta: columns::v4::COL_AVAILABILITY_META,
col_approval_data: columns::v4::COL_APPROVAL_DATA,
col_chain_selection_data: columns::v4::COL_CHAIN_SELECTION_DATA,
col_dispute_coordinator_data: columns::v4::COL_DISPUTE_COORDINATOR_DATA,
};
#[derive(PartialEq, Copy, Clone)]
pub(crate) enum DatabaseKind {
ParityDB,
RocksDB,
}
/// The cache size for each column, in megabytes.
#[derive(Debug, Clone)]
pub struct CacheSizes {
/// Cache used by availability data.
pub availability_data: usize,
/// Cache used by availability meta.
pub availability_meta: usize,
/// Cache used by approval data.
pub approval_data: usize,
}
impl Default for CacheSizes {
fn default() -> Self {
CacheSizes { availability_data: 25, availability_meta: 1, approval_data: 5 }
}
}
#[cfg(feature = "full-node")]
pub(crate) fn other_io_error(err: String) -> io::Error {
io::Error::new(io::ErrorKind::Other, err)
}
/// Open the database on disk, creating it if it doesn't exist.
#[cfg(feature = "full-node")]
pub fn open_creating_rocksdb(
root: PathBuf,
cache_sizes: CacheSizes,
) -> io::Result<Arc<dyn Database>> {
use kvdb_rocksdb::{Database, DatabaseConfig};
let path = root.join("teyrchains").join("db");
let mut db_config = DatabaseConfig::with_columns(columns::v4::NUM_COLUMNS);
let _ = db_config
.memory_budget
.insert(columns::v4::COL_AVAILABILITY_DATA, cache_sizes.availability_data);
let _ = db_config
.memory_budget
.insert(columns::v4::COL_AVAILABILITY_META, cache_sizes.availability_meta);
let _ = db_config
.memory_budget
.insert(columns::v4::COL_APPROVAL_DATA, cache_sizes.approval_data);
let path_str = path
.to_str()
.ok_or_else(|| other_io_error(format!("Bad database path: {:?}", path)))?;
std::fs::create_dir_all(&path_str)?;
upgrade::try_upgrade_db(&path, DatabaseKind::RocksDB, upgrade::CURRENT_VERSION)?;
let db = Database::open(&db_config, &path_str)?;
let db = pezkuwi_node_subsystem_util::database::kvdb_impl::DbAdapter::new(
db,
columns::v4::ORDERED_COL,
);
Ok(Arc::new(db))
}
/// Open a parity db database.
#[cfg(feature = "full-node")]
pub fn open_creating_paritydb(
root: PathBuf,
_cache_sizes: CacheSizes,
) -> io::Result<Arc<dyn Database>> {
let path = root.join("teyrchains");
let path_str = path
.to_str()
.ok_or_else(|| other_io_error(format!("Bad database path: {:?}", path)))?;
std::fs::create_dir_all(&path_str)?;
upgrade::try_upgrade_db(&path, DatabaseKind::ParityDB, upgrade::CURRENT_VERSION)?;
let db = parity_db::Db::open_or_create(&upgrade::paritydb_version_3_config(&path))
.map_err(|err| io::Error::new(io::ErrorKind::Other, format!("{:?}", err)))?;
let db = pezkuwi_node_subsystem_util::database::paritydb_impl::DbAdapter::new(
db,
columns::v4::ORDERED_COL,
);
Ok(Arc::new(db))
}
@@ -0,0 +1,757 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//! Migration code for the teyrchain's DB.
#![cfg(feature = "full-node")]
use super::{columns, other_io_error, DatabaseKind, LOG_TARGET};
use std::{
fs, io,
path::{Path, PathBuf},
str::FromStr,
sync::Arc,
};
use pezkuwi_node_core_approval_voting::approval_db::{
common::{Config as ApprovalDbConfig, Result as ApprovalDbResult},
v2::migration_helpers::v1_to_latest,
v3::migration_helpers::v2_to_latest,
};
use pezkuwi_node_subsystem_util::database::{
kvdb_impl::DbAdapter as RocksDbAdapter, paritydb_impl::DbAdapter as ParityDbAdapter, Database,
};
type Version = u32;
/// Version file name.
const VERSION_FILE_NAME: &'static str = "teyrchain_db_version";
/// Current db version.
/// Version 4 changes approval db format for `OurAssignment`.
/// Version 5 changes approval db format to hold some additional
/// information about delayed approvals.
pub(crate) const CURRENT_VERSION: Version = 5;
#[derive(thiserror::Error, Debug)]
pub enum Error {
#[error("I/O error when reading/writing the version")]
Io(#[from] io::Error),
#[error("The version file format is incorrect")]
CorruptedVersionFile,
#[error("Teyrchains DB has a future version (expected {current:?}, found {got:?})")]
FutureVersion { current: Version, got: Version },
#[error("Teyrchain DB migration failed")]
MigrationFailed,
#[error("Teyrchain DB migration would take forever")]
MigrationLoop,
}
impl From<Error> for io::Error {
fn from(me: Error) -> io::Error {
match me {
Error::Io(e) => e,
_ => super::other_io_error(me.to_string()),
}
}
}
/// Try upgrading teyrchain's database to a target version.
pub(crate) fn try_upgrade_db(
db_path: &Path,
db_kind: DatabaseKind,
target_version: Version,
) -> Result<(), Error> {
// Ensure we don't loop forever below because of a bug.
const MAX_MIGRATIONS: u32 = 30;
#[cfg(test)]
remove_file_lock(&db_path);
// Loop migrations until we reach the target version.
for _ in 0..MAX_MIGRATIONS {
let version = try_upgrade_db_to_next_version(db_path, db_kind)?;
#[cfg(test)]
remove_file_lock(&db_path);
if version == target_version {
return Ok(());
}
}
Err(Error::MigrationLoop)
}
/// Try upgrading teyrchain's database to the next version.
/// If successful, it returns the current version.
pub(crate) fn try_upgrade_db_to_next_version(
db_path: &Path,
db_kind: DatabaseKind,
) -> Result<Version, Error> {
let is_empty = db_path.read_dir().map_or(true, |mut d| d.next().is_none());
let new_version = if !is_empty {
match get_db_version(db_path)? {
// 0 -> 1 migration
Some(0) => migrate_from_version_0_to_1(db_path, db_kind)?,
// 1 -> 2 migration
Some(1) => migrate_from_version_1_to_2(db_path, db_kind)?,
// 2 -> 3 migration
Some(2) => migrate_from_version_2_to_3(db_path, db_kind)?,
// 3 -> 4 migration
Some(3) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v1_to_latest)?,
Some(4) => migrate_from_version_3_or_4_to_5(db_path, db_kind, v2_to_latest)?,
// Already at current version, do nothing.
Some(CURRENT_VERSION) => CURRENT_VERSION,
// This is an arbitrary future version, we don't handle it.
Some(v) => return Err(Error::FutureVersion { current: CURRENT_VERSION, got: v }),
// No version file. For `RocksDB` we don't need to do anything.
None if db_kind == DatabaseKind::RocksDB => CURRENT_VERSION,
// No version file. `ParityDB` did not previously have a version defined.
// We handle this as a `0 -> 1` migration.
None if db_kind == DatabaseKind::ParityDB =>
migrate_from_version_0_to_1(db_path, db_kind)?,
None => unreachable!(),
}
} else {
CURRENT_VERSION
};
update_version(db_path, new_version)?;
Ok(new_version)
}
/// Reads current database version from the file at given path.
/// If the file does not exist returns `None`, otherwise the version stored in the file.
fn get_db_version(path: &Path) -> Result<Option<Version>, Error> {
match fs::read_to_string(version_file_path(path)) {
Err(ref err) if err.kind() == io::ErrorKind::NotFound => Ok(None),
Err(err) => Err(err.into()),
Ok(content) => u32::from_str(&content)
.map(|v| Some(v))
.map_err(|_| Error::CorruptedVersionFile),
}
}
/// Writes current database version to the file.
/// Creates a new file if the version file does not exist yet.
fn update_version(path: &Path, new_version: Version) -> Result<(), Error> {
fs::create_dir_all(path)?;
fs::write(version_file_path(path), new_version.to_string()).map_err(Into::into)
}
/// Returns the version file path.
fn version_file_path(path: &Path) -> PathBuf {
let mut file_path = path.to_owned();
file_path.push(VERSION_FILE_NAME);
file_path
}
fn migrate_from_version_0_to_1(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
gum::info!(target: LOG_TARGET, "Migrating teyrchains db from version 0 to version 1 ...");
match db_kind {
DatabaseKind::ParityDB => paritydb_migrate_from_version_0_to_1(path),
DatabaseKind::RocksDB => rocksdb_migrate_from_version_0_to_1(path),
}
.and_then(|result| {
gum::info!(target: LOG_TARGET, "Migration complete! ");
Ok(result)
})
}
fn migrate_from_version_1_to_2(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
gum::info!(target: LOG_TARGET, "Migrating teyrchains db from version 1 to version 2 ...");
match db_kind {
DatabaseKind::ParityDB => paritydb_migrate_from_version_1_to_2(path),
DatabaseKind::RocksDB => rocksdb_migrate_from_version_1_to_2(path),
}
.and_then(|result| {
gum::info!(target: LOG_TARGET, "Migration complete! ");
Ok(result)
})
}
// Migrate approval voting database.
// In 4 `OurAssignment` has been changed to support the v2 assignments.
// In 5, `BlockEntry` has been changed to store the number of delayed approvals.
// As these are backwards compatible, we'll convert the old entries in the new format.
fn migrate_from_version_3_or_4_to_5<F>(
path: &Path,
db_kind: DatabaseKind,
migration_function: F,
) -> Result<Version, Error>
where
F: Fn(Arc<dyn Database>, ApprovalDbConfig) -> ApprovalDbResult<()>,
{
gum::info!(target: LOG_TARGET, "Migrating teyrchains db from version 3 to version 4 ...");
let approval_db_config =
ApprovalDbConfig { col_approval_data: super::REAL_COLUMNS.col_approval_data };
let _result = match db_kind {
DatabaseKind::ParityDB => {
let db = ParityDbAdapter::new(
parity_db::Db::open(&paritydb_version_3_config(path))
.map_err(|e| other_io_error(format!("Error opening db {:?}", e)))?,
super::columns::v3::ORDERED_COL,
);
migration_function(Arc::new(db), approval_db_config)
.map_err(|_| Error::MigrationFailed)?;
},
DatabaseKind::RocksDB => {
let db_path = path
.to_str()
.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
let db_cfg =
kvdb_rocksdb::DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
let db = RocksDbAdapter::new(
kvdb_rocksdb::Database::open(&db_cfg, db_path)?,
&super::columns::v3::ORDERED_COL,
);
migration_function(Arc::new(db), approval_db_config)
.map_err(|_| Error::MigrationFailed)?;
},
};
gum::info!(target: LOG_TARGET, "Migration complete! ");
Ok(CURRENT_VERSION)
}
fn migrate_from_version_2_to_3(path: &Path, db_kind: DatabaseKind) -> Result<Version, Error> {
gum::info!(target: LOG_TARGET, "Migrating teyrchains db from version 2 to version 3 ...");
match db_kind {
DatabaseKind::ParityDB => paritydb_migrate_from_version_2_to_3(path),
DatabaseKind::RocksDB => rocksdb_migrate_from_version_2_to_3(path),
}
.and_then(|result| {
gum::info!(target: LOG_TARGET, "Migration complete! ");
Ok(result)
})
}
/// Migration from version 0 to version 1:
/// * the number of columns has changed from 3 to 5;
fn rocksdb_migrate_from_version_0_to_1(path: &Path) -> Result<Version, Error> {
use kvdb_rocksdb::{Database, DatabaseConfig};
let db_path = path
.to_str()
.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
let db_cfg = DatabaseConfig::with_columns(super::columns::v0::NUM_COLUMNS);
let mut db = Database::open(&db_cfg, db_path)?;
db.add_column()?;
db.add_column()?;
Ok(1)
}
/// Migration from version 1 to version 2:
/// * the number of columns has changed from 5 to 6;
fn rocksdb_migrate_from_version_1_to_2(path: &Path) -> Result<Version, Error> {
use kvdb_rocksdb::{Database, DatabaseConfig};
let db_path = path
.to_str()
.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
let db_cfg = DatabaseConfig::with_columns(super::columns::v1::NUM_COLUMNS);
let mut db = Database::open(&db_cfg, db_path)?;
db.add_column()?;
Ok(2)
}
fn rocksdb_migrate_from_version_2_to_3(path: &Path) -> Result<Version, Error> {
use kvdb_rocksdb::{Database, DatabaseConfig};
let db_path = path
.to_str()
.ok_or_else(|| super::other_io_error("Invalid database path".into()))?;
let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS);
let mut db = Database::open(&db_cfg, db_path)?;
db.remove_last_column()?;
Ok(3)
}
// This currently clears columns which had their configs altered between versions.
// The columns to be changed are constrained by the `allowed_columns` vector.
fn paritydb_fix_columns(
path: &Path,
options: parity_db::Options,
allowed_columns: Vec<u32>,
) -> io::Result<()> {
// Figure out which columns to delete. This will be determined by inspecting
// the metadata file.
if let Some(metadata) = parity_db::Options::load_metadata(&path)
.map_err(|e| other_io_error(format!("Error reading metadata {:?}", e)))?
{
let columns_to_clear = metadata
.columns
.into_iter()
.enumerate()
.filter(|(idx, _)| allowed_columns.contains(&(*idx as u32)))
.filter_map(|(idx, opts)| {
let changed = opts != options.columns[idx];
if changed {
gum::debug!(
target: LOG_TARGET,
"Column {} will be cleared. Old options: {:?}, New options: {:?}",
idx,
opts,
options.columns[idx]
);
Some(idx)
} else {
None
}
})
.collect::<Vec<_>>();
if columns_to_clear.len() > 0 {
gum::debug!(
target: LOG_TARGET,
"Database column changes detected, need to cleanup {} columns.",
columns_to_clear.len()
);
}
for column in columns_to_clear {
gum::debug!(target: LOG_TARGET, "Clearing column {}", column,);
parity_db::clear_column(path, column.try_into().expect("Invalid column ID"))
.map_err(|e| other_io_error(format!("Error clearing column {:?}", e)))?;
}
// Write the updated column options.
options
.write_metadata(path, &metadata.salt)
.map_err(|e| other_io_error(format!("Error writing metadata {:?}", e)))?;
}
Ok(())
}
/// Database configuration for version 1.
pub(crate) fn paritydb_version_1_config(path: &Path) -> parity_db::Options {
let mut options =
parity_db::Options::with_columns(&path, super::columns::v1::NUM_COLUMNS as u8);
for i in columns::v4::ORDERED_COL {
options.columns[*i as usize].btree_index = true;
}
options
}
/// Database configuration for version 2.
pub(crate) fn paritydb_version_2_config(path: &Path) -> parity_db::Options {
let mut options =
parity_db::Options::with_columns(&path, super::columns::v2::NUM_COLUMNS as u8);
for i in columns::v4::ORDERED_COL {
options.columns[*i as usize].btree_index = true;
}
options
}
/// Database configuration for version 3.
pub(crate) fn paritydb_version_3_config(path: &Path) -> parity_db::Options {
let mut options =
parity_db::Options::with_columns(&path, super::columns::v3::NUM_COLUMNS as u8);
for i in columns::v3::ORDERED_COL {
options.columns[*i as usize].btree_index = true;
}
options
}
/// Database configuration for version 0. This is useful just for testing.
#[cfg(test)]
pub(crate) fn paritydb_version_0_config(path: &Path) -> parity_db::Options {
let mut options =
parity_db::Options::with_columns(&path, super::columns::v0::NUM_COLUMNS as u8);
options.columns[super::columns::v4::COL_AVAILABILITY_META as usize].btree_index = true;
options
}
/// Migration from version 0 to version 1.
/// Cases covered:
/// - upgrading from v0.9.23 or earlier -> the `dispute coordinator column` was changed
/// - upgrading from v0.9.24+ -> this is a no op assuming the DB has been manually fixed as per
/// release notes
fn paritydb_migrate_from_version_0_to_1(path: &Path) -> Result<Version, Error> {
// Delete the `dispute coordinator` column if needed (if column configuration is changed).
paritydb_fix_columns(
path,
paritydb_version_1_config(path),
vec![super::columns::v4::COL_DISPUTE_COORDINATOR_DATA],
)?;
Ok(1)
}
/// Migration from version 1 to version 2:
/// - add a new column for session information storage
fn paritydb_migrate_from_version_1_to_2(path: &Path) -> Result<Version, Error> {
let mut options = paritydb_version_1_config(path);
// Adds the session info column.
parity_db::Db::add_column(&mut options, Default::default())
.map_err(|e| other_io_error(format!("Error adding column {:?}", e)))?;
Ok(2)
}
/// Migration from version 2 to version 3:
/// - drop the column used by `RollingSessionWindow`
fn paritydb_migrate_from_version_2_to_3(path: &Path) -> Result<Version, Error> {
parity_db::Db::drop_last_column(&mut paritydb_version_2_config(path))
.map_err(|e| other_io_error(format!("Error removing COL_SESSION_WINDOW_DATA {:?}", e)))?;
Ok(3)
}
/// Remove the lock file. If file is locked, it will wait up to 1s.
#[cfg(test)]
pub fn remove_file_lock(path: &std::path::Path) {
use std::{io::ErrorKind, thread::sleep, time::Duration};
let mut lock_path = std::path::PathBuf::from(path);
lock_path.push("lock");
for _ in 0..10 {
let result = std::fs::remove_file(lock_path.as_path());
match result {
Err(error) => match error.kind() {
ErrorKind::WouldBlock => {
sleep(Duration::from_millis(100));
continue;
},
_ => return,
},
Ok(_) => {},
}
}
unreachable!("Database is locked, waited 1s for lock file: {:?}", lock_path);
}
#[cfg(test)]
mod tests {
use super::{
columns::{v2::COL_SESSION_WINDOW_DATA, v4::*},
*,
};
use kvdb_rocksdb::{Database, DatabaseConfig};
use pezkuwi_node_core_approval_voting::approval_db::{
v2::migration_helpers::v1_fill_test_data,
v3::migration_helpers::{v1_to_latest_sanity_check, v2_fill_test_data},
};
use pezkuwi_node_subsystem_util::database::kvdb_impl::DbAdapter;
use pezkuwi_primitives_test_helpers::dummy_candidate_receipt_v2;
#[test]
fn test_paritydb_migrate_0_to_1() {
use parity_db::Db;
let db_dir = tempfile::tempdir().unwrap();
let path = db_dir.path();
{
let db = Db::open_or_create(&paritydb_version_0_config(&path)).unwrap();
db.commit(vec![(
COL_AVAILABILITY_META as u8,
b"5678".to_vec(),
Some(b"somevalue".to_vec()),
)])
.unwrap();
}
try_upgrade_db(&path, DatabaseKind::ParityDB, 1).unwrap();
let db = Db::open(&paritydb_version_1_config(&path)).unwrap();
assert_eq!(
db.get(COL_AVAILABILITY_META as u8, b"5678").unwrap(),
Some("somevalue".as_bytes().to_vec())
);
}
#[test]
fn test_paritydb_migrate_1_to_2() {
use parity_db::Db;
let db_dir = tempfile::tempdir().unwrap();
let path = db_dir.path();
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(path), "1").expect("Failed to write DB version");
{
let db = Db::open_or_create(&paritydb_version_1_config(&path)).unwrap();
// Write some dummy data
db.commit(vec![(
COL_DISPUTE_COORDINATOR_DATA as u8,
b"1234".to_vec(),
Some(b"somevalue".to_vec()),
)])
.unwrap();
assert_eq!(db.num_columns(), columns::v1::NUM_COLUMNS as u8);
}
try_upgrade_db(&path, DatabaseKind::ParityDB, 2).unwrap();
let db = Db::open(&paritydb_version_2_config(&path)).unwrap();
assert_eq!(db.num_columns(), columns::v2::NUM_COLUMNS as u8);
assert_eq!(
db.get(COL_DISPUTE_COORDINATOR_DATA as u8, b"1234").unwrap(),
Some("somevalue".as_bytes().to_vec())
);
// Test we can write the new column.
db.commit(vec![(
COL_SESSION_WINDOW_DATA as u8,
b"1337".to_vec(),
Some(b"0xdeadb00b".to_vec()),
)])
.unwrap();
// Read back data from new column.
assert_eq!(
db.get(COL_SESSION_WINDOW_DATA as u8, b"1337").unwrap(),
Some("0xdeadb00b".as_bytes().to_vec())
);
}
#[test]
fn test_rocksdb_migrate_1_to_2() {
use kvdb::{DBKey, DBOp};
use kvdb_rocksdb::{Database, DatabaseConfig};
use pezkuwi_node_subsystem_util::database::{
kvdb_impl::DbAdapter, DBTransaction, KeyValueDB,
};
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v1::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v1::NUM_COLUMNS as u32);
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(db_dir.path()), "1").expect("Failed to write DB version");
{
let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
db.write(DBTransaction {
ops: vec![DBOp::Insert {
col: COL_DISPUTE_COORDINATOR_DATA,
key: DBKey::from_slice(b"1234"),
value: b"0xdeadb00b".to_vec(),
}],
})
.unwrap();
}
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 2).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v2::NUM_COLUMNS);
let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
assert_eq!(
db.get(COL_DISPUTE_COORDINATOR_DATA, b"1234").unwrap(),
Some("0xdeadb00b".as_bytes().to_vec())
);
// Test we can write the new column.
db.write(DBTransaction {
ops: vec![DBOp::Insert {
col: COL_SESSION_WINDOW_DATA,
key: DBKey::from_slice(b"1337"),
value: b"0xdeadb00b".to_vec(),
}],
})
.unwrap();
// Read back data from new column.
assert_eq!(
db.get(COL_SESSION_WINDOW_DATA, b"1337").unwrap(),
Some("0xdeadb00b".as_bytes().to_vec())
);
}
#[test]
fn test_migrate_3_to_5() {
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
let approval_cfg = ApprovalDbConfig {
col_approval_data: crate::teyrchains_db::REAL_COLUMNS.col_approval_data,
};
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(db_dir.path()), "3").expect("Failed to write DB version");
let expected_candidates = {
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
// Fill the approval voting column with test data.
v1_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt_v2)
.unwrap()
};
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates)
.unwrap();
}
#[test]
fn test_migrate_4_to_5() {
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
let db_cfg: DatabaseConfig = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
let approval_cfg = ApprovalDbConfig {
col_approval_data: crate::teyrchains_db::REAL_COLUMNS.col_approval_data,
};
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(db_dir.path()), "4").expect("Failed to write DB version");
let expected_candidates = {
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS as u32);
let db = DbAdapter::new(db, columns::v3::ORDERED_COL);
// Fill the approval voting column with test data.
v2_fill_test_data(std::sync::Arc::new(db), approval_cfg, dummy_candidate_receipt_v2)
.unwrap()
};
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
let db = DbAdapter::new(db, columns::v4::ORDERED_COL);
v1_to_latest_sanity_check(std::sync::Arc::new(db), approval_cfg, expected_candidates)
.unwrap();
}
#[test]
fn test_rocksdb_migrate_0_to_5() {
use kvdb_rocksdb::{Database, DatabaseConfig};
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
fs::write(version_file_path(db_dir.path()), "0").expect("Failed to write DB version");
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 5).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v4::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS);
}
#[test]
fn test_paritydb_migrate_0_to_5() {
use parity_db::Db;
let db_dir = tempfile::tempdir().unwrap();
let path = db_dir.path();
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(path), "0").expect("Failed to write DB version");
{
let db = Db::open_or_create(&paritydb_version_0_config(&path)).unwrap();
assert_eq!(db.num_columns(), columns::v0::NUM_COLUMNS as u8);
}
try_upgrade_db(&path, DatabaseKind::ParityDB, 5).unwrap();
let db = Db::open(&paritydb_version_3_config(&path)).unwrap();
assert_eq!(db.num_columns(), columns::v4::NUM_COLUMNS as u8);
}
#[test]
fn test_paritydb_migrate_2_to_3() {
use parity_db::Db;
let db_dir = tempfile::tempdir().unwrap();
let path = db_dir.path();
let test_key = b"1337";
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(path), "2").expect("Failed to write DB version");
{
let db = Db::open_or_create(&paritydb_version_2_config(&path)).unwrap();
// Write some dummy data
db.commit(vec![(
COL_SESSION_WINDOW_DATA as u8,
test_key.to_vec(),
Some(b"0xdeadb00b".to_vec()),
)])
.unwrap();
assert_eq!(db.num_columns(), columns::v2::NUM_COLUMNS as u8);
}
try_upgrade_db(&path, DatabaseKind::ParityDB, 3).unwrap();
let db = Db::open(&paritydb_version_3_config(&path)).unwrap();
assert_eq!(db.num_columns(), columns::v3::NUM_COLUMNS as u8);
}
#[test]
fn test_rocksdb_migrate_2_to_3() {
use kvdb_rocksdb::{Database, DatabaseConfig};
let db_dir = tempfile::tempdir().unwrap();
let db_path = db_dir.path().to_str().unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v2::NUM_COLUMNS);
{
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v2::NUM_COLUMNS as u32);
}
// We need to properly set db version for upgrade to work.
fs::write(version_file_path(db_dir.path()), "2").expect("Failed to write DB version");
try_upgrade_db(&db_dir.path(), DatabaseKind::RocksDB, 3).unwrap();
let db_cfg = DatabaseConfig::with_columns(super::columns::v3::NUM_COLUMNS);
let db = Database::open(&db_cfg, db_path).unwrap();
assert_eq!(db.num_columns(), super::columns::v3::NUM_COLUMNS);
}
}
+500
View File
@@ -0,0 +1,500 @@
// Copyright (C) Parity Technologies (UK) Ltd.
// This file is part of Pezkuwi.
// Pezkuwi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Pezkuwi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Pezkuwi. If not, see <http://www.gnu.org/licenses/>.
//! Utilities and tests for locating the PVF worker binaries.
use super::Error;
use is_executable::IsExecutable;
use std::path::PathBuf;
#[cfg(test)]
thread_local! {
static TMP_DIR: std::cell::RefCell<Option<tempfile::TempDir>> = std::cell::RefCell::new(None);
}
/// Override the workers pezkuwi binary directory path, used for testing.
#[cfg(test)]
fn workers_exe_path_override() -> Option<PathBuf> {
TMP_DIR.with_borrow(|t| t.as_ref().map(|t| t.path().join("usr/bin")))
}
/// Override the workers lib directory path, used for testing.
#[cfg(test)]
fn workers_lib_path_override() -> Option<PathBuf> {
TMP_DIR.with_borrow(|t| t.as_ref().map(|t| t.path().join("usr/lib/pezkuwi")))
}
/// Determines the final set of paths to use for the PVF workers.
///
/// 1. Get the binaries from the workers path if it is passed in, or consider all possible
/// locations on the filesystem in order and get all sets of paths at which the binaries exist.
///
/// 2. If no paths exist, error out. We can't proceed without workers.
///
/// 3. Log a warning if more than one set of paths exists. Continue with the first set of paths.
///
/// 4. Check if the returned paths are executable. If not it's evidence of a borked installation
/// so error out.
///
/// 5. Do the version check, if mismatch error out.
///
/// 6. At this point the final set of paths should be good to use.
pub fn determine_workers_paths(
given_workers_path: Option<PathBuf>,
workers_names: Option<(String, String)>,
node_version: Option<String>,
) -> Result<(PathBuf, PathBuf), Error> {
let mut workers_paths = list_workers_paths(given_workers_path.clone(), workers_names.clone())?;
if workers_paths.is_empty() {
let current_exe_path = get_exe_path()?;
return Err(Error::MissingWorkerBinaries {
given_workers_path,
current_exe_path,
workers_names,
});
} else if workers_paths.len() > 1 {
log::warn!("multiple sets of worker binaries found ({:?})", workers_paths,);
}
let (prep_worker_path, exec_worker_path) = workers_paths.swap_remove(0);
if !prep_worker_path.is_executable() || !exec_worker_path.is_executable() {
return Err(Error::InvalidWorkerBinaries { prep_worker_path, exec_worker_path });
}
// Do the version check.
if let Some(node_version) = node_version {
let worker_version = pezkuwi_node_core_pvf::get_worker_version(&prep_worker_path)?;
if worker_version != node_version {
return Err(Error::WorkerBinaryVersionMismatch {
worker_version,
node_version,
worker_path: prep_worker_path,
});
}
let worker_version = pezkuwi_node_core_pvf::get_worker_version(&exec_worker_path)?;
if worker_version != node_version {
return Err(Error::WorkerBinaryVersionMismatch {
worker_version,
node_version,
worker_path: exec_worker_path,
});
}
} else {
log::warn!("Skipping node/worker version checks. This could result in incorrect behavior in PVF workers.");
}
Ok((prep_worker_path, exec_worker_path))
}
/// Get list of workers paths by considering the passed-in `given_workers_path` option, or possible
/// locations on the filesystem. See `new_full`.
fn list_workers_paths(
given_workers_path: Option<PathBuf>,
workers_names: Option<(String, String)>,
) -> Result<Vec<(PathBuf, PathBuf)>, Error> {
if let Some(path) = given_workers_path {
log::trace!("Using explicitly provided workers path {:?}", path);
if path.is_executable() {
return Ok(vec![(path.clone(), path)]);
}
let (prep_worker, exec_worker) = build_worker_paths(path, workers_names);
// Check if both workers exist. Otherwise return an empty vector which results in an error.
return if prep_worker.exists() && exec_worker.exists() {
Ok(vec![(prep_worker, exec_worker)])
} else {
Ok(vec![])
};
}
// Workers path not provided, check all possible valid locations.
let mut workers_paths = vec![];
// Consider the pezkuwi binary directory.
{
let exe_path = get_exe_path()?;
let (prep_worker, exec_worker) =
build_worker_paths(exe_path.clone(), workers_names.clone());
// Add to set if both workers exist. Warn on partial installs.
let (prep_worker_exists, exec_worker_exists) = (prep_worker.exists(), exec_worker.exists());
if prep_worker_exists && exec_worker_exists {
log::trace!("Worker binaries found at current exe path: {:?}", exe_path);
workers_paths.push((prep_worker, exec_worker));
} else if prep_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", prep_worker, exec_worker);
} else if exec_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", exec_worker, prep_worker);
}
}
// Consider the /usr/lib/pezkuwi/ directory.
{
let lib_path = PathBuf::from("/usr/lib/pezkuwi");
#[cfg(test)]
let lib_path = if let Some(o) = workers_lib_path_override() { o } else { lib_path };
let (prep_worker, exec_worker) = build_worker_paths(lib_path, workers_names);
// Add to set if both workers exist. Warn on partial installs.
let (prep_worker_exists, exec_worker_exists) = (prep_worker.exists(), exec_worker.exists());
if prep_worker_exists && exec_worker_exists {
log::trace!("Worker binaries found at /usr/lib/pezkuwi");
workers_paths.push((prep_worker, exec_worker));
} else if prep_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", prep_worker, exec_worker);
} else if exec_worker_exists {
log::warn!("Worker binary found at {:?} but not {:?}", exec_worker, prep_worker);
}
}
Ok(workers_paths)
}
fn get_exe_path() -> Result<PathBuf, Error> {
let mut exe_path = std::env::current_exe()?;
let _ = exe_path.pop(); // executable file will always have a parent directory.
#[cfg(test)]
if let Some(o) = workers_exe_path_override() {
exe_path = o;
}
Ok(exe_path)
}
fn build_worker_paths(
worker_dir: PathBuf,
workers_names: Option<(String, String)>,
) -> (PathBuf, PathBuf) {
let (prep_worker_name, exec_worker_name) = workers_names.unwrap_or((
pezkuwi_node_core_pvf::PREPARE_BINARY_NAME.to_string(),
pezkuwi_node_core_pvf::EXECUTE_BINARY_NAME.to_string(),
));
let mut prep_worker = worker_dir.clone();
prep_worker.push(prep_worker_name);
let mut exec_worker = worker_dir;
exec_worker.push(exec_worker_name);
(prep_worker, exec_worker)
}
// Tests that set up a temporary directory tree according to what scenario we want to test and
// run worker detection.
#[cfg(test)]
mod tests {
use super::*;
use assert_matches::assert_matches;
use std::{fs, os::unix::fs::PermissionsExt, path::Path};
const TEST_NODE_VERSION: &'static str = "v0.1.2";
/// Write a dummy executable to the path which satisfies the version check.
fn write_worker_exe(path: impl AsRef<Path>) -> Result<(), Box<dyn std::error::Error>> {
let program = get_program(TEST_NODE_VERSION);
fs::write(&path, program)?;
Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?)
}
fn write_worker_exe_invalid_version(
path: impl AsRef<Path>,
version: &str,
) -> Result<(), Box<dyn std::error::Error>> {
let program = get_program(version);
fs::write(&path, program)?;
Ok(fs::set_permissions(&path, fs::Permissions::from_mode(0o744))?)
}
fn get_program(version: &str) -> String {
format!(
"#!/usr/bin/env bash
if [[ $# -ne 1 ]] ; then
echo \"unexpected number of arguments: $#\"
exit 1
fi
if [[ \"$1\" != \"--version\" ]] ; then
echo \"unexpected argument: $1\"
exit 1
fi
echo {}
",
version
)
}
/// Sets up an empty temp dir structure where the workers can be put by tests. Uses the temp dir
/// to override the standard locations where the node searches for the workers.
fn with_temp_dir_structure(
f: impl FnOnce(PathBuf, PathBuf) -> Result<(), Box<dyn std::error::Error>>,
) -> Result<(), Box<dyn std::error::Error>> {
// Set up <tmp>/usr/lib/pezkuwi and <tmp>/usr/bin, both empty.
let tempdir = tempfile::tempdir().unwrap();
let tmp_dir = tempdir.path().to_path_buf();
TMP_DIR.with_borrow_mut(|t| *t = Some(tempdir));
fs::create_dir_all(workers_lib_path_override().unwrap()).unwrap();
fs::create_dir_all(workers_exe_path_override().unwrap()).unwrap();
let custom_path = tmp_dir.join("usr/local/bin");
// Set up custom path at <tmp>/usr/local/bin.
fs::create_dir_all(&custom_path).unwrap();
f(tmp_dir, workers_exe_path_override().unwrap())
}
#[test]
fn test_given_worker_path() {
with_temp_dir_structure(|tempdir, exe_path| {
let given_workers_path = tempdir.join("usr/local/bin");
// Try with provided workers path that has missing binaries.
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: Some(p1), current_exe_path: p2, workers_names: None }) if p1 == given_workers_path && p2 == exe_path
);
// Try with provided workers path that has non-executable binaries.
let prepare_worker_path = given_workers_path.join("pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_path)?;
fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o644))?;
let execute_worker_path = given_workers_path.join("pezkuwi-execute-worker");
write_worker_exe(&execute_worker_path)?;
fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o644))?;
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Err(Error::InvalidWorkerBinaries { prep_worker_path: p1, exec_worker_path: p2 }) if p1 == prepare_worker_path && p2 == execute_worker_path
);
// Try with valid workers directory path that has executable binaries.
fs::set_permissions(&prepare_worker_path, fs::Permissions::from_mode(0o744))?;
fs::set_permissions(&execute_worker_path, fs::Permissions::from_mode(0o744))?;
assert_matches!(
determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == prepare_worker_path && p2 == execute_worker_path
);
// Try with valid provided workers path that is a binary file.
let given_workers_path = tempdir.join("usr/local/bin/test-worker");
write_worker_exe(&given_workers_path)?;
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == given_workers_path && p2 == given_workers_path
);
Ok(())
})
.unwrap();
}
#[test]
fn missing_workers_paths_throws_error() {
with_temp_dir_structure(|tempdir, exe_path| {
// Try with both binaries missing.
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
// Try with only prep worker (at bin location).
let prepare_worker_path = tempdir.join("usr/bin/pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
// Try with only exec worker (at bin location).
fs::remove_file(&prepare_worker_path)?;
let execute_worker_path = tempdir.join("usr/bin/pezkuwi-execute-worker");
write_worker_exe(&execute_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
// Try with only prep worker (at lib location).
fs::remove_file(&execute_worker_path)?;
let prepare_worker_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
// Try with only exec worker (at lib location).
fs::remove_file(&prepare_worker_path)?;
let execute_worker_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-execute-worker");
write_worker_exe(execute_worker_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::MissingWorkerBinaries { given_workers_path: None, current_exe_path: p, workers_names: None }) if p == exe_path
);
Ok(())
})
.unwrap()
}
#[test]
fn should_find_workers_at_all_locations() {
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_bin_path = tempdir.join("usr/bin/pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_bin_path)?;
let execute_worker_bin_path = tempdir.join("usr/bin/pezkuwi-execute-worker");
write_worker_exe(&execute_worker_bin_path)?;
let prepare_worker_lib_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_lib_path)?;
let execute_worker_lib_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-execute-worker");
write_worker_exe(&execute_worker_lib_path)?;
assert_matches!(
list_workers_paths(None, None),
Ok(v) if v == vec![(prepare_worker_bin_path, execute_worker_bin_path), (prepare_worker_lib_path, execute_worker_lib_path)]
);
Ok(())
})
.unwrap();
}
#[test]
fn should_find_workers_with_custom_names_at_all_locations() {
with_temp_dir_structure(|tempdir, _| {
let (prep_worker_name, exec_worker_name) = ("test-prepare", "test-execute");
let prepare_worker_bin_path = tempdir.join("usr/bin").join(prep_worker_name);
write_worker_exe(&prepare_worker_bin_path)?;
let execute_worker_bin_path = tempdir.join("usr/bin").join(exec_worker_name);
write_worker_exe(&execute_worker_bin_path)?;
let prepare_worker_lib_path = tempdir.join("usr/lib/pezkuwi").join(prep_worker_name);
write_worker_exe(&prepare_worker_lib_path)?;
let execute_worker_lib_path = tempdir.join("usr/lib/pezkuwi").join(exec_worker_name);
write_worker_exe(&execute_worker_lib_path)?;
assert_matches!(
list_workers_paths(None, Some((prep_worker_name.into(), exec_worker_name.into()))),
Ok(v) if v == vec![(prepare_worker_bin_path, execute_worker_bin_path), (prepare_worker_lib_path, execute_worker_lib_path)]
);
Ok(())
})
.unwrap();
}
#[test]
fn workers_version_mismatch_throws_error() {
let bad_version = "v9.9.9.9";
with_temp_dir_structure(|tempdir, _| {
// Workers at bin location return bad version.
let prepare_worker_bin_path = tempdir.join("usr/bin/pezkuwi-prepare-worker");
let execute_worker_bin_path = tempdir.join("usr/bin/pezkuwi-execute-worker");
write_worker_exe_invalid_version(&prepare_worker_bin_path, bad_version)?;
write_worker_exe(&execute_worker_bin_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_bin_path
);
// Workers at lib location return bad version.
fs::remove_file(prepare_worker_bin_path)?;
fs::remove_file(execute_worker_bin_path)?;
let prepare_worker_lib_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-prepare-worker");
let execute_worker_lib_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-execute-worker");
write_worker_exe(&prepare_worker_lib_path)?;
write_worker_exe_invalid_version(&execute_worker_lib_path, bad_version)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == execute_worker_lib_path
);
// Workers at provided workers location return bad version.
let given_workers_path = tempdir.join("usr/local/bin");
let prepare_worker_path = given_workers_path.join("pezkuwi-prepare-worker");
let execute_worker_path = given_workers_path.join("pezkuwi-execute-worker");
write_worker_exe_invalid_version(&prepare_worker_path, bad_version)?;
write_worker_exe_invalid_version(&execute_worker_path, bad_version)?;
assert_matches!(
determine_workers_paths(Some(given_workers_path), None, Some(TEST_NODE_VERSION.into())),
Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == prepare_worker_path
);
// Given worker binary returns bad version.
let given_workers_path = tempdir.join("usr/local/bin/test-worker");
write_worker_exe_invalid_version(&given_workers_path, bad_version)?;
assert_matches!(
determine_workers_paths(Some(given_workers_path.clone()), None, Some(TEST_NODE_VERSION.into())),
Err(Error::WorkerBinaryVersionMismatch { worker_version: v1, node_version: v2, worker_path: p }) if v1 == bad_version && v2 == TEST_NODE_VERSION && p == given_workers_path
);
Ok(())
})
.unwrap();
}
#[test]
fn should_find_valid_workers() {
// Test bin location.
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_bin_path = tempdir.join("usr/bin/pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_bin_path)?;
let execute_worker_bin_path = tempdir.join("usr/bin/pezkuwi-execute-worker");
write_worker_exe(&execute_worker_bin_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == prepare_worker_bin_path && p2 == execute_worker_bin_path
);
Ok(())
})
.unwrap();
// Test lib location.
with_temp_dir_structure(|tempdir, _| {
let prepare_worker_lib_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-prepare-worker");
write_worker_exe(&prepare_worker_lib_path)?;
let execute_worker_lib_path = tempdir.join("usr/lib/pezkuwi/pezkuwi-execute-worker");
write_worker_exe(&execute_worker_lib_path)?;
assert_matches!(
determine_workers_paths(None, None, Some(TEST_NODE_VERSION.into())),
Ok((p1, p2)) if p1 == prepare_worker_lib_path && p2 == execute_worker_lib_path
);
Ok(())
})
.unwrap();
}
}