Skip to content

fix: create Pool on demand in reward() to avoid null-Pool abort#246

Open
rickstaa wants to merge 1 commit into
mainfrom
fix/reward-pool-null-guard
Open

fix: create Pool on demand in reward() to avoid null-Pool abort#246
rickstaa wants to merge 1 commit into
mainfrom
fix/reward-pool-null-guard

Conversation

@rickstaa

@rickstaa rickstaa commented Jul 20, 2026

Copy link
Copy Markdown
Member

Problem

reward() loads the round's Pool with Pool.load() and dereferences it with pool!, which hard-aborts the subgraph when the Pool is missing:

Mapping aborted at src/mappings/bondingManager.ts, line 499:
  unexpected null in handler `reward` at block #144056642

Observed on the Subgraph Studio indexer for the Livepeer subgraph.

Root cause (as far as traced)

Pools are created only in newRound(), built from on-chain eth_calls (getFirstTranscoderInPool / getNextTranscoderInPool). When that set comes out empty or short for a round, a transcoder that later calls reward() has no Pool → Pool.load() returns null → pool! aborts.

This is non-deterministic: with identical code, one indexer aborted at 144056642 while the live network deployment indexed straight past it. That points to the pool-building eth_calls resolving differently across archive environments/syncs. The precise reason the set is built short is not fully pinned down (deep-archive eth_call behavior vs a genuine on-chain edge case). Either way, reward() should never hard-halt on a missing Pool.

Fix

Create the Pool on demand when it's missing, populating the same non-null fields newRound() sets:

if (pool == null) {
  pool = new Pool(poolId);
  pool.round = round.id;
  pool.delegate = event.params.transcoder.toHex();
  pool.fees = ZERO_BD;
  pool.totalStake = transcoder.totalStake;
}

Validation

Reproduced locally against an Arbitrum One archive RPC: the pre-fix build aborts at 144056642 (unexpected null in handler reward); this build runs the reward handler and indexes past it (Done processing trigger … handler: reward).

Notes

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved reward processing when pool information is unavailable.
    • Pools are now initialized automatically with accurate staking and reward data, preventing indexing interruptions.

reward() loaded the round's Pool with `Pool.load()` and dereferenced it via
`pool!`, which aborts the subgraph if the Pool is missing (observed at block
144056642: "unexpected null in handler `reward`" at bondingManager.ts:499).

Pools are created per active transcoder in newRound() from on-chain calls
(getFirstTranscoderInPool / getNextTranscoderInPool). If that set comes back
empty — the call reverted, or an incomplete archive node on a resync — no Pool
exists for a transcoder that later calls reward(). Create the Pool on demand so
a missing Pool can never hard-halt indexing.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7e6e56fa-a17d-4f88-a65f-5be179ac4714

📥 Commits

Reviewing files that changed from the base of the PR and between 2eac27f and f58d155.

📒 Files selected for processing (1)
  • src/mappings/bondingManager.ts

📝 Walkthrough

Walkthrough

The reward handler now creates missing Pool entities on demand, initializes their state from defaults and the current transcoder stake, and proceeds with existing reward accounting.

Changes

Reward pool initialization

Layer / File(s) Summary
Missing Pool fallback
src/mappings/bondingManager.ts
The reward handler creates absent Pool entities with zero-valued fields and the transcoder’s current total stake before updating rewards.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main fix: creating Pool on demand in reward() to prevent null aborts.
Description check ✅ Passed The description covers the problem, root cause, fix, and validation, though it omits an issue number and checklist details.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/reward-pool-null-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown

🚀 Subgraph Studio preview deployed

Item Details
Version label pr-246-eba263b-29754235787
Query endpoint https://api.studio.thegraph.com/query/31909/livepeer-ci/pr-246-eba263b-29754235787
curl -H 'Content-Type: application/json' \
  -d '{"query":"{ protocol(id: \"0\") { inflation } }"}' \
  https://api.studio.thegraph.com/query/31909/livepeer-ci/pr-246-eba263b-29754235787

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant