feat: initialize Kurdistan SDK - independent fork of Polkadot SDK

This commit is contained in:
2025-12-13 15:44:15 +03:00
commit 286de54384
6841 changed files with 1848356 additions and 0 deletions
+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();
}
}