Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Stage 4: VDF Utility Binaries (benchmark & prove_timing)

Crate: kinetic-vdf Reading Time: ~10-15 minutes Depends on: 03_engine.md (ChiaVdfEngine)


What Is This?

These are standalone utility programs compiled directly from the kinetic-vdf crate’s src/bin/ directory. They do not provide core network functionality to the Kinetic daemon itself. They are never imported as library code by other modules. Instead, they provide critical tooling used by developers and node operators.

The first tool, benchmark.rs:The second tool, prove_timing.rs:
- Calibrates the baseline consensus difficulty for a Kinetic network.- Evaluates the exact millisecond performance differences.
- Bases this calibration on your specific CPU’s execution speed.- Compares generating a VDF proof (proving) versus validating one (verifying).

Together, they form the hardware evaluation suite for Kinetic’s Verifiable Delay Function implementation.


Why Kinetic Needs This

Kinetic’s consensus security depends heavily on precise, hardware-dependent VDF timing. The core of a VDF is that it takes a verifiable, non-parallelizable amount of time to execute. You cannot throw more CPU cores at a VDF to make it go faster. It relies purely on the single-thread execution speed of the hardware.

If the required VDF iteration counts hardcoded into the network are too low:

  • Blocks will be produced far too fast.
  • The network will fork constantly.

If they are too high:

  • The network grinds to a halt.
  • Block production stalls for minutes at a time.

We need a concrete way to ask: “On this specific machine, how many VDF iterations equal exactly 60 seconds of computation?” That is precisely what benchmark.rs answers.

Similarly, Kinetic nodes spend a percentage of their CPU cycles verifying proofs generated by others. If verifying took as long as proving, the network would collapse under its own weight. We need empirical, real-world evidence to ensure that verification remains fast (logarithmic) compared to the slow, linear process of proof generation. prove_timing.rs acts as a developer sanity check. It guarantees the math holds up in the real world when interacting with the underlying C++ wrapper.


How It Works

The bin/ Directory Structure

In Rust, the src/bin/ directory is special. Cargo (Rust’s build system) automatically detects any .rs files placed in this directory. It builds them as standalone executable programs. This happens independently of the main library output of the crate.

They still have full access to the main library’s code. For example, they can import kinetic_vdf::ChiaVdfEngine. However, they get their own independent main() function entry point.

This pattern is why you don’t see these CLI tools polluting the core Kinetic daemon’s codebase. They sit cleanly alongside the VDF library code they test. This ensures the library remains lightweight while the tools are easily accessible via cargo run --bin.

Benchmark Routine

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/benchmark.rs — Lines 29 to 54
}

The benchmarking tool runs an infinite loop evaluating small chunks of the VDF. Instead of trying to run one evaluation that might overshoot the 60-second window, it executes the evaluate function in chunks of 10,000 iterations.

Step-by-step: 1. It instantiates the ChiaVdfEngine. 2. It creates a dummy 32-byte zeroed challenge hash. 3. It starts a high-precision timer using std::time::Instant::now(). 4. It enters a while loop, asking the ChiaVdfEngine to run exactly 10,000 iterations. 5. Every time a chunk finishes, it checks start.elapsed().as_secs() < 60. 6. If 60 seconds have passed on the monotonic clock, it breaks out of the loop. 7. It looks at the total iterations performed. 8. It then normalizes the result exactly to a 60.0-second window.

Normalization is fundamentally necessary. The very last 10,000-iteration chunk almost certainly pushed the clock slightly past the exact 60.000-second mark (e.g., stopping at 60.05 seconds). Normalization prevents timing drift.

This entire process repeats sequentially for 10 rounds. This smooths out random CPU spikes, thermal throttling, or operating system background tasks. The result is a stable, reliable hardware average.

Normalizing the Hardware Timing

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/benchmark.rs — Lines 62 to 72
}

Once the CPU’s average iterations-per-minute are averaged across all 10 rounds, the script fetches kinetic_core::constants::TARGET_MINUTES. This core constant determines what the target block time is supposed to be for the entire Kinetic network (usually a few minutes).

The script multiplies the hardware’s 1-minute baseline average by this network target. The result is the exact integer number that the user should copy and paste. It belongs in the network.json configuration file under the key benchmark_base_iterations.

Timing the Proofs vs Verification

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/prove_timing.rs — Lines 21 to 42
}

This script runs a preset array of iteration targets (T). These range from a trivial 100 all the way up to a 500,000 iterations.

For every single target T: 1. It first runs evaluate (proof generation) using a dummy 0x42 challenge hash. 2. It records the raw milliseconds elapsed. 3. Then, it takes the generated proof output. 4. It immediately runs verify on it. 5. It records that time separately.

Crucially, it runs assert!(valid) on the verification result. If the C++ library somehow generates an invalid proof, the script will panic and crash immediately rather than printing false data.

It prints the side-by-side comparison in a structured ASCII table. This proves that verification remains fast (usually well under 100ms). It holds true even as proof generation times scale linearly into the hundreds or thousands of milliseconds.


Key Pieces

std::time::Instant (Rust Standard Library)

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/benchmark.rs — Line 9
}
  • What it does: Provides a monotonically non-decreasing hardware clock for the benchmark loop.
  • Why it matters: Conceptually, Instant::now() is not asking the OS for the current date and time (like SystemTime). It queries a hardware counter. This guarantees that system clock changes (like NTP syncs or daylight savings adjustments occurring during a benchmark) do not suddenly warp the elapsed times. It purely measures absolute time passed.

chunk_size

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/benchmark.rs — Line 24
}
  • What it does: Determines the stepping size for the evaluation loop (hardcoded to 10,000).
  • Why it matters: If the chunk size was 1, the overhead of crossing the C++ FFI boundary would dwarf the actual VDF mathematical work. This would invalidate the benchmark entirely. If the chunk size was 1,000,000, we might wildly overshoot the 60-second timer window. We would be waiting minutes for the final chunk to finish before we can stop the clock.

total_iterations_per_minute (Vector)

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/benchmark.rs — Line 27
}
  • What it does: A Vec that accumulates the normalized iterations across all 10 rounds.
  • Why it matters: Storing these in a dynamically sized array allows us to calculate the true average at the very end of the run. In the future, this vector could easily be used to calculate standard deviation. It could also be used to discard statistical outliers if the CPU spiked randomly during one specific 60-second round.

t0.elapsed().as_secs_f64()

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/prove_timing.rs — Line 27
}
  • What it does: Extracts the elapsed time from an Instant as a high-precision 64-bit float instead of an integer.
  • Why it matters: For timing measurements in the microsecond or millisecond range, integer seconds are totally useless. They would just round down to 0. The float conversion lets us accurately scale down to exact fractional milliseconds for the output table by multiplying the result by 1000.0.

assert!(valid, ...)

#![allow(unused)]
fn main() {
-> See: kinetic-vdf/src/bin/prove_timing.rs — Line 36
}
  • What it does: Instantly aborts the program if the verify function returns false.
  • Why it matters: > [!IMPORTANT]

Silent failures in cryptography benchmarking are dangerous. If the engine somehow produces garbage bytes, assert! ensures the developer is notified via a loud panic. This is far better than silently logging 0ms verify times.


How This Connects to the Rest of Kinetic

CROSS-CRATE:

Both of these binaries directly pull in kinetic_core::traits::VdfEngine and kinetic_core::types::Commitment. This ensures they are using the exact same interface, traits, and data structures the main daemon uses.

FORWARD DEPENDENCY:

The console output of benchmark.rs forms the literal bedrock of Kinetic’s consensus difficulty. The benchmark_base_iterations value generated here is ingested by the networking stack upon node startup. It is used by the consensus layer to determine valid proof lengths during block generation. If this calibration is wrong, the network’s first epoch will have wildly unstable block intervals.


Quick Reference

  • Command cargo run --release --bin benchmark - Find your machine’s optimal benchmark_base_iterations.
  • Always run with --release otherwise debug overhead ruins the timing.
  • Command cargo run --release --bin prove_timing - See the performance differences between generating and verifying proofs on your architecture.
  • Both scripts rely entirely on the ChiaVdfEngine implementation binding to the underlying C++ libraries via FFI.
  • The std::time::Instant type is crucial for all reliable sub-second hardware clock measurements in Rust.

Open Questions / Things to Revisit

  • The benchmark script currently hardcodes the chunk size to exactly 10,000.
  • Is this optimal for all hardware architectures?
  • What if a future CPU is so fast that 10k iterations take less time than the Rust-to-C++ FFI context switch?
  • The timing script notes at the bottom that pure-Rust kyn-vdf verify times are measured separately.
  • It seems the pure-Rust verification might not be fully integrated into this automated test matrix yet, meaning the script only tests the C++ verify path.
  • The benchmark runs for exactly 10 rounds of 60 seconds (10 full minutes total).
  • This is a very long wait for a developer just trying to quickly spin up a local testnet.
  • Perhaps we need to introduce a --fast command-line flag for quick-and-dirty calibrations that only run 1 or 2 rounds.
  • The dummy challenge hash uses [0u8; 32] in the benchmark but [0x42u8; 32] in the timing script.
  • This inconsistency has no functional impact, but it might be worth standardizing to avoid developer confusion.