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//!
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` (`sp_genesis_config::DEVELOPMENT`) preset.
53//!
54//! Note that we intend to run this chain-spec with `polkadot-omni-node`, which is tailored for
55//! running parachains. 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 polkadot_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 `polkadot-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//! polkadot-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//! > [`sc_cli::commands::PurgeChainCmd`] and [`sc_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`]: (polkadot_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 rand::Rng;
107	use sc_chain_spec::{DEV_RUNTIME_PRESET, LOCAL_TESTNET_RUNTIME_PRESET};
108	use sp_genesis_builder::PresetId;
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 = "parachain-template-runtime";
117	const CHAIN_SPEC_BUILDER: &'static str = "chain-spec-builder";
118	const OMNI_NODE: &'static str = "polkadot-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 parachain-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("staging-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 polkadot-omni-node...");
195			Command::new("cargo")
196				.arg("build")
197				.arg("--release")
198				.arg("-p")
199				.arg("polkadot-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		sp_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_parachain_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_parachain_spec_works();
306	}
307
308	#[tokio::test]
309	async fn parachain_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 parachain'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("parachain")
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	// `cumulus_primitives_core::GetParachainInfo`.
336	async fn omni_node_dev_mode_works_without_getparachaininfo() {
337		let dev_chain_spec = std::env::current_dir()
338			.unwrap()
339			.join("src/guides/parachain_without_getparachaininfo.json");
340		omni_node_test_setup(dev_chain_spec).await;
341	}
342}