partner_chains_cli/start_node/
mod.rs1use 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}