iuna

iuna

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

commit fd3e2deb0be0c264730adc8cb638525cec4fafc1
parent 6490b49513e4c7eb97efb7dfa7d5fd920a4d956f
Author: Joris Hartog <jorishartog@hotmail.com>
Date:   Mon,  7 Sep 2026 09:28:21 +0200

Start fallback VDFs before rank slots

Diffstat:
Msrc/app/automatic_mining.rs | 33+++++++++++++++++++++++++++++++++
Msrc/main.rs | 11++++++++++-
Msrc/main_tests.rs | 29++++++++++++++++++++++-------
3 files changed, 65 insertions(+), 8 deletions(-)

diff --git a/src/app/automatic_mining.rs b/src/app/automatic_mining.rs @@ -776,6 +776,39 @@ mod tests { } #[test] + fn configured_top_percentage_limits_fallback_ticket_vdfs() { + let wallets = (0..4) + .map(|index| Wallet::from_seed(&format!("fallback-percent-wallet-{index}"))) + .collect::<Vec<_>>(); + let ledger = funded_ledger(&wallets); + assert_eq!(ledger.finalizer_rank_count_for_next_block(), wallets.len()); + + let rank_one = wallets + .iter() + .find(|wallet| ledger.finalizer_rank_for_next_block(wallet.address()) == Some(1)) + .expect("fixture should contain the rank-one fallback") + .clone(); + let mut node = + NodeCore::from_ledger_with_burn_fee_and_enabled(rank_one, ledger, true, 0, 1); + + node.set_recovery_vdf_top_rank_percent(25); + assert!(!node.wallet_rank_runs_vdf(1)); + let excluded = node.prepare_automatic_finalization(1); + assert!(excluded.work.is_none()); + assert_eq!( + excluded.skipped_reason.as_deref(), + Some("wallet finalizer rank 1 is outside the top 25% VDF threshold") + ); + + node.set_recovery_vdf_top_rank_percent(50); + assert!(node.wallet_rank_runs_vdf(1)); + + node.set_recovery_vdf_top_rank_percent(0); + assert!(node.wallet_rank_runs_vdf(0)); + assert!(!node.wallet_rank_runs_vdf(1)); + } + + #[test] fn same_slot_bundles_for_different_members_do_not_conflict_locally() { let wallet = Wallet::from_seed("rank-bundle-cache-node"); let ledger = funded_ledger(std::slice::from_ref(&wallet)); diff --git a/src/main.rs b/src/main.rs @@ -730,9 +730,10 @@ async fn run_automatic_finalizer(node: SharedNode, gossip: p2p::GossipNetwork, d let seed = work.vdf_seed().to_string(); let rounds = work.vdf_rounds(); let publish_at_ms = work.timestamp_ms(); + let precheck_at_ms = automatic_finalization_precheck_time(now_ms(), publish_at_ms); let precheck = { let node = node.lock().await; - node.precheck_prepared_block_without_vdf_at(&work, now_ms()) + node.precheck_prepared_block_without_vdf_at(&work, precheck_at_ms) }; if let Err(error) = precheck { let message = format!("skipped before VDF: {error:#}"); @@ -867,6 +868,14 @@ async fn run_automatic_finalizer(node: SharedNode, gossip: p2p::GossipNetwork, d } } +fn automatic_finalization_precheck_time(now_ms: u64, publish_at_ms: u64) -> u64 { + // A fallback ticket's rank slot can be well beyond the normal future-drift + // allowance. Validate the candidate as it will stand when that known slot + // opens, so its VDF can run in advance; final application still validates + // the actual publication timestamp against the then-current clock. + now_ms.max(publish_at_ms) +} + fn should_log_automatic_finalization_skip( last_logged_skip: &mut Option<(u64, String)>, height: u64, diff --git a/src/main_tests.rs b/src/main_tests.rs @@ -19,13 +19,13 @@ use super::{ GENESIS_INITIAL_BURN_FEE, GENESIS_INITIAL_BURN_PER_BLOCK, StartupWallet, apply_cli_p2p_config_overrides, apply_cli_stratum_config_overrides, apply_startup_mining_config_overrides, apply_startup_setup_config_override, - 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, parse_startup_pow_mining_workers_env_value, - persist_chain_snapshot, project_ui_data_store, run_chain_persistence_with_interval, - setup_ledger, should_defer_sync_checkpoint, should_log_automatic_finalization_skip, - validate_wallet_for_mode, + apply_startup_wallet_password_config, automatic_finalization_precheck_time, + 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, + parse_startup_pow_mining_workers_env_value, persist_chain_snapshot, project_ui_data_store, + run_chain_persistence_with_interval, setup_ledger, should_defer_sync_checkpoint, + should_log_automatic_finalization_skip, validate_wallet_for_mode, }; fn parse(args: &[&str]) -> anyhow::Result<Option<CliOptions>> { @@ -555,6 +555,21 @@ fn automatic_finalization_skip_logging_only_reports_state_changes() { } #[test] +fn automatic_finalization_prechecks_future_fallback_at_its_publish_time() { + let now_ms = 1_000; + let publish_at_ms = now_ms + 20 * 60 * 1_000; + + assert_eq!( + automatic_finalization_precheck_time(now_ms, publish_at_ms), + publish_at_ms + ); + assert_eq!( + automatic_finalization_precheck_time(publish_at_ms, now_ms), + publish_at_ms + ); +} + +#[test] fn removed_wallet_seed_is_rejected() { let error = parse(&["--wallet-seed", "alice", "--genesis"]).unwrap_err(); assert!(error.to_string().contains("--wallet-seed was removed"));