fix: carry cumulative factors across round gaps (fixes CRF reset from #249) - #2
Closed
adamsoffer wants to merge 1 commit into
Closed
fix: carry cumulative factors across round gaps (fixes CRF reset from #249)#2adamsoffer wants to merge 1 commit into
adamsoffer wants to merge 1 commit into
Conversation
The on-demand Pool creation added in livepeer#249 never seeded cumulativeRewardFactor /cumulativeFeeFactor, so a newly created Pool defaulted them to 0. When a Pool was created for a round whose previous round had no Pool (transcoder inactive, or newRound's non-deterministic enumeration skipped it), reward() saw prevCRF == 0, fell back to the base 10^27, and RESET the cumulative reward factor mid-history. That reset corrupts stake for any delegator whose lastClaimRound predates the gap: pendingStake = bonded * CRF[now] / CRF[lastClaim] straddles the break and under-reports. Observed on Arbitrum staging: an orchestrator's factor dropped to base at rounds 3178/3192/3215… (multi-round gaps where even the previous round's Pool was missing), under-reporting one delegator by ~36.6k LPT versus on-chain BondingManager.pendingStake. Fix: seed the new Pool's factors from the most recent EXISTING pool, walking back from round-1 to lastRewardRound (guaranteed to have a Pool with valid factors) — mirroring the contract's latestCumulativeFactorsPool. The prior one-round lookback was insufficient because the gaps span multiple rounds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Owner
Author
|
Folded directly into PR livepeer#217's branch ( |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes a
cumulativeRewardFactorcorruption that under-reports delegator stake. Targets this feature branch (claude/magical-tereshkova) because that's the only branch with the cumulative-factor schema —maindoesn't have these fields yet.Where the bug was introduced
#249 — "fix: create missing Pool on demand to avoid null-Pool abort" (commit
00c74c1). That PR'screateOrLoadPool()setstotalStake/rewardCut/feeSharebut never seedscumulativeRewardFactor/cumulativeFeeFactor, so an on-demand Pool defaults them to0.Failure mechanism
When a Pool is created for a round whose previous round has no Pool (transcoder inactive, or
newRound()'s non-deterministic eth_call enumeration skipped it),reward()readsprevCRF = pool.cumulativeRewardFactor == 0, falls back to the base10^27, and resets the cumulative reward factor mid-history:Because pending stake is
bonded * CRF[now] / CRF[lastClaim], any delegator whoselastClaimRoundpredates the reset straddles the discontinuity and is under-reported.Observed on Arbitrum staging: orchestrator
0x21d1…38ff's factor dropped to base at rounds 3178 / 3192 / 3215… (all multi-round gaps — the previous round's Pool was also missing). One delegator read 91,155 LPT vs 127,746 LPT on-chain (BondingManager.pendingStake) — a 36,590 LPT shortfall. Round 3178 = 2023-11-16, so a fresh re-index corrupts long-settled history.Fix
Seed the new Pool's factors from the most recent existing pool, walking back from round-1 down to
lastRewardRound(guaranteed to have a Pool with valid factors) — mirroring the contract'slatestCumulativeFactorsPool. A one-round lookback (added earlier on this branch) was insufficient because the gaps span multiple rounds, so the immediately previous round's Pool is itself missing.The walk is bounded by
lastRewardRoundand only iterates when round-1's Pool is absent (i.e. reactivation), so the common active-transcoder path stays a single load.Verification
yarn codegen && yarn buildpass.getTranscoderEarningsPoolForRound(...)and the delegator total should equal on-chainpendingStake(213,713 LPT for the affected set).Note
The deterministic root cause of the missing Pools (
newRound()enumeration) is tracked in livepeer#248; this change makes the on-demand fallback correct regardless.🤖 Generated with Claude Code