fix(cli): keep auditor commit salts across retries; name aggregate-t2 work after the T2 batch (#202) - #214
Conversation
… work after the T2 batch (InfiniteZeroFoundation#202) Part 1 - auditor commit retry (auditor-side twin of PR InfiniteZeroFoundation#197's aggregator fix). `auditor lms-evaluation evaluate --submit` drew a fresh salt for every LM on every run and wrote the commit cache unconditionally after the send. A rerun after a partial failure overwrote the salt behind an LM already committed on-chain, so its reveal failed TA_RevealHashMismatch and the auditor was S1-slashed for an honest vote. Now: - an LM with hasCommittedLM set is skipped before re-evaluating, leaving its cache untouched; - the cache is written before the commit tx is sent, because build_and_send_tx returns None both on a revert and when the receipt wait fails for a tx that still mines. Part 2 - aggregate_t2 rebound `bid` while collecting T1 final CIDs, so the T2 models path, worker job and container were named after the last T1 batch. The inner loop now unpacks into distinct names, and --batch uses `is not None` (0 was treated as "no batch"). Tests: tests/test_auditor_commit_retry.py (skip-if-committed, all committed sends nothing, cache-before-send matches the sent hash) and tests/test_aggregator_t2_batch_id.py (T2 naming with 3 T1 batches, with and without --batch 0). All new tests fail against the old code. Docs: DINTaskAuditor.md No. 9 and DINTaskCoordinator.md No. 10 marked fixed; auditors.md --submit row describes commit + safe re-run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Reviewed against Claimed: the 6 new tests pass, and against the old Verified — exact match: at the PR head, Going further — which test guards which change: I reverted each sub-fix on its own:
The Claimed: Verified: Claimed: the fix mirrors PR No. 197's aggregator fix. Verified: the cache-before-send block and its rationale follow Claimed: Verified — 395 passed in a venv with torch installed, at the PR head and on a trial merge into Claimed: all doc links resolve. Verified: Not independently re-verified: an end-to-end commit/reveal against a live chain. The tests drive Notes (non-blocking):
Every checkable claim held up. The bug fixes are each pinned by a test that fails when the fix is reverted. The one untestable change ( |
Files changed (7) — as of
|
| Field | Value |
|---|---|
| Change | Modified |
| Lines | +16/-3 |
| Diff (what exactly is in this PR) | In evaluate_lms: with --submit, any LM for which hasCommittedLM(curr_GI, batch_id, account, model_index) is true is skipped before evaluation (yellow notice pointing at reveal). _save_commit(...) moves from after build_and_send_tx(...) to immediately before it. |
| Functionality — how & why | How: the skip runs after the --lmi filter and found_any = True, so no worker run, salt or cache write happens for a slot already committed on-chain. For uncommitted slots, the (score, vote, salt) preimage is on disk before the tx is signed and sent. Why: a rerun after a partial failure used to draw a new salt for an LM already committed and overwrite its cache. Its reveal then failed TA_RevealHashMismatch, and slashAuditors S1-slashed an honest auditor for AUD_NO_VOTE. Saving only after the send could also strand a mined commit without its preimage, because build_and_send_tx returns None on a failed receipt wait too. Break-then-fix: reverting the guard fails 2 tests; reverting the ordering fails 1. |
Diff vs current develop HEAD |
None — untouched by develop since merge-base |
| Recommended merge proposal | Merge as-is. |
| Actual merge proposal | Applied as-is, byte-identical to PR head. Merged in d2d2213. The optional cache-reuse hardening isn't in this merge. Evidence on the merged tree (develop @ 4ec42d4 + this PR): forge build (via_ir) clean; forge test 484 passed / 0 failed / 0 skipped; pytest -m "not integration" 395 passed / 0 failed (389 before + the 6 new tests). |
| Pending proposal | Optional later hardening, shared with aggregate_t1/aggregate_t2: reuse an existing cache entry's (score, vote, salt) for an uncommitted slot, which closes the pending-tx rerun window. Not needed for this PR. |
| Local merge conflict | No |
| GitHub merge conflict | No |
dincli/cli/aggregator.py
| Field | Value |
|---|---|
| Change | Modified |
| Lines | +7/-4 |
| Diff (what exactly is in this PR) | In aggregate_t2: the T1-collection loop unpacks getTier1Batch into (_, _, _, _, t1_final_cid) instead of rebinding bid. Both --batch checks use batch_id is not None instead of truthiness. |
| Functionality — how & why | How: bid now keeps the T2 batch id read from getTier2Batch, so the models path (.../T2/<bid>/models), worker job (aggregator_t2_gi_<gi>_batch_<bid>), container name and log lines all name the T2 batch. Why: after the loop, bid held the last T1 batch's id, so T2 work was filed under an unrelated batch. The on-chain commit already used i and was unaffected. The is not None change matches aggregate_t1. It has no observable effect today, because the contract creates exactly one T2 batch and dincli hard-codes t2_batches_count = 1, which is why no test can catch it. Break-then-fix: reverting the unpacking fails both naming tests. |
Diff vs current develop HEAD |
None — untouched by develop since merge-base |
| Recommended merge proposal | Merge as-is. |
| Actual merge proposal | Applied as-is, byte-identical to PR head. Merged in d2d2213. Evidence on the merged tree (develop @ 4ec42d4 + this PR): forge build (via_ir) clean; forge test 484 passed / 0 failed / 0 skipped; pytest -m "not integration" 395 passed / 0 failed (389 before + the 6 new tests). |
| Pending proposal | None |
| Local merge conflict | No |
| GitHub merge conflict | No |
tests/test_auditor_commit_retry.py, tests/test_aggregator_t2_batch_id.py
| Field | Value |
|---|---|
| Change | New (2 files) |
| Lines | +187/-0, +105/-0 |
| Diff (what exactly is in this PR) | Auditor: an LM already committed is skipped with its cache byte-identical; an all-committed run sends nothing and runs no worker; with a failed send, the cache exists at send time and the _audit_commit_hash of the cached preimage equals the hash sent. T2: 3 T1 batches, run with and without --batch 0; asserts the job, container and models path name batch 0 and that all 3 T1 CIDs are collected; --batch 1 is still rejected. |
| Functionality — how & why | How: mocked contracts, worker and build_and_send_tx, in the style of tests/test_aggregator_commit_retry.py. Unmarked, so they run in CI's not integration job. Why: they pin both issue No. 202 bugs. Against the old source, 5 of 6 fail; the out-of-range check passes because that behaviour is unchanged. |
Diff vs current develop HEAD |
None — new files |
| Recommended merge proposal | Merge as-is. |
| Actual merge proposal | Both applied as-is, byte-identical to PR head; all 6 tests pass (6 passed run on its own and in the full suite). Merged in d2d2213. Evidence on the merged tree (develop @ 4ec42d4 + this PR): forge build (via_ir) clean; forge test 484 passed / 0 failed / 0 skipped; pytest -m "not integration" 395 passed / 0 failed (389 before + the 6 new tests). |
| Pending proposal | None |
| Local merge conflict | No |
| GitHub merge conflict | No |
Documentation/technical/contracts/DINTaskAuditor.md, Documentation/technical/contracts/DINTaskCoordinator.md, Documentation/public/roles/auditors.md
| Field | Value |
|---|---|
| Change | Modified (3 files) |
| Lines | +1/-1 each |
| Diff (what exactly is in this PR) | DINTaskAuditor.md §13 No. 9 is rewritten as Fixed (skip-if-committed + cache-before-send, rerun safe). DINTaskCoordinator.md §10 No. 10 moves the aggregate-t2 naming bug into a "fixed in issue No. 202 Part 2" parenthetical, and keeps the constructor-lag item. The auditors.md --submit row now describes commit-then-reveal, the local preimage cache and safe reruns. |
| Functionality — how & why | How: documentation only. Why: it keeps Documentation/ describing develop as it is: the "don't rerun" warning is no longer needed. Link check: 258 links, all resolve. |
Diff vs current develop HEAD |
None — untouched by develop since merge-base |
| Recommended merge proposal | Merge as-is, plus one doc fix outside the PR: Developer/ROADMAP.md line 22 still says "dincli's auditor commit retry can still lose a committed salt (No. 202)". Drop or mark fixed at merge time. |
| Actual merge proposal | All three applied as-is, byte-identical to PR head, in d2d2213. The out-of-PR fix is in e491f0b: the stale No. 202 sentence is dropped from Developer/ROADMAP.md line 22. Doc links check passed in the local CI mirror on e491f0b. |
| Pending proposal | Developer/ROADMAP.md line 22 stale No. 202 sentence (not in this PR). |
| Local merge conflict | No |
| GitHub merge conflict | No |
Verification
Full detail is in the verification comment above. In short: 6 of 6 new tests pass, and 5 of 6 fail against the old source. Each bug-fix sub-change is caught when reverted on its own. pytest -m "not integration" gives 395 passed at the PR head and on a trial merge with develop 4ec42d4. Doc links: 258/258 resolve. git diff --check is clean. GitHub CI is green.
Local vs. GitHub agree: both report a clean merge; no file overlap with the 1 commit develop gained since the merge-base.
Actual outcome — PR No. 214 merged + review follow-up applied (pushed)This supersedes the pre-merge review comment above with what actually happened when landing this PR on Two commits, both on
Files unchanged from the PR (7 of 7)All 7 files are byte-identical to
Outside the PR, changed by
|
| File | What changed |
|---|---|
Developer/ROADMAP.md |
Line 22 drops "dincli's auditor commit retry can still lose a committed salt (No. 202)", which this PR fixes. The rest of the line is unchanged. |
Why the change
It's the review's recommended doc fix, not develop drift. Nothing in this PR needed a functional fix.
Verification
On the merged tree (develop 4ec42d4 + this PR):
forge build(via_ir) is clean, andforge testgives 484 passed, 0 failed (same as the4ec42d4baseline).pytest -m "not integration": 395 passed, 0 failed. That's the baseline 389 plus the 6 new tests. The 2 new files also pass on their own (6 passed).- The local CI mirror on
e491f0bpassed: doc links and pytest with an empty HOME. Solidity was skipped because nothing underfoundry/,hardhat/or.github/changed. - GitHub
pushrun ondevelop: green (Docs, Python, Solidity, CI OK).
Local vs. GitHub agree: clean merge with no conflicts, as both predicted. Issue No. 202 is fixed by this PR.
Summary
PR 3 of task_021026_19, Part D. This is dincli only, with no contract changes. Closes #202.
It builds on #211 (
getAggregatorSubmission) and #212 (_audit_commit_hash), both merged.D1. Auditor commit retry (issue #202 Part 1)
The bug:
dincli auditor lms-evaluation evaluate --submitdrew a fresh salt for every LM on every run, and wrote the commit cache unconditionally after the send.Suppose LM 0's commit lands and LM 1's fails, and the auditor reruns. LM 0 gets a new salt,
commitAuditScorerevertsTA_AlreadyCommitted(build_and_send_txreturnsNone), and LM 0's cache is overwritten anyway. Its reveal then failsTA_RevealHashMismatch, andslashAuditorshits an honest auditor withAUD_NO_VOTE(S1).The fix mirrors PR #197's aggregator fix in
evaluate_lms:hasCommittedLM(gi, batchId, account, modelIndex)already set is skipped before re-evaluating, and its cache is left untouched.build_and_send_tx(..., exit_on_failure=False)returnsNoneboth on a revert and on a failed receipt wait for a tx that still mines.D2.
aggregate-t2batch id (issue #202 Part 2)The bug:
aggregate_t2readbidfromgetTier2Batch, then rebound it while looping over T1 batches. After the loop,bidheld the last T1 batch's id. That id named the T2 models path, the worker job, the container and the log lines. The on-chain commit usediand was correct.The fix:
(_, _, _, _, t1_final_cid).--batchchecks useis not None, matchingaggregate_t1. Before,--batch 0was treated as no batch.Tests
tests/test_auditor_commit_retry.py, mirroringtests/test_aggregator_commit_retry.py:_audit_commit_hashof the cached(score, vote, salt)equals the hash actually sent.tests/test_aggregator_t2_batch_id.py, with 3 T1 batches:aggregator_t2_gi_1_batch_0, the container ends-batch-0, and the models path is.../T2/0/models.bidpassed to the worker is 0, and all 3 T1 CIDs are collected.--batch 0.--batch 1is still rejected.auditor.py/aggregator.py, all 3 auditor tests and both T2 naming cases fail. Only the out-of-range check passes there, because that behaviour is unchanged.Docs
DINTaskAuditor.md§13 No. 9 andDINTaskCoordinator.md§10 No. 10 are marked fixed.--submitrow inauditors.mdnow describes the commit and the safe rerun, likeaggregators.mdalready does.Verification
pytest -m "not integration": 319 passed, including the 6 new tests. The torch-dependent files and the 2test_integration_markerchecks that collect them can't run in my local venv (no torch), the same as ondevelop. CI installs the full deps.check_doc_links.py Documentation: all links resolve.forgeresults are unchanged.🤖 Generated with Claude Code