diff --git a/Cargo.lock b/Cargo.lock index 7466975fa428d..82d7fe88c6a38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4867,6 +4867,55 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "dev-omni-node" +version = "1.0.0" +dependencies = [ + "assert_cmd", + "clap 4.5.11", + "docify", + "frame-system-rpc-runtime-api", + "futures", + "futures-timer", + "jsonrpsee", + "log", + "nix 0.28.0", + "pallet-transaction-payment", + "pallet-transaction-payment-rpc-runtime-api", + "sc-basic-authorship", + "sc-chain-spec", + "sc-cli", + "sc-client-api", + "sc-consensus", + "sc-consensus-manual-seal", + "sc-executor", + "sc-network", + "sc-offchain", + "sc-rpc-api", + "sc-service", + "sc-telemetry", + "sc-transaction-pool", + "sc-transaction-pool-api", + "serde_json", + "sp-api", + "sp-block-builder", + "sp-blockchain", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-timestamp", + "sp-transaction-pool", + "sp-version", + "sp-weights", + "substrate-build-script-utils", + "substrate-frame-rpc-system", +] + [[package]] name = "diff" version = "0.1.13" @@ -8772,6 +8821,7 @@ dependencies = [ "parity-scale-codec", "polkadot-sdk-frame", "scale-info", + "serde_json", "sp-genesis-builder", "sp-runtime", "substrate-wasm-builder", @@ -14650,6 +14700,7 @@ dependencies = [ "sp-core", "sp-inherents", "sp-io", + "sp-keyring", "sp-offchain", "sp-runtime", "sp-session", @@ -17153,6 +17204,7 @@ dependencies = [ "serde_json", "sp-blockchain", "sp-core", + "sp-genesis-builder", "sp-keyring", "sp-keystore", "sp-panic-handler", diff --git a/Cargo.toml b/Cargo.toml index 38ef9075a7311..7f85b9375659a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -244,6 +244,7 @@ members = [ "substrate/bin/node/testing", "substrate/bin/utils/chain-spec-builder", "substrate/bin/utils/subkey", + "substrate/bin/utils/dev-omni-node", "substrate/client/allocator", "substrate/client/api", "substrate/client/authority-discovery", diff --git a/polkadot/runtime/rococo/src/genesis_config_presets.rs b/polkadot/runtime/rococo/src/genesis_config_presets.rs index 67dcd6cd7a510..0c8fdeba76540 100644 --- a/polkadot/runtime/rococo/src/genesis_config_presets.rs +++ b/polkadot/runtime/rococo/src/genesis_config_presets.rs @@ -79,6 +79,7 @@ fn get_authority_keys_from_seed_no_beefy( } fn testnet_accounts() -> Vec { + // TODO: just use sp_keyring::AccountKeyring::iter()?? Vec::from([ get_account_id_from_seed::("Alice"), get_account_id_from_seed::("Bob"), @@ -532,8 +533,8 @@ fn wococo_local_testnet_genesis() -> serde_json::Value { /// Provides the JSON representation of predefined genesis config for given `id`. pub fn get_preset(id: &sp_genesis_builder::PresetId) -> Option> { let patch = match id.try_into() { + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => rococo_development_config_genesis(), Ok("local_testnet") => rococo_local_testnet_genesis(), - Ok("development") => rococo_development_config_genesis(), Ok("staging_testnet") => rococo_staging_testnet_config_genesis(), Ok("wococo_local_testnet") => wococo_local_testnet_genesis(), Ok("versi_local_testnet") => versi_local_testnet_genesis(), diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 2a1cb58fc17f3..4da1d693bb753 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -2572,8 +2572,8 @@ sp_api::impl_runtime_apis! { fn preset_names() -> Vec { vec![ + PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), PresetId::from("local_testnet"), - PresetId::from("development"), PresetId::from("staging_testnet"), PresetId::from("wococo_local_testnet"), PresetId::from("versi_local_testnet"), diff --git a/substrate/bin/node/cli/src/command.rs b/substrate/bin/node/cli/src/command.rs index 51fbf0904cf8c..c0d22c8764ba4 100644 --- a/substrate/bin/node/cli/src/command.rs +++ b/substrate/bin/node/cli/src/command.rs @@ -75,6 +75,29 @@ impl SubstrateCli for Cli { }; Ok(spec) } + + fn load_spec_from_runtime( + &self, + runtime: &[u8], + maybe_preset: Option<&str>, + ) -> std::result::Result, String> { + use sc_service::{ChainType, Properties}; + let mut properties = Properties::new(); + properties.insert("tokenDecimals".to_string(), 12.into()); + properties.insert("tokenSymbol".to_string(), "SUB-DEV".into()); + + let mut builder = chain_spec::ChainSpec::builder(runtime, Default::default()) + .with_name("Development") + .with_id("dev") + .with_chain_type(ChainType::Development) + .with_properties(properties); + + if let Some(preset) = maybe_preset { + builder = builder.with_genesis_config_preset_name(preset); + } + + Ok(Box::new(builder.build())) + } } /// Parse command line arguments into service configuration. diff --git a/substrate/bin/utils/dev-omni-node/Cargo.toml b/substrate/bin/utils/dev-omni-node/Cargo.toml new file mode 100644 index 0000000000000..186c9c36f6d29 --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/Cargo.toml @@ -0,0 +1,73 @@ +[package] +name = "dev-omni-node" +description = "A minimal omni node capable of running runtimes without any consensus." +version = "1.0.0" +authors.workspace = true +edition.workspace = true +repository.workspace = true +publish = true +build = "build.rs" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://paritytech.github.io/polkadot-sdk" + + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +clap = { features = ["derive"], workspace = true } +futures = { features = ["thread-pool"], workspace = true } +futures-timer = { workspace = true } +jsonrpsee = { features = ["server"], workspace = true } +serde_json = { workspace = true, default-features = true } +log = { worksapce = true } +docify = { workspace = true } + +sc-cli = { workspace = true, default-features = true } +sc-executor = { workspace = true, default-features = true } +sc-network = { workspace = true, default-features = true } +sc-service = { workspace = true, default-features = true } +sc-telemetry = { workspace = true, default-features = true } +sc-transaction-pool = { workspace = true, default-features = true } +sc-transaction-pool-api = { workspace = true, default-features = true } +sc-consensus = { workspace = true, default-features = true } +sc-consensus-manual-seal = { workspace = true, default-features = true } +sc-rpc-api = { workspace = true, default-features = true } +sc-basic-authorship = { workspace = true, default-features = true } +sc-offchain = { workspace = true, default-features = true } +sc-client-api = { workspace = true, default-features = true } +sc-chain-spec = { workspace = true, default-features = true } + +sp-timestamp = { workspace = true, default-features = true } +sp-api = { workspace = true, default-features = true } +sp-blockchain = { workspace = true, default-features = true } +sp-block-builder = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } +sp-transaction-pool = { workspace = true, default-features = true } +sp-session = { workspace = true, default-features = true } +sp-offchain = { workspace = true, default-features = true } +sp-core = { workspace = true, default-features = true } +sp-inherents = { workspace = true, default-features = true } +sp-consensus-aura = { workspace = true, default-features = true } +sp-consensus-grandpa = { workspace = true, default-features = true } +sp-weights = { workspace = true, default-features = true } +sp-version = { workspace = true, default-features = true } + +# frame and pallets +pallet-transaction-payment-rpc-runtime-api = { workspace = true, default-features = true } +pallet-transaction-payment = { workspace = true, default-features = true } + +substrate-frame-rpc-system = { workspace = true, default-features = true } +frame-system-rpc-runtime-api = { workspace = true, default-features = true } + +[build-dependencies] +substrate-build-script-utils = { workspace = true, default-features = true } + +[dev-dependencies] +assert_cmd = "2.0.14" +nix = { version = "0.28.0", features = ["signal"] } + + +[features] +default = [] diff --git a/substrate/bin/utils/dev-omni-node/build.rs b/substrate/bin/utils/dev-omni-node/build.rs new file mode 100644 index 0000000000000..fa7686e01099f --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/build.rs @@ -0,0 +1,23 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use substrate_build_script_utils::{generate_cargo_keys, rerun_if_git_head_changed}; + +fn main() { + generate_cargo_keys(); + rerun_if_git_head_changed(); +} diff --git a/substrate/bin/utils/dev-omni-node/src/cli.rs b/substrate/bin/utils/dev-omni-node/src/cli.rs new file mode 100644 index 0000000000000..bb0d92c2525bd --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/cli.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The CLI parameters of the dev omni-node. + +use sc_cli::RunCmd; + +/// The consensus algorithm to use. +#[derive(Debug, Clone)] +pub enum Consensus { + /// Manual seal, with the block time in milliseconds. + /// + /// Should be provided as `manual-seal-3000` for a 3 seconds block time. + ManualSeal(u64), + /// Instant seal. + /// + /// Authors a new block as soon as a transaction is received. + InstantSeal, +} + +impl std::str::FromStr for Consensus { + type Err = String; + + fn from_str(s: &str) -> Result { + Ok(if s == "instant-seal" { + Consensus::InstantSeal + } else if let Some(block_time) = s.strip_prefix("manual-seal-") { + Consensus::ManualSeal(block_time.parse().map_err(|_| "invalid block time")?) + } else { + return Err("incorrect consensus identifier".into()) + }) + } +} + +/// You typically run this node with: +/// +/// * Either of `--chain runtime.wasm` or `--chain spec.json`. +/// * `--tmp` to use a temporary database. +/// +/// This binary goes hand in hand with: +/// +/// * a `.wasm` file. You typically get this from your runtime template. +/// * `chain-spec-builder` to generate chain-spec. You might possibly edit this chain-spec manually, +/// or alter your runtime's `sp_genesis_builder` impl with specific presets. +/// +/// * `frame-omni-bencher` to create benchmarking. +#[derive(Debug, clap::Parser)] +pub struct Cli { + /// The subcommand to use. + #[command(subcommand)] + pub subcommand: Option, + + /// The block authoring (aka. consensus) engine to use. + #[clap(long, default_value = "manual-seal-1000")] + pub consensus: Consensus, + + /// The main run command + #[clap(flatten)] + pub run: RunCmd, +} + +/// Possible sub-commands. +#[derive(Debug, clap::Subcommand)] +pub enum Subcommand { + /// Key management cli utilities + #[command(subcommand)] + Key(sc_cli::KeySubcommand), + + /// Validate blocks. + CheckBlock(sc_cli::CheckBlockCmd), + + /// Export blocks. + ExportBlocks(sc_cli::ExportBlocksCmd), + + /// Export the state of a given block into a chain spec. + ExportState(sc_cli::ExportStateCmd), + + /// Import blocks. + ImportBlocks(sc_cli::ImportBlocksCmd), + + /// Remove the whole chain. + PurgeChain(sc_cli::PurgeChainCmd), + + /// Revert the chain to a previous state. + Revert(sc_cli::RevertCmd), + + /// Db meta columns information. + ChainInfo(sc_cli::ChainInfoCmd), +} diff --git a/substrate/bin/utils/dev-omni-node/src/command.rs b/substrate/bin/utils/dev-omni-node/src/command.rs new file mode 100644 index 0000000000000..5e1e70c63faf5 --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/command.rs @@ -0,0 +1,151 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + cli::{Cli, Subcommand}, + service, +}; +use sc_cli::SubstrateCli; +use sc_service::{ChainType, NoExtension, PartialComponents, Properties}; +type ChainSpec = sc_chain_spec::GenericChainSpec; + +impl SubstrateCli for Cli { + fn impl_name() -> String { + "Polkadot SDK Dev Omni Node".into() + } + + fn impl_version() -> String { + env!("SUBSTRATE_CLI_IMPL_VERSION").into() + } + + fn description() -> String { + env!("CARGO_PKG_DESCRIPTION").into() + } + + fn author() -> String { + env!("CARGO_PKG_AUTHORS").into() + } + + fn support_url() -> String { + "support.anonymous.an".into() + } + + fn copyright_start_year() -> i32 { + 2017 + } + + fn load_spec_from_runtime( + &self, + runtime: &[u8], + maybe_preset: Option<&str>, + ) -> std::result::Result, String> { + log::info!("building chain-spec from defaults, and preset-id {:?}", maybe_preset); + + let mut properties = Properties::new(); + properties.insert("tokenDecimals".to_string(), 12.into()); + properties.insert("tokenSymbol".to_string(), "SUB-OMNI-DEV".into()); + + let mut builder = ChainSpec::builder(runtime.as_ref(), Default::default()) + .with_name("Development") + .with_id("dev") + .with_chain_type(ChainType::Development) + .with_properties(properties); + + if let Some(preset) = maybe_preset { + builder = builder.with_genesis_config_preset_name(preset); + } + + Ok(Box::new(builder.build())) + } + + fn load_spec(&self, maybe_path: &str) -> Result, String> { + match maybe_path { + "" => Err("No --chain or --runtime provided".into()), + "dev" | "local" => + Err("--dev, --chain=dev, --chain=local or any other 'magic' chain id is not \ + supported in omni-nodes, please provide --chain chain-spec.json or --runtime \ + runtime.wasm" + .into()), + x if x.ends_with("json") => { + log::info!("Loading json chain spec from {}", maybe_path); + Ok(Box::new(ChainSpec::from_json_file(std::path::PathBuf::from(maybe_path))?)) + }, + _ => Err("Unknown argument to --chain. should be `.wasm` or `.json`".into()), + } + } +} + +/// Parse and run command line arguments +pub fn run() -> sc_cli::Result<()> { + let cli = Cli::from_args(); + + match &cli.subcommand { + Some(Subcommand::Key(cmd)) => cmd.run(&cli), + Some(Subcommand::CheckBlock(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::ExportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + Ok((cmd.run(client, config.database), task_manager)) + }) + }, + Some(Subcommand::ExportState(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, .. } = service::new_partial(&config)?; + Ok((cmd.run(client, config.chain_spec), task_manager)) + }) + }, + Some(Subcommand::ImportBlocks(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, import_queue, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, import_queue), task_manager)) + }) + }, + Some(Subcommand::PurgeChain(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run(config.database)) + }, + Some(Subcommand::Revert(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.async_run(|config| { + let PartialComponents { client, task_manager, backend, .. } = + service::new_partial(&config)?; + Ok((cmd.run(client, backend, None), task_manager)) + }) + }, + Some(Subcommand::ChainInfo(cmd)) => { + let runner = cli.create_runner(cmd)?; + runner.sync_run(|config| cmd.run::(&config)) + }, + None => { + let runner = cli.create_runner(&cli.run)?; + runner.run_node_until_exit(|config| async move { + service::new_full(config, cli.consensus).map_err(sc_cli::Error::Service) + }) + }, + } +} diff --git a/substrate/bin/utils/dev-omni-node/src/fake_runtime_api.rs b/substrate/bin/utils/dev-omni-node/src/fake_runtime_api.rs new file mode 100644 index 0000000000000..b0de07e7c613c --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/fake_runtime_api.rs @@ -0,0 +1,196 @@ +//! # Fake Runtime +//! +//! A fake runtime that implements (hopefully) all of the runtime apis defined in polkadot-sdk as a +//! stub. All implementations are fulfilled with `unreachable!()`. +//! +//! See [`FakeRuntime`] + +#![allow(unused_variables)] + +use sp_runtime::traits::Block as BlockT; + +/// The fake runtime. +pub(crate) struct FakeRuntime; + +/// Some types that we need to fulfill the trait bounds at compile-time, but in runtime they don't +/// matter at all. +mod whatever { + pub(crate) type Block = crate::standards::OpaqueBlock; + pub(crate) type Header = ::Header; + pub(crate) type AccountId = sp_runtime::AccountId32; + pub(crate) type Nonce = u32; + pub(crate) type AuraId = sp_consensus_aura::sr25519::AuthorityId; + pub(crate) type BlockNumber = u32; + pub(crate) type Balance = u128; + pub(crate) type Weight = sp_weights::Weight; + pub(crate) type RuntimeCall = (); +} +use whatever::*; + +sp_api::impl_runtime_apis! { + impl sp_api::Core for FakeRuntime { + fn version() -> sp_version::RuntimeVersion { + unreachable!() + } + + fn execute_block(block: Block) { + unreachable!() + } + + fn initialize_block(header: &Header) -> sp_runtime::ExtrinsicInclusionMode { + unreachable!() + } + } + + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for FakeRuntime { + fn validate_transaction( + _source: sp_runtime::transaction_validity::TransactionSource, + _tx: ::Extrinsic, + _hash: ::Hash, + ) -> sp_runtime::transaction_validity::TransactionValidity { + unreachable!() + } + } + + impl sp_block_builder::BlockBuilder for FakeRuntime { + fn apply_extrinsic( + extrinsic: ::Extrinsic + ) -> sp_runtime::ApplyExtrinsicResult { + unreachable!() + } + + fn finalize_block() -> ::Header { + unreachable!() + } + + fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { + unreachable!() + } + + fn check_inherents( + block: Block, + data: sp_inherents::InherentData, + ) -> sp_inherents::CheckInherentsResult { + unreachable!() + } + } + + impl sp_api::Metadata for FakeRuntime { + fn metadata() -> sp_core::OpaqueMetadata { + unreachable!() + } + + fn metadata_at_version(version: u32) -> Option { + unreachable!() + } + + fn metadata_versions() -> Vec { + unreachable!() + } + } + + impl frame_system_rpc_runtime_api::AccountNonceApi for FakeRuntime { + fn account_nonce(account: AccountId) -> Nonce { + unreachable!(); + } + } + + impl sp_session::SessionKeys for FakeRuntime { + fn generate_session_keys(_seed: Option>) -> Vec { + unreachable!() + } + + fn decode_session_keys( + _encoded: Vec, + ) -> Option, sp_session::KeyTypeId)>> { + unreachable!() + } + } + + impl sp_offchain::OffchainWorkerApi for FakeRuntime { + fn offchain_worker(header: &::Header) { + unreachable!() + } + } + + impl sp_consensus_aura::AuraApi for FakeRuntime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + unreachable!(); + } + + fn authorities() -> Vec { + unreachable!(); + } + } + + impl sp_consensus_grandpa::GrandpaApi for FakeRuntime { + fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { + unreachable!() + } + + fn current_set_id() -> sp_consensus_grandpa::SetId { + unreachable!() + } + + fn submit_report_equivocation_unsigned_extrinsic( + _equivocation_proof: sp_consensus_grandpa::EquivocationProof< + ::Hash, + BlockNumber, + >, + _key_owner_proof: sp_consensus_grandpa::OpaqueKeyOwnershipProof, + ) -> Option<()> { + unreachable!() + } + + fn generate_key_ownership_proof( + _set_id: sp_consensus_grandpa::SetId, + _authority_id: sp_consensus_grandpa::AuthorityId, // TODO: double check, but it should not even matter. + ) -> Option { + unreachable!() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for FakeRuntime { + fn query_info( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + unreachable!() + } + fn query_fee_details( + uxt: ::Extrinsic, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + unreachable!() + } + fn query_weight_to_fee(weight: Weight) -> Balance { + unreachable!() + } + fn query_length_to_fee(length: u32) -> Balance { + unreachable!() + } + } + + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi + for FakeRuntime + { + fn query_call_info( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { + unreachable!() + } + fn query_call_fee_details( + call: RuntimeCall, + len: u32, + ) -> pallet_transaction_payment::FeeDetails { + unreachable!() + } + fn query_weight_to_fee(weight: Weight) -> Balance { + unreachable!() + } + fn query_length_to_fee(length: u32) -> Balance { + unreachable!() + } + } +} diff --git a/substrate/bin/utils/dev-omni-node/src/main.rs b/substrate/bin/utils/dev-omni-node/src/main.rs new file mode 100644 index 0000000000000..069f491aec581 --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/main.rs @@ -0,0 +1,163 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A minimal omni-node capable of running any polkadot-sdk-based runtime so long as it adheres +//! [`standards`]. See this module for more information about the assumptions of this node. +//! +//! See [`cli::Cli`] for usage info. + +#![warn(missing_docs)] + +pub mod cli; +mod command; +mod fake_runtime_api; +mod rpc; +mod service; +pub mod standards; + +fn main() -> sc_cli::Result<()> { + command::run() +} + +#[cfg(test)] +mod tests { + use nix::{ + sys::signal::{kill, Signal::SIGINT}, + unistd::Pid, + }; + use std::{ + io::{BufRead, BufReader}, + path::Path, + process, + }; + + const RUNTIME_PATH: &'static str = + "target/release/wbuild/minimal-template-runtime/minimal_template_runtime.wasm"; + const CHAIN_SPEC_BUILDER: &'static str = "target/release/chain-spec-builder"; + const DEV_OMNI_NODE: &'static str = "target/release/dev-omni-node"; + + fn maybe_build_runtime() { + if !Path::new(RUNTIME_PATH).exists() { + println!("Building runtime..."); + assert_cmd::Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg("minimal-template-runtime") + .assert() + .success(); + } + assert!(Path::new(RUNTIME_PATH).exists(), "runtime must now exist!"); + } + + fn maybe_build_chain_spec_builder() { + // build chain-spec-builder if it does not exist + if !Path::new(CHAIN_SPEC_BUILDER).exists() { + println!("Building chain-spec-builder..."); + assert_cmd::Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg("staging-chain-spec-builder") + .assert() + .success(); + } + assert!(Path::new(CHAIN_SPEC_BUILDER).exists(), "chain-spec-builder must now exist!"); + } + + fn maybe_build_dev_omni_node() { + // build dev-omni-node if it does not exist + if !Path::new(DEV_OMNI_NODE).exists() { + println!("Building dev-omni-node..."); + assert_cmd::Command::new("cargo") + .arg("build") + .arg("--release") + .arg("-p") + .arg("dev-omni-node") + .assert() + .success(); + } + assert!(Path::new(DEV_OMNI_NODE).exists(), "dev-omni-node must now exist!"); + } + + fn ensure_node_process_works(node_cmd: &mut process::Command) { + let mut node_process = node_cmd.spawn().unwrap(); + + std::thread::sleep(std::time::Duration::from_secs(15)); + let stderr = node_process.stderr.take().unwrap(); + + kill(Pid::from_raw(node_process.id().try_into().unwrap()), SIGINT).unwrap(); + let exit_status = node_process.wait().unwrap(); + println!("Exit status: {:?}", exit_status); + + // ensure in stderr there is at least one line containing: "Imported #10" + assert!( + BufReader::new(stderr).lines().any(|l| { l.unwrap().contains("Imported #10") }), + "failed to find 10 imported blocks in the output.", + ); + } + + #[test] + #[ignore = "is flaky"] + fn run_omni_node_with_chain_spec() { + // set working directory to project root, 4 parents + std::env::set_current_dir(std::env::current_dir().unwrap().join("../../../..")).unwrap(); + // last segment of cwd must now be `polkadot-sdk` + assert!(std::env::current_dir().unwrap().ends_with("polkadot-sdk")); + + maybe_build_runtime(); + maybe_build_chain_spec_builder(); + maybe_build_dev_omni_node(); + + process::Command::new(CHAIN_SPEC_BUILDER) + .arg("create") + .args(["-r", RUNTIME_PATH]) + .arg("default") + .stderr(process::Stdio::piped()) + .spawn() + .unwrap(); + + // join current dir and chain_spec.json + let chain_spec_file = std::env::current_dir().unwrap().join("chain_spec.json"); + + let mut binding = process::Command::new(DEV_OMNI_NODE); + let node_cmd = &mut binding + .arg("--tmp") + .args(["--chain", chain_spec_file.to_str().unwrap()]) + .stderr(process::Stdio::piped()); + + ensure_node_process_works(node_cmd); + + // delete chain_spec.json + std::fs::remove_file(chain_spec_file).unwrap(); + } + + #[test] + #[ignore = "is flaky"] + fn run_omni_node_with_runtime() { + maybe_build_runtime(); + maybe_build_dev_omni_node(); + + let mut binding = process::Command::new(DEV_OMNI_NODE); + let node_cmd = binding + .arg("--tmp") + .args(["--runtime", RUNTIME_PATH]) + .stderr(process::Stdio::piped()); + + ensure_node_process_works(node_cmd); + } +} diff --git a/substrate/bin/utils/dev-omni-node/src/rpc.rs b/substrate/bin/utils/dev-omni-node/src/rpc.rs new file mode 100644 index 0000000000000..2a7510cad669a --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/rpc.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A collection of node-specific RPC methods. +//! Substrate provides the `sc-rpc` crate, which defines the core RPC layer +//! used by Substrate nodes. This file extends those RPC definitions with +//! capabilities that are specific to this project's runtime configuration. + +#![warn(missing_docs)] + +use crate::standards::{AccountId, Nonce, OpaqueBlock}; +use jsonrpsee::RpcModule; +use sc_transaction_pool_api::TransactionPool; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; +use std::sync::Arc; +use substrate_frame_rpc_system::{System, SystemApiServer}; + +pub use sc_rpc_api::DenyUnsafe; + +/// Full client dependencies. +pub struct FullDeps { + /// The client instance to use. + pub client: Arc, + /// Transaction pool instance. + pub pool: Arc

, + /// Whether to deny unsafe calls + pub deny_unsafe: DenyUnsafe, +} + +/// Instantiate all full RPC extensions. +pub fn create_full( + deps: FullDeps, +) -> Result, Box> +where + C: Send + + Sync + + 'static + + sp_api::ProvideRuntimeApi + + HeaderBackend + + HeaderMetadata + + 'static, + C::Api: sp_block_builder::BlockBuilder, + C::Api: substrate_frame_rpc_system::AccountNonceApi, + P: TransactionPool + 'static, +{ + let mut module = RpcModule::new(()); + let FullDeps { client, pool, deny_unsafe } = deps; + + module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + + Ok(module) +} diff --git a/substrate/bin/utils/dev-omni-node/src/service.rs b/substrate/bin/utils/dev-omni-node/src/service.rs new file mode 100644 index 0000000000000..5a1e381745aa5 --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/service.rs @@ -0,0 +1,260 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{cli::Consensus, fake_runtime_api::RuntimeApi, standards::OpaqueBlock}; +use futures::FutureExt; +use sc_client_api::backend::Backend; +use sc_executor::WasmExecutor; +use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; +use sc_telemetry::{Telemetry, TelemetryWorker}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use std::sync::Arc; + +type HostFunctions = sp_io::SubstrateHostFunctions; + +pub(crate) type FullClient = + sc_service::TFullClient>; +type FullBackend = sc_service::TFullBackend; +type FullSelectChain = sc_consensus::LongestChain; + +/// Assembly of PartialComponents (enough to run chain ops subcommands) +pub type Service = sc_service::PartialComponents< + FullClient, + FullBackend, + FullSelectChain, + sc_consensus::DefaultImportQueue, + sc_transaction_pool::FullPool, + Option, +>; + +pub fn new_partial(config: &Configuration) -> Result { + let telemetry = config + .telemetry_endpoints + .clone() + .filter(|x| !x.is_empty()) + .map(|endpoints| -> Result<_, sc_telemetry::Error> { + let worker = TelemetryWorker::new(16)?; + let telemetry = worker.handle().new_telemetry(endpoints); + Ok((worker, telemetry)) + }) + .transpose()?; + + let executor = sc_service::new_wasm_executor(&config); + + let (client, backend, keystore_container, task_manager) = + sc_service::new_full_parts::( + config, + telemetry.as_ref().map(|(_, telemetry)| telemetry.handle()), + executor, + )?; + let client = Arc::new(client); + + let telemetry = telemetry.map(|(worker, telemetry)| { + task_manager.spawn_handle().spawn("telemetry", None, worker.run()); + telemetry + }); + + let select_chain = sc_consensus::LongestChain::new(backend.clone()); + + let transaction_pool = sc_transaction_pool::BasicPool::new_full( + config.transaction_pool.clone(), + config.role.is_authority().into(), + config.prometheus_registry(), + task_manager.spawn_essential_handle(), + client.clone(), + ); + + let import_queue = sc_consensus_manual_seal::import_queue( + Box::new(client.clone()), + &task_manager.spawn_essential_handle(), + config.prometheus_registry(), + ); + + Ok(sc_service::PartialComponents { + client, + backend, + task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: (telemetry), + }) +} + +/// Builds a new service for a full client. +pub fn new_full(config: Configuration, consensus: Consensus) -> Result { + let sc_service::PartialComponents { + client, + backend, + mut task_manager, + import_queue, + keystore_container, + select_chain, + transaction_pool, + other: mut telemetry, + } = new_partial(&config)?; + + use sc_network::NetworkBackend; + + let net_config = + sc_network::config::FullNetworkConfiguration::< + OpaqueBlock, + ::Hash, + sc_network::NetworkWorker, + >::new(&config.network, config.prometheus_config.as_ref().map(|cfg| cfg.registry.clone())); + + let metrics = sc_network::NetworkWorker::< + OpaqueBlock, + ::Hash, + >::register_notification_metrics( + config.prometheus_config.as_ref().map(|cfg| &cfg.registry) + ); + + let (network, system_rpc_tx, tx_handler_controller, network_starter, 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_params: None, + block_relay: None, + metrics, + })?; + + if config.offchain_worker.enabled { + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-worker", + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + is_validator: config.role.is_authority(), + keystore: Some(keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: Arc::new(network.clone()), + enable_http_requests: true, + custom_extensions: |_| vec![], + }) + .run(client.clone(), task_manager.spawn_handle()) + .boxed(), + ); + } + + let rpc_extensions_builder = { + let client = client.clone(); + let pool = transaction_pool.clone(); + + Box::new(move |deny_unsafe, _| { + let deps = + crate::rpc::FullDeps { client: client.clone(), pool: pool.clone(), deny_unsafe }; + crate::rpc::create_full(deps).map_err(Into::into) + }) + }; + + let prometheus_registry = config.prometheus_registry().cloned(); + + 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(), + })?; + + 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()), + ); + + match consensus { + Consensus::InstantSeal => { + let params = sc_consensus_manual_seal::InstantSealParams { + block_import: client.clone(), + env: proposer, + client, + pool: transaction_pool, + select_chain, + consensus_data_provider: None, + create_inherent_data_providers: move |_, ()| async move { + Ok(sp_timestamp::InherentDataProvider::from_system_time()) + }, + }; + + let authorship_future = sc_consensus_manual_seal::run_instant_seal(params); + + task_manager.spawn_essential_handle().spawn_blocking( + "instant-seal", + None, + authorship_future, + ); + }, + Consensus::ManualSeal(block_time) => { + let (mut sink, commands_stream) = futures::channel::mpsc::channel(1024); + task_manager.spawn_handle().spawn("block_authoring", None, async move { + loop { + futures_timer::Delay::new(std::time::Duration::from_millis(block_time)).await; + sink.try_send(sc_consensus_manual_seal::EngineCommand::SealNewBlock { + create_empty: true, + finalize: true, + parent_hash: None, + sender: None, + }) + .unwrap(); + } + }); + + let params = sc_consensus_manual_seal::ManualSealParams { + block_import: client.clone(), + env: proposer, + client, + pool: transaction_pool, + select_chain, + commands_stream: Box::pin(commands_stream), + consensus_data_provider: None, + create_inherent_data_providers: move |_, ()| async move { + Ok(sp_timestamp::InherentDataProvider::from_system_time()) + }, + }; + let authorship_future = sc_consensus_manual_seal::run_manual_seal(params); + + task_manager.spawn_essential_handle().spawn_blocking( + "manual-seal", + None, + authorship_future, + ); + }, + } + + network_starter.start_network(); + Ok(task_manager) +} diff --git a/substrate/bin/utils/dev-omni-node/src/standards.rs b/substrate/bin/utils/dev-omni-node/src/standards.rs new file mode 100644 index 0000000000000..4b940817163b2 --- /dev/null +++ b/substrate/bin/utils/dev-omni-node/src/standards.rs @@ -0,0 +1,42 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Hardcoded assumptions of this omni-node. +//! +//! Consensus: This template uses [`sc-manual-seal`] consensus and therefore has no expectation of +//! the runtime having any consensus-related pallets. The block time of the node can easily be +//! adjusted by [`crate::cli::Cli::consensus`] +//! +//! RPC: This node exposes only [`substrate_frame_rpc_system`] as additional RPC endpoints from the +//! runtime. + +use sp_runtime::{traits, MultiSignature}; + +/// The account id type that is expected to be used in `frame-system`. +pub type AccountId = + <::Signer as traits::IdentifyAccount>::AccountId; +/// The index type that is expected to be used in `frame-system`. +pub type Nonce = u32; +/// The block type that is expected to be used in `frame-system`. +pub type BlockNumber = u32; +/// The hash type that is expected to be used in `frame-system`. +pub type Hashing = sp_runtime::traits::BlakeTwo256; + +/// The hash type that is expected to be used in the runtime. +pub type Header = sp_runtime::generic::Header; +/// The opaque block type that is expected to be used in the runtime. +pub type OpaqueBlock = sp_runtime::generic::Block; diff --git a/substrate/client/chain-spec/src/chain_spec.rs b/substrate/client/chain-spec/src/chain_spec.rs index aa3c1ba3e6f13..fd5335ec77da7 100644 --- a/substrate/client/chain-spec/src/chain_spec.rs +++ b/substrate/client/chain-spec/src/chain_spec.rs @@ -47,6 +47,16 @@ enum GenesisBuildAction { NamedPreset(String, PhantomData), } +impl std::fmt::Debug for GenesisBuildAction { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Patch(_) => write!(f, "Patch)"), + Self::Full(_) => write!(f, "Full"), + Self::NamedPreset(ref p, _) => write!(f, "NamedPreset({:?})", p), + } + } +} + impl Clone for GenesisBuildAction { fn clone(&self) -> Self { match self { @@ -66,6 +76,17 @@ enum GenesisSource { GenesisBuilderApi(GenesisBuildAction, Vec), } +impl std::fmt::Debug for GenesisSource { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::File(ref path) => write!(f, "File({:?})", path), + Self::Binary(_) => write!(f, "Binary"), + Self::Storage(_) => write!(f, "Storage"), + Self::GenesisBuilderApi(_, _) => write!(f, "GenesisBuilderApi"), + } + } +} + impl Clone for GenesisSource { fn clone(&self) -> Self { match *self { @@ -415,6 +436,15 @@ impl ChainSpecBuilder { /// Builds a [`ChainSpec`] instance using the provided settings. pub fn build(self) -> ChainSpec { + // TODO: the logger is not yet init-ed in this code path, fix it. + log::info!(target: "chain-spec-builder", "👓 building chain-spec from runtime code:"); + log::info!(target: "chain-spec-builder", "👓 genesis: {:?}", self.genesis_build_action); + log::info!(target: "chain-spec-builder", "👓 properties: {:?}", self.properties); + log::info!(target: "chain-spec-builder", "👓 boot-nodes: {:?}", self.boot_nodes); + log::info!(target: "chain-spec-builder", "👓 chain-type: {:?}", self.chain_type); + log::info!(target: "chain-spec-builder", "👓 id: {:?}", self.id); + log::info!(target: "chain-spec-builder", "👓 name: {:?}", self.name); + let client_spec = ClientSpec { name: self.name, id: self.id, diff --git a/substrate/client/chain-spec/src/lib.rs b/substrate/client/chain-spec/src/lib.rs index c43f9e89b8a99..96d5b7ce19e20 100644 --- a/substrate/client/chain-spec/src/lib.rs +++ b/substrate/client/chain-spec/src/lib.rs @@ -406,7 +406,7 @@ pub trait ChainSpec: BuildStorage + Send + Sync { fn add_boot_node(&mut self, addr: MultiaddrWithPeerId); /// Return spec as JSON. fn as_json(&self, raw: bool) -> Result; - /// Return StorageBuilder for this spec. + /// Return `StorageBuilder` for this spec. fn as_storage_builder(&self) -> &dyn BuildStorage; /// Returns a cloned `Box`. fn cloned_box(&self) -> Box; diff --git a/substrate/client/cli/Cargo.toml b/substrate/client/cli/Cargo.toml index b7d29aebc3d77..b5a57d114f211 100644 --- a/substrate/client/cli/Cargo.toml +++ b/substrate/client/cli/Cargo.toml @@ -51,6 +51,8 @@ sp-keystore = { workspace = true, default-features = true } sp-panic-handler = { workspace = true, default-features = true } sp-runtime = { workspace = true, default-features = true } sp-version = { workspace = true, default-features = true } +sp-genesis-builder = { workspace = true, default-features = true } +sp-tracing = { workspace = true, default-features = true } [dev-dependencies] tempfile = { workspace = true } diff --git a/substrate/client/cli/src/config.rs b/substrate/client/cli/src/config.rs index 283148a6d6adb..5e988e1befa8e 100644 --- a/substrate/client/cli/src/config.rs +++ b/substrate/client/cli/src/config.rs @@ -466,6 +466,20 @@ pub trait CliConfiguration: Sized { Ok(true) } + /// The genesis preset to use when `--runtime` is being used. + fn genesis_preset(&self, is_dev: bool) -> Option<&str> { + self.shared_params().genesis_preset.as_ref().map_or_else( + || { + if is_dev { + Some(sp_genesis_builder::DEV_RUNTIME_PRESET.as_ref()) + } else { + None + } + }, + |p| Some(p.as_str()), + ) + } + /// Create a Configuration object from the current object fn create_configuration( &self, @@ -474,7 +488,17 @@ pub trait CliConfiguration: Sized { ) -> Result { let is_dev = self.is_dev()?; let chain_id = self.chain_id(is_dev)?; - let chain_spec = cli.load_spec(&chain_id)?; + debug_assert!( + self.shared_params().chain.is_some() ^ self.shared_params().runtime.is_some(), + "The CLI (clap) rules should ensure either `--chain` is set or `--runtime`" + ); + let chain_spec = if let Some(runtime_path) = self.shared_params().runtime.as_ref() { + let runtime = std::fs::read(runtime_path) + .map_err(|e| format!("Failed to read runtime: {}", e))?; + cli.load_spec_from_runtime(&runtime, self.genesis_preset(is_dev))? + } else { + cli.load_spec(&chain_id)? + }; let base_path = base_path_or_default(self.base_path()?, &C::executable_name()); let config_dir = build_config_dir(&base_path, chain_spec.id()); let net_config_dir = build_net_config_dir(&config_dir); diff --git a/substrate/client/cli/src/lib.rs b/substrate/client/cli/src/lib.rs index 1bb9fec0e2769..6bbad2ddb6ef9 100644 --- a/substrate/client/cli/src/lib.rs +++ b/substrate/client/cli/src/lib.rs @@ -89,9 +89,44 @@ pub trait SubstrateCli: Sized { /// Copyright starting year (x-current year) fn copyright_start_year() -> i32; - /// Chain spec factory + /// Build the chain spec of a chain, with the given "chain-id". + /// + /// This function is exposed to substrate-based-nodes so that they can specify their own special + /// chain-id -> chain-spec mappings. For example, a bitcoin chain might specify + /// + /// ``` + /// fn main() { + /// # let chain_id = "something"; + /// match chain_id + /// "bitcoin" => { todo!() } + /// "bitcoing-dev" => { todo!() } + /// "bitcoing-local" => { todo!() } + /// _ => { todo!("we interpret the chain-id as a path to a pre-generated chain-spec") } + /// } + /// } + /// ``` + /// + /// By convention, many substrate-based chains follow a similar pattern here, and expose `-dev` + /// and `-local` as special expected chain-ids. fn load_spec(&self, id: &str) -> std::result::Result, String>; + /// Construct a chain-spec on the fly, from the given runtime and preset name. + /// + /// This is the other counter-part of [`load_spec`], and can be used to create chain-specs from + /// a wam blob, and a given preset name. Not all fields of the chain-spec are configurable in + /// this mode. + /// + /// For now, this is only reasonably useful for testing and local development, when you don't + /// want the hassle of re-generating a chain-spec. For any production chain, using `--chain` and + /// a proper chain-spec is suggested. + fn load_spec_from_runtime( + &self, + _runtime: &[u8], + _preset: Option<&str>, + ) -> std::result::Result, String> { + panic!("🫵🏻 unsupported: This node does not support on-the-fly chain-spec generation. The code must be updated to provide an impl for `SubstrateCli::load_spec_from_runtime`.\n Try `--chain` instead.") + } + /// Helper function used to parse the command line arguments. This is the equivalent of /// [`clap::Parser::parse()`]. /// diff --git a/substrate/client/cli/src/params/shared_params.rs b/substrate/client/cli/src/params/shared_params.rs index 465372fba17d4..632e896151155 100644 --- a/substrate/client/cli/src/params/shared_params.rs +++ b/substrate/client/cli/src/params/shared_params.rs @@ -27,15 +27,32 @@ pub struct SharedParams { /// Specify the chain specification. /// /// It can be one of the predefined ones (dev, local, or staging) or it can be a path to - /// a file with the chainspec (such as one exported by the `build-spec` subcommand). - #[arg(long, value_name = "CHAIN_SPEC")] + /// a file with the chain-spec (such as one exported by the `build-spec` subcommand). + #[arg(long, value_name = "CHAIN_SPEC", conflicts_with_all = &["runtime", "genesis_preset"])] pub chain: Option, + /// Path to a `.wasm` runtime blob. + /// + /// This, possibly with `--genesis-preset`, can be used as an alternative to `--chain`. + #[arg(long, value_name = "RUNTIME")] + pub runtime: Option, + + /// A genesis preset to use. + /// + /// If not specified, the `Default` preset is used. If `--dev` is preset, it imp + /// + /// This, combined with `--runtime`, can be used as an alternative to `--chain`. + #[arg(long, value_name = "GENESIS_PRESET")] + pub genesis_preset: Option, + /// Specify the development chain. /// - /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, - /// `--alice`, and `--tmp` flags, unless explicitly overridden. - /// It also disables local peer discovery (see --no-mdns and --discover-local) + /// This flag sets `--force-authoring`, `--rpc-cors=all`, `--alice`, and `--tmp` flags, unless + /// explicitly overridden. It also disables local peer discovery (see --no-mdns and + /// --discover-local). + /// + /// If combined with `--runtime`, it sets `--genesis-preset=dev`. Otherwise, it implies + /// `--chain=dev`. #[arg(long, conflicts_with_all = &["chain"])] pub dev: bool, diff --git a/substrate/client/service/src/builder.rs b/substrate/client/service/src/builder.rs index 638f30fad10e0..164eef75db265 100644 --- a/substrate/client/service/src/builder.rs +++ b/substrate/client/service/src/builder.rs @@ -148,12 +148,15 @@ where { let backend = new_db_backend(config.db_config())?; + // println!("Creating genesis block builder"); + // println!("chain_spec {:?}", config.chain_spec); let genesis_block_builder = GenesisBlockBuilder::new( config.chain_spec.as_storage_builder(), !config.no_genesis(), backend.clone(), executor.clone(), - )?; + ) + .unwrap(); new_full_parts_with_genesis_builder( config, diff --git a/substrate/frame/Cargo.toml b/substrate/frame/Cargo.toml index 41ece6c9a27fe..833cf0c9b0b9a 100644 --- a/substrate/frame/Cargo.toml +++ b/substrate/frame/Cargo.toml @@ -46,6 +46,7 @@ sp-consensus-aura = { optional = true, workspace = true } sp-consensus-grandpa = { optional = true, workspace = true } sp-inherents = { optional = true, workspace = true } sp-storage = { optional = true, workspace = true } +sp-keyring = { optional = true, workspace = true } frame-executive = { optional = true, workspace = true } frame-system-rpc-runtime-api = { optional = true, workspace = true } @@ -77,6 +78,7 @@ runtime = [ "sp-storage", "sp-transaction-pool", "sp-version", + "sp-keyring", "frame-executive", "frame-system-rpc-runtime-api", diff --git a/substrate/frame/src/lib.rs b/substrate/frame/src/lib.rs index 3836e71cb00f2..916fece1d15e0 100644 --- a/substrate/frame/src/lib.rs +++ b/substrate/frame/src/lib.rs @@ -165,6 +165,11 @@ pub mod testing_prelude { /// All of the types and tools needed to build FRAME-based runtimes. #[cfg(any(feature = "runtime", feature = "std"))] pub mod runtime { + /// A set of testing accounts for the runtime. + /// + /// See [`sp_keyring`] for more information. + pub use sp_keyring::AccountKeyring; + /// The main prelude of `FRAME` for building runtimes. /// /// A runtime typically starts with: @@ -385,6 +390,8 @@ pub mod deps { #[cfg(feature = "runtime")] pub use sp_inherents; #[cfg(feature = "runtime")] + pub use sp_keyring; + #[cfg(feature = "runtime")] pub use sp_offchain; #[cfg(feature = "runtime")] pub use sp_storage; diff --git a/substrate/primitives/genesis-builder/src/lib.rs b/substrate/primitives/genesis-builder/src/lib.rs index 2cbac305b4d97..15216fb9d7542 100644 --- a/substrate/primitives/genesis-builder/src/lib.rs +++ b/substrate/primitives/genesis-builder/src/lib.rs @@ -62,6 +62,10 @@ pub type Result = core::result::Result<(), sp_runtime::RuntimeString>; /// The type representing preset ID. pub type PresetId = sp_runtime::RuntimeString; +/// The default 'development' preset used to communicate with the runtime via +/// [`sp_genesis_builder`]. +pub const DEV_RUNTIME_PRESET: &'static str = "development"; + sp_api::decl_runtime_apis! { /// API to interact with RuntimeGenesisConfig for the runtime pub trait GenesisBuilder { diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs index 305a9b960b986..928d57148f070 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -39,7 +39,7 @@ use sp_core::{ traits::{CallContext, CodeExecutor, ReadRuntimeVersionExt, WrappedRuntimeCode}, }; use sp_externalities::Extensions; -use sp_genesis_builder::{PresetId, Result as GenesisBuildResult}; +use sp_genesis_builder::{PresetId, Result as GenesisBuildResult, DEV_RUNTIME_PRESET}; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::traits::Hash; use sp_state_machine::{OverlayedChanges, StateMachine}; @@ -160,9 +160,6 @@ generate the genesis state is deprecated. Please remove the `--chain`/`--dev`/`- point `--runtime` to your runtime blob and set `--genesis-builder=runtime`. This warning may \ become a hard error any time after December 2024."; -/// The preset that we expect to find in the GenesisBuilder runtime API. -const GENESIS_PRESET: &str = "development"; - impl PalletCmd { /// Runs the command and benchmarks a pallet. #[deprecated( @@ -574,7 +571,7 @@ impl PalletCmd { &self, chain_spec: &Option>, ) -> Result<(sp_storage::Storage, OverlayedChanges)> { - Ok(match (self.genesis_builder, self.runtime.is_some()) { + Ok(match (self.genesis_builder, self.shared_params().runtime.is_some()) { (Some(GenesisBuilder::None), _) => Default::default(), (Some(GenesisBuilder::Spec), _) | (None, false) => { log::warn!("{WARN_SPEC_GENESIS_CTOR}"); @@ -646,7 +643,7 @@ impl PalletCmd { &mut Default::default(), &executor, "GenesisBuilder_get_preset", - &Some::(GENESIS_PRESET.into()).encode(), // Use the default preset + &self.genesis_preset(false).encode(), &mut Self::build_extensions(executor.clone()), &runtime_code, CallContext::Offchain, @@ -664,7 +661,7 @@ impl PalletCmd { json_merge(&mut genesis_json, dev); } else { log::warn!( - "Could not find genesis preset '{GENESIS_PRESET}'. Falling back to default." + "Could not find genesis preset '{DEV_RUNTIME_PRESET}'. Falling back to default." ); } @@ -728,7 +725,7 @@ impl PalletCmd { &self, state: &'a BenchmarkingState, ) -> Result, H>> { - if let Some(runtime) = &self.runtime { + if let Some(runtime) = &self.shared_params().runtime { log::info!("Loading WASM from {}", runtime.display()); let code = fs::read(runtime)?; let hash = sp_core::blake2_256(&code).to_vec(); @@ -975,8 +972,10 @@ impl PalletCmd { /// Sanity check the CLI arguments. fn check_args(&self) -> Result<()> { - if self.runtime.is_some() && self.shared_params.chain.is_some() { - unreachable!("Clap should not allow both `--runtime` and `--chain` to be provided.") + if (self.shared_params().runtime.is_some() || self.shared_params().genesis_preset.is_some()) && + self.shared_params.chain.is_some() + { + unreachable!("Clap should not allow both (`--runtime` or `--genesis-preset`) and `--chain` to be provided.") } if let Some(output_path) = &self.output { diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs index d05b52f1ac870..308cb4e39a7d3 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -167,10 +167,6 @@ pub struct PalletCmd { )] pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, - /// Optional runtime blob to use instead of the one from the genesis config. - #[arg(long, conflicts_with = "chain")] - pub runtime: Option, - /// Do not fail if there are unknown but also unused host functions in the runtime. #[arg(long)] pub allow_missing_host_functions: bool, diff --git a/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs b/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs index 2bb00d66560f3..2fde0255174ac 100644 --- a/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs +++ b/substrate/utils/frame/benchmarking-cli/src/pallet/types.rs @@ -31,6 +31,8 @@ pub enum GenesisBuilder { /// state. However, to keep backwards compatibility, this is not the default. None, /// Let the runtime build the genesis state through its `BuildGenesisConfig` runtime API. + /// + /// This uses [`sp_genesis_builder::DEV_RUNTIME_PRESET`] preset. Runtime, /// Use the spec file to build the genesis state. This fails when there is no spec. Spec, diff --git a/templates/minimal/node/src/chain_spec.rs b/templates/minimal/node/src/chain_spec.rs index 5b53b0f80ac00..dc4451b5a85ab 100644 --- a/templates/minimal/node/src/chain_spec.rs +++ b/templates/minimal/node/src/chain_spec.rs @@ -43,7 +43,7 @@ pub fn development_config() -> Result { /// Configure initial storage state for FRAME pallets. fn testnet_genesis() -> Value { use frame::traits::Get; - use minimal_template_runtime::interface::{Balance, MinimumBalance}; + use minimal_template_runtime::{Balance, MinimumBalance}; let endowment = >::get().max(1) * 1000; let balances = AccountKeyring::iter() .map(|a| (a.to_account_id(), endowment)) diff --git a/templates/minimal/node/src/command.rs b/templates/minimal/node/src/command.rs index c17f9bc55927b..d57153a1988db 100644 --- a/templates/minimal/node/src/command.rs +++ b/templates/minimal/node/src/command.rs @@ -114,9 +114,7 @@ pub fn run() -> sc_cli::Result<()> { }, Some(Subcommand::ChainInfo(cmd)) => { let runner = cli.create_runner(cmd)?; - runner.sync_run(|config| { - cmd.run::(&config) - }) + runner.sync_run(|config| cmd.run::(&config)) }, None => { let runner = cli.create_runner(&cli.run)?; diff --git a/templates/minimal/node/src/rpc.rs b/templates/minimal/node/src/rpc.rs index 451e7b21dd0c1..cf2ed6aea2baa 100644 --- a/templates/minimal/node/src/rpc.rs +++ b/templates/minimal/node/src/rpc.rs @@ -23,7 +23,7 @@ #![warn(missing_docs)] use jsonrpsee::RpcModule; -use minimal_template_runtime::interface::{AccountId, Nonce, OpaqueBlock}; +use minimal_template_runtime::{AccountId, Nonce, OpaqueBlock}; use sc_transaction_pool_api::TransactionPool; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use std::sync::Arc; diff --git a/templates/minimal/node/src/service.rs b/templates/minimal/node/src/service.rs index e298d3dd6dc1f..072c85cda7adb 100644 --- a/templates/minimal/node/src/service.rs +++ b/templates/minimal/node/src/service.rs @@ -16,7 +16,7 @@ // limitations under the License. use futures::FutureExt; -use minimal_template_runtime::{interface::OpaqueBlock as Block, RuntimeApi}; +use minimal_template_runtime::{OpaqueBlock as Block, RuntimeApi}; use sc_client_api::backend::Backend; use sc_executor::WasmExecutor; use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; diff --git a/templates/minimal/runtime/Cargo.toml b/templates/minimal/runtime/Cargo.toml index 5d3cf8492e522..b981603812645 100644 --- a/templates/minimal/runtime/Cargo.toml +++ b/templates/minimal/runtime/Cargo.toml @@ -12,6 +12,7 @@ publish = false [dependencies] codec = { workspace = true } scale-info = { workspace = true } +serde_json = { features = ["alloc"], workspace = true } # this is a frame-based runtime, thus importing `frame` with runtime feature enabled. frame = { features = [ @@ -41,6 +42,7 @@ default = ["std"] std = [ "codec/std", "scale-info/std", + "serde_json/std", "frame/std", diff --git a/templates/minimal/runtime/src/lib.rs b/templates/minimal/runtime/src/lib.rs index 08ad537ecdd14..98c372cffd6e1 100644 --- a/templates/minimal/runtime/src/lib.rs +++ b/templates/minimal/runtime/src/lib.rs @@ -24,7 +24,6 @@ include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); extern crate alloc; - use alloc::{vec, vec::Vec}; use frame::{ deps::frame_support::{ @@ -42,7 +41,37 @@ use frame::{ }, }; -/// The runtime version. +pub type Block = frame::runtime::types_common::BlockOf; +pub type Header = HeaderFor; +pub type OpaqueBlock = frame::runtime::types_common::OpaqueBlock; +pub type AccountId = ::AccountId; +pub type Nonce = ::Nonce; +pub type Hash = ::Hash; +pub type Balance = ::Balance; +pub type MinimumBalance = ::ExistentialDeposit; + +/// Genesis presets to use. +pub mod genesis_config_presets { + use super::*; + + // TODO local tests for this, can be automated by a lot. + + /// The `dev` preset. + pub fn dev() -> serde_json::Value { + let endowment = 1_000_000_000_000_000u128; + let balances = frame::runtime::AccountKeyring::iter() + .map(|acc| (acc.to_account_id(), endowment)) + .collect::>(); + let sudo = frame::runtime::AccountKeyring::Alice.to_account_id(); + serde_json::json!({ + "balances": { "balances": balances}, + "sudo": { "key": Some(sudo) } + // un-specified pallets will use `Default`. + }) + } +} + +/// The runtime version #[runtime_version] pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("minimal-template-runtime"), @@ -165,9 +194,6 @@ impl pallet_transaction_payment::Config for Runtime { // Implements the types required for the template pallet. impl pallet_minimal_template::Config for Runtime {} -type Block = frame::runtime::types_common::BlockOf; -type Header = HeaderFor; - type RuntimeExecutive = Executive, Runtime, AllPalletsWithSystem>; @@ -250,26 +276,26 @@ impl_runtime_apis! { } } - impl apis::AccountNonceApi for Runtime { - fn account_nonce(account: interface::AccountId) -> interface::Nonce { + impl apis::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { System::account_nonce(account) } } impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi< Block, - interface::Balance, + Balance, > for Runtime { - fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { + fn query_info(uxt: ExtrinsicFor, len: u32) -> RuntimeDispatchInfo { TransactionPayment::query_info(uxt, len) } - fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { + fn query_fee_details(uxt: ExtrinsicFor, len: u32) -> FeeDetails { TransactionPayment::query_fee_details(uxt, len) } - fn query_weight_to_fee(weight: Weight) -> interface::Balance { + fn query_weight_to_fee(weight: Weight) -> Balance { TransactionPayment::weight_to_fee(weight) } - fn query_length_to_fee(length: u32) -> interface::Balance { + fn query_length_to_fee(length: u32) -> Balance { TransactionPayment::length_to_fee(length) } } @@ -280,29 +306,31 @@ impl_runtime_apis! { } fn get_preset(id: &Option) -> Option> { - get_preset::(id, |_| None) + Self::do_get_preset(id) } fn preset_names() -> Vec { - vec![] + Self::do_preset_names() } } } -/// Some re-exports that the node side code needs to know. Some are useful in this context as well. -/// -/// Other types should preferably be private. -// TODO: this should be standardized in some way, see: -// https://github.com/paritytech/substrate/issues/10579#issuecomment-1600537558 -pub mod interface { - use super::Runtime; - use frame::deps::frame_system; - - pub type Block = super::Block; - pub use frame::runtime::types_common::OpaqueBlock; - pub type AccountId = ::AccountId; - pub type Nonce = ::Nonce; - pub type Hash = ::Hash; - pub type Balance = ::Balance; - pub type MinimumBalance = ::ExistentialDeposit; +impl Runtime { + fn do_preset_names() -> Vec { + vec!["dev".into()] + } + + fn do_get_preset(id: &Option) -> Option> { + get_preset::(id, |preset| { + let patch = match preset.try_into() { + Ok(sp_genesis_builder::DEV_RUNTIME_PRESET) => genesis_config_presets::dev(), + _ => return None, + }; + Some( + serde_json::to_string(&patch) + .expect("serialization to json is expected to work. qed.") + .into_bytes(), + ) + }) + } }