iuna

iuna - experimental devnet protocol
git clone https://iuna.jhx.app/git/iuna.git
Log | Files | Refs | README | LICENSE

commit 8b029352111dd8ac0db2dd66de67614965cf6fbd
parent 9847a52accc3a764803f92bc0a2bdaf723eee9e8
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Wed, 22 Jul 2026 22:11:33 +0200

Require fresh wallet for genesis

Diffstat:
M.gitignore | 2+-
MREADME.md | 14++++++++------
Msrc/main.rs | 37++++++++++++++++++++++---------------
3 files changed, 31 insertions(+), 22 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,2 @@ /target -/.mivora +/.luun diff --git a/README.md b/README.md @@ -6,21 +6,23 @@ The current devnet assumes friendly nodes. It has one binary that acts as wallet ## Run +To start a fresh chain, run genesis mode from an empty wallet path: + ```sh -cargo run -- --http 127.0.0.1:18661 --p2p 127.0.0.1:9444 +cargo run -- --genesis --http 127.0.0.1:18661 --p2p 127.0.0.1:9444 ``` -Open `http://127.0.0.1:18661` and complete the initial setup modal. The setup flow lets you generate a local recovery phrase or import one, verifies generated phrases with a 4-word check, stores the wallet in `.luun/wallet.json`, stores runtime config in `.luun/config.json`, and does not create a chain yet. +Open `http://127.0.0.1:18661` and complete the initial setup modal. The setup flow verifies the generated 24-word recovery phrase with a 4-word check, stores the wallet in `.luun/wallet.json`, and stores runtime config in `.luun/config.json`. -For fast local development, set `LUUN_DEV_SKIP_SEED_VERIFY=1` before starting the node to show a setup-only skip button for the recovery phrase check. +`--genesis` only works when the wallet file does not already exist and `.luun/chain.sqlite3` does not already contain a blockchain. It creates a fresh setup wallet, opens the setup modal so the recovery phrase can be recorded, creates the starter chain, adaptively measures VDF throughput locally, extrapolates that measurement to a 60-second initial round count, and persists the validated chain. The same data directory resumes automatically on later runs without `--genesis`. -After setup, restart with genesis mode: +For setup-only local development without creating a chain yet: ```sh -cargo run -- --genesis --http 127.0.0.1:18661 --p2p 127.0.0.1:9444 +cargo run -- --http 127.0.0.1:18661 --p2p 127.0.0.1:9444 ``` -`--genesis` only works when the wallet file already exists and `.luun/chain.sqlite3` does not already contain a blockchain. It creates the starter chain, adaptively measures VDF throughput locally, extrapolates that measurement to a 60-second initial round count, and persists the validated chain. The same data directory resumes automatically on later runs without `--genesis`. +For fast local development, set `LUUN_DEV_SKIP_SEED_VERIFY=1` before starting the node to show a setup-only skip button for the recovery phrase check. Mining is automatic. There is no "mine block" button and no exact sleep. Each node can burn its configured amount once per chain height. Those burns become one-shot leader tickets after the launch profile's maturity delay. The current devnet uses a 3-block maturity delay and a 3-block eligibility window: a burn included at height `h` can win heights `h + 3` through `h + 5`, then expires if it was not selected. Only the selected ticket owner builds the next block, signs a leader proof, performs the VDF work, and gossips the finished block. The VDF is the clock. diff --git a/src/main.rs b/src/main.rs @@ -30,10 +30,20 @@ async fn main() -> Result<()> { let config_path = opts.config_path(); let wallet_file_exists = wallet_path.exists(); validate_wallet_for_mode(&opts, &wallet_path, wallet_file_exists)?; - let wallet = wallet_store::load_or_create(&wallet_path)?; - let ui_config = config_store::load_or_create(&config_path)?; let chain_store = SqliteChainStore::open(opts.chain_db_path())?; let persisted_chain_exists = chain_store.load()?.is_some(); + if opts.chain_mode == ChainMode::Genesis && persisted_chain_exists { + bail!( + "--genesis refuses to run because chain database already contains a blockchain at {}; start without --genesis to resume it", + chain_store.path().display() + ); + } + let wallet = wallet_store::load_or_create(&wallet_path)?; + let mut ui_config = config_store::load_or_create(&config_path)?; + if opts.chain_mode == ChainMode::Genesis { + ui_config.setup_complete = false; + config_store::save(&config_path, &ui_config)?; + } let ledger = initialize_ledger(&opts, wallet.address(), &chain_store).await?; let has_chain = opts.has_chain() || persisted_chain_exists; let initial_burn_per_block = initial_burn_per_block(&opts, &ui_config); @@ -256,9 +266,9 @@ fn validate_wallet_for_mode( wallet_path: &Path, wallet_file_exists: bool, ) -> Result<()> { - if opts.chain_mode == ChainMode::Genesis && !wallet_file_exists { + if opts.chain_mode == ChainMode::Genesis && wallet_file_exists { bail!( - "--genesis requires an existing wallet file at {}; start once without --genesis and complete UI setup first", + "--genesis requires a fresh wallet path, but {} already exists; start without --genesis to reuse it or choose an empty --data-dir/--wallet", wallet_path.display() ); } @@ -283,7 +293,7 @@ fn help_text() -> &'static str { luun --genesis [options]\n\ luun --join <addr:port> [options]\n\n\ Options:\n\ - --genesis Create a new chain from an existing setup wallet\n\ + --genesis Create a new chain with a fresh setup wallet\n\ --wallet <path> Wallet file (default <data-dir>/wallet.json)\n\ --chain-db <path> Chain SQLite database (default <data-dir>/chain.sqlite3)\n\ --http <addr:port> HTTP management UI address (default 127.0.0.1:18661)\n\ @@ -720,19 +730,16 @@ mod tests { } #[test] - fn genesis_requires_existing_wallet_file() { + fn genesis_requires_fresh_wallet_path() { let opts = parse(&["--genesis"]).unwrap().unwrap(); - let missing = std::path::Path::new("missing-wallet.json"); - let error = validate_wallet_for_mode(&opts, missing, false).unwrap_err(); - assert!( - error - .to_string() - .contains("requires an existing wallet file") - ); + let wallet_path = std::path::Path::new("wallet.json"); + + validate_wallet_for_mode(&opts, wallet_path, false).unwrap(); + let error = validate_wallet_for_mode(&opts, wallet_path, true).unwrap_err(); + assert!(error.to_string().contains("requires a fresh wallet path")); - validate_wallet_for_mode(&opts, missing, true).unwrap(); let setup = parse(&[]).unwrap().unwrap(); - validate_wallet_for_mode(&setup, missing, false).unwrap(); + validate_wallet_for_mode(&setup, wallet_path, true).unwrap(); } #[test]