iuna

iuna

iuna - experimental mainnet-candidate protocol
git clone https://getiuna.org/git/iuna.git
Log | Files | Refs | README | LICENSE

commit 8d36a4e47dcfa5319af45b1243a6dc9b61ddb68e
parent 5e022036ad212a0a710fd7e8f4d3da5c7f5cf5a1
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Thu, 20 Aug 2026 11:28:44 +0200

Configure local testnet mining roles

Diffstat:
MREADME.md | 16+++++++++++++---
Mdocker-compose.yml | 2++
Msrc/cli.rs | 3+++
Msrc/main.rs | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/main_tests.rs | 68+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
5 files changed, 134 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md @@ -119,7 +119,10 @@ docker compose up --build ``` The compose file starts one bootstrap genesis node and two joining nodes on an -isolated Docker network. Management UIs are exposed on: +isolated Docker network. Bootstrap automatically finalizes with burns, node2 +starts automatic PoW mining, and node3 starts with automatic burn/finalization +enabled. Node3 can only create burns once its wallet has spendable IUNA. +Management UIs are exposed on: - bootstrap: <http://127.0.0.1:18661/> - node2: <http://127.0.0.1:18662/> @@ -131,8 +134,9 @@ stable Docker-network addresses to each other. The local compose file sets `IUNA_WALLET_PASSWORD` for every container. On first start this configures the management UI password and encrypts the wallet; on -restart it unlocks the encrypted wallet so finalization can continue without UI -login. Override the local defaults from your shell or a `.env` file: +restart it unlocks the encrypted wallet so finalization and automatic mining can +continue without UI login. Override the local defaults from your shell or a +`.env` file: ```sh IUNA_BOOTSTRAP_WALLET_PASSWORD='change-this-bootstrap-password' \ @@ -143,6 +147,12 @@ docker compose up --build Do not use compose-file default passwords for public nodes or valuable wallets. +For unattended local nodes, startup environment flags can also persist mining +settings: + +- `IUNA_AUTOMATIC_BURN_ENABLED=true|false` +- `IUNA_POW_MINING_ENABLED=true|false` + Stop the network while keeping chain data: ```sh diff --git a/docker-compose.yml b/docker-compose.yml @@ -37,6 +37,7 @@ services: hostname: iuna-node2 environment: IUNA_WALLET_PASSWORD: ${IUNA_NODE2_WALLET_PASSWORD:-local-testnet-node2-password} + IUNA_POW_MINING_ENABLED: "true" entrypoint: ["/bin/sh", "-c"] command: - exec iuna --join 172.28.0.10:9444 --data-dir /data --http 0.0.0.0:18661 --p2p 0.0.0.0:9444 --p2p-announce 172.28.0.11:9444 --debug @@ -57,6 +58,7 @@ services: hostname: iuna-node3 environment: IUNA_WALLET_PASSWORD: ${IUNA_NODE3_WALLET_PASSWORD:-local-testnet-node3-password} + IUNA_AUTOMATIC_BURN_ENABLED: "true" entrypoint: ["/bin/sh", "-c"] command: - exec iuna --join 172.28.0.10:9444 --data-dir /data --http 0.0.0.0:18661 --p2p 0.0.0.0:9444 --p2p-announce 172.28.0.12:9444 --stratum 0.0.0.0:3333 --debug diff --git a/src/cli.rs b/src/cli.rs @@ -286,6 +286,9 @@ pub(crate) fn help_text() -> &'static str { --data-dir <path> Local wallet directory (default ~/.iuna)\n\ --debug Print verbose runtime logs\n\n\ Environment:\n\ + IUNA_WALLET_PASSWORD=<password> Unlock/encrypt the startup wallet and configure UI auth\n\ + IUNA_AUTOMATIC_BURN_ENABLED=true|false Persist automatic burn/finalization at startup\n\ + IUNA_POW_MINING_ENABLED=true|false Persist automatic PoW mining at startup\n\ IUNA_DEV_SKIP_SEED_VERIFY=1 Show a setup button to skip seed verification\n" } diff --git a/src/main.rs b/src/main.rs @@ -39,6 +39,8 @@ const VDF_MEASUREMENT_INITIAL_ROUNDS: u64 = 1_000; const VDF_MEASUREMENT_MAX_ROUNDS: u64 = 10_000_000; const VDF_MEASUREMENT_MIN_ELAPSED: Duration = Duration::from_millis(150); const VDF_PROGRESS_LOG_INTERVAL: Duration = Duration::from_secs(10); +const AUTOMATIC_BURN_ENABLED_ENV: &str = "IUNA_AUTOMATIC_BURN_ENABLED"; +const POW_MINING_ENABLED_ENV: &str = "IUNA_POW_MINING_ENABLED"; const WALLET_PASSWORD_ENV: &str = "IUNA_WALLET_PASSWORD"; #[tokio::main] @@ -65,6 +67,8 @@ async fn main() -> Result<()> { } let mut ui_config = config_store::load_or_create(&config_path)?; let startup_wallet_password = startup_wallet_password_from_env()?; + let startup_automatic_burn_enabled = startup_bool_from_env(AUTOMATIC_BURN_ENABLED_ENV)?; + let startup_pow_mining_enabled = startup_bool_from_env(POW_MINING_ENABLED_ENV)?; let p2p_config_dirty = apply_cli_p2p_config_overrides(&opts, &mut ui_config); let stratum_config_dirty = apply_cli_stratum_config_overrides(&opts, &mut ui_config); let p2p_announce_addr = configured_p2p_announce_addr(&opts, &ui_config)?; @@ -79,6 +83,11 @@ async fn main() -> Result<()> { ui_config.burn_per_block = GENESIS_INITIAL_BURN_PER_BLOCK; ui_config.burn_fee = GENESIS_INITIAL_BURN_FEE; } + let mining_config_dirty = apply_startup_mining_config_overrides( + &mut ui_config, + startup_automatic_burn_enabled, + startup_pow_mining_enabled, + ); let auth_config_dirty = apply_startup_wallet_password_config( &config_path, &mut ui_config, @@ -86,8 +95,10 @@ async fn main() -> Result<()> { )?; let wallet_load = load_startup_wallet(&wallet_path, startup_wallet_password.as_deref())?; let wallet_address = wallet_load.address().to_string(); - let ui_config_dirty = - opts.chain_mode == ChainMode::Genesis || p2p_config_dirty || stratum_config_dirty; + let ui_config_dirty = opts.chain_mode == ChainMode::Genesis + || p2p_config_dirty + || stratum_config_dirty + || mining_config_dirty; if ui_config_dirty || auth_config_dirty { config_store::save(&config_path, &ui_config)?; } @@ -297,6 +308,46 @@ fn startup_wallet_password_from_env() -> Result<Option<String>> { Ok(Some(password)) } +fn startup_bool_from_env(name: &str) -> Result<Option<bool>> { + let Some(value) = std::env::var_os(name) else { + return Ok(None); + }; + let value = value + .into_string() + .map_err(|_| anyhow::anyhow!("{name} must be valid UTF-8"))?; + let normalized = value.trim().to_ascii_lowercase(); + parse_startup_bool_env_value(name, &normalized).map(Some) +} + +fn parse_startup_bool_env_value(name: &str, normalized: &str) -> Result<bool> { + match normalized { + "1" | "true" | "yes" | "on" => Ok(true), + "0" | "false" | "no" | "off" => Ok(false), + _ => bail!("{name} must be one of true, false, 1, 0, yes, no, on, or off"), + } +} + +fn apply_startup_mining_config_overrides( + ui_config: &mut config_store::UiConfig, + automatic_burn_enabled: Option<bool>, + pow_mining_enabled: Option<bool>, +) -> bool { + let mut dirty = false; + if let Some(enabled) = automatic_burn_enabled { + if ui_config.mining_enabled != enabled { + ui_config.mining_enabled = enabled; + dirty = true; + } + } + if let Some(enabled) = pow_mining_enabled { + if ui_config.pow_mining_enabled != enabled { + ui_config.pow_mining_enabled = enabled; + dirty = true; + } + } + dirty +} + fn apply_startup_wallet_password_config( config_path: &Path, ui_config: &mut config_store::UiConfig, diff --git a/src/main_tests.rs b/src/main_tests.rs @@ -17,11 +17,11 @@ use tokio::sync::Mutex; use super::{ ChainMode, CliOptions, GENESIS_INITIAL_BURN_FEE, GENESIS_INITIAL_BURN_PER_BLOCK, StartupWallet, apply_cli_p2p_config_overrides, apply_cli_stratum_config_overrides, - apply_startup_wallet_password_config, configured_p2p_announce_addr, configured_p2p_bind_addr, - configured_stratum_addr, extrapolate_vdf_rounds, help_text, initial_burn_fee, - initial_burn_per_block, initialize_ledger, load_startup_wallet, measure_vdf_rounds, - persist_chain_snapshot, project_ui_data_store, run_chain_persistence_with_interval, - validate_wallet_for_mode, + apply_startup_mining_config_overrides, apply_startup_wallet_password_config, + configured_p2p_announce_addr, configured_p2p_bind_addr, configured_stratum_addr, + extrapolate_vdf_rounds, help_text, initial_burn_fee, initial_burn_per_block, initialize_ledger, + load_startup_wallet, measure_vdf_rounds, parse_startup_bool_env_value, persist_chain_snapshot, + project_ui_data_store, run_chain_persistence_with_interval, validate_wallet_for_mode, }; fn parse(args: &[&str]) -> anyhow::Result<Option<CliOptions>> { @@ -30,6 +30,9 @@ fn parse(args: &[&str]) -> anyhow::Result<Option<CliOptions>> { #[test] fn help_mentions_dev_seed_verify_bypass_env() { + assert!(help_text().contains("IUNA_WALLET_PASSWORD=<password>")); + assert!(help_text().contains("IUNA_AUTOMATIC_BURN_ENABLED=true|false")); + assert!(help_text().contains("IUNA_POW_MINING_ENABLED=true|false")); assert!(help_text().contains("IUNA_DEV_SKIP_SEED_VERIFY=1")); assert!(help_text().contains("skip seed verification")); assert!(help_text().contains("--stratum <addr:port>")); @@ -204,6 +207,61 @@ fn startup_wallet_password_mismatch_does_not_encrypt_plaintext_wallet() { } #[test] +fn startup_mining_env_overrides_persisted_config() { + let mut config = UiConfig { + mining_enabled: false, + pow_mining_enabled: true, + ..UiConfig::default() + }; + + assert!(apply_startup_mining_config_overrides( + &mut config, + Some(true), + Some(false), + )); + assert!(config.mining_enabled); + assert!(!config.pow_mining_enabled); + + assert!(!apply_startup_mining_config_overrides( + &mut config, + Some(true), + Some(false), + )); +} + +#[test] +fn startup_mining_env_can_override_genesis_defaults() { + let mut config = UiConfig { + mining_enabled: true, + pow_mining_enabled: false, + burn_per_block: GENESIS_INITIAL_BURN_PER_BLOCK, + burn_fee: GENESIS_INITIAL_BURN_FEE, + ..UiConfig::default() + }; + + assert!(apply_startup_mining_config_overrides( + &mut config, + Some(false), + Some(true), + )); + assert!(!config.mining_enabled); + assert!(config.pow_mining_enabled); + assert_eq!(config.burn_per_block, GENESIS_INITIAL_BURN_PER_BLOCK); + assert_eq!(config.burn_fee, GENESIS_INITIAL_BURN_FEE); +} + +#[test] +fn startup_bool_env_parser_accepts_common_boolean_values() { + for value in ["1", "true", "yes", "on"] { + assert!(parse_startup_bool_env_value("TEST_BOOL", value).unwrap()); + } + for value in ["0", "false", "no", "off"] { + assert!(!parse_startup_bool_env_value("TEST_BOOL", value).unwrap()); + } + assert!(parse_startup_bool_env_value("TEST_BOOL", "maybe").is_err()); +} + +#[test] fn no_args_starts_setup_mode() { let opts = parse(&[]).unwrap().unwrap(); assert_eq!(opts.chain_mode, ChainMode::Setup);