commit 4f4640dfd5a6e6c3b5c4466d6b0a78bc04aceba7
parent dbbac427e7708a05cdaa957c16ec5f5c3af6127e
Author: Joris Hartog <jorishartog@hotmail.com>
Date: Wed, 22 Jul 2026 13:52:50 +0200
Make genesis VDF calibration adaptive
Diffstat:
2 files changed, 51 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
@@ -18,7 +18,7 @@ After setup, restart with genesis mode:
cargo run -- --genesis --http 127.0.0.1:18661 --p2p 127.0.0.1:9444
```
-`--genesis` only works when the wallet file already exists. It creates the starter chain, measures 10,000 VDF rounds locally, extrapolates that measurement to a 60-second initial round count, and persists the validated chain to `.mivora/chain.sqlite3`. The same data directory resumes automatically on later runs.
+`--genesis` only works when the wallet file already exists. 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 to `.mivora/chain.sqlite3`. The same data directory resumes automatically on later runs.
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 for a future height after the launch profile's maturity delay. 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
@@ -16,7 +16,9 @@ use mivora::{
use tokio::sync::Mutex;
const GENESIS_SPENDABLE_AMOUNT: u64 = 1;
-const VDF_MEASUREMENT_ROUNDS: u32 = 10_000;
+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);
#[tokio::main]
async fn main() -> Result<()> {
@@ -298,17 +300,43 @@ fn start_genesis_ledger(wallet_address: &str) -> Result<Ledger> {
fn measure_initial_vdf_rounds() -> u32 {
let seed = "mivora-vdf-calibration";
- let started = Instant::now();
- let _ = run_vdf(seed, VDF_MEASUREMENT_ROUNDS);
- let elapsed = started.elapsed();
- let rounds = extrapolate_vdf_rounds(VDF_MEASUREMENT_ROUNDS, elapsed, Duration::from_secs(60));
+ let (measured_rounds, elapsed) = measure_vdf_rounds(
+ seed,
+ VDF_MEASUREMENT_INITIAL_ROUNDS,
+ VDF_MEASUREMENT_MIN_ELAPSED,
+ VDF_MEASUREMENT_MAX_ROUNDS,
+ );
+ let rounds = extrapolate_vdf_rounds(measured_rounds, elapsed, Duration::from_secs(60));
println!(
- "measured {VDF_MEASUREMENT_ROUNDS} VDF rounds in {}ms; initial VDF rounds: {rounds}",
- elapsed.as_millis()
+ "measured {measured_rounds} VDF rounds in {:.3}ms; initial VDF rounds: {rounds}",
+ elapsed.as_secs_f64() * 1000.0
);
rounds
}
+fn measure_vdf_rounds(
+ seed: &str,
+ initial_rounds: u32,
+ min_elapsed: Duration,
+ max_rounds_per_attempt: u32,
+) -> (u32, Duration) {
+ let mut rounds = initial_rounds.max(1).min(max_rounds_per_attempt.max(1));
+ let mut measured_rounds = 0_u32;
+ let mut measured_elapsed = Duration::ZERO;
+
+ loop {
+ let started = Instant::now();
+ let _ = run_vdf(seed, rounds);
+ measured_elapsed += started.elapsed();
+ measured_rounds = measured_rounds.saturating_add(rounds);
+
+ if measured_elapsed >= min_elapsed || rounds >= max_rounds_per_attempt {
+ return (measured_rounds, measured_elapsed);
+ }
+ rounds = rounds.saturating_mul(2).min(max_rounds_per_attempt);
+ }
+}
+
fn extrapolate_vdf_rounds(measured_rounds: u32, elapsed: Duration, target: Duration) -> u32 {
let elapsed_ns = elapsed.as_nanos().max(1);
let target_ns = target.as_nanos().max(1);
@@ -477,8 +505,8 @@ mod tests {
use tokio::sync::Mutex;
use super::{
- ChainMode, CliOptions, extrapolate_vdf_rounds, initialize_ledger, persist_chain_snapshot,
- run_chain_persistence_with_interval, validate_wallet_for_mode,
+ ChainMode, CliOptions, extrapolate_vdf_rounds, initialize_ledger, measure_vdf_rounds,
+ persist_chain_snapshot, run_chain_persistence_with_interval, validate_wallet_for_mode,
};
fn parse(args: &[&str]) -> anyhow::Result<Option<CliOptions>> {
@@ -661,6 +689,19 @@ mod tests {
);
}
+ #[test]
+ fn vdf_measurement_keeps_sampling_until_elapsed_is_useful() {
+ let (rounds, elapsed) = measure_vdf_rounds(
+ "mivora-test-vdf-calibration",
+ 1,
+ Duration::from_millis(1),
+ 1_000_000,
+ );
+
+ assert!(rounds > 1);
+ assert!(elapsed > Duration::ZERO);
+ }
+
#[tokio::test]
async fn startup_resumes_persisted_chain_instead_of_creating_new_genesis() {
let dir = tempdir().unwrap();