You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
security: S5 recidivism is per-model, so a validator can spread missed votes across models and never escalate #193
S5 recidivism escalation (full MIN_STAKE slash + jail) is counted per slasher contract, i.e. per model. A validator registered on several models can spread liveness faults across them so no single model's counter ever reaches s5RecidivismThreshold.
DinValidatorStake.slashPartial records each partial slash in _partialSlashGIs[validator][msg.sender] (foundry/src/DinValidatorStake.sol:309), where msg.sender is the calling DINTaskCoordinator / DINTaskAuditor. With the defaults (window 5 GIs, threshold 3, DinValidatorStake.sol:196-198), a validator can miss up to 2 votes/submissions per model per window on every model it serves. Across N models that is (threshold − 1) × N misses per window without S5 ever firing, and isValidatorActive stays true throughout.
Why it is keyed per contract today
This is deliberate, not an oversight. giIndex is a per-model counter, so different models' GI sequences interleave arbitrarily. A single shared ring per validator would receive non-monotonic values and underflow the ascending-order trim in _trimAndRecordPartialSlash (see the NatSpec on _partialSlashGIs, DinValidatorStake.sol:136-146). Any fix has to keep that safety property.
Impact
A validator can stay chronically unreliable across the network while only ever paying the S2 partial slashes (s2SlashFractionBps, 30% of minStake per miss by default). The escalation that is meant to eject repeat offenders never triggers. The per-miss partial slashes still apply, so this is a cost-reduction gap, not a free pass.
Options
Model-independent window: track partial slashes per validator in a time-based (block timestamp) or platform-level sequence window that is monotonic across all slashers, and escalate on the aggregate count.
Two-level check: keep the per-slasher ring for per-model escalation and add a global per-validator counter with its own threshold.
Accept and document it as a trust assumption, with the per-model threshold calibrated so that (threshold − 1) × typical model count misses per window is tolerable.
Tracking
Row 11 (KNOWN GAP) of the adversarial threat model, Developer/design/adversarial-threat-model.md (task_240926_17, merged via PR No. 182). Part 2's test_knownGap_… for Row 11 should flip to test_defended_… when this is fixed.
Fixed by #236 (merged to develop on 25 Oct 2026): S5 now also counts missed duties across all models within a time window, with an escalation epoch so no miss counts toward two penalties. Closing manually because "Closes #193" only auto-closes on merges to the default branch (main).
Follow-ups: default threshold calibration is tracked in #155, and the penalty-severity decision in #274.
@Santiagocetran@robertocarlous could one of you run the closure check on this (din-close-issue 193)? PR No. 236 says "Closes" this issue and was merged into develop on 25 Oct, and Abraham's comment above says it's fixed, but the issue is still open. I can't close it myself because I implemented it (MAINTAINING No. 5.2).
Pre-check for whoever closes this. My verification on origin/develop @ 6e73dca says it's closable. I can't close it myself because I implemented it (MAINTAINING No. 5.2), so @Santiagocetran / @robertocarlous, please confirm and close.
Fix (option 2, two-level check): PR No. 236, merged into develop as 55c4b52 (push CI green). DinValidatorStake.slashPartial keeps the per-slasher ring and also records every partial slash in a time-windowed, per-validator global ring (_trimAndRecordGlobalPartialSlash, L357-367). It escalates on either level. The default is 7 days / 6 misses, settable through setS5GlobalParams, and 0 turns it off. The global ring uses timestamps, not per-model GI numbers, so the ascending-order trim stays safe across models.
No double counting: escalation increments s5EscalationEpoch[validator] and clears the caller's ring and the global ring, so a miss can't count toward two penalties.
Tests:foundry/test/SlashingInvariants.t.sol has seven test_s5Global_* tests (escalates across slashers, settles the other slashers' rings, two slashers of one model, window expiry, off at 0, setter validation, defaults). DeployPlatform.t.sol and DeployPlatformScript.t.sol cover the deploy defaults and overrides. Local forge test on developf5b2284: 516 passed, 0 failed.
Threat model:Developer/design/adversarial-threat-model.md L172 marks Row 11 "Flipped by task_061026_20 Part C".
↪️ The issue's "flip test_knownGap_… to test_defended_…" line:P3Adversarial.t.sol doesn't exist yet (issue No. 154, Part 2), so there's no known-gap test to flip. Row 11's test should be written as a defended case when that suite lands.
Follow-ups, already tracked: default threshold calibration in issue No. 155, and the S5 penalty severity in issue No. 274 (PR No. 286, still open).
@umeradl, I checked the implementation and CI evidence on develop at 1f1431c. The tests cover counting misses across models, triggering S5 escalation, expiring old misses and preventing already-counted misses from causing another penalty. The relevant regressions passed in CI as part of the 516 passing Foundry tests. This was source/test inspection and CI verification; I didn’t run tests locally, and full-GI integration was skipped.
Based on that evidence, the cross-model recidivism bug is fixed and I recommend closing #193 for the implemented fix. Following the task plan’s sequencing, the remaining Row 11 defended adversarial test stays tracked under #154 and is intended for PR #218; it is not yet completed or merged.
Activity
umeradl commentedon Sep 30, 2026
Status update: not closable yet
Checked on
origin/develop@6ccc28c. No fix and no decision yet._partialSlashGIs[validator][msg.sender](DinValidatorStake.sol:309,:317)Developer/design/adversarial-threat-model.mdRow 11 (KNOWN GAP)test_knownGap_…for Row 11 flips totest_defended_…P3Adversarial.t.solisn't written yet (issue No. 154, Part 2)Remaining
docs(tasks-plan): narrow 051026-1 to contract follow-ups (InfiniteZer…
docs(tasks-plan): 051026-1 — apply PR No. 219 review amendments 1-5 a…
docs(tasks-plan): umermjd11 task plan 051026-1 — DINTaskAuditor size (#…
docs(tasks): add task_061026_20 and task_061026_21 (forwarded from ta…
test(adversarial): address PR InfiniteZeroFoundation#218 review fixes…
feat(stake): cross-model S5 escalation (InfiniteZeroFoundation#193)
Merge pull request #236 from umermjd11/feat/task-061026-20-cross-mode…
abrahamnash commentedon Oct 25, 2026
Fixed by #236 (merged to develop on 25 Oct 2026): S5 now also counts missed duties across all models within a time window, with an escalation epoch so no miss counts toward two penalties. Closing manually because "Closes #193" only auto-closes on merges to the default branch (main).
Follow-ups: default threshold calibration is tracked in #155, and the penalty-severity decision in #274.
umeradl commentedon Oct 29, 2026
@Santiagocetran @robertocarlous could one of you run the closure check on this (
din-close-issue 193)? PR No. 236 says "Closes" this issue and was merged intodevelopon 25 Oct, and Abraham's comment above says it's fixed, but the issue is still open. I can't close it myself because I implemented it (MAINTAINING No. 5.2).umeradl commentedon Oct 29, 2026
Pre-check for whoever closes this. My verification on
origin/develop@6e73dcasays it's closable. I can't close it myself because I implemented it (MAINTAINING No. 5.2), so @Santiagocetran / @robertocarlous, please confirm and close.developas55c4b52(push CI green).DinValidatorStake.slashPartialkeeps the per-slasher ring and also records every partial slash in a time-windowed, per-validator global ring (_trimAndRecordGlobalPartialSlash, L357-367). It escalates on either level. The default is 7 days / 6 misses, settable throughsetS5GlobalParams, and 0 turns it off. The global ring uses timestamps, not per-model GI numbers, so the ascending-order trim stays safe across models.s5EscalationEpoch[validator]and clears the caller's ring and the global ring, so a miss can't count toward two penalties.foundry/test/SlashingInvariants.t.solhas seventest_s5Global_*tests (escalates across slashers, settles the other slashers' rings, two slashers of one model, window expiry, off at 0, setter validation, defaults).DeployPlatform.t.solandDeployPlatformScript.t.solcover the deploy defaults and overrides. Localforge testondevelopf5b2284: 516 passed, 0 failed.Developer/design/adversarial-threat-model.mdL172 marks Row 11 "Flipped by task_061026_20 Part C".test_knownGap_…totest_defended_…" line:P3Adversarial.t.soldoesn't exist yet (issue No. 154, Part 2), so there's no known-gap test to flip. Row 11's test should be written as a defended case when that suite lands.Follow-ups, already tracked: default threshold calibration in issue No. 155, and the S5 penalty severity in issue No. 274 (PR No. 286, still open).
Santiagocetran commentedon Oct 29, 2026
@umeradl, I checked the implementation and CI evidence on develop at
1f1431c. The tests cover counting misses across models, triggering S5 escalation, expiring old misses and preventing already-counted misses from causing another penalty. The relevant regressions passed in CI as part of the 516 passing Foundry tests. This was source/test inspection and CI verification; I didn’t run tests locally, and full-GI integration was skipped.Based on that evidence, the cross-model recidivism bug is fixed and I recommend closing #193 for the implemented fix. Following the task plan’s sequencing, the remaining Row 11 defended adversarial test stays tracked under #154 and is intended for PR #218; it is not yet completed or merged.
CI evidence · Task sequencing