Difficulty: Igneum dual-lane rule measured against Kaspa, Monero and LWMA; spec 2.3, analysis, simulator, devnet and test-network records

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-josh 2026-10-03 22:11:27 +00:00
parent f5f8c805d1
commit e9328c68ab
9 changed files with 6599 additions and 9 deletions

View file

@ -0,0 +1,447 @@
# Difficulty controller for Igneum: data, simulation, implementation, test network
3 October 2026, consensus-engineer. Everything measured here is reproducible from `sim/difficulty/`
(record, simulator, raw outputs) and the `difficulty` branch of `vendor/igneum-node`.
## 1. The question
Igneum's effective hash rate steps every hour when the mining program changes (35 to 48 Mhash/s
across seeds on the M5 Max, 228 vs 185 Mhash/s for 104 vs 128 loads on the RTX 5090,
`docs/bench-log.md`), and GPU miners hop in and out fast. Kaspa's sampled DAA (KIP-4) averages a
44-minute window and holds genesis bits for 600 blocks. The devnet of 3 October showed what that
costs. This document measures five controllers on synthetic steps and on the devnet record, tunes
the Igneum candidate on the synthetic set only, then validates it on the record and on a live
3-node test network.
## 2. Data: the overnight devnet record
`sim/difficulty/devnet-2026-10-03.csv`: 3,682 headers pulled through the observer node's wRPC JSON
endpoint (`getBlocks` from genesis, read only), columns hash, DAA score, blue score, timestamp,
bits, difficulty (2^255 / target, the node's own convention; expected hashes per block is twice
that), log2 target, epoch, chain flag, parent count, selected parent. Genesis bits `0x1d100000`
(difficulty 134,217,727, 2^28 expected hashes per block), sized for the RTX 5090 plus the M5 Max.
Timeline (block 1 at 19:07:49 UTC = 20:07 BST):
| Minutes from block 1 | Blocks | Blocks/s | Bits at end | Difficulty at end | DAA score at end | What was mining |
|---|---|---|---|---|---|---|
| 0 to 5 | 36 | 0.12 | 0x1d100000 | 134,217,727 | 35 | Mac Metal worker, 30 MH/s |
| 5 to 10 | 43 | 0.14 | 0x1d100000 | 134,217,727 | 78 | same |
| 10 to 15 | 24 | 0.08 | 0x1d100000 | 134,217,727 | 102 | same |
| 15 to 20 | 34 | 0.11 | 0x1d100000 | 134,217,727 | 136 | same, stopped at 19:28 UTC |
| 20 to 25 | 3 | 0.01 | 0x1d100000 | 134,217,727 | 139 | about 1.5 MH/s (three isolated blocks) |
| 25 to 30 | 1 | 0.00 | 0x1d100000 | 134,217,727 | 140 | same |
| 30 to 35 | 9 | 0.03 | 0x1d100000 | 134,217,727 | 149 | PC joins at 19:42 UTC |
| 35 to 40 | 172 | 0.57 | 0x1d100000 | 134,217,727 | 321 | PC, 116 MH/s estimated from the blocks (coordinator: 154 MH/s plus 4 MH/s AMD) |
| 40 to 45 | 187 | 0.62 | 0x1d100000 | 134,217,727 | 507 | same |
| 45 to 50 | 421 | 1.40 | 0x1e00b6a3 | 11,758,225 | 925 | first retarget at DAA 600 (19:56:12 UTC): bits 0x1d4caca1, then easing every block |
| 50 to 55 | 1,633 | 5.44 | 0x1e00d950 | 9,882,007 | 2,559 | difficulty bottoms at 8,552,118 (15.7x below genesis); peak 355 blocks in one minute |
| 55 to 60 | 639 | 2.13 | 0x1d36b2ef | 39,260,045 | 3,201 | the window fills with fast blocks, difficulty climbs past the correct level |
| 60 to 65 | 400 | 1.33 | 0x1d3a4442 | 36,856,148 | 3,599 | still above the correct level (about 58 million for 116 MH/s at 1 block/s) |
| 65 to 70 | 79 | 0.26 | 0x1d44da0d | 31,189,959 | 3,680 | epoch boundary at DAA 3,600; the PC's launcher rebuilds its kernel (miner exit 42) and the chain idles |
What Kaspa's rule did, as the coordinator saw it live and the record confirms: genesis difficulty
held through block 600 while the real rate was 0.6 blocks/s; the first retarget saw a window whose
600 blocks spanned 48 minutes (the Metal period, the idle gap, the PC period) and eased 4.8x at
once, then kept easing to 15.7x as more of the slow history entered the average; the chain ran at
5.4 blocks/s for five minutes (1,633 blocks) and 355 blocks in its peak minute; it then overshot
the other way to 1.5x too hard. The DAG widened accordingly: 3,681 blocks against 1,656 chain
blocks.
Hash-rate profile derived from the record (expected hashes of every block over wall time, between
the two visible events, Metal stop 19:28:05 UTC and PC start 19:42:40 UTC):
| From s | To s | Blocks/s at genesis difficulty | MH/s |
|---|---|---|---|
| 0 | 1,215 | 0.114 | 30.7 |
| 1,215 | 2,090 | 0.006 | 1.5 |
| 2,090 | 4,064 | 0.433 | 116.2 |
Exact replay check: the record's own timestamps and bits replayed through rusty-kaspa's integer
arithmetic (`sim.py kaspa_bits_exact`, same sampling rule `(parent_daa + 1) mod 4 = 0`, 661 samples,
earliest sample dropped from the average, compact-bits rounding) reproduce the chain's bits exactly
for the 244 retargets from DAA 600 to 844 and diverge from DAA 845, when the chain had passed
1 block/s and the sampled window began to include merged blocks that a chain-only replay cannot see.
The simulator's Kaspa model is therefore exact on a chain and approximate on a wide DAG.
## 3. Simulator
`sim/difficulty/sim.py`. One selected chain, DAA score = height, 1 block per second target,
solve times exponential with mean (expected hashes) / (hash rate); a hash-rate profile is piecewise
constant in time (miners) or keyed to the 3,600-block epoch (programs); timestamps are the true
times, optionally with uniform jitter. The difficulty unit is 2^28 hashes (the devnet genesis), so
every synthetic run starts at a realistic target far from `MAX_DIFFICULTY_TARGET`.
Metrics. "First within 10%": the first time after a step when the centred 121-block mean of the
expected block rate (hash rate over difficulty) is within 10% of target. "Settled": the first time
it then stays within 10% for 100 blocks. "Overshoot": how far past target the mean goes on the far
side before the next step. "Blocks above 2x / below 0.5x": blocks produced while the expected rate
was outside that band. "Worst gap": longest inter-block time. Steady state: standard deviation of
the expected rate ratio over a steady hour ("steady std"), and the coefficient of variation of
blocks per minute, which is 0.129 for a Poisson process at 60 per minute (the controller adds to it
only when it is noisy).
Controllers.
| Name | Rule | Source |
|---|---|---|
| kaspa | KIP-4 sampled DAA as the fork runs it | `consensus/src/processes/difficulty.rs`, `constants.rs` (661 samples, every 4th block, min 150 samples) |
| monero | 720 blocks, sorted timestamps, 60 cut each side, lag 15 | Monero `next_difficulty`, approximate from memory of the reference |
| lwma60, lwma120 | Zawy LWMA-1: linear weights, average target, solvetimes in [-132 s, 6 T] | Zawy's 2018 reference, approximate from memory |
| igneum | the rule of spec 2.3 | this document |
| igneum-literal | the brief's trigger: short lane engaged while the short-window rate is more than 25% off target | kept to measure the chatter |
Profiles. up50 (x50 at 3 h), down50 (/50 at 3 h), epoch30 (x1.3 or /1.3 drawn at each epoch
boundary from epoch 2, six boundaries), walk10 (10% per hour log random walk), hop3 and hop10 (a 3x
or 10x pool in and out every 15 minutes, twelve steps), polluted (the devnet case: 21 minutes at
0.11 blocks/s, 13 minutes idle, then 0.6 blocks/s at genesis difficulty), warmup-hard (genesis 10x
too hard), warmup-easy (genesis 10x too easy), real (the record's profile above).
## 4. Designing the Igneum candidate
Two findings changed the brief's sketch.
Zawy's LWMA estimator is biased while targets ramp. LWMA computes average target times average
solvetime. While the clamp drags the target down 3% per block after a 50x step, the short window
holds blocks mined at targets from 1x to 15x apart; average target times average solvetime then
over-estimates the target (Jensen), and the fast lane stalled at about 15x of the 50x step for a
full window length (trajectory dump, first implementation). Replacing every lane's estimator with
work over time (the sum of blue-work increments over the sum of clamped solvetimes, linear weights
for the short lane) removed the stall: the 50x step-up settles in 62 s instead of 224 s. This is the
estimator behind Kaspa's `estimateNetworkHashesPerSecond`, so the lineage stays Kaspa's.
The brief's trigger chatters. "Fast lane takes over when the short-window rate departs from target
by more than 25%" releases the fast lane as soon as the short window is back on target, while the
long window is still polluted by the pre-step blocks for 44 minutes; the long lane then pulls the
target the wrong way until the short window departs again. Measured as `igneum-literal`: polluted
case settles at 1,910 s against 72 s, hop3 fails to settle on 8 of 12 steps against 0, hop10 on 10
against 0. The trigger adopted compares the two lanes: the short lane rules while it disagrees with
the reference lane by more than 25%, which stays true exactly as long as the long window is wrong.
The epoch windows. At an epoch boundary the program changes and the hash rate steps; blocks of the
old program carry no information about the new one. Every lane is therefore restricted to the
block's own epoch: the short lane is the newest 120 blocks of the epoch, the reference lane is the
epoch window (blended with the parent's implied rate as k : 16 for small k) until the epoch has 600
blocks and Kaspa's sampled window restricted to the epoch after that. The parent's target is held
for the first 8 blocks of an epoch. Epoch 0 starts at genesis, so this is also the warm-up rule:
the dead zone is 8 blocks, not 600.
Asymmetric clamps. A step down is the painful case for any past-only controller: after a 50x drop
blocks arrive every 50 s and each one is the only evidence. With a symmetric 3% clamp the 50x
step-down took 1,786 s with a 211 s worst gap. The clamps are not what bounds timestamp
manipulation (the symmetric solvetime cap and the cancellation of a forged timestamp by the next
honest block are), so the ease direction can be faster: 10% per block gives 1,164 s and a 65 s
worst gap with no measurable change anywhere else, and widening the cap from 6 to 20 block times
(section 5) brings it to 657 s and 35 s.
## 5. Tuning on the synthetic set
One parameter at a time, two sweeps. Columns per profile: settled s ("rec"), first within 10% s
("first"), steady std; down50 adds the worst gap; polluted adds the peak rate. `clamp_pct` in the
first sweep moves both clamps together (the ease clamp did not exist yet).
First sweep, around the brief's symmetric 3% clamp and the 6 T cap (ns 120, trigger 25%, clamp 3% both ways, prior 16):
#### Tune ns
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ns=60 | 72.8 | 72.8 | 0.069 | 1843.1 | 1843.1 | 0.069 | 114.2 | 127.0 | 80.2 | 0.067 | none | none | 0.089 | 0/0 | 301.6 | 251.0 | 0.069 | 56.4 | 56.4 | 0.083 | 4.4 |
| ns=120 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| ns=180 | 63.3 | 63.3 | 0.035 | 2131.4 | 2131.4 | 0.035 | 125.1 | 156.3 | 99.0 | 0.017 | none | none | 0.056 | 0/0 | 356.1 | 356.1 | 0.035 | 76.5 | 76.5 | 0.042 | 5.6 |
#### Tune trig
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| trig=0.15 | 66.7 | 66.7 | 0.077 | 1591.5 | 1591.5 | 0.077 | 98.7 | 137.3 | 107.3 | 0.070 | none | none | 0.082 | 0/0 | 312.9 | 261.8 | 0.077 | 58.1 | 58.1 | 0.075 | 5.8 |
| trig=0.25 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| trig=0.35 | 62.9 | 62.9 | 0.034 | 1835.4 | 1835.4 | 0.034 | 126.4 | 156.3 | 99.0 | 0.016 | none | none | 0.054 | 0/0 | 328.5 | 279.2 | 0.034 | 78.4 | 78.4 | 0.042 | 5.5 |
#### Tune clamp_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| clamp_pct=0.02 | 84.0 | 84.0 | 0.039 | 2588.2 | 2588.2 | 0.039 | 216.8 | 156.3 | 99.4 | 0.031 | none | none | 0.060 | 0/0 | 373.6 | 336.9 | 0.039 | 80.1 | 80.1 | 0.046 | 5.4 |
| clamp_pct=0.03 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| clamp_pct=0.05 | 62.2 | 62.2 | 0.040 | 1312.5 | 1312.5 | 0.040 | 173.8 | 155.8 | 98.9 | 0.034 | none | none | 0.062 | 0/0 | 284.6 | 258.8 | 0.040 | 59.9 | 59.9 | 0.049 | 5.4 |
#### Tune ease_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ease_pct=0.03 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| ease_pct=0.06 | 62.7 | 62.7 | 0.039 | 1282.5 | 1282.5 | 0.039 | 176.0 | 155.6 | 98.8 | 0.031 | none | none | 0.061 | 0/0 | 299.5 | 265.0 | 0.039 | 58.5 | 58.5 | 0.048 | 5.8 |
| ease_pct=0.1 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
#### Tune k0
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| k0=8 | 63.0 | 63.0 | 0.040 | 1786.6 | 1786.6 | 0.040 | 211.1 | 156.0 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 311.5 | 280.9 | 0.040 | 67.1 | 67.1 | 0.048 | 5.5 |
| k0=16 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| k0=32 | 63.0 | 63.0 | 0.040 | 1786.9 | 1786.9 | 0.040 | 211.9 | 156.0 | 99.1 | 0.033 | none | none | 0.061 | 0/0 | 290.0 | 263.0 | 0.040 | 71.4 | 71.4 | 0.047 | 5.7 |
#### Tune cap
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cap=6 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| cap=20 | 63.8 | 63.8 | 0.039 | 1703.7 | 1703.7 | 0.039 | 165.8 | 143.7 | 86.8 | 0.033 | none | none | 0.061 | 0/0 | 274.9 | 257.2 | 0.039 | 62.9 | 62.9 | 0.048 | 5.8 |
| cap=132 | 63.8 | 63.8 | 0.039 | 1649.6 | 1649.6 | 0.039 | 161.0 | 143.7 | 86.8 | 0.033 | none | none | 0.061 | 0/0 | 263.5 | 238.2 | 0.039 | 75.8 | 75.8 | 0.048 | 13.1 |
Second sweep, around the adopted defaults (ns 120, trigger 25%, harden 3%, ease 10%, prior 16, cap 6 T at the time of the sweep; the cap row decided the final value):
#### Tune ns
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ns=60 | 57.5 | 57.5 | 0.080 | 595.9 | 595.9 | 0.080 | 87.5 | 120.2 | 92.6 | 0.076 | none | none | 0.093 | 0/0 | 201.2 | 152.1 | 0.080 | 60.8 | 60.8 | 0.090 | 5.2 |
| ns=120 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| ns=180 | 63.5 | 63.5 | 0.034 | 1742.3 | 1742.3 | 0.034 | 93.4 | 155.8 | 98.8 | 0.017 | none | none | 0.056 | 0/0 | 354.2 | 344.3 | 0.034 | 77.3 | 77.3 | 0.042 | 5.7 |
#### Tune trig
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| trig=0.15 | 63.2 | 63.2 | 0.078 | 1098.2 | 1098.2 | 0.078 | 75.1 | 136.1 | 106.1 | 0.072 | none | none | 0.083 | 0/0 | 244.8 | 234.9 | 0.078 | 62.1 | 62.1 | 0.075 | 5.2 |
| trig=0.25 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| trig=0.35 | 63.5 | 63.5 | 0.034 | 1238.4 | 1238.4 | 0.034 | 75.5 | 155.8 | 98.8 | 0.016 | none | none | 0.054 | 0/0 | 276.3 | 263.9 | 0.034 | 63.1 | 63.1 | 0.042 | 5.8 |
#### Tune clamp_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| clamp_pct=0.02 | 97.1 | 97.1 | 0.037 | 1091.3 | 1091.3 | 0.037 | 71.6 | 155.3 | 98.7 | 0.027 | none | none | 0.061 | 0/0 | 269.7 | 258.5 | 0.037 | 71.9 | 71.9 | 0.048 | 6.2 |
| clamp_pct=0.03 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| clamp_pct=0.05 | 60.1 | 60.1 | 0.040 | 1371.6 | 1371.6 | 0.040 | 175.9 | 155.5 | 98.6 | 0.033 | none | none | 0.063 | 0/0 | 295.1 | 262.7 | 0.040 | 71.6 | 71.6 | 0.049 | 6.1 |
#### Tune ease_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ease_pct=0.03 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| ease_pct=0.06 | 62.7 | 62.7 | 0.039 | 1282.5 | 1282.5 | 0.039 | 176.0 | 155.6 | 98.8 | 0.031 | none | none | 0.061 | 0/0 | 299.5 | 265.0 | 0.039 | 58.5 | 58.5 | 0.048 | 5.8 |
| ease_pct=0.1 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| ease_pct=0.15 | 61.6 | 61.6 | 0.038 | 1111.4 | 1111.4 | 0.038 | 65.4 | 155.2 | 98.4 | 0.030 | none | none | 0.062 | 0/0 | 261.6 | 250.5 | 0.038 | 67.1 | 67.1 | 0.048 | 6.4 |
#### Tune k0
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| k0=8 | 62.2 | 62.2 | 0.038 | 1144.7 | 1144.7 | 0.038 | 65.3 | 155.5 | 98.7 | 0.030 | none | none | 0.062 | 0/0 | 247.7 | 247.7 | 0.038 | 70.2 | 70.2 | 0.048 | 6.2 |
| k0=16 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| k0=32 | 62.2 | 62.2 | 0.038 | 1169.9 | 1169.9 | 0.038 | 65.3 | 155.7 | 98.9 | 0.030 | none | none | 0.061 | 0/0 | 287.5 | 266.4 | 0.038 | 71.4 | 71.4 | 0.048 | 6.3 |
#### Tune cap
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cap=6 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| cap=20 | 61.7 | 61.7 | 0.038 | 657.4 | 657.4 | 0.038 | 35.3 | 143.7 | 86.5 | 0.030 | none | none | 0.062 | 0/0 | 211.9 | 208.1 | 0.038 | 70.0 | 70.0 | 0.049 | 8.5 |
| cap=132 | 61.7 | 61.7 | 0.038 | 629.5 | 629.5 | 0.038 | 32.4 | 143.7 | 86.5 | 0.030 | none | none | 0.062 | 0/0 | 212.9 | 209.1 | 0.038 | 63.8 | 63.8 | 0.049 | 16.4 |
Decisions: ns 120 (60 doubles the steady noise, 180 is slower on hops); trigger 25% (15% engages on
noise and doubles the steady noise, 35% slower on hops); harden 3% (2% slows the step-up to 97 s,
5% buys nothing on the up step and worsens the down step's gap); ease 10% (3% leaves the step-down
at 1,786 s, 15% buys 50 s more at no cost but was not adopted to keep a margin); prior 16 (no effect
across 8 to 32); cap 20 T (6 T leaves the step-down at 1,164 s, 20 T gives 657 s and a 35 s worst
gap and also helps the epoch steps and hopping; the 132 s cap, Kaspa's future tolerance, gains
nothing more and lets the idle gap of the polluted case over-ease the target to a 16x peak against
8.5x at 20 T and 6.2x at 6 T). The cap is not the manipulation bound: under the symmetric cap a
forged timestamp's contribution is cancelled by the next honest block whatever the cap, so the cap
only decides how much a genuine slow block may say. Nothing was tuned on the record.
## 6. Results on the synthetic set
Seed 7, honest timestamps, final parameters. Full output in `sim/difficulty/results.md`.
#### up50: hash rate x50 at 3 h
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1542.1 | 1542.1 | 4967 | 0.009 | 0.012 | 0.126 | 9.5 | 3760 | 0 | 21511 | none |
| monero | 94.0 | 94.0 | 713 | 0.089 | 0.037 | 0.144 | 10.3 | 657 | 0 | 18490 | none |
| lwma60 | 105.0 | 105.0 | 182 | 0.208 | 0.131 | 0.088 | 11.3 | 78 | 0 | 17977 | none |
| lwma120 | 231.2 | 231.2 | 388 | 0.189 | 0.092 | 0.096 | 10.5 | 164 | 0 | 18078 | none |
| igneum | 61.7 | 61.7 | 181 | 0.140 | 0.038 | 0.135 | 11.3 | 125 | 0 | 17927 | 377 |
| igneum-literal | 63.7 | 63.7 | 186 | 0.217 | 0.034 | 0.130 | 11.3 | 127 | 0 | 17956 | 230 |
#### down50: hash rate /50 at 3 h
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 12295.7 | 12295.7 | 1569 | 5.102 | 0.012 | 0.126 | 178.5 | 1648 | 1256 | 16185 | none |
| monero | 6432.8 | 6432.8 | 701 | 0.039 | 0.037 | 0.144 | 187.3 | 0 | 567 | 19356 | none |
| lwma60 | 577.9 | 577.9 | 111 | 0.154 | 0.131 | 0.088 | 118.7 | 0 | 55 | 24588 | none |
| lwma120 | 1073.6 | 1073.6 | 177 | 0.129 | 0.092 | 0.096 | 64.9 | 0 | 97 | 24205 | none |
| igneum | 657.4 | 657.4 | 147 | 0.062 | 0.038 | 0.135 | 35.3 | 0 | 90 | 24548 | 474 |
| igneum-literal | 967.0 | 967.0 | 146 | 0.062 | 0.034 | 0.130 | 121.1 | 0 | 86 | 24276 | 267 |
#### epoch30: x1.3 or /1.3 at each epoch boundary from epoch 2
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1583.0 | 1583.0 | 1974.6 | 0 | 0.043 | 0.010 | 0.130 | 12.3 | 0 | 0 | 29403 | none |
| monero | 456.2 | 456.2 | 603.3 | 0 | 0.116 | 0.038 | 0.141 | 16.0 | 0 | 0 | 28857 | none |
| lwma60 | 91.1 | 156.9 | 310.6 | 0 | 0.291 | 0.132 | 0.090 | 11.6 | 0 | 0 | 28658 | none |
| lwma120 | 136.9 | 153.1 | 240.8 | 0 | 0.229 | 0.094 | 0.097 | 11.5 | 0 | 0 | 28719 | none |
| igneum | 86.5 | 143.7 | 260.8 | 0 | 0.126 | 0.030 | 0.133 | 11.3 | 0 | 0 | 28735 | 351 |
| igneum-literal | 86.4 | 143.8 | 260.8 | 0 | 0.050 | 0.018 | 0.130 | 11.3 | 0 | 0 | 28775 | 149 |
#### walk10: 10% per hour log random walk
| controller | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|
| kaspa | 0.049 | 0.144 | 11.1 | 0 | 0 | 21728 | none |
| monero | 0.049 | 0.143 | 10.6 | 0 | 0 | 21541 | none |
| lwma60 | 0.132 | 0.088 | 11.1 | 0 | 0 | 21486 | none |
| lwma120 | 0.093 | 0.105 | 10.5 | 0 | 0 | 21518 | none |
| igneum | 0.062 | 0.130 | 11.2 | 0 | 0 | 21521 | 519 |
| igneum-literal | 0.055 | 0.136 | 11.1 | 0 | 0 | 21523 | 184 |
#### hop3: 3x pool hops in and out every 15 min from 3 h
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | none | none | none | 12 | 0 | 0.012 | 0.126 | 21.0 | 1208 | 1036 | 22128 | none |
| monero | 336.7 | 336.7 | 362.4 | 6 | 0.062 | 0.037 | 0.144 | 18.6 | 2438 | 1437 | 20382 | none |
| lwma60 | 109.5 | 166.9 | 373.4 | 0 | 0.270 | 0.131 | 0.088 | 14.7 | 111 | 42 | 21408 | none |
| lwma120 | 161.8 | 184.3 | 292.1 | 0 | 0.201 | 0.092 | 0.096 | 15.0 | 215 | 129 | 21364 | none |
| igneum | 124.0 | 189.5 | 410.3 | 0 | 0.229 | 0.038 | 0.135 | 11.8 | 213 | 182 | 21228 | 8054 |
| igneum-literal | 429.3 | 372.9 | 493.1 | 8 | 0.202 | 0.034 | 0.130 | 16.1 | 86 | 529 | 20913 | 4294 |
#### hop10: 10x pool hops in and out every 15 min from 3 h
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | none | none | none | 12 | 0 | 0.012 | 0.126 | 52.1 | 5700 | 1035 | 22951 | none |
| monero | 283.5 | 283.5 | 312.9 | 6 | 0.085 | 0.037 | 0.144 | 83.2 | 3551 | 569 | 19203 | none |
| lwma60 | 165.5 | 263.9 | 542.0 | 0 | 0.242 | 0.131 | 0.088 | 34.3 | 315 | 198 | 21008 | none |
| lwma120 | 282.8 | 325.5 | 789.0 | 0 | 0.199 | 0.092 | 0.096 | 30.2 | 576 | 417 | 20676 | none |
| igneum | 208.1 | 211.9 | 382.9 | 0 | 0.230 | 0.038 | 0.135 | 25.0 | 422 | 321 | 20631 | 8445 |
| igneum-literal | 466.6 | 505.8 | 698.0 | 6 | 0.237 | 0.034 | 0.130 | 35.7 | 816 | 666 | 20683 | 5432 |
#### polluted: window polluted by a pre-step slow period (21 min at 0.11 blocks/s, 13 min idle, then 0.6 blocks/s at genesis difficulty)
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks | peak rate | trough rate after 10 min |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 650.7 | 2748.3 | 5163 | 6.942 | 0.010 | 0.134 | 11.2 | 2320 | 0 | 18592 | none | 7.9 | 0.6 |
| monero | 124.0 | 124.0 | 735 | 0.092 | 0.032 | 0.142 | 10.4 | 675 | 0 | 17641 | none | 15.0 | 0.9 |
| lwma60 | 66.1 | 66.1 | 113 | 0.183 | 0.132 | 0.089 | 11.3 | 36 | 0 | 17138 | none | 5.7 | 0.6 |
| lwma120 | 110.3 | 110.3 | 201 | 0.147 | 0.091 | 0.108 | 10.5 | 74 | 0 | 17190 | none | 6.0 | 0.7 |
| igneum | 70.0 | 70.0 | 136 | 0.180 | 0.049 | 0.122 | 11.3 | 72 | 0 | 17005 | 2585 | 8.5 | 0.6 |
| igneum-literal | 817.0 | 1876.4 | 2546 | 0.197 | 0.040 | 0.141 | 11.3 | 571 | 0 | 17618 | 1986 | 8.4 | 0.6 |
#### warmup-hard: genesis difficulty 10x too hard
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 5790.7 | none | none | 92.876 | 32.727 | 3.002 | 67.2 | 4860 | 599 | 6444 | none |
| monero | 286.6 | 286.6 | 159 | 0.047 | 0.038 | 0.135 | 17.5 | 0 | 33 | 7011 | none |
| lwma60 | 155.2 | 155.2 | 118 | 0.147 | 0.132 | 0.078 | 10.5 | 0 | 18 | 7128 | none |
| lwma120 | 187.9 | 187.9 | 152 | 0.111 | 0.095 | 0.102 | 10.5 | 0 | 18 | 7128 | none |
| igneum | 321.9 | 321.9 | 199 | 0.024 | 0.031 | 0.126 | 10.5 | 0 | 23 | 6953 | 138 |
| igneum-literal | 351.4 | 702.7 | 568 | 0.024 | 0.019 | 0.134 | 10.5 | 0 | 23 | 6979 | 179 |
#### warmup-easy: genesis difficulty 10x too easy
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1554.8 | 1554.8 | 3168 | 0.011 | 0.008 | 0.137 | 9.8 | 599 | 0 | 8869 | none |
| monero | 95.4 | 95.4 | 76 | 0.091 | 0.038 | 0.154 | 16.1 | 16 | 16 | 7145 | none |
| lwma60 | 57.9 | 57.9 | 87 | 0.183 | 0.133 | 0.073 | 9.2 | 8 | 0 | 7192 | none |
| lwma120 | 88.3 | 88.3 | 136 | 0.147 | 0.096 | 0.099 | 9.4 | 8 | 0 | 7225 | none |
| igneum | 58.7 | 58.7 | 114 | 0.082 | 0.029 | 0.133 | 10.3 | 61 | 0 | 7192 | 120 |
| igneum-literal | 58.4 | 58.4 | 114 | 0.086 | 0.015 | 0.129 | 10.3 | 61 | 0 | 7209 | 191 |
With +-500 ms timestamp jitter (half a block time, a harsh clock model), the ordering holds:
up50 settled Kaspa 1,535 s, LWMA60 159 s, LWMA120 140 s, Igneum 169 s; down50 Kaspa 12,523 s,
LWMA60 847 s, LWMA120 966 s, Igneum 1,047 s (first within 10% at 896 s, worst gap 49 s against 78
and 81); epoch30 Kaspa 1,535 s, LWMA60 63 s, LWMA120 124 s, Igneum 55 s; hop10 Kaspa never, LWMA60
210 s, LWMA120 318 s, Igneum 228 s; polluted Kaspa 2,722 s (peak 8.5x), LWMA60 89 s, LWMA120 174 s,
Igneum 71 s; steady std Kaspa 0.010, LWMA60 0.125, LWMA120 0.085, Igneum 0.046. The jitter costs the
Igneum short lane on the 50x cases because 20 ms solve times are drowned by 500 ms clock noise; it
still beats Kaspa by 9x and 12x there.
Reading. Kaspa's rule has the lowest steady-state noise (0.012) and the worst response to every
step: 26 minutes for a 50x up, 3.4 hours for a 50x down with a 5x overshoot, never settling under
pool hopping, 46 minutes and a 7.9x peak on the devnet case, and a 600-block dead zone that turns
a 10x-too-hard genesis into 97 minutes of near-silence. LWMA 60 is the fastest on step-downs and
the noisiest (0.132, 13% block-rate swings all day). The Igneum rule keeps the steady noise at
0.038 (three times Kaspa's, less than half of LWMA120's), is the fastest on the 50x step-up (62 s),
the epoch steps (144 s settled, 87 s first within 10%, against Kaspa's 1,583 s), the devnet case
(70 s), the 3x and 10x hopping (190 s and 212 s) and the too-easy genesis (59 s), second to LWMA60
on the 50x step-down (657 s against 578 s, with a 35 s worst gap against 119 s) and on the too-hard
genesis (322 s against 155 s, the price of the 10% ease clamp). On the polluted case every past-only
controller shows a peak when the PC arrives, because the difficulty was correctly set for the Metal
miner; the number that matters is how long the peak lasts: Igneum 70 s, Kaspa 46 minutes with a
second, larger peak of its own making.
## 7. Validation on the record
The record's hash-rate profile (section 2) replayed through every controller, not tuned on it:
| Controller | First within 10% s | Settled s | Overshoot | Blocks above 2x | Blocks within 10% of target (of about 3,700) | Fast-lane blocks |
|---|---|---|---|---|---|---|
| kaspa | 262 | never | 6.24 (peak 7.6x) | 2,340 | 0 | |
| monero | 136 | 136 | 0.09 | 672 | 2,822 | |
| lwma60 | 75 | 75 | 0.18 | 87 | 2,175 | |
| lwma120 | 157 | 157 | 0.15 | 174 | 2,390 | |
| igneum | 79 | 79 | 0.18 | 132 | 2,608 | 2,484 |
| igneum-literal | 482 | 1,516 | 0.01 | 515 | 2,170 | 1,959 |
The simulated Kaspa trajectory reproduces the live one: a late first retarget, a multi-x peak,
thousands of blocks above 2x, no settling within the record. The Igneum rule settles 79 s after the
PC arrives; it runs on the short lane for most of the remaining 33 minutes (2,484 blocks) because
the long window stays polluted by the 75x step for 44 minutes, which is the designed behaviour.
## 8. Implementation
Branch `difficulty` of `vendor/igneum-node` (worktree `vendor/igneum-node-diff`).
| File | Change |
|---|---|
| `consensus/core/src/igneum.rs` | `DifficultyRule { KaspaSampled, IgneumDual }` (serde kebab-case) and the `difficulty` constants module (SHORT_WINDOW 120, EPOCH_HOLD 8, PRIOR_BLOCKS 16, LONG_MIN 600, TRIGGER_PERCENT 25, HARDEN_PERCENT 3, EASE_PERCENT 10, CAP_BLOCKS 20) |
| `consensus/core/src/config/params.rs` | `Params.difficulty_rule` (IgneumDual on all four networks), `OverrideParams.difficulty_rule` and `OverrideParams.genesis_bits` (test networks: the genesis hash is recomputed from the new bits) |
| `consensus/src/processes/difficulty.rs` | `calculate_difficulty_bits(window, ghostdag, daa_score)` dispatches on the rule; `kaspa_difficulty_bits` is the old body unchanged; `igneum_difficulty_bits` gathers the chain steps (blue-work increment and clamped solvetime per selected-chain block of the epoch, at most 600 or 120) and the in-epoch samples of the window, then calls the pure `igneum_target` (Uint320 arithmetic, rates as unreduced fractions, no floating point) |
| `consensus/src/processes/window.rs`, `consensus/src/consensus/services.rs` | the rule and the epoch length threaded to the manager; the DAA score of the block passed to the retarget |
| `testing/integration/src/consensus_integration_tests.rs` | `difficulty_test` pins `KaspaSampled` (it asserts Kaspa's behaviour on mainnet parameters) |
Unit tests (`cargo test -p kaspa-consensus --lib difficulty`, 9 tests): hold for the first 8
blocks, steady state keeps the target within 0.1%, a 50x step up hardens by exactly 3% per block,
a 50x step down eases by exactly 10% per block, the short lane takes over only beyond 25%
disagreement with the long lane (on target and 20% fast stay on the long lane, 2x fast binds the
3% clamp), the epoch lane's shrinkage (8 blocks at 10% slower move the target 3.1%, 8 blocks at half
speed bind the 10% clamp), the maximum target is never exceeded, plus Kaspa's two level-work tests.
`cargo test -p kaspa-consensus-core --lib params`: 4 tests including the override-file parsing.
Cost: up to 600 compact-header and ghostdag reads per header for the first 600 blocks of an epoch,
120 afterwards, all from in-memory caches at 1 BPS. Re-measure before the 4 BPS step.
## 9. Test network
Three `igneumd` nodes from the `difficulty` branch (gRPC 26800, 26810, 26820; P2P 26801, 26811, 26821; node 2 and node 3 `--connect` to node 1; appdir `/tmp/igneum-diff-test/igneum`; override file `{"difficulty_rule": "igneum-dual", "genesis_bits": 505413632}`, i.e. bits `0x1e200000`, 2^19 expected hashes, genesis hash `f682b78a...e099`), three `igneum-miner mine --engine igneum-pow` CPU miners of 4 threads each with their own payout identities, on the shared Mac at load average 50 to 90 (two other agents were building). Schedule: miner A on node 1 for 1,200 s from block 1; miners B and C on nodes 2 and 3 from 359 s to 779 s. 1,133 blocks, 0 rejected (814 + 159 + 160 found), one sink at the end. Record: `sim/difficulty/testnet-igneum-2026-10-03.csv`.
What the miners delivered (expected hashes over wall time, the same estimator the simulator's record profile uses): 0.0653 MH/s with A alone, 0.0988 MH/s with A, B and C (1.51x, not 3x: three 4-thread miners on a loaded 18-core Mac share the cores, B and C each reported 0.032 MH/s against A's 0.054), 0.0686 MH/s after B and C stopped (0.69x). Genesis difficulty 262,144 was 4.0x too hard for A alone (the correct value is about 65,000).
| Event | Measured, first within 10% (61-block mean) | Simulator prediction on the same profile, 5 seeds | Kaspa's rule, simulator |
|---|---|---|---|
| Warm-up from a 4x too hard genesis | 132 s (held 8 blocks, then eased 10% per block; difficulty 190,283 at 60 s, 62,248 at 90 s) | 3, 92, 263, 278, 309 s (median 263) | never within 20 min (no retarget before block 600; 337 to 378 blocks in 20 min) |
| Miners B and C join, x1.51 | 214 s (within 10% at 23 s, then a 1.9 blocks/s burst at 360 to 390 s as the DAA score jumped with the merged blocks) | 49, 54, 61, 87, 180 s (median 61) | never |
| Miners B and C leave, /1.45 | 85 s | 110, 231, 300 s and twice not within the remaining 413 s | never |
| Worst gap after the warm-up | 7.3 s | 4.5 to 13.0 s | 13 to 21 s |
Reading: the live network recovered faster than the simulator on the warm-up and the step-down and slower on the step-up. The differences have a cause the simulator does not model: the measured hash rate of CPU miners on a loaded machine swings by 2x between 30-second bins (0.03 to 0.15 MH/s in the table above), so a 1.5x step is of the same size as the noise, and the 61-block mean crosses the 10% band on noise as often as on control. The warm-up, the one clean signal (4x), came in at 132 s against a simulator median of 263 s, both an order of magnitude inside Kaspa's 600-block dead zone (which at 0.25 blocks/s would have lasted 40 minutes). Kaspa's rule, live, on the same network and genesis (`sim/difficulty/testnet-kaspa-2026-10-03.csv`, override `{"difficulty_rule": "kaspa-sampled", "genesis_bits": 505413632}`, 10 minutes, A for 600 s, B and C from 200 s to 500 s): 70 blocks, bits `0x1e200000` on all 70 (difficulty 262,144 never moved), 0.08 blocks/s, worst gap 63 s, delivered hash rate 0.042 MH/s (the other agents' builds were heavier during this run). Never within 10% of target at any point: the 600-block dead zone would have ended after about 2 hours at that rate. The Igneum run on the same genesis was within 10% at 132 s.
Per-30-second table (blocks, mean difficulty, work over time) in `sim/difficulty/results.md`.
## 10. Limits and what is still open
The simulator has no DAG: blocks off the selected chain enter the implementation only through blue
work, and the record replay is chain-only (exact for 244 retargets, approximate after). No network
latency, no template refresh delay. Monero and LWMA are reproduced from memory of their reference
code and labelled approximate. The step-down remains the slowest case (657 s for 50x); real-time
targeting would cut it further and is left out on purpose (spec 2.3). Red blocks' work is ignored
by every lane, as by Kaspa's estimator. The 4 BPS and 10 BPS steps need the sampled window to
replace the chain walk for the epoch lane and a re-derivation of the block-count constants.

View file

@ -260,7 +260,7 @@ Record: 3,682 headers of the overnight devnet pulled read-only through the obser
Simulator `sim/difficulty/sim.py` (Python, one chain, exponential solve times, 1 BPS): Kaspa sampled DAA, Monero 720, LWMA 60 and 120, the Igneum rule and the brief's literal trigger, on nine synthetic profiles plus the record. Two design findings: Zawy's average-target LWMA estimator is biased while targets ramp (the fast lane stalled at 15x of a 50x step), so every Igneum lane uses work over time (Kaspa's `estimateNetworkHashesPerSecond` estimator); the brief's trigger (short-window rate off target) chatters once the short window is back on target while the long window is still polluted (polluted case 1,876 s against 70 s), so the trigger compares the two lanes. Tuned on the synthetic set only: short window 120, hold 8, prior 16, long lane from 600 blocks of the epoch, trigger 25%, harden 3% per block, ease 10% per block, solvetime cap 20 T.
Settled seconds (121-block mean within 10% for 100 blocks), Kaspa / Monero / LWMA60 / LWMA120 / Igneum: x50 step 1,542 / 94 / 105 / 231 / 62; /50 step 12,296 / 6,433 / 578 / 1,074 / 657 (worst gap 179 / 187 / 119 / 65 / 35 s); epoch +-30% steps 1,583 / 456 / 157 / 153 / 144; 10x hopping never / 284 (6 of 12 never) / 264 / 326 / 212; polluted window 2,748 (peak 7.9x) / 124 (peak 15x) / 66 / 110 / 70; genesis 10x too hard never / 287 / 155 / 188 / 322; steady std of rate 0.012 / 0.037 / 0.131 / 0.092 / 0.038; the record's 75x step never (peak 7.6x, 2,340 blocks above 2x) / 136 / 75 / 157 / 79. With +-500 ms timestamp jitter the ordering holds (Igneum x50 169 s, /50 1,047 s, epoch 55 s, polluted 71 s).
Implementation: `DifficultyRule { KaspaSampled, IgneumDual }` as a network parameter (IgneumDual on all four networks, `"difficulty_rule": "kaspa-sampled"` in the override file selects Kaspa's), `OverrideParams.genesis_bits` (genesis hash recomputed) for test networks, `SampledDifficultyManager::igneum_difficulty_bits` over a selected-chain walk plus the in-epoch samples of the existing window, pure integer core `igneum_target` (Uint320). `cargo test -p kaspa-consensus --lib difficulty`: 9 pass (hold, steady state, 3% harden, 10% ease, trigger, epoch shrinkage, max target, Kaspa's two level-work tests); `cargo test -p kaspa-consensus-core --lib params`: 4 pass. The miner now takes the genesis from the node (pruning point before the first pruning) so it mines an override-genesis network; before the fix it hashed the compiled devnet genesis as the epoch seed and every block was rejected.
TESTNET_BENCH
Test network (ports 26800 to 26821, appdir /tmp/igneum-diff-test, override file with `difficulty_rule` and `genesis_bits` 0x1e200000): 3 igneumd nodes, 3 CPU miners of 4 threads (A for 1,200 s; B and C from 359 s to 779 s), 1,133 blocks, 0 rejected, one sink. Delivered hash rate 0.0653 / 0.0988 / 0.0686 MH/s (the join is x1.51, not 3x: shared cores at load 50 to 90). Genesis 4x too hard. Measured first within 10% of 1 block/s (61-block mean): warm-up 132 s, join 214 s (23 s to first touch), leave 85 s; simulator on the same profile, 5 seeds: medians 263 s, 61 s, 231 s; worst gap 7.3 s. Kaspa's rule on the same genesis: no retarget inside 20 minutes in any seed (600-block dead zone). Kaspa's rule live on the same genesis, 10 minutes: 70 blocks, bits unchanged on all 70, 0.08 blocks/s, worst gap 63 s, never within 10% of target.
Not done: no DAG in the simulator (red blocks' work is ignored by every lane, as by Kaspa's estimator); Monero and LWMA reproduced from memory (approximate); real-time targeting left out; the chain walk (600 reads per header early in an epoch) must be re-measured before the 4 BPS step.
## 3 October 2026, igneum-node devnet v2: sustained-mining finality rule v2 on a four-miner test network, and as a follower of the live devnet (consensus-engineer and cryptographer)

View file

@ -35,17 +35,59 @@ What the nonce commits to: the pre-PoW header hash `H` enters the register initi
## 2.3 Difficulty adjustment (fork points c1, c2)
Kaspa's sampled DAA (KIP-4) is kept as the retarget: average target of a sampled window, `new_target = avg x measured / expected`, clamped to `MAX_DIFFICULTY_TARGET` = 2^255 - 1, minimum window 150 samples (`difficulty.rs:97 to 198`).
Status: Implemented on the `difficulty` branch of `vendor/igneum-node` (`SampledDifficultyManager::igneum_difficulty_bits`, pure core `igneum_target`, constants `kaspa_consensus_core::igneum::difficulty`), measured in `sim/difficulty/` and on a 3-node CPU test network (`docs/analysis/difficulty-2026-10-03.md`). Network parameter `difficulty_rule`: `igneum-dual` on every Igneum network, `kaspa-sampled` selectable through the override file (`{"difficulty_rule": "kaspa-sampled"}`) so the two rules can be compared on the same genesis. This closes O-2.1: the window is not shortened and the cost-equalising rule of section 1.4.2 is no longer load-bearing for difficulty (it remains desirable for the verifier bound).
| Parameter | Value | Label |
|---|---|---|
| Window duration | 2,641 DAA s (Kaspa's `DIFFICULTY_WINDOW_DURATION`) | Designed, prototype value, to be fixed at gate 2. The design document says "about 2,600 blocks, roughly 45 minutes" |
| Sample interval | 4 s, 661 samples | Kaspa's constants, kept |
| Keyed to | DAA score, never wall-clock | Designed |
Lineage, credited: the long lane is Kaspa's KIP-4 sampled window (`difficulty.rs`, samples at every 4th block, 661 samples, `estimateNetworkHashesPerSecond`'s blue-work-over-time estimator); the short lane is Zawy's LWMA weighting (linear weights rising to the newest block, solvetimes clamped symmetrically, 20 target times here against Zawy's 6) applied to the same work-over-time estimator instead of LWMA's average target, which is biased while targets ramp (measured: the fast lane stalled at 15x of a 50x step before the change, `docs/analysis/difficulty-2026-10-03.md`).
What fixes the window: the hash rate steps at every epoch because programs differ in cost (35 to 48 Mhash/s across seeds on the M5 Max, `docs/bench-log.md` first-run entry; 228 vs 185 Mhash/s for 104 vs 128 loads on the RTX 5090). With a 44-minute window a 30% step means roughly half an epoch at the wrong block rate (fork map c1). Two remedies, decided at gate 2 by a simpa run (fork map c2): shorten the window (candidate 1,800 s), or equalise cost per program through the exact-load-count rule of section 1.4.2, which this specification prefers because it removes the step rather than tracking it.
### The rule
The 30-day vote-weight window of section 3 is denominated in past-median time since 3 October 2026 (section 3.1 W2, ledger F14), so a retarget lag cannot age it; it is not a retarget change.
1. Every lane estimates the hash rate as work over time (blue-work increment over clamped solvetime), never as average target times average solvetime.
2. Short lane S: the newest 120 selected-chain blocks of the block's own epoch, linear weights. Reference lane R: for the first 600 blocks of an epoch the epoch window (all its blocks, blended with the parent's implied rate as k : 16); from block 600 of the epoch Kaspa's sampled window restricted to the epoch.
3. S takes over when S and R differ by more than 25%; otherwise R rules. The trigger compares the two lanes, not the short rate to target (the literal form chatters once the short window is back on target while the long window is still polluted: measured 1,910 s against 72 s to settle on the polluted-window case).
4. The output may make the target fall (difficulty rise) by at most 3% per block and rise (difficulty fall) by at most 10% per block, relative to the selected parent's target, then is capped at `MAX_DIFFICULTY_TARGET`.
5. The parent's target is held for the first 8 blocks of every epoch, including epoch 0: the warm-up starts at block 8, not 600.
| Parameter | Value | Constant | Label |
|---|---|---|---|
| Epoch | 3,600 DAA s, the block's own DAA score (section 1.12) | `POW_EPOCH_BLOCKS` | Designed |
| Short window | 120 chain blocks | `SHORT_WINDOW` | Measured: 60 is noisier (steady std 0.069 vs 0.039), 180 slower on hops (356 s vs 276 s) |
| Hold at epoch start | 8 blocks | `EPOCH_HOLD` | Designed |
| Epoch-lane prior weight | 16 blocks | `PRIOR_BLOCKS` | Measured: 8, 16, 32 indistinguishable |
| Long lane from | 600 blocks of the epoch (150 samples) | `LONG_MIN` | Kaspa's minimum window, kept |
| Long window | 661 samples at 4-block intervals (2,644 blocks), restricted to the epoch | Kaspa's constants, kept | Designed |
| Trigger | 25% disagreement between S and R | `TRIGGER_PERCENT` | Measured: 15% engages on noise (steady std 0.077), 35% slower on hops |
| Harden clamp | 3% per block | `HARDEN_PERCENT` | Measured: 2% slows the 50x step-up to 84 s from 62 s, 5% buys nothing on the up step |
| Ease clamp | 10% per block | `EASE_PERCENT` | Measured: 3% leaves the 50x step-down at 1,786 s and a 211 s worst gap, 10% gives 1,164 s and 65 s |
| Solvetime cap | 20 target times, both signs | `CAP_BLOCKS` | Measured: Zawy's 6 T leaves the 50x step-down at 1,164 s, 20 T gives 657 s and a 35 s worst gap; 132 s (the future tolerance) gains nothing more and lets an idle gap over-ease (16x peak against 8.5x on the devnet case) |
| Timestamp rules | 132 s future tolerance in isolation, strictly above the sampled past median in context | Kaspa's, unchanged | Designed |
What the clamps and caps bound. A forged timestamp can move one capped solvetime by at most 20 s in either direction and the next honest block's solvetime cancels it under the symmetric cap (a run of L forged blocks nets L - 1 s of real time), so a minority cannot bias a lane by more than a few percent; the per-block clamps bound how fast any estimator error, honest or not, reaches the target. The 3% harden clamp means a 50x step up is corrected in about 130 blocks (a few seconds of wall time at 50x), the 10% ease clamp means a 50x step down costs about 40 blocks of shrinking gaps (11 minutes measured, against 3.4 hours for Kaspa's rule).
### Measured comparison
Simulator (`sim/difficulty/sim.py`, seed 7, honest timestamps; the full tables with overshoot, gap and noise columns are in the analysis document). "Settled" is the first time the 121-block mean rate stays within 10% of target for 100 blocks.
| Case | Kaspa sampled DAA | Monero 720 | LWMA 60 | LWMA 120 | Igneum |
|---|---|---|---|---|---|
| Hash rate x50, settled s | 1,542 | 94 | 105 | 231 | 62 |
| Hash rate /50, settled s | 12,296 | 6,433 | 578 | 1,074 | 657 |
| Hash rate /50, worst gap s | 179 | 187 | 119 | 65 | 35 |
| Epoch step +-30%, settled s (mean of 6) | 1,583 | 456 | 157 | 153 | 144 |
| Epoch step +-30%, first within 10% s | 1,583 | 456 | 91 | 137 | 87 |
| 3x pool hopping every 15 min, settled s | never | 337 (6 of 12 never) | 167 | 184 | 190 |
| 10x pool hopping every 15 min, settled s | never | 284 (6 of 12 never) | 264 | 326 | 212 |
| Polluted window (devnet case), settled s | 2,748 (peak 7.9x) | 124 (peak 15x) | 66 | 110 | 70 |
| Genesis 10x too hard, settled s | never | 287 | 155 | 188 | 322 |
| Genesis 10x too easy, settled s | 1,555 | 95 | 58 | 88 | 59 |
| Steady std of block rate, constant hash rate | 0.012 | 0.037 | 0.131 | 0.092 | 0.038 |
| Steady std of block rate, 10%/h random walk | 0.049 | 0.049 | 0.132 | 0.093 | 0.062 |
| Devnet record (75x step on arrival of the PC), settled s | never (peak 7.6x, 2,340 blocks above 2x) | 136 | 75 | 157 | 79 |
Test network (3 igneumd nodes, CPU miners, 20 minutes, `docs/analysis/difficulty-2026-10-03.md`): genesis 4x too hard, CPU miners A then A+B+C (x1.51) then A (/1.45) on a loaded machine; measured first-within-10% 132 s, 214 s, 85 s against simulator medians of 263 s, 61 s and 231 s on the same profile; 1,133 blocks, 0 rejected; Kaspa's rule on the same genesis would not have retargeted within the run (600-block dead zone at 0.25 blocks/s). Kaspa's rule live on the same genesis, 10 minutes: 70 blocks, bits unchanged on all 70, 0.08 blocks/s, worst gap 63 s, never within 10% of target.
### What this section does not do
Blocks off the selected chain count only through blue work (red blocks carry work the estimator ignores, as Kaspa's estimator does). The chain walk costs up to 600 compact-header reads per block for the first 600 blocks of an epoch and 120 afterwards; re-measure before the 4 BPS step, where the sampled window should replace the walk for the epoch lane too. Real-time targeting (a target that depends on the header's own timestamp) was considered for step-downs and left out: it changes template building and header validation, and the 10% ease clamp with the 20 T cap already brings the 50x step-down to 11 minutes. The 30-day vote-weight window of section 3 is denominated in past-median time since 3 October 2026 (section 3.1 W2, ledger F14), so a retarget lag cannot age it; it is not a retarget change.
## 2.4 Header (fork point d)

55
sim/difficulty/README.md Normal file
View file

@ -0,0 +1,55 @@
# sim/difficulty
Difficulty controller simulator for Igneum (3 October 2026). Model, candidates, results and the
devnet record behind `docs/analysis/difficulty-2026-10-03.md` and section 2.3 of `docs/spec/02-consensus.md`.
## Files
| File | What |
|---|---|
| `sim.py` | the simulator: block production from a hash-rate profile through six controllers, metrics as markdown |
| `devnet-2026-10-03.csv` | every header of the overnight devnet (observer node wRPC, `ws://127.0.0.1:28640`): hash, DAA score, blue score, timestamp, bits, difficulty (2^255 / target, the node's convention), log2 target, epoch, chain flag, parent count, selected parent |
| `results.md` | raw simulator output: synthetic set, timestamp-noise set, tuning sweeps, record replay, test-network comparison |
| `testnet-igneum-2026-10-03.csv`, `testnet-kaspa-2026-10-03.csv` | every header of the two 3-node CPU test networks (Igneum rule, 20 min; Kaspa rule, 10 min), same columns |
## Model
One selected chain, DAA score = height, target 1 block per second, solve times exponential with
mean (expected hashes per block) / (hash rate). A hash-rate profile is piecewise constant in time
(miners joining and leaving) or keyed to the 3,600-block epoch (program cost steps). Timestamps are
the true times in milliseconds; `--ts-noise N` adds uniform +-N ms miner clock jitter.
Metrics per step: first time the 121-block centred mean of the expected block rate is within 10% of
target; "settled" = the first time it then stays within 10% for 100 blocks; overshoot past target on
the far side; blocks spent above 2x and below 0.5x target rate; worst inter-block gap. Steady state:
standard deviation of the expected rate ratio over a steady hour, blocks-per-minute coefficient of
variation (Poisson alone gives 0.129 at 60 blocks per minute).
## Controllers
| Name | Rule |
|---|---|
| `kaspa` | KIP-4 sampled DAA exactly as the fork runs it: samples at every 4th block, 661 samples, average target of the window without its earliest block, times measured over expected, genesis bits held until 150 samples (600 blocks) |
| `monero` | Monero `next_difficulty`: 720 blocks, timestamps sorted, 60 cut from each end, lag 15 |
| `lwma60`, `lwma120` | Zawy LWMA-1 (2018 reference), linear weights, solvetimes in [-132 s, 6 T] |
| `igneum` | the Igneum rule of spec 2.3 (two lanes, work-over-time estimators, epoch windows, 3% harden and 10% ease clamps) |
| `igneum-literal` | the same with the brief's literal trigger (short-window rate off target by 25%), kept to show the chatter |
## Runs
python3 sim.py synthetic set, every controller
python3 sim.py --ts-noise 500 the same with +-500 ms timestamp jitter
python3 sim.py --tune parameter sweep for the Igneum rule
python3 sim.py --record devnet-2026-10-03.csv --events 2026-10-03T19:28:05+00:00,2026-10-03T19:42:40+00:00
exact replay check of Kaspa's rule on the record,
then the record as a hash-rate profile through every controller
python3 sim.py --dump up50:igneum per-block trajectory (t ms, height, D, hash rate, rate ratio)
Requirements: Python 3 only (numpy not needed). The full synthetic set takes about two minutes.
## Limits
No DAG (one chain, no parallel blocks, no reds), no network latency, no miner template refresh
delay, honest timestamps unless `--ts-noise`. The exact-replay check of Kaspa's rule matches the
record bit for bit while the devnet was a chain and diverges once it widened past one block per
second, because a chain-only replay cannot see the merged blocks that the sampled window includes.

File diff suppressed because it is too large Load diff

362
sim/difficulty/results.md Normal file
View file

@ -0,0 +1,362 @@
# sim/difficulty results, 3 October 2026
Raw simulator output behind `docs/analysis/difficulty-2026-10-03.md`. Final parameters: short window 120,
hold 8, prior 16, long lane from 600, trigger 25%, harden 3%, ease 10%, cap 20 T.
## Synthetic set, honest timestamps
python3 sim.py
### up50: hash rate x50 at 3 h
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1542.1 | 1542.1 | 4967 | 0.009 | 0.012 | 0.126 | 9.5 | 3760 | 0 | 21511 | none |
| monero | 94.0 | 94.0 | 713 | 0.089 | 0.037 | 0.144 | 10.3 | 657 | 0 | 18490 | none |
| lwma60 | 105.0 | 105.0 | 182 | 0.208 | 0.131 | 0.088 | 11.3 | 78 | 0 | 17977 | none |
| lwma120 | 231.2 | 231.2 | 388 | 0.189 | 0.092 | 0.096 | 10.5 | 164 | 0 | 18078 | none |
| igneum | 61.7 | 61.7 | 181 | 0.140 | 0.038 | 0.135 | 11.3 | 125 | 0 | 17927 | 377 |
| igneum-literal | 63.7 | 63.7 | 186 | 0.217 | 0.034 | 0.130 | 11.3 | 127 | 0 | 17956 | 230 |
### down50: hash rate /50 at 3 h
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 12295.7 | 12295.7 | 1569 | 5.102 | 0.012 | 0.126 | 178.5 | 1648 | 1256 | 16185 | none |
| monero | 6432.8 | 6432.8 | 701 | 0.039 | 0.037 | 0.144 | 187.3 | 0 | 567 | 19356 | none |
| lwma60 | 577.9 | 577.9 | 111 | 0.154 | 0.131 | 0.088 | 118.7 | 0 | 55 | 24588 | none |
| lwma120 | 1073.6 | 1073.6 | 177 | 0.129 | 0.092 | 0.096 | 64.9 | 0 | 97 | 24205 | none |
| igneum | 657.4 | 657.4 | 147 | 0.062 | 0.038 | 0.135 | 35.3 | 0 | 90 | 24548 | 474 |
| igneum-literal | 967.0 | 967.0 | 146 | 0.062 | 0.034 | 0.130 | 121.1 | 0 | 86 | 24276 | 267 |
### epoch30: x1.3 or /1.3 at each epoch boundary from epoch 2
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1583.0 | 1583.0 | 1974.6 | 0 | 0.043 | 0.010 | 0.130 | 12.3 | 0 | 0 | 29403 | none |
| monero | 456.2 | 456.2 | 603.3 | 0 | 0.116 | 0.038 | 0.141 | 16.0 | 0 | 0 | 28857 | none |
| lwma60 | 91.1 | 156.9 | 310.6 | 0 | 0.291 | 0.132 | 0.090 | 11.6 | 0 | 0 | 28658 | none |
| lwma120 | 136.9 | 153.1 | 240.8 | 0 | 0.229 | 0.094 | 0.097 | 11.5 | 0 | 0 | 28719 | none |
| igneum | 86.5 | 143.7 | 260.8 | 0 | 0.126 | 0.030 | 0.133 | 11.3 | 0 | 0 | 28735 | 351 |
| igneum-literal | 86.4 | 143.8 | 260.8 | 0 | 0.050 | 0.018 | 0.130 | 11.3 | 0 | 0 | 28775 | 149 |
### walk10: 10% per hour log random walk
| controller | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|
| kaspa | 0.049 | 0.144 | 11.1 | 0 | 0 | 21728 | none |
| monero | 0.049 | 0.143 | 10.6 | 0 | 0 | 21541 | none |
| lwma60 | 0.132 | 0.088 | 11.1 | 0 | 0 | 21486 | none |
| lwma120 | 0.093 | 0.105 | 10.5 | 0 | 0 | 21518 | none |
| igneum | 0.062 | 0.130 | 11.2 | 0 | 0 | 21521 | 519 |
| igneum-literal | 0.055 | 0.136 | 11.1 | 0 | 0 | 21523 | 184 |
### hop3: 3x pool hops in and out every 15 min from 3 h
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | none | none | none | 12 | 0 | 0.012 | 0.126 | 21.0 | 1208 | 1036 | 22128 | none |
| monero | 336.7 | 336.7 | 362.4 | 6 | 0.062 | 0.037 | 0.144 | 18.6 | 2438 | 1437 | 20382 | none |
| lwma60 | 109.5 | 166.9 | 373.4 | 0 | 0.270 | 0.131 | 0.088 | 14.7 | 111 | 42 | 21408 | none |
| lwma120 | 161.8 | 184.3 | 292.1 | 0 | 0.201 | 0.092 | 0.096 | 15.0 | 215 | 129 | 21364 | none |
| igneum | 124.0 | 189.5 | 410.3 | 0 | 0.229 | 0.038 | 0.135 | 11.8 | 213 | 182 | 21228 | 8054 |
| igneum-literal | 429.3 | 372.9 | 493.1 | 8 | 0.202 | 0.034 | 0.130 | 16.1 | 86 | 529 | 20913 | 4294 |
### hop10: 10x pool hops in and out every 15 min from 3 h
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | none | none | none | 12 | 0 | 0.012 | 0.126 | 52.1 | 5700 | 1035 | 22951 | none |
| monero | 283.5 | 283.5 | 312.9 | 6 | 0.085 | 0.037 | 0.144 | 83.2 | 3551 | 569 | 19203 | none |
| lwma60 | 165.5 | 263.9 | 542.0 | 0 | 0.242 | 0.131 | 0.088 | 34.3 | 315 | 198 | 21008 | none |
| lwma120 | 282.8 | 325.5 | 789.0 | 0 | 0.199 | 0.092 | 0.096 | 30.2 | 576 | 417 | 20676 | none |
| igneum | 208.1 | 211.9 | 382.9 | 0 | 0.230 | 0.038 | 0.135 | 25.0 | 422 | 321 | 20631 | 8445 |
| igneum-literal | 466.6 | 505.8 | 698.0 | 6 | 0.237 | 0.034 | 0.130 | 35.7 | 816 | 666 | 20683 | 5432 |
### polluted: window polluted by a pre-step slow period (21 min at 0.11 blocks/s, 13 min idle, then 0.6 blocks/s at genesis difficulty)
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks | peak rate | trough rate after 10 min |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 650.7 | 2748.3 | 5163 | 6.942 | 0.010 | 0.134 | 11.2 | 2320 | 0 | 18592 | none | 7.9 | 0.6 |
| monero | 124.0 | 124.0 | 735 | 0.092 | 0.032 | 0.142 | 10.4 | 675 | 0 | 17641 | none | 15.0 | 0.9 |
| lwma60 | 66.1 | 66.1 | 113 | 0.183 | 0.132 | 0.089 | 11.3 | 36 | 0 | 17138 | none | 5.7 | 0.6 |
| lwma120 | 110.3 | 110.3 | 201 | 0.147 | 0.091 | 0.108 | 10.5 | 74 | 0 | 17190 | none | 6.0 | 0.7 |
| igneum | 70.0 | 70.0 | 136 | 0.180 | 0.049 | 0.122 | 11.3 | 72 | 0 | 17005 | 2585 | 8.5 | 0.6 |
| igneum-literal | 817.0 | 1876.4 | 2546 | 0.197 | 0.040 | 0.141 | 11.3 | 571 | 0 | 17618 | 1986 | 8.4 | 0.6 |
### warmup-hard: genesis difficulty 10x too hard
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 5790.7 | none | none | 92.876 | 32.727 | 3.002 | 67.2 | 4860 | 599 | 6444 | none |
| monero | 286.6 | 286.6 | 159 | 0.047 | 0.038 | 0.135 | 17.5 | 0 | 33 | 7011 | none |
| lwma60 | 155.2 | 155.2 | 118 | 0.147 | 0.132 | 0.078 | 10.5 | 0 | 18 | 7128 | none |
| lwma120 | 187.9 | 187.9 | 152 | 0.111 | 0.095 | 0.102 | 10.5 | 0 | 18 | 7128 | none |
| igneum | 321.9 | 321.9 | 199 | 0.024 | 0.031 | 0.126 | 10.5 | 0 | 23 | 6953 | 138 |
| igneum-literal | 351.4 | 702.7 | 568 | 0.024 | 0.019 | 0.134 | 10.5 | 0 | 23 | 6979 | 179 |
### warmup-easy: genesis difficulty 10x too easy
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1554.8 | 1554.8 | 3168 | 0.011 | 0.008 | 0.137 | 9.8 | 599 | 0 | 8869 | none |
| monero | 95.4 | 95.4 | 76 | 0.091 | 0.038 | 0.154 | 16.1 | 16 | 16 | 7145 | none |
| lwma60 | 57.9 | 57.9 | 87 | 0.183 | 0.133 | 0.073 | 9.2 | 8 | 0 | 7192 | none |
| lwma120 | 88.3 | 88.3 | 136 | 0.147 | 0.096 | 0.099 | 9.4 | 8 | 0 | 7225 | none |
| igneum | 58.7 | 58.7 | 114 | 0.082 | 0.029 | 0.133 | 10.3 | 61 | 0 | 7192 | 120 |
| igneum-literal | 58.4 | 58.4 | 114 | 0.086 | 0.015 | 0.129 | 10.3 | 61 | 0 | 7209 | 191 |
## Synthetic set, +-500 ms timestamp jitter
python3 sim.py --ts-noise 500 --profiles up50,down50,epoch30,hop10,polluted
### up50: hash rate x50 at 3 h
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1535.1 | 1535.1 | 4997 | 0.050 | 0.010 | 0.125 | 8.0 | 3876 | 0 | 21640 | none |
| monero | 113.1 | 186.1 | 807 | 0.039 | 0.034 | 0.127 | 8.8 | 656 | 0 | 18496 | none |
| lwma60 | 158.7 | 158.7 | 238 | 0.142 | 0.125 | 0.087 | 9.6 | 83 | 0 | 17939 | none |
| lwma120 | 139.8 | 139.8 | 305 | 0.115 | 0.085 | 0.104 | 9.3 | 171 | 0 | 18047 | none |
| igneum | 168.5 | 168.5 | 288 | 0.024 | 0.046 | 0.133 | 8.6 | 131 | 0 | 18082 | 435 |
| igneum-literal | 115.8 | 115.8 | 253 | 0.026 | 0.039 | 0.114 | 8.6 | 143 | 0 | 18145 | 338 |
### down50: hash rate /50 at 3 h
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 12523.3 | 12523.3 | 1531 | 5.826 | 0.010 | 0.125 | 138.0 | 1736 | 1224 | 16001 | none |
| monero | 6425.5 | 6425.5 | 686 | 0.061 | 0.034 | 0.127 | 191.9 | 0 | 555 | 19343 | none |
| lwma60 | 712.2 | 846.7 | 232 | 0.168 | 0.125 | 0.087 | 77.8 | 0 | 51 | 24398 | none |
| lwma120 | 965.9 | 965.9 | 156 | 0.140 | 0.085 | 0.104 | 80.7 | 0 | 93 | 24224 | none |
| igneum | 895.5 | 1046.6 | 289 | 0.135 | 0.046 | 0.133 | 49.4 | 0 | 90 | 24438 | 593 |
| igneum-literal | 805.8 | 933.2 | 254 | 0.142 | 0.039 | 0.114 | 114.6 | 0 | 91 | 24579 | 307 |
### epoch30: x1.3 or /1.3 at each epoch boundary from epoch 2
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 1534.5 | 1534.5 | 1859.4 | 0 | 0.042 | 0.010 | 0.120 | 10.4 | 0 | 0 | 29420 | none |
| monero | 440.2 | 440.2 | 559.7 | 0 | 0.089 | 0.038 | 0.124 | 10.0 | 0 | 0 | 28818 | none |
| lwma60 | 63.2 | 63.2 | 131.8 | 0 | 0.256 | 0.119 | 0.087 | 11.9 | 0 | 0 | 28592 | none |
| lwma120 | 84.3 | 123.6 | 259.8 | 0 | 0.223 | 0.079 | 0.097 | 10.4 | 0 | 0 | 28657 | none |
| igneum | 55.2 | 55.2 | 172.1 | 0 | 0.200 | 0.036 | 0.130 | 9.9 | 0 | 0 | 28888 | 619 |
| igneum-literal | 51.4 | 51.4 | 172.1 | 0 | 0.108 | 0.036 | 0.106 | 10.1 | 0 | 0 | 28953 | 276 |
### hop10: 10x pool hops in and out every 15 min from 3 h
| controller | first within 10% s | settled s | worst recovery s | not recovered | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | none | none | none | 12 | 0 | 0.010 | 0.125 | 34.3 | 5786 | 1007 | 23012 | none |
| monero | 276.9 | 276.9 | 305.1 | 6 | 0.083 | 0.034 | 0.127 | 76.5 | 3569 | 557 | 19172 | none |
| lwma60 | 178.1 | 209.9 | 352.7 | 0 | 0.201 | 0.125 | 0.087 | 42.2 | 281 | 192 | 20902 | none |
| lwma120 | 307.9 | 317.6 | 729.2 | 0 | 0.115 | 0.085 | 0.104 | 38.8 | 600 | 412 | 20529 | none |
| igneum | 205.1 | 228.0 | 491.7 | 0 | 0.130 | 0.046 | 0.133 | 31.9 | 430 | 320 | 20770 | 8333 |
| igneum-literal | 473.2 | 382.0 | 647.2 | 9 | 0.086 | 0.039 | 0.114 | 28.1 | 714 | 759 | 20753 | 5370 |
### polluted: window polluted by a pre-step slow period (21 min at 0.11 blocks/s, 13 min idle, then 0.6 blocks/s at genesis difficulty)
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks | peak rate | trough rate after 10 min |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 721.0 | 2722.4 | 5176 | 7.512 | 0.017 | 0.117 | 10.0 | 2384 | 0 | 18668 | none | 8.5 | 0.6 |
| monero | 126.9 | 211.0 | 826 | 0.077 | 0.032 | 0.133 | 9.7 | 675 | 0 | 17706 | none | 14.9 | 0.9 |
| lwma60 | 89.4 | 89.4 | 131 | 0.168 | 0.128 | 0.088 | 11.9 | 35 | 0 | 17088 | none | 5.7 | 0.6 |
| lwma120 | 173.7 | 173.7 | 259 | 0.138 | 0.086 | 0.092 | 10.4 | 77 | 0 | 17152 | none | 5.9 | 0.7 |
| igneum | 71.3 | 71.3 | 142 | 0.152 | 0.042 | 0.120 | 10.6 | 81 | 0 | 17139 | 2823 | 8.5 | 0.7 |
| igneum-literal | 715.3 | 2041.7 | 2720 | 0.110 | 0.036 | 0.120 | 11.1 | 633 | 0 | 17775 | 2018 | 8.5 | 0.7 |
## The devnet record
python3 sim.py --profiles none --record devnet-2026-10-03.csv --events 2026-10-03T19:28:05+00:00,2026-10-03T19:42:40+00:00
### Exact replay of Kaspa's rule on the record
Blocks 3682, chain blocks 1656, retargets checked 1056, exact before the first mismatch 0, mismatches 1056, first at daa 845: have 0x1e00a097 want 0x1e009b9a
(A chain-only replay cannot see the merged blocks that Kaspa's sampled window includes once the DAG widens.)
Real profile segments (hash rate estimated as expected hashes over wall time):
| from s | to s | blocks/s at genesis difficulty | MH/s |
|---|---|---|---|
| 0 | 1215 | 0.114 | 30.7 |
| 1215 | 2090 | 0.006 | 1.5 |
| 2090 | 4064 | 0.433 | 116.2 |
### real: the devnet record replayed as a hash-rate profile
| controller | first within 10% s | settled s | recovery blocks | overshoot | steady std of rate | steady blocks/min CV | worst gap s | blocks above 2x | blocks below 0.5x | blocks | fast-lane blocks | blocks within 10% | step factor |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| kaspa | 261.9 | none | none | 6.244 | 1.599 | 0.567 | 7.8 | 2340 | 0 | 4776 | none | 0 | 75.7 |
| monero | 135.9 | 135.9 | 729 | 0.092 | 0.027 | 0.119 | 6.8 | 672 | 0 | 3729 | none | 2822 | 75.7 |
| lwma60 | 74.6 | 74.6 | 157 | 0.183 | 0.109 | 0.067 | 7.4 | 87 | 0 | 3890 | none | 2175 | 75.7 |
| lwma120 | 156.5 | 156.5 | 330 | 0.147 | 0.084 | 0.086 | 7.1 | 174 | 0 | 3691 | none | 2390 | 75.7 |
| igneum | 78.7 | 78.7 | 192 | 0.180 | 0.087 | 0.105 | 7.3 | 132 | 0 | 3740 | 2484 | 2608 | 75.7 |
| igneum-literal | 482.4 | 1516.2 | 2112 | 0.008 | 0.456 | 0.186 | 7.4 | 515 | 0 | 4119 | 1959 | 2170 | 75.7 |
## Tuning sweeps
First sweep (symmetric 3% clamp, cap 6 T baseline):
### Tune ns
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ns=60 | 72.8 | 72.8 | 0.069 | 1843.1 | 1843.1 | 0.069 | 114.2 | 127.0 | 80.2 | 0.067 | none | none | 0.089 | 0/0 | 301.6 | 251.0 | 0.069 | 56.4 | 56.4 | 0.083 | 4.4 |
| ns=120 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| ns=180 | 63.3 | 63.3 | 0.035 | 2131.4 | 2131.4 | 0.035 | 125.1 | 156.3 | 99.0 | 0.017 | none | none | 0.056 | 0/0 | 356.1 | 356.1 | 0.035 | 76.5 | 76.5 | 0.042 | 5.6 |
### Tune trig
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| trig=0.15 | 66.7 | 66.7 | 0.077 | 1591.5 | 1591.5 | 0.077 | 98.7 | 137.3 | 107.3 | 0.070 | none | none | 0.082 | 0/0 | 312.9 | 261.8 | 0.077 | 58.1 | 58.1 | 0.075 | 5.8 |
| trig=0.25 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| trig=0.35 | 62.9 | 62.9 | 0.034 | 1835.4 | 1835.4 | 0.034 | 126.4 | 156.3 | 99.0 | 0.016 | none | none | 0.054 | 0/0 | 328.5 | 279.2 | 0.034 | 78.4 | 78.4 | 0.042 | 5.5 |
### Tune clamp_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| clamp_pct=0.02 | 84.0 | 84.0 | 0.039 | 2588.2 | 2588.2 | 0.039 | 216.8 | 156.3 | 99.4 | 0.031 | none | none | 0.060 | 0/0 | 373.6 | 336.9 | 0.039 | 80.1 | 80.1 | 0.046 | 5.4 |
| clamp_pct=0.03 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| clamp_pct=0.05 | 62.2 | 62.2 | 0.040 | 1312.5 | 1312.5 | 0.040 | 173.8 | 155.8 | 98.9 | 0.034 | none | none | 0.062 | 0/0 | 284.6 | 258.8 | 0.040 | 59.9 | 59.9 | 0.049 | 5.4 |
### Tune ease_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ease_pct=0.03 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| ease_pct=0.06 | 62.7 | 62.7 | 0.039 | 1282.5 | 1282.5 | 0.039 | 176.0 | 155.6 | 98.8 | 0.031 | none | none | 0.061 | 0/0 | 299.5 | 265.0 | 0.039 | 58.5 | 58.5 | 0.048 | 5.8 |
| ease_pct=0.1 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
### Tune k0
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| k0=8 | 63.0 | 63.0 | 0.040 | 1786.6 | 1786.6 | 0.040 | 211.1 | 156.0 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 311.5 | 280.9 | 0.040 | 67.1 | 67.1 | 0.048 | 5.5 |
| k0=16 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| k0=32 | 63.0 | 63.0 | 0.040 | 1786.9 | 1786.9 | 0.040 | 211.9 | 156.0 | 99.1 | 0.033 | none | none | 0.061 | 0/0 | 290.0 | 263.0 | 0.040 | 71.4 | 71.4 | 0.047 | 5.7 |
### Tune cap
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cap=6 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| cap=20 | 63.8 | 63.8 | 0.039 | 1703.7 | 1703.7 | 0.039 | 165.8 | 143.7 | 86.8 | 0.033 | none | none | 0.061 | 0/0 | 274.9 | 257.2 | 0.039 | 62.9 | 62.9 | 0.048 | 5.8 |
| cap=132 | 63.8 | 63.8 | 0.039 | 1649.6 | 1649.6 | 0.039 | 161.0 | 143.7 | 86.8 | 0.033 | none | none | 0.061 | 0/0 | 263.5 | 238.2 | 0.039 | 75.8 | 75.8 | 0.048 | 13.1 |
Second sweep (harden 3%, ease 10%, cap 6 T baseline; the cap row fixed the final value at 20 T):
### Tune ns
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ns=60 | 57.5 | 57.5 | 0.080 | 595.9 | 595.9 | 0.080 | 87.5 | 120.2 | 92.6 | 0.076 | none | none | 0.093 | 0/0 | 201.2 | 152.1 | 0.080 | 60.8 | 60.8 | 0.090 | 5.2 |
| ns=120 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| ns=180 | 63.5 | 63.5 | 0.034 | 1742.3 | 1742.3 | 0.034 | 93.4 | 155.8 | 98.8 | 0.017 | none | none | 0.056 | 0/0 | 354.2 | 344.3 | 0.034 | 77.3 | 77.3 | 0.042 | 5.7 |
### Tune trig
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| trig=0.15 | 63.2 | 63.2 | 0.078 | 1098.2 | 1098.2 | 0.078 | 75.1 | 136.1 | 106.1 | 0.072 | none | none | 0.083 | 0/0 | 244.8 | 234.9 | 0.078 | 62.1 | 62.1 | 0.075 | 5.2 |
| trig=0.25 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| trig=0.35 | 63.5 | 63.5 | 0.034 | 1238.4 | 1238.4 | 0.034 | 75.5 | 155.8 | 98.8 | 0.016 | none | none | 0.054 | 0/0 | 276.3 | 263.9 | 0.034 | 63.1 | 63.1 | 0.042 | 5.8 |
### Tune clamp_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| clamp_pct=0.02 | 97.1 | 97.1 | 0.037 | 1091.3 | 1091.3 | 0.037 | 71.6 | 155.3 | 98.7 | 0.027 | none | none | 0.061 | 0/0 | 269.7 | 258.5 | 0.037 | 71.9 | 71.9 | 0.048 | 6.2 |
| clamp_pct=0.03 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| clamp_pct=0.05 | 60.1 | 60.1 | 0.040 | 1371.6 | 1371.6 | 0.040 | 175.9 | 155.5 | 98.6 | 0.033 | none | none | 0.063 | 0/0 | 295.1 | 262.7 | 0.040 | 71.6 | 71.6 | 0.049 | 6.1 |
### Tune ease_pct
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ease_pct=0.03 | 63.0 | 63.0 | 0.040 | 1786.2 | 1786.2 | 0.040 | 211.2 | 155.9 | 99.0 | 0.033 | none | none | 0.061 | 0/0 | 275.6 | 275.6 | 0.040 | 69.3 | 69.3 | 0.048 | 5.8 |
| ease_pct=0.06 | 62.7 | 62.7 | 0.039 | 1282.5 | 1282.5 | 0.039 | 176.0 | 155.6 | 98.8 | 0.031 | none | none | 0.061 | 0/0 | 299.5 | 265.0 | 0.039 | 58.5 | 58.5 | 0.048 | 5.8 |
| ease_pct=0.1 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| ease_pct=0.15 | 61.6 | 61.6 | 0.038 | 1111.4 | 1111.4 | 0.038 | 65.4 | 155.2 | 98.4 | 0.030 | none | none | 0.062 | 0/0 | 261.6 | 250.5 | 0.038 | 67.1 | 67.1 | 0.048 | 6.4 |
### Tune k0
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| k0=8 | 62.2 | 62.2 | 0.038 | 1144.7 | 1144.7 | 0.038 | 65.3 | 155.5 | 98.7 | 0.030 | none | none | 0.062 | 0/0 | 247.7 | 247.7 | 0.038 | 70.2 | 70.2 | 0.048 | 6.2 |
| k0=16 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| k0=32 | 62.2 | 62.2 | 0.038 | 1169.9 | 1169.9 | 0.038 | 65.3 | 155.7 | 98.9 | 0.030 | none | none | 0.061 | 0/0 | 287.5 | 266.4 | 0.038 | 71.4 | 71.4 | 0.048 | 6.3 |
### Tune cap
| ctrl | up50 rec | up50 first | up50 std | down50 rec | down50 first | down50 std | down50 gap | epoch30 rec | epoch30 first | epoch30 std | walk10 rec | walk10 first | walk10 std | walk10 >2x/<0.5x | hop10 rec | hop10 first | hop10 std | polluted rec | polluted first | polluted std | polluted peak |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| cap=6 | 62.2 | 62.2 | 0.039 | 1164.4 | 1164.4 | 0.039 | 65.3 | 155.5 | 98.8 | 0.030 | none | none | 0.062 | 0/0 | 253.4 | 253.4 | 0.039 | 71.7 | 71.7 | 0.048 | 6.2 |
| cap=20 | 61.7 | 61.7 | 0.038 | 657.4 | 657.4 | 0.038 | 35.3 | 143.7 | 86.5 | 0.030 | none | none | 0.062 | 0/0 | 211.9 | 208.1 | 0.038 | 70.0 | 70.0 | 0.049 | 8.5 |
| cap=132 | 61.7 | 61.7 | 0.038 | 629.5 | 629.5 | 0.038 | 32.4 | 143.7 | 86.5 | 0.030 | none | none | 0.062 | 0/0 | 212.9 | 209.1 | 0.038 | 63.8 | 63.8 | 0.049 | 16.4 |
## Test network
Igneum rule, 3 nodes, 3 CPU miners, 20 minutes (`testnet-igneum-2026-10-03.csv`), analysed with the same 61-block mean as the simulator:
```
block 1 at 2026-10-03T21:38:32.221000 schedule start 21:38:31 = 1 s before block 1
miner starts (s from block 1): [('A', -1.221), ('B', 358.779), ('C', 358.779), ('B+C stop', 778.8)]
| from s | blocks | blocks/s | mean difficulty | MH/s (work over 30 s) |
|---|---|---|---|---|
| 0 | 5 | 0.17 | 262,143 | 0.087 |
| 30 | 3 | 0.10 | 262,143 | 0.052 |
| 60 | 6 | 0.20 | 190,283 | 0.076 |
| 90 | 22 | 0.73 | 62,248 | 0.091 |
| 120 | 16 | 0.53 | 33,904 | 0.036 |
| 150 | 25 | 0.83 | 31,146 | 0.052 |
| 180 | 37 | 1.23 | 31,340 | 0.077 |
| 210 | 38 | 1.27 | 32,492 | 0.082 |
| 240 | 27 | 0.90 | 32,722 | 0.059 |
| 270 | 30 | 1.00 | 32,591 | 0.065 |
| 300 | 29 | 0.97 | 32,698 | 0.063 |
| 330 | 28 | 0.93 | 32,395 | 0.060 |
| 360 | 57 | 1.90 | 35,345 | 0.134 |
| 390 | 25 | 0.83 | 44,446 | 0.074 |
| 420 | 26 | 0.87 | 35,183 | 0.061 |
| 450 | 23 | 0.77 | 34,670 | 0.053 |
| 480 | 39 | 1.30 | 34,488 | 0.090 |
| 510 | 34 | 1.13 | 34,902 | 0.079 |
| 540 | 48 | 1.60 | 38,841 | 0.124 |
| 570 | 27 | 0.90 | 49,788 | 0.090 |
| 600 | 31 | 1.03 | 49,682 | 0.103 |
| 630 | 39 | 1.30 | 56,451 | 0.147 |
| 660 | 23 | 0.77 | 57,562 | 0.088 |
| 690 | 36 | 1.20 | 57,271 | 0.137 |
| 720 | 20 | 0.67 | 58,646 | 0.078 |
| 750 | 35 | 1.17 | 51,839 | 0.121 |
| 780 | 16 | 0.53 | 43,030 | 0.046 |
| 810 | 19 | 0.63 | 40,907 | 0.052 |
| 840 | 26 | 0.87 | 40,517 | 0.070 |
| 870 | 15 | 0.50 | 39,733 | 0.040 |
| 900 | 30 | 1.00 | 28,790 | 0.058 |
| 930 | 17 | 0.57 | 26,404 | 0.030 |
| 960 | 43 | 1.43 | 24,354 | 0.070 |
| 990 | 27 | 0.90 | 26,346 | 0.047 |
| 1020 | 40 | 1.33 | 29,237 | 0.078 |
| 1050 | 42 | 1.40 | 38,348 | 0.107 |
| 1080 | 39 | 1.30 | 42,203 | 0.110 |
| 1110 | 21 | 0.70 | 41,639 | 0.058 |
| 1140 | 39 | 1.30 | 38,832 | 0.101 |
| 1170 | 30 | 1.00 | 39,650 | 0.079 |
segments (hash rate = expected hashes over wall time):
0 to 359 s: 0.0653 MH/s
359 to 779 s: 0.0988 MH/s
779 to 1192 s: 0.0686 MH/s
recovery after each event (61-block centred mean of expected rate within 10% of 1 block/s):
start at 0 s: first within 10% 132 s, settled (60 blocks) 132 s, worst gap 15.4 s, blocks after 1133
A at -1 s: first within 10% 134 s, settled (60 blocks) 134 s, worst gap 15.4 s, blocks after 1133
B at 359 s: first within 10% 214 s, settled (60 blocks) 214 s, worst gap 7.3 s, blocks after 869
C at 359 s: first within 10% 214 s, settled (60 blocks) 214 s, worst gap 7.3 s, blocks after 869
B+C stop at 779 s: first within 10% 85 s, settled (60 blocks) none s, worst gap 7.3 s, blocks after 404
```
Kaspa rule, same network and genesis, 10 minutes (`testnet-kaspa-2026-10-03.csv`): 70 blocks, bits 0x1e200000 on every header, 0.08 blocks/s, worst gap 63 s, delivered 0.042 MH/s; no retarget (block 600 was about 2 hours away).

794
sim/difficulty/sim.py Normal file
View file

@ -0,0 +1,794 @@
#!/usr/bin/env python3
"""Igneum difficulty controller simulator.
Replays block production from a hash-rate profile through candidate difficulty controllers and
reports how each one tracks the target rate. Chain model: one selected chain, DAA score = height,
1 block per second target, block times exponential with mean (expected hashes per block) / (hash rate).
Timestamps are the true block times in milliseconds (option --ts-noise adds miner clock jitter).
Controllers
kaspa Kaspa's sampled DAA (KIP-4) as the fork runs it: samples every 4th block, 661 samples,
average target of the window without its earliest block, times measured over expected,
fixed genesis bits for the first 600 blocks (150 samples).
monero Monero style: last 720 blocks, timestamps sorted, 60 cut from each end, lag 15.
lwma60 Zawy LWMA-1, N = 60, solvetimes clamped to [-FTL, 6T], FTL = 132 s.
lwma120 the same with N = 120.
igneum the Igneum candidate (two lanes, epoch windows, 3% harden and 10% ease clamps, 20 T solvetime cap, warm-up from block 8).
igneum-literal
the brief's literal trigger (fast lane engaged while the short-window RATE is more than
25% off target) kept for the chatter measurement.
Profiles
real the devnet record (--record CSV) turned into a step profile of estimated hash rate.
up50, down50, epoch30, walk10, hop3, hop10, warmup-hard, warmup-easy
Usage
python3 sim.py every synthetic profile, every controller, markdown out
python3 sim.py --record devnet-2026-10-03.csv adds the real profile and the exact-bits replay check
python3 sim.py --tune the parameter sweep for the Igneum candidate
"""
import argparse
import csv
import math
import random
import sys
from collections import deque
T = 1000 # target time per block, ms
EPOCH = 3600 # blocks per program epoch
FTL = 132 * 1000 # Kaspa's future timestamp tolerance, ms
MAX_TARGET = (1 << 255) - 1
ONE = float(1 << 255) # genesis-independent scale: target = ONE / D, D = expected hashes
# ---------------------------------------------------------------- controllers
class Controller:
name = "base"
def __init__(self, genesis_target):
self.g = genesis_target
self.ts = [] # timestamps ms, index = height
self.tg = [] # targets (float), index = height
def push(self, ts, target):
self.ts.append(ts)
self.tg.append(target)
def next_target(self):
raise NotImplementedError
def clamp(x, lo, hi):
return lo if x < lo else hi if x > hi else x
class Kaspa(Controller):
"""KIP-4 sampled DAA as in rusty-kaspa `SampledDifficultyManager::calculate_difficulty_bits`.
Sampling: block at height h is a sample when (h_parent_daa + 1) % rate == 0, i.e. h % 4 == 3 on a chain;
genesis is never a sample. Window keeps the 661 highest samples. Retarget needs 150 samples."""
name = "kaspa"
def __init__(self, g, window=661, rate=4, min_samples=150):
super().__init__(g)
self.window = window
self.rate = rate
self.min_samples = min_samples
self.samples = deque() # (ts, target) of sampled blocks, oldest first
def push(self, ts, target):
h = len(self.ts)
super().push(ts, target)
if h > 0 and h % self.rate == self.rate - 1:
self.samples.append((ts, target))
if len(self.samples) > self.window:
self.samples.popleft()
def next_target(self):
n = len(self.samples)
if n < self.min_samples:
return self.tg[-1] if self.tg else self.g
min_i = min(range(n), key=lambda i: self.samples[i][0])
max_i = max(range(n), key=lambda i: self.samples[i][0])
min_ts = self.samples[min_i][0]
max_ts = self.samples[max_i][0]
tsum = sum(t for i, (_, t) in enumerate(self.samples) if i != min_i)
avg = tsum / (n - 1)
measured = max(max_ts - min_ts, 1)
expected = T * self.rate * (n - 1)
return min(avg * measured / expected, ONE)
class Monero(Controller):
"""Monero next_difficulty: N 720, cut 60 each side on sorted timestamps, lag 15.
Difficulty units, converted to target at the end."""
name = "monero"
def __init__(self, g, n=720, cut=60, lag=15):
super().__init__(g)
self.n, self.cut, self.lag = n, cut, lag
def next_target(self):
h = len(self.ts)
end = h - self.lag
if end < 2:
return self.g
start = max(0, end - self.n)
ts = sorted(self.ts[start:end])
length = len(ts)
if length <= 2 * self.cut + 2:
cb, ce = 0, length
else:
cb = (length - (self.n - 2 * self.cut) + 1) // 2 if length >= self.n else self.cut
cb = clamp(cb, 0, self.cut)
ce = length - cb
if ce - cb < 2:
cb, ce = 0, length
span = max(ts[ce - 1] - ts[cb], 1)
work = sum(ONE / t for t in self.tg[start + cb:start + ce])
d = work * T / span
return min(ONE / d, ONE)
class Lwma(Controller):
"""Zawy LWMA-1 (2018 reference): linear weights, solvetime in [-FTL, 6T], average target over N.
Grows from 4 blocks instead of waiting for N."""
name = "lwma"
def __init__(self, g, n=120, lo=-FTL, hi=6 * T, min_blocks=4):
super().__init__(g)
self.n, self.lo, self.hi, self.min_blocks = n, lo, hi, min_blocks
self.name = "lwma%d" % n
def next_target(self):
h = len(self.ts)
if h < self.min_blocks + 1:
return self.g
n = min(self.n, h - 1)
ts = self.ts
wsum = 0.0
tsum = 0.0
for i in range(1, n + 1):
j = h - n - 1 + i
st = clamp(ts[j] - ts[j - 1], self.lo, self.hi)
wsum += st * i
tsum += self.tg[j]
wsum = max(wsum, n * n * T / 20)
avg = tsum / n
return min(avg * wsum / (T * n * (n + 1) / 2), ONE)
class Igneum(Controller):
"""Igneum dual-lane controller (docs/spec/02-consensus.md section 2.3).
Every lane estimates the hash rate as work over time (what Kaspa's estimateNetworkHashesPerSecond does),
never as average target times average solvetime, which is biased while targets ramp.
k = blocks of the current epoch already in the past (epoch = height // 3600).
S (short): linear-weighted work over capped solvetimes of the last min(NS, k) chain blocks.
E (epoch): work over capped time of the whole epoch so far, shrunk toward the parent's implied rate by
k / (k + K0); used as the reference while k < LONG_MIN.
L (long): blue work between the earliest and latest sample of Kaspa's sampled window restricted to the
epoch, over their time span; the reference once k >= LONG_MIN.
Candidate = S when S and the reference disagree by more than TRIG, else the reference. The output may
fall (harder) by at most CLAMP and rise (easier) by at most EASE per block. The parent's target is held
for the first K_MIN blocks of an epoch. Genesis bits only for blocks 1 to K_MIN."""
name = "igneum"
def __init__(self, g, ns=120, k_min=8, k0=16, long_min=600, trig=0.25, clamp_pct=0.03, ease_pct=0.10, cap=20 * T,
window=661, rate=4, literal=False, epoch_aware=True, kaspa_formula=False):
super().__init__(g)
self.ns, self.k_min, self.k0, self.long_min = ns, k_min, k0, long_min
self.trig, self.clamp_pct, self.cap = trig, clamp_pct, cap
self.ease_pct = clamp_pct if ease_pct is None else ease_pct
self.window, self.rate = window, rate
self.literal = literal
self.epoch_aware = epoch_aware
self.kaspa_formula = kaspa_formula
self.samples = deque() # (height, ts, blue_work)
self.pref_d = [0.0] # prefix sums of D (work), index h+1 = work through block h = blue work
self.pref_t = [0.0] # prefix sums of targets (only for the kaspa_formula variant)
self.pref_st = [0.0] # prefix sums of capped solvetimes
self.engaged = 0
def push(self, ts, target):
h = len(self.ts)
st = clamp(ts - self.ts[-1], -self.cap, self.cap) if h > 0 else 0
super().push(ts, target)
self.pref_d.append(self.pref_d[-1] + ONE / target)
self.pref_t.append(self.pref_t[-1] + target)
self.pref_st.append(self.pref_st[-1] + st)
if h > 0 and h % self.rate == self.rate - 1:
self.samples.append((h, ts, self.pref_d[-1]))
if len(self.samples) > self.window:
self.samples.popleft()
def rate_s(self, lo, hi):
"""Linear-weighted hash rate over chain steps lo+1..hi-1 (work of block j over solvetime of block j)."""
n = hi - lo - 1
if n < 1:
return None
wsum = 0.0
dsum = 0.0
ts = self.ts
for i in range(1, n + 1):
j = lo + i
wsum += clamp(ts[j] - ts[j - 1], -self.cap, self.cap) * i
dsum += (self.pref_d[j + 1] - self.pref_d[j]) * i
wsum = max(wsum, n * n * T / 20)
return dsum / wsum
def rate_e(self, lo, hi, k):
n = hi - lo - 1
if n < 1:
return None
work = self.pref_d[hi] - self.pref_d[lo + 1]
measured = max(self.pref_st[hi] - self.pref_st[lo + 1], 1)
r = work / measured
prior = (self.pref_d[hi] - self.pref_d[hi - 1]) / T # the parent's implied rate
return (k * r + self.k0 * prior) / (k + self.k0)
def rate_l(self, lo):
samp = [s for s in self.samples if s[0] >= lo]
n = len(samp)
if n < self.long_min // self.rate:
return None
mn = min(samp, key=lambda s: s[1])
mx = max(samp, key=lambda s: s[1])
measured = max(mx[1] - mn[1], 1)
if self.kaspa_formula:
tsum = sum(self.tg[s[0]] for s in samp if s is not mn)
return 1.0 / ((tsum / (n - 1)) * measured / (T * self.rate * (n - 1))) / ONE * ONE * (1.0 / T) * T if False else None
return (mx[2] - mn[2]) / measured
def next_target(self):
h = len(self.ts)
if h == 0:
return self.g
parent = self.tg[-1]
epoch_start = (h // EPOCH) * EPOCH if self.epoch_aware else 0
k = h - epoch_start
if k < self.k_min:
cand = parent
else:
s_lo = max(epoch_start, h - self.ns)
r_s = self.rate_s(s_lo, h)
r_ref = None
if k >= self.long_min:
r_ref = self.rate_l(max(epoch_start, h - self.window * self.rate))
if r_ref is None:
r_ref = self.rate_e(epoch_start, h, k)
if self.literal:
n = h - s_lo
measured = max(self.pref_st[h] - self.pref_st[s_lo + 1], 1)
use_s = abs(T * (n - 1) / measured - 1) > self.trig
else:
use_s = abs(r_s / r_ref - 1) > self.trig
r = r_s if use_s else r_ref
self.engaged += 1 if use_s else 0
cand = ONE / (r * T)
out = clamp(cand, parent / (1 + self.clamp_pct), parent * (1 + self.ease_pct))
return min(out, ONE)
def make_controller(name, g, **kw):
if name == "kaspa":
return Kaspa(g)
if name == "monero":
return Monero(g)
if name == "lwma60":
return Lwma(g, n=60)
if name == "lwma120":
return Lwma(g, n=120)
if name == "igneum":
return Igneum(g, **kw)
if name == "igneum-literal":
return Igneum(g, literal=True, **kw)
if name == "igneum-noepoch":
return Igneum(g, epoch_aware=False, **kw)
raise ValueError(name)
# ---------------------------------------------------------------- profiles
class Profile:
"""Hash rate in hashes per ms as a function of (time ms, height). `steps` lists (t_ms, factor) events
for the recovery metrics; `epoch_steps` are height-keyed factors applied at epoch boundaries."""
def __init__(self, name, base, duration_ms, genesis_d=None, seed=7):
self.name = name
self.base = base
self.duration = duration_ms
self.time_steps = [] # (t_ms, factor)
self.epoch_steps = {} # height -> factor
self.walk = None # (sigma per minute)
self.genesis_d = genesis_d if genesis_d is not None else base * T
self.rng = random.Random(seed)
self._walk_cache = {}
def rate(self, t, h):
f = 1.0
for ts, fac in self.time_steps:
if t >= ts:
f *= fac
for hh, fac in self.epoch_steps.items():
if h >= hh:
f *= fac
if self.walk:
m = int(t // 60000)
if m not in self._walk_cache:
prev = self._walk_cache.get(m - 1, 0.0) if m > 0 else 0.0
self._walk_cache[m] = prev + self.rng.gauss(0, self.walk)
f *= math.exp(self._walk_cache[m])
return self.base * f
def events(self):
"""Step events as (t_ms or None, height or None, factor)."""
out = [(t, None, f) for t, f in self.time_steps]
out += [(None, h, f) for h, f in self.epoch_steps.items()]
return out
SCALE = float(1 << 28) # synthetic difficulty unit: 2^28 hashes per block, the devnet genesis
H1 = SCALE / T # hash rate that gives one block per second at D = SCALE
def synthetic_profiles(seed):
P = []
hour = 3600 * 1000
p = Profile("up50", H1, 5 * hour, seed=seed); p.time_steps = [(3 * hour, 50.0)]; P.append(p)
p = Profile("down50", 50 * H1, 7 * hour, seed=seed); p.time_steps = [(3 * hour, 1 / 50.0)]; P.append(p)
p = Profile("epoch30", H1, 8 * hour, seed=seed)
rng = random.Random(seed)
fac = 1.0
for e in range(2, 8):
s = rng.choice([1.3, 1 / 1.3])
p.epoch_steps[e * EPOCH] = s
P.append(p)
p = Profile("walk10", H1, 6 * hour, seed=seed); p.walk = 0.10 / math.sqrt(60); P.append(p)
p = Profile("hop3", H1, 6 * hour, seed=seed)
p.time_steps = [(3 * hour + i * 15 * 60000, 3.0 if i % 2 == 0 else 1 / 3.0) for i in range(12)]
P.append(p)
p = Profile("hop10", H1, 6 * hour, seed=seed)
p.time_steps = [(3 * hour + i * 15 * 60000, 10.0 if i % 2 == 0 else 1 / 10.0) for i in range(12)]
P.append(p)
# the devnet of 3 Oct 2026: genesis sized for 0.6 blocks/s of GPU, a 0.11 blocks/s Metal worker for 21 min,
# 13 min of nothing, then the PC; Kaspa's first retarget at block 600 sees the whole slow history
p = Profile("polluted", 0.6 * H1, 5 * hour, genesis_d=1.0 * SCALE, seed=seed)
p.time_steps = [(0, 0.11 / 0.6), (21 * 60000, 1e-9), (34 * 60000, 0.6 / 0.11 / 1e-9)]
P.append(p)
p = Profile("warmup-hard", H1, 2 * hour, genesis_d=10.0 * SCALE, seed=seed); p.time_steps = [(0, 1.0)]; P.append(p)
p = Profile("warmup-easy", H1, 2 * hour, genesis_d=0.1 * SCALE, seed=seed); p.time_steps = [(0, 1.0)]; P.append(p)
return P
# ---------------------------------------------------------------- record (real devnet)
def load_record(path):
"""Returns (chain, all): chain blocks (one per blue score, for the exact replay) and every block
(for the hash-rate profile), as (blue_score, daa_score, timestamp_ms, bits)."""
rows = list(csv.DictReader(open(path)))
rows.sort(key=lambda r: (int(r["blue_score"]), int(r["daa_score"]), int(r["timestamp_ms"])))
every = [(int(r["blue_score"]), int(r["daa_score"]), int(r["timestamp_ms"]), int(r["bits"], 16)) for r in rows]
chain = []
seen = set()
for r in rows:
if r["is_chain_block"] != "1" and r["blue_score"] != "0":
continue
bs = int(r["blue_score"])
if bs in seen:
continue
seen.add(bs)
chain.append((bs, int(r["daa_score"]), int(r["timestamp_ms"]), int(r["bits"], 16)))
return chain, every
def bits_to_target(bits):
exp = bits >> 24
mant = bits & 0x7FFFFF
if exp <= 3:
return mant >> (8 * (3 - exp))
return mant << (8 * (exp - 3))
def target_to_bits(t):
"""Uint256::compact_target_bits: size in bytes, mantissa top 3 bytes, sign-bit carry."""
if t == 0:
return 0
size = (t.bit_length() + 7) // 8
if size <= 3:
mant = t << (8 * (3 - size))
else:
mant = t >> (8 * (size - 3))
if mant & 0x800000:
mant >>= 8
size += 1
return (size << 24) | mant
def kaspa_bits_exact(record, window=661, rate=4, min_samples=150):
"""Replay the record's own timestamps and bits through rusty-kaspa's integer arithmetic; return
(checked, mismatches). Sample rule as in `sampled_mergeset_iterator` on a chain."""
samples = deque()
checked = mism = 0
first_bad = None
exact_before_first = 0
for i, (bs, daa, ts, bits) in enumerate(record):
if i > 0:
n = len(samples)
if n >= min_samples:
min_i = min(range(n), key=lambda j: samples[j][0])
max_i = max(range(n), key=lambda j: samples[j][0])
tsum = sum(t for j, (_, t) in enumerate(samples) if j != min_i)
avg = tsum // (n - 1)
measured = max(samples[max_i][0] - samples[min_i][0], 1)
expected = T * rate * (n - 1)
new = min(avg * measured // expected, MAX_TARGET)
want = target_to_bits(new)
checked += 1
if want != bits:
mism += 1
if first_bad is None:
first_bad = (daa, hex(bits), hex(want))
elif first_bad is None:
exact_before_first += 1
parent_daa = record[i - 1][1] if i > 0 else None
if i > 0 and (parent_daa + 1) % rate == 0:
samples.append((ts, bits_to_target(bits)))
if len(samples) > window:
samples.popleft()
return checked, mism, first_bad, exact_before_first
def record_profile(record, every, events, seed):
"""Piecewise-constant hash rate estimated from the record between the given event times.
events: sorted list of (t_ms, label). Rate in a segment = sum of expected hashes / duration."""
t0 = record[1][2]
tend = record[-1][2]
bounds = [t0] + [t for t, _ in events if t0 < t < tend] + [tend]
segs = []
for a, b in zip(bounds, bounds[1:]):
hashes = sum(2 * ONE / bits_to_target(bits) for (_, _, ts, bits) in every[1:] if a < ts <= b)
dur = max(b - a, 1)
segs.append((a, b, hashes / dur))
p = Profile("real", segs[0][2], tend - t0, genesis_d=ONE / bits_to_target(record[0][3]), seed=seed)
for (a, b, r), (a2, b2, r2) in zip(segs, segs[1:]):
p.time_steps.append((a2 - t0, r2 / r if r > 0 else 1.0))
p.segments = [(a - t0, b - t0, r) for a, b, r in segs]
p.min_rate = 1e-12 * H1
return p
# ---------------------------------------------------------------- simulation
def simulate(profile, ctrl, rng, ts_noise=0, max_blocks=400000):
"""Returns list of (t_ms, height, D, hashrate, r) per block, r = expected block rate / target."""
t = 0
out = []
target = ONE / profile.genesis_d
ctrl.push(0, target) # genesis at t = 0
h = 1
while t < profile.duration and h < max_blocks:
target = ctrl.next_target()
D = ONE / target
work = rng.expovariate(1.0) * D # hashes needed for this block
t_start = t
tt = t
while True:
hr = profile.rate(tt, h)
nxt = min((st for st, _ in profile.time_steps if st > tt), default=None)
if hr > 0 and (nxt is None or work <= hr * (nxt - tt)):
tt += work / hr
break
if nxt is None: # zero hash rate for ever: end the run
tt = profile.duration + 1
break
work -= hr * (nxt - tt)
tt = nxt
gap = tt - t_start
hr = profile.rate(tt, h)
gap = max(gap, 1.0)
t += gap
ts = int(t) + (rng.randint(-ts_noise, ts_noise) if ts_noise else 0)
ctrl.push(ts, target)
r = hr * T / D
out.append((t, h, D, hr, r))
h += 1
return out
def smoothed(xs, half):
"""Centred moving mean over 2*half+1 values (shorter at the ends)."""
n = len(xs)
pref = [0.0]
for x in xs:
pref.append(pref[-1] + x)
out = []
for i in range(n):
lo = max(0, i - half)
hi = min(n, i + half + 1)
out.append((pref[hi] - pref[lo]) / (hi - lo))
return out
def metrics(blocks, profile, t_step=None, h_step=None, settle_blocks=100, t_end=None):
"""Recovery after one step, overshoot, steady state and gap statistics."""
res = {}
if t_step is not None or h_step is not None:
if t_step is None:
t_step = next((b[0] for b in blocks if b[1] >= h_step), None)
post = [b for b in blocks if b[0] >= t_step and (t_end is None or b[0] < t_end)] if t_step is not None else []
if not post:
res["recover_s"] = None
res["overshoot"] = None
else:
rec = None
sm = smoothed([b[4] for b in post], 60)
first = next((i for i, x in enumerate(sm) if abs(x - 1) <= 0.10), None)
res["first10_s"] = None if first is None else (post[first][0] - t_step) / 1000
for i in range(len(post)):
seg = sm[i:i + settle_blocks]
if len(seg) == settle_blocks and all(abs(x - 1) <= 0.10 for x in seg):
rec = post[i][0] - t_step
break
res["recover_s"] = None if rec is None else rec / 1000
res["recover_blocks"] = None if rec is None else sum(1 for b in post if b[0] - t_step <= rec)
r0 = post[0][4]
side = 1 if r0 > 1 else -1
cross = next((i for i, x in enumerate(sm) if (x - 1) * side <= 0), None)
if cross is None:
res["overshoot"] = 0.0
else:
tail = sm[cross:cross + 3600]
res["overshoot"] = max(0.0, max((1 - x) * side for x in tail))
res["above2x"] = sum(1 for b in post if b[4] > 2)
res["below05"] = sum(1 for b in post if b[4] < 0.5)
res["worst_gap_s"] = max((post[i][0] - post[i - 1][0]) for i in range(1, len(post))) / 1000 if len(post) > 1 else None
return res
def steady(blocks, t_from, t_to):
seg = [b for b in blocks if t_from <= b[0] < t_to]
if len(seg) < 10:
return None, None, None
rs = [b[4] for b in seg]
mean = sum(rs) / len(rs)
std = math.sqrt(sum((x - mean) ** 2 for x in rs) / len(rs))
# blocks per minute
mins = {}
for b in seg:
mins[int(b[0] // 60000)] = mins.get(int(b[0] // 60000), 0) + 1
counts = list(mins.values())[1:-1]
cm = sum(counts) / len(counts) if counts else 0
cs = math.sqrt(sum((c - cm) ** 2 for c in counts) / len(counts)) if counts else 0
gaps = [seg[i][0] - seg[i - 1][0] for i in range(1, len(seg))]
return std, (cs / cm if cm else None), max(gaps) / 1000
def fmt(x, nd=1):
if x is None:
return "none"
if isinstance(x, float):
return ("%." + str(nd) + "f") % x
return str(x)
def run_profile(profile, names, seed, ts_noise, kw=None):
rows = []
hour = 3600 * 1000
for name in names:
rng = random.Random(seed)
ctrl = make_controller(name, ONE / profile.genesis_d, **(kw or {}))
blocks = simulate(profile, ctrl, rng, ts_noise=ts_noise)
row = {"ctrl": name, "blocks": len(blocks)}
ev = profile.events()
if profile.name.startswith("warmup"):
m = metrics(blocks, profile, t_step=0)
row.update(m)
s = steady(blocks, 1 * hour, 2 * hour)
elif profile.name == "epoch30":
recs, ovs, a2, b5, wg = [], [], 0, 0, 0
firsts = []
for h, f in sorted(profile.epoch_steps.items()):
t_end = next((b[0] for b in blocks if b[1] >= h + EPOCH), None)
m = metrics(blocks, profile, h_step=h, t_end=t_end)
recs.append(m.get("recover_s"))
firsts.append(m.get("first10_s"))
ovs.append(m.get("overshoot") or 0)
a2 += m.get("above2x", 0)
b5 += m.get("below05", 0)
m = metrics(blocks, profile, h_step=2 * EPOCH)
good = [r for r in recs if r is not None]
gf = [r for r in firsts if r is not None]
row["first10_s"] = (sum(gf) / len(gf)) if gf else None
row["recover_s"] = (sum(good) / len(good)) if good else None
row["recover_max_s"] = max(good) if good else None
row["recover_fail"] = len(recs) - len(good)
row["overshoot"] = max(ovs)
row["above2x"] = sum(1 for b in blocks if b[4] > 2 and b[1] >= 2 * EPOCH)
row["below05"] = sum(1 for b in blocks if b[4] < 0.5 and b[1] >= 2 * EPOCH)
row["worst_gap_s"] = max(blocks[i][0] - blocks[i - 1][0] for i in range(1, len(blocks))) / 1000
s = steady(blocks, 1 * hour, 2 * hour)
elif profile.name.startswith("hop"):
recs = []
ovs = []
firsts = []
for i, (t, f) in enumerate(profile.time_steps):
t_end = profile.time_steps[i + 1][0] if i + 1 < len(profile.time_steps) else None
m = metrics(blocks, profile, t_step=t, t_end=t_end)
recs.append(m.get("recover_s"))
firsts.append(m.get("first10_s"))
ovs.append(m.get("overshoot") or 0)
good = [r for r in recs if r is not None]
gf = [r for r in firsts if r is not None]
row["first10_s"] = (sum(gf) / len(gf)) if gf else None
row["recover_s"] = (sum(good) / len(good)) if good else None
row["recover_max_s"] = max(good) if good else None
row["recover_fail"] = len(recs) - len(good)
row["overshoot"] = max(ovs)
t0 = profile.time_steps[0][0]
row["above2x"] = sum(1 for b in blocks if b[4] > 2 and b[0] >= t0)
row["below05"] = sum(1 for b in blocks if b[4] < 0.5 and b[0] >= t0)
row["worst_gap_s"] = max(blocks[i][0] - blocks[i - 1][0] for i in range(1, len(blocks)) if blocks[i][0] >= t0) / 1000
s = steady(blocks, 1 * hour, 3 * hour)
elif profile.name == "walk10":
row["above2x"] = sum(1 for b in blocks if b[4] > 2 and b[0] >= hour)
row["below05"] = sum(1 for b in blocks if b[4] < 0.5 and b[0] >= hour)
row["worst_gap_s"] = max(blocks[i][0] - blocks[i - 1][0] for i in range(1, len(blocks)) if blocks[i][0] >= hour) / 1000
s = steady(blocks, 1 * hour, 6 * hour)
elif profile.name == "polluted":
t_pc = profile.time_steps[2][0]
m = metrics(blocks, profile, t_step=t_pc)
row.update(m)
post = [b for b in blocks if b[0] >= t_pc]
row["peak_rate"] = max(b[4] for b in post)
row["trough_rate"] = min(b[4] for b in post if b[0] >= t_pc + 600000) if any(b[0] >= t_pc + 600000 for b in post) else None
s = steady(blocks, 3 * hour, 5 * hour)
elif getattr(profile, "custom", False):
recs, firsts, ovs = [], [], []
for i, (t, f) in enumerate(profile.time_steps):
t_end = profile.time_steps[i + 1][0] if i + 1 < len(profile.time_steps) else None
m = metrics(blocks, profile, t_step=t, t_end=t_end)
row["step%d_first" % (i + 1)] = m.get("first10_s")
row["step%d_settled" % (i + 1)] = m.get("recover_s")
row["step%d_gap" % (i + 1)] = m.get("worst_gap_s")
row["blocks_within10"] = sum(1 for b in blocks if abs(b[4] - 1) <= 0.1)
s = (None, None, None)
elif profile.name == "real":
segs = profile.segments
# the biggest step in the record
big = max(profile.time_steps, key=lambda e: abs(math.log(e[1])))
m = metrics(blocks, profile, t_step=big[0])
row.update(m)
row["step"] = big[1]
s = steady(blocks, segs[-1][0] + 600000, segs[-1][1]) if segs[-1][1] - segs[-1][0] > 1200000 else (None, None, None)
row["blocks_within10"] = sum(1 for b in blocks if abs(b[4] - 1) <= 0.1)
else:
t, f = profile.time_steps[0]
m = metrics(blocks, profile, t_step=t)
row.update(m)
s = steady(blocks, 1 * hour, 3 * hour)
row["ss_std"], row["ss_bpm_cv"], row["ss_worst_gap_s"] = s
if isinstance(ctrl, Igneum):
row["engaged"] = ctrl.engaged
rows.append(row)
return rows
COLS = [("ctrl", "controller"), ("first10_s", "first within 10% s"), ("recover_s", "settled s"), ("recover_blocks", "recovery blocks"),
("recover_max_s", "worst recovery s"), ("recover_fail", "not recovered"), ("overshoot", "overshoot"),
("ss_std", "steady std of rate"), ("ss_bpm_cv", "steady blocks/min CV"), ("worst_gap_s", "worst gap s"),
("above2x", "blocks above 2x"), ("below05", "blocks below 0.5x"), ("blocks", "blocks"), ("engaged", "fast-lane blocks"),
("blocks_within10", "blocks within 10%"), ("step", "step factor"), ("peak_rate", "peak rate"), ("trough_rate", "trough rate after 10 min")]
def print_table(title, rows):
cols = [(k, h) for k, h in COLS if any(k in r for r in rows)]
print("\n### " + title + "\n")
print("| " + " | ".join(h for _, h in cols) + " |")
print("|" + "---|" * len(cols))
for r in rows:
print("| " + " | ".join(fmt(r.get(k), 3 if k in ("ss_std", "ss_bpm_cv", "overshoot") else 1) for k, _ in cols) + " |")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--record")
ap.add_argument("--seed", type=int, default=7)
ap.add_argument("--ts-noise", type=int, default=0, help="uniform timestamp jitter, ms")
ap.add_argument("--profiles", default="all")
ap.add_argument("--controllers", default="kaspa,monero,lwma60,lwma120,igneum,igneum-literal")
ap.add_argument("--tune", action="store_true")
ap.add_argument("--events", default="", help="real record: comma list of ISO times of miner events")
ap.add_argument("--dump", default="", help="profile:controller, write per-block trajectory to stdout as CSV and exit")
ap.add_argument("--custom", default="", help="extra profile: name=genesis_bits_hex,base_MHs,duration_s,t_s:factor,t_s:factor,...")
args = ap.parse_args()
names = args.controllers.split(",")
profiles = synthetic_profiles(args.seed)
if args.profiles != "all":
want = args.profiles.split(",")
profiles = [p for p in profiles if p.name in want]
if args.custom:
name, rest = args.custom.split("=")
parts = rest.split(",")
gbits, mhs, dur = int(parts[0], 16), float(parts[1]), float(parts[2])
p = Profile(name, mhs * 1e6 / 1000, dur * 1000, genesis_d=ONE / bits_to_target(gbits), seed=args.seed)
p.time_steps = [(float(x.split(":")[0]) * 1000, float(x.split(":")[1])) for x in parts[3:]]
p.custom = True
profiles.append(p)
if args.dump:
pn, cn = args.dump.split(":")
p = next(q for q in synthetic_profiles(args.seed) if q.name == pn)
ctrl = make_controller(cn, ONE / p.genesis_d)
for t, h, D, hr, r in simulate(p, ctrl, random.Random(args.seed)):
print("%d,%d,%.6g,%.6g,%.4f" % (t, h, D, hr, r))
return
print("# Difficulty controller simulation, seed %d, ts noise %d ms\n" % (args.seed, args.ts_noise))
if args.tune:
p_names = ["up50", "down50", "epoch30", "hop10", "walk10", "polluted"]
base = {}
grid = [("ns", [60, 120, 180]), ("trig", [0.15, 0.25, 0.35]), ("clamp_pct", [0.02, 0.03, 0.05]),
("ease_pct", [0.03, 0.06, 0.10, 0.15]),
("k0", [8, 16, 32]), ("cap", [6 * T, 20 * T, FTL])]
for key, vals in grid:
rows = []
for v in vals:
kw = dict(base)
kw[key] = v
agg = {"ctrl": "%s=%s" % (key, v if key != "cap" else v // 1000)}
for p in [q for q in synthetic_profiles(args.seed) if q.name in p_names]:
r = run_profile(p, ["igneum"], args.seed, args.ts_noise, kw)[0]
agg[p.name + " rec"] = r.get("recover_s")
agg[p.name + " first"] = r.get("first10_s")
agg[p.name + " std"] = r.get("ss_std")
if p.name == "down50":
agg["down50 gap"] = r.get("worst_gap_s")
if p.name == "polluted":
agg["polluted peak"] = r.get("peak_rate")
if p.name == "walk10":
agg["walk10 >2x/<0.5x"] = "%d/%d" % (r.get("above2x", 0), r.get("below05", 0))
rows.append(agg)
keys = list(rows[0].keys())
print("\n### Tune %s\n" % key)
print("| " + " | ".join(keys) + " |")
print("|" + "---|" * len(keys))
for r in rows:
print("| " + " | ".join(fmt(r[k], 3 if k.endswith("std") else 1) for k in keys) + " |")
return
for p in profiles:
rows = run_profile(p, names, args.seed, args.ts_noise)
if getattr(p, "custom", False):
continue
desc = {"up50": "hash rate x50 at 3 h", "down50": "hash rate /50 at 3 h",
"epoch30": "x1.3 or /1.3 at each epoch boundary from epoch 2", "walk10": "10% per hour log random walk",
"hop3": "3x pool hops in and out every 15 min from 3 h", "hop10": "10x pool hops in and out every 15 min from 3 h",
"polluted": "window polluted by a pre-step slow period (21 min at 0.11 blocks/s, 13 min idle, then 0.6 blocks/s at genesis difficulty)", "warmup-hard": "genesis difficulty 10x too hard", "warmup-easy": "genesis difficulty 10x too easy"}[p.name]
print_table("%s: %s" % (p.name, desc), rows)
for p in profiles:
if getattr(p, "custom", False):
rows = run_profile(p, names, args.seed, args.ts_noise)
keys = [k for k in rows[0].keys() if k != "ss_std" and k != "ss_bpm_cv" and k != "ss_worst_gap_s"]
print("\n### %s: custom profile\n" % p.name)
print("| " + " | ".join(keys) + " |")
print("|" + "---|" * len(keys))
for r in rows:
print("| " + " | ".join(fmt(r.get(k)) for k in keys) + " |")
if args.record:
rec, every = load_record(args.record)
checked, mism, first_bad, exact = kaspa_bits_exact(rec)
print("\n### Exact replay of Kaspa's rule on the record\n")
print("Blocks %d, chain blocks %d, retargets checked %d, exact before the first mismatch %d, mismatches %d%s" % (
len(every), len(rec), checked, exact, mism, "" if first_bad is None else ", first at daa %d: have %s want %s" % first_bad))
print("(A chain-only replay cannot see the merged blocks that Kaspa's sampled window includes once the DAG widens.)")
import datetime
ev = []
for s in [x for x in args.events.split(",") if x]:
ev.append((int(datetime.datetime.fromisoformat(s).timestamp() * 1000), s))
p = record_profile(rec, every, sorted(ev), args.seed)
print("\nReal profile segments (hash rate estimated as expected hashes over wall time):\n")
print("| from s | to s | blocks/s at genesis difficulty | MH/s |")
print("|---|---|---|---|")
g = p.genesis_d
for a, b, r in p.segments:
print("| %d | %d | %.3f | %.1f |" % (a / 1000, b / 1000, r * T / (2 * g), r * 1000 / 1e6))
rows = run_profile(p, names, args.seed, args.ts_noise)
print_table("real: the devnet record replayed as a hash-rate profile", rows)
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,72 @@
hash,daa_score,blue_score,timestamp_ms,bits,difficulty,target_log2,epoch,is_chain_block,parents,selected_parent
f682b78a4e64f0d21c7978af0d341aef55ae30bdea750f8f5d8ac715bb46e099,0,0,1790985600000,0x1e200000,262143,237.000,0,1,0,fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe
0322b4a319b626591c8757e6dd37cd2a0101ac60ca4fec5e11d78bbfc5e49377,0,1,1791064841899,0x1e200000,262143,237.000,0,1,1,f682b78a4e64f0d21c7978af0d341aef55ae30bdea750f8f5d8ac715bb46e099
8d513cd789b7b1e346ceb775ca9e6e494e7159f83495059baecc6469690c7dfd,1,2,1791064845785,0x1e200000,262143,237.000,0,1,1,0322b4a319b626591c8757e6dd37cd2a0101ac60ca4fec5e11d78bbfc5e49377
737a7b5f6eabf30ebe329d71485e0fa9c25300f45cc45bd60e7bb6b1be9e0cfd,2,3,1791064851888,0x1e200000,262143,237.000,0,1,1,8d513cd789b7b1e346ceb775ca9e6e494e7159f83495059baecc6469690c7dfd
bd97690b8210e032bd894f3c79377abafae22b3c669dc63f3c15ed45eb2514a6,3,4,1791064866516,0x1e200000,262143,237.000,0,1,1,737a7b5f6eabf30ebe329d71485e0fa9c25300f45cc45bd60e7bb6b1be9e0cfd
c62206a65591599ee931b606cdbca8fd0bc654dabf25e16653603f7811a54998,4,5,1791064869993,0x1e200000,262143,237.000,0,1,1,bd97690b8210e032bd894f3c79377abafae22b3c669dc63f3c15ed45eb2514a6
60559f78fbecf1f1d2f57a002382fb2d1d40bedcc71901eb27d2f759315d4cbd,5,6,1791064870816,0x1e200000,262143,237.000,0,1,1,c62206a65591599ee931b606cdbca8fd0bc654dabf25e16653603f7811a54998
620fa01ae50573d9829e94e02db41736ba075667e1ab4922bc90996d6e981cb4,6,7,1791064877486,0x1e200000,262143,237.000,0,1,1,60559f78fbecf1f1d2f57a002382fb2d1d40bedcc71901eb27d2f759315d4cbd
c7000d7537787bd32713dcfd9e585144287ebde7729fd0a9113176aba7cce656,7,8,1791064878412,0x1e200000,262143,237.000,0,1,1,620fa01ae50573d9829e94e02db41736ba075667e1ab4922bc90996d6e981cb4
3575023193a84235ee38a5b71ed249b605fd03aa94c4b35594237e8a4d9fa849,8,9,1791064880523,0x1e200000,262143,237.000,0,1,1,c7000d7537787bd32713dcfd9e585144287ebde7729fd0a9113176aba7cce656
5cec2c6908fb88fd48e5fbf95ab85cc2eeaf1d71bd1d8b77b8c9b363a4ef4641,9,10,1791064881901,0x1e200000,262143,237.000,0,1,1,3575023193a84235ee38a5b71ed249b605fd03aa94c4b35594237e8a4d9fa849
6fa137426b9941e6dc6a429a8eb6f0c9b4dac6c6ce279cc357ef72ceec4fd6b5,10,11,1791064887677,0x1e200000,262143,237.000,0,1,1,5cec2c6908fb88fd48e5fbf95ab85cc2eeaf1d71bd1d8b77b8c9b363a4ef4641
fec7a670e822c1f08b14407f48ac8a473ddb46189abdace2bd927d0eabeeaf46,11,12,1791064889947,0x1e200000,262143,237.000,0,1,1,6fa137426b9941e6dc6a429a8eb6f0c9b4dac6c6ce279cc357ef72ceec4fd6b5
c03383e906bf2f321d79b4e7efe097056c53469c3058462cf93039a19d57543c,12,13,1791064900681,0x1e200000,262143,237.000,0,1,1,fec7a670e822c1f08b14407f48ac8a473ddb46189abdace2bd927d0eabeeaf46
3e1b589dbe8faa0461f4cb3b8b33d29980ed2f05c3b3f3d15b04768f592dd2f5,13,14,1791064902336,0x1e200000,262143,237.000,0,1,1,c03383e906bf2f321d79b4e7efe097056c53469c3058462cf93039a19d57543c
a0cd09bc47c9ed43d225988c06a8cc346484c9b71134a64b56613af6466aab2a,14,15,1791064911067,0x1e200000,262143,237.000,0,1,1,3e1b589dbe8faa0461f4cb3b8b33d29980ed2f05c3b3f3d15b04768f592dd2f5
08a85eaf1ff27d9d325f7554a5988952f5e562389f8dcfcfce40af43c48fa209,15,16,1791064918039,0x1e200000,262143,237.000,0,1,1,a0cd09bc47c9ed43d225988c06a8cc346484c9b71134a64b56613af6466aab2a
41b30a0cbf2a158d8e491fc6290cfcfe4a2bce32eb613fd15c8b31abca3208fc,16,17,1791064919174,0x1e200000,262143,237.000,0,1,1,08a85eaf1ff27d9d325f7554a5988952f5e562389f8dcfcfce40af43c48fa209
1e836535a3ec1b6658ee6b20c28d03f24c509d4dabc9d6faac8a687f567990b4,17,18,1791064921291,0x1e200000,262143,237.000,0,1,1,41b30a0cbf2a158d8e491fc6290cfcfe4a2bce32eb613fd15c8b31abca3208fc
be41f51e9a963987eac903aed6e33d7b7c22a15d18c5f679152876bc00d5e034,18,19,1791064923900,0x1e200000,262143,237.000,0,1,1,1e836535a3ec1b6658ee6b20c28d03f24c509d4dabc9d6faac8a687f567990b4
f9d894fc96d4b90a936428ce5e81602174947c3bfcf16f5f1814c0b3a506a049,19,20,1791064927151,0x1e200000,262143,237.000,0,1,1,be41f51e9a963987eac903aed6e33d7b7c22a15d18c5f679152876bc00d5e034
1ffe476303b0e413e3351465754aa4acd0d4f11277ea378eb5b0bb7397a81a2b,20,21,1791064943449,0x1e200000,262143,237.000,0,1,1,f9d894fc96d4b90a936428ce5e81602174947c3bfcf16f5f1814c0b3a506a049
c147e6657d92dc29972dcf85988856fb57bb933b73e564684d0216ca948f82f4,21,22,1791064945254,0x1e200000,262143,237.000,0,1,1,1ffe476303b0e413e3351465754aa4acd0d4f11277ea378eb5b0bb7397a81a2b
b261d241f3ddf5bc60fdb3e6cdadc2433e94f726c6f8ff69458601fd07d46cf6,22,23,1791064950482,0x1e200000,262143,237.000,0,1,1,c147e6657d92dc29972dcf85988856fb57bb933b73e564684d0216ca948f82f4
35e97c3f2b24223c908fd76af68dbb5867d8f14b4547107b719960cc44e7f49b,23,24,1791064958922,0x1e200000,262143,237.000,0,1,1,b261d241f3ddf5bc60fdb3e6cdadc2433e94f726c6f8ff69458601fd07d46cf6
fb5bbdb7d72646bd03e48b5b974b0931cd7162661520396481603889c52e2d91,24,25,1791064973190,0x1e200000,262143,237.000,0,1,1,35e97c3f2b24223c908fd76af68dbb5867d8f14b4547107b719960cc44e7f49b
a1cb9b1e9bc6c8572a9262a87ec0e4ad67cb91cd80671d2721f28de3a7420803,25,26,1791064985334,0x1e200000,262143,237.000,0,1,1,fb5bbdb7d72646bd03e48b5b974b0931cd7162661520396481603889c52e2d91
d761677b75518c1696bdc4e8c3da23da337855719f0c4b837fbf2bdc5a67255e,26,27,1791064988846,0x1e200000,262143,237.000,0,1,1,a1cb9b1e9bc6c8572a9262a87ec0e4ad67cb91cd80671d2721f28de3a7420803
accdf3d7f1e05c4ccf4abe1ddfa2e8ee321c10dc4e2632ec6ed0e29b3bd33534,27,28,1791064990073,0x1e200000,262143,237.000,0,1,1,d761677b75518c1696bdc4e8c3da23da337855719f0c4b837fbf2bdc5a67255e
0a287ebbccd3702ef0f9ae0b1a16e96235c9ffed351a6c5d729e35fa91c4e26e,28,29,1791064991359,0x1e200000,262143,237.000,0,1,1,accdf3d7f1e05c4ccf4abe1ddfa2e8ee321c10dc4e2632ec6ed0e29b3bd33534
f9148a720809f21174eb868a8693be44cf6107eb86c61bd3c8627729f9132fce,29,30,1791064991825,0x1e200000,262143,237.000,0,1,1,0a287ebbccd3702ef0f9ae0b1a16e96235c9ffed351a6c5d729e35fa91c4e26e
054796677bd96555ffeb9ebb976e7e9debdb0f844329306f32db421ae4915701,30,31,1791064999476,0x1e200000,262143,237.000,0,1,1,f9148a720809f21174eb868a8693be44cf6107eb86c61bd3c8627729f9132fce
e81ff6542c6db03010fb2d28818bb8af31dafecb9b03aa6a066c6d677887a795,31,32,1791065002853,0x1e200000,262143,237.000,0,1,1,054796677bd96555ffeb9ebb976e7e9debdb0f844329306f32db421ae4915701
e6949659f12c8e7977948d1424b8d45f290f59bc2b8664488c0daac28f07012f,32,33,1791065003701,0x1e200000,262143,237.000,0,1,1,e81ff6542c6db03010fb2d28818bb8af31dafecb9b03aa6a066c6d677887a795
17ede982c9bfb3f4596784fe8015da4cc0e1c64db3de5cf7aa3f7140a2b2ed52,33,34,1791065006811,0x1e200000,262143,237.000,0,1,1,e6949659f12c8e7977948d1424b8d45f290f59bc2b8664488c0daac28f07012f
a8500c0de976454edaf793a7d39d444f3d4322c2263277e6efef2124f456081f,34,35,1791065008031,0x1e200000,262143,237.000,0,1,1,17ede982c9bfb3f4596784fe8015da4cc0e1c64db3de5cf7aa3f7140a2b2ed52
c99d13b3187b2bd58081ba9f9f8cfdf814ef2cb4be1c5113e21dd8bced9da38d,35,36,1791065020076,0x1e200000,262143,237.000,0,1,1,a8500c0de976454edaf793a7d39d444f3d4322c2263277e6efef2124f456081f
1f77783a4d3c9a281858097a38637238b9f40f5815a1230b928001a7021c389a,36,37,1791065026806,0x1e200000,262143,237.000,0,1,1,c99d13b3187b2bd58081ba9f9f8cfdf814ef2cb4be1c5113e21dd8bced9da38d
2c712b8ae345b2e810df9751a5ec50c39b7a00443807fbbfc02e9a8bf1e9a726,37,38,1791065030951,0x1e200000,262143,237.000,0,1,1,1f77783a4d3c9a281858097a38637238b9f40f5815a1230b928001a7021c389a
fe5ddb5b18283727c8be65baaabab2d41c50879c9e36e20e16fd27f6f66ab4c1,38,39,1791065035617,0x1e200000,262143,237.000,0,1,1,2c712b8ae345b2e810df9751a5ec50c39b7a00443807fbbfc02e9a8bf1e9a726
f2fa37cc103d237778e9313ce6e8d2153d696fb4625997eca76d330c354f7ed0,39,40,1791065039405,0x1e200000,262143,237.000,0,1,1,fe5ddb5b18283727c8be65baaabab2d41c50879c9e36e20e16fd27f6f66ab4c1
2d4b40be333452a6f8902c1794806245946b78ff58f4784c590c83f421305f35,40,41,1791065046855,0x1e200000,262143,237.000,0,1,1,f2fa37cc103d237778e9313ce6e8d2153d696fb4625997eca76d330c354f7ed0
b5f8bf309a9a382670f745b023b5a4ebaa8e4e1bba5c47b4dabcc1a92d30b1ec,41,42,1791065050857,0x1e200000,262143,237.000,0,1,1,2d4b40be333452a6f8902c1794806245946b78ff58f4784c590c83f421305f35
48ca377d34e136796de5157b5c3b0ce14eac73e2399e3a6afc4627a9d1cbb7a6,42,43,1791065052476,0x1e200000,262143,237.000,0,1,1,b5f8bf309a9a382670f745b023b5a4ebaa8e4e1bba5c47b4dabcc1a92d30b1ec
e2b56dad6d08493dc2d5aeab31e9f3e8dc8c191cfa27036481bcedf3241e1860,43,44,1791065060489,0x1e200000,262143,237.000,0,1,1,48ca377d34e136796de5157b5c3b0ce14eac73e2399e3a6afc4627a9d1cbb7a6
9d11abb454f2e37c0cd22c124859ae328998a653aeea8be7e3a7659fa0dd63c3,44,45,1791065061231,0x1e200000,262143,237.000,0,1,1,e2b56dad6d08493dc2d5aeab31e9f3e8dc8c191cfa27036481bcedf3241e1860
eb8294eabb1cc9ce45672ec6f0ca70594ab4a0090c23bce3373181b9a849874c,45,46,1791065074683,0x1e200000,262143,237.000,0,1,1,9d11abb454f2e37c0cd22c124859ae328998a653aeea8be7e3a7659fa0dd63c3
e171a36ec1db1c80f10be66dbcfc040280f23045062ddbbeaf244dfcecc50c52,46,47,1791065076616,0x1e200000,262143,237.000,0,1,1,eb8294eabb1cc9ce45672ec6f0ca70594ab4a0090c23bce3373181b9a849874c
3dd1e017f956d795cf38afa8b9d4fecbe946f2235e6a3b707e035cbdb2b9149a,47,48,1791065077978,0x1e200000,262143,237.000,0,1,1,e171a36ec1db1c80f10be66dbcfc040280f23045062ddbbeaf244dfcecc50c52
48082590e3b3a9a26b342b30c59e8ef1fb5c8504a4ce4d2d80861e60bdc61522,48,49,1791065089796,0x1e200000,262143,237.000,0,1,1,3dd1e017f956d795cf38afa8b9d4fecbe946f2235e6a3b707e035cbdb2b9149a
07255ff6f23c535ab57649fbebe4a2f77c3ae2bf4693481b49039b0ec2c7fd47,49,50,1791065090108,0x1e200000,262143,237.000,0,1,1,48082590e3b3a9a26b342b30c59e8ef1fb5c8504a4ce4d2d80861e60bdc61522
ab5bf826fe7448ec3c9972646a44361dd756bb040db04904a2c3c9830689e018,50,51,1791065094402,0x1e200000,262143,237.000,0,1,1,07255ff6f23c535ab57649fbebe4a2f77c3ae2bf4693481b49039b0ec2c7fd47
675447226354a45e35b1b1febfa936dd5731ef428d012c8c10a9b07f9a0b2c04,51,52,1791065130757,0x1e200000,262143,237.000,0,1,1,ab5bf826fe7448ec3c9972646a44361dd756bb040db04904a2c3c9830689e018
d6220f9c1ec76d2bb5c86038b27c0d15f57be979902b40df7cd1bf11ce4c9ef1,52,53,1791065147386,0x1e200000,262143,237.000,0,1,1,675447226354a45e35b1b1febfa936dd5731ef428d012c8c10a9b07f9a0b2c04
f949325b145a3b62e7af1536eb3e9f48506bd772bff91994cb9aa37e18e78d25,53,54,1791065154993,0x1e200000,262143,237.000,0,1,1,d6220f9c1ec76d2bb5c86038b27c0d15f57be979902b40df7cd1bf11ce4c9ef1
6c19660fcccf3567c08028cbe732117bd4d9f879c1399e8e94b81d7100017428,54,55,1791065158489,0x1e200000,262143,237.000,0,1,1,f949325b145a3b62e7af1536eb3e9f48506bd772bff91994cb9aa37e18e78d25
b0c456d7bbc1e3c12ec07e93838e491d8b7c63a21c1d7b6bc8835c02bd8f09cf,55,56,1791065170560,0x1e200000,262143,237.000,0,1,1,6c19660fcccf3567c08028cbe732117bd4d9f879c1399e8e94b81d7100017428
8365a9bb74f2d1dc38e8bde28bf0a2a85578a2f2d68be7f885dcdd287c61a9e6,56,57,1791065183725,0x1e200000,262143,237.000,0,1,1,b0c456d7bbc1e3c12ec07e93838e491d8b7c63a21c1d7b6bc8835c02bd8f09cf
7d48c5d69d6b4bd376df1f96d03ee9d6d835abbf7f4b8c466a7517a02155709c,57,58,1791065186929,0x1e200000,262143,237.000,0,1,1,8365a9bb74f2d1dc38e8bde28bf0a2a85578a2f2d68be7f885dcdd287c61a9e6
a707d2cd94132978bdc7edea6c9b0e4fb0bc61e1522a6e7634d699d1d566e0fd,58,59,1791065205228,0x1e200000,262143,237.000,0,1,1,7d48c5d69d6b4bd376df1f96d03ee9d6d835abbf7f4b8c466a7517a02155709c
e8f83031bdcf103cc198195b502b75111eb2cd5cd84dbefe88915c9029fac73f,59,60,1791065210983,0x1e200000,262143,237.000,0,1,1,a707d2cd94132978bdc7edea6c9b0e4fb0bc61e1522a6e7634d699d1d566e0fd
9ccc5bc3e825d5c4caec2d4f988e75d1186f602a3311cfbc82fc60b247037676,60,61,1791065235129,0x1e200000,262143,237.000,0,1,1,e8f83031bdcf103cc198195b502b75111eb2cd5cd84dbefe88915c9029fac73f
84351ab4fa287b444ee9ca53ed29c86b7e03f1e8041ec4335c0d519ba12dda96,61,62,1791065240447,0x1e200000,262143,237.000,0,1,1,9ccc5bc3e825d5c4caec2d4f988e75d1186f602a3311cfbc82fc60b247037676
dba4a780d02c6403a7f0f4fb3f97cc5e8c656510c656cc0b2414e5820d74c6ae,62,63,1791065241055,0x1e200000,262143,237.000,0,1,1,84351ab4fa287b444ee9ca53ed29c86b7e03f1e8041ec4335c0d519ba12dda96
6302c364ca17963f38cc8cc58bd48ab31abd680d0a0d18f9a1bcff7ef4bc59f2,63,64,1791065242751,0x1e200000,262143,237.000,0,1,1,dba4a780d02c6403a7f0f4fb3f97cc5e8c656510c656cc0b2414e5820d74c6ae
7cc366e9d92bbd164f2b993d148b800c9527baf873bb923843deb28a96d9f899,64,65,1791065296547,0x1e200000,262143,237.000,0,1,1,6302c364ca17963f38cc8cc58bd48ab31abd680d0a0d18f9a1bcff7ef4bc59f2
053e38976f96c4c626118cb58f4374f48f454ff4cbf907ae77ebbf05abf5e796,65,66,1791065308412,0x1e200000,262143,237.000,0,1,1,7cc366e9d92bbd164f2b993d148b800c9527baf873bb923843deb28a96d9f899
bf028090f1acd0e1d3267787a988da59ef835be47f2d8d5e1a33be673db94ab3,66,67,1791065323011,0x1e200000,262143,237.000,0,1,1,053e38976f96c4c626118cb58f4374f48f454ff4cbf907ae77ebbf05abf5e796
e3f6898abe54a323ee14c9dbd413a03b63120aae2cebe5854ebf03effd648012,67,68,1791065386033,0x1e200000,262143,237.000,0,1,1,bf028090f1acd0e1d3267787a988da59ef835be47f2d8d5e1a33be673db94ab3
b5ded0f901dc18df095fdeacacb213bc08ee44b322b095b3fac8bc46260210ad,68,69,1791065403843,0x1e200000,262143,237.000,0,1,1,e3f6898abe54a323ee14c9dbd413a03b63120aae2cebe5854ebf03effd648012
ea9432a210ce50f7556b17cfec9509892dcdb732c5e3b3dff4bd098965801fe0,69,70,1791065407394,0x1e200000,262143,237.000,0,1,1,b5ded0f901dc18df095fdeacacb213bc08ee44b322b095b3fac8bc46260210ad
1 hash daa_score blue_score timestamp_ms bits difficulty target_log2 epoch is_chain_block parents selected_parent
2 f682b78a4e64f0d21c7978af0d341aef55ae30bdea750f8f5d8ac715bb46e099 0 0 1790985600000 0x1e200000 262143 237.000 0 1 0 fefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefefe
3 0322b4a319b626591c8757e6dd37cd2a0101ac60ca4fec5e11d78bbfc5e49377 0 1 1791064841899 0x1e200000 262143 237.000 0 1 1 f682b78a4e64f0d21c7978af0d341aef55ae30bdea750f8f5d8ac715bb46e099
4 8d513cd789b7b1e346ceb775ca9e6e494e7159f83495059baecc6469690c7dfd 1 2 1791064845785 0x1e200000 262143 237.000 0 1 1 0322b4a319b626591c8757e6dd37cd2a0101ac60ca4fec5e11d78bbfc5e49377
5 737a7b5f6eabf30ebe329d71485e0fa9c25300f45cc45bd60e7bb6b1be9e0cfd 2 3 1791064851888 0x1e200000 262143 237.000 0 1 1 8d513cd789b7b1e346ceb775ca9e6e494e7159f83495059baecc6469690c7dfd
6 bd97690b8210e032bd894f3c79377abafae22b3c669dc63f3c15ed45eb2514a6 3 4 1791064866516 0x1e200000 262143 237.000 0 1 1 737a7b5f6eabf30ebe329d71485e0fa9c25300f45cc45bd60e7bb6b1be9e0cfd
7 c62206a65591599ee931b606cdbca8fd0bc654dabf25e16653603f7811a54998 4 5 1791064869993 0x1e200000 262143 237.000 0 1 1 bd97690b8210e032bd894f3c79377abafae22b3c669dc63f3c15ed45eb2514a6
8 60559f78fbecf1f1d2f57a002382fb2d1d40bedcc71901eb27d2f759315d4cbd 5 6 1791064870816 0x1e200000 262143 237.000 0 1 1 c62206a65591599ee931b606cdbca8fd0bc654dabf25e16653603f7811a54998
9 620fa01ae50573d9829e94e02db41736ba075667e1ab4922bc90996d6e981cb4 6 7 1791064877486 0x1e200000 262143 237.000 0 1 1 60559f78fbecf1f1d2f57a002382fb2d1d40bedcc71901eb27d2f759315d4cbd
10 c7000d7537787bd32713dcfd9e585144287ebde7729fd0a9113176aba7cce656 7 8 1791064878412 0x1e200000 262143 237.000 0 1 1 620fa01ae50573d9829e94e02db41736ba075667e1ab4922bc90996d6e981cb4
11 3575023193a84235ee38a5b71ed249b605fd03aa94c4b35594237e8a4d9fa849 8 9 1791064880523 0x1e200000 262143 237.000 0 1 1 c7000d7537787bd32713dcfd9e585144287ebde7729fd0a9113176aba7cce656
12 5cec2c6908fb88fd48e5fbf95ab85cc2eeaf1d71bd1d8b77b8c9b363a4ef4641 9 10 1791064881901 0x1e200000 262143 237.000 0 1 1 3575023193a84235ee38a5b71ed249b605fd03aa94c4b35594237e8a4d9fa849
13 6fa137426b9941e6dc6a429a8eb6f0c9b4dac6c6ce279cc357ef72ceec4fd6b5 10 11 1791064887677 0x1e200000 262143 237.000 0 1 1 5cec2c6908fb88fd48e5fbf95ab85cc2eeaf1d71bd1d8b77b8c9b363a4ef4641
14 fec7a670e822c1f08b14407f48ac8a473ddb46189abdace2bd927d0eabeeaf46 11 12 1791064889947 0x1e200000 262143 237.000 0 1 1 6fa137426b9941e6dc6a429a8eb6f0c9b4dac6c6ce279cc357ef72ceec4fd6b5
15 c03383e906bf2f321d79b4e7efe097056c53469c3058462cf93039a19d57543c 12 13 1791064900681 0x1e200000 262143 237.000 0 1 1 fec7a670e822c1f08b14407f48ac8a473ddb46189abdace2bd927d0eabeeaf46
16 3e1b589dbe8faa0461f4cb3b8b33d29980ed2f05c3b3f3d15b04768f592dd2f5 13 14 1791064902336 0x1e200000 262143 237.000 0 1 1 c03383e906bf2f321d79b4e7efe097056c53469c3058462cf93039a19d57543c
17 a0cd09bc47c9ed43d225988c06a8cc346484c9b71134a64b56613af6466aab2a 14 15 1791064911067 0x1e200000 262143 237.000 0 1 1 3e1b589dbe8faa0461f4cb3b8b33d29980ed2f05c3b3f3d15b04768f592dd2f5
18 08a85eaf1ff27d9d325f7554a5988952f5e562389f8dcfcfce40af43c48fa209 15 16 1791064918039 0x1e200000 262143 237.000 0 1 1 a0cd09bc47c9ed43d225988c06a8cc346484c9b71134a64b56613af6466aab2a
19 41b30a0cbf2a158d8e491fc6290cfcfe4a2bce32eb613fd15c8b31abca3208fc 16 17 1791064919174 0x1e200000 262143 237.000 0 1 1 08a85eaf1ff27d9d325f7554a5988952f5e562389f8dcfcfce40af43c48fa209
20 1e836535a3ec1b6658ee6b20c28d03f24c509d4dabc9d6faac8a687f567990b4 17 18 1791064921291 0x1e200000 262143 237.000 0 1 1 41b30a0cbf2a158d8e491fc6290cfcfe4a2bce32eb613fd15c8b31abca3208fc
21 be41f51e9a963987eac903aed6e33d7b7c22a15d18c5f679152876bc00d5e034 18 19 1791064923900 0x1e200000 262143 237.000 0 1 1 1e836535a3ec1b6658ee6b20c28d03f24c509d4dabc9d6faac8a687f567990b4
22 f9d894fc96d4b90a936428ce5e81602174947c3bfcf16f5f1814c0b3a506a049 19 20 1791064927151 0x1e200000 262143 237.000 0 1 1 be41f51e9a963987eac903aed6e33d7b7c22a15d18c5f679152876bc00d5e034
23 1ffe476303b0e413e3351465754aa4acd0d4f11277ea378eb5b0bb7397a81a2b 20 21 1791064943449 0x1e200000 262143 237.000 0 1 1 f9d894fc96d4b90a936428ce5e81602174947c3bfcf16f5f1814c0b3a506a049
24 c147e6657d92dc29972dcf85988856fb57bb933b73e564684d0216ca948f82f4 21 22 1791064945254 0x1e200000 262143 237.000 0 1 1 1ffe476303b0e413e3351465754aa4acd0d4f11277ea378eb5b0bb7397a81a2b
25 b261d241f3ddf5bc60fdb3e6cdadc2433e94f726c6f8ff69458601fd07d46cf6 22 23 1791064950482 0x1e200000 262143 237.000 0 1 1 c147e6657d92dc29972dcf85988856fb57bb933b73e564684d0216ca948f82f4
26 35e97c3f2b24223c908fd76af68dbb5867d8f14b4547107b719960cc44e7f49b 23 24 1791064958922 0x1e200000 262143 237.000 0 1 1 b261d241f3ddf5bc60fdb3e6cdadc2433e94f726c6f8ff69458601fd07d46cf6
27 fb5bbdb7d72646bd03e48b5b974b0931cd7162661520396481603889c52e2d91 24 25 1791064973190 0x1e200000 262143 237.000 0 1 1 35e97c3f2b24223c908fd76af68dbb5867d8f14b4547107b719960cc44e7f49b
28 a1cb9b1e9bc6c8572a9262a87ec0e4ad67cb91cd80671d2721f28de3a7420803 25 26 1791064985334 0x1e200000 262143 237.000 0 1 1 fb5bbdb7d72646bd03e48b5b974b0931cd7162661520396481603889c52e2d91
29 d761677b75518c1696bdc4e8c3da23da337855719f0c4b837fbf2bdc5a67255e 26 27 1791064988846 0x1e200000 262143 237.000 0 1 1 a1cb9b1e9bc6c8572a9262a87ec0e4ad67cb91cd80671d2721f28de3a7420803
30 accdf3d7f1e05c4ccf4abe1ddfa2e8ee321c10dc4e2632ec6ed0e29b3bd33534 27 28 1791064990073 0x1e200000 262143 237.000 0 1 1 d761677b75518c1696bdc4e8c3da23da337855719f0c4b837fbf2bdc5a67255e
31 0a287ebbccd3702ef0f9ae0b1a16e96235c9ffed351a6c5d729e35fa91c4e26e 28 29 1791064991359 0x1e200000 262143 237.000 0 1 1 accdf3d7f1e05c4ccf4abe1ddfa2e8ee321c10dc4e2632ec6ed0e29b3bd33534
32 f9148a720809f21174eb868a8693be44cf6107eb86c61bd3c8627729f9132fce 29 30 1791064991825 0x1e200000 262143 237.000 0 1 1 0a287ebbccd3702ef0f9ae0b1a16e96235c9ffed351a6c5d729e35fa91c4e26e
33 054796677bd96555ffeb9ebb976e7e9debdb0f844329306f32db421ae4915701 30 31 1791064999476 0x1e200000 262143 237.000 0 1 1 f9148a720809f21174eb868a8693be44cf6107eb86c61bd3c8627729f9132fce
34 e81ff6542c6db03010fb2d28818bb8af31dafecb9b03aa6a066c6d677887a795 31 32 1791065002853 0x1e200000 262143 237.000 0 1 1 054796677bd96555ffeb9ebb976e7e9debdb0f844329306f32db421ae4915701
35 e6949659f12c8e7977948d1424b8d45f290f59bc2b8664488c0daac28f07012f 32 33 1791065003701 0x1e200000 262143 237.000 0 1 1 e81ff6542c6db03010fb2d28818bb8af31dafecb9b03aa6a066c6d677887a795
36 17ede982c9bfb3f4596784fe8015da4cc0e1c64db3de5cf7aa3f7140a2b2ed52 33 34 1791065006811 0x1e200000 262143 237.000 0 1 1 e6949659f12c8e7977948d1424b8d45f290f59bc2b8664488c0daac28f07012f
37 a8500c0de976454edaf793a7d39d444f3d4322c2263277e6efef2124f456081f 34 35 1791065008031 0x1e200000 262143 237.000 0 1 1 17ede982c9bfb3f4596784fe8015da4cc0e1c64db3de5cf7aa3f7140a2b2ed52
38 c99d13b3187b2bd58081ba9f9f8cfdf814ef2cb4be1c5113e21dd8bced9da38d 35 36 1791065020076 0x1e200000 262143 237.000 0 1 1 a8500c0de976454edaf793a7d39d444f3d4322c2263277e6efef2124f456081f
39 1f77783a4d3c9a281858097a38637238b9f40f5815a1230b928001a7021c389a 36 37 1791065026806 0x1e200000 262143 237.000 0 1 1 c99d13b3187b2bd58081ba9f9f8cfdf814ef2cb4be1c5113e21dd8bced9da38d
40 2c712b8ae345b2e810df9751a5ec50c39b7a00443807fbbfc02e9a8bf1e9a726 37 38 1791065030951 0x1e200000 262143 237.000 0 1 1 1f77783a4d3c9a281858097a38637238b9f40f5815a1230b928001a7021c389a
41 fe5ddb5b18283727c8be65baaabab2d41c50879c9e36e20e16fd27f6f66ab4c1 38 39 1791065035617 0x1e200000 262143 237.000 0 1 1 2c712b8ae345b2e810df9751a5ec50c39b7a00443807fbbfc02e9a8bf1e9a726
42 f2fa37cc103d237778e9313ce6e8d2153d696fb4625997eca76d330c354f7ed0 39 40 1791065039405 0x1e200000 262143 237.000 0 1 1 fe5ddb5b18283727c8be65baaabab2d41c50879c9e36e20e16fd27f6f66ab4c1
43 2d4b40be333452a6f8902c1794806245946b78ff58f4784c590c83f421305f35 40 41 1791065046855 0x1e200000 262143 237.000 0 1 1 f2fa37cc103d237778e9313ce6e8d2153d696fb4625997eca76d330c354f7ed0
44 b5f8bf309a9a382670f745b023b5a4ebaa8e4e1bba5c47b4dabcc1a92d30b1ec 41 42 1791065050857 0x1e200000 262143 237.000 0 1 1 2d4b40be333452a6f8902c1794806245946b78ff58f4784c590c83f421305f35
45 48ca377d34e136796de5157b5c3b0ce14eac73e2399e3a6afc4627a9d1cbb7a6 42 43 1791065052476 0x1e200000 262143 237.000 0 1 1 b5f8bf309a9a382670f745b023b5a4ebaa8e4e1bba5c47b4dabcc1a92d30b1ec
46 e2b56dad6d08493dc2d5aeab31e9f3e8dc8c191cfa27036481bcedf3241e1860 43 44 1791065060489 0x1e200000 262143 237.000 0 1 1 48ca377d34e136796de5157b5c3b0ce14eac73e2399e3a6afc4627a9d1cbb7a6
47 9d11abb454f2e37c0cd22c124859ae328998a653aeea8be7e3a7659fa0dd63c3 44 45 1791065061231 0x1e200000 262143 237.000 0 1 1 e2b56dad6d08493dc2d5aeab31e9f3e8dc8c191cfa27036481bcedf3241e1860
48 eb8294eabb1cc9ce45672ec6f0ca70594ab4a0090c23bce3373181b9a849874c 45 46 1791065074683 0x1e200000 262143 237.000 0 1 1 9d11abb454f2e37c0cd22c124859ae328998a653aeea8be7e3a7659fa0dd63c3
49 e171a36ec1db1c80f10be66dbcfc040280f23045062ddbbeaf244dfcecc50c52 46 47 1791065076616 0x1e200000 262143 237.000 0 1 1 eb8294eabb1cc9ce45672ec6f0ca70594ab4a0090c23bce3373181b9a849874c
50 3dd1e017f956d795cf38afa8b9d4fecbe946f2235e6a3b707e035cbdb2b9149a 47 48 1791065077978 0x1e200000 262143 237.000 0 1 1 e171a36ec1db1c80f10be66dbcfc040280f23045062ddbbeaf244dfcecc50c52
51 48082590e3b3a9a26b342b30c59e8ef1fb5c8504a4ce4d2d80861e60bdc61522 48 49 1791065089796 0x1e200000 262143 237.000 0 1 1 3dd1e017f956d795cf38afa8b9d4fecbe946f2235e6a3b707e035cbdb2b9149a
52 07255ff6f23c535ab57649fbebe4a2f77c3ae2bf4693481b49039b0ec2c7fd47 49 50 1791065090108 0x1e200000 262143 237.000 0 1 1 48082590e3b3a9a26b342b30c59e8ef1fb5c8504a4ce4d2d80861e60bdc61522
53 ab5bf826fe7448ec3c9972646a44361dd756bb040db04904a2c3c9830689e018 50 51 1791065094402 0x1e200000 262143 237.000 0 1 1 07255ff6f23c535ab57649fbebe4a2f77c3ae2bf4693481b49039b0ec2c7fd47
54 675447226354a45e35b1b1febfa936dd5731ef428d012c8c10a9b07f9a0b2c04 51 52 1791065130757 0x1e200000 262143 237.000 0 1 1 ab5bf826fe7448ec3c9972646a44361dd756bb040db04904a2c3c9830689e018
55 d6220f9c1ec76d2bb5c86038b27c0d15f57be979902b40df7cd1bf11ce4c9ef1 52 53 1791065147386 0x1e200000 262143 237.000 0 1 1 675447226354a45e35b1b1febfa936dd5731ef428d012c8c10a9b07f9a0b2c04
56 f949325b145a3b62e7af1536eb3e9f48506bd772bff91994cb9aa37e18e78d25 53 54 1791065154993 0x1e200000 262143 237.000 0 1 1 d6220f9c1ec76d2bb5c86038b27c0d15f57be979902b40df7cd1bf11ce4c9ef1
57 6c19660fcccf3567c08028cbe732117bd4d9f879c1399e8e94b81d7100017428 54 55 1791065158489 0x1e200000 262143 237.000 0 1 1 f949325b145a3b62e7af1536eb3e9f48506bd772bff91994cb9aa37e18e78d25
58 b0c456d7bbc1e3c12ec07e93838e491d8b7c63a21c1d7b6bc8835c02bd8f09cf 55 56 1791065170560 0x1e200000 262143 237.000 0 1 1 6c19660fcccf3567c08028cbe732117bd4d9f879c1399e8e94b81d7100017428
59 8365a9bb74f2d1dc38e8bde28bf0a2a85578a2f2d68be7f885dcdd287c61a9e6 56 57 1791065183725 0x1e200000 262143 237.000 0 1 1 b0c456d7bbc1e3c12ec07e93838e491d8b7c63a21c1d7b6bc8835c02bd8f09cf
60 7d48c5d69d6b4bd376df1f96d03ee9d6d835abbf7f4b8c466a7517a02155709c 57 58 1791065186929 0x1e200000 262143 237.000 0 1 1 8365a9bb74f2d1dc38e8bde28bf0a2a85578a2f2d68be7f885dcdd287c61a9e6
61 a707d2cd94132978bdc7edea6c9b0e4fb0bc61e1522a6e7634d699d1d566e0fd 58 59 1791065205228 0x1e200000 262143 237.000 0 1 1 7d48c5d69d6b4bd376df1f96d03ee9d6d835abbf7f4b8c466a7517a02155709c
62 e8f83031bdcf103cc198195b502b75111eb2cd5cd84dbefe88915c9029fac73f 59 60 1791065210983 0x1e200000 262143 237.000 0 1 1 a707d2cd94132978bdc7edea6c9b0e4fb0bc61e1522a6e7634d699d1d566e0fd
63 9ccc5bc3e825d5c4caec2d4f988e75d1186f602a3311cfbc82fc60b247037676 60 61 1791065235129 0x1e200000 262143 237.000 0 1 1 e8f83031bdcf103cc198195b502b75111eb2cd5cd84dbefe88915c9029fac73f
64 84351ab4fa287b444ee9ca53ed29c86b7e03f1e8041ec4335c0d519ba12dda96 61 62 1791065240447 0x1e200000 262143 237.000 0 1 1 9ccc5bc3e825d5c4caec2d4f988e75d1186f602a3311cfbc82fc60b247037676
65 dba4a780d02c6403a7f0f4fb3f97cc5e8c656510c656cc0b2414e5820d74c6ae 62 63 1791065241055 0x1e200000 262143 237.000 0 1 1 84351ab4fa287b444ee9ca53ed29c86b7e03f1e8041ec4335c0d519ba12dda96
66 6302c364ca17963f38cc8cc58bd48ab31abd680d0a0d18f9a1bcff7ef4bc59f2 63 64 1791065242751 0x1e200000 262143 237.000 0 1 1 dba4a780d02c6403a7f0f4fb3f97cc5e8c656510c656cc0b2414e5820d74c6ae
67 7cc366e9d92bbd164f2b993d148b800c9527baf873bb923843deb28a96d9f899 64 65 1791065296547 0x1e200000 262143 237.000 0 1 1 6302c364ca17963f38cc8cc58bd48ab31abd680d0a0d18f9a1bcff7ef4bc59f2
68 053e38976f96c4c626118cb58f4374f48f454ff4cbf907ae77ebbf05abf5e796 65 66 1791065308412 0x1e200000 262143 237.000 0 1 1 7cc366e9d92bbd164f2b993d148b800c9527baf873bb923843deb28a96d9f899
69 bf028090f1acd0e1d3267787a988da59ef835be47f2d8d5e1a33be673db94ab3 66 67 1791065323011 0x1e200000 262143 237.000 0 1 1 053e38976f96c4c626118cb58f4374f48f454ff4cbf907ae77ebbf05abf5e796
70 e3f6898abe54a323ee14c9dbd413a03b63120aae2cebe5854ebf03effd648012 67 68 1791065386033 0x1e200000 262143 237.000 0 1 1 bf028090f1acd0e1d3267787a988da59ef835be47f2d8d5e1a33be673db94ab3
71 b5ded0f901dc18df095fdeacacb213bc08ee44b322b095b3fac8bc46260210ad 68 69 1791065403843 0x1e200000 262143 237.000 0 1 1 e3f6898abe54a323ee14c9dbd413a03b63120aae2cebe5854ebf03effd648012
72 ea9432a210ce50f7556b17cfec9509892dcdb732c5e3b3dff4bd098965801fe0 69 70 1791065407394 0x1e200000 262143 237.000 0 1 1 b5ded0f901dc18df095fdeacacb213bc08ee44b322b095b3fac8bc46260210ad