B6 (R15-10): the B1 lane's reference values (10:27 UK) in the verification budget table: n = 20, k = 64, 32-byte labels, three openings a challenge; proof bytes 45 + k(1 + 96(n + 1)) = 129,133 measured; the verifier's k(3n + 2) + 2k = 4,096 BLAKE2b calls; build-7's measured BLAKE2b rate 245 ns a call gives about 1.0 ms a proof cold on one core (0.1 percent of a core at 1 bps, 1 percent at 10 bps); and the row that decides the shape: the paper's Corollary-2 k of about 2.9e7 at n = 20 puts a proof near 56 GB, 200 times the 256 MiB message limit and 7.5 minutes a core to verify — the block-carried proof is BLOCKED on k (the B1 lane's row O-1). Every verdict NOT RUN

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-09 09:30:45 +00:00
parent 206e73f70e
commit 4182e2be13

View file

@ -8,39 +8,44 @@ The question B6 asks (the plan, section 6): what a node, a pool and the network
| Symbol | Meaning | First value | Owner |
|---|---|---|---|
| `q` | challenges per proof (the paper's sampled positions) | BLOCKED: the B1 fixture's challenge count | B1 lane |
| `d` | Merkle depth of the commitment (log2 of the leaf count `N`) | BLOCKED: the B1 fixture's depth | B1 lane |
| `L` | leaf (label) size in bytes | BLOCKED: the B1 fixture's label width | B1 lane |
| `h` | inner node size: one hash, 32 bytes (the paper's hash; the node's `Hash` is 32 bytes, `crypto/hashes/src/lib.rs`) | 32 | node lane |
| `Δ` | openings per challenge beyond the challenged leaf itself (the DRSample parents the verifier re-derives a label from; MTP's δ) | BLOCKED: the B1 fixture's parent count | B1 lane |
| `B_p` | proof bytes per block (section 2) | formula below; BLOCKED as a number | node lane (formula), B1 lane (values) |
| `q` (the paper's `k`) | challenges per proof (the paper's sampled positions) | 64 in the B1 fixtures (a fixture size, not a security parameter: [BS25] Corollary 2 with ABH17's DRSample constant gives `k ≈ 5,700 λ log2 N ≈ 2.9 × 10^7` at `n = 20`; that `k` is the B1 lane's open row O-1 and this table's section 2.1) | B1 lane (10:27 UK) |
| `d` (the paper's `n`) | Merkle depth of the commitment (log2 of the leaf count `N = 2^n`) | 20 in the reference fixture (also 16, 12, 8, 4) | B1 lane |
| `L` | leaf (label) size in bytes | 32 (a raw BLAKE2b-256 label; `λ = 256`) | B1 lane |
| `h` | inner node size: one hash, 32 bytes (the paper's BLAKE2b-256; the node's `Hash` is 32 bytes, `crypto/hashes/src/lib.rs`) | 32 | node lane |
| `Δ` | openings per challenge beyond the challenged leaf itself (the DRSample parents the verifier re-derives a label from; the paper's `indeg`) | 2 for every node ≥ 3 (node 2 has 1 parent, node 1 none): 3 openings per challenge, each with its own full path, no path sharing, as the paper writes it | B1 lane |
| `B_p` | proof bytes per block (section 2) | 129,133 at `n = 20, k = 64` (measured on the B1 fixture; the formula below reproduces it) | node lane (formula), B1 lane (values) |
| `H_hdr` | the block header's wire bytes today (section 2) | about 222 + 32 × (parents by level); at most about 222 + 32 × 16 × levels | node lane |
| `r_64` | single-core SHA-256 rate on 64-byte inputs (one Merkle node) on the hub-class box | 6.8 × 10^6 per second (build-7, section 3) | node lane, measured |
| `r_64` | single-core SHA-256 rate on 64-byte inputs on the hub-class box (kept as the node's own hash rate) | 6.8 × 10^6 per second (build-7, section 3) | node lane, measured |
| `r_b2` | single-core BLAKE2b rate on 64-byte inputs on the hub-class box (the paper's oracle: every verifier input is one BLAKE2b block of 41 to 105 bytes) | 4.1 × 10^6 per second = 245 ns a call (build-7, `openssl speed -evp blake2b512`, 261.0 MB/s at 64 B, read 10:29 UK) | node lane, measured |
| `r_L` | single-core SHA-256 bytes per second on `L`-byte inputs | 1.55 × 10^9 bytes/s at 1 KiB, 1.02 × 10^9 at 256 B (build-7, section 3) | node lane, measured |
| `T_v` | cold CPU verification time of one proof (section 3) | formula below; BLOCKED as a number | node lane (formula), B1 lane (values) |
| `T_v` | cold CPU verification time of one proof (section 3) | about 1.0 ms at `n = 20, k = 64` on one build-7 core (4,096 BLAKE2b calls × 245 ns), a computed first value; the B1 lane's measured cold time replaces it | node lane (formula and the rate), B1 lane (the measured time) |
| `bps` | blocks per second: devnet-4 1, testnet 1, mainnet 10 (`consensus/core/src/config/params.rs` 2342 via 2208 `BlockrateParams::new::<1>()`, 1928, 1789 `new::<10>()`) | 1 / 10 | node lane |
## 2. Proof bytes per block and the header
`B_p = h + q × (L + Δ × L + d × h)` — the commitment root (carried in the header or beside it, the B1 lane's call), then per challenge the challenged label, its `Δ` parent labels and one `d`-deep authentication path of 32-byte nodes (sibling paths shared between challenges shrink this; the formula is the upper bound the paper's non-shared layout gives). With the B1 fixture's `q`, `d`, `L`, `Δ` the number follows; until then the row is BLOCKED.
The B1 lane's exact layout (10:27 UK, [BS25] section 3.2 and appendix C, the encoding pinned with B2): `B_p = 45 + Σ_i (1 + 32 (n + 1)(1 + indeg_i))` = `45 + k (1 + 96 (n + 1))` when every challenged node has two parents — a 45-byte head (`τ` and the counts), then per challenge one byte and three openings (the label and its two parents), each a 32-byte label with its own `n + 1`-node path, no path sharing. Measured on the fixtures: `n = 20, k = 64`: 129,133 bytes; `n = 16`: 104,557; `n = 12`: 79,981; `n = 8`: 55,405; `n = 4, k = 8`: 3,893. The general form the first landing gave, `h + q (L + ΔL + dh)`, is the same count without the per-challenge byte and the head.
### 2.1 The row that decides the shape: `k` is not 64
The fixtures' `k = 64` is a size the B1 lane chose to exercise the code, not the soundness parameter. The paper's Corollary 2 with ABH17's proven DRSample constant gives `k ≈ 5,700 × λ × log2 N`, about 2.9 × 10^7 at `n = 20`: a proof of about `2.9 × 10^7 × 1,921` bytes ≈ 56 GB, far above the `N` labels it proves and 200 times the node's 256 MiB message limit. Until the B1 lane's row O-1 names a `k` with a proof (a tighter constant, a different extractor, or a smaller `N` per unit), every row below that reads a number at `k = 64` is a shape, and the unit as a block-carried proof is BLOCKED on `k`.
The header today (`consensus/core/src/header.rs` 158–174: version u16, parents by level, hash merkle root, accepted id merkle root, utxo commitment, timestamp u64, bits u32, nonce u64, daa score u64, blue work u192, blue score u64, pruning point, Igneum's vote key hash): about 222 bytes of fixed fields plus 32 bytes per parent reference, with at most `max_block_parents` direct parents (10 at 1 bps, `consensus/core/src/config/bps.rs` 60–66: `ghostdag_k / 2` clamped to 10..16) per level. A proof-carrying unit adds its root to the header (32 bytes) and `B_p` to the block or beside it; which is the B1 lane's design call and a BLOCKED row here.
| Row | Formula | First value | Node limit (source) | Verdict |
|---|---|---|---|---|
| Proof bytes per block `B_p` | `h + q(L + ΔL + dh)` | BLOCKED (q, d, L, Δ) | one P2P message is at most 256 MiB (`protocol/p2p/src/core/connection_handler.rs` 47, `P2P_MAX_MESSAGE_SIZE`); a block body's mass is bounded by the network's mass limits, which a proof would have to be exempt from or counted in (the B1 lane's call) | NOT RUN |
| Header growth | +32 bytes (the root) | 32 | the header is hashed whole for PoW; the class v5 bound hash reads the header's prehash (the engine's `hash_bound(prehash, nonce)`, `pool/src/verify.rs` 54): a 32-byte field is within every reader's prehash layout only if the layout is re-frozen (a digest move) | NOT RUN |
| Proof bytes per second at the block rate | `B_p × bps` | BLOCKED | a node relays every block it accepts to up to 128 inbound and 8 outbound peers (`kaspad/src/args.rs` 124–125): the uplink carries `B_p × bps × peers`; the plan's propagation row | NOT RUN |
| Proof bytes per block `B_p` | `45 + k(1 + 96(n + 1))` | 129,133 B at `n = 20, k = 64`; about 56 GB at the Corollary-2 `k` (section 2.1) | one P2P message is at most 256 MiB (`protocol/p2p/src/core/connection_handler.rs` 47, `P2P_MAX_MESSAGE_SIZE`); a block body's mass is bounded by the network's mass limits, which a proof would have to be exempt from or counted in (the B1 lane's call) | NOT RUN |
| Header growth | +32 bytes (`τ`, the commitment) plus whatever B2's binding adds (a nonce, `k`); the primitive has no nonce and no difficulty target of its own | 32 + B2's fields | the header is hashed whole for PoW; the class v5 bound hash reads the header's prehash (the engine's `hash_bound(prehash, nonce)`, `pool/src/verify.rs` 54): a 32-byte field is within every reader's prehash layout only if the layout is re-frozen (a digest move) | NOT RUN |
| Proof bytes per second at the block rate | `B_p × bps` | 129 KB/s at 1 bps, 1.29 MB/s at 10 bps per peer at `k = 64`; times up to 136 peers: 17.6 MB/s and 176 MB/s of uplink per node | a node relays every block it accepts to up to 128 inbound and 8 outbound peers (`kaspad/src/args.rs` 124–125): the uplink carries `B_p × bps × peers`; the plan's propagation row | NOT RUN |
## 3. Cold verification time on the hub-class box and on the minimum node
`T_v = q × (1 + Δ) × d / r_64 + q × (1 + Δ) × L / r_L + T_label`, where `T_label` is the cost of re-deriving one label from its `Δ` parents (the paper's per-node function; BLOCKED until the B1 lane names it) times `q`. Measured today, so the formula has a rate to multiply: on build-7 (AMD EPYC 9454, the hub-class box main named for this row), single-core SHA-256 via `openssl speed -evp sha256 -seconds 2`, read at 10:07 UK: 435.4 MB/s on 64-byte inputs = 6.8 × 10^6 hashes per second (one Merkle node per hash), 1,024.4 MB/s at 256 bytes, 1,550.9 MB/s at 1 KiB, 1,805.8 MB/s at 8 KiB. One hash of a 64-byte node therefore costs about 147 ns on one core; a path of `d = 20` costs about 2.9 µs; a proof with `q = 100` challenges and `Δ = 3` would cost about 1.2 ms of path hashing plus its labels — a shape, not the number, which waits on the fixture. The B1 lane's verifier, timed cold (no cache, a fresh process) on build-7, replaces the product the moment its fixtures exist.
The B1 lane's operation count (10:27 UK): per proof `k` challenge hashes, per challenge `(1 + indeg) × n` Merkle hashes plus one local-consistency hash (the label re-derived from its parents), plus two graph draws per challenged node (one BLAKE2b each, a rejection retry with probability under 2^-40): `k (3n + 2)` oracle calls plus `2k` graph calls when `indeg = 2`; at `n = 20, k = 64`: 3,968 + 128 = 4,096 BLAKE2b calls, each on one block of 41 to 105 bytes. So `T_v = (k (3n + 2) + 2k) / r_b2`, and `T_label` is the one local-consistency hash already counted. Measured today, so the formula has a rate to multiply: on build-7 (AMD EPYC 9454, the hub-class box main named for this row), single-core SHA-256 via `openssl speed -evp sha256 -seconds 2`, read at 10:07 UK: 435.4 MB/s on 64-byte inputs = 6.8 × 10^6 hashes per second (one Merkle node per hash), 1,024.4 MB/s at 256 bytes, 1,550.9 MB/s at 1 KiB, 1,805.8 MB/s at 8 KiB. BLAKE2b, the paper's oracle, measured the same way at 10:29 UK: 261.0 MB/s on 64-byte inputs = 4.1 × 10^6 calls a second, 245 ns a call (BLAKE2s-256 319.3 MB/s, 200 ns). So the reference proof at `n = 20, k = 64` costs about 4,096 × 245 ns ≈ 1.0 ms of oracle calls on one build-7 core, cold (no cache helps a Merkle path), before the Python overhead the B1 verifier carries; at the Corollary-2 `k` of 2.9 × 10^7 the same arithmetic gives about 7.5 minutes per proof on one core — the second reading of section 2.1. The B1 lane's verifier, timed cold (no cache, a fresh process) on build-7, replaces the product the moment its fixtures exist.
| Row | Formula | First value | Node limit (source) | Verdict |
|---|---|---|---|---|
| `T_v` on the hub-class box (build-7, one core) | above, with `r_64` = 6.8 M/s | BLOCKED (q, d, L, Δ, T_label) | the block time: 1,000 ms at 1 bps, 100 ms at 10 bps (`consensus/core/src/config/bps.rs` 52–56, `target_time_per_block = 1000 / BPS`); a node must verify at the admitted rate with headroom, so `T_v × bps` must sit well under one core-second per second on the smallest node | NOT RUN |
| `T_v` on the hub-class box (build-7, one core) | `(k(3n + 2) + 2k) / r_b2` | about 1.0 ms at `n = 20, k = 64` (computed from the measured rate); the B1 lane's Python cold time per `n` lands in its rows by about 11:00 UK; a compiled verifier does not exist (BLOCKED, B1 or B3) | the block time: 1,000 ms at 1 bps, 100 ms at 10 bps (`consensus/core/src/config/bps.rs` 52–56, `target_time_per_block = 1000 / BPS`); a node must verify at the admitted rate with headroom, so `T_v × bps` must sit well under one core-second per second on the smallest node | NOT RUN |
| `T_v` on the minimum node | the same formula with that node's measured `r_64` | BLOCKED: no minimum node is defined in any file the node lane holds (TV-04 carries the same gap for the hub count); the Mac entry serves 2.0.2 to 16 GB and 32 GB machines | the fd limit and the memory bound are the node's two hard floors today: `DESIRED_DAEMON_SOFT_FD_LIMIT` 1,048,576 (`kaspad/src/daemon.rs` 57, 2.0.3) and the executor's memory bound (the 2.0.3 rules' ring and record thinning; a fresh 2.0.2 sync read 62 to 86 GB on build-1 at 09:5x UK, main's separate read by 12:00) | NOT RUN |
| Verification at the maximum admitted rate (the plan's "cold verification, not only cached success") | `T_v × (bps + r_orphan)` where `r_orphan` is the orphan and side-block rate a node also verifies | BLOCKED | every block a node validates is PoW-checked before its body (`consensus/src/pipeline/header_processor`); the class v5 engine already costs a dataset read per header, so the proof's `T_v` adds to, never replaces, that read | NOT RUN |
| Verification at the maximum admitted rate (the plan's "cold verification, not only cached success") | `T_v × (bps + r_orphan)` where `r_orphan` is the orphan and side-block rate a node also verifies | about 1 ms × 1 = 0.1 percent of one core at 1 bps, 1 percent at 10 bps, at `k = 64`; at the Corollary-2 `k` the unit cannot be verified at any block rate on one core | every block a node validates is PoW-checked before its body (`consensus/src/pipeline/header_processor`); the class v5 engine already costs a dataset read per header, so the proof's `T_v` adds to, never replaces, that read | NOT RUN |
## 4. Invalid-proof amplification against the node's ban budget
@ -52,7 +57,7 @@ What an invalid proof costs a node is `T_v` (the verifier runs to the failing op
| Ledger N6, the refused-block memory | a block refused by a rule is remembered for `RELAY_REFUSAL_WINDOW`; a peer that advertises it again is banned for `RELAY_REFUSAL_BAN` | window 3,600 s, ban 600 s (`protocol/flows/src/flow_context.rs` 106, 109) | the same invalid block costs `T_v` once per node per hour; a flood of distinct invalid blocks is bounded by the strike guard above, not by N6 | NOT RUN |
| The class v5 state wait (not a strike) | a block whose epoch state this node has not reached is held off without a strike (`protocol/flows/src/v10/blockrelay/flow.rs` 328–337; `ibd/flow.rs` 1282 `is_v5_state_wait`) | — | a proof whose verification needs the epoch's state (an input-specific unit, B2) inherits this: a node behind the cut cannot verify it and must hold it, which is the body-sync class the night's kits 2 to 7 worked through (fixes 2 to 7 on foreign-seed-capture-2 0156e250) | NOT RUN |
| The fd class (closed this morning) | accepted connections that never close held sockets in ESTAB until "Too many open files" at 524,288 on lp-4090-04 (05:49 UK); fixed by the transport close on router teardown and the server's HTTP/2 and TCP keepalive (76cc5a88 on release-2.0.3-node-k6/k7) | keepalive 10 s, unanswered 20 s (`protocol/p2p/src/core/connection_handler.rs` `server_keepalive`); 128 inbound (`kaspad/src/args.rs` 125) | a flood's connections cost sockets, not proofs: 128 inbound at once, each closed within 30 s of silence; a proof flood rides the inbound limit times the strike budget | NOT RUN |
| Amplification | `A = (bytes a peer sends) / (CPU-seconds the node spends)` per hour per IP | `A ≤ 2 × T_v` CPU-seconds per `2 × B_p` bytes per hour per IP under the strike guard | the plan's caution: batching and worst-case payloads need their own analysis; the rows above are the rules' bounds, not a proof | NOT RUN |
| Amplification | `A = (bytes a peer sends) / (CPU-seconds the node spends)` per hour per IP | `A ≤ 2 × T_v` CPU-seconds per `2 × B_p` bytes per hour per IP under the strike guard: at `k = 64` about 2 ms of CPU and 258 KB per IP per hour — the CPU side is small, the bytes side is the one to watch at the Corollary-2 `k` | the plan's caution: batching and worst-case payloads need their own analysis; the rows above are the rules' bounds, not a proof | NOT RUN |
## 5. The pool's share checks
@ -60,16 +65,16 @@ Today a share is one hash: the pool evaluates the member's lane on the CPU, `che
| Row | Today | With a proof-carrying unit | Node limit (source) | Verdict |
|---|---|---|---|---|
| Work per share | one bound hash (`verify.rs` 54), `cost_ms` per verdict | a share must carry the unit's proof and the pool verifies `T_v` per share, or the share is a hash of a committed unit the pool verifies once per unit (the B1/B2 design call) | the pool's semaphore and its box's cores: `shares/s × T_v` core-seconds per second | NOT RUN |
| Bytes per share | nonce and hash (`protocol.rs` `ShareResult`) | `+ B_p` per share, or per unit | the pool protocol's line length (`protocol.rs` 178 `line`) | NOT RUN |
| Work per share | one bound hash (`verify.rs` 54), `cost_ms` per verdict | a share must carry the unit's proof and the pool verifies `T_v` per share (about 1 ms at `k = 64`: one core verifies about 1,000 shares a second), or the share is a hash of a committed unit the pool verifies once per unit (the B1/B2 design call) | the pool's semaphore and its box's cores: `shares/s × T_v` core-seconds per second | NOT RUN |
| Bytes per share | nonce and hash (`protocol.rs` `ShareResult`) | `+ B_p` per share (129 KB at `k = 64`), or per unit | the pool protocol's line length (`protocol.rs` 178 `line`) | NOT RUN |
| A member's invalid share | `wrong_hash` / `above_target`, counted (`server.rs` 275, 293), no ban today | an invalid proof costs the pool `T_v`; the pool needs its own strike rule (the N6 shape) | none today in `pool/src` | NOT RUN |
## 6. Propagation, the epoch wall and the body sync
| Row | Formula | Value today (source) | Verdict |
|---|---|---|---|
| Bytes per block on the wire | `H_hdr + body + B_p` | `H_hdr` about 222 + 32 × parents; `B_p` BLOCKED | NOT RUN |
| Against the message limit | `H_hdr + body + B_p ≤ 256 MiB` | `P2P_MAX_MESSAGE_SIZE` 256 MiB (`connection_handler.rs` 47); an IBD batch carries `IBD_BATCH_SIZE` = 99 blocks per request (`protocol/flows/src/ibd/streams.rs` 25), so a batch carries `99 × B_p` of proof bytes | NOT RUN |
| Bytes per block on the wire | `H_hdr + body + B_p` | `H_hdr` about 222 + 32 × parents; `B_p` 129,133 at `k = 64` — the proof cannot travel in the header (the B1 lane's word); body or beside the block is B2's and this lane's call | NOT RUN |
| Against the message limit | `H_hdr + body + B_p ≤ 256 MiB` | `P2P_MAX_MESSAGE_SIZE` 256 MiB (`connection_handler.rs` 47); an IBD batch carries `IBD_BATCH_SIZE` = 99 blocks per request (`protocol/flows/src/ibd/streams.rs` 25), so a batch carries `99 × B_p` = 12.8 MB of proof bytes at `k = 64` (fits); at the Corollary-2 `k` one proof is 200 times the limit (does not fit, section 2.1) | NOT RUN |
| Against the block time | the proof must reach every peer inside the block time with headroom: `(H_hdr + body + B_p) × peers / uplink < 1/bps` | 1,000 ms at 1 bps, 100 ms at 10 bps (`bps.rs` 52–56); up to 136 peers per node (`args.rs` 124–125) | NOT RUN |
| The class v5 epoch wall | every `POW_EPOCH_BLOCKS` = 3,600 blocks the engine's seed moves to the state after the cut block less `POW_EPOCH_LEAD` = 600 (`consensus/core/src/igneum.rs` 227, 233); the IBD catch-up waits up to `V5_STATE_WAIT` = 300 s per cut for the executor (`protocol/flows/src/ibd/flow.rs` 1278) | an input-specific proof (B2) keyed to the epoch's state is verifiable only by a node at or past that cut; the sync shape of the night (the per-cut body sync e189afb4, the heaviest anchor 94e4c6ba, the foreign-seed capture fixes 2 to 7) is the cost a joiner pays before it can verify a proof at all | NOT RUN |
| The body sync | a proof that travels in the body is fetched with the body (`RequestBlockBodies`, 99 per batch) after the header validated; a proof that travels in the header is fetched with the header | the headers-first IBD validates headers before bodies (`ibd/flow.rs` `sync_headers`), so a header-carried proof is verified at header time — `T_v` per header in IBD, at the IBD's rate, not the block rate | NOT RUN |
@ -79,10 +84,12 @@ Today a share is one hash: the pool evaluates the member's lane on the CPU, `che
| Symbol or row | Owner | Clock |
|---|---|---|
| `q`, `d`, `L`, `Δ`, `T_label` (the fixture's parameters and the per-label function) | B1 lane (a4490b2dfe9114a55) | asked 10:08 UK; by 12:00, else these rows stay BLOCKED in the 13:00 landing |
| `T_v` measured cold on build-7 with the B1 verifier | node lane, the moment the B1 fixtures and verifier exist on a box | after the B1 lane's clock |
| `k` with a proof (the soundness parameter; the fixtures' 64 is a size) — the row that decides whether a block-carried proof fits any limit above | B1 lane (a4490b2dfe9114a55), its row O-1 | no clock from the B1 lane today |
| the fixture's parameters `n`, `L`, `indeg` and the exact byte layout | landed in this table (the B1 lane, 10:27 UK; fixtures on build-6:/srv/builds/mhpow-b1/out/, kat-n20.proof.bin sha256 a49367d9…, kat-n20.json fd0d75b6…) | done |
| `T_v` measured (the Python verifier's cold time per `n`) | B1 lane, out/row-nXX-k64.json | about 11:00 UK |
| `T_v` measured cold on build-7 with a compiled verifier | B1 or B3 lane (none exists; the Python one is the reference) | no clock today |
| the minimum node and its `r_64` | main (no file defines it; TV-04 carries the same gap for the hub count) | main's word |
| the proof's place (header, body, beside) and the record's size | B1/B2 lanes | their design call |
| the proof's place (body or beside the block; the header carries `τ` and B2's binding) and the record's size | B2 lane with this lane | their design call |
| the equivalent lottery-contribution and security-budget comparison the Track B rule requires before any number is compared | the coordinator (written and approved first) | before any row here reads anything but NOT RUN |
Nothing in this document changes a rule, a parameter or a line of the node fork; the limits cited are the lines as they stand on 27f54124 and the parent's master, read 9 October 2026.