feat: initialize Kurdistan SDK - independent fork of Polkadot SDK
This commit is contained in:
@@ -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, ¶_data)?;
|
||||
|
||||
Ok(inherent_data)
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
})
|
||||
}
|
||||
@@ -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!()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
}
|
||||
@@ -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()))
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user