Skip to content

Commit

Permalink
Add testnet command, and clean up CLI (#205)
Browse files Browse the repository at this point in the history
* removed index parameter, added testnet command, simplified init command

* Update help message

* Remove `--config`, `--genesis` and `--private-key` options

* Unpretty private key bytes

* Log output in `testnet` command

* Use CometBFT-compatible format for `priv_validator_key.json` file

* Add test for `testnet` command

---------

Co-authored-by: Greg Szabo <greg@philosobear.com>
  • Loading branch information
romac and greg-szabo authored Jun 4, 2024
1 parent ae40835 commit d4b9f35
Show file tree
Hide file tree
Showing 11 changed files with 314 additions and 231 deletions.
1 change: 0 additions & 1 deletion code/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ malachite-actors.workspace = true
malachite-node.workspace = true
malachite-test.workspace = true

base64 = { workspace = true }
clap = { workspace = true, features = ["derive", "env"] }
color-eyre = { workspace = true }
directories = { workspace = true }
Expand Down
136 changes: 29 additions & 107 deletions code/cli/src/args.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
//! Node command-line interface configuration
//!
//! The node CLI reads configuration from the configuration file provided with the
//! `--config` parameter. Some configuration parameters can be overridden on the command-line.
//! The node CLI reads configuration from the configuration files found in the directory
//! provided with the `--home` global parameter.
//!
//! The command-line parameters are stored in the `Args` structure.
//! `clap` parses the command-line parameters into this structure.
//!
use std::path::{Path, PathBuf};

use base64::prelude::BASE64_STANDARD;
use base64::Engine;
use clap::{Parser, Subcommand};
use color_eyre::eyre::{eyre, Context, Result};
use directories::BaseDirs;
Expand All @@ -20,6 +17,7 @@ use malachite_node::config::Config;
use malachite_test::{PrivateKey, ValidatorSet};

use crate::logging::DebugSection;
use crate::priv_key::PrivValidatorKey;

const APP_FOLDER: &str = ".malachite";
const CONFIG_FILE: &str = "config.toml";
Expand All @@ -29,36 +27,13 @@ const PRIV_VALIDATOR_KEY_FILE: &str = "priv_validator_key.json";
#[derive(Parser, Clone, Debug, Default)]
#[command(version, about, long_about = None)]
pub struct Args {
/// Config file path
#[arg(long, value_name = "HOME_DIR")]
/// Home directory for Malachite (default: `~/.malachite`)
#[arg(long, global = true, value_name = "HOME_DIR")]
pub home: Option<PathBuf>,

/// Config file path
#[arg(short, long, value_name = "CONFIG_FILE")]
pub config: Option<PathBuf>,

/// Genesis file path
#[arg(short, long, value_name = "GENESIS_FILE")]
pub genesis: Option<PathBuf>,

/// Base64-encoded private key
#[clap(
long,
default_value = "",
hide_default_value = true,
value_name = "BASE64_STRING",
env = "PRIVATE_KEY",
value_parser = | s: & str | BASE64_STANDARD.decode(s)
)]
pub private_key: std::vec::Vec<u8>, // Keep the fully qualified path for Vec<u8> or else clap will not be able to parse it: https://github.com/clap-rs/clap/issues/4481.

/// Validator index only for the init command
#[clap(short, long, value_name = "INDEX", env = "INDEX")]
pub index: Option<usize>,

#[clap(
short,
long = "debug",
global = true,
help = "Enable debug output for the given comma-separated sections",
value_enum,
value_delimiter = ','
Expand All @@ -71,12 +46,26 @@ pub struct Args {

#[derive(Subcommand, Clone, Debug, Default, PartialEq)]
pub enum Commands {
/// Initialize configuration
///
Init,
/// Start node
#[default]
Start,

/// Initialize configuration
Init,

/// Generate testnet configuration
Testnet(TestnetArgs),
}

#[derive(Parser, Debug, Clone, PartialEq)]
pub struct TestnetArgs {
/// Number of validator nodes in the testnet
#[clap(short, long)]
pub nodes: usize,

/// Generate deterministic private keys for reproducibility
#[clap(short, long)]
pub deterministic: bool,
}

impl Args {
Expand Down Expand Up @@ -105,19 +94,13 @@ impl Args {
/// get_config_file_path returns the configuration file path based on the command-ine arguments
/// and the configuration folder.
pub fn get_config_file_path(&self) -> Result<PathBuf> {
Ok(match &self.config {
Some(path) => path.clone(),
None => self.get_config_dir()?.join(CONFIG_FILE),
})
Ok(self.get_config_dir()?.join(CONFIG_FILE))
}

/// get_genesis_file_path returns the genesis file path based on the command-line arguments and
/// the configuration folder.
pub fn get_genesis_file_path(&self) -> Result<PathBuf> {
Ok(match &self.genesis {
Some(path) => path.clone(),
None => self.get_config_dir()?.join(GENESIS_FILE),
})
Ok(self.get_config_dir()?.join(GENESIS_FILE))
}

/// get_priv_validator_key_file_path returns the private validator key file path based on the
Expand All @@ -143,18 +126,10 @@ impl Args {
/// load_private_key returns the private key either from the command-line parameter or
/// from the priv_validator_key.json file.
pub fn load_private_key(&self) -> Result<PrivateKey> {
if self.private_key.is_empty()
|| self.private_key == vec![0u8; 32]
|| self.private_key.len() < 32
{
let priv_key_file = self.get_priv_validator_key_file_path()?;
info!("Loading private key from {:?}", priv_key_file.display());
load_json_file(&priv_key_file)
} else {
let mut key: [u8; 32] = [0; 32];
key.copy_from_slice(&self.private_key);
Ok(PrivateKey::from(key))
}
let priv_key_file = self.get_priv_validator_key_file_path()?;
info!("Loading private key from {:?}", priv_key_file.display());
let priv_validator_key: PrivValidatorKey = load_json_file(&priv_key_file)?;
Ok(priv_validator_key.private_key)
}
}

Expand Down Expand Up @@ -193,22 +168,6 @@ mod tests {
let args = Args::parse_from(["test", "start"]);
assert_eq!(args.debug, vec![]);
assert_eq!(args.command, Commands::Start);

let args = Args::parse_from([
"test",
"--config",
"myconfig.toml",
"--genesis",
"mygenesis.json",
"--private-key",
"c2VjcmV0",
"init",
]);
assert_eq!(args.config, Some(PathBuf::from("myconfig.toml")));
assert_eq!(args.genesis, Some(PathBuf::from("mygenesis.json")));
assert_eq!(args.private_key, b"secret");
assert!(args.get_home_dir().is_ok());
assert!(args.get_config_dir().is_ok());
}

#[test]
Expand All @@ -229,41 +188,4 @@ mod tests {
writeln!(file, "{{}}").unwrap();
assert!(load_json_file::<TestStruct>(&PathBuf::from(tmpfile.path())).is_ok());
}

#[test]
fn args_load_config() {
let args = Args::parse_from(["test", "--config", "../config.toml", "start"]);
let config = args.load_config().unwrap();
assert_eq!(config.moniker, "malachite");
}

#[test]
fn args_load_genesis() {
let args = Args::parse_from(["test", "--genesis", "../genesis.json", "start"]);
assert!(args.load_genesis().is_err());
}

#[test]
fn args_private_key() {
let args = Args::parse_from(["test", "start"]);
if !args.get_priv_validator_key_file_path().unwrap().exists() {
assert!(args.load_private_key().is_err());
assert!(args.private_key.is_empty());
}

let args = Args::parse_from(["test", "--private-key", "c2VjcmV0", "start"]);
if !args.get_priv_validator_key_file_path().unwrap().exists() {
assert!(args.load_private_key().is_err());
}

let args = Args::parse_from([
"test",
"--private-key",
"c2VjcmV0c2VjcmV0c2VjcmV0c2VjcmV0c2VjcmV0MDA=",
"start",
]);
let pk = args.load_private_key().unwrap();

assert_eq!(pk.inner().as_bytes(), b"secretsecretsecretsecretsecret00");
}
}
36 changes: 18 additions & 18 deletions code/cli/src/cmd/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,49 +7,49 @@ use color_eyre::eyre::{eyre, Context, Result};
use tracing::{info, warn};

use malachite_node::config::Config;
use malachite_test::PrivateKey;
use malachite_test::ValidatorSet as Genesis;

use crate::example::{generate_config, generate_genesis, generate_private_key};
use crate::cmd::testnet::{generate_config, generate_genesis, generate_private_keys};
use crate::priv_key::PrivValidatorKey;

/// Execute the init command
pub fn run(
config_file: &Path,
genesis_file: &Path,
priv_validator_key_file: &Path,
index: usize,
) -> Result<()> {
pub fn run(config_file: &Path, genesis_file: &Path, priv_validator_key_file: &Path) -> Result<()> {
// Save default configuration
if config_file.exists() {
warn!(
"Configuration file already exists at {:?}, skipping.",
"Configuration file already exists at {:?}, skipping",
config_file.display()
)
} else {
info!("Saving configuration to {:?}.", config_file);
save_config(config_file, &generate_config(index))?;
info!("Saving configuration to {:?}", config_file);
save_config(config_file, &generate_config(0, 1))?;
}

// Save default genesis
if genesis_file.exists() {
warn!(
"Genesis file already exists at {:?}, skipping.",
"Genesis file already exists at {:?}, skipping",
genesis_file.display()
)
} else {
let private_keys = generate_private_keys(1, true);
let public_keys = private_keys.iter().map(|pk| pk.public_key()).collect();
let genesis = generate_genesis(public_keys, true);
info!("Saving test genesis to {:?}.", genesis_file);
save_genesis(genesis_file, &generate_genesis())?;
save_genesis(genesis_file, &genesis)?;
}

// Save default priv_validator_key
if priv_validator_key_file.exists() {
warn!(
"Private key file already exists at {:?}, skipping.",
"Private key file already exists at {:?}, skipping",
priv_validator_key_file.display()
)
} else {
info!("Saving private key to {:?}.", priv_validator_key_file);
save_priv_validator_key(priv_validator_key_file, &generate_private_key(index))?;
info!("Saving private key to {:?}", priv_validator_key_file);
let private_keys = generate_private_keys(1, false);
let priv_validator_key = PrivValidatorKey::from(private_keys[0].clone());
save_priv_validator_key(priv_validator_key_file, &priv_validator_key)?;
}

Ok(())
Expand All @@ -68,11 +68,11 @@ pub fn save_genesis(genesis_file: &Path, genesis: &Genesis) -> Result<()> {
/// Save private_key validator key to file
pub fn save_priv_validator_key(
priv_validator_key_file: &Path,
private_key: &PrivateKey,
priv_validator_key: &PrivValidatorKey,
) -> Result<()> {
save(
priv_validator_key_file,
&serde_json::to_string_pretty(private_key)?,
&serde_json::to_string_pretty(priv_validator_key)?,
)
}

Expand Down
1 change: 1 addition & 0 deletions code/cli/src/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
pub mod init;
pub mod start;
pub mod testnet;
Loading

0 comments on commit d4b9f35

Please sign in to comment.