V6-08 design: proving task protection (leases, backpressure gate, shard-sized tasks, bounded transport, preflight, flow conservation), docs/design/proving-task-protection.md
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c0e43ec600
commit
b51ce846af
1 changed files with 273 additions and 0 deletions
273
docs/design/proving-task-protection.md
Normal file
273
docs/design/proving-task-protection.md
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
# Proving task protection (master review R1, residual V6-08)
|
||||
|
||||
8 October 2026, 20:4x UK, the enforced-proving lane's V6-08 sub-lane. The order, from the master review's closure of V6-08
|
||||
(`docs/plans/igneum-2.0-master/evidence/04_full_system/IGNEUM_V6_Full_System_Review.md`, pages 210 to 212): "smaller verifiable paid
|
||||
tasks for constrained hardware where economical; a chain-health/backpressure gate; bounded binary/chunked proof transport; preflight
|
||||
package integrity and dependency checks. Any protected reservation must resist claim-and-abandon griefing and restore open recovery
|
||||
when the lease expires", and the measurement correction: "queue size alone cannot certify throughput. Use flow conservation: closing
|
||||
backlog = opening backlog + arrivals - verified completions - expiries - cancellations. Report each departure category". The outcome
|
||||
ledger that already landed (b7507dd79 on master: `tools/fleet/prover-outcomes.py`, `tools/fleet/box-prover.py` point 6,
|
||||
`docs/analysis/proving-outcome-ledger.md`) is the instrument this document's six items read; it is not repeated here.
|
||||
|
||||
The facts this document stands on are read from the code at the 2.0.2 line's tip (`successor-2.0.1` at aea5b465, the node fork) and
|
||||
from box master (cef5234b5): `vendor/igneum-node/igneum/exec/src/proving.rs` (the pool, the claims, the segment status), `rpc.rs`
|
||||
(the prover's RPC), `protocol/flows/src/v10/proving.rs` (the relay bound), `tools/fleet/box-prover.py` (the fleet prover),
|
||||
`proving/igneum-prove/host/src/main.rs` and `pinned.rs` (the host's modes and its pinned-guest check), and the day's record
|
||||
`docs/analysis/proving-pipeline-2026-10-08.md` (3,421 claims, 93 paid, 137 steals, 313 claims and no paid segment on the 12 GB tier,
|
||||
629 chain failures from a dangling host link, 264 from the host's proof string, 1,681 records submitted into a chain that never paid).
|
||||
|
||||
## 0. Activation, in one place
|
||||
|
||||
| Part | Where it lives | Default on the live object | On in tests | Consensus |
|
||||
|---|---|---|---|---|
|
||||
| Leases, abandon deprioritising, lease counters, the `leased` fields of `igneum_getAssignedShards` | `ProvingConfig.leases: Option<LeaseRules>` in the node fork; the daemon sets it from `IGNEUM_PROVING_LEASES=1` (the pattern of `IGNEUM_PROOF_VERIFY`, `proving.rs` `VerifyMode::from_env`), else `None` | off (`None`): the claim path behaves exactly as 0.3.20's | `Some(LeaseRules::test())` in the pool and RPC tests | none: claims are local to the node they are posted to; no Params field, the digest does not move |
|
||||
| Bounded transport at the pool and the RPC | `ProofPool::submit`, `submit_segment`, `igneum_submitProofRecord`, `igneum_submitSegmentRecord` | always on, because the bound is the relay's existing `MAX_PROOF_BYTES` (8 MiB): a proof over it is already undeliverable to any peer, so no valid proof's path changes | the same | none: the body rule and the consensus-side cap are the node lane's (V6-09) |
|
||||
| Backpressure gate, task sizing, preflight, the `abandoned` outcome and the conservation line | `tools/fleet/box-prover.py`, `tools/fleet/prover-outcomes.py` | prover-side; the gate and preflight are on (a prover that cannot pass preflight must not claim); `TASK` defaults to `auto` | the python self-tests | none |
|
||||
| A lease honoured in the payment rule (a record from a non-holder inside the lease pays nothing) | NOT BUILT. Named: `Params::proving_lease_rule_daa`, `u64::MAX` on every network, entering the digest only once set, a later cut with its own fast-time crossing | never | | consensus: the shipper's word only |
|
||||
| Lease gossip between nodes (`IgneumSegmentClaim` p2p message) | NOT BUILT. Named as phase 2 of item 1; a protocol message, not a consensus rule | | | protocol, not consensus; still not built without the node lane's word |
|
||||
| A per-tier segment length | NOT BUILT. `proving_v1_segment_blocks` is a consensus param; "smaller" is answered below without touching it | | | consensus |
|
||||
|
||||
Nothing here moves the live object's digest (be5f4068 on 2.0.1 and 2.0.2, the node lane's word of 20:3x UK).
|
||||
|
||||
## 1. The protected reservation: a claim is a lease
|
||||
|
||||
**What exists today.** `ProofPool::claim_segment(first, last, key_hash, tip_daa)` (`proving.rs` 1151) stores any claim; a second claim
|
||||
for the same `(first, last, key)` refreshes it; a claim by another key for the same range is stored beside it, never refused;
|
||||
the expiry is `tip_daa + unproven_daa`, the whole 600 DAA window. `open_claims(tip_daa)` (1166) drops a claim whose expiry passed or
|
||||
whose key's segment record is in the pool. `igneum_claimSegment` (`rpc.rs` 1521) always answers `accepted: true`; `igneum_getProofClaims`
|
||||
lists the open ones; `igneum_getAssignedShards` (1278) lists the claims covering a block only through `igneum_getSegment`. The claims
|
||||
are consulted by nothing: `submit_segment` (1430) and the payment rule never read them. And the fleet prover never posts one:
|
||||
`box-prover.py` "claims" in its own state only (`state["claimed"]`, `RESULT claim`) and spreads by FNV-1a of (first, key). So on
|
||||
8 October two boxes could prove the same segment and the faster one was paid: 137 steals, 4.0 percent of claims, the slow box's
|
||||
work wasted (the pipeline record, "Steals").
|
||||
|
||||
**The change.** `LeaseRules { lease_daa, abandon_window_daa, abandon_threshold, penalty_daa }` on `ProvingConfig` (`None` = today's
|
||||
behaviour). With it set:
|
||||
|
||||
1. A claim is a lease: `expires_daa = tip_daa + lease_daa` (default 300 DAA: the measured claim-to-submitted p95 is 230 s and the
|
||||
prover's own margin floor is 240 DAA; `unproven_daa` is the cap), never the whole window.
|
||||
2. A second claimant with a different key inside the lease is refused: `claim_segment` returns `ClaimOutcome { accepted: false,
|
||||
holder: Some(claim), reason: "leased by 0x.. until DAA N" }`; `igneum_claimSegment` answers `accepted: false` with the holder and
|
||||
the expiry so the prover skips the segment instead of racing. The same key inside its own lease is answered `accepted: true,
|
||||
new: false` and the expiry does NOT move (a refresh cannot hold a segment for ever).
|
||||
3. On expiry the segment reopens: `open_claims` drops the lease, any key may claim, and the first claimant's record is still accepted
|
||||
if it arrives before the segment's deadline, because `submit_segment` does not consult the claims (unchanged; the test pins it).
|
||||
That is "open recovery when the lease expires".
|
||||
4. Claim-and-abandon: when a lease expires and the pool holds no segment record of its key for that range, `open_claims` records an
|
||||
abandon for the key (a per-key ring of expiry DAA scores, bounded to 64 entries). A key with `abandon_threshold` (default 3) or
|
||||
more abandons inside `abandon_window_daa` (default 3,600 DAA, one hour) is deprioritised, never banned: its claim on a segment is
|
||||
refused with `reason: "deprioritised: N abandoned leases in the window; claim again from DAA D"` until `tip_daa >= last_daa +
|
||||
penalty_daa` (default 120 DAA past the segment's last block), so prompt provers have first pick and the abandoner takes the
|
||||
leftovers; the count decays out of the window on its own. `igneum_getAssignedShards` reports, for the caller's keys,
|
||||
`claimPriority: { abandons, deprioritised, claimFromDaa }` on every shard row, and each row gains `leased`, `leaseHolder`,
|
||||
`leaseExpiresDaa` so the prover's candidate grouping (every shard "open, unpaid, not in the pool") also requires "not leased by
|
||||
another key".
|
||||
5. The prover posts the claim: `box-prover.py` calls `igneum_claimSegment` before the export and treats `accepted: false` as a
|
||||
skip (a `RESULT lease_refused` line, no ledger row: a refused claim is not an eligible job). The lease is the prover's reservation
|
||||
for the time it takes to prove; the segment deadline stays the chain's.
|
||||
6. Counters for the chain-side view, in `igneum_getProvingStatus` under `leases`: `open`, `granted`, `refusedHeld` (a holder's lease
|
||||
refused a second claimant), `refusedDeprioritised`, `landed` (a lease whose record arrived), `abandoned` (expired with no record).
|
||||
Flow: `granted = landed + abandoned + open` at any read (the test asserts it).
|
||||
|
||||
**Tests, known-failed first** (`proving.rs`, the pool's test module; `rpc.rs`, the node-info test module's `Ctx` pattern):
|
||||
|
||||
- `a_second_claimant_is_refused_inside_the_lease_and_the_segment_reopens_at_expiry`: today's pool accepts both claims (the known
|
||||
failure); with leases, B is refused while A's lease runs, B is accepted one DAA past the expiry, and A's record submitted after the
|
||||
expiry but before the deadline is still accepted.
|
||||
- `a_refresh_by_the_holder_does_not_move_the_expiry`.
|
||||
- `three_abandoned_leases_in_the_window_deprioritise_a_key_and_the_window_clears_it`: the key is refused inside the penalty, accepted
|
||||
past it, accepted again on a fresh segment once the abandons age out; a key with two abandons is never refused.
|
||||
- `lease_counters_conserve_flow`: granted = landed + abandoned + open after a mixed sequence.
|
||||
- `with_leases_off_the_claim_path_is_0_3_20s`: both claims accepted, no counters, the RPC reply shape unchanged.
|
||||
- RPC: `igneum_claimSegment` answers `accepted: false` with `holder` and `expiresDaa` for the second key; `igneum_getAssignedShards`
|
||||
carries `leased` and `claimPriority`.
|
||||
|
||||
**Cost.** A block: none (nothing in consensus). A prover: one `igneum_claimSegment` per task (one round trip, under a millisecond on
|
||||
the node) and the chance of a refusal, which costs one pass (15 s) and saves the whole export, cut and chain of a segment another
|
||||
box is already proving (median 75 s, p95 230 s of wasted card time per steal on 8 October). The node: the claims vector (bounded at
|
||||
10,000 today, unchanged) plus a per-key abandon ring of at most 64 entries.
|
||||
|
||||
**What a lease does not do tonight.** It binds only the node it is posted to. A fleet whose boxes run their own nodes still race
|
||||
across nodes; a pool operator's node serving many provers gets the full protection. Cross-node leases are the gossip message named in
|
||||
section 0 (phase 2); a lease the payment rule honours is the consensus item there.
|
||||
|
||||
## 2. The chain-health backpressure gate
|
||||
|
||||
**What exists today.** `box-prover.py` waits once at start for `igneum-miner watch` to print `synced=true`, then claims on every pass
|
||||
whatever the node's state. The node reports its own health: `igneum_getExecStatus` (`rpc.rs` 1398) carries `blocked`, `reexecuting`,
|
||||
`recordsContinuous`, `synced`, `executedTip`, `executedTipDaa` and `sinkNumber` (the consensus sink's chain block number); `pause_reason`
|
||||
(`rpc.rs` 156) is the node's own definition of "paused" (blocked, re-executing, vetoing, or the executor more than two chain blocks
|
||||
behind the sink) but is served only on `igneum_getTransactionStatus`; `igneum_getProvingStatus` carries `execSync.stateFresh`,
|
||||
`pausedSinceMs` (the finality pause) and `tipDaa`. On 8 October the chain stalled at 11:45Z and partitioned; the fleet kept claiming
|
||||
and submitted 1,681 segment records into a chain that never paid them, the day's largest waste.
|
||||
|
||||
**The change.** `chain_health(ex, st, clock)` in `box-prover.py`, read every pass before `candidates`, returns a hold cause or `None`:
|
||||
|
||||
| cause | read | threshold (env, default) |
|
||||
|---|---|---|
|
||||
| `blocked` | `igneum_getExecStatus.blocked` set | any |
|
||||
| `reexecuting` | `.reexecuting` set | any |
|
||||
| `discontinuous` | `.recordsContinuous` is false | any |
|
||||
| `lagging` | `.sinkNumber - .executedTip` | over `LAG_BLOCKS` (64, the settled floor's depth) |
|
||||
| `stalled` | `igneum_getProvingStatus.tipDaa` unchanged across passes for | `STALL_S` (120 s: DAA moves about one a second) |
|
||||
| `finality_paused` | `.pausedSinceMs` set for longer than | `FINALITY_PAUSE_S` (600 s) |
|
||||
| `unreachable` | either RPC answers nothing | any |
|
||||
|
||||
On a hold the prover does not claim, does not export, and prints `RESULT backpressure <stamp> hold cause=<cause> <reading>` once per
|
||||
cause change and `RESULT backpressure <stamp> released after <s> s` when the gate clears; every pass under a hold counts in
|
||||
`state["backpressure"] = {holds, held_s, by_cause}`. Submitted records keep being polled (the paid state of an accepted record is still
|
||||
worth reading) and held fresh records keep being offered, because both are free. The node side needs no change: every reading
|
||||
is already served. `pause_reason` is reused by name in the design so the app's pause and the prover's hold agree on what "paused" is;
|
||||
adding it to `igneum_getProvingStatus` as `paused` is a one-line follow-up for the node lane, not needed tonight.
|
||||
|
||||
**Tests, known-failed first** (`box-prover.py --self-test`, pure functions, no node): a healthy reading holds nothing (the known
|
||||
failure is a stub that holds on everything); each cause fires on its own reading at the threshold and not below it; the stall
|
||||
detector needs two readings `STALL_S` apart with the same DAA and clears on a move.
|
||||
|
||||
**Cost.** A block: none. A prover: one extra RPC per pass (`igneum_getExecStatus`, already called per task for the export start); a
|
||||
hold costs the prover nothing it would have been paid for, by the record of 8 October (zero paid from the stall on).
|
||||
|
||||
## 3. Smaller verifiable paid tasks
|
||||
|
||||
**What exists today.** The protocol already pays two sizes. The shard record (`ProofRecord`, spec 7.7, `igneum_submitProofRecord`,
|
||||
paid by `shard_parts`: 90 percent of a block's pool credit split over its shards) is one shard of one block, bounded by the shard
|
||||
budget S_p (`shard_proving_gas_budget_at`); the segment record (`SegmentRecord`, spec 7.8, the aggregator's 10 percent) is the chain
|
||||
over eight blocks' shards. The host makes a single shard proof with `--mode compressed --shard i` (`main.rs` `run_compressed`: the
|
||||
statement, the proof sha256 and the proof file, everything `sign-record` needs) and aggregates shard proofs it did not make with
|
||||
`--mode aggregate --proofs <a.bin,...>` from the pool (`igneum_getProofBytes`). The app's shard loop (`app/igneum-app/src/prover.rs`
|
||||
`choose`) claims one shard at a time. The fleet prover claims only whole segments (`--mode chain`, eight blocks, every shard, then the
|
||||
aggregation): peak memory 11,948 MiB median and 26,210 MiB max, and the 12 GB tier claimed 313 segments and was paid for none.
|
||||
|
||||
**What "smaller" is.** One shard of one block: the smallest unit the chain pays, verifiable on its own (the node verifies each shard
|
||||
record at submission under the pinned shard program id), with no aggregation step and no chain link to lose. A constrained card proves
|
||||
shards and leaves the segment chain to a card with the memory for it; the aggregator's share goes to the aggregator, which is the
|
||||
split the spec already makes. No consensus change: the segment length and the shard budget stay.
|
||||
|
||||
**The change, the sizing knob.** `box-prover.py` gains `TASK=segment|shard|auto` (default `auto`: `shard` when the card's total memory
|
||||
read from `nvidia-smi` is under `SEGMENT_MIN_MIB`, default 16,384, else `segment`) and `MAX_SHARDS` (default 0 = no cap): in segment
|
||||
mode a segment with more shards than `MAX_SHARDS` is skipped before the claim (`RESULT skip ... shards N over MAX_SHARDS`), in shard
|
||||
mode a task is one shard. Shard mode's pass: the candidate shards are the worklist rows that are open, unpaid, not in the pool and not
|
||||
leased by another key, assigned-first then open, newest first and smallest `pgas` among equals (the app's `choose`); one claim
|
||||
(`igneum_claimSegment` over the one block, the lease of section 1), one export of that block, one cut, one `--mode compressed --shard i
|
||||
--out`, one `sign-record`, one `igneum_submitProofRecord`; the paid state is polled through `igneum_getAssignedShards` (`paid` on the
|
||||
row). Each shard task is a ledger row of `kind: shard` with the same four outcomes and causes, so `prover-outcomes.py` reports both
|
||||
kinds in one table with the kind beside each count. The planner is not a knob: `proving_v1_segment_blocks` is a consensus param
|
||||
(section 0).
|
||||
|
||||
**Tests, known-failed first.** `box-prover.py --self-test`: `task_mode(total_mib, env)` picks shard under the line and segment over
|
||||
it (the known failure is a stub that always says segment); `shard_candidates(work, attempted, leases)` orders assigned before open,
|
||||
newest first, smallest pgas among equals, and never offers a leased, paid or pooled shard; a segment over `MAX_SHARDS` is not a
|
||||
candidate. The live check is the fleet's: one 12 GB box in shard mode for an hour, the ledger's paid count above zero, which is the
|
||||
figure the review asked for ("where economical": a shard on a 12 GB card is 5.7 s at threshold 2^26 by the floor rows of
|
||||
`docs/plans/proving-v1.md`, against a chain the card never finished).
|
||||
|
||||
**Cost.** A block: none. A prover: a shard task's export and cut are one block, not eight; its paid unit is one shard part (a 90 percent
|
||||
share split over the block's shards, about 0.17 IGN on 8 October's credits) rather than a segment's aggregator share (1.5 to 1.6 IGN),
|
||||
so the constrained card earns in small units it can finish instead of large ones it cannot.
|
||||
|
||||
## 4. Bounded transport
|
||||
|
||||
**What exists today.** The relay bounds a proof message at `MAX_PROOF_BYTES = 8 << 20` (`protocol/flows/src/v10/proving.rs` 18; a
|
||||
compressed SP1 shard proof is about 1.3 MB); a larger message is a protocol error and the peer is dropped. The RPC has no bound of its
|
||||
own: `igneum_submitProofRecord` and `igneum_submitSegmentRecord` (`rpc.rs` 1270, 1313) decode whatever hex the 32 MiB axum body limit
|
||||
lets through (`rpc.rs` 339), hash it (`proof_hash`, sha256 over the bytes) and hold it by hash in `proofs` before the native checks.
|
||||
`ProofPool::submit` (1173) and `submit_segment` (1430) have no bound either. So a local or relayed proof of up to 32 MiB is hashed and
|
||||
held, and a proof over 8 MiB that passes the RPC can never reach a peer. The fleet's "invalid string length" class (264 chain failures,
|
||||
the host's proof string on the 48 GB cards' larger segments) is the host's own JSON, not this path, and stays with the node lane as
|
||||
recorded.
|
||||
|
||||
**The change.** One bound, the relay's: `kaspa_consensus_core::proving::MAX_PROOF_BYTES` moves to consensus-core so the flows crate and
|
||||
the exec crate share it (the flows constant becomes a re-export; no value change). `ProofPool::submit` and `submit_segment` refuse
|
||||
`proof.len() > MAX_PROOF_BYTES` before hashing, with the size in the refusal: `"proof is N bytes, over the M-byte bound"`. The two RPC
|
||||
methods refuse on the hex string's length before decoding (`len > 2 + 2 * MAX_PROOF_BYTES`), so nothing is allocated for an oversize
|
||||
submission, with the same text. The outcome is `accepted: false, new: false` so the prover's existing refusal path logs it (`RESULT seg
|
||||
N shard ... refused: proof is ... bytes`) and the ledger closes the task `cancelled, refused`.
|
||||
|
||||
**Chunked transport.** Not built, and named: a chunked RPC alone would let a prover deliver a proof the relay cannot carry, so the
|
||||
chunking has to be the relay's (an `IgneumProofChunk` message with the proof hash, the index and the count, reassembled under the same
|
||||
bound times a chunk count the object states). That is the node lane's transport item when a proof larger than the bound exists; none
|
||||
does today (1.3 MB shard, under 1 MB segment).
|
||||
|
||||
**Tests, known-failed first** (`proving.rs`): `a_proof_over_the_bound_is_refused_before_it_is_held`: today the pool holds it by hash
|
||||
(the known failure: `holds_proof` reads true); after, `accepted: false`, the reason names the size and the bound, `holds_proof` reads
|
||||
false, nothing in `proofs`; a proof exactly at the bound is held. `rpc.rs`: the hex refusal on both methods, with the reason text.
|
||||
|
||||
**Cost.** A block: none. A prover: none for any proof it makes today; an oversize one is refused in constant time instead of being
|
||||
hashed and held.
|
||||
|
||||
## 5. Preflight package integrity and dependency checks
|
||||
|
||||
**What exists today.** `box-prover.py` prints `host={os.path.exists(HOST)}` in its start line and goes on either way; the floor
|
||||
tarball's `igneum-prove-host` was a dangling symlink from 08:00 to 10:59Z on 8 October and every chain failed with "No such file or
|
||||
directory" (629 failures, the day's second class). The host checks its own embedded guests against the pinned manifest at every start
|
||||
(`pinned.rs` `Pinned::load`: the two ELFs and the two keys by sha256 against `elf/manifest.json`, the ids derived from the keys and
|
||||
compared with the manifest's) and prints them on `--mode id`, but only when it runs, which is after the claim, the export and the cut.
|
||||
The node serves the ids it pins on `igneum_getProvingStatus` (`v1.shardProgramId`, `v1.aggregatorId`) and its manifest on
|
||||
`igneum_getManifest` (`verifier.shardProgramId`, `verifier.aggregatorId`, `verifier.proverVersion`).
|
||||
|
||||
**The change.** `preflight()` in `box-prover.py`, before the key read and the first claim, in order, each with a RESULT line:
|
||||
|
||||
1. The binaries: `HOST` and `EXPORT` resolve (`os.path.realpath`), exist, are executable; `ldd` reports nothing "not found".
|
||||
2. Their sha256 against the expected set in `PREFLIGHT_MANIFEST` (default `/root/fleet/in/prover-manifest.json`, shipped beside
|
||||
`box-prover.py` by `tools/fleet/fleet.py`; the format `{"igneum-prove-host": "<sha256>", "igneum-prove-export": "<sha256>",
|
||||
"shard_program_id": "0x..", "aggregator_id": "0x.."}`). A mismatch is `RESULT preflight_failed <stamp> file=<path> expected=<sha>
|
||||
got=<sha>` and exit 4. No manifest file is `RESULT preflight <stamp> manifest=absent` and the sha256 of each binary is recorded in
|
||||
the line, not enforced: the ids check below still runs.
|
||||
3. The ELF manifest: `HOST --mode id` must run and print both ids; they must equal the manifest's when given and the node's
|
||||
`v1.shardProgramId` and `v1.aggregatorId` always (the node's pinned pair is what the chain pays for; a host with other guests
|
||||
proves for nothing). A mismatch is `RESULT preflight_failed <stamp> file=<host path> ids=<printed> node=<node's>` and exit 4.
|
||||
4. Dependencies: `nvidia-smi` answers for `IGNEUM_CUDA_DEVICE`; the floor server binary `/opt/igneum-floor/bin/sp1-gpu-server` exists
|
||||
and is executable; `igneum-miner` answers `key-hash`. Each failure names the file.
|
||||
|
||||
`RESULT preflight <stamp> ok host=<sha16> export=<sha16> shard=<id8> aggregator=<id8> device=<n>` on success; the state file carries
|
||||
`preflight: {host_sha256, export_sha256, shard_program_id, aggregator_id, at}` so the collector can prove what proved.
|
||||
|
||||
**Tests, known-failed first** (`box-prover.py --self-test`): `preflight_verdict(files, manifest, printed_ids, node_ids)` as a pure
|
||||
function over readings: a dangling link fails naming the file (the known failure: the stub passes on `exists` alone, which is what
|
||||
8 October's prover did); a sha mismatch names the file and both hashes; an id mismatch against the node fails even with no manifest;
|
||||
the absent-manifest case passes with the shas recorded.
|
||||
|
||||
**Cost.** A block: none. A prover: sha256 over about 76 MB of binaries (under a second) and one host run of `--mode id` (the pinned
|
||||
check, about 0.3 s), once per start.
|
||||
|
||||
## 6. Flow conservation, reported per departure category
|
||||
|
||||
**What exists today.** The ledger (b7507dd79) gives every claimed segment one of `paid`, `expired` (cause `unpaid`, `held_expired`,
|
||||
or `never_submitted` by log reconstruction), `cancelled` (nine causes) or `active`, and the report prints the counts, waste by cause
|
||||
and throughput. A claim whose prover never submitted and never wrote a cause (killed mid-chain, a box restarted, a lease that ran out)
|
||||
is `active` for ever in the state file, and `never_submitted` only when a log is reconstructed. The review's identity is not printed.
|
||||
|
||||
**The change.**
|
||||
|
||||
1. The fourth closing outcome `abandoned`: a claim whose lease (section 1) or deadline passed with no record submitted. The live
|
||||
prover closes it: on every pass, an active task past its lease expiry with no submit is closed `abandoned` with the cause (`lease`,
|
||||
or `restart` for active rows found in the state file at start, which a restarted prover closes before its first claim); the
|
||||
log reconstruction's `never_submitted` becomes `abandoned, never_submitted`. The outcome set is then exactly the order's: paid,
|
||||
expired, cancelled by cause, abandoned.
|
||||
2. The conservation line, in the state (`RESULT ledger`) and in `prover-outcomes.py`'s report:
|
||||
`flow: claims N = paid P + expired E + cancelled C + abandoned A + active O` with `ok` when the sum holds and `BROKEN by d` when it
|
||||
does not (a broken line is a bug in the prover, never silently repaired). Over a span: `opening backlog + arrivals - paid - expired
|
||||
- cancelled - abandoned = closing backlog`, where the backlog is the active count at the span's ends and the arrivals the claims
|
||||
inside it; the report prints the identity with the numbers in it, per kind (segment, shard) and in all, and the departure
|
||||
categories each with its causes.
|
||||
3. The chain side: the `leases` counters of section 1 give the node's own identity (`granted = landed + abandoned + open`) on
|
||||
`igneum_getProvingStatus`, so the fleet's view and the chain's view can be compared per departure category, which the ledger
|
||||
document named as the next step.
|
||||
|
||||
**Tests, known-failed first.** `prover-outcomes.py --self-test` (the existing synthetic log gains an abandoned job): the conservation
|
||||
line is `ok` on the fixture and `BROKEN` on a state whose counters were edited (the known failure is the report without the line);
|
||||
`box-prover.py --self-test`: `close()` on an active row past its lease yields `abandoned`, a second close is ignored, the outcome
|
||||
counters sum to the claims after a mixed sequence.
|
||||
|
||||
**Cost.** None to a block; to a prover, a few integers.
|
||||
|
||||
## 7. What the build on the branches is, in order, with the clocks
|
||||
|
||||
| # | Branch | Change | Test | Clock (UK) |
|
||||
|---|---|---|---|---|
|
||||
| 1 | `v608-proving-tasks-node` (node fork, off `successor-2.0.1`) | `LeaseRules`, `ClaimOutcome`, `claim_segment` and `open_claims` with leases, the abandon ring, the counters; `igneum_claimSegment`, `igneum_getAssignedShards`, `igneum_getProvingStatus` fields; `MAX_PROOF_BYTES` in consensus-core, the pool and RPC bounds | the five pool tests and the RPC tests above, `cargo test -p igneum-exec` on build-2 or build-3 through `tools/build-remote.sh` | 00:30 |
|
||||
| 2 | `v608-proving-tasks` (igneum) | `box-prover.py`: the lease call, the backpressure gate, `TASK`/`MAX_SHARDS` and shard mode, `preflight()`, the `abandoned` outcome, the conservation line, `--self-test`; `prover-outcomes.py`: `abandoned`, the conservation line, the kind column; `tools/ci/pre-push.sh` runs `box-prover.py --self-test` beside the ledger's | the python self-tests | 01:30 |
|
||||
| 3 | both | the node tip merged once more; shas to the node lane and the coordinator | | 02:00 |
|
||||
|
||||
Landing: the igneum branch through `tools/ci/merge-to-master.sh --remote box` on the coordinator's word; the node branch's sha to the
|
||||
node lane, which lands it on `successor-2.0.1` through its own gate (the six suites, the pair, the devnet-4 canary on build-9).
|
||||
Loading…
Reference in a new issue