iuna

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

commit 2c1dcedf8fd9f7e3e9f8923ebadcf4f542b7e7f2
parent c5fb08d38133f9cb1e74de581287fb78e9da6f47
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Fri, 24 Jul 2026 13:38:24 +0200

Use mine action for genesis issuance

Diffstat:
Msrc/domain.rs | 64++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Msrc/main.rs | 57+++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 99 insertions(+), 22 deletions(-)

diff --git a/src/domain.rs b/src/domain.rs @@ -110,6 +110,28 @@ pub enum Transaction { } impl Transaction { + pub fn genesis_mine(to: impl Into<String>) -> Result<Self> { + let output = TxOutput { + address: to.into(), + amount: MINE_REWARD, + }; + let anchor = genesis_mine_anchor(); + let difficulty_bits = MINE_DIFFICULTY_BITS; + for nonce in 0..u64::MAX { + let signature = mine_signature(&output, &anchor, nonce, difficulty_bits); + if hash_meets_difficulty(&signature, difficulty_bits) { + return Ok(Self::Mine { + output, + anchor, + nonce, + difficulty_bits, + signature, + }); + } + } + bail!("could not find valid genesis mine proof"); + } + pub fn genesis_burn(from: impl Into<String>, amount: Amount) -> Self { let from = from.into(); Self::genesis_burn_with_change(from, amount, Vec::new()) @@ -453,6 +475,10 @@ fn mine_signature(output: &TxOutput, anchor: &str, nonce: u64, difficulty_bits: hex_hash(mine_payload(output, anchor, nonce, difficulty_bits)) } +fn genesis_mine_anchor() -> String { + "0".repeat(64) +} + fn hash_meets_difficulty(hash: &str, difficulty_bits: u32) -> bool { let full_zero_nibbles = (difficulty_bits / 4) as usize; let remaining_bits = difficulty_bits % 4; @@ -884,6 +910,14 @@ impl Ledger { Self::new_with_genesis_transactions(genesis_allocations, transactions, vdf_rounds) } + pub fn new_with_genesis_mine(recipient: impl Into<String>, vdf_rounds: u32) -> Result<Self> { + Self::new_with_genesis_transactions( + BTreeMap::new(), + vec![Transaction::genesis_mine(recipient)?], + vdf_rounds, + ) + } + fn new_with_genesis_transactions( genesis_allocations: BTreeMap<String, Amount>, genesis_transactions: Vec<Transaction>, @@ -2243,8 +2277,12 @@ fn utxos_after_genesis( for transaction in &genesis.transactions { match transaction { Transaction::Burn { .. } => apply_transaction(transaction, &mut utxos)?, - Transaction::Transfer { .. } | Transaction::Mine { .. } => { - bail!("genesis only supports burn transactions") + Transaction::Mine { .. } => { + validate_genesis_mine_transaction(transaction)?; + apply_transaction(transaction, &mut utxos)?; + } + Transaction::Transfer { .. } => { + bail!("genesis only supports burn and mine transactions") } } } @@ -2321,6 +2359,28 @@ fn validate_genesis_block(block: &Block) -> Result<()> { Ok(()) } +fn validate_genesis_mine_transaction(transaction: &Transaction) -> Result<()> { + let Transaction::Mine { + output, + anchor, + difficulty_bits, + .. + } = transaction + else { + bail!("genesis transaction is not a mine transaction"); + }; + if output.amount != MINE_REWARD { + bail!("genesis mine reward is invalid"); + } + if anchor != &genesis_mine_anchor() { + bail!("genesis mine anchor is invalid"); + } + if *difficulty_bits != MINE_DIFFICULTY_BITS { + bail!("genesis mine difficulty is invalid"); + } + transaction.verify_signature() +} + fn genesis_miner( genesis_allocations: &BTreeMap<String, Amount>, transactions: &[Transaction], diff --git a/src/main.rs b/src/main.rs @@ -11,12 +11,12 @@ use anyhow::{Context, Result, bail}; use luun::{ adapters::{chain_store::SqliteChainStore, config_store, http, p2p, wallet_store}, app::{NodeCore, PeerBook, SharedNode, SharedPeerBook, now_ms}, - domain::{Amount, BLOCK_REWARD, ChainSnapshot, GenesisBurn, Ledger, MICRO_LUUN, run_vdf}, + domain::{Amount, ChainSnapshot, Ledger, MICRO_LUUN, MINE_REWARD, run_vdf}, }; use tokio::sync::Mutex; -const GENESIS_BOOTSTRAP_BURN_AMOUNT: Amount = MICRO_LUUN; -const GENESIS_INITIAL_BURN_PER_BLOCK: Amount = BLOCK_REWARD; +const GENESIS_INITIAL_BURN_PER_BLOCK: Amount = MINE_REWARD; +const GENESIS_INITIAL_BURN_FEE: Amount = 0; const VDF_MEASUREMENT_INITIAL_ROUNDS: u32 = 1_000_000; const VDF_MEASUREMENT_MAX_ROUNDS: u32 = 100_000_000; const VDF_MEASUREMENT_MIN_ELAPSED: Duration = Duration::from_millis(150); @@ -47,7 +47,7 @@ async fn main() -> Result<()> { 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); - let initial_burn_fee = ui_config.burn_fee; + let initial_burn_fee = initial_burn_fee(&opts, &ui_config); let node: SharedNode = Arc::new(Mutex::new(NodeCore::from_ledger_with_burn_fee( wallet, @@ -299,6 +299,13 @@ fn initial_burn_per_block(opts: &CliOptions, ui_config: &config_store::UiConfig) } } +fn initial_burn_fee(opts: &CliOptions, ui_config: &config_store::UiConfig) -> Amount { + match opts.chain_mode { + ChainMode::Genesis => GENESIS_INITIAL_BURN_FEE, + ChainMode::Setup | ChainMode::Join => ui_config.burn_fee, + } +} + fn print_help() { println!("{}", help_text()); } @@ -335,16 +342,7 @@ fn setup_ledger() -> Ledger { fn start_genesis_ledger(wallet_address: &str) -> Result<Ledger> { let vdf_rounds = measure_initial_vdf_rounds(); - let mut genesis = BTreeMap::new(); - genesis.insert(wallet_address.to_string(), GENESIS_BOOTSTRAP_BURN_AMOUNT); - Ledger::new_with_genesis_burns( - genesis, - vec![GenesisBurn::new( - wallet_address, - GENESIS_BOOTSTRAP_BURN_AMOUNT, - )], - vdf_rounds, - ) + Ledger::new_with_genesis_mine(wallet_address, vdf_rounds) } fn measure_initial_vdf_rounds() -> u32 { @@ -547,16 +545,16 @@ mod tests { use luun::{ adapters::{chain_store::SqliteChainStore, config_store::UiConfig}, app::{DEFAULT_BURN_PER_BLOCK, NodeCore}, - domain::{BLOCK_REWARD, GenesisBurn, Ledger, Wallet}, + domain::{GenesisBurn, Ledger, MINE_REWARD, Transaction, Wallet}, }; use rusqlite::Connection; use tempfile::tempdir; use tokio::sync::Mutex; use super::{ - ChainMode, CliOptions, extrapolate_vdf_rounds, help_text, initial_burn_per_block, - initialize_ledger, measure_vdf_rounds, persist_chain_snapshot, - run_chain_persistence_with_interval, validate_wallet_for_mode, + ChainMode, CliOptions, extrapolate_vdf_rounds, help_text, initial_burn_fee, + initial_burn_per_block, initialize_ledger, measure_vdf_rounds, persist_chain_snapshot, + run_chain_persistence_with_interval, start_genesis_ledger, validate_wallet_for_mode, }; fn parse(args: &[&str]) -> anyhow::Result<Option<CliOptions>> { @@ -631,14 +629,31 @@ mod tests { } #[test] - fn genesis_mode_starts_with_full_reward_burn_rate() { + fn genesis_mode_starts_with_mine_reward_burn_rate_and_zero_fee() { let genesis = parse(&["--genesis"]).unwrap().unwrap(); let configured = UiConfig { burn_per_block: 50, burn_fee: 3, ..UiConfig::default() }; - assert_eq!(initial_burn_per_block(&genesis, &configured), BLOCK_REWARD); + assert_eq!(initial_burn_per_block(&genesis, &configured), MINE_REWARD); + assert_eq!(initial_burn_fee(&genesis, &configured), 0); + } + + #[test] + fn genesis_ledger_starts_with_single_mine_action() { + let wallet = Wallet::from_seed("genesis-mine-wallet"); + let ledger = start_genesis_ledger(wallet.address()).unwrap(); + let genesis = &ledger.chain()[0]; + + assert_eq!(genesis.height, 0); + assert_eq!(genesis.miner, "genesis"); + assert_eq!(genesis.reward, 0); + assert_eq!(genesis.transactions.len(), 1); + assert!(matches!(genesis.transactions[0], Transaction::Mine { .. })); + assert_eq!(genesis.transactions[0].amount(), MINE_REWARD); + assert_eq!(ledger.balance_of(wallet.address()), MINE_REWARD); + assert_eq!(ledger.expected_leader_for_next_block(), None); } #[test] @@ -651,9 +666,11 @@ mod tests { let setup = parse(&[]).unwrap().unwrap(); assert_eq!(initial_burn_per_block(&setup, &configured), 50); + assert_eq!(initial_burn_fee(&setup, &configured), 3); let join = parse(&["--join", "127.0.0.1:9444"]).unwrap().unwrap(); assert_eq!(initial_burn_per_block(&join, &configured), 50); + assert_eq!(initial_burn_fee(&join, &configured), 3); assert_eq!( initial_burn_per_block(&setup, &UiConfig::default()),