diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bf95f1c8..39378fc92 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +* Add `init` command for node and faucet (#392). * Added crate to distribute node RPC protobuf files (#391). * Changed state sync endpoint to return a list of `TransactionSummary` objects instead of just transaction IDs (#386). * Fixed faucet note script so that it uses the `aux` input (#387). diff --git a/Cargo.lock b/Cargo.lock index d0241ae62..85186f11a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1680,6 +1680,7 @@ dependencies = [ "actix-files", "actix-web", "async-mutex", + "clap", "derive_more", "figment", "miden-lib", @@ -1691,6 +1692,7 @@ dependencies = [ "rand_chacha", "serde", "thiserror", + "toml", "tonic", "tracing", ] @@ -1721,6 +1723,7 @@ dependencies = [ "rand_chacha", "serde", "tokio", + "toml", "tracing", "tracing-subscriber", ] diff --git a/bin/faucet/Cargo.toml b/bin/faucet/Cargo.toml index 22ebfe1b5..9bb0dd0b4 100644 --- a/bin/faucet/Cargo.toml +++ b/bin/faucet/Cargo.toml @@ -21,6 +21,7 @@ actix-cors = "0.7.0" actix-files = "0.6.5" actix-web = "4" async-mutex = "1.4.0" +clap = { version = "4.3", features = ["derive"] } derive_more = "0.99.17" figment = { version = "0.10", features = ["toml", "env"] } miden-lib = { workspace = true, features = ["concurrent"] } @@ -32,5 +33,6 @@ rand = { version = "0.8.5" } rand_chacha = "0.3" serde = { version = "1.0", features = ["derive"] } thiserror = { workspace = true } +toml = { version = "0.8" } tonic = { workspace = true } tracing = { workspace = true } diff --git a/bin/faucet/README.md b/bin/faucet/README.md index 5b67f4dce..2d9fe8edb 100644 --- a/bin/faucet/README.md +++ b/bin/faucet/README.md @@ -5,13 +5,23 @@ This crate contains a binary for running a Miden rollup faucet. ## Running the faucet 1. Run a local node, for example using the docker image. From the "miden-node" repo root run the following commands: ```bash -cargo make docker-build-node -cargo make docker-run-node +make docker-build-node +make docker-run-node ``` -2. From the "miden-node" repo root run the faucet: +2. Install the faucet (with the "testing" feature): ```bash -cargo run --bin miden-faucet --features testing --release +make install-faucet-testing +``` + +3. Create the default faucet configuration file: +```bash +miden-faucet init +``` + +4. Start the faucet server: +```bash +miden-faucet start ``` After a few seconds you may go to `http://localhost:8080` and see the faucet UI. diff --git a/bin/faucet/src/config.rs b/bin/faucet/src/config.rs index ccfca65f6..316b06200 100644 --- a/bin/faucet/src/config.rs +++ b/bin/faucet/src/config.rs @@ -3,7 +3,7 @@ use std::{ path::PathBuf, }; -use miden_node_utils::config::Endpoint; +use miden_node_utils::config::{Endpoint, DEFAULT_FAUCET_SERVER_PORT, DEFAULT_NODE_RPC_PORT}; use serde::{Deserialize, Serialize}; // Faucet config @@ -43,3 +43,18 @@ impl Display for FaucetConfig { )) } } + +impl Default for FaucetConfig { + fn default() -> Self { + Self { + endpoint: Endpoint::localhost(DEFAULT_FAUCET_SERVER_PORT), + node_url: Endpoint::localhost(DEFAULT_NODE_RPC_PORT).to_string(), + timeout_ms: 10000, + database_filepath: PathBuf::from("store.sqlite3"), + asset_amount_options: vec![100, 500, 1000], + token_symbol: "POL".to_string(), + decimals: 8, + max_supply: 1000000, + } + } +} diff --git a/bin/faucet/src/main.rs b/bin/faucet/src/main.rs index 11706465f..f501c1b7d 100644 --- a/bin/faucet/src/main.rs +++ b/bin/faucet/src/main.rs @@ -4,7 +4,7 @@ mod errors; mod handlers; mod state; -use std::path::PathBuf; +use std::{fs::File, io::Write, path::PathBuf}; use actix_cors::Cors; use actix_files::Files; @@ -12,6 +12,7 @@ use actix_web::{ middleware::{DefaultHeaders, Logger}, web, App, HttpServer, }; +use clap::{Parser, Subcommand}; use errors::FaucetError; use miden_node_utils::config::load_config; use state::FaucetState; @@ -26,8 +27,32 @@ use crate::{ // ================================================================================================= const COMPONENT: &str = "miden-faucet"; +const FAUCET_CONFIG_FILE_PATH: &str = "miden-faucet.toml"; -const FAUCET_CONFIG_FILE_PATH: &str = "config/miden-faucet.toml"; +// COMMANDS +// ================================================================================================ + +#[derive(Parser)] +#[command(version, about, long_about = None)] +pub struct Cli { + #[command(subcommand)] + pub command: Command, +} + +#[derive(Subcommand)] +pub enum Command { + /// Start the faucet server + Start { + #[arg(short, long, value_name = "FILE", default_value = FAUCET_CONFIG_FILE_PATH)] + config: PathBuf, + }, + + /// Generates default configuration file for the faucet + Init { + #[arg(short, long, default_value = FAUCET_CONFIG_FILE_PATH)] + config_path: String, + }, +} // MAIN // ================================================================================================= @@ -37,37 +62,69 @@ async fn main() -> Result<(), FaucetError> { miden_node_utils::logging::setup_logging() .map_err(|err| FaucetError::StartError(err.to_string()))?; - let config: FaucetConfig = load_config(PathBuf::from(FAUCET_CONFIG_FILE_PATH).as_path()) - .extract() - .map_err(|err| FaucetError::ConfigurationError(err.to_string()))?; - - let faucet_state = FaucetState::new(config.clone()).await?; - - info!(target: COMPONENT, %config, "Initializing server"); - - info!("Server is now running on: {}", config.endpoint_url()); - - HttpServer::new(move || { - let cors = Cors::default().allow_any_origin().allow_any_method(); - App::new() - .app_data(web::Data::new(faucet_state.clone())) - .wrap(cors) - .wrap(Logger::default()) - .wrap(DefaultHeaders::new().add(("Cache-Control", "no-cache"))) - .service(get_metadata) - .service(get_tokens) - .service( - Files::new("/", "bin/faucet/src/static") - .use_etag(false) - .use_last_modified(false) - .index_file("index.html"), - ) - }) - .bind((config.endpoint.host, config.endpoint.port)) - .map_err(|err| FaucetError::StartError(err.to_string()))? - .run() - .await - .map_err(|err| FaucetError::StartError(err.to_string()))?; + let cli = Cli::parse(); + + match &cli.command { + Command::Start { config } => { + let config: FaucetConfig = load_config(config.as_path()) + .extract() + .map_err(|err| FaucetError::ConfigurationError(err.to_string()))?; + + let faucet_state = FaucetState::new(config.clone()).await?; + + info!(target: COMPONENT, %config, "Initializing server"); + + info!("Server is now running on: {}", config.endpoint_url()); + + HttpServer::new(move || { + let cors = Cors::default().allow_any_origin().allow_any_method(); + App::new() + .app_data(web::Data::new(faucet_state.clone())) + .wrap(cors) + .wrap(Logger::default()) + .wrap(DefaultHeaders::new().add(("Cache-Control", "no-cache"))) + .service(get_metadata) + .service(get_tokens) + .service( + Files::new("/", "bin/faucet/src/static") + .use_etag(false) + .use_last_modified(false) + .index_file("index.html"), + ) + }) + .bind((config.endpoint.host, config.endpoint.port)) + .map_err(|err| FaucetError::StartError(err.to_string()))? + .run() + .await + .map_err(|err| FaucetError::StartError(err.to_string()))?; + }, + Command::Init { config_path } => { + let current_dir = std::env::current_dir().map_err(|err| { + FaucetError::ConfigurationError(format!("failed to open current directory: {err}")) + })?; + + let mut config_file_path = current_dir.clone(); + config_file_path.push(config_path); + + let config = FaucetConfig::default(); + let config_as_toml_string = toml::to_string(&config).map_err(|err| { + FaucetError::ConfigurationError(format!( + "Failed to serialize default config: {err}" + )) + })?; + + let mut file_handle = + File::options().write(true).create_new(true).open(&config_file_path).map_err( + |err| FaucetError::ConfigurationError(format!("Error opening the file: {err}")), + )?; + + file_handle.write(config_as_toml_string.as_bytes()).map_err(|err| { + FaucetError::ConfigurationError(format!("Error writing to file: {err}")) + })?; + + println!("Config file successfully created at: {:?}", config_file_path); + }, + } Ok(()) } diff --git a/bin/node/Cargo.toml b/bin/node/Cargo.toml index 0c522e35e..4934d9891 100644 --- a/bin/node/Cargo.toml +++ b/bin/node/Cargo.toml @@ -29,6 +29,7 @@ miden-objects = { workspace = true } rand_chacha = "0.3" serde = { version = "1.0", features = ["derive"] } tokio = { version = "1.29", features = ["rt-multi-thread", "net", "macros"] } +toml = { version = "0.8" } tracing = { workspace = true } tracing-subscriber = { workspace = true } diff --git a/bin/node/src/commands/genesis/inputs.rs b/bin/node/src/commands/genesis/inputs.rs index d455e30a0..851b09d8d 100644 --- a/bin/node/src/commands/genesis/inputs.rs +++ b/bin/node/src/commands/genesis/inputs.rs @@ -1,4 +1,6 @@ -use serde::Deserialize; +use std::time::{SystemTime, UNIX_EPOCH}; + +use serde::{Deserialize, Serialize}; // INPUT HELPER STRUCTS // ================================================================================================ @@ -6,21 +8,21 @@ use serde::Deserialize; /// Input types are helper structures designed for parsing and deserializing genesis input files. /// They serve as intermediary representations, facilitating the conversion from /// placeholder types (like `GenesisInput`) to internal types (like `GenesisState`). -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct GenesisInput { pub version: u32, pub timestamp: u32, pub accounts: Vec, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] #[serde(tag = "type")] pub enum AccountInput { BasicWallet(BasicWalletInputs), BasicFungibleFaucet(BasicFungibleFaucetInputs), } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct BasicWalletInputs { pub init_seed: String, pub auth_scheme: AuthSchemeInput, @@ -28,7 +30,7 @@ pub struct BasicWalletInputs { pub storage_mode: String, } -#[derive(Debug, Clone, Deserialize)] +#[derive(Debug, Clone, Deserialize, Serialize)] pub struct BasicFungibleFaucetInputs { pub init_seed: String, pub auth_scheme: AuthSchemeInput, @@ -39,7 +41,40 @@ pub struct BasicFungibleFaucetInputs { pub storage_mode: String, } -#[derive(Debug, Clone, Copy, Deserialize)] +#[derive(Debug, Clone, Copy, Deserialize, Serialize)] pub enum AuthSchemeInput { RpoFalcon512, } + +impl Default for GenesisInput { + fn default() -> Self { + Self { + version: 1, + timestamp: SystemTime::now() + .duration_since(UNIX_EPOCH) + .expect("Current timestamp should be greater than unix epoch") + .as_secs() as u32, + accounts: vec![ + AccountInput::BasicWallet(BasicWalletInputs { + init_seed: "0xa123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .to_string(), + auth_scheme: AuthSchemeInput::RpoFalcon512, + auth_seed: "0xb123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .to_string(), + storage_mode: "off-chain".to_string(), + }), + AccountInput::BasicFungibleFaucet(BasicFungibleFaucetInputs { + init_seed: "0xc123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .to_string(), + auth_scheme: AuthSchemeInput::RpoFalcon512, + auth_seed: "0xd123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + .to_string(), + token_symbol: "POL".to_string(), + decimals: 12, + max_supply: 1000000, + storage_mode: "on-chain".to_string(), + }), + ], + } + } +} diff --git a/bin/node/src/commands/genesis/mod.rs b/bin/node/src/commands/genesis/mod.rs index 32c1942e0..d0a0fb886 100644 --- a/bin/node/src/commands/genesis/mod.rs +++ b/bin/node/src/commands/genesis/mod.rs @@ -4,7 +4,7 @@ use std::{ }; use anyhow::{anyhow, Result}; -use inputs::{AccountInput, AuthSchemeInput, GenesisInput}; +pub use inputs::{AccountInput, AuthSchemeInput, GenesisInput}; use miden_lib::{ accounts::{faucets::create_basic_fungible_faucet, wallets::create_basic_wallet}, AuthScheme, diff --git a/bin/node/src/commands/init.rs b/bin/node/src/commands/init.rs new file mode 100644 index 000000000..13ea35c45 --- /dev/null +++ b/bin/node/src/commands/init.rs @@ -0,0 +1,42 @@ +use std::{fs::File, io::Write, path::PathBuf}; + +use anyhow::{anyhow, Result}; + +use crate::{commands::genesis::GenesisInput, config::NodeConfig}; + +// INIT +// =================================================================================================== + +pub fn init_config_files(config_file_path: PathBuf, genesis_file_path: PathBuf) -> Result<()> { + let config = NodeConfig::default(); + let config_as_toml_string = toml::to_string(&config) + .map_err(|err| anyhow!("Failed to serialize default config: {}", err))?; + + write_string_in_file(config_as_toml_string, &config_file_path)?; + + println!("Config file successfully created at: {:?}", config_file_path); + + let genesis = GenesisInput::default(); + let genesis_as_toml_string = toml::to_string(&genesis) + .map_err(|err| anyhow!("Failed to serialize default config: {}", err))?; + + write_string_in_file(genesis_as_toml_string, &genesis_file_path)?; + + println!("Genesis config file successfully created at: {:?}", genesis_file_path); + + Ok(()) +} + +fn write_string_in_file(content: String, path: &PathBuf) -> Result<()> { + let mut file_handle = File::options() + .write(true) + .create_new(true) + .open(path) + .map_err(|err| anyhow!("Error opening the file: {err}"))?; + + file_handle + .write(content.as_bytes()) + .map_err(|err| anyhow!("Error writing to file: {err}"))?; + + Ok(()) +} diff --git a/bin/node/src/commands/mod.rs b/bin/node/src/commands/mod.rs index 2dede7e49..642a71584 100644 --- a/bin/node/src/commands/mod.rs +++ b/bin/node/src/commands/mod.rs @@ -1,3 +1,4 @@ mod genesis; +pub mod init; pub mod start; pub use genesis::make_genesis; diff --git a/bin/node/src/config.rs b/bin/node/src/config.rs index 219fb2aed..65ee98abd 100644 --- a/bin/node/src/config.rs +++ b/bin/node/src/config.rs @@ -11,6 +11,16 @@ pub struct NodeConfig { pub store: Option, } +impl Default for NodeConfig { + fn default() -> Self { + Self { + block_producer: Some(Default::default()), + rpc: Some(Default::default()), + store: Some(Default::default()), + } + } +} + #[cfg(test)] mod tests { use std::path::PathBuf; diff --git a/bin/node/src/main.rs b/bin/node/src/main.rs index f0a1426ee..e8a014bd3 100644 --- a/bin/node/src/main.rs +++ b/bin/node/src/main.rs @@ -2,7 +2,10 @@ use std::path::PathBuf; use anyhow::{anyhow, Context}; use clap::{Parser, Subcommand}; -use commands::start::{start_block_producer, start_node, start_rpc, start_store}; +use commands::{ + init::init_config_files, + start::{start_block_producer, start_node, start_rpc, start_store}, +}; use config::NodeConfig; use miden_node_utils::config::load_config; @@ -55,6 +58,18 @@ pub enum Command { #[arg(short, long)] force: bool, }, + + /// Generates default configuration files for the node + /// + /// This command creates two files (miden-node.toml and genesis.toml) that provide configuration + /// details to the node. These files may be modified to change the node behavior. + Init { + #[arg(short, long, default_value = NODE_CONFIG_FILE_PATH)] + config_path: String, + + #[arg(short, long, default_value = DEFAULT_GENESIS_INPUTS_PATH)] + genesis_path: String, + }, } #[derive(Subcommand)] @@ -95,5 +110,17 @@ async fn main() -> anyhow::Result<()> { Command::MakeGenesis { output_path, force, inputs_path } => { commands::make_genesis(inputs_path, output_path, force) }, + Command::Init { config_path, genesis_path } => { + let current_dir = std::env::current_dir() + .map_err(|err| anyhow!("failed to open current directory: {err}"))?; + + let mut config = current_dir.clone(); + let mut genesis = current_dir.clone(); + + config.push(config_path); + genesis.push(genesis_path); + + init_config_files(config, genesis) + }, } } diff --git a/crates/block-producer/src/config.rs b/crates/block-producer/src/config.rs index ff810bad4..c54541cc6 100644 --- a/crates/block-producer/src/config.rs +++ b/crates/block-producer/src/config.rs @@ -1,6 +1,6 @@ use std::fmt::{Display, Formatter}; -use miden_node_utils::config::Endpoint; +use miden_node_utils::config::{Endpoint, DEFAULT_BLOCK_PRODUCER_PORT, DEFAULT_STORE_PORT}; use serde::{Deserialize, Serialize}; // Main config @@ -37,3 +37,13 @@ impl Display for BlockProducerConfig { )) } } + +impl Default for BlockProducerConfig { + fn default() -> Self { + Self { + endpoint: Endpoint::localhost(DEFAULT_BLOCK_PRODUCER_PORT), + store_url: Endpoint::localhost(DEFAULT_STORE_PORT).to_string(), + verify_tx_proofs: true, + } + } +} diff --git a/crates/rpc/src/config.rs b/crates/rpc/src/config.rs index 8d20a3921..dbdedb91d 100644 --- a/crates/rpc/src/config.rs +++ b/crates/rpc/src/config.rs @@ -1,6 +1,8 @@ use std::fmt::{Display, Formatter}; -use miden_node_utils::config::Endpoint; +use miden_node_utils::config::{ + Endpoint, DEFAULT_BLOCK_PRODUCER_PORT, DEFAULT_NODE_RPC_PORT, DEFAULT_STORE_PORT, +}; use serde::{Deserialize, Serialize}; // Main config @@ -29,3 +31,16 @@ impl Display for RpcConfig { )) } } + +impl Default for RpcConfig { + fn default() -> Self { + Self { + endpoint: Endpoint { + host: "0.0.0.0".to_string(), + port: DEFAULT_NODE_RPC_PORT, + }, + store_url: Endpoint::localhost(DEFAULT_STORE_PORT).to_string(), + block_producer_url: Endpoint::localhost(DEFAULT_BLOCK_PRODUCER_PORT).to_string(), + } + } +} diff --git a/crates/store/src/config.rs b/crates/store/src/config.rs index b7830b1b1..0cc92b569 100644 --- a/crates/store/src/config.rs +++ b/crates/store/src/config.rs @@ -3,7 +3,7 @@ use std::{ path::PathBuf, }; -use miden_node_utils::config::Endpoint; +use miden_node_utils::config::{Endpoint, DEFAULT_STORE_PORT}; use serde::{Deserialize, Serialize}; // Main config @@ -35,3 +35,15 @@ impl Display for StoreConfig { )) } } + +impl Default for StoreConfig { + fn default() -> Self { + const NODE_STORE_DIR: &str = "./"; + Self { + endpoint: Endpoint::localhost(DEFAULT_STORE_PORT), + database_filepath: PathBuf::from(NODE_STORE_DIR.to_string() + "miden-store.sqlite3"), + genesis_filepath: PathBuf::from(NODE_STORE_DIR.to_string() + "genesis.dat"), + blockstore_dir: PathBuf::from(NODE_STORE_DIR.to_string() + "blocks"), + } + } +} diff --git a/crates/utils/src/config.rs b/crates/utils/src/config.rs index bd0a1be7f..2b0ecb949 100644 --- a/crates/utils/src/config.rs +++ b/crates/utils/src/config.rs @@ -12,6 +12,11 @@ use figment::{ }; use serde::{Deserialize, Serialize}; +pub const DEFAULT_NODE_RPC_PORT: u16 = 57291; +pub const DEFAULT_BLOCK_PRODUCER_PORT: u16 = 48046; +pub const DEFAULT_STORE_PORT: u16 = 28943; +pub const DEFAULT_FAUCET_SERVER_PORT: u16 = 8080; + /// The `(host, port)` pair for the server's listening socket. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize)] pub struct Endpoint { @@ -21,6 +26,12 @@ pub struct Endpoint { pub port: u16, } +impl Endpoint { + pub fn localhost(port: u16) -> Self { + Endpoint { host: "localhost".to_string(), port } + } +} + impl ToSocketAddrs for Endpoint { type Iter = vec::IntoIter; fn to_socket_addrs(&self) -> io::Result {