igneum/sim/difficulty/README.md
igneum-labs e17d3816ba Difficulty: the live oscillation of 4 October, its cause, the DAG replay and rule v2 (reference window 600 behind a height switch)
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>
2026-10-04 13:26:23 +00:00

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.