Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix initial rewards rate calculation #1908

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions config/consensus.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ type ConsensusParams struct {
// EnableAssetCloseAmount adds an extra field to the ApplyData. The field contains the amount of the remaining
// asset that were sent to the close-to address.
EnableAssetCloseAmount bool

// update the initial rewards rate calculation to take the reward pool minimum balance into account
InitialRewardsRateCalculation bool
}

// PaysetCommitType enumerates possible ways for the block header to commit to
Expand Down Expand Up @@ -868,6 +871,8 @@ func initConsensusProtocols() {
vFuture.CompactCertWeightThreshold = (1 << 32) * 30 / 100
vFuture.CompactCertSecKQ = 128

// enable the InitialRewardsRateCalculation fix
vFuture.InitialRewardsRateCalculation = true
// Enable transaction Merkle tree.
vFuture.PaysetCommit = PaysetCommitMerkle

Expand Down
54 changes: 54 additions & 0 deletions data/bookkeeping/block_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,3 +375,57 @@ func TestDecodeMalformedSignedTxn(t *testing.T) {
_, _, err = b.DecodeSignedTxn(txib2)
require.Error(t, err)
}

// TestInitialRewardsRateCalculation perform positive and negative testing for the InitialRewardsRateCalculation fix by
// running the rounds in the same way eval() is executing them over RewardsRateRefreshInterval rounds.
func TestInitialRewardsRateCalculation(t *testing.T) {
consensusParams := config.Consensus[protocol.ConsensusCurrentVersion]

runTest := func() bool {
incentivePoolBalance := uint64(125000000000000)
totalRewardUnits := uint64(10000000000)
require.GreaterOrEqual(t, incentivePoolBalance, consensusParams.MinBalance)

curRewardsState := RewardsState{
RewardsLevel: 0,
RewardsResidue: 0,
RewardsRecalculationRound: basics.Round(consensusParams.RewardsRateRefreshInterval),
}
if consensusParams.InitialRewardsRateCalculation {
curRewardsState.RewardsRate = basics.SubSaturate(incentivePoolBalance, consensusParams.MinBalance) / uint64(consensusParams.RewardsRateRefreshInterval)
} else {
curRewardsState.RewardsRate = incentivePoolBalance / uint64(consensusParams.RewardsRateRefreshInterval)
}
for rnd := 1; rnd < int(consensusParams.RewardsRateRefreshInterval+2); rnd++ {
nextRewardState := curRewardsState.NextRewardsState(basics.Round(rnd), consensusParams, basics.MicroAlgos{Raw: incentivePoolBalance}, totalRewardUnits)
// adjust the incentive pool balance
var ot basics.OverflowTracker

// get number of rewards per unit
rewardsPerUnit := ot.Sub(nextRewardState.RewardsLevel, curRewardsState.RewardsLevel)
require.False(t, ot.Overflowed)

// subtract the total dispersed funds from the pool balance
incentivePoolBalance = ot.Sub(incentivePoolBalance, ot.Mul(totalRewardUnits, rewardsPerUnit))
require.False(t, ot.Overflowed)

// make sure the pool retain at least the min balance
ot.Sub(incentivePoolBalance, consensusParams.MinBalance)
if ot.Overflowed {
return false
}

// prepare for the next iteration
curRewardsState = nextRewardState
}
return true
}

// test expected failuire
consensusParams.InitialRewardsRateCalculation = false
require.False(t, runTest())

// test expected success
consensusParams.InitialRewardsRateCalculation = true
require.True(t, runTest())
}
7 changes: 6 additions & 1 deletion data/ledger.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,16 @@ func makeGenesisBlock(proto protocol.ConsensusVersion, genesisBal GenesisBalance
FeeSink: genesisBal.feeSink,
RewardsPool: genesisBal.rewardsPool,
RewardsLevel: 0,
RewardsRate: incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval),
RewardsResidue: 0,
RewardsRecalculationRound: basics.Round(params.RewardsRateRefreshInterval),
}

if params.InitialRewardsRateCalculation {
genesisRewardsState.RewardsRate = basics.SubSaturate(incentivePoolBalanceAtGenesis.Raw, params.MinBalance) / uint64(params.RewardsRateRefreshInterval)
} else {
genesisRewardsState.RewardsRate = incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval)
}

genesisProtoState := bookkeeping.UpgradeState{
CurrentProtocol: proto,
}
Expand Down
7 changes: 6 additions & 1 deletion ledger/ledger_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ func testGenerateInitState(tb testing.TB, proto protocol.ConsensusVersion) (gene
initAccounts[sinkAddr] = basics.MakeAccountData(basics.NotParticipating, basics.MicroAlgos{Raw: 7654321})

incentivePoolBalanceAtGenesis := initAccounts[poolAddr].MicroAlgos
initialRewardsPerRound := incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval)
var initialRewardsPerRound uint64
if params.InitialRewardsRateCalculation {
initialRewardsPerRound = basics.SubSaturate(incentivePoolBalanceAtGenesis.Raw, params.MinBalance) / uint64(params.RewardsRateRefreshInterval)
} else {
initialRewardsPerRound = incentivePoolBalanceAtGenesis.Raw / uint64(params.RewardsRateRefreshInterval)
}

initBlock := bookkeeping.Block{
BlockHeader: bookkeeping.BlockHeader{
Expand Down