fix(pse): repair mainnet v7 migration TotalScore invariant + Add Error Context#129
Conversation
TxCorpi0x
left a comment
There was a problem hiding this comment.
@TxCorpi0x reviewed 9 files and all commit messages, and made 1 comment.
Reviewable status: all files reviewed, 1 unresolved discussion (waiting on metalarm10 and ysv).
app/upgrade/v7/migrate_pse.go line 309 at r1 (raw file):
ctx context.Context, storeService sdkstore.KVStoreService, distributionID uint64, totalScore sdkmath.Int, ) error { if !totalScore.IsZero() {
The migration flow may leave total score missing when the sum is zero, so phase2 does not change and halt community distribution.
distribute.go:168 totalScore, err := k.TotalScore.Get(ctx, ongoingID) treats missing Total score as zero.
distribute.go:177 if totalPSEAmount.IsPositive() && !totalScore.IsPositive() { errirs when amount is positive and total score inot positive, if migration return zero eligible score first pahse 2 run can fail instad of clean finalization of community pool.
Note: It is worth it to cover no-data migration sucess for zero/missing total score. (in migration tes)
metalarm10
left a comment
There was a problem hiding this comment.
@metalarm10 made 1 comment.
Reviewable status: 6 of 11 files reviewed, 1 unresolved discussion (waiting on TxCorpi0x and ysv).
app/upgrade/v7/migrate_pse.go line 309 at r1 (raw file):
Previously, TxCorpi0x wrote…
The migration flow may leave total score missing when the sum is zero, so phase2 does not change and halt community distribution.
distribute.go:168
totalScore, err := k.TotalScore.Get(ctx, ongoingID)treats missing Total score as zero.
distribute.go:177if totalPSEAmount.IsPositive() && !totalScore.IsPositive() {errirs when amount is positive and total score inot positive, if migration return zero eligible score first pahse 2 run can fail instad of clean finalization of community pool.Note: It is worth it to cover no-data migration sucess for zero/missing total score. (in migration tes)
Done.
Good catch, thanks. I applied a reorder in distribute.go so that the empty-snapshot-batch finalize path now runs before the totalScore == 0 invariant check, so a distribution with no eligible recipients refunds cleanly to the community pool - without any disable of PSE. The invariant check only fires when there are snapshot entries but TotalScore is zero (which is a real bug).
Added three migration tests:
TestMigratePSEStore_TotalScoreFromMultipleEntries: sum of pre-v7 entries is correctly written toTotalScore(related to testnet incident mostly).TestMigratePSEStore_EmptyStoreLeavesTotalScoreUnset: empty pre-v7 store,TotalScoreunset, PSE stays enabled.TestMigratePSEStore_AllExcludedLeavesTotalScoreUnset: every pre-v7 entry excluded,TotalScoreunset, PSE stays enabled.
TxCorpi0x
left a comment
There was a problem hiding this comment.
@TxCorpi0x reviewed 5 files and all commit messages, and resolved 1 discussion.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on ysv).
ysv
left a comment
There was a problem hiding this comment.
@ysv reviewed 11 files and all commit messages.
Reviewable status:complete! all files reviewed, all discussions resolved (waiting on metalarm10).
Description
Full report: https://app.clickup.com/t/868jb9jdc
Update: The fix implemented here is applied to testnet as well and chain patched with
v7patch1. Testnet PSE bug fixed and stuck distributions are retroactively resolved: #130Summary
Fixes the v7 PSE migration bug that stalled testnet's first multi-block community distribution on 2026-04-21 (~528M utestcore overpayment, intermediary drained, circuit breaker tripped).
migrateAccountScoreSnapshotscopied pre-v7AccountScoreSnapshotentries, bypassingaddToMainScoreand leavingTotalScore[firstMultiBlockDistributionID]unset. Phase 2 then divided by an understated denominator, inflating every payout.Changes
migrateAccountScoreSnapshotsnow sums non-excluded migrated scores intoTotalScore[distID]and routes excluded entries toExcludedAddressScore(matches steady-state v7 routing).distribution_id,delegator,validator,amount,total_score, block height, and stack trace — diagnosis becomes one log line instead of hours of state forensics.Tests
3 new/strengthened migration tests:
TestMigratePSEStore— happy-path now assertsTotalScoreinvariantTestMigratePSEStore_TotalScoreInvariant— reproduces the testnet failure with real score values; fails against the unfixed migrationTestMigratePSEStore_RoutesExcludedAddresses— excluded entries →ExcludedAddressScore, not counted inTotalScorePlus
TestProcessOngoingTokenDistribution_ErrorContextto lock in the error wrappers.Mainnet impact
Mainnet hasn't run v7 yet — with this PR merged, the first multi-block distribution on mainnet will process initial
TotalScorecalculations correctly. Testnet was already repaired separately via thev7patch1upgrade (proposal #84); this PR doesn't touch testnet but uses identical routing semantics so the two networks stay aligned.Reviewers checklist:
Authors checklist
This change is