Live devnet v4: a second RTX 5090 joining 7 minutes into an epoch left the whole-epoch reference lane polluted for the hour; the short lane read 11 to 25% above it and the 25% trigger flipped between the two for 40 minutes (102M to 164M, 54 to 81 blocks a minute). Record and hash-rate truth under sim/difficulty/records/. sim.py gains a DAG model (miners on nodes with igneum-miner's template staleness, GHOSTDAG, the rule as the node runs it) and --live replay: std of log difficulty 0.115 against the record's 0.134, 4.3 peaks of 1.31x against 4 of 1.37x. The brief's candidates (short lane 240/360, ease clamp 3%, clamp once per DAA second, hysteresis, median of three) leave 0.09 to 0.13; capping the reference lane at the newest 600 blocks of the epoch gives 0.026 with no flips. Rule v2 = that cap, epoch lane only, behind difficulty_v2_activation_daa (devnet-v4 fork). Attack suite and synthetic set before and after, 3-node test network of the switch (testnet_v2.py), analysis document, spec 2.3, bench-log entry, ledger M24, fast-time file carries the new field. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
76 lines
6.1 KiB
Markdown
76 lines
6.1 KiB
Markdown
# 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 |
|
|
| `records/live-2026-10-04.csv` | every header of live devnet v4 through 11:18 UTC on 4 October 2026 (observer node wRPC, `records/pull_live.py`): the columns above plus blue work, mergeset blues and reds, vote key hash |
|
|
| `records/live-2026-10-04-hashrate.csv` | the hash-rate truth of the same hours: every STATUS line of the four GPU workers from the log intake (last-30-s MH/s every 30 s, run id, which node the worker mined through) |
|
|
| `testnet_v2.py` | the 3-node CPU test network of rule v2 and its height switch (ports 29600 and up, `/tmp/igneum-diffv2`) |
|
|
|
|
`docs/analysis/difficulty-2026-10-04-oscillation.md` holds the live finding, the DAG replay and rule v2.
|
|
|
|
## Model
|
|
|
|
Chain model (every synthetic profile and the attacks): 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).
|
|
|
|
DAG model (`--live`, 4 October 2026): miners on nodes, each fetching templates from its node with the staleness of
|
|
`igneum-miner`'s job queue, blocks carrying the parents and the node's stamp of their template, GHOSTDAG with every
|
|
merged block blue, the rule run exactly as the node runs it (chain walk from the virtual's selected parent, sampled
|
|
window per mergeset). The hash-rate schedule comes from the records CSV. Fitted once to the record's merge fraction
|
|
(`--dag-delay`); compared per UTC window on block rate, DAG width, difficulty band, std of log difficulty, peaks over
|
|
15%, blocks per minute, lane flips.
|
|
|
|
## 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 |
|
|
| `igneum-v2` | rule v2 (4 October 2026): the reference lane is the epoch lane over the newest 600 blocks of the epoch; the sampled long lane is not consulted |
|
|
| `igneum:key=value+...` | any variant: `ns`, `trig`, `trig_exit` (hysteresis), `soft=a/b` (blend), `median3`, `clamp_window` (ms), `ref_window`, `long_min`, `clamp_pct`, `ease_pct`, `cap`, `k0` |
|
|
|
|
## Runs
|
|
|
|
python3 sim.py synthetic set, every controller
|
|
python3 sim.py --ts-noise 500 the same with +-500 ms timestamp jitter
|
|
python3 sim.py --quick CI smoke run: up50 and warmup-hard, kaspa and igneum (about a minute)
|
|
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)
|
|
python3 sim.py --live records/live-2026-10-04.csv --hashrate records/live-2026-10-04-hashrate.csv \
|
|
--controllers igneum,igneum-v2 --seed 7 --dag-delay 0.75 --live-end 10:58
|
|
the live devnet replayed through the DAG model against the record
|
|
python3 testnet_v2.py --minutes 28 --activation 900 --out /tmp/igneum-diffv2/run1
|
|
3 CPU nodes: a 2x step under v1, the switch at DAA 900, steps under v2
|
|
|
|
Requirements: Python 3 only (numpy not needed). The full synthetic set takes about two minutes.
|
|
|
|
## Limits
|
|
|
|
The chain model has no DAG (one chain, no parallel blocks, no reds), no network latency, no miner template
|
|
refresh delay, honest timestamps unless `--ts-noise`. The DAG model has no reds, one fitted staleness scale, and
|
|
the Mac's hash rate as a constant. 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.
|