#
pezkuwi_sdk_docs/guides/
your_first_node.rs

    1//! # Your first Node
    2//!
    3//! In this guide, you will learn how to run a runtime, such as the one created in
    4//! [`your_first_runtime`], in a node. Within the context of this guide, we will focus on running
    5//! the runtime with an [`omni-node`]. Please first read this page to learn about the OmniNode, and
    6//! other options when it comes to running a node.
    7//!
    8//! [`your_first_runtime`] is a runtime with no consensus related code, and therefore can only be
    9//! executed with a node that also expects no consensus ([`pezsc_consensus_manual_seal`]).
    10//! `pezkuwi-omni-node`'s [`--dev-block-time`] precisely does this.
    11//!
    12//! > All of the following steps are coded as unit tests of this module. Please see `Source` of the
    13//! > page for more information.
    14//!
    15//! ## Running The Omni Node
    16//!
    17//! ### Installs
    18//!
    19//! The `pezkuwi-omni-node` can either be downloaded from the latest [Release](https://github.com/pezkuwichain/pezkuwi-sdk/releases/) of `pezkuwi-sdk`,
    20//! or installed using `cargo`:
    21//!
    22//! ```text
    23//! cargo install pezkuwi-omni-node
    24//! ```
    25//!
    26//! Next, we need to install the `chain-spec-builder`. This is the tool that allows us to build
    27//! chain-specifications, through interacting with the genesis related APIs of the runtime, as
    28//! described in [`crate::guides::your_first_runtime#genesis-configuration`].
    29//!
    30//! ```text
    31//! cargo install pezstaging-chain-spec-builder
    32//! ```
    33//!
    34//! > The name of the crate is prefixed with `staging` as the crate name `chain-spec-builder` on
    35//! > crates.io is already taken and is not controlled by `pezkuwi-sdk` developers.
    36//!
    37//! ### Building Runtime
    38//!
    39//! Next, we need to build the corresponding runtime that we wish to interact with.
    40//!
    41//! ```text
    42//! cargo build --release -p path-to-runtime
    43//! ```
    44//! Equivalent code in tests:
    45#![doc = docify::embed!("./src/guides/your_first_runtime.rs", build_runtime)]
    46//!
    47//! This creates the wasm file under `./target/{release}/wbuild/release` directory.
    48//!
    49//! ### Building Chain Spec
    50//!
    51//! Next, we can generate the corresponding chain-spec file. For this example, we will use the
    52//! `development` (`pezsp_genesis_config::DEVELOPMENT`) preset.
    53//!
    54//! Note that we intend to run this chain-spec with `pezkuwi-omni-node`, which is tailored for
    55//! running teyrchains. This requires the chain-spec to always contain the `para_id` and a
    56//! `relay_chain` fields, which are provided below as CLI arguments.
    57//!
    58//! ```text
    59//! chain-spec-builder \
    60//! 	-c <path-to-output> \
    61//! 	create \
    62//! 	--relay-chain dontcare \
    63//! 	--runtime pezkuwi_sdk_docs_first_runtime.wasm \
    64//! 	named-preset development
    65//! ```
    66//!
    67//! Equivalent code in tests:
    68#![doc = docify::embed!("./src/guides/your_first_node.rs", csb)]
    69//!
    70//!
    71//! ### Running `pezkuwi-omni-node`
    72//!
    73//! Finally, we can run the node with the generated chain-spec file. We can also specify the block
    74//! time using the `--dev-block-time` flag.
    75//!
    76//! ```text
    77//! pezkuwi-omni-node \
    78//! 	--tmp \
    79//! 	--dev-block-time 1000 \
    80//! 	--chain <chain_spec_file>.json
    81//! ```
    82//!
    83//! > Note that we always prefer to use `--tmp` for testing, as it will save the chain state to a
    84//! > temporary folder, allowing the chain-to be easily restarted without `purge-chain`. See
    85//! > [`pezsc_cli::commands::PurgeChainCmd`] and [`pezsc_cli::commands::RunCmd::tmp`] for more info.
    86//!
    87//! This will start the node and import the blocks. Note while using `--dev-block-time`, the node
    88//! will use the testing-specific manual-seal consensus. This is an efficient way to test the
    89//! application logic of your runtime, without needing to yet care about consensus, block
    90//! production, relay-chain and so on.
    91//!
    92//! ### Next Steps
    93//!
    94//! * See the rest of the steps in [`crate::reference_docs::omni_node#user-journey`].
    95//!
    96//! [`runtime`]: crate::reference_docs::glossary#runtime
    97//! [`node`]: crate::reference_docs::glossary#node
    98//! [`build_config`]: first_runtime::Runtime#method.build_config
    99//! [`omni-node`]: crate::reference_docs::omni_node
    100//! [`--dev-block-time`]: (pezkuwi_omni_node_lib::cli::Cli::dev_block_time)
    101
    102#[cfg(test)]
    103mod tests {
    104	use assert_cmd::assert::OutputAssertExt;
    105	use cmd_lib::*;
    106	use pezsc_chain_spec::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET};
    107	use pezsp_genesis_builder::PresetId;
    108	use rand::Rng;
    109	use std::{
    110		io::{BufRead, BufReader},
    111		path::PathBuf,
    112		process::{ChildStderr, Command, Stdio},
    113		time::Duration,
    114	};
    115
    116	const PARA_RUNTIME: &'static str = "teyrchain-template-runtime";
    117	const CHAIN_SPEC_BUILDER: &'static str = "chain-spec-builder";
    118	const OMNI_NODE: &'static str = "pezkuwi-omni-node";
    119
    120	fn cargo() -> Command {
    121		Command::new(std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()))
    122	}
    123
    124	fn get_target_directory() -> Option<PathBuf> {
    125		let output = cargo().arg("metadata").arg("--format-version=1").output().ok()?;
    126
    127		if !output.status.success() {
    128			return None;
    129		}
    130
    131		let metadata: serde_json::Value = serde_json::from_slice(&output.stdout).ok()?;
    132		let target_directory = metadata["target_directory"].as_str()?;
    133
    134		Some(PathBuf::from(target_directory))
    135	}
    136
    137	fn find_release_binary(name: &str) -> Option<PathBuf> {
    138		let target_dir = get_target_directory()?;
    139		let release_path = target_dir.join("release").join(name);
    140
    141		if release_path.exists() {
    142			Some(release_path)
    143		} else {
    144			None
    145		}
    146	}
    147
    148	fn find_wasm(runtime_name: &str) -> Option<PathBuf> {
    149		let target_dir = get_target_directory()?;
    150		let wasm_path = target_dir
    151			.join("release")
    152			.join("wbuild")
    153			.join(runtime_name)
    154			.join(format!("{}.wasm", runtime_name.replace('-', "_")));
    155
    156		if wasm_path.exists() {
    157			Some(wasm_path)
    158		} else {
    159			None
    160		}
    161	}
    162
    163	fn maybe_build_runtimes() {
    164		if find_wasm(&PARA_RUNTIME).is_none() {
    165			println!("Building teyrchain-template-runtime...");
    166			Command::new("cargo")
    167				.arg("build")
    168				.arg("--release")
    169				.arg("-p")
    170				.arg(PARA_RUNTIME)
    171				.assert()
    172				.success();
    173		}
    174
    175		assert!(find_wasm(PARA_RUNTIME).is_some());
    176	}
    177
    178	fn maybe_build_chain_spec_builder() {
    179		if find_release_binary(CHAIN_SPEC_BUILDER).is_none() {
    180			println!("Building chain-spec-builder...");
    181			Command::new("cargo")
    182				.arg("build")
    183				.arg("--release")
    184				.arg("-p")
    185				.arg("pezstaging-chain-spec-builder")
    186				.assert()
    187				.success();
    188		}
    189		assert!(find_release_binary(CHAIN_SPEC_BUILDER).is_some());
    190	}
    191
    192	fn maybe_build_omni_node() {
    193		if find_release_binary(OMNI_NODE).is_none() {
    194			println!("Building pezkuwi-omni-node...");
    195			Command::new("cargo")
    196				.arg("build")
    197				.arg("--release")
    198				.arg("-p")
    199				.arg("pezkuwi-omni-node")
    200				.assert()
    201				.success();
    202		}
    203	}
    204
    205	async fn imported_block_found(stderr: ChildStderr, block: u64, timeout: u64) -> bool {
    206		tokio::time::timeout(Duration::from_secs(timeout), async {
    207			let want = format!("Imported #{}", block);
    208			let reader = BufReader::new(stderr);
    209			let mut found_block = false;
    210			for line in reader.lines() {
    211				if line.unwrap().contains(&want) {
    212					found_block = true;
    213					break;
    214				}
    215			}
    216			found_block
    217		})
    218		.await
    219		.unwrap()
    220	}
    221
    222	async fn test_runtime_preset(
    223		runtime: &'static str,
    224		block_time: u64,
    225		maybe_preset: Option<PresetId>,
    226	) {
    227		pezsp_tracing::try_init_simple();
    228		maybe_build_runtimes();
    229		maybe_build_chain_spec_builder();
    230		maybe_build_omni_node();
    231
    232		let chain_spec_builder =
    233			find_release_binary(&CHAIN_SPEC_BUILDER).expect("we built it above; qed");
    234		let omni_node = find_release_binary(OMNI_NODE).expect("we built it above; qed");
    235		let runtime_path = find_wasm(runtime).expect("we built it above; qed");
    236
    237		let random_seed: u32 = rand::thread_rng().gen();
    238		let chain_spec_file = std::env::current_dir()
    239			.unwrap()
    240			.join(format!("{}_{}_{}.json", runtime, block_time, random_seed));
    241
    242		Command::new(chain_spec_builder)
    243			.args(["-c", chain_spec_file.to_str().unwrap()])
    244			.arg("create")
    245			.args(["--relay-chain", "dontcare"])
    246			.args(["-r", runtime_path.to_str().unwrap()])
    247			.args(match maybe_preset {
    248				Some(preset) => vec!["named-preset".to_string(), preset.to_string()],
    249				None => vec!["default".to_string()],
    250			})
    251			.assert()
    252			.success();
    253
    254		let mut child = Command::new(omni_node)
    255			.arg("--tmp")
    256			.args(["--chain", chain_spec_file.to_str().unwrap()])
    257			.args(["--dev-block-time", block_time.to_string().as_str()])
    258			.stderr(Stdio::piped())
    259			.spawn()
    260			.unwrap();
    261
    262		// Take stderr and parse it with timeout.
    263		let stderr = child.stderr.take().unwrap();
    264		let expected_blocks = (10_000 / block_time).saturating_div(2);
    265		assert!(expected_blocks > 0, "test configuration is bad, should give it more time");
    266		assert_eq!(imported_block_found(stderr, expected_blocks, 100).await, true);
    267		std::fs::remove_file(chain_spec_file).unwrap();
    268		child.kill().unwrap();
    269	}
    270
    271	// Sets up omni-node to run a text exercise based on a chain spec.
    272	async fn omni_node_test_setup(chain_spec_path: PathBuf) {
    273		maybe_build_omni_node();
    274		let omni_node = find_release_binary(OMNI_NODE).unwrap();
    275
    276		let mut child = Command::new(omni_node)
    277			.arg("--dev")
    278			.args(["--chain", chain_spec_path.to_str().unwrap()])
    279			.stderr(Stdio::piped())
    280			.spawn()
    281			.unwrap();
    282
    283		let stderr = child.stderr.take().unwrap();
    284		assert_eq!(imported_block_found(stderr, 7, 100).await, true);
    285		child.kill().unwrap();
    286	}
    287
    288	#[tokio::test]
    289	async fn works_with_different_block_times() {
    290		test_runtime_preset(PARA_RUNTIME, 100, Some(DEV_RUNTIME_PRESET.into())).await;
    291		test_runtime_preset(PARA_RUNTIME, 3000, Some(DEV_RUNTIME_PRESET.into())).await;
    292
    293		// we need this snippet just for docs
    294		#[docify::export_content(csb)]
    295		fn build_teyrchain_spec_works() {
    296			let chain_spec_builder = find_release_binary(&CHAIN_SPEC_BUILDER).unwrap();
    297			let runtime_path = find_wasm(PARA_RUNTIME).unwrap();
    298			let output = "/tmp/demo-chain-spec.json";
    299			let runtime_str = runtime_path.to_str().unwrap();
    300			run_cmd!(
    301				$chain_spec_builder -c $output create --relay-chain dontcare -r $runtime_str named-preset development
    302			).expect("Failed to run command");
    303			std::fs::remove_file(output).unwrap();
    304		}
    305		build_teyrchain_spec_works();
    306	}
    307
    308	#[tokio::test]
    309	async fn teyrchain_runtime_works() {
    310		// TODO: None doesn't work. But maybe it should? it would be misleading as many users might
    311		// use it.
    312		for preset in [Some(DEV_RUNTIME_PRESET.into()), Some(LOCAL_TESTNET_RUNTIME_PRESET.into())] {
    313			test_runtime_preset(PARA_RUNTIME, 1000, preset).await;
    314		}
    315	}
    316
    317	#[tokio::test]
    318	async fn omni_node_dev_mode_works() {
    319		//Omni Node in dev mode works with teyrchain's template `dev_chain_spec`
    320		let dev_chain_spec = std::env::current_dir()
    321			.unwrap()
    322			.parent()
    323			.unwrap()
    324			.parent()
    325			.unwrap()
    326			.join("templates")
    327			.join("teyrchain")
    328			.join("dev_chain_spec.json");
    329		omni_node_test_setup(dev_chain_spec).await;
    330	}
    331
    332	#[tokio::test]
    333	// This is a regresion test so that we still remain compatible with runtimes that use
    334	// `para-id` in chain specs, instead of implementing the
    335	// `pezcumulus_primitives_core::GetTeyrchainInfo`.
    336	async fn omni_node_dev_mode_works_without_getteyrchaininfo() {
    337		let dev_chain_spec = std::env::current_dir()
    338			.unwrap()
    339			.join("src/guides/teyrchain_without_getteyrchaininfo.json");
    340		omni_node_test_setup(dev_chain_spec).await;
    341	}
    342}