referrerpolicy=no-referrer-when-downgrade

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