Skip to content

Commit 84c6271

Browse files
envestccclaude
andcommitted
feat(genesis,protocol): add IIP-59 era params + IsEraBoundary helper
Additive scaffolding for the era-based voter reward distribution described in the design doc (IIP-59 §8): - blockchain/genesis: three new Rewarding fields - EpochsPerRewardEra (default 24): epochs per voter-reward era - VoterBudgetPerBlock (default 2000): max voters credited per block during the era-boundary chunked credit path - CompoundBatchSize (default 500): max voters swept per block by the background compound sweep Not part of the genesis Hash proto — no consensus impact until a subsequent PR wires them into a fork-gated code path. - action/protocol: IsEraBoundary(epochNum, epochsPerEra) helper. Epoch 0 is never a boundary; epochsPerEra=0 disables the cadence entirely so existing tests are undisturbed. Follow-up PRs (B/C/D) consume these; no behavioral change here. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 6e45518 commit 84c6271

4 files changed

Lines changed: 60 additions & 0 deletions

File tree

action/protocol/context.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,14 @@ func GetVMConfigCtx(ctx context.Context) (vm.Config, bool) {
442442
cfg, ok := ctx.Value(vmConfigContextKey{}).(vm.Config)
443443
return cfg, ok
444444
}
445+
446+
// IsEraBoundary reports whether the given epoch number falls on an IIP-59 voter reward era boundary.
447+
// An era boundary is any epoch where epochNum%epochsPerEra == 0. Epoch 0 is never a boundary because
448+
// the genesis pre-epoch has no rewards to distribute; the first live boundary is at epoch epochsPerEra.
449+
// epochsPerEra == 0 disables era boundaries entirely (used by tests that opt out of the era cadence).
450+
func IsEraBoundary(epochNum, epochsPerEra uint64) bool {
451+
if epochsPerEra == 0 || epochNum == 0 {
452+
return false
453+
}
454+
return epochNum%epochsPerEra == 0
455+
}

action/protocol/context_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,3 +183,30 @@ func TestGetVMConfigCtx(t *testing.T) {
183183
require.True(ok)
184184
require.True(ret.NoBaseFee)
185185
}
186+
187+
func TestIsEraBoundary(t *testing.T) {
188+
require := require.New(t)
189+
190+
for _, c := range []struct {
191+
name string
192+
epochNum uint64
193+
epochsPerEra uint64
194+
want bool
195+
}{
196+
{"epoch zero never boundary", 0, 24, false},
197+
{"epoch zero with disabled cadence", 0, 0, false},
198+
{"epochsPerEra zero disables boundaries", 100, 0, false},
199+
{"first live boundary at epoch = epochsPerEra", 24, 24, true},
200+
{"exact multiple", 48, 24, true},
201+
{"one before boundary", 23, 24, false},
202+
{"one after boundary", 25, 24, false},
203+
{"epochsPerEra of 1 makes every non-zero epoch a boundary", 1, 1, true},
204+
{"epochsPerEra of 1 also holds for arbitrary epochs", 12345, 1, true},
205+
{"large multiple", 24 * 30, 24, true},
206+
{"large non-multiple", 24*30 + 1, 24, false},
207+
} {
208+
t.Run(c.name, func(t *testing.T) {
209+
require.Equal(c.want, IsEraBoundary(c.epochNum, c.epochsPerEra))
210+
})
211+
}
212+
}

blockchain/genesis/genesis.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ func defaultConfig() Genesis {
163163
FoundationBonusP2EndEpoch: 18458,
164164
ProductivityThreshold: 85,
165165
WakeBlockRewardStr: "4000000000000000000",
166+
EpochsPerRewardEra: 24,
167+
VoterBudgetPerBlock: 2000,
168+
CompoundBatchSize: 500,
166169
},
167170
Staking: Staking{
168171
VoteWeightCalConsts: VoteWeightCalConsts{
@@ -493,6 +496,16 @@ type (
493496
ProductivityThreshold uint64 `yaml:"productivityThreshold"`
494497
// WakeBlockRewardStr is the block reward amount, in decimal string format, effective from the Wake height
495498
WakeBlockRewardStr string `yaml:"wakeBlockRewardStr"`
499+
// EpochsPerRewardEra is the number of epochs per IIP-59 voter reward era. Era boundaries are epochs where
500+
// epochNum%EpochsPerRewardEra==0. Only consulted when the IIP-59 voter reward distribution feature is active.
501+
EpochsPerRewardEra uint64 `yaml:"epochsPerRewardEra"`
502+
// VoterBudgetPerBlock is the maximum number of voters credited per block during the era-boundary chunked
503+
// credit path (IIP-59 Phase 2). 0 falls back to a single-block drain, preserving pre-IIP-59 behavior for
504+
// tests that never touch the field.
505+
VoterBudgetPerBlock uint64 `yaml:"voterBudgetPerBlock"`
506+
// CompoundBatchSize is the maximum number of voters processed per block by the Phase 3 background compound
507+
// sweep. 0 disables the sweep (voters compound lazily at Claim time only).
508+
CompoundBatchSize uint64 `yaml:"compoundBatchSize"`
496509
}
497510
// Staking contains the configs for staking protocol
498511
Staking struct {

blockchain/genesis/genesis_test.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,12 @@ func TestGenesisMapEmpty(t *testing.T) {
130130
// Therefore, the default map must be empty.
131131
r.Empty(g.Account.InitBalanceMap, "InitBalanceMap should be empty")
132132
}
133+
134+
func TestIIP59EraDefaults(t *testing.T) {
135+
r := require.New(t)
136+
cfg, err := New("")
137+
r.NoError(err)
138+
r.Equal(uint64(24), cfg.Rewarding.EpochsPerRewardEra)
139+
r.Equal(uint64(2000), cfg.Rewarding.VoterBudgetPerBlock)
140+
r.Equal(uint64(500), cfg.Rewarding.CompoundBatchSize)
141+
}

0 commit comments

Comments
 (0)