iuna

iuna

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

commit 071fefae8b02690c40f3a438ac67cc46080e0698
parent 9aebf6a491c7d003595fba89c3df7f6ef22c4954
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Sat, 22 Aug 2026 07:38:11 +0200

Improve local testnet burn defaults and warnings

Diffstat:
MREADME.md | 28++++++++++++++++++----------
Mdocker-compose.yml | 6+++---
Msrc/adapters/config_store.rs | 6++++--
Msrc/adapters/http/index_html.rs | 3+++
Msrc/main_tests.rs | 31+++++++++++++++++++++++++++++++
Mwww/assets/iuna-ui.js | 24++++++++++++++++++++++--
6 files changed, 81 insertions(+), 17 deletions(-)

diff --git a/README.md b/README.md @@ -132,22 +132,30 @@ Node3 also exposes Stratum on `127.0.0.1:3333`. P2P ports are mapped to `19444`, `19445`, and `19446` for local inspection, while nodes announce their 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 and automatic mining can -continue without UI login. Compose also sets `IUNA_SETUP_COMPLETE=true`, so -after unlocking the management UI you land directly in the node instead of the -initial setup wizard. Override the local password defaults from your shell or a -`.env` file: +The local compose file uses `test` as the management UI and wallet password for +all three nodes. On first start this configures the management UI password and +encrypts the wallet; on restart it unlocks the encrypted wallet so finalization +and automatic mining can continue without UI login. Compose also sets +`IUNA_SETUP_COMPLETE=true`, so after unlocking the management UI you land +directly in the node instead of the initial setup wizard. Override the shared +local-testnet password from your shell or a `.env` file: ```sh -IUNA_BOOTSTRAP_WALLET_PASSWORD='change-this-bootstrap-password' \ -IUNA_NODE2_WALLET_PASSWORD='change-this-node2-password' \ -IUNA_NODE3_WALLET_PASSWORD='change-this-node3-password' \ +IUNA_TESTNET_PASSWORD='change-this-testnet-password' \ docker compose up --build ``` Do not use compose-file default passwords for public nodes or valuable wallets. +If the volumes were created with the older per-node passwords, change those +passwords first or recreate the disposable local-testnet volumes before starting +the updated compose stack. + +Bootstrap uses `--genesis` only while `/data/chain.sqlite3` is absent or empty. +Once the chain database exists, container restarts launch bootstrap normally and +preserve the existing chain. + +Fresh nodes prefill automatic burns at `0.0001 IUNA` per block with a +`0.000001 IUNA` per-byte fee. For unattended local nodes, startup environment flags can also persist mining settings: diff --git a/docker-compose.yml b/docker-compose.yml @@ -19,7 +19,7 @@ services: <<: *iuna-node hostname: iuna-bootstrap environment: - IUNA_WALLET_PASSWORD: ${IUNA_BOOTSTRAP_WALLET_PASSWORD:-local-testnet-bootstrap-password} + IUNA_WALLET_PASSWORD: ${IUNA_TESTNET_PASSWORD:-test} IUNA_SETUP_COMPLETE: "true" IUNA_LOCAL_TESTNET: "true" entrypoint: ["/bin/sh", "-c"] @@ -38,7 +38,7 @@ services: <<: *iuna-node hostname: iuna-node2 environment: - IUNA_WALLET_PASSWORD: ${IUNA_NODE2_WALLET_PASSWORD:-local-testnet-node2-password} + IUNA_WALLET_PASSWORD: ${IUNA_TESTNET_PASSWORD:-test} IUNA_SETUP_COMPLETE: "true" IUNA_POW_MINING_ENABLED: "true" entrypoint: ["/bin/sh", "-c"] @@ -60,7 +60,7 @@ services: <<: *iuna-node hostname: iuna-node3 environment: - IUNA_WALLET_PASSWORD: ${IUNA_NODE3_WALLET_PASSWORD:-local-testnet-node3-password} + IUNA_WALLET_PASSWORD: ${IUNA_TESTNET_PASSWORD:-test} IUNA_SETUP_COMPLETE: "true" IUNA_AUTOMATIC_BURN_ENABLED: "true" entrypoint: ["/bin/sh", "-c"] diff --git a/src/adapters/config_store.rs b/src/adapters/config_store.rs @@ -17,7 +17,7 @@ const CONFIG_FILE_VERSION: u32 = 1; const AMOUNT_UNIT_MICROIUNA: &str = "microiuna"; const LEGACY_AMOUNT_UNIT_PRE_RENAME: &str = concat!("micro", "l", "uun"); pub const DEFAULT_BURN_AMOUNT: Amount = MICRO_IUNA / 10_000; -pub const DEFAULT_BURN_FEE: Amount = MICRO_IUNA / 10_000; +pub const DEFAULT_BURN_FEE: Amount = 1; pub const DEFAULT_RECOVERY_VDF_TOP_RANK_PERCENT: u8 = 50; pub const DEFAULT_POW_MINING_WORKERS: u8 = 1; pub const MAX_POW_MINING_WORKERS: u8 = 32; @@ -335,7 +335,7 @@ mod tests { assert!(stored.contains("\"pow_mining_enabled\": false")); assert!(stored.contains("\"pow_mining_workers\": 1")); assert!(stored.contains("\"burn_per_block\": 100")); - assert!(stored.contains("\"burn_fee\": 100")); + assert!(stored.contains("\"burn_fee\": 1")); assert!(!stored.contains("\"pow_mine_fee\"")); assert!(!stored.contains("required_burn")); assert!(stored.contains("\"keep_track_of_metrics\": false")); @@ -477,6 +477,8 @@ mod tests { assert!(!config.mining_enabled); assert_eq!(config.burn_per_block, DEFAULT_BURN_AMOUNT); assert_eq!(config.burn_fee, DEFAULT_BURN_FEE); + assert_eq!(config.burn_per_block, 100); + assert_eq!(config.burn_fee, 1); } #[test] diff --git a/src/adapters/http/index_html.rs b/src/adapters/http/index_html.rs @@ -214,6 +214,7 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html> .mine-settings-form { display: grid; gap: 10px; } .mine-fee-fields { display: flex; flex-wrap: wrap; gap: 10px; align-items: end; } .fee-preview { flex-basis: 100%; color: #9eb3bc; font-size: 12px; font-weight: 700; } + .fee-warning { flex-basis: 100%; color: #ffd070; font-size: 12px; font-weight: 800; } .mine-stats { display: grid; grid-template-columns: repeat(4, minmax(112px, 1fr)); gap: 8px; min-width: 0; } .local-mining-stats { grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); } .fee-history { grid-template-columns: repeat(3, minmax(112px, 1fr)); margin-top: 12px; } @@ -536,6 +537,7 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html> </div> <label>Fee / byte<input x-model="transferFee" @input="scheduleFeeEstimates" type="number" min="0" step="0.000001" required></label> <div class="fee-preview" x-text="feeEstimateLabel('transfer')"></div> + <div class="fee-warning" x-show="feeExceedsAmount('transfer')" x-text="feeExceedsAmountLabel('transfer')" role="status" aria-live="polite"></div> <button class="advanced-toggle" type="button" @click="toggleSendAdvanced" x-text="showSendAdvanced ? 'Hide UTXOs' : 'UTXOs'"></button> <div class="send-utxo-summary" x-show="showSendAdvanced"> <div>Selected UTXOs: <span x-text="selectedTransferUtxos.length"></span></div> @@ -713,6 +715,7 @@ pub(super) const INDEX_HTML: &str = r#"<!doctype html> <button class="primary" type="submit">Save</button> </div> <div class="fee-preview" x-text="feeEstimateLabel('burn')"></div> + <div class="fee-warning" x-show="feeExceedsAmount('burn')" x-text="feeExceedsAmountLabel('burn')" role="status" aria-live="polite"></div> </form> <div class="mine-stats fee-history" aria-label="Recent block fees"> <div class="mine-stat"> diff --git a/src/main_tests.rs b/src/main_tests.rs @@ -42,6 +42,37 @@ fn help_mentions_dev_seed_verify_bypass_env() { assert!(help_text().contains("--debug")); } +#[test] +fn local_testnet_compose_uses_one_obvious_test_password() { + let compose = include_str!("../docker-compose.yml"); + assert_eq!( + compose + .matches("IUNA_WALLET_PASSWORD: ${IUNA_TESTNET_PASSWORD:-test}") + .count(), + 3 + ); + assert!(!compose.contains("IUNA_BOOTSTRAP_WALLET_PASSWORD")); + assert!(!compose.contains("IUNA_NODE2_WALLET_PASSWORD")); + assert!(!compose.contains("IUNA_NODE3_WALLET_PASSWORD")); +} + +#[test] +fn local_testnet_bootstrap_only_uses_genesis_for_an_empty_chain() { + let compose = include_str!("../docker-compose.yml"); + assert!(compose.contains("if [ -s /data/chain.sqlite3 ]; then exec iuna --data-dir /data")); + assert!(compose.contains("else exec iuna --genesis --data-dir /data")); +} + +#[test] +fn management_ui_warns_when_estimated_fees_exceed_amounts() { + let html = include_str!("adapters/http/index_html.rs"); + let javascript = include_str!("../www/assets/iuna-ui.js"); + assert!(html.contains("feeExceedsAmount('transfer')")); + assert!(html.contains("feeExceedsAmount('burn')")); + assert!(javascript.contains("feeExceedsAmount(kind)")); + assert!(javascript.contains("estimated fee IUNA ${this.amountLabel(fee)} exceeds")); +} + fn ledger_with_one_spendable_iuna(wallet: &Wallet) -> Ledger { let mut genesis = BTreeMap::new(); genesis.insert(wallet.address().to_string(), 3); diff --git a/www/assets/iuna-ui.js b/www/assets/iuna-ui.js @@ -82,8 +82,8 @@ window.iunaApp = function iunaApp() { setupFeedback: null, burnAmount: 100, burnAmountDraft: "0.0001", - burnFee: 100, - burnFeeDraft: "0.0001", + burnFee: 1, + burnFeeDraft: "0.000001", miningEnabled: false, powMiningEnabled: false, powMiningWorkers: 1, @@ -1418,6 +1418,26 @@ window.iunaApp = function iunaApp() { return `${estimate.bytes} bytes -> IUNA ${this.amountLabel(estimate.fee)}`; }, + feeExceedsAmount(kind) { + const estimate = this.feeEstimates[kind]; + if (!estimate || estimate.error) return false; + const amount = kind === "burn" + ? this.parseiunaAmount(this.burnAmountDraft) + : this.parseiunaAmount(this.transferAmount); + const fee = Number(estimate.fee); + return amount > 0 && Number.isFinite(fee) && fee > amount; + }, + + feeExceedsAmountLabel(kind) { + if (!this.feeExceedsAmount(kind)) return ""; + const label = kind === "burn" ? "burn" : "transfer"; + const amount = kind === "burn" + ? this.parseiunaAmount(this.burnAmountDraft) + : this.parseiunaAmount(this.transferAmount); + const fee = Number(this.feeEstimates[kind].fee); + return `Warning: estimated fee IUNA ${this.amountLabel(fee)} exceeds the ${label} amount IUNA ${this.amountLabel(amount)}.`; + }, + async saveBurn() { try { const amount = this.parseiunaAmount(this.burnAmountDraft);