From 65956956ef28d8b461bf9d0706d4bee7423e69c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Wed, 29 May 2024 23:15:44 +0200 Subject: [PATCH 1/6] Implements `PoV` export and local validation This pull requests adds a new CLI flag to `polkadot-parachains` `--export-pov-to-path`. This CLI flag will instruct the node to export any `PoV` that it build locally to export to the given folder. Then these `PoV` files can be validated using the introduced `cumulus-pov-validator`. The combination of export and validation can be used for debugging parachain validation issues that may happen on the relay chain. --- Cargo.lock | 22 ++- Cargo.toml | 1 + cumulus/bin/pov-validator/Cargo.toml | 25 +++ cumulus/bin/pov-validator/src/main.rs | 147 ++++++++++++++++++ .../consensus/aura/src/collators/lookahead.rs | 123 ++++++++++++++- cumulus/polkadot-parachain/src/cli.rs | 7 + cumulus/polkadot-parachain/src/command.rs | 49 +++--- cumulus/polkadot-parachain/src/service.rs | 112 +++++++------ 8 files changed, 399 insertions(+), 87 deletions(-) create mode 100644 cumulus/bin/pov-validator/Cargo.toml create mode 100644 cumulus/bin/pov-validator/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index c971ebcba9c6..1fcc5c1ba734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,9 +262,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0952808a6c2afd1aa8947271f3a60f1a6763c7b912d210184c5149b5cf147247" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "approx" @@ -4038,6 +4038,24 @@ dependencies = [ "staging-xcm", ] +[[package]] +name = "cumulus-pov-validator" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 4.5.3", + "parity-scale-codec", + "polkadot-node-primitives", + "polkadot-parachain-primitives", + "polkadot-primitives", + "sc-executor", + "sp-core", + "sp-io", + "sp-maybe-compressed-blob", + "tracing", + "tracing-subscriber 0.3.18", +] + [[package]] name = "cumulus-primitives-aura" version = "0.7.0" diff --git a/Cargo.toml b/Cargo.toml index d6099e420f91..64729746696a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ members = [ "bridges/snowbridge/primitives/router", "bridges/snowbridge/runtime/runtime-common", "bridges/snowbridge/runtime/test-common", + "cumulus/bin/pov-validator", "cumulus/client/cli", "cumulus/client/collator", "cumulus/client/consensus/aura", diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml new file mode 100644 index 000000000000..233332c61067 --- /dev/null +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "cumulus-pov-validator" +version = "0.1.0" +authors.workspace = true +edition.workspace = true +repository.workspace = true +license.workspace = true +homepage.workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"] } +clap = { version = "4.5.3", features = ["derive"] } +sc-executor = { path = "../../../substrate/client/executor" } +sp-io = { path = "../../../substrate/primitives/io" } +sp-core = { path = "../../../substrate/primitives/core" } +sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } +polkadot-node-primitives = { path = "../../../polkadot/node/primitives" } +polkadot-parachain-primitives = { path = "../../../polkadot/parachain" } +polkadot-primitives = { path = "../../../polkadot/primitives" } +anyhow = "1.0.86" +tracing = "0.1.40" +tracing-subscriber.workspace = true + +[lints] +workspace = true diff --git a/cumulus/bin/pov-validator/src/main.rs b/cumulus/bin/pov-validator/src/main.rs new file mode 100644 index 000000000000..14151960c532 --- /dev/null +++ b/cumulus/bin/pov-validator/src/main.rs @@ -0,0 +1,147 @@ +// This file is part of Cumulus. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0 + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with this program. If not, see . + +use clap::Parser; +use codec::{Decode, Encode}; +use polkadot_node_primitives::{BlockData, PoV, POV_BOMB_LIMIT, VALIDATION_CODE_BOMB_LIMIT}; +use polkadot_parachain_primitives::primitives::ValidationParams; +use polkadot_primitives::{BlockNumber as RBlockNumber, Hash as RHash, HeadData}; +use sc_executor::WasmExecutor; +use sp_core::traits::{CallContext, CodeExecutor, RuntimeCode, WrappedRuntimeCode}; +use std::{fs, path::PathBuf, time::Instant}; +use tracing::level_filters::LevelFilter; + +/// Tool for validating a `PoV` locally. +#[derive(Parser)] +struct Cli { + /// The path to the validation code that should be used to validate the `PoV`. + #[arg(long)] + validation_code: PathBuf, + + /// The path to the `PoV` to validate. + #[arg(long)] + pov: PathBuf, +} + +fn main() -> anyhow::Result<()> { + let _ = tracing_subscriber::fmt() + .with_env_filter( + tracing_subscriber::EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into()), + ) + .with_writer(std::io::stderr) + .try_init(); + + let cli = Cli::parse(); + + let validation_code = fs::read(&cli.validation_code).map_err(|error| { + tracing::error!(%error, path = %cli.validation_code.display(), "Failed to read validation code"); + anyhow::anyhow!("Failed to read validation code") + })?; + + let validation_code = + sp_maybe_compressed_blob::decompress(&validation_code, VALIDATION_CODE_BOMB_LIMIT) + .map_err(|error| { + tracing::error!(%error, "Failed to decompress validation code"); + anyhow::anyhow!("Failed to decompress validation code") + })?; + + let pov_file = fs::read(&cli.pov).map_err(|error| { + tracing::error!(%error, path = %cli.pov.display(), "Failed to read PoV"); + anyhow::anyhow!("Failed to read PoV") + })?; + + let executor = WasmExecutor::::builder() + .with_allow_missing_host_functions(true) + .build(); + + let runtime_code = RuntimeCode { + code_fetcher: &WrappedRuntimeCode(validation_code.into()), + heap_pages: None, + // The hash is used for caching, which we need here, but we only use one wasm file. So, the + // actual hash is not that important. + hash: vec![1, 2, 3], + }; + + // We are calling `Core_version` to get the wasm file compiled. We don't care about the result. + let _ = executor + .call( + &mut sp_io::TestExternalities::default().ext(), + &runtime_code, + "Core_version", + &[], + CallContext::Offchain, + ) + .0; + + let pov_file_ptr = &mut &pov_file[..]; + let pov = PoV::decode(pov_file_ptr).map_err(|error| { + tracing::error!(%error, "Failed to decode `PoV`"); + anyhow::anyhow!("Failed to decode `PoV`") + })?; + let head_data = HeadData::decode(pov_file_ptr).map_err(|error| { + tracing::error!(%error, "Failed to `HeadData`"); + anyhow::anyhow!("Failed to decode `HeadData`") + })?; + let relay_parent_storage_root = RHash::decode(pov_file_ptr).map_err(|error| { + tracing::error!(%error, "Failed to relay storage root"); + anyhow::anyhow!("Failed to decode relay storage root") + })?; + let relay_parent_number = RBlockNumber::decode(pov_file_ptr).map_err(|error| { + tracing::error!(%error, "Failed to relay block number"); + anyhow::anyhow!("Failed to decode relay block number") + })?; + + let pov = sp_maybe_compressed_blob::decompress(&pov.block_data.0, POV_BOMB_LIMIT).map_err( + |error| { + tracing::error!(%error, "Failed to decompress `PoV`"); + anyhow::anyhow!("Failed to decompress `PoV`") + }, + )?; + + let validation_params = ValidationParams { + relay_parent_number, + relay_parent_storage_root, + parent_head: head_data, + block_data: BlockData(pov.into()), + }; + + tracing::info!("Starting validation"); + + let start = Instant::now(); + + let res = executor + .call( + &mut sp_io::TestExternalities::default().ext(), + &runtime_code, + "validate_block", + &validation_params.encode(), + CallContext::Offchain, + ) + .0; + + let duration = start.elapsed(); + + match res { + Ok(_) => tracing::info!("Validation was successful"), + Err(error) => tracing::error!(%error, "Validation failed"), + } + + tracing::info!("Validation took {}ms", duration.as_millis()); + + Ok(()) +} diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index 09416233ea9b..d1f85adacd35 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -44,13 +44,14 @@ use cumulus_primitives_core::{ }; use cumulus_relay_chain_interface::RelayChainInterface; -use polkadot_node_primitives::SubmitCollationParams; +use polkadot_node_primitives::{PoV, SubmitCollationParams}; use polkadot_node_subsystem::messages::{ CollationGenerationMessage, RuntimeApiMessage, RuntimeApiRequest, }; use polkadot_overseer::Handle as OverseerHandle; use polkadot_primitives::{ - AsyncBackingParams, CollatorPair, CoreIndex, CoreState, Id as ParaId, OccupiedCoreAssumption, + AsyncBackingParams, BlockNumber as RBlockNumber, CollatorPair, CoreIndex, CoreState, + Hash as RHash, Id as ParaId, OccupiedCoreAssumption, HeadData }; use futures::{channel::oneshot, prelude::*}; @@ -65,11 +66,53 @@ use sp_consensus_aura::{AuraApi, Slot}; use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; -use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, NumberFor}; use sp_timestamp::Timestamp; -use std::{sync::Arc, time::Duration}; +use std::{ + fs::{self, File}, + path::PathBuf, + sync::Arc, + time::Duration, +}; + +use crate::{ + collator::{self as collator_util, SlotClaim}, + LOG_TARGET, +}; -use crate::collator::{self as collator_util, SlotClaim}; +/// Export the given `pov` to the file system at `path`. +/// +/// The file will be named `block_hash_block_number.pov`. +/// +/// The `parent_header`, `relay_parent_storage_root` and `relay_parent_number` will also be +/// stored in the file alongside the `pov`. This enables stateless validation of the `pov`. +fn export_pov_to_path( + path: PathBuf, + pov: PoV, + block_hash: Block::Hash, + block_number: NumberFor, + parent_header: Block::Header, + relay_parent_storage_root: RHash, + relay_parent_number: RBlockNumber, +) { + if let Err(error) = fs::create_dir_all(&path) { + tracing::error!(target: LOG_TARGET, %error, path = %path.display(), "Failed to create PoV export directory"); + return + } + + let mut file = match File::create(path.join(format!("{block_hash:?}_{block_number}.pov"))) { + Ok(f) => f, + Err(error) => { + tracing::error!(target: LOG_TARGET, %error, "Failed to export PoV."); + return + }, + }; + + pov.encode_to(&mut file); + HeadData(parent_header.encode()).encode_to(&mut file); + relay_parent_storage_root.encode_to(&mut file); + relay_parent_number.encode_to(&mut file); +} /// Parameters for [`run`]. pub struct Params { @@ -111,7 +154,63 @@ pub struct Params { /// Run async-backing-friendly Aura. pub fn run( - mut params: Params, + params: Params, +) -> impl Future + Send + 'static +where + Block: BlockT, + Client: ProvideRuntimeApi + + BlockOf + + AuxStore + + HeaderBackend + + BlockBackend + + Send + + Sync + + 'static, + Client::Api: + AuraApi + CollectCollationInfo + AuraUnincludedSegmentApi, + Backend: sc_client_api::Backend + 'static, + RClient: RelayChainInterface + Clone + 'static, + CIDP: CreateInherentDataProviders + 'static, + CIDP::InherentDataProviders: Send, + BI: BlockImport + ParachainBlockImportMarker + Send + Sync + 'static, + SO: SyncOracle + Send + Sync + Clone + 'static, + Proposer: ProposerInterface + Send + Sync + 'static, + CS: CollatorServiceInterface + Send + Sync + 'static, + CHP: consensus_common::ValidationCodeHashProvider + Send + 'static, + P: Pair, + P::Public: AppPublic + Member + Codec, + P::Signature: TryFrom> + Member + Codec, +{ + run_with_export::<_, P, _, _, _, _, _, _, _, _, _>(ParamsWithExport { + params, + export_pov: None, + }) +} + +/// Parameters for [`run_with_export`]. +pub struct ParamsWithExport { + /// The parameters. + pub params: Params, + /// When set, the collator will export every produced `POV` to this folder. + pub export_pov: Option, +} + +/// Run async-backing-friendly Aura. +/// +/// This is exactly the same as [`run`], but it supports the optional export of each produced `POV` +/// to the file system. +pub fn run_with_export( + ParamsWithExport { mut params, export_pov }: ParamsWithExport< + BI, + CIDP, + Client, + Backend, + RClient, + CHP, + SO, + Proposer, + CS, + >, ) -> impl Future + Send + 'static where Block: BlockT, @@ -400,6 +499,18 @@ where // and provides sybil-resistance, as it should. collator.collator_service().announce_block(new_block_hash, None); + if let Some(ref export_pov) = export_pov { + export_pov_to_path::( + export_pov.clone(), + collation.proof_of_validity.clone().into_compressed(), + new_block_hash, + *block_data.header().number(), + parent_header.clone(), + *relay_parent_header.state_root(), + *relay_parent_header.number(), + ); + } + // Send a submit-collation message to the collation generation subsystem, // which then distributes this to validators. // diff --git a/cumulus/polkadot-parachain/src/cli.rs b/cumulus/polkadot-parachain/src/cli.rs index f7d2fd0f0be3..a9fccb7ef675 100644 --- a/cumulus/polkadot-parachain/src/cli.rs +++ b/cumulus/polkadot-parachain/src/cli.rs @@ -90,6 +90,13 @@ pub struct Cli { #[arg(long)] pub no_hardware_benchmarks: bool, + /// Export all `PoVs` build by this collator to the given folder. + /// + /// This is useful for debugging issues that are occurring while validating these `PoVs` on the + /// relay chain. + #[arg(long)] + pub export_pov_to_path: Option, + /// Relay chain arguments #[arg(raw = true)] pub relaychain_args: Vec, diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index 653ea3281f0f..e12ab18b8d71 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -15,13 +15,12 @@ // along with Cumulus. If not, see . use crate::{ - chain_spec, - chain_spec::GenericChainSpec, + chain_spec::{self, GenericChainSpec}, cli::{Cli, RelayChainCli, Subcommand}, fake_runtime_api::{ asset_hub_polkadot_aura::RuntimeApi as AssetHubPolkadotRuntimeApi, aura::RuntimeApi, }, - service::{new_partial, Block, Hash}, + service::{new_partial, Block, Hash, Options}, }; use cumulus_client_service::storage_proof_size::HostFunctions as ReclaimHostFunctions; use cumulus_primitives_core::ParaId; @@ -667,14 +666,19 @@ pub fn run() -> Result<()> { info!("Parachain Account: {}", parachain_account); info!("Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); + let options = Options { + hwbench, + para_id: id, + export_pov: cli.export_pov_to_path, + }; + match polkadot_config.network.network_backend { sc_network::config::NetworkBackendType::Libp2p => start_node::>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await, sc_network::config::NetworkBackendType::Litep2p => @@ -682,8 +686,7 @@ pub fn run() -> Result<()> { config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await, } @@ -696,15 +699,14 @@ async fn start_node>( config: sc_service::Configuration, polkadot_config: sc_service::Configuration, collator_options: cumulus_client_cli::CollatorOptions, - id: ParaId, - hwbench: Option, + options: Options, ) -> Result { match config.chain_spec.runtime()? { Runtime::AssetHubPolkadot => crate::service::start_asset_hub_lookahead_node::< AssetHubPolkadotRuntimeApi, AssetHubPolkadotAuraId, Network, - >(config, polkadot_config, collator_options, id, hwbench) + >(config, polkadot_config, collator_options, options) .await .map(|r| r.0) .map_err(Into::into), @@ -714,8 +716,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0) @@ -726,8 +727,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0) @@ -737,8 +737,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0) @@ -748,8 +747,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0) @@ -770,8 +768,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0), @@ -793,8 +790,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0), @@ -806,8 +802,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0) @@ -818,8 +813,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0) @@ -840,8 +834,7 @@ async fn start_node>( config, polkadot_config, collator_options, - id, - hwbench, + options, ) .await .map(|r| r.0), diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index 12eda3e8a9cb..3508979341fb 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -60,7 +60,7 @@ use sp_runtime::{ app_crypto::AppCrypto, traits::{Block as BlockT, Header as HeaderT}, }; -use std::{marker::PhantomData, sync::Arc, time::Duration}; +use std::{marker::PhantomData, path::PathBuf, sync::Arc, time::Duration}; use substrate_prometheus_endpoint::Registry; use polkadot_primitives::CollatorPair; @@ -91,6 +91,18 @@ pub type Service = PartialComponents< (ParachainBlockImport, Option, Option), >; +/// Extra options for setting up a node. +pub struct Options { + /// The parachain id of the parachain. + pub para_id: ParaId, + + /// Optional hardware bench. + pub hwbench: Option, + + /// If set, each `PoV` build by the node will be exported to this folder. + pub export_pov: Option, +} + /// Starts a `ServiceBuilder` for a full service. /// /// Use this macro if you don't actually need the full service, but just the builder in order to @@ -193,11 +205,10 @@ async fn start_node_impl( polkadot_config: Configuration, collator_options: CollatorOptions, sybil_resistance_level: CollatorSybilResistance, - para_id: ParaId, + options: Options, rpc_ext_builder: RB, build_import_queue: BIQ, start_consensus: SC, - hwbench: Option, ) -> sc_service::error::Result<(TaskManager, Arc>)> where RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, @@ -235,7 +246,7 @@ where Arc>, KeystorePtr, Duration, - ParaId, + &Options, CollatorPair, OverseerHandle, Arc>) + Send + Sync>, @@ -258,7 +269,7 @@ where telemetry_worker_handle, &mut task_manager, collator_options.clone(), - hwbench.clone(), + options.hwbench.clone(), ) .await .map_err(|e| sc_service::Error::Application(Box::new(e) as Box<_>))?; @@ -275,7 +286,7 @@ where net_config, client: client.clone(), transaction_pool: transaction_pool.clone(), - para_id, + para_id: options.para_id, spawn_handle: task_manager.spawn_handle(), relay_chain_interface: relay_chain_interface.clone(), import_queue: params.import_queue, @@ -313,10 +324,10 @@ where telemetry: telemetry.as_mut(), })?; - if let Some(hwbench) = hwbench { - sc_sysinfo::print_hwbench(&hwbench); + if let Some(ref hwbench) = options.hwbench { + sc_sysinfo::print_hwbench(hwbench); if validator { - warn_if_slow_hardware(&hwbench); + warn_if_slow_hardware(hwbench); } if let Some(ref mut telemetry) = telemetry { @@ -324,7 +335,7 @@ where task_manager.spawn_handle().spawn( "telemetry_hwbench", None, - sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench), + sc_sysinfo::initialize_hwbench_telemetry(telemetry_handle, hwbench.clone()), ); } } @@ -343,7 +354,7 @@ where start_relay_chain_tasks(StartRelayChainTasksParams { client: client.clone(), announce_block: announce_block.clone(), - para_id, + para_id: options.para_id, relay_chain_interface: relay_chain_interface.clone(), task_manager: &mut task_manager, da_recovery_profile: if validator { @@ -369,7 +380,7 @@ where sync_service.clone(), params.keystore_container.keystore(), relay_chain_slot_duration, - para_id, + &options, collator_key.expect("Command line arguments do not allow this. qed"), overseer_handle, announce_block, @@ -390,8 +401,6 @@ pub fn build_aura_import_queue( telemetry: Option, task_manager: &TaskManager, ) -> Result, sc_service::Error> { - let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; - cumulus_client_consensus_aura::import_queue::< sp_consensus_aura::sr25519::AuthorityPair, _, @@ -401,17 +410,23 @@ pub fn build_aura_import_queue( _, >(cumulus_client_consensus_aura::ImportQueueParams { block_import, - client, - create_inherent_data_providers: move |_, _| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + client: client.clone(), + create_inherent_data_providers: move |_, _| { + let client = client.clone(); - let slot = + async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + + let slot_duration = cumulus_client_consensus_aura::slot_duration(&*client)?; + + let slot = sp_consensus_aura::inherents::InherentDataProvider::from_timestamp_and_slot_duration( *timestamp, slot_duration, ); - Ok((slot, timestamp)) + Ok((slot, timestamp)) + } }, registry: config.prometheus_registry(), spawner: &task_manager.spawn_essential_handle(), @@ -425,19 +440,17 @@ pub async fn start_rococo_parachain_node>( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, + options: Options, ) -> sc_service::error::Result<(TaskManager, Arc>)> { start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura - para_id, + options, build_parachain_rpc_extensions::, build_aura_import_queue, start_lookahead_aura_consensus, - hwbench, ) .await } @@ -494,19 +507,17 @@ pub async fn start_shell_node>( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, + options: Options, ) -> sc_service::error::Result<(TaskManager, Arc>)> { start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Unresistant, // free-for-all consensus - para_id, + options, |_, _, _, _| Ok(RpcModule::new(())), build_shell_import_queue, start_relay_chain_consensus, - hwbench, ) .await } @@ -691,19 +702,17 @@ pub async fn start_generic_aura_lookahead_node> parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, + options: Options, ) -> sc_service::error::Result<(TaskManager, Arc>)> { start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura - para_id, + options, build_parachain_rpc_extensions::, build_relay_to_aura_import_queue::<_, AuraId>, start_lookahead_aura_consensus, - hwbench, ) .await } @@ -722,8 +731,7 @@ pub async fn start_asset_hub_lookahead_node< parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, + options: Options, ) -> sc_service::error::Result<(TaskManager, Arc>)> where RuntimeApi: ConstructRuntimeApi> + Send + Sync + 'static, @@ -747,7 +755,7 @@ where polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura - para_id, + options, build_parachain_rpc_extensions::, build_relay_to_aura_import_queue::<_, AuraId>, |client, @@ -760,7 +768,7 @@ where sync_oracle, keystore, relay_chain_slot_duration, - para_id, + options, collator_key, overseer_handle, announce_block, @@ -784,6 +792,9 @@ where telemetry.clone(), ); + let export_pov = options.export_pov.clone(); + let para_id = options.para_id; + let collation_future = Box::pin(async move { // Start collating with the `shell` runtime while waiting for an upgrade to an Aura // compatible runtime. @@ -843,7 +854,7 @@ where * to aura */ }; - aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params) + aura::run_with_export::::Pair, _, _, _, _, _, _, _, _, _>(aura::ParamsWithExport { params, export_pov }) .await }); @@ -852,7 +863,6 @@ where Ok(()) }, - hwbench, ) .await } @@ -870,7 +880,7 @@ fn start_relay_chain_consensus( _sync_oracle: Arc>, _keystore: KeystorePtr, _relay_chain_slot_duration: Duration, - para_id: ParaId, + options: &Options, collator_key: CollatorPair, overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, @@ -884,9 +894,11 @@ fn start_relay_chain_consensus( telemetry, ); + let para_id = options.para_id; + let free_for_all = cumulus_client_consensus_relay_chain::build_relay_chain_consensus( cumulus_client_consensus_relay_chain::BuildRelayChainConsensusParams { - para_id, + para_id: options.para_id, proposer_factory, block_import, relay_chain_interface: relay_chain_interface.clone(), @@ -916,7 +928,7 @@ fn start_relay_chain_consensus( // Required for free-for-all consensus #[allow(deprecated)] old_consensus::start_collator_sync(old_consensus::StartCollatorParams { - para_id, + para_id: options.para_id, block_status: client.clone(), announce_block, overseer_handle, @@ -941,7 +953,7 @@ fn start_lookahead_aura_consensus( sync_oracle: Arc>, keystore: KeystorePtr, relay_chain_slot_duration: Duration, - para_id: ParaId, + options: &Options, collator_key: CollatorPair, overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, @@ -974,7 +986,7 @@ fn start_lookahead_aura_consensus( sync_oracle, keystore, collator_key, - para_id, + para_id: options.para_id, overseer_handle, relay_chain_slot_duration, proposer: Proposer::new(proposer_factory), @@ -983,7 +995,9 @@ fn start_lookahead_aura_consensus( reinitialize: false, }; - let fut = aura::run::::Pair, _, _, _, _, _, _, _, _, _>(params); + let fut = aura::run_with_export::::Pair, _, _, _, _, _, _, _, _, _>( + aura::ParamsWithExport { params, export_pov: options.export_pov.clone() }, + ); task_manager.spawn_essential_handle().spawn("aura", None, fut); Ok(()) @@ -996,19 +1010,17 @@ pub async fn start_basic_lookahead_node>( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, + options: Options, ) -> sc_service::error::Result<(TaskManager, Arc>)> { start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura - para_id, + options, |_, _, _, _| Ok(RpcModule::new(())), build_relay_to_aura_import_queue::<_, AuraId>, start_lookahead_aura_consensus, - hwbench, ) .await } @@ -1018,19 +1030,17 @@ pub async fn start_contracts_rococo_node>( parachain_config: Configuration, polkadot_config: Configuration, collator_options: CollatorOptions, - para_id: ParaId, - hwbench: Option, + options: Options, ) -> sc_service::error::Result<(TaskManager, Arc>)> { start_node_impl::( parachain_config, polkadot_config, collator_options, CollatorSybilResistance::Resistant, // Aura - para_id, + options, build_contracts_rpc_extensions, build_aura_import_queue, start_lookahead_aura_consensus, - hwbench, ) .await } From 047354c2db4ff2bd96d6859b5fb6a643eb87df88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 18 Jul 2024 14:53:52 +0200 Subject: [PATCH 2/6] Make it compile again --- cumulus/bin/pov-validator/Cargo.toml | 22 +++---- cumulus/bin/pov-validator/src/main.rs | 9 ++- .../consensus/aura/src/collators/lookahead.rs | 23 ++----- cumulus/polkadot-parachain/src/command.rs | 20 +++--- cumulus/polkadot-parachain/src/common/mod.rs | 1 + cumulus/polkadot-parachain/src/service.rs | 64 +++++++++++-------- 6 files changed, 76 insertions(+), 63 deletions(-) diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index 233332c61067..cdca61baa9b0 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -8,17 +8,17 @@ license.workspace = true homepage.workspace = true [dependencies] -codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"] } -clap = { version = "4.5.3", features = ["derive"] } -sc-executor = { path = "../../../substrate/client/executor" } -sp-io = { path = "../../../substrate/primitives/io" } -sp-core = { path = "../../../substrate/primitives/core" } -sp-maybe-compressed-blob = { path = "../../../substrate/primitives/maybe-compressed-blob" } -polkadot-node-primitives = { path = "../../../polkadot/node/primitives" } -polkadot-parachain-primitives = { path = "../../../polkadot/parachain" } -polkadot-primitives = { path = "../../../polkadot/primitives" } -anyhow = "1.0.86" -tracing = "0.1.40" +codec.workspace = true +clap = { workspace = true, features = [ "derive" ] } +sc-executor.workspace = true +sp-io.workspace = true +sp-core.workspace = true +sp-maybe-compressed-blob.workspace = true +polkadot-node-primitives.workspace = true +polkadot-parachain-primitives.workspace = true +polkadot-primitives.workspace = true +anyhow.workspace = true +tracing.workspace = true tracing-subscriber.workspace = true [lints] diff --git a/cumulus/bin/pov-validator/src/main.rs b/cumulus/bin/pov-validator/src/main.rs index 14151960c532..1c08f218f6b8 100644 --- a/cumulus/bin/pov-validator/src/main.rs +++ b/cumulus/bin/pov-validator/src/main.rs @@ -30,10 +30,16 @@ use tracing::level_filters::LevelFilter; #[derive(Parser)] struct Cli { /// The path to the validation code that should be used to validate the `PoV`. + /// + /// The validation code can either be downloaded from the relay chain that the parachain is + /// connected to or by building the runtime manually to obtain the WASM binary. #[arg(long)] validation_code: PathBuf, /// The path to the `PoV` to validate. + /// + /// The `PoV`'s can be obtained by running `polkadot-parachains --collator --chain YOUR_CHAIN + /// --export-pov-to-path PATH_TO_EXPORT` and then choose one of the exported `PoV`'s. #[arg(long)] pov: PathBuf, } @@ -41,7 +47,8 @@ struct Cli { fn main() -> anyhow::Result<()> { let _ = tracing_subscriber::fmt() .with_env_filter( - tracing_subscriber::EnvFilter::from_default_env().add_directive(LevelFilter::INFO.into()), + tracing_subscriber::EnvFilter::from_default_env() + .add_directive(LevelFilter::INFO.into()), ) .with_writer(std::io::stderr) .try_init(); diff --git a/cumulus/client/consensus/aura/src/collators/lookahead.rs b/cumulus/client/consensus/aura/src/collators/lookahead.rs index a61a14f29556..02d60538a732 100644 --- a/cumulus/client/consensus/aura/src/collators/lookahead.rs +++ b/cumulus/client/consensus/aura/src/collators/lookahead.rs @@ -40,13 +40,11 @@ use cumulus_primitives_core::{CollectCollationInfo, PersistedValidationData}; use cumulus_relay_chain_interface::RelayChainInterface; use polkadot_node_primitives::{PoV, SubmitCollationParams}; -use polkadot_node_subsystem::messages::{ - CollationGenerationMessage, RuntimeApiMessage, RuntimeApiRequest, -}; +use polkadot_node_subsystem::messages::CollationGenerationMessage; use polkadot_overseer::Handle as OverseerHandle; use polkadot_primitives::{ - AsyncBackingParams, BlockNumber as RBlockNumber, CollatorPair, CoreIndex, CoreState, - Hash as RHash, HeadData, Id as ParaId, OccupiedCoreAssumption, + BlockNumber as RBlockNumber, CollatorPair, Hash as RHash, HeadData, Id as ParaId, + OccupiedCoreAssumption, }; use futures::prelude::*; @@ -60,7 +58,6 @@ use sp_core::crypto::Pair; use sp_inherents::CreateInherentDataProviders; use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, Member, NumberFor}; -use sp_timestamp::Timestamp; use std::{ fs::{self, File}, path::PathBuf, @@ -68,10 +65,7 @@ use std::{ time::Duration, }; -use crate::{ - collator::{self as collator_util, SlotClaim}, - LOG_TARGET, -}; +use crate::{collator as collator_util, LOG_TARGET}; /// Export the given `pov` to the file system at `path`. /// @@ -171,16 +165,13 @@ where P::Public: AppPublic + Member + Codec, P::Signature: TryFrom> + Member + Codec, { - run_with_export::<_, P, _, _, _, _, _, _, _, _, _>(ParamsWithExport { - params, - export_pov: None, - }) + run_with_export::<_, P, _, _, _, _, _, _, _, _>(ParamsWithExport { params, export_pov: None }) } /// Parameters for [`run_with_export`]. -pub struct ParamsWithExport { +pub struct ParamsWithExport { /// The parameters. - pub params: Params, + pub params: Params, /// When set, the collator will export every produced `POV` to this folder. pub export_pov: Option, } diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index 9391ece0c4ea..d443f1e303ae 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -387,7 +387,7 @@ impl SubstrateCli for RelayChainCli { fn new_node_spec( config: &sc_service::Configuration, - extra_args: NodeExtraArgs, + extra_args: &NodeExtraArgs, ) -> std::result::Result, sc_cli::Error> { Ok(match config.chain_spec.runtime()? { Runtime::AssetHubPolkadot => @@ -420,35 +420,35 @@ pub fn run() -> Result<()> { Some(Subcommand::CheckBlock(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.prepare_check_block_cmd(config, cmd) }) }, Some(Subcommand::ExportBlocks(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.prepare_export_blocks_cmd(config, cmd) }) }, Some(Subcommand::ExportState(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.prepare_export_state_cmd(config, cmd) }) }, Some(Subcommand::ImportBlocks(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.prepare_import_blocks_cmd(config, cmd) }) }, Some(Subcommand::Revert(cmd)) => { let runner = cli.create_runner(cmd)?; runner.async_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.prepare_revert_cmd(config, cmd) }) }, @@ -470,7 +470,7 @@ pub fn run() -> Result<()> { Some(Subcommand::ExportGenesisHead(cmd)) => { let runner = cli.create_runner(cmd)?; runner.sync_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.run_export_genesis_head_cmd(config, cmd) }) }, @@ -493,7 +493,7 @@ pub fn run() -> Result<()> { )) }), BenchmarkCmd::Block(cmd) => runner.sync_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.run_benchmark_block_cmd(config, cmd) }), #[cfg(feature = "runtime-benchmarks")] @@ -606,9 +606,9 @@ async fn start_node( extra_args: NodeExtraArgs, hwbench: Option, ) -> Result { - let node_spec = new_node_spec(&config, extra_args)?; + let node_spec = new_node_spec(&config, &extra_args)?; node_spec - .start_node(config, polkadot_config, collator_options, id, hwbench) + .start_node(config, polkadot_config, collator_options, id, hwbench, extra_args) .await .map_err(Into::into) } diff --git a/cumulus/polkadot-parachain/src/common/mod.rs b/cumulus/polkadot-parachain/src/common/mod.rs index 99c0360f701d..d7718931b872 100644 --- a/cumulus/polkadot-parachain/src/common/mod.rs +++ b/cumulus/polkadot-parachain/src/common/mod.rs @@ -26,6 +26,7 @@ use sp_block_builder::BlockBuilder; use sp_runtime::traits::Block as BlockT; use sp_session::SessionKeys; use sp_transaction_pool::runtime_api::TaggedTransactionQueue; +use std::path::PathBuf; /// Convenience trait that defines the basic bounds for the `RuntimeApi` of a parachain node. pub trait NodeRuntimeApi: diff --git a/cumulus/polkadot-parachain/src/service.rs b/cumulus/polkadot-parachain/src/service.rs index 6a6cf15635e0..ef01f7f1f6a6 100644 --- a/cumulus/polkadot-parachain/src/service.rs +++ b/cumulus/polkadot-parachain/src/service.rs @@ -125,6 +125,7 @@ where overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, backend: Arc, + node_extra_args: NodeExtraArgs, ) -> Result<(), sc_service::Error>; } @@ -226,6 +227,7 @@ pub(crate) trait NodeSpec { collator_options: CollatorOptions, para_id: ParaId, hwbench: Option, + node_extra_args: NodeExtraArgs, ) -> Pin>>> where Net: NetworkBackend, @@ -361,6 +363,7 @@ pub(crate) trait NodeSpec { overseer_handle, announce_block, backend.clone(), + node_extra_args, )?; } @@ -524,7 +527,7 @@ where const SYBIL_RESISTANCE: CollatorSybilResistance = CollatorSybilResistance::Resistant; } -pub fn new_aura_node_spec(extra_args: NodeExtraArgs) -> Box +pub fn new_aura_node_spec(extra_args: &NodeExtraArgs) -> Box where RuntimeApi: ConstructNodeRuntimeApi>, RuntimeApi::RuntimeApi: AuraRuntimeApi @@ -567,6 +570,7 @@ impl StartConsensus for StartRelayChainConsensus { overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, _backend: Arc, + _node_extra_args: NodeExtraArgs, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), @@ -691,6 +695,7 @@ where _overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, backend: Arc, + _node_extra_args: NodeExtraArgs, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), @@ -786,6 +791,7 @@ where overseer_handle: OverseerHandle, announce_block: Arc>) + Send + Sync>, backend: Arc, + node_extra_args: NodeExtraArgs, ) -> Result<(), Error> { let proposer_factory = sc_basic_authorship::ProposerFactory::with_proof_recording( task_manager.spawn_handle(), @@ -802,33 +808,37 @@ where client.clone(), ); - let params = AuraParams { - create_inherent_data_providers: move |_, ()| async move { Ok(()) }, - block_import, - para_client: client.clone(), - para_backend: backend, - relay_client: relay_chain_interface, - code_hash_provider: { - let client = client.clone(); - move |block_hash| { - client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) - } + let params = aura::ParamsWithExport { + export_pov: node_extra_args.export_pov, + params: AuraParams { + create_inherent_data_providers: move |_, ()| async move { Ok(()) }, + block_import, + para_client: client.clone(), + para_backend: backend, + relay_client: relay_chain_interface, + code_hash_provider: { + let client = client.clone(); + move |block_hash| { + client.code_at(block_hash).ok().map(|c| ValidationCode::from(c).hash()) + } + }, + keystore, + collator_key, + para_id, + overseer_handle, + relay_chain_slot_duration, + proposer: Proposer::new(proposer_factory), + collator_service, + authoring_duration: Duration::from_millis(1500), + reinitialize: false, }, - keystore, - collator_key, - para_id, - overseer_handle, - relay_chain_slot_duration, - proposer: Proposer::new(proposer_factory), - collator_service, - authoring_duration: Duration::from_millis(1500), - reinitialize: false, }; - let fut = async move { - wait_for_aura(client).await; - aura::run::::Pair, _, _, _, _, _, _, _, _>(params).await; - }; + let fut = + async move { + wait_for_aura(client).await; + aura::run_with_export::::Pair, _, _, _, _, _, _, _, _>(params).await; + }; task_manager.spawn_essential_handle().spawn("aura", None, fut); Ok(()) @@ -910,6 +920,7 @@ pub(crate) trait DynNodeSpec { collator_options: CollatorOptions, para_id: ParaId, hwbench: Option, + node_extra_args: NodeExtraArgs, ) -> Pin>>>; } @@ -1000,6 +1011,7 @@ where collator_options: CollatorOptions, para_id: ParaId, hwbench: Option, + node_extra_args: NodeExtraArgs, ) -> Pin>>> { match parachain_config.network.network_backend { sc_network::config::NetworkBackendType::Libp2p => @@ -1009,6 +1021,7 @@ where collator_options, para_id, hwbench, + node_extra_args, ), sc_network::config::NetworkBackendType::Litep2p => ::start_node::( @@ -1017,6 +1030,7 @@ where collator_options, para_id, hwbench, + node_extra_args, ), } } From 32beacaeee623a63ba60d68c65def8f7526931df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 18 Jul 2024 16:25:20 +0200 Subject: [PATCH 3/6] Fixes --- cumulus/bin/pov-validator/Cargo.toml | 2 +- cumulus/polkadot-parachain/src/command.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index cdca61baa9b0..920e132fbf48 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -9,7 +9,7 @@ homepage.workspace = true [dependencies] codec.workspace = true -clap = { workspace = true, features = [ "derive" ] } +clap = { workspace = true, features = ["derive"] } sc-executor.workspace = true sp-io.workspace = true sp-core.workspace = true diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs index d443f1e303ae..e867a41bee2b 100644 --- a/cumulus/polkadot-parachain/src/command.rs +++ b/cumulus/polkadot-parachain/src/command.rs @@ -498,7 +498,7 @@ pub fn run() -> Result<()> { }), #[cfg(feature = "runtime-benchmarks")] BenchmarkCmd::Storage(cmd) => runner.sync_run(|config| { - let node = new_node_spec(&config, cli.node_extra_args())?; + let node = new_node_spec(&config, &cli.node_extra_args())?; node.run_benchmark_storage_cmd(config, cmd) }), BenchmarkCmd::Machine(cmd) => From b5b1dcdd4a8c73557c22b0590210da348562bc2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 18 Jul 2024 16:42:22 +0200 Subject: [PATCH 4/6] PRDOC --- prdoc/pr_4640.prdoc | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 prdoc/pr_4640.prdoc diff --git a/prdoc/pr_4640.prdoc b/prdoc/pr_4640.prdoc new file mode 100644 index 000000000000..52abc8f4baa5 --- /dev/null +++ b/prdoc/pr_4640.prdoc @@ -0,0 +1,20 @@ +title: Introduce tool for validating PoVs locally + +doc: + - audience: + - Runtime Dev + - Node Dev + description: | + Introduces the `cumulus-pov-validator` for running PoVs locally. This can be helpful for debugging issues that are + only happening when the PoV gets validated on the relay chain or for example to profile the validation code. + Besides that the `polkadot-parachain` was extended with the CLI flag `--export-pov-to-path` to let a collator export + all its build PoV's to the given directory. These PoV's can then be feed into the `cumulus-pov-validator`. + +crates: + - name: polkadot-parachain-bin + bump: minor + - name: cumulus-client-consensus-aura + bump: minor + - name: cumulus-pov-validator + bump: patch + validate: false From 7e6a3215232594456a76c92b945ab3d9adfc46e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 18 Jul 2024 16:45:50 +0200 Subject: [PATCH 5/6] Add description --- cumulus/bin/pov-validator/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/cumulus/bin/pov-validator/Cargo.toml b/cumulus/bin/pov-validator/Cargo.toml index 920e132fbf48..9be92960ad77 100644 --- a/cumulus/bin/pov-validator/Cargo.toml +++ b/cumulus/bin/pov-validator/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true repository.workspace = true license.workspace = true homepage.workspace = true +description = "A tool for validating PoVs locally" [dependencies] codec.workspace = true From 9819b3f3d276839072c436b6c00c2fd446af8c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Thu, 18 Jul 2024 16:47:41 +0200 Subject: [PATCH 6/6] Cargo.lock --- Cargo.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 46d0beca0777..2db1c32f80b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4154,7 +4154,7 @@ name = "cumulus-pov-validator" version = "0.1.0" dependencies = [ "anyhow", - "clap 4.5.3", + "clap 4.5.9", "parity-scale-codec", "polkadot-node-primitives", "polkadot-parachain-primitives",