partner_chains_cli/start_node/
mod.rs

1use crate::config::config_fields::{NODE_P2P_PORT, POSTGRES_CONNECTION_STRING};
2use crate::config::{CHAIN_CONFIG_FILE_PATH, CHAIN_SPEC_PATH, CardanoParameters};
3use crate::generate_keys::network_key_path;
4use crate::io::IOContext;
5use crate::keystore::*;
6use crate::{config::config_fields, *};
7use serde::Deserialize;
8
9#[cfg(test)]
10mod tests;
11
12#[derive(Clone, Debug, clap::Parser)]
13pub struct StartNodeCmd {
14	#[arg(long)]
15	silent: bool,
16}
17
18pub struct StartNodeConfig {
19	pub substrate_node_base_path: String,
20}
21
22impl StartNodeConfig {
23	pub fn load<C: IOContext>(context: &C) -> Self {
24		Self {
25			substrate_node_base_path: config_fields::SUBSTRATE_NODE_DATA_BASE_PATH
26				.load_or_prompt_and_save(context),
27		}
28	}
29	pub fn keystore_path(&self) -> String {
30		keystore_path(&self.substrate_node_base_path)
31	}
32}
33
34#[derive(Deserialize)]
35pub struct StartNodeChainConfig {
36	pub cardano: CardanoParameters,
37	pub bootnodes: Vec<String>,
38}
39
40impl CmdRun for StartNodeCmd {
41	fn run<C: IOContext>(&self, context: &C) -> anyhow::Result<()> {
42		let config = StartNodeConfig::load(context);
43
44		if !check_keystore(&config, context)? || !check_chain_spec(context) {
45			return Ok(());
46		}
47
48		let db_connection_string = POSTGRES_CONNECTION_STRING.load_or_prompt_and_save(context);
49
50		let Some(chain_config) = load_chain_config(context)? else { return Ok(()) };
51
52		if !self.silent
53			&& !prompt_values_fine(&config, &chain_config, &db_connection_string, context)
54		{
55			context.eprint("Aborting. Edit configuration files and rerun the command.");
56			return Ok(());
57		}
58
59		start_node(config, chain_config, &db_connection_string, context)?;
60
61		Ok(())
62	}
63}
64
65fn load_chain_config<C: IOContext>(context: &C) -> anyhow::Result<Option<StartNodeChainConfig>> {
66	let Some(chain_config_file) = context.read_file(CHAIN_CONFIG_FILE_PATH) else {
67		context.eprint(&format!("⚠️ Chain config file {CHAIN_CONFIG_FILE_PATH} does not exists. Run prepare-configuration wizard first."));
68		return Ok(None);
69	};
70	let chain_config = match serde_json::from_str::<StartNodeChainConfig>(&chain_config_file) {
71		Ok(chain_config) => chain_config,
72		Err(err) => {
73			context.eprint(&format!("⚠️ Chain config file {CHAIN_CONFIG_FILE_PATH} is invalid: {err}. Run prepare-configuration wizard or fix errors manually."));
74			return Ok(None);
75		},
76	};
77	Ok(Some(chain_config))
78}
79
80#[rustfmt::skip]
81fn prompt_values_fine<C: IOContext>(
82	StartNodeConfig { substrate_node_base_path }: &StartNodeConfig,
83	StartNodeChainConfig {
84		cardano,
85		bootnodes,
86	}: &StartNodeChainConfig,
87	db_connection_string: &str,
88	context: &C,
89) -> bool
90{
91	context.eprint("The following values will be used to run the node:");
92	context.eprint(&format!("    base path  = {}", substrate_node_base_path));
93	context.eprint(&format!("    chain spec = {}", CHAIN_SPEC_PATH));
94	context.eprint(&format!("    bootnodes  = [{}]", bootnodes.join(", ")));
95	context.eprint("    environment:");
96	context.eprint(&format!("        BLOCK_STABILITY_MARGIN             = {}", 0));
97	context.eprint(&format!("        CARDANO_SECURITY_PARAMETER         = {}", cardano.security_parameter));
98	context.eprint(&format!("        CARDANO_ACTIVE_SLOTS_COEFF         = {}", cardano.active_slots_coeff));
99	context.eprint(&format!("        FIRST_EPOCH_TIMESTAMP_MILLIS       = {}", cardano.first_epoch_timestamp_millis));
100	context.eprint(&format!("        EPOCH_DURATION_MILLIS              = {}", cardano.epoch_duration_millis));
101	context.eprint(&format!("        FIRST_EPOCH_NUMBER                 = {}", cardano.first_epoch_number));
102	context.eprint(&format!("        FIRST_SLOT_NUMBER                  = {}", cardano.first_slot_number));
103	context.eprint(&format!("        DB_SYNC_POSTGRES_CONNECTION_STRING = {}", db_connection_string));
104	context.prompt_yes_no("Proceed?", true)
105}
106
107fn check_chain_spec<C: IOContext>(context: &C) -> bool {
108	if context.file_exists(CHAIN_SPEC_PATH) {
109		true
110	} else {
111		context.eprint(&format!("Chain spec file {} missing.", CHAIN_SPEC_PATH));
112		context.eprint(
113			"Please run the create-chain-spec wizard first or you can get it from your chain governance.",
114		);
115		false
116	}
117}
118
119fn check_keystore<C: IOContext>(config: &StartNodeConfig, context: &C) -> anyhow::Result<bool> {
120	let existing_keys = context.list_directory(&config.keystore_path())?.unwrap_or_default();
121	Ok(key_present(&AURA, &existing_keys, context)
122		&& key_present(&GRANDPA, &existing_keys, context)
123		&& key_present(&CROSS_CHAIN, &existing_keys, context))
124}
125
126fn key_present<C: IOContext>(key: &KeyDefinition, existing_keys: &[String], context: &C) -> bool {
127	if find_existing_key(existing_keys, key).is_none() {
128		context.eprint(&format!(
129			"⚠️ {} key is missing from the keystore. Please run generate-keys wizard first.",
130			key.name
131		));
132		false
133	} else {
134		true
135	}
136}
137
138pub fn start_node<C: IOContext>(
139	StartNodeConfig { substrate_node_base_path }: StartNodeConfig,
140	StartNodeChainConfig {
141		cardano:
142			CardanoParameters {
143				security_parameter,
144				active_slots_coeff,
145				first_epoch_number,
146				first_slot_number,
147				epoch_duration_millis,
148				first_epoch_timestamp_millis,
149				slot_duration_millis,
150			},
151		bootnodes,
152	}: StartNodeChainConfig,
153	db_connection_string: &str,
154	context: &C,
155) -> anyhow::Result<()> {
156	let executable = context.current_executable()?;
157	let environment_prefix = format!(
158		"CARDANO_SECURITY_PARAMETER='{security_parameter}' \\
159         CARDANO_ACTIVE_SLOTS_COEFF='{active_slots_coeff}' \\
160         DB_SYNC_POSTGRES_CONNECTION_STRING='{db_connection_string}' \\
161         MC__FIRST_EPOCH_TIMESTAMP_MILLIS='{first_epoch_timestamp_millis}' \\
162         MC__EPOCH_DURATION_MILLIS='{epoch_duration_millis}' \\
163         MC__SLOT_DURATION_MILLIS='{slot_duration_millis}' \\
164         MC__FIRST_EPOCH_NUMBER='{first_epoch_number}' \\
165         MC__FIRST_SLOT_NUMBER='{first_slot_number}' \\
166         BLOCK_STABILITY_MARGIN='0' \\
167"
168	);
169	let bootnodes = bootnodes
170		.iter()
171		.map(|bootnode| format!("--bootnodes {}", bootnode))
172		.collect::<Vec<String>>()
173		.join(" ");
174
175	let ws_port = NODE_P2P_PORT.save_if_empty(
176		NODE_P2P_PORT
177			.default
178			.expect("Default NODE_WS_PORT should always be set")
179			.parse()
180			.expect("Default NODE_WS_PORT should be valid u16"),
181		context,
182	);
183	let keystore_path = keystore_path(&substrate_node_base_path);
184	let network_key_path = network_key_path(&substrate_node_base_path);
185	context.run_command(&format!(
186		"{environment_prefix} {executable} --validator --chain {CHAIN_SPEC_PATH} --base-path {substrate_node_base_path} --keystore-path {keystore_path} --node-key-file {network_key_path} --port {ws_port} {bootnodes}",
187	))?;
188
189	Ok(())
190}