File:
foundry/src/DINTaskCoordinator.solSPDX-License-Identifier: UNLICENSED Solidity:^0.8.28Deployment: once per model by the model owner (plainOwnable, not upgradeable)
DINTaskCoordinator orchestrates the Global Iteration (GI) lifecycle of one federated-learning model. The model owner drives every phase transition; validators act only inside the phase that is open. It owns:
- the one-time setup sequence (pair with
DINTaskAuditor, confirm both are slashers, record the genesis model); - aggregator registration (active-validator, per-model stake floor, concurrent-registration cap, 300-per-GI cap);
- the batch-assignment seeds for auditor batches and T1/T2 batches (future-block hash, locked by anyone);
- Tier-1 / Tier-2 batch formation and commit-then-reveal, majority-CID aggregation with a submission quorum;
- aggregator slashing (S2 partial for no submission, full for bad consensus);
- per-aggregator reward weights, handed to
DINTaskAuditor.settleRewardsatendGI; - the aggregation dispute scaffold (S4): bonded challenges, fresh-subgroup reassignment, owner adjudication.
Auditor registration, client submissions, scoring and rewards live in the paired DINTaskAuditor; the shared state enum, interfaces and errors are in DINShared.
| Component | Purpose |
|---|---|
Ownable (OpenZeppelin, non-upgradeable) |
Model owner = deployer; drives all phase transitions |
ReentrancyGuardTransient |
Guards openDispute / claimDisputeBond |
SafeERC20 / IERC20 |
DIN dispute bonds |
IBurnableDinToken (local) |
Burns the burn half of a forfeited dispute bond (all of it if no treasury is set) |
DINShared.sol |
GIstates, IDinValidatorStake, IDINTaskAuditor, TC_* errors |
| Variable | Description |
|---|---|
dinvalidatorStakeContract |
DinValidatorStake proxy (constructor) |
dinTaskAuditorContract |
Paired auditor (setDINTaskAuditorContract) |
taskProtocolVersion() |
Pure getter returning 1: requires a registry-aware auditor implementing validateGIStart; used by CLI deployment preflight |
modelId (immutable) |
Registry model ID, used for the per-model stake floor (constructor) |
GI |
Current GI counter (starts at 0; the first GI is 1) |
GIstate |
Current GIstates value |
genesisModelIpfsHash |
Genesis model CID (bytes32) |
dinToken |
DIN token for dispute bonds (setDinToken) |
| Variable | Description |
|---|---|
dinAggregators[gi], isDINAggregator[gi][addr] |
Registration list and flag |
registrationSlotsReleased[gi] |
Guard for releaseGIRegistrationSlots |
tier1Batches[gi], tier2Batches[gi] |
Batch arrays (Tier1Batch / Tier2Batch); T2 always has at most one batch, id 0. Internal: read through getTier1Batch / getTier2Batch |
isTier1Aggregator, isTier2Aggregator |
Batch assignment flags (not public) |
t1CommitHash, t1Committed (and t2*) |
Commit hash and commit flag per [gi][batchId][aggregator] |
t1SubmissionCID, t1Submitted, t1Votes (and t2*) |
Revealed CID, revealed flag and per-CID vote count. Written only by the reveal functions; t*Submitted stays false for an aggregator who committed but never revealed |
tier1FinalizedAt, tier2FinalizedAt |
Finalization timestamps (dispute window start) |
tier2Score[gi] |
Owner-recorded quality score for the T2 model |
aggregatorWeight[gi][addr], totalAggregatorWeight[gi] |
Reward weights (one unit per aggregator per finalized batch) |
auditSeedBlock[gi], auditSeed[gi] |
Anchor block and locked seed for auditor batch assignment (§6.3) |
aggSeedBlock[gi], aggSeed[gi] |
Anchor block and locked seed for T1/T2 batch assignment (§6.3) |
The batch arrays and the ten t1*/t2* maps are internal, to keep the contract under EIP-170 (issue #201 Part A). Read per-aggregator state through getAggregatorSubmission(gi, tier, batchId, aggregator), which returns (committed, commitHash, submitted, cid, votes). votes is the vote count of that aggregator's own revealed CID, because the vote maps are keyed by CID. It is 0 before the reveal. A vote count for an arbitrary CID is no longer readable from outside the contract.
| Constant | Value | Meaning |
|---|---|---|
T1_AGGREGATORS_PER_BATCH |
3 | Aggregators per T1 batch — and per T2 batch and fresh dispute subgroup |
T1_MODELS_PER_BATCH |
3 | Target models per T1 batch; also the minimum number of approved models to form batches |
MIN_T1_MODELS_PER_BATCH |
2 | Smallest allowed final T1 batch |
MAX_REGISTERED_AGGREGATORS |
300 | Registration cap per GI |
| Variable | Default | Setter |
|---|---|---|
s2SlashFractionBps |
3000 (30%) | setS2SlashFractionBps (1 – 10 000) |
disputeBond |
100 DIN | setDisputeParams (non-zero) |
disputeWindow |
1 day | setDisputeParams (non-zero) |
resolutionWindow |
2 days | setDisputeParams (non-zero) |
disputeSeedDelay |
7 blocks | setDisputeParams (1 – 256). Also the delay for the two batch-assignment seeds |
networkFeeFloor |
0 | setNetworkFeeFloor (stored only, not enforced) |
The S1/S2 slash-fraction default is one of the testnet values still to be decided (issue #155). There is no treasury address on this contract: forfeited bonds go to dinvalidatorStakeContract.slashTreasury() (§7).
disputes[gi][tierKind][batchId] (Dispute: challenger, bond, openedAt, resolutionDeadline, seedBlock, resolved, upheld, finalized, seed), reEvaluationAssignees[gi][tierKind][batchId], disputeBondClaimable[addr], treasuryAccrued (a running counter of forfeited bonds routed out; no tokens are held against it).
| Caller | Functions |
|---|---|
owner() (model owner) |
Setup, every phase transition, createAuditorsBatches, autoCreateTier1AndTier2, finalizeT1/T2Aggregation, slashAuditors, slashAggregators, setTier2Score, endGI, releaseGIRegistrationSlots, resolveDispute, settleRecomputation, all setters |
| Any active validator | registerDINaggregator, openDispute |
| Assigned active aggregator | commitT1Aggregation, revealT1Aggregation, commitT2Aggregation, revealT2Aggregation |
| Anyone | lockAuditSeed, lockAggSeed, lockDisputeSeed, expireDispute (after the deadline), claimDisputeBond (own balance), views |
constructor(dinValidatorStake, modelId) → AwaitingDINTaskAuditorToBeSet
setDINTaskAuditorContract(auditor) → AwaitingDINTaskCoordinatorAsSlasher
[DIN-Representative: DinCoordinator.addSlasherContract(this)]
setDINTaskCoordinatorAsSlasher() → AwaitingDINTaskAuditorAsSlasher (checks isSlasherContract)
[DIN-Representative: DinCoordinator.addSlasherContract(auditor)]
setDINTaskAuditorAsSlasher() → AwaitingGenesisModel
setGenesisModelIpfsHash(cid) → GenesisModelCreated
setDinToken(dinToken) (needed for dispute bonds; dincli's deploy sends it)
Registry approval is required after setup and before the first GI. Both start overloads share the auditor's validateGIStart(gi) call after the state/index checks and before any mutation. This replaces the direct reward-pool lookup to preserve the coordinator's size budget. See auditor wiring and future-GI guard for required pair compatibility and registry trust.
The numbers are GIstates ordinals (see DINShared §2.1).
| Step | Function (owner unless noted) | From → To state |
|---|---|---|
| Start | startGI(gi) / startGI(gi, passScore) |
GenesisModelCreated (4) or GIended (28) → GIstarted (5) |
| Aggregator registration | startDINaggregatorsRegistration → validators: registerDINaggregator → closeDINaggregatorsRegistration |
→ 6 → 7 |
| Auditor registration | startDINauditorsRegistration → validators on the auditor contract → closeDINauditorsRegistration |
→ 8 → 9 |
| Local model submissions | startLMsubmissions → clients on the auditor contract → closeLMsubmissions (anchors the audit seed) |
→ 10 → 11 |
| Audit seed | anyone: lockAuditSeed (a re-anchor leaves the state unchanged) |
→ 12 |
| Audit batches | createAuditorsBatches (delegates to the auditor, passing the seed) |
→ 13 |
| Test data | DINTaskAuditor.assignAuditTestDataset per batch; then setTestDataAssignedFlag (the auditor checks every batch has a commitment) |
→ 14 |
| Score commit | startLMsubmissionsEvaluation → auditors commit on the auditor contract |
→ 15 |
| Score reveal | startLMsubmissionsEvaluationReveal → auditors reveal |
→ 16 |
| Finalize evaluation | closeLMsubmissionsEvaluation (calls finalizeEvaluation; anchors the aggregation seed) |
→ 17 |
| Aggregation seed | anyone: lockAggSeed (a re-anchor leaves the state unchanged) |
→ 18 |
| Batches | autoCreateTier1AndTier2 |
→ 19 |
| T1 commit | startT1Aggregation → aggregators: commitT1Aggregation |
→ 20 |
| T1 reveal | startT1AggregationReveal → aggregators: revealT1Aggregation → finalizeT1Aggregation |
→ 21 → 22 |
| T2 commit | startT2Aggregation → aggregators: commitT2Aggregation |
→ 23 |
| T2 reveal | startT2AggregationReveal → aggregators: revealT2Aggregation → finalizeT2Aggregation |
→ 24 → 25 |
| Slash | slashAuditors → slashAggregators |
→ 26 → 27 |
| End | endGI (settles rewards) |
→ 28 |
| Housekeeping | releaseGIRegistrationSlots(gi) |
no state change |
Every owner transition after startGI takes the expected _GI and reverts TC_WrongGI if it is not the current one. Every state change goes through _setGIstate, which emits GIStateChanged(GI, newState).
The three reveal-opening calls (startLMsubmissionsEvaluationReveal, startT1AggregationReveal, startT2AggregationReveal) are manual owner steps. Forgetting one stalls the GI in the commit window (finalize reverts) without corrupting state.
Requires _GI == GI + 1 and the genesis/GI-ended state, then calls dinTaskAuditorContract.validateGIStart(_GI): registry approval, enabled status and a nonzero reward pool are required. The auditor preserves TC_GIRewardPoolNotFunded for an empty pool. Both overloads check before mutation; the two-argument overload then calls updatePassScore, and both increment GI and enter GIstarted.
In DINaggregatorsRegistrationStarted only, and onlyCurrentGI: _GI must be the current GI (TC_WrongGI), as for DINTaskAuditor.registerDINAuditor (issue #206). Checks in order: isValidatorActive (TC_AggregatorNotActive), not already registered, fewer than 300 registered (TC_RegistrationCapReached), stake ≥ getModelStakeMin(modelId) when non-zero (TC_StakeBelowModelFloor), and — when maxConcurrentRegistrationsPerStakeUnit > 0 — activeRegistrationCount < (stake / minStake) × cap (TC_ConcurrentRegistrationCapReached). Then records the aggregator, calls incrementActiveRegistration, emits DINValidatorRegistered.
Both batch-creation steps shuffle with a seed that must be locked first (issue #156 H-2, PR #191). It replaces blockhash(block.number - 1) / block.timestamp entropy, which the caller could grind. The pattern is the same as the dispute seed (§7): a future block is anchored, and anyone may lock its hash once it is mined.
| Auditor batches | T1/T2 batches | |
|---|---|---|
| Anchor | closeLMsubmissions sets auditSeedBlock[gi] = block.number + disputeSeedDelay |
closeLMsubmissionsEvaluation sets aggSeedBlock[gi] the same way |
| Lock (anyone, current GI) | lockAuditSeed(gi) |
lockAggSeed(gi) |
| Seed | keccak256(blockhash(seedBlock), gi, "AUD") |
keccak256(blockhash(seedBlock), gi, "AGG") |
| State on lock | LMSclosed → AuditSeedLocked |
LMSevaluationClosed → AggSeedLocked |
| Consumer | createAuditorsBatches, gated on AuditSeedLocked (TC_AuditSeedNotLocked otherwise; the auditor contract also rejects a zero seed with TA_AuditSeedNotLocked) |
autoCreateTier1AndTier2, gated on AggSeedLocked (TC_AggSeedNotLocked otherwise) |
| Events | AuditSeedLocked, AuditSeedReanchored |
AggSeedLocked, AggSeedReanchored |
A lock call reverts if the seed isn't anchored yet (TC_*SeedNotAnchored), is already locked (TC_*SeedAlreadyLocked), or the anchor block isn't mined yet (TC_*SeedBlockNotMined). If more than 256 blocks have passed and blockhash(seedBlock) is zero, the call re-anchors to block.number + disputeSeedDelay and emits the …Reanchored event; it never stores a zero-derived seed.
Residual trust and gaps (as of issue #243 / PR #253; tracked as BL-26 in Developer/BACK_LOG.md):
- Sequencer trust.
blockhashis sequencer-produced on OP Stack, so this design trusts the sequencer not to grind. VRF is the mainnet-grade follow-up (issue #178). - Re-roll by declining to lock. Once
seedBlockis mined, anyone can compute the resulting shuffle off-chain. A model owner who dislikes it can simply not lock, wait out the 256-block window, and get a fresh anchor on the next lock call. The permissionless lock only prevents this if another party locks first. Post-lock pool reshaping.Fixed by issue #243. Batch layout is now derived from the fixed per-GI registration list (dinAggregators[_GI]) instead of the live active pool. An unstake between seed-lock and batch-creation forfeits only the leaver's own seat; every other validator's batch and seat position is determined solely by the fixed list and the locked seed. See §6.4 for the position-stable seat assignment algorithm.
Requires AggSeedLocked, i.e. a locked aggSeed[gi].
Position-stable seat assignment (issue #243): The batch layout is derived from the fixed per-GI registration list (dinAggregators[_GI]), not the live active pool. An unstake between seed-lock and this call can only forfeit the leaver's own seat — every other validator's batch and seat position is fixed. The active count then caps t1cnt/hasT2 so the fill never exhausts the reserve (liveness fix).
- Shuffles the fixed per-GI registration list
dinAggregators[_GI]in memory withkeccak256(seed, "AGG_ADDR"). Also shuffles approved model indexes withkeccak256(seed, "AGG_IDX"). - Computes
t1cntMaxand T2's fixed position (t2Start = t1cntMax * T1_AGGREGATORS_PER_BATCH) from the registered count. Counts active validators and capst1cnt = min(t1cntMax, activeCount / T1_AGGREGATORS_PER_BATCH);hasT2only ifactiveCount ≥ (t1cnt+1) * T1_AGGREGATORS_PER_BATCH. - Position-stable seat fill: for each T1 seat i inactive, draws from the gap (
t1cnt*T1_PER_BATCH..t2Start-1) then from the true reserve (t2Start+T1_PER_BATCH..rLen-1). Inactive T2 seats draw from the same combined reserve. - Builds T1 batches (positions
0..t1cnt*3-1) and the model array. The last T1 batch may receiveMIN_T1_MODELS_PER_BATCHmodels. T2 is built from the fixed ranget2Start..t2Start+2. - If T2 exists, builds the T2 batch from positions
3*t1cnt..totalSeats-1.
Leftover aggregators and models (beyond totalSeats) are not used this GI. If fewer than T1_AGGREGATORS_PER_BATCH aggregators remain beyond the T1 seats, no T2 batch is created and finalizeT2Aggregation has nothing to finalize.
Submissions are commit-then-reveal (issue #156 M-1, PR #197). With a single-shot submit, a late aggregator could read an earlier aggregator's CID from public state and copy it. Now no CID is visible while commits are open.
- Commit (
T1AggregationStarted/T2AggregationStarted):commitT1Aggregation(gi, batchId, commitHash)/commitT2Aggregation(…). The caller must be an assigned (TC_NotBatchAggregator), active (TC_AggregatorNotActive) aggregator of an existing batch (TC_InvalidBatch; for T2 anybatchId != 0revertsTC_OnlyOneTier2Batch). A zero hash (TC_T*EmptyCommitHash) and a second commit (TC_T*AlreadyCommitted) are rejected. EmitsT1AggregationCommitted/T2AggregationCommitted. - Commit hash:
keccak256(abi.encode(cid, salt, msg.sender, gi, TierKind, batchId)). Because the sender, GI, tier and batch are bound in, an aggregator who copies a peer's commit hash cannot later reveal the peer's(cid, salt)under their own address. - Open reveals (owner):
startT1AggregationReveal/startT2AggregationReveal. No further commits are accepted. - Reveal (
T1AggregationRevealStarted/T2AggregationRevealStarted):revealT1Aggregation(gi, batchId, cid, salt)/revealT2Aggregation(…). Same batch, assignment and active checks; requires a prior commit (TC_T*NoCommitFound), no prior reveal (TC_AlreadySubmitted), a non-zero CID (TC_ZeroCID) and a matching hash (TC_T*RevealHashMismatch). Records the CID, incrementst*Votes[cid], emitsT1AggregationSubmitted/T2AggregationSubmitted. - Finalize (owner, from the reveal state): per batch, the CID with the most votes wins (on a tie, the first tied CID in batch order). Reverts
TC_NoSubmissionsif nobody revealed, andTC_InsufficientSubmissionsif fewer than3 / 2 + 1 = 2revealed. RecordsfinalCIDand the finalization time, emitsT1BatchFinalized/T2Finalized, and adds oneaggregatorWeightunit for every assigned aggregator of the batch, whether or not they revealed or matched consensus. A failing batch reverts the whole call, so no weight persists for unfinalized batches.
An aggregator who commits but never reveals is treated exactly like one who never committed: excluded from the vote and slashed as a non-submitter (§6.6).
For every T1 and T2 batch member:
- No reveal (S2):
slashPartial(agg, minStake × s2SlashFractionBps / 10 000, "AGG_T1_NO_SUBMISSION" | "AGG_T2_NO_SUBMISSION", GI)— liveness fault with S5 recidivism escalation. Covers both never committing and committing without revealing. - Revealed a non-consensus CID:
slash(agg, minStake, "AGG_T1_BAD_CONSENSUS" | "AGG_T2_BAD_CONSENSUS")— full severity.
minStake is read from DinValidatorStake at call time. Emits AggregatorSlashed(gi, batchId, aggregator, reason, requested, actual); actual can be lower than requested if the aggregator's remaining stake is smaller. The S6 counter is deliberately not fired here (it would stack with S2/S5).
slashAuditors(gi)requiresT2AggregationDoneand only delegates toDINTaskAuditor.slashAuditors(see DINTaskAuditor §8), then moves toAuditorsSlashed.setTier2Score(gi, score)is allowed inT2AggregationDoneorGenesisModelCreated; stored only, it does not affect slashing or rewards.endGIrequiresAggregatorsSlashed, callsdinTaskAuditorContract.settleRewards(GI, totalAggregatorWeight[GI])and moves toGIended. It runs no participant loops (BL-10).releaseGIRegistrationSlots(gi)(owner, once per ended GI) decrements the active-registration counters of that GI's aggregators and, viadecrementAuditorRegistrations, its auditors. Until it is called, those validators keep occupying concurrent-registration slots.
Bond custody, windows and fresh-subgroup assignment are on-chain; adjudication is not (the owner decides — verifying an aggregation CID would mean re-running aggregation).
| Step | Who | Effect |
|---|---|---|
openDispute(gi, tierKind, batchId) |
Any active validator | Within disputeWindow of the batch's finalization; pulls disputeBond DIN; anchors the dispute seed at block.number + disputeSeedDelay |
lockDisputeSeed(gi, tierKind, batchId) |
Anyone, once the anchor block is mined and before resolution | Stores seed = keccak256(blockhash(seedBlock), gi, tierKind, batchId); re-anchors if the block hash is no longer available (emits DisputeSeedReanchored) |
resolveDispute(…, upheld=false) |
Owner | Dispute closed; bond forfeited |
resolveDispute(…, upheld=true) |
Owner | Requires a locked seed (TC_DisputeSeedNotLocked). Shuffles the GI's registered aggregators with the seed and takes the first 3 that are active and were not in the disputed batch (TC_NotEnoughValidators if fewer than 3). Starts resolutionWindow |
settleRecomputation(…, confirmed=true) |
Owner | Bond credited back to the challenger; every original aggregator whose revealed CID matched the disputed finalCID is slashed minStake (S4_INVALID_AGGREGATION) |
settleRecomputation(…, confirmed=false) |
Owner | Bond forfeited |
expireDispute(…) |
Anyone, after resolutionDeadline |
Bond credited back; every fresh-subgroup member slashed minStake (S4_FRESH_SUBGROUP_TIMEOUT) |
claimDisputeBond() |
Challenger | Pull payment of credited bonds |
A forfeited bond is split 50% burned / 50% forwarded to dinvalidatorStakeContract.slashTreasury(); both halves are burned if no treasury is set. treasuryAccrued counts the full forfeited amount.
The fresh subgroup is bookkeeping only: nothing re-opens the state machine for it to submit a recomputation on-chain (§10 No. 3).
One dispute per batch, ever. openDispute checks disputes[gi][tierKind][batchId].challenger != address(0) and reverts TC_DisputeAlreadyOpen. The challenger field is set when the dispute opens and never cleared, so this check blocks reopening after resolution just as it blocks a second open while the first is pending — both produce TC_DisputeAlreadyOpen. Consequence: a colluder can open a weak dispute on their own batch early, accept the forfeited bond, and leave the batch immune to a better-evidenced challenge for the rest of the window. Revisit if multi-dispute-per-batch is ever desired; see issue #181 (decentralised adjudication).
| Group | Events |
|---|---|
| Lifecycle | GIStateChanged(GI, newState) on every state change (GI is 0 during setup) |
| Registration & batches | DINValidatorRegistered, Tier1BatchAuto, Tier2BatchAuto |
| Seeds | AuditSeedLocked, AuditSeedReanchored, AggSeedLocked, AggSeedReanchored, DisputeSeedLocked, DisputeSeedReanchored |
| Aggregation | T1AggregationCommitted, T1AggregationSubmitted (on reveal), T1BatchFinalized, T2AggregationCommitted, T2AggregationSubmitted (on reveal), T2Finalized |
| Slashing | AggregatorSlashed, S2SlashFractionBpsUpdated |
| Disputes | DisputeOpened, DisputeResolved, ReEvaluationAssigned, RecomputationSettled, DisputeExpired, DisputeBondClaimed |
DINTaskCoordinator
├── DinValidatorStake: isSlasherContract, isValidatorActive, getStake, minStake,
│ getModelStakeMin, maxConcurrentRegistrationsPerStakeUnit,
│ activeRegistrationCount, increment/decrementActiveRegistration,
│ slashPartial (S2), slash (bad consensus, S4), slashTreasury
├── DINTaskAuditor: validateGIStart, updatePassScore, createAuditorsBatches (with the audit seed),
│ setTestDataAssignedFlag, finalizeEvaluation, approvedModelIndexes,
│ slashAuditors, settleRewards, decrementAuditorRegistrations
└── DinToken: safeTransferFrom / safeTransfer / burn (dispute bonds)
DINTaskAuditor → DINTaskCoordinator: GI, GIstate, aggregatorWeight (at claim time)
DINModelRegistry is never read: disabling a model has no effect here.
Earlier findings from the foundry/src security review should be read alongside these. Several are now fixed: unbounded registration (capped at 300), zero-CID collision (TC_ZeroCID), missing quorum (TC_InsufficientSubmissions), grindable shuffle (H-2, §6.3) and copy-the-leader aggregation (M-1, §6.5).
- No. 1 — Little room under EIP-170. Issue #201 Part A brought this contract back under the 24,576-byte limit. It folded the T1/T2 slash loops into
_slashBatch, and replaced the public batch and per-aggregator getters withgetAggregatorSubmission. CI now runs.github/scripts/contract_size_gate.py. The gate fails anyfoundry/srccontract with less than 1,024 bytes of margin, and warns below 2,048 bytes. This contract sits in the warning band, so the next feature added here may need another size reduction first.foundry/anvil.shstill lifts the limit locally, so a local deploy is not a size check; the CI gate is. - No. 2 — Selective non-reveal is cheaper than being wrong. Reveals land one at a time. A committed aggregator who sees that peers' CIDs differ from their own can withhold the reveal and take the S2 liveness slash (30% of
minStakeby default) instead of the full-minStakebad-consensus slash. The same trade-off exists for auditors. Whether committed-but-unrevealed gets its own reason code and fraction is open in issue #201 (Part B). - No. 3 —
expireDisputecan slash an idle fresh subgroup that had no on-chain way to act. The fresh subgroup cannot submit a recomputation to this contract, so the only thing preventing the timeout slash is the owner callingsettleRecomputationin time. If the owner does nothing, anyone can slash three uninvolved validators a fullminStakeeach. - No. 4 —
modelIdis fixed at construction, but assigned at registry approval. The registry assigns the ID only when it approves the request, which requires this contract to already exist. The deployer must predict the ID. A wrong guess silently applies another model's stake floor (or none). - No. 5 — Plurality with three aggregators. Two colluding aggregators in a batch win the vote; nothing verifies the aggregated model itself. The dispute path (§7) is the only recourse, and the owner adjudicates it.
- No. 6 — No recovery from a stalled GI. If a batch never reaches the reveal quorum,
finalizeT1Aggregation/finalizeT2Aggregationkeep reverting and there is no owner path to skip the batch or abort the GI. - No. 7 — Stale NatSpec: comments around the dispute scaffold still say
DinTreasury"doesn't exist on develop yet" (forfeitures are already forwarded). Several parameters are described as "DAO-settable"; they areonlyOwner, i.e. set by the model owner. - No. 8 — Leftovers:
networkFeeFlooris stored but not enforced. - No. 9 — Not upgradeable: a bug in a model's task contracts requires redeploying them and re-registering the model.
- No. 10 — Fixed: dincli supports the current two-argument constructor.
dincli model-owner deploy task-coordinatordefaults--model-idtoDINModelRegistry.totalModels(), with a warning that this ID only holds if no other registration is approved first. It then sets the platformDinToken. Legacy one-argument artifacts remain supported and skip token setup because they have nosetDinTokenfunction. (dincli aggregator aggregate-t2used to name its working directory, worker job and container after the last T1 batch id; fixed in issue No. 202 Part 2.) - No. 11 — Fixed:
registerDINaggregatornow checks the GI. It used to have noonlyCurrentGI, so during GI N's window a validator could register for GI N+1. Up to 300 addresses could fill that list, which locked out honest registrants and handed the attacker every T1/T2 batch. Registering for a released past GI also leaked the caller's own slot. Fixed by addingonlyCurrentGI(issue #206); it cost 9 bytes inside the No. 1 budget.
- Reward pool gate on
startGI; per-aggregator reward weights handed tosettleRewardsatendGI(BL-10). - Commit-then-reveal evaluation phase (
startLMsubmissionsEvaluationReveal,LMSevaluationRevealStarted). - Registration caps and floors (
MAX_REGISTERED_AGGREGATORS, per-model stake floor, concurrent-registration cap) withreleaseGIRegistrationSlots. - Submission quorum and zero-CID rejection on T1/T2 finalization.
- S2 partial slashing via
slashPartial; full-severity slashing kept for bad consensus. - S4 aggregation dispute scaffold (bonds, fresh subgroups, recomputation settlement, expiry), with a locked future-block seed for the fresh subgroup.
- Forfeited dispute bonds forwarded to the platform treasury (
slashTreasury()), replacing the per-contract treasury address (issue #152). GIStateChangedemitted on every state change;T1BatchFinalized/T2Finalizedon finalization.modelIdconstructor argument.- Progress states (task_061026_21 Part B):
lockAuditSeed→AuditSeedLocked,setTestDataAssignedFlag→AuditTestDataAssigned,lockAggSeed→AggSeedLocked.createAuditorsBatches,startLMsubmissionsEvaluationandautoCreateTier1AndTier2are gated on them, so evaluation can no longer start before every batch has test data.GIendedmoved from 25 to 28;TC_EvalPhaseNotClosedis gone. To fit the coordinator's size budget,releaseGIRegistrationSlots' two stringrequires becameTC_GINotEnded/TC_SlotsAlreadyReleased(−88 B). - Locked batch-assignment seeds for auditor and T1/T2 batches (
lockAuditSeed,lockAggSeed; issue #156 H-2, PR #191). - Commit-then-reveal T1/T2 aggregation with a sender-bound commit hash (
commitT*Aggregation,revealT*Aggregation,startT*AggregationReveal; issue #156 M-1, PR #197). Adds statesT1AggregationRevealStarted(18) andT2AggregationRevealStarted(21), shifting later ordinals. - Back under EIP-170 (issue #201 Part A). The
slashAggregatorsT1/T2 loops are folded into_slashBatch, with unchanged slashes and events.tier1Batches/tier2Batchesand the tent1*/t2*per-aggregator maps are nowinternal, and the newgetAggregatorSubmissionview replaces their getters. This is a view-ABI change; dincli and the bundled ABI were updated with it. registerDINaggregatorgainsonlyCurrentGI: registering for any GI other than the current one reverts withTC_WrongGI(issue #206).