feat(staking,state): IIP-59 commissionRate fields + feature flag (PR 1/5)#4859
feat(staking,state): IIP-59 commissionRate fields + feature flag (PR 1/5)#4859envestcc wants to merge 1 commit into
Conversation
| // 0 = legacy (no auto-distribution). | ||
| uint64 commissionRate = 11; | ||
| // IIP-59: epoch number of the last SetCommissionRate (cooldown enforcement). | ||
| uint64 commissionRateLastEpoch = 12; |
There was a problem hiding this comment.
why commissionRateLastEpoch is needed, and what it will be used for?
| // distribution. When true, GrantEpochReward auto-splits each delegate's | ||
| // epoch reward between the delegate's commission and a proportional | ||
| // distribution to voters, and SetCommissionRate actions are accepted. | ||
| EnableVoterRewardDistribution bool |
There was a problem hiding this comment.
It is best for feature flags to default to indicating the activation status of a new feature. Since the default value of bool is false, it is advisable to rename the flag to something like NoVoterRewardDistribution and document this rule in the comments, so that AI will avoid making the same mistake in the future.
64e1895 to
57b3e7e
Compare
|
Good catch — IIP-59 doesn't actually prescribe a per-rate-change cooldown. Re-reading the spec:
The IIP's only protection against rapid manipulation is epoch-boundary activation. In our design, the existing poll Cur/Nxt snapshot machinery already gives us that for free: a SetCommissionRate call at any moment only affects rewards in the epoch after the next PutPollResult snapshot (~1.5 epoch reaction window). No The 168-epoch cooldown in the PoC (#4811 commit I've dropped the field from both PRs and force-pushed:
Stacked PR #4860 (VoterWeightView) is rebased on the updated PR 1 head. |
Adds the proto-level field and feature gate that the rest of the IIP-59 protocol-native voter reward distribution PRs will hang behavior off of. No runtime behavior changes in this PR — the field is populated as zero on existing chain data, default behavior matches today exactly. Schema: - stakingpb.Candidate gains commissionRate=11; Go Candidate struct mirrors it. Equal/Clone/toProto/fromProto updated (the original PoC missed Equal — flagged in iotexproject#4811 review #2). - state.Candidate gains CommissionRate, snapshotted per epoch from the staking candidate state by PutPollResult (added in PR 4). The latest user-set value lives on staking.Candidate; state.Candidate holds the per-epoch frozen value consumed by GrantEpochReward. - iotextypes.Candidate.commissionRate is set/read in candidateToPb / pbToCandidate so the new field travels through poll snapshots and over the wire. Feature flag: - FeatureCtx.NoVoterRewardDistribution, bound to !g.IsToBeEnabled(height) per AGENTS.md convention for WIP features (the gate will be swapped for a real hardfork height at release time). - Named so that the bool zero value (false) corresponds to the post-fork activated behavior, matching the existing NoCandidateExitQueue / NotSlashUnproductiveDelegates convention. A docstring records this rule next to the field for future readers. Why no separate commissionRateLastEpoch field: - IIP-59 doesn't prescribe a per-rate-change cooldown. Its protection against rapid manipulation is epoch-boundary activation, which our design already provides for free: a SetCommissionRate at any moment only affects rewards in the epoch *after* the next PutPollResult snapshot, giving voters ~1.5 epochs of reaction time. - If the protocol later decides cooldown is needed, adding the field is an additive proto change with no migration cost. Toolchain: - stakingpb regenerated with protoc-gen-go v1.26.0 to match the version recorded in the existing staking.pb.go header. Local dev: - go.mod replace pointing at ../iotex-proto so the build resolves the new iotex-proto fields prior to the proto PR being tagged. Remove the replace once iotex-proto cuts a release containing the SetCommissionRate + Candidate.commissionRate additions. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
57b3e7e to
f52ee59
Compare
|
|
Good convention — adopted. Looking at the existing flags in context.go ( Renamed // NoVoterRewardDistribution gates IIP-59: protocol-native voter reward
// distribution. When false (the post-fork / activated state, which is also
// the bool zero value), GrantEpochReward auto-splits each delegate's
// epoch reward between the delegate's commission and a proportional
// distribution to voters, and SetCommissionRate actions are accepted.
// When true (the pre-fork legacy state), the protocol keeps today's
// behavior of granting the full epoch reward to the delegate.
//
// Naming convention: feature flags should be named such that the bool
// zero value (false) corresponds to the post-fork activated behavior.
// This matches NoCandidateExitQueue / NotSlashUnproductiveDelegates and
// makes a missing / partially-initialized FeatureCtx default to the
// long-term mainnet behavior rather than the temporary pre-fork state.
NoVoterRewardDistribution boolBoth branches force-pushed:
|
|
Closing as part of IIP-59 PR reorganization. The proposal was amended (iotexproject/iips#73) to cover both block reward + epoch reward and to base voter distribution on a per-epoch snapshot. The new PR split is:
The incremental voter weight view (old #4860 + #4863) is dropped in favor of a per-PutPollResult full scan; ~40k buckets sorted+encoded once per epoch fits well inside the mint budget and eliminates the 13-handler-hook consensus-safety surface. Old #4864 (voter reward distribution) is replaced by new PR 3 which reads the snapshot instead of the live view. Superseded by new PRs — will link once opened. |



Summary
PR 1 of the IIP-59 series (protocol-native voter reward distribution; replaces the centralized Hermes service). This PR adds the schema fields and feature gate; no runtime behavior changes — fields are populated as zero on existing chain data and default behavior matches today exactly.
Series
Changes
Schema:
stakingpb.CandidategainscommissionRate = 11+commissionRateLastEpoch = 12; Gostaking.Candidatemirrors them.Equal/Clone/toProto/fromProtoall updated (the original PoC at feat(iip-59): protocol-native voter reward distribution #4811 missedEqual— flagged in review Batch update 2019-04-27 6PM PDT #2 of that PR).state.CandidategainsCommissionRate. The latest user-set value lives onstaking.Candidate(mutable);state.Candidate.CommissionRateis the per-epoch frozen snapshot consumed byGrantEpochReward(set byPutPollResultin PR 4).iotextypes.Candidate.commissionRateis set/read incandidateToPb/pbToCandidateso the new field travels through poll snapshots and over the wire.Feature flag:
FeatureCtx.EnableVoterRewardDistribution, bound tog.IsToBeEnabled(height)perAGENTS.mdconvention for WIP features. The gate will be swapped for a real hardfork height at release time.Toolchain:
stakingpbregenerated withprotoc-gen-go v1.26.0to match the version recorded in the existingstaking.pb.goheader (minimizes diff noise).Local dev (transient):
go.modreplacepointing at../iotex-protoso the build resolves the newiotextypesfields prior to iotex-proto PR add validation to minicluster integrate test #174 being merged/tagged. Remove the replace before final merge once iotex-proto cuts a release containing the SetCommissionRate + Candidate.commissionRate additions.Why these fields, not more
The original PoC (#4811) used three rate fields on
staking.Candidate:commissionRate+pendingCommissionRate+commissionRateLastEpoch, with explicit pending→active promotion logic. This PR instead uses only two fields onstaking.Candidate(latest value + cooldown metadata) and lets the existing pollCur/Nxtsnapshot machinery handle the current-vs-next epoch separation naturally — current epoch's rate lives on thestate.Candidatesnapshot inCurCandidateKey, the latest user-set value lives onstaking.Candidate. No new promotion code paths.Test plan
go build ./...passes with the newgo.modreplace pointing at the local iotex-proto branchgo test ./action/protocol/... ./state/...— 935 tests passTestCandidateCommissionRateinstate/candidate_test.goverifiesEqual/Clone/Proto roundtrip forstate.Candidate.CommissionRateTestCandidateCommissionRateinaction/protocol/staking/candidate_test.goverifiesEqual/Clone/toProto/fromProtoforstaking.Candidate.CommissionRate+CommissionRateLastEpoch, including the explicit assertion thatEqualreflects both new fields (regression test for the PoC bug)TestClone,TestCloneWithDeactivation, andTestCandidate*continue to pass — no regression in the existing schemaBehavior gating
EnableVoterRewardDistribution = false. Fields exist as zero. No code path reads them. Behavior identical to current master.🤖 Generated with Claude Code