From d283731088bf591c4be12f490fa4aa783c876124 Mon Sep 17 00:00:00 2001 From: Serban Iorga Date: Tue, 3 Sep 2024 17:35:32 +0300 Subject: [PATCH] Add manual seal node --- Cargo.lock | 2 + .../polkadot-parachain-lib/Cargo.toml | 2 + .../polkadot-parachain-lib/src/cli.rs | 6 + .../polkadot-parachain-lib/src/command.rs | 49 +++-- .../polkadot-parachain-lib/src/nodes/aura.rs | 20 +- .../src/nodes/manual_seal.rs | 207 ++++++++++++++++++ .../polkadot-parachain-lib/src/nodes/mod.rs | 34 +++ 7 files changed, 293 insertions(+), 27 deletions(-) create mode 100644 cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs diff --git a/Cargo.lock b/Cargo.lock index 83c40e9a4c7a9..4c72709e777c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14060,6 +14060,7 @@ dependencies = [ "frame-system-rpc-runtime-api", "frame-try-runtime", "futures", + "futures-timer", "jsonrpsee", "log", "nix 0.28.0", @@ -14076,6 +14077,7 @@ dependencies = [ "sc-client-api", "sc-client-db", "sc-consensus", + "sc-consensus-manual-seal", "sc-executor", "sc-network", "sc-rpc", diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml index 066cbfae53ae7..5e4c9fcf1a3d0 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/Cargo.toml @@ -61,6 +61,7 @@ pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-feature sp-inherents = { workspace = true, default-features = true } sp-api = { workspace = true, default-features = true } sp-consensus-aura = { workspace = true, default-features = true } +sc-consensus-manual-seal = { workspace = true, default-features = true } sc-sysinfo = { workspace = true, default-features = true } prometheus-endpoint = { workspace = true, default-features = true } substrate-frame-rpc-system = { workspace = true, default-features = true } @@ -83,6 +84,7 @@ cumulus-client-service = { workspace = true, default-features = true } cumulus-primitives-aura = { workspace = true, default-features = true } cumulus-primitives-core = { workspace = true, default-features = true } cumulus-relay-chain-interface = { workspace = true, default-features = true } +futures-timer = "3.0.3" [dev-dependencies] assert_cmd = { workspace = true } diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs index 349dc01d8a4f2..77ecde736691d 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/cli.rs @@ -119,6 +119,12 @@ pub struct Cli { #[command(flatten)] pub run: cumulus_client_cli::RunCmd, + /// Use manual seal consensus. + /// + /// This works only with dev chains (e.g. asset-hub-rococo-dev). + #[arg(long)] + pub manual_seal: bool, + /// EXPERIMENTAL: Use slot-based collator which can handle elastic scaling. /// /// Use with care, this flag is unstable and subject to change. diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs index d015b0b9b3bf5..e1c5c75337ab4 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/command.rs @@ -22,12 +22,11 @@ use crate::{ AuraConsensusId, Consensus, Runtime, RuntimeResolver as RuntimeResolverT, RuntimeResolver, }, - spec::DynNodeSpec, types::Block, NodeBlock, NodeExtraArgs, }, fake_runtime_api, - nodes::shell::ShellNode, + nodes::{shell::ShellNode, DynNodeSpecExt}, runtime::BlockNumber, }; #[cfg(feature = "runtime-benchmarks")] @@ -39,7 +38,6 @@ use sc_cli::{Result, SubstrateCli}; use sp_runtime::traits::AccountIdConversion; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::HashingFor; -use std::panic::{RefUnwindSafe, UnwindSafe}; /// Structure that can be used in order to provide customizers for different functionalities of the /// node binary that is being built using this library. @@ -53,10 +51,9 @@ pub struct RunConfig { pub fn new_aura_node_spec( aura_id: AuraConsensusId, extra_args: &NodeExtraArgs, -) -> Box +) -> Box where - Block: NodeBlock + UnwindSafe + RefUnwindSafe, - Block::BoundedHeader: UnwindSafe + RefUnwindSafe, + Block: NodeBlock, { match aura_id { AuraConsensusId::Sr25519 => crate::nodes::aura::new_aura_node_spec::< @@ -76,7 +73,7 @@ fn new_node_spec( config: &sc_service::Configuration, runtime_resolver: &Box, extra_args: &NodeExtraArgs, -) -> std::result::Result, sc_cli::Error> { +) -> std::result::Result, sc_cli::Error> { let runtime = runtime_resolver.runtime(config.chain_spec.as_ref())?; Ok(match runtime { @@ -215,7 +212,24 @@ pub fn run(cmd_config: RunConfig) -> Result<() RelayChainCli::::new(runner.config(), cli.relay_chain_args.iter()); let collator_options = cli.run.collator_options(); + let is_dev_chain = runner.config().chain_spec.id().ends_with("-dev"); + if cli.manual_seal && !is_dev_chain { + return Err("Manual sealing can be turned on only for dev chains".into()); + } + runner.run_node_until_exit(|config| async move { + let node_spec = + new_node_spec(&config, &cmd_config.runtime_resolver, &cli.node_extra_args())?; + let para_id = ParaId::from( + Extensions::try_get(&*config.chain_spec) + .map(|e| e.para_id) + .ok_or("Could not find parachain extension in chain-spec.")?, + ); + + if cli.manual_seal { + return node_spec.start_manual_seal_node(config, para_id).map_err(Into::into) + } + // If Statemint (Statemine, Westmint, Rockmine) DB exists and we're using the // asset-hub chain spec, then rename the base path to the new chain ID. In the case // that both file paths exist, the node will exit, as the user must decide (by @@ -262,15 +276,9 @@ pub fn run(cmd_config: RunConfig) -> Result<() })) .flatten(); - let para_id = Extensions::try_get(&*config.chain_spec) - .map(|e| e.para_id) - .ok_or("Could not find parachain extension in chain-spec.")?; - - let id = ParaId::from(para_id); - let parachain_account = AccountIdConversion::::into_account_truncating( - &id, + ¶_id, ); let tokio_handle = config.tokio_handle.clone(); @@ -278,16 +286,16 @@ pub fn run(cmd_config: RunConfig) -> Result<() SubstrateCli::create_configuration(&polkadot_cli, &polkadot_cli, tokio_handle) .map_err(|err| format!("Relay chain argument error: {}", err))?; - info!("🪪 Parachain id: {:?}", id); + info!("🪪 Parachain id: {:?}", para_id); info!("🧾 Parachain Account: {}", parachain_account); info!("✍️ Is collating: {}", if config.role.is_authority() { "yes" } else { "no" }); start_node( + node_spec, config, - &cmd_config.runtime_resolver, polkadot_config, collator_options, - id, + para_id, cli.node_extra_args(), hwbench, ) @@ -297,17 +305,20 @@ pub fn run(cmd_config: RunConfig) -> Result<() } } +/// This method is needed only for `#[sc_tracing::logging::prefix_logs_with("Parachain")]`. +/// +/// Adding this annotation to `DynNodeSpec::start_node()` doesn't work, probably because of the +/// `Pin>>` return type. #[sc_tracing::logging::prefix_logs_with("Parachain")] async fn start_node( + node_spec: Box, config: sc_service::Configuration, - runtime_resolver: &Box, polkadot_config: sc_service::Configuration, collator_options: cumulus_client_cli::CollatorOptions, id: ParaId, extra_args: NodeExtraArgs, hwbench: Option, ) -> Result { - let node_spec = new_node_spec(&config, runtime_resolver, &extra_args)?; node_spec .start_node(config, polkadot_config, collator_options, id, hwbench, extra_args) .await diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs index 86a04f127c285..533072c6cea56 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/aura.rs @@ -14,14 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Cumulus. If not, see . -use crate::common::{ - aura::{AuraIdT, AuraRuntimeApi}, - rpc::BuildParachainRpcExtensions, - spec::{BaseNodeSpec, BuildImportQueue, DynNodeSpec, NodeSpec, StartConsensus}, - types::{ - AccountId, Balance, Hash, Nonce, ParachainBackend, ParachainBlockImport, ParachainClient, +use crate::{ + common::{ + aura::{AuraIdT, AuraRuntimeApi}, + rpc::BuildParachainRpcExtensions, + spec::{BaseNodeSpec, BuildImportQueue, NodeSpec, StartConsensus}, + types::{ + AccountId, Balance, Hash, Nonce, ParachainBackend, ParachainBlockImport, + ParachainClient, + }, + ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, }, - ConstructNodeRuntimeApi, NodeBlock, NodeExtraArgs, + nodes::DynNodeSpecExt, }; use cumulus_client_collator::service::{ CollatorService, ServiceInterface as CollatorServiceInterface, @@ -199,7 +203,7 @@ where pub fn new_aura_node_spec( extra_args: &NodeExtraArgs, -) -> Box +) -> Box where Block: NodeBlock, RuntimeApi: ConstructNodeRuntimeApi>, diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs new file mode 100644 index 0000000000000..78a12bf042544 --- /dev/null +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/manual_seal.rs @@ -0,0 +1,207 @@ +use crate::common::{ + rpc::BuildRpcExtensions as BuildRpcExtensionsT, + spec::{BaseNodeSpec, BuildImportQueue, NodeSpec as NodeSpecT}, + types::{Hash, ParachainBlockImport, ParachainClient}, + ConstructNodeRuntimeApi, NodeBlock, +}; +use codec::Encode; +use cumulus_client_parachain_inherent::{MockValidationDataInherentDataProvider, MockXcmConfig}; +use cumulus_primitives_core::ParaId; +use sc_consensus::{DefaultImportQueue, LongestChain}; +use sc_consensus_manual_seal::rpc::{ManualSeal, ManualSealApiServer}; +use sc_network::NetworkBackend; +use sc_service::{Configuration, PartialComponents, TaskManager}; +use sc_telemetry::TelemetryHandle; +use sp_runtime::traits::Header; +use sp_timestamp::Timestamp; +use std::{marker::PhantomData, sync::Arc}; + +/// Build the import queue for a manual seal node. +pub struct BuildManualSealImportQueue(PhantomData<(Block, RuntimeApi)>); + +impl< + Block: NodeBlock, + RuntimeApi: ConstructNodeRuntimeApi>, + > BuildImportQueue for BuildManualSealImportQueue +{ + fn build_import_queue( + client: Arc>, + _block_import: ParachainBlockImport, + config: &Configuration, + _telemetry_handle: Option, + task_manager: &TaskManager, + ) -> sc_service::error::Result> { + Ok(sc_consensus_manual_seal::import_queue( + Box::new(client.clone()), + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + )) + } +} + +pub struct ManualSealNode(PhantomData); + +impl BaseNodeSpec for ManualSealNode { + type Block = NodeSpec::Block; + type RuntimeApi = NodeSpec::RuntimeApi; + type BuildImportQueue = BuildManualSealImportQueue; +} + +impl ManualSealNode { + pub fn new() -> Self { + Self(Default::default()) + } + + pub fn start_node( + &self, + config: Configuration, + para_id: ParaId, + ) -> sc_service::error::Result + where + Net: NetworkBackend, + { + let PartialComponents { + client, + backend, + mut task_manager, + import_queue, + keystore_container, + select_chain: _, + transaction_pool, + other: (_, mut telemetry, _), + } = Self::new_partial(&config)?; + let select_chain = LongestChain::new(backend.clone()); + + let net_config = sc_network::config::FullNetworkConfiguration::<_, _, Net>::new( + &config.network, + config.prometheus_config.as_ref().map(|cfg| cfg.registry.clone()), + ); + let metrics = Net::register_notification_metrics( + config.prometheus_config.as_ref().map(|cfg| &cfg.registry), + ); + + let (network, system_rpc_tx, tx_handler_controller, start_network, sync_service) = + sc_service::build_network(sc_service::BuildNetworkParams { + config: &config, + client: client.clone(), + transaction_pool: transaction_pool.clone(), + spawn_handle: task_manager.spawn_handle(), + import_queue, + net_config, + block_announce_validator_builder: None, + warp_sync_config: None, + block_relay: None, + metrics, + })?; + + let proposer = sc_basic_authorship::ProposerFactory::new( + task_manager.spawn_handle(), + client.clone(), + transaction_pool.clone(), + None, + None, + ); + + let (manual_seal_sink, manual_seal_stream) = futures::channel::mpsc::channel(1024); + let mut manual_seal_sink_clone = manual_seal_sink.clone(); + task_manager.spawn_handle().spawn("block_authoring", None, async move { + loop { + futures_timer::Delay::new(std::time::Duration::from_millis(6000)).await; + manual_seal_sink_clone + .try_send(sc_consensus_manual_seal::EngineCommand::SealNewBlock { + create_empty: true, + finalize: true, + parent_hash: None, + sender: None, + }) + .unwrap(); + } + }); + + let client_for_cidp = client.clone(); + let params = sc_consensus_manual_seal::ManualSealParams { + block_import: client.clone(), + env: proposer, + client: client.clone(), + pool: transaction_pool.clone(), + select_chain, + commands_stream: Box::pin(manual_seal_stream), + consensus_data_provider: None, + create_inherent_data_providers: move |block: Hash, ()| { + let current_para_head = client_for_cidp + .header(block) + .expect("Header lookup should succeed") + .expect("Header passed in as parent should be present in backend."); + let current_para_block_head = + Some(polkadot_primitives::HeadData(current_para_head.encode())); + let client_for_xcm = client_for_cidp.clone(); + async move { + use sp_runtime::traits::UniqueSaturatedInto; + + let mocked_parachain = MockValidationDataInherentDataProvider { + // When using manual seal we start from block 0, and it's very unlikely to + // reach a block number > u32::MAX. + current_para_block: UniqueSaturatedInto::::unique_saturated_into( + *current_para_head.number(), + ), + para_id, + current_para_block_head, + relay_offset: 1000, + relay_blocks_per_para_block: 1, + para_blocks_per_relay_epoch: 10, + relay_randomness_config: (), + xcm_config: MockXcmConfig::new(&*client_for_xcm, block, Default::default()), + raw_downward_messages: vec![], + raw_horizontal_messages: vec![], + additional_key_values: None, + }; + Ok(( + sp_timestamp::InherentDataProvider::new(Timestamp::new(0)), + mocked_parachain, + )) + } + }, + }; + let authorship_future = sc_consensus_manual_seal::run_manual_seal(params); + task_manager.spawn_essential_handle().spawn_blocking( + "manual-seal", + None, + authorship_future, + ); + let rpc_extensions_builder = { + let client = client.clone(); + let transaction_pool = transaction_pool.clone(); + let backend_for_rpc = backend.clone(); + + Box::new(move |_| { + let mut module = NodeSpec::BuildRpcExtensions::build_rpc_extensions( + client.clone(), + backend_for_rpc.clone(), + transaction_pool.clone(), + )?; + module + .merge(ManualSeal::new(manual_seal_sink.clone()).into_rpc()) + .map_err(|e| sc_service::Error::Application(e.into()))?; + Ok(module) + }) + }; + + let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { + network, + client: client.clone(), + keystore: keystore_container.keystore(), + task_manager: &mut task_manager, + transaction_pool: transaction_pool.clone(), + rpc_builder: rpc_extensions_builder, + backend, + system_rpc_tx, + tx_handler_controller, + sync_service, + config, + telemetry: telemetry.as_mut(), + })?; + + start_network.start_network(); + Ok(task_manager) + } +} diff --git a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs index 23654df6f9b21..7601805420e48 100644 --- a/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs +++ b/cumulus/polkadot-parachain/polkadot-parachain-lib/src/nodes/mod.rs @@ -15,4 +15,38 @@ // along with Cumulus. If not, see . pub mod aura; +mod manual_seal; pub mod shell; + +use crate::common::spec::{DynNodeSpec, NodeSpec as NodeSpecT}; +use cumulus_primitives_core::ParaId; +use manual_seal::ManualSealNode; +use sc_service::{Configuration, TaskManager}; + +pub trait DynNodeSpecExt: DynNodeSpec { + fn start_manual_seal_node( + &self, + config: Configuration, + para_id: ParaId, + ) -> sc_service::error::Result; +} + +impl DynNodeSpecExt for T +where + T: NodeSpecT + DynNodeSpec, +{ + #[sc_tracing::logging::prefix_logs_with("Parachain")] + fn start_manual_seal_node( + &self, + config: Configuration, + para_id: ParaId, + ) -> sc_service::error::Result { + let node = ManualSealNode::::new(); + match config.network.network_backend { + sc_network::config::NetworkBackendType::Libp2p => + node.start_node::>(config, para_id), + sc_network::config::NetworkBackendType::Litep2p => + node.start_node::(config, para_id), + } + } +}