IIP-59: recompute voter weights, drain voter-major - #82
Merged
Conversation
The previous revision specified a committed (candidate, voter) weight table materialized at each era boundary, and a delegate-major drain that walked that table. Both are gone. Weights are now recomputed on demand from bucket state. What consensus commits is the input, not the output: a per-delegate scalar snapshot plus a copy-on-write era window that makes reads as of the freeze height H reproducible even though settlement mutates the very buckets it measures. Section 4 is restructured around that: 4.1 the scalar snapshot, 4.2 the window, 4.3 on-demand recomputation. Settlement walks the voter address space in 256 shards rather than the delegate list, so a voter is paid once across every delegate they staked with. That changes the per-block budget unit from (candidate, voter) pairs to distinct voters, adds a second bound on index keys scanned because the first address byte is grindable, and removes the last-positive-voter dust rule -- a shard walk has no notion of "last". Floor-division dust and clamp shortfalls are swept at completion instead. Because a stateless recompute and a frozen path-dependent accumulator can disagree, the payout clamp is now mandatory and normative: every disagreement becomes an under-payment, never an over-payment. Also in this revision: - 1.1 replaces the multi-block owner-index seeding window with a single backfill at the activation block, with the ordering argument for why no era window can be open at that point. - 10 records a real gap: an overrunning drain loses its window at the next freeze height, about 1.5 epochs before overrun recovery detects it. The only guard is capacity, hence the new EpochsPerRewardEra >= 2 requirement in 14. - 11.1 adds bool[] compounded as the authoritative routing discriminator; compoundBucketIds[i] == 0 is ambiguous because native bucket 0 is real. - 12 renames the Web3 views and drops the entry-list-shaped return fields. - Two corrections: the snapshot hash domain separator is v2, not v1 (v1 scoped the retired (voter, weight) list digest), and orphan refunds go to the rewarding fund's unclaimed balance, not its available balance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Brings the IIP in line with the implementation on iotexproject/iotex-core#4953. The previous revision described a design that has since been replaced.
What changed in the design
No committed weight table. The previous revision specified a
(candidate, voter)weight table materialized into state at each era boundary, and required nodes to load it rather than recompute. That table is gone. Weights are recomputed on demand from bucket state.What consensus commits is now the input, not the output: a per-delegate scalar snapshot plus a copy-on-write era window. The window exists because settlement mutates the very state it measures — a compound payout grows the bucket whose weight a later chunk would read — so without it the era's split would stop being a function of the state at the freeze height
H. Section 4 is restructured accordingly: 4.1 the scalar snapshot, 4.2 the window, 4.3 on-demand recomputation.Voter-major settlement. The drain walks the voter address space in 256 shards keyed on the first address byte, not the delegate list. A voter is paid once for everything they are owed across every delegate, in one combined transfer. Consequences, all specified:
(candidate, voter)pairs to distinct voters;The payout clamp is now mandatory and normative. The numerator is a stateless recompute; the denominator is a frozen path-dependent accumulator. They can disagree. The clamp turns every disagreement into an under-payment rather than an over-payment, and section 9's residual sweep collects what the clamp and floor division leave behind.
Also in this revision
EpochsPerRewardEra >= 2requirement in section 14. Note this is not yet enforced in genesis validation in code.bool[] compoundedas the authoritative routing discriminator.compoundBucketIds[i] == 0is ambiguous because native bucket 0 is a real bucket, so consumers MUST NOT infer routing from it.VoterWeightSeedBatchSizedeprecated and unused, and notes that capacity estimates made under the old pair-based unit remain safe but conservative.Two corrections to pre-existing text
v1scoped a digest over the frozen(voter, weight)list — a preimage of an entirely different shape — and bumping keeps the domains disjoint.Removed normative text
Two requirements in the old section 4 directly contradict the implementation and are deleted:
The property they were protecting is still obtained, by a different route — the Rationale now carries an entry explaining that agreement comes from the committed copy-on-write window rather than from a committed result.
Unchanged
Section 1's migration rule, sections 2, 3, 5, 6, 11.4, 11.5, 13, and Backward Compatibility. The settlement seed derivation is unchanged — same domain string, same parent-hash input; only its use changed, from an entry offset to a starting shard.
🤖 Generated with Claude Code