Skip to content

fix(contracts): position-stable batch seat assignment (issue #243) - #253

Open
robertocarlous wants to merge 6 commits into
InfiniteZeroFoundation:developfrom
robertocarlous:feat/issue-243-batch-pool-reshaping
Open

robertocarlous wants to merge 6 commits into
InfiniteZeroFoundation:developfrom
robertocarlous:feat/issue-243-batch-pool-reshaping

Conversation

@robertocarlous

@robertocarlous robertocarlous commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #243

Summary

Batch layout is now derived from the fixed per-GI registration list
(dinAggregators[_GI] / dinAuditors[_GI]) instead of the live active pool,
so an unstake between seed-lock and batch creation forfeits only the
leaver's own seat — every other validator's batch and position is
determined solely by the fixed list and the locked seed.

Key changes:

  • autoCreateTier1AndTier2: shuffle fixed regList in-place; closed-form
    batch count (t1cnt = mLen/T1_MODELS_PER_BATCH + partial-batch term,
    capped by rLen/T1_AGGREGATORS_PER_BATCH); position-stable seat fill
    scans reserve from totalSeats..rLen-1 to replace inactive primaries.
  • createAuditorsBatches (DINTaskAuditor): same three-step pattern —
    shuffle fixed regList, closed-form batchCnt, position-stable fill.
  • DINTaskCoordinator.createAuditorsBatches wrapper: removed dead
    bool-success check (DINTaskAuditor always reverts or returns true;
    the branch was unreachable); saves 75 bytes, putting the contract
    1094 B below EIP-170 and passing the CI size gate.
  • New test: BatchAssignmentStability.t.sol — three snapshot/revert
    tests verify that unstaking exactly one validator displaces only
    that validator's own seat and leaves all others unchanged.
  • Docs: adversarial-threat-model.md Row 2 updated, DINTaskCoordinator.md
    §6.3/§6.4 rewritten, DINTaskAuditor.md createAuditorsBatches section
    updated, BACK_LOG.md BL-26 flipped to Fixed.
  • Tasks: task specs 23–28 added for issues BL-26: Batch-assignment pool-reshaping gap — unstake between seed lock and autoCreateTier1AndTier2 re-rolls all assignments #243–BL-15: One-shot dispute resolution (one dispute per batch, ever) is undocumented #248.

robertocarlous added a commit to robertocarlous/DevNet that referenced this pull request Oct 15, 2026
@robertocarlous

Copy link
Copy Markdown
Collaborator Author

Cc: @umeradl

@Abidoyesimze Abidoyesimze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Went through this properly - rebuilt, ran the full suite twice to be safe, hand-traced the actual algorithm rather than just trusting the description.

Solid fix, correctly solves the problem it set out to solve.

  • forge build + forge test: 504 passed, 0 failed, both runs
  • size gate matches claims exactly: TaskCoordinator 23,482B (1,094B margin, same warn band as develop), TaskAuditor 21,342B (3,234B margin, ok)
  • doc links all resolve

Traced the new closed-form batch-count math (t1cnt = mLen/T1_MODELS_PER_BATCH + partial term, capped by rLen/T1_AGGREGATORS_PER_BATCH) against the old greedy while-loop for model-limited, validator-limited, and zero-model cases by hand - they agree in every case, including the T2 boundary (any floor-division remainder is necessarily smaller than the divisor, so "cap triggered means no T2" falls out of the arithmetic the same way on both old and new code).

Also traced the position-stable reserve-fill loop: the reserve pointer only moves forward and the primary/reserve regions are disjoint, so nobody can get double-assigned or silently dropped.

Checked the dead "if (!success) revert" removal - grepped through DINTaskAuditor.createAuditorsBatches end to end, it genuinely never returns false, so that branch really was unreachable.

The three new tests use vm.snapshot/vm.revertTo to run the identical scenario with and without one unstake and assert every other seat is byte-identical across both runs. That's the right way to actually prove position-stability, not just "it doesn't crash."

Two things worth raising before merge, neither blocking:

  1. Batch count is now computed purely from the registered count and model count, before checking which registered validators are actually active. If enough registered validators have gone inactive by call time that the reserve can't cover every vacated seat, the whole call now reverts (TC_NotEnoughValidators / TA_NotEnoughAuditors). The old code sized batches off the already-active-filtered pool, so it would've gracefully formed fewer batches in that same scenario instead of reverting outright. Not a security issue, just a liveness/UX tradeoff none of the three new tests exercise (all three only unstake exactly one validator). Worth a test for "enough inactive to exhaust the reserve," and worth confirming the fail-closed behavior here is intentional.

  2. _activeAggregatorPool / _activeAuditorPool have zero callers now but weren't deleted. I actually tested this instead of guessing - removed one locally and rebuilt, size was byte-for-byte identical, so the optimizer already eliminates them and it costs nothing on-chain. Still, worth cleaning up since the issue #249 review already flagged this exact pattern as something to delete rather than leave behind, and this contract's margin is tight enough that dead source just confuses the next reader.

No conflicts with my own open PRs - different call sites in everything we both touch.

Security review: no vulnerabilities. No new reentrancy surface, no privilege-boundary change (both entry points stay onlyOwner), and every new unchecked block is guarded by a preceding bounds check before the arithmetic runs.

Abidoyesimze
Abidoyesimze previously approved these changes Oct 24, 2026

@Abidoyesimze Abidoyesimze left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving - nothing blocking from my review above. The two notes I left (reserve-exhaustion liveness edge case, dead helper cleanup) are follow-up suggestions, not merge blockers.

…undation#243)

Batch layout is now derived from the fixed per-GI registration list
(dinAggregators[_GI] / dinAuditors[_GI]) instead of the live active pool,
so an unstake between seed-lock and batch creation forfeits only the
leaver's own seat — every other validator's batch and position is
determined solely by the fixed list and the locked seed.

Key changes:
- autoCreateTier1AndTier2: shuffle fixed regList in-place; closed-form
  batch count (t1cnt = mLen/T1_MODELS_PER_BATCH + partial-batch term,
  capped by rLen/T1_AGGREGATORS_PER_BATCH); position-stable seat fill
  scans reserve from totalSeats..rLen-1 to replace inactive primaries.
- createAuditorsBatches (DINTaskAuditor): same three-step pattern —
  shuffle fixed regList, closed-form batchCnt, position-stable fill.
- DINTaskCoordinator.createAuditorsBatches wrapper: removed dead
  bool-success check (DINTaskAuditor always reverts or returns true;
  the branch was unreachable); saves 75 bytes, putting the contract
  1094 B below EIP-170 and passing the CI size gate.
- New test: BatchAssignmentStability.t.sol — three snapshot/revert
  tests verify that unstaking exactly one validator displaces only
  that validator's own seat and leaves all others unchanged.
- Docs: adversarial-threat-model.md Row 2 updated, DINTaskCoordinator.md
  §6.3/§6.4 rewritten, DINTaskAuditor.md createAuditorsBatches section
  updated, BACK_LOG.md BL-26 flipped to Fixed.
- Tasks: task specs 23–28 added for issues InfiniteZeroFoundation#243–InfiniteZeroFoundation#248.
…ssignmentStabilityTest

PR No. 272 added a per-batch testDataCommitments check to
setTestDataAssignedFlag; the stability test's _runToSeedLocked helper
was missing the placeholder assignment step before that call.
@robertocarlous

Copy link
Copy Markdown
Collaborator Author

Rebased onto develop and all tests passing (09eaab83). The branch had conflict sites from PR No. 272 (richer GI states) which merged onto develop after this PR was opened.

Conflicts resolved:

foundry/src/DINTaskCoordinator.sol — two sites:

  • createAuditorsBatches NatSpec and state guard: kept develop's AuditSeedLocked check (if (GIstate != GIstates.AuditSeedLocked) revert TC_AuditSeedNotLocked()); the old LMSclosed guard was superseded by the new state machine.
  • autoCreateTier1AndTier2 NatSpec: merged develop's "caller cannot steer" text with this PR's position-stable algorithm description (fixed registration list, not live active pool; inactive seat replaced from first active reserve entry).

Documentation/technical/contracts/DINTaskCoordinator.md — kept develop's AggSeedLocked prerequisite wording, dropped old active-pool algorithm steps.

Documentation/technical/contracts/DINTaskAuditor.md — kept AuditSeedLocked state reference, dropped old algorithm steps.

Test fix: PR No. 272 added a per-batch testDataCommitments check to setTestDataAssignedFlag; the BatchAssignmentStabilityTest._runToSeedLocked helper was missing the assignPlaceholderTestData call before that step. Added in 09eaab83.

Rebased commits:

  • a6fbb1d9 — fix(contracts): position-stable batch seat assignment (No. 243)
  • cbcf31b4 — docs: fill in PR No. 253 in BL-26 tracking references
  • 09eaab83 — test(contracts): add missing assignPlaceholderTestData call in BatchAssignmentStabilityTest

Full forge test: 526 tests, 526 passed, 0 failed. Ready for re-approval (Abidoyesimze's previous approval was on b2851682 and was dismissed by the rebase).

robertocarlous and others added 3 commits October 27, 2026 14:15
Resolve conflicts in DINTaskCoordinator.sol (NatSpec: keep position-stable
description, drop incorrect AggSeedLocked state reference — lockAggSeed
emits that event, it is not a GI state), DINTaskCoordinator.md (keep correct
LMSevaluationClosed state name from develop, keep PR's position-stable
algorithm description), and DINTaskAuditor.md (keep correct LMSclosed state
name from develop, keep PR's position-stable algorithm description).
…k_061026_21

PlaceholderTestData.sol was removed in PR No. 273 (revert of task_061026_21
GI-states richer states). BatchAssignmentStability.t.sol imported it for a
per-batch commitment check that only existed in that reverted feature;
setTestDataAssignedFlag no longer requires per-batch assignAuditTestDataset
calls, so the import and call are simply removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants