One juror can hold several concurrent panels on a single stake
Introduced by the fix for panel grinding: frozen snapshot members are re-checked at seating for activity and stake but not for existing assignments. One juror can be drawn onto several concurrent panels on a single stake, so per-case economic security and panel independence are lost.
Description
The request-time eligibility snapshot freezes the drawable population when a case is armed, which
is what closes the panel-grinding route. At seating, members of that frozen list are re-checked by
_isLiveSnapshotMember, which tests only that the juror is still active and still meets their
tier's stake floor.
It does not test activeAssignments. The request-time filter does exclude jurors already serving,
but that check is evaluated once, when the case is armed. A juror who is seated onto another case
after this case was armed therefore remains drawable here.
Whenever several cases are armed before any of them seats — the normal shape of concurrent disputes, since arming and seating are separate transactions — their frozen snapshots overlap, and the same juror can be drawn onto every one of them.
Before the snapshot change, selection re-read activeAssignments at seating, so a juror locked
onto one case could not be drawn onto a second. In the same scenario on the previous commit the two
panels shared no jurors; on the current one they share a juror.
Vulnerable Scenario: The following steps help understand the issue:
- Four disputes are raised and each case is opened, so four snapshots are captured while every juror is idle and therefore eligible for all four.
- The coordinator delivers the four random words.
- Each case is seated. Nothing prevents the same juror being drawn for more than one of them.
- One juror is seated on all four panels, holding four concurrent assignments against a single minimum stake.
Measured with eight jurors in the pool and four cases seated: one juror held 4 of 4 panels at once, backed by a single 25,000 cNGN stake. Across the four quorum failures that followed, that juror was slashed four times for 2,666 cNGN in total — comfortably inside their stake, so the collateral is never exhausted and nothing reverts.
Impact
Economic security per case is no longer backed by a dedicated stake. The deterrent assumed for a panel is one juror's stake at risk on that panel; when one juror sits on several panels, the same stake is counted once per case. Corrupting or bribing that juror reaches every dispute they sit on for the price of influencing one, and their slashing exposure never rises to match.
Panel independence is also lost. Two disputes intended to be decided by separate randomly drawn panels can share a majority of their members, so an error or bias by one juror propagates across concurrent cases rather than being confined to one.
Both effects scale with how many cases are armed before seating, and concurrency is the expected operating state rather than an edge case.
Recommendation
Re-check activeAssignments when a frozen member is tested at seating, alongside the active and
stake-floor checks already in _isLiveSnapshotMember. That keeps the property the snapshot exists
to provide — the drawable set can only shrink after the word is delivered, never grow — while
restoring the one-panel-at-a-time invariant that selection previously enforced.
Excluding busy members can shrink a snapshot below panel size, which the existing underfill path already handles by requeueing for a fresh word rather than seating a short panel, so the convergence behaviour verified for the snapshot design continues to apply.
Resolution
Open. Not fixed at the time of publication.
Affected files
lib/ArbitrationPoolSelectLib.sol—_isLiveSnapshotMember, the filter applied to a frozen snapshot at seatinglib/ArbitrationPoolSelectLib.sol—captureEligible/_isEligibleAtRequest, which does excludeactiveAssignmentsat request timeArbitrationPool.sol—_seat/_openCaseInternal, which locks each seated juror