iuna

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

commit 37ccc7173ce1f1afbb6ae9a51d48fd0af1c8e0c8
parent bf93c4325e179e8b18f4c49fc83029da9d297029
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Sun, 26 Jul 2026 23:55:00 +0200

Set block target to ten minutes

Diffstat:
MREADME.md | 2+-
Massets/iuna-ui.js | 5++++-
Mdevlogs/006-dynamic-vdf-target.md | 4++--
Msrc/domain.rs | 2+-
Msrc/main.rs | 26++++++++++++++++++++------
Mtests/iuna.rs | 2+-
6 files changed, 29 insertions(+), 12 deletions(-)

diff --git a/README.md b/README.md @@ -72,7 +72,7 @@ iuna combines three mechanisms: - **VDF clock:** the selected finalizer must run sequential delay work before publishing a block. - **PoW issuance:** miners create new IUNA through mine actions and choose the fee paid to the finalizer that includes them. -The current devnet targets 60-second blocks, uses local wallet encryption, stores chain state in SQLite, and includes an in-memory network test harness for protocol testing. +The current devnet targets 10-minute blocks, uses local wallet encryption, stores chain state in SQLite, and includes an in-memory network test harness for protocol testing. ## Contributing diff --git a/assets/iuna-ui.js b/assets/iuna-ui.js @@ -1053,7 +1053,10 @@ window.iunaApp = function iunaApp() { targetSecondsLabel() { const ms = this.status.mining?.vdf_target_block_ms; - return ms ? `${Math.round(ms / 1000)}s` : "-"; + if (!ms) return "-"; + const seconds = Math.round(ms / 1000); + if (seconds % 60 === 0) return `${seconds / 60}m`; + return `${seconds}s`; }, stratumListenAddr() { diff --git a/devlogs/006-dynamic-vdf-target.md b/devlogs/006-dynamic-vdf-target.md @@ -1,9 +1,9 @@ -# Devlog 006: Let The Chain Aim For One Minute +# Devlog 006: Let The Chain Aim For Ten Minutes The sync problem was tempting to solve in the wrong place. We could make peers trust each other more, but that weakens the protocol exactly where it should be strongest. So this change keeps VDF validation as consensus, but makes the VDF round count dynamic. Blocks still carry the exact round count they used. Nodes validate that it is the round count the chain expected for that height. -After each block, the chain looks at a rolling average of recent block times and nudges the next round count toward a 60 second target. The nudge is small, about 10% per block, so one weird timestamp cannot throw the chain completely off. +After each block, the chain looks at a rolling average of recent block times and nudges the next round count toward a 10 minute target. The nudge is small, about 10% per block, so one weird timestamp cannot throw the chain completely off. This gives gossip and catch-up more breathing room while keeping the rule deterministic: every node can derive the same next VDF rounds from the chain it has validated. diff --git a/src/domain.rs b/src/domain.rs @@ -13,7 +13,7 @@ pub const DEFAULT_MINE_FEE: Amount = MINE_REWARD / 100; pub const DEFAULT_TRANSACTION_FEE: Amount = MICRO_IUNA; pub const DEFAULT_FEE_PER_BYTE: Amount = 1; pub const MAX_BLOCK_BYTES: usize = 100_000; -pub const VDF_TARGET_BLOCK_MS: u64 = 60_000; +pub const VDF_TARGET_BLOCK_MS: u64 = 10 * 60 * 1_000; pub const MINE_DIFFICULTY_BITS: u32 = 12; const MINE_RETARGET_WINDOW_BLOCKS: u64 = 10; const MINE_TARGET_ACTIONS_PER_BLOCK: u64 = 1; diff --git a/src/main.rs b/src/main.rs @@ -11,7 +11,9 @@ use anyhow::{Context, Result, bail}; use iuna::{ adapters::{chain_store::SqliteChainStore, config_store, http, p2p, stratum, wallet_store}, app::{NodeCore, PeerBook, SharedNode, SharedPeerBook, StratumStatus, now_ms}, - domain::{Amount, ChainSnapshot, GenesisBurn, Ledger, MICRO_IUNA, run_vdf}, + domain::{ + Amount, ChainSnapshot, GenesisBurn, Ledger, MICRO_IUNA, VDF_TARGET_BLOCK_MS, run_vdf, + }, }; use tokio::sync::Mutex; @@ -441,7 +443,11 @@ fn measure_initial_vdf_rounds() -> u32 { VDF_MEASUREMENT_MIN_ELAPSED, VDF_MEASUREMENT_MAX_ROUNDS, ); - let rounds = extrapolate_vdf_rounds(measured_rounds, elapsed, Duration::from_secs(60)); + let rounds = extrapolate_vdf_rounds( + measured_rounds, + elapsed, + Duration::from_millis(VDF_TARGET_BLOCK_MS), + ); println!( "measured {measured_rounds} VDF rounds in {:.3}ms; initial VDF rounds: {rounds}", elapsed.as_secs_f64() * 1000.0 @@ -633,7 +639,7 @@ mod tests { use iuna::{ adapters::{chain_store::SqliteChainStore, config_store::UiConfig, wallet_store}, app::{DEFAULT_BURN_PER_BLOCK, NodeCore}, - domain::{BLOCK_REWARD, GenesisBurn, Ledger, MICRO_IUNA, Wallet}, + domain::{BLOCK_REWARD, GenesisBurn, Ledger, MICRO_IUNA, VDF_TARGET_BLOCK_MS, Wallet}, }; use rusqlite::Connection; use tempfile::tempdir; @@ -922,11 +928,19 @@ mod tests { #[test] fn vdf_measurement_extrapolates_to_target() { assert_eq!( - extrapolate_vdf_rounds(10_000, Duration::from_secs(1), Duration::from_secs(60)), - 600_000 + extrapolate_vdf_rounds( + 10_000, + Duration::from_secs(1), + Duration::from_millis(VDF_TARGET_BLOCK_MS), + ), + 6_000_000 ); assert_eq!( - extrapolate_vdf_rounds(10_000, Duration::from_secs(0), Duration::from_secs(60)), + extrapolate_vdf_rounds( + 10_000, + Duration::from_secs(0), + Duration::from_millis(VDF_TARGET_BLOCK_MS), + ), u32::MAX ); } diff --git a/tests/iuna.rs b/tests/iuna.rs @@ -1010,7 +1010,7 @@ fn vdf_solution_verifies_without_rerunning_delay() { } #[test] -fn vdf_rounds_retarget_toward_one_minute_blocks() { +fn vdf_rounds_retarget_toward_target_block_time() { let wallet = Wallet::from_seed("alice"); let mut genesis = BTreeMap::new(); genesis.insert(wallet.address().to_string(), 1_000);