Skip to content

Commit

Permalink
Fix initial rewards rate calculation (algorand#1908)
Browse files Browse the repository at this point in the history
This is a leftover missed when implementing the `PendingResidueRewards` fix. The initial `RewardsRate` calculation also need to take into account the `MinBalance`, and avoid including these funds in the `RewardsRate`.

The workaround is to fund the rewards pool within the first 500,000 rounds.
  • Loading branch information
tsachiherman authored Feb 16, 2021
1 parent 3740af8 commit ed49367
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 2 deletions.
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

0 comments on commit ed49367

Please sign in to comment.