1pub mod arglang;
6
7use std::{
8 alloc::System,
9 fs,
10 path::{Path, PathBuf},
11 str::FromStr,
12 sync::Arc,
13};
14
15use anyhow::{self, bail, Context};
16use prometheus::Registry;
17use regex::Regex;
18use stats_alloc::{StatsAlloc, INSTRUMENTED_SYSTEM};
19use structopt::StructOpt;
20use toml::{value::Table, Value};
21use tracing::{error, info};
22
23use casper_types::{Chainspec, ChainspecRawBytes};
24
25use crate::{
26 components::network::Identity as NetworkIdentity,
27 logging,
28 reactor::{main_reactor, Runner},
29 setup_signal_hooks,
30 types::ExitCode,
31 utils::{
32 chain_specification::validate_chainspec, config_specification::validate_config, Loadable,
33 WithDir,
34 },
35};
36
37#[global_allocator]
40static ALLOC: &StatsAlloc<System> = &INSTRUMENTED_SYSTEM;
41
42#[derive(Debug, StructOpt)]
44#[structopt(version = crate::VERSION_STRING_COLOR.as_str())]
45#[allow(rustdoc::invalid_html_tags)]
46pub enum Cli {
48 #[structopt(alias = "validator")]
53 Standard {
54 config: PathBuf,
56
57 #[structopt(
58 short = "C",
59 long,
60 env = "NODE_CONFIG",
61 use_delimiter(true),
62 value_delimiter(";")
63 )]
64 config_ext: Vec<ConfigExt>,
67 },
68 MigrateConfig {
70 #[structopt(long)]
72 old_config: PathBuf,
73 #[structopt(long)]
75 new_config: PathBuf,
76 },
77 MigrateData {
79 #[structopt(long)]
81 old_config: PathBuf,
82 #[structopt(long)]
84 new_config: PathBuf,
85 },
86 ValidateConfig {
88 config: PathBuf,
90 },
91}
92
93#[derive(Debug)]
94pub struct ConfigExt {
96 section: String,
97 key: String,
98 value: String,
99}
100
101impl ConfigExt {
102 fn update_toml_table(&self, toml_value: &mut Value) -> anyhow::Result<()> {
107 let table = toml_value
108 .as_table_mut()
109 .ok_or_else(|| anyhow::anyhow!("configuration table is not a table"))?;
110
111 if !table.contains_key(&self.section) {
112 table.insert(self.section.clone(), Value::Table(Table::new()));
113 }
114 let val = arglang::parse(&self.value)?;
115 table[&self.section]
116 .as_table_mut()
117 .ok_or_else(|| {
118 anyhow::anyhow!("configuration section {} is not a table", self.section)
119 })?
120 .insert(self.key.clone(), val);
121 Ok(())
122 }
123}
124
125impl FromStr for ConfigExt {
126 type Err = anyhow::Error;
127
128 fn from_str(input: &str) -> Result<Self, Self::Err> {
130 let re = Regex::new(r"^([^.]+)\.([^=]+)=(.+)$").unwrap();
131 let captures = re
132 .captures(input)
133 .context("could not parse config_ext (see README.md)")?;
134 Ok(ConfigExt {
135 section: captures
136 .get(1)
137 .context("failed to find section")?
138 .as_str()
139 .to_owned(),
140 key: captures
141 .get(2)
142 .context("failed to find key")?
143 .as_str()
144 .to_owned(),
145 value: captures
146 .get(3)
147 .context("failed to find value")?
148 .as_str()
149 .to_owned(),
150 })
151 }
152}
153
154impl Cli {
155 pub async fn run(self) -> anyhow::Result<i32> {
157 match self {
158 Cli::Standard { config, config_ext } => {
159 setup_signal_hooks();
161
162 let mut reactor_config = Self::init(&config, config_ext)?;
163
164 let mut rng = crate::new_rng();
169
170 let registry = Registry::new();
171
172 let (chainspec, chainspec_raw_bytes) =
173 <(Chainspec, ChainspecRawBytes)>::from_path(reactor_config.dir())?;
174
175 info!(
176 protocol_version = %chainspec.protocol_version(),
177 build_version = %crate::VERSION_STRING.as_str(),
178 "node starting up"
179 );
180
181 if !validate_chainspec(&chainspec) {
182 bail!("invalid chainspec");
183 }
184
185 if !validate_config(reactor_config.value()) {
186 bail!("invalid config");
187 }
188
189 reactor_config.value_mut().ensure_valid(&chainspec);
190
191 let network_identity = NetworkIdentity::from_config(WithDir::new(
192 reactor_config.dir(),
193 reactor_config.value().network.clone(),
194 ))
195 .context("failed to create a network identity")?;
196
197 let mut main_runner = Runner::<main_reactor::MainReactor>::with_metrics(
198 reactor_config,
199 Arc::new(chainspec),
200 Arc::new(chainspec_raw_bytes),
201 network_identity,
202 &mut rng,
203 ®istry,
204 )
205 .await?;
206
207 let exit_code = main_runner.run(&mut rng).await;
208 Ok(exit_code as i32)
209 }
210 Cli::MigrateConfig {
211 old_config,
212 new_config,
213 } => {
214 let new_config = Self::init(&new_config, vec![])?;
215
216 let old_root = old_config
217 .parent()
218 .map_or_else(|| "/".into(), Path::to_path_buf);
219 let encoded_old_config = fs::read_to_string(&old_config)
220 .context("could not read old configuration file")
221 .with_context(|| old_config.display().to_string())?;
222 let old_config = toml::from_str(&encoded_old_config)?;
223
224 info!(build_version = %crate::VERSION_STRING.as_str(), "migrating config");
225 crate::config_migration::migrate_config(
226 WithDir::new(old_root, old_config),
227 new_config,
228 )?;
229 Ok(ExitCode::Success as i32)
230 }
231 Cli::MigrateData {
232 old_config,
233 new_config,
234 } => {
235 let new_config = Self::init(&new_config, vec![])?;
236
237 let old_root = old_config
238 .parent()
239 .map_or_else(|| "/".into(), Path::to_path_buf);
240 let encoded_old_config = fs::read_to_string(&old_config)
241 .context("could not read old configuration file")
242 .with_context(|| old_config.display().to_string())?;
243 let old_config = toml::from_str(&encoded_old_config)?;
244
245 info!(build_version = %crate::VERSION_STRING.as_str(), "migrating data");
246 crate::data_migration::migrate_data(
247 WithDir::new(old_root, old_config),
248 new_config,
249 )?;
250 Ok(ExitCode::Success as i32)
251 }
252 Cli::ValidateConfig { config } => {
253 info!(build_version = %crate::VERSION_STRING.as_str(), config_file = ?config, "validating config file");
254 match Self::init(&config, vec![]) {
255 Ok(_config) => {
256 info!(build_version = %crate::VERSION_STRING.as_str(), config_file = ?config, "config file is valid");
257 Ok(ExitCode::Success as i32)
258 }
259 Err(err) => {
260 logging::init_with_config(&Default::default())?;
262 error!(build_version = %crate::VERSION_STRING.as_str(), config_file = ?config, "config file is not valid");
263 Err(err)
264 }
265 }
266 }
267 }
268 }
269
270 fn init(
272 config: &Path,
273 config_ext: Vec<ConfigExt>,
274 ) -> anyhow::Result<WithDir<main_reactor::Config>> {
275 let root = config
278 .parent()
279 .map_or_else(|| "/".into(), Path::to_path_buf);
280
281 let encoded_config = fs::read_to_string(config)
283 .context("could not read configuration file")
284 .with_context(|| config.display().to_string())?;
285
286 let mut config_table: Value = toml::from_str(&encoded_config)?;
289
290 for item in config_ext {
292 item.update_toml_table(&mut config_table)?;
293 }
294
295 let main_config: main_reactor::Config = config_table.try_into()?;
297 logging::init_with_config(&main_config.logging)?;
298
299 Ok(WithDir::new(root, main_config))
300 }
301}