feat(rewarding): fold block reward through per-delegate pool (IIP-59 PR 4')#4926
Draft
envestcc wants to merge 1 commit into
Draft
Conversation
…PR 4')
Under IIP-59 §3.2 an opted-in delegate's block reward can no longer be
credited directly to its RewardAddress at block time — it must join the
epoch stream and be split by the same frozen voter snapshot. This commit
introduces the pending block-reward pool, dual-rate distribution, and the
epoch-close orphan drain.
State layout: a new `_pendingBlockRewardPoolKeyPrefix` ("pbrp") holds one
`PendingBlockRewardPool` entry per delegate (proto added, pb.go regen), plus
a sorted `pbrpx` index so end-of-epoch enumeration is canonical without a
namespace scan.
GrantBlockReward now branches on the block producer's frozen
PollSnapshot (via staking.PollSnapshotFor) — if VoterRewardOnchainOptIn is
set and the fork gate is on, the base reward is debited from
unclaimedBalance and credited into the pool instead of the delegate's
account; the priority tip stays with the producer directly since it is fee
income, not voter-splittable. Legacy BLOCK_REWARD log is suppressed on the
opt-in path.
GrantEpochReward folds both streams via a new `distributeCombinedReward`
built on the extracted `allocateAndRouteVoters` helper: block portion
splits by BlockCommissionBasisPoints, epoch portion by
EpochCommissionBasisPoints, both voter pools sum into a single per-voter
allocation, and one batched DelegateDistributed log is emitted per
delegate. When the combined path declines (opt-out mid-epoch) but a pool
balance exists, the pool balance is legacy-granted to the delegate so
nothing is stranded.
After the per-candidate loop, `drainPendingBlockRewardOrphans` sweeps any
pool entries not visited this epoch — these are delegates that dropped
out of the poll list entirely. Destinations, in order: live candidate's
current Reward address (via staking.ConstructBaseView.GetCandidateByOwner)
or, if the candidate is unregistered, refund to fund.unclaimedBalance.
Never burn — the fund invariant unclaimedBalance + Σ(pool) ≤ totalBalance
stays intact.
Test suite covers pool credit accumulation, sorted-index invariant,
idempotent delete, refund fund arithmetic, drain no-op on empty index,
orphan refund when candidate is gone, and visited-set filtering.
newVoterRewardCtx now seeds the staking base view so tests exercising the
drain path can resolve candidate lookups.
Co-Authored-By: Claude Opus 4.7 <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.



Summary
PR 4' folds the block reward side of IIP-59 §3.2 on top of PR 3' (#4924, epoch stream). For opted-in delegates, the block reward is no longer credited straight to the producer at block time — it accumulates in a new per-delegate on-chain pending pool and is drained at epoch close through the same voter-split path, using BlockCommissionBasisPoints for the block portion and EpochCommissionBasisPoints for the epoch portion. Both streams collapse into a single batched `DelegateDistributed` log per delegate per epoch.
Direct prerequisite
Upstream dependencies (already in the amended stack)
What changes
New state type — per-delegate pending pool
Block-time — `GrantBlockReward`
Epoch-close — `GrantEpochReward`
Orphan drain
Invariants
Test plan
Explicitly out of scope
🤖 Generated with Claude Code