graphprotocol / graphprotocol/contracts
SubgraphService: unbounded _releaseStake(indexer, 0) inside collect self-deadlocks after a collection outage (99.4M-gas collect observed)
Nobody has claimed this yet.
- Dominant language
- Solidity
- Stars
- 374
- Forks
- 176
- Avg merge
- 1d 15h
- Merged PRs (30d)
- 4
Description
Summary
SubgraphService._collectQueryFees calls _releaseStake(indexer, 0) — releasing all expired stake claims — before collecting. Because claims are (in practice) only released inside collect, an indexer whose collections stop for a while accumulates a claims backlog that eventually makes collect itself exceed the L2 per-transaction gas ceiling: a self-deadlock. The in-code comment anticipates the OOG case and points at releaseStake, which does work — but nothing in the standard indexer stack calls it, so recovery is a manual, undocumented-in-tooling intervention. A bounded release inside collect would make the path self-healing.
Real-world proof case (Arbitrum One, 2026-08)
Indexer 0xEdca8740873152fF30a2696Add66D1ab41882beB:
- An 18-day TAP outage (indexer-side) meant no Horizon collect succeeded after cutover while POI/allocation activity continued → 4,373 expired stake claims accumulated.
- Every
collect(address,uint8,bytes)then cost ~99.41M gas (measured viadebug_traceCall; two different RAVs within 17 gas of each other — release-all dominates at ~22.7k gas/claim;releaseStake(0)alone traced at 98.85M). - estimateGas therefore reverted bare (
no data) on every attempt fromindexer-agentv0.25.10, wedging ~42k GRT of RAVs. The error shape (empty revert data from a custom-errors codebase) made this expensive to diagnose. - Recovery: manual
releaseStake(1000)transactions from the service-provider wallet (22.2M gas each, e.g. Arbiscan tx0xda36f1cfc79102ce7b95f2f8c53e3384f85851c76ca975c57ffe5725ac09bb66), after which collect dropped to 8.7M gas and the agent redeemed 323 RAVs / 42,152 GRT in one batch.
Suggestions (any subset helps)
- Bound the in-collect release —
_releaseStake(indexer, N)with a modest N (e.g. 100–200): collect stays O(1)-ish and the backlog self-drains across normal collections instead of deadlocking. - If unbounded stays, revert with a custom error naming the claims count when the loop would be pathological, so operators get a diagnosable failure instead of a bare OOG-shaped revert.
- Indexer-agent support: have the agent detect the oversized-queue condition and call
releaseStakein batches itself before submitting collects (we can file this on graphprotocol/indexer instead if preferred).
Full trace evidence available on request.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by locating _collectQueryFees, _releaseStake, releaseStake, and collect in the contracts repository, then trace how expired claims are processed and compare that path with the reported gas measurements. Done should include an agreed bounded or diagnosable recovery behavior for claim backlogs, with the relevant contract checks or tests updated accordingly.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- solidity
- Domain
- blockchain
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100