A stale VRF request can consume a later pending generation
Request-to-case mappings are only deleted on fulfilment, so a delayed stale VRF request can resolve to its case and consume a later generation's pending record, seating the wrong panel.
Description
_vrfRequestToCaseId[requestId] is written per request but deleted only on fulfilment.
retryStuckCase and openAppealCase create additional requests for the same caseId while the old
mappings survive, so a delayed request still resolves to that caseId and consumes whatever
_pendingVRFCase[caseId] currently holds — including a later generation's record.
Reachable sequence: round-0 request 1 is dropped; retryStuckCase issues request 2; request 2
fulfils and seats the panel; the case tallies; an appeal is filed; openAppealCase issues request
3; request 1 finally arrives, consumes the appeal pending slot and seats the five-member appeal
panel with round-0 randomness; request 3 then reverts NoPendingCase.
The randomness remains coordinator-supplied, unbiased and correctly sized — panelSize is read
from the pending record rather than the request — so no party gains selection influence. The
defect is generation correspondence, and the cost is a wasted paid VRF request.
Recommendation
Bind every request to an immutable case-generation nonce and expected round, reject mismatches,
and invalidate all older _vrfRequestToCaseId entries whenever the generation advances.
Resolution
Fixed. A stale VRF request no longer seats the appeal panel with round-zero randomness.
Affected files
ArbitrationPool.sol#L575-L586, #L596-L646, #L997-L1013