Implement the new monetary policy (#1892)

* Remove unused functions, constants, and variables.

* Rename maxSubsidy to baseSubsidy.

* Remove unused parameters from CalcBlockSubsidy.

* Remove link to old monetary policy.

* If a block's DAA score is smaller than half a year, it should have a base subsidy.

* Fix merge errors.

* Fix more merge errors.

* Add DeflationaryPhaseBaseSubsidy to the params.

* Implement TestCalcDeflationaryPeriodBlockSubsidy.

* Implement calcDeflationaryPeriodBlockSubsidy naively.

* Implement calcDeflationaryPeriodBlockSubsidy not naively.

* Adjust the subsidy based on target block rate.

* Fix deflationaryPhaseDaaScore in TestCalcDeflationaryPeriodBlockSubsidy.

* Explain how secondsPerMonth is calculated.

* Don't adjust the subsidy based on the target block rate.

* Update defaultDeflationaryPhaseDaaScore and add an explanation.

* Use a pre-calculated table for subsidy per month

* Make the generation function fail if base subsidy is changed

* go fmt

* Use test logger for printing + simplify print loop

Co-authored-by: msutton <mikisiton2@gmail.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
This commit is contained in:
stasatdaglabs 2021-12-29 20:49:20 +02:00 committed by GitHub
parent 7c1cddff11
commit 0bdd19136f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 224 additions and 328 deletions

View File

@ -203,14 +203,11 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
coinbaseManager := coinbasemanager.New( coinbaseManager := coinbasemanager.New(
dbManager, dbManager,
config.SubsidyGenesisReward, config.SubsidyGenesisReward,
config.MinSubsidy, config.PreDeflationaryPhaseBaseSubsidy,
config.MaxSubsidy,
config.SubsidyPastRewardMultiplier,
config.SubsidyMergeSetRewardMultiplier,
config.CoinbasePayloadScriptPublicKeyMaxLength, config.CoinbasePayloadScriptPublicKeyMaxLength,
config.GenesisHash, config.GenesisHash,
config.FixedSubsidySwitchPruningPointInterval, config.DeflationaryPhaseDaaScore,
config.FixedSubsidySwitchHashRateThreshold, config.DeflationaryPhaseBaseSubsidy,
dagTraversalManager, dagTraversalManager,
ghostdagDataStore, ghostdagDataStore,
acceptanceDataStore, acceptanceDataStore,

View File

@ -7,6 +7,6 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
type CoinbaseManager interface { type CoinbaseManager interface {
ExpectedCoinbaseTransaction(stagingArea *StagingArea, blockHash *externalapi.DomainHash, ExpectedCoinbaseTransaction(stagingArea *StagingArea, blockHash *externalapi.DomainHash,
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error)
CalcBlockSubsidy(blockHash *externalapi.DomainHash) (uint64, error) CalcBlockSubsidy(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint64, error)
ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction) (blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error) ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction) (blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error)
} }

View File

@ -178,7 +178,7 @@ func (v *blockValidator) checkCoinbaseSubsidy(
return err return err
} }
expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(blockHash) expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(stagingArea, blockHash)
if err != nil { if err != nil {
return err return err
} }

View File

@ -1,86 +0,0 @@
package coinbasemanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"math/big"
)
func (c *coinbaseManager) isBlockRewardFixed(stagingArea *model.StagingArea, blockPruningPoint *externalapi.DomainHash) (bool, error) {
blockPruningPointIndex, found, err := c.findPruningPointIndex(stagingArea, blockPruningPoint)
if err != nil {
return false, err
}
// The given `pruningPointBlock` may only not be found under one circumstance:
// we're currently in the process of building the next pruning point. As such,
// we must manually set highIndex to currentIndex + 1 because the next pruning
// point is not yet stored in the database
highPruningPointIndex := blockPruningPointIndex
highPruningPointHash := blockPruningPoint
if !found {
currentPruningPointIndex, err := c.pruningStore.CurrentPruningPointIndex(c.databaseContext, stagingArea)
if err != nil {
return false, err
}
highPruningPointIndex = currentPruningPointIndex + 1
}
for {
if highPruningPointIndex <= c.fixedSubsidySwitchPruningPointInterval {
break
}
lowPruningPointIndex := highPruningPointIndex - c.fixedSubsidySwitchPruningPointInterval
lowPruningPointHash, err := c.pruningStore.PruningPointByIndex(c.databaseContext, stagingArea, lowPruningPointIndex)
if err != nil {
return false, err
}
highPruningPointHeader, err := c.blockHeaderStore.BlockHeader(c.databaseContext, stagingArea, highPruningPointHash)
if err != nil {
return false, err
}
lowPruningPointHeader, err := c.blockHeaderStore.BlockHeader(c.databaseContext, stagingArea, lowPruningPointHash)
if err != nil {
return false, err
}
blueWorkDifference := new(big.Int).Sub(highPruningPointHeader.BlueWork(), lowPruningPointHeader.BlueWork())
blueScoreDifference := new(big.Int).SetUint64(highPruningPointHeader.BlueScore() - lowPruningPointHeader.BlueScore())
estimatedAverageHashRate := new(big.Int).Div(blueWorkDifference, blueScoreDifference)
if estimatedAverageHashRate.Cmp(c.fixedSubsidySwitchHashRateThreshold) >= 0 {
return true, nil
}
highPruningPointIndex--
highPruningPointHash, err = c.pruningStore.PruningPointByIndex(c.databaseContext, stagingArea, highPruningPointIndex)
if err != nil {
return false, err
}
}
return false, nil
}
func (c *coinbaseManager) findPruningPointIndex(stagingArea *model.StagingArea, pruningPointHash *externalapi.DomainHash) (uint64, bool, error) {
currentPruningPointHash, err := c.pruningStore.PruningPoint(c.databaseContext, stagingArea)
if err != nil {
return 0, false, err
}
currentPruningPointIndex, err := c.pruningStore.CurrentPruningPointIndex(c.databaseContext, stagingArea)
if err != nil {
return 0, false, err
}
for !currentPruningPointHash.Equal(pruningPointHash) && currentPruningPointIndex > 0 {
currentPruningPointIndex--
currentPruningPointHash, err = c.pruningStore.PruningPointByIndex(c.databaseContext, stagingArea, currentPruningPointIndex)
if err != nil {
return 0, false, err
}
}
if currentPruningPointIndex == 0 && !currentPruningPointHash.Equal(pruningPointHash) {
return 0, false, nil
}
return currentPruningPointIndex, true, nil
}

View File

@ -1,90 +0,0 @@
package coinbasemanager_test
import (
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/kaspanet/kaspad/util/difficulty"
"testing"
"time"
)
func TestBlockRewardSwitch(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
// Set the pruning depth to 10
consensusConfig.MergeSetSizeLimit = 1
consensusConfig.K = 1
consensusConfig.FinalityDuration = 1 * time.Second
consensusConfig.TargetTimePerBlock = 1 * time.Second
// Disable difficulty adjustment so that we could reason about blue work
consensusConfig.DisableDifficultyAdjustment = true
// Disable pruning so that we could have access to all the blocks
consensusConfig.IsArchival = true
// Set the interval to 10
consensusConfig.FixedSubsidySwitchPruningPointInterval = 10
// Set the hash rate difference such that the switch would trigger exactly
// on the `FixedSubsidySwitchPruningPointInterval + 1`th pruning point
workToAcceptGenesis := difficulty.CalcWork(consensusConfig.GenesisBlock.Header.Bits())
consensusConfig.FixedSubsidySwitchHashRateThreshold = workToAcceptGenesis
// Set the min, max, and post-switch subsidies to values that would make it
// easy to tell whether the switch happened
consensusConfig.MinSubsidy = 2 * constants.SompiPerKaspa
consensusConfig.MaxSubsidy = 2 * constants.SompiPerKaspa
consensusConfig.SubsidyGenesisReward = 1 * constants.SompiPerKaspa
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestBlockRewardSwitch")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
// Make the pruning point move FixedSubsidySwitchPruningPointInterval times
tipHash := consensusConfig.GenesisHash
for i := uint64(0); i < consensusConfig.PruningDepth()+consensusConfig.FixedSubsidySwitchPruningPointInterval; i++ {
addedBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}
tipHash = addedBlockHash
}
// We expect to see `FixedSubsidySwitchPruningPointInterval` pruning points + the genesis
pruningPointHeaders, err := tc.PruningPointHeaders()
if err != nil {
t.Fatalf("PruningPointHeaders: %+v", pruningPointHeaders)
}
expectedPruningPointHeaderAmount := consensusConfig.FixedSubsidySwitchPruningPointInterval + 1
if uint64(len(pruningPointHeaders)) != expectedPruningPointHeaderAmount {
t.Fatalf("Unexpected amount of pruning point headers. "+
"Want: %d, got: %d", expectedPruningPointHeaderAmount, len(pruningPointHeaders))
}
// Make sure that all the headers thus far had a non-fixed subsidies
// Note that we skip the genesis, since that always has the post-switch
// value
for _, pruningPointHeader := range pruningPointHeaders[1:] {
pruningPointHash := consensushashing.HeaderHash(pruningPointHeader)
pruningPoint, err := tc.GetBlock(pruningPointHash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
pruningPointCoinbase := pruningPoint.Transactions[transactionhelper.CoinbaseTransactionIndex]
_, _, subsidy, err := tc.CoinbaseManager().ExtractCoinbaseDataBlueScoreAndSubsidy(pruningPointCoinbase)
if err != nil {
t.Fatalf("ExtractCoinbaseDataBlueScoreAndSubsidy: %+v", err)
}
if subsidy != consensusConfig.MinSubsidy {
t.Fatalf("Subsidy has unexpected value. Want: %d, got: %d", consensusConfig.MinSubsidy, subsidy)
}
}
})
}

View File

@ -1,9 +1,6 @@
package coinbasemanager package coinbasemanager
import ( import (
"encoding/binary"
"math/big"
"github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/constants"
@ -12,18 +9,16 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
"github.com/kaspanet/kaspad/infrastructure/db/database" "github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/pkg/errors" "github.com/pkg/errors"
"math"
) )
type coinbaseManager struct { type coinbaseManager struct {
subsidyGenesisReward uint64 subsidyGenesisReward uint64
minSubsidy uint64 preDeflationaryPhaseBaseSubsidy uint64
maxSubsidy uint64
subsidyPastRewardMultiplier *big.Rat
subsidyMergeSetRewardMultiplier *big.Rat
coinbasePayloadScriptPublicKeyMaxLength uint8 coinbasePayloadScriptPublicKeyMaxLength uint8
genesisHash *externalapi.DomainHash genesisHash *externalapi.DomainHash
fixedSubsidySwitchPruningPointInterval uint64 deflationaryPhaseDaaScore uint64
fixedSubsidySwitchHashRateThreshold *big.Int deflationaryPhaseBaseSubsidy uint64
databaseContext model.DBReader databaseContext model.DBReader
dagTraversalManager model.DAGTraversalManager dagTraversalManager model.DAGTraversalManager
@ -84,7 +79,7 @@ func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.Staging
txOuts = append(txOuts, txOut) txOuts = append(txOuts, txOut)
} }
subsidy, err := c.CalcBlockSubsidy(blockHash) subsidy, err := c.CalcBlockSubsidy(stagingArea, blockHash)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -182,97 +177,68 @@ func acceptanceDataFromArrayToMap(acceptanceData externalapi.AcceptanceData) map
// should have. This is mainly used for determining how much the coinbase for // should have. This is mainly used for determining how much the coinbase for
// newly generated blocks awards as well as validating the coinbase for blocks // newly generated blocks awards as well as validating the coinbase for blocks
// has the expected value. // has the expected value.
// func (c *coinbaseManager) CalcBlockSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint64, error) {
// Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6
func (c *coinbaseManager) CalcBlockSubsidy(blockHash *externalapi.DomainHash) (uint64, error) {
if blockHash.Equal(c.genesisHash) { if blockHash.Equal(c.genesisHash) {
return c.subsidyGenesisReward, nil return c.subsidyGenesisReward, nil
} }
blockDaaScore, err := c.daaBlocksStore.DAAScore(c.databaseContext, stagingArea, blockHash)
return c.maxSubsidy, nil
}
func (c *coinbaseManager) calculateAveragePastSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*big.Rat, error) {
const subsidyPastWindowSize = 100
blockWindow, err := c.dagTraversalManager.BlockWindow(stagingArea, blockHash, subsidyPastWindowSize)
if err != nil {
return nil, err
}
if len(blockWindow) == 0 {
return new(big.Rat).SetFrac64(int64(c.subsidyGenesisReward), 1), nil
}
pastBlocks, err := c.blockStore.Blocks(c.databaseContext, stagingArea, blockWindow)
if err != nil {
return nil, err
}
pastBlockSubsidySum := int64(0)
for _, pastBlock := range pastBlocks {
coinbaseTransaction := pastBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
_, _, pastBlockSubsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTransaction)
if err != nil {
return nil, err
}
pastBlockSubsidySum += int64(pastBlockSubsidy)
}
return big.NewRat(pastBlockSubsidySum, int64(len(blockWindow))), nil
}
func (c *coinbaseManager) calculateMergeSetSubsidySum(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (*big.Rat, error) {
ghostdagData, err := c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, true)
if !database.IsNotFoundError(err) && err != nil {
return nil, err
}
// If there's ghostdag data with trusted data we prefer it because we need the original merge set non-pruned merge set.
if database.IsNotFoundError(err) {
ghostdagData, err = c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, false)
if err != nil {
return nil, err
}
}
mergeSet := append(ghostdagData.MergeSetBlues(), ghostdagData.MergeSetReds()...)
mergeSetBlocks, err := c.blockStore.Blocks(c.databaseContext, stagingArea, mergeSet)
if err != nil {
return nil, err
}
mergeSetSubsidySum := int64(0)
for _, mergeSetBlock := range mergeSetBlocks {
coinbaseTransaction := mergeSetBlock.Transactions[transactionhelper.CoinbaseTransactionIndex]
_, _, mergeSetBlockSubsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTransaction)
if err != nil {
return nil, err
}
mergeSetSubsidySum += int64(mergeSetBlockSubsidy)
}
return big.NewRat(mergeSetSubsidySum, 1), nil
}
func (c *coinbaseManager) calculateSubsidyRandomVariable(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (int64, error) {
ghostdagData, err := c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, false)
if err != nil { if err != nil {
return 0, err return 0, err
} }
selectedParentHash := ghostdagData.SelectedParent() if blockDaaScore < c.deflationaryPhaseDaaScore {
if selectedParentHash == nil { return c.preDeflationaryPhaseBaseSubsidy, nil
return 0, nil
} }
const binomialSteps = 10 blockSubsidy := c.calcDeflationaryPeriodBlockSubsidy(blockDaaScore)
binomialSum := int64(0) return blockSubsidy, nil
}
// The first two bytes of a hash are a good deterministic source func (c *coinbaseManager) calcDeflationaryPeriodBlockSubsidy(blockDaaScore uint64) uint64 {
// of randomness, so we use that instead of any rand implementation // We define a year as 365.25 days and a month as 365.25 / 12 = 30.4375
firstTwoBytes := binary.LittleEndian.Uint16(selectedParentHash.ByteSlice()[:2]) // secondsPerMonth = 30.4375 * 24 * 60 * 60
for i := 0; i < binomialSteps; i++ { const secondsPerMonth = 2629800
step := firstTwoBytes & 1 // Note that this calculation implicitly assumes that block per second = 1 (by assuming daa score diff is in second units).
firstTwoBytes >>= 1 monthsSinceDeflationaryPhaseStarted := (blockDaaScore - c.deflationaryPhaseDaaScore) / secondsPerMonth
binomialSum += int64(step) // Return the pre-calculated value from subsidy-per-month table
return c.getDeflationaryPeriodBlockSubsidyFromTable(monthsSinceDeflationaryPhaseStarted)
}
/*
This table was pre-calculated by calling `calcDeflationaryPeriodBlockSubsidyFloatCalc` for all months until reaching 0 subsidy.
To regenerate this table, run `TestBuildSubsidyTable` in coinbasemanager_test.go (note the `deflationaryPhaseBaseSubsidy` therein)
*/
var subsidyByDeflationaryMonthTable = []uint64{
44000000000, 41530469757, 39199543598, 36999442271, 34922823143, 32962755691, 31112698372, 29366476791, 27718263097, 26162556530, 24694165062, 23308188075, 22000000000, 20765234878, 19599771799, 18499721135, 17461411571, 16481377845, 15556349186, 14683238395, 13859131548, 13081278265, 12347082531, 11654094037, 11000000000,
10382617439, 9799885899, 9249860567, 8730705785, 8240688922, 7778174593, 7341619197, 6929565774, 6540639132, 6173541265, 5827047018, 5500000000, 5191308719, 4899942949, 4624930283, 4365352892, 4120344461, 3889087296, 3670809598, 3464782887, 3270319566, 3086770632, 2913523509, 2750000000, 2595654359,
2449971474, 2312465141, 2182676446, 2060172230, 1944543648, 1835404799, 1732391443, 1635159783, 1543385316, 1456761754, 1375000000, 1297827179, 1224985737, 1156232570, 1091338223, 1030086115, 972271824, 917702399, 866195721, 817579891, 771692658, 728380877, 687500000, 648913589, 612492868,
578116285, 545669111, 515043057, 486135912, 458851199, 433097860, 408789945, 385846329, 364190438, 343750000, 324456794, 306246434, 289058142, 272834555, 257521528, 243067956, 229425599, 216548930, 204394972, 192923164, 182095219, 171875000, 162228397, 153123217, 144529071,
136417277, 128760764, 121533978, 114712799, 108274465, 102197486, 96461582, 91047609, 85937500, 81114198, 76561608, 72264535, 68208638, 64380382, 60766989, 57356399, 54137232, 51098743, 48230791, 45523804, 42968750, 40557099, 38280804, 36132267, 34104319,
32190191, 30383494, 28678199, 27068616, 25549371, 24115395, 22761902, 21484375, 20278549, 19140402, 18066133, 17052159, 16095095, 15191747, 14339099, 13534308, 12774685, 12057697, 11380951, 10742187, 10139274, 9570201, 9033066, 8526079, 8047547,
7595873, 7169549, 6767154, 6387342, 6028848, 5690475, 5371093, 5069637, 4785100, 4516533, 4263039, 4023773, 3797936, 3584774, 3383577, 3193671, 3014424, 2845237, 2685546, 2534818, 2392550, 2258266, 2131519, 2011886, 1898968,
1792387, 1691788, 1596835, 1507212, 1422618, 1342773, 1267409, 1196275, 1129133, 1065759, 1005943, 949484, 896193, 845894, 798417, 753606, 711309, 671386, 633704, 598137, 564566, 532879, 502971, 474742, 448096,
422947, 399208, 376803, 355654, 335693, 316852, 299068, 282283, 266439, 251485, 237371, 224048, 211473, 199604, 188401, 177827, 167846, 158426, 149534, 141141, 133219, 125742, 118685, 112024, 105736,
99802, 94200, 88913, 83923, 79213, 74767, 70570, 66609, 62871, 59342, 56012, 52868, 49901, 47100, 44456, 41961, 39606, 37383, 35285, 33304, 31435, 29671, 28006, 26434, 24950,
23550, 22228, 20980, 19803, 18691, 17642, 16652, 15717, 14835, 14003, 13217, 12475, 11775, 11114, 10490, 9901, 9345, 8821, 8326, 7858, 7417, 7001, 6608, 6237, 5887,
5557, 5245, 4950, 4672, 4410, 4163, 3929, 3708, 3500, 3304, 3118, 2943, 2778, 2622, 2475, 2336, 2205, 2081, 1964, 1854, 1750, 1652, 1559, 1471, 1389,
1311, 1237, 1168, 1102, 1040, 982, 927, 875, 826, 779, 735, 694, 655, 618, 584, 551, 520, 491, 463, 437, 413, 389, 367, 347, 327,
309, 292, 275, 260, 245, 231, 218, 206, 194, 183, 173, 163, 154, 146, 137, 130, 122, 115, 109, 103, 97, 91, 86, 81, 77,
73, 68, 65, 61, 57, 54, 51, 48, 45, 43, 40, 38, 36, 34, 32, 30, 28, 27, 25, 24, 22, 21, 20, 19, 18,
17, 16, 15, 14, 13, 12, 12, 11, 10, 10, 9, 9, 8, 8, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4,
4, 3, 3, 3, 3, 3, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0,
}
func (c *coinbaseManager) getDeflationaryPeriodBlockSubsidyFromTable(month uint64) uint64 {
if month >= uint64(len(subsidyByDeflationaryMonthTable)) {
month = uint64(len(subsidyByDeflationaryMonthTable) - 1)
} }
return binomialSum - (binomialSteps / 2), nil return subsidyByDeflationaryMonthTable[month]
}
func (c *coinbaseManager) calcDeflationaryPeriodBlockSubsidyFloatCalc(month uint64) uint64 {
baseSubsidy := c.deflationaryPhaseBaseSubsidy
subsidy := float64(baseSubsidy) / math.Pow(2, float64(month)/12)
return uint64(subsidy)
} }
func (c *coinbaseManager) calcMergedBlockReward(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash, func (c *coinbaseManager) calcMergedBlockReward(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
@ -312,14 +278,11 @@ func New(
databaseContext model.DBReader, databaseContext model.DBReader,
subsidyGenesisReward uint64, subsidyGenesisReward uint64,
minSubsidy uint64, preDeflationaryPhaseBaseSubsidy uint64,
maxSubsidy uint64,
subsidyPastRewardMultiplier *big.Rat,
subsidyMergeSetRewardMultiplier *big.Rat,
coinbasePayloadScriptPublicKeyMaxLength uint8, coinbasePayloadScriptPublicKeyMaxLength uint8,
genesisHash *externalapi.DomainHash, genesisHash *externalapi.DomainHash,
fixedSubsidySwitchPruningPointInterval uint64, deflationaryPhaseDaaScore uint64,
fixedSubsidySwitchHashRateThreshold *big.Int, deflationaryPhaseBaseSubsidy uint64,
dagTraversalManager model.DAGTraversalManager, dagTraversalManager model.DAGTraversalManager,
ghostdagDataStore model.GHOSTDAGDataStore, ghostdagDataStore model.GHOSTDAGDataStore,
@ -333,14 +296,11 @@ func New(
databaseContext: databaseContext, databaseContext: databaseContext,
subsidyGenesisReward: subsidyGenesisReward, subsidyGenesisReward: subsidyGenesisReward,
minSubsidy: minSubsidy, preDeflationaryPhaseBaseSubsidy: preDeflationaryPhaseBaseSubsidy,
maxSubsidy: maxSubsidy,
subsidyPastRewardMultiplier: subsidyPastRewardMultiplier,
subsidyMergeSetRewardMultiplier: subsidyMergeSetRewardMultiplier,
coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength, coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength,
genesisHash: genesisHash, genesisHash: genesisHash,
fixedSubsidySwitchPruningPointInterval: fixedSubsidySwitchPruningPointInterval, deflationaryPhaseDaaScore: deflationaryPhaseDaaScore,
fixedSubsidySwitchHashRateThreshold: fixedSubsidySwitchHashRateThreshold, deflationaryPhaseBaseSubsidy: deflationaryPhaseBaseSubsidy,
dagTraversalManager: dagTraversalManager, dagTraversalManager: dagTraversalManager,
ghostdagDataStore: ghostdagDataStore, ghostdagDataStore: ghostdagDataStore,

View File

@ -0,0 +1,126 @@
package coinbasemanager
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/dagconfig"
"strconv"
"testing"
)
func TestCalcDeflationaryPeriodBlockSubsidy(t *testing.T) {
const secondsPerMonth = 2629800
const secondsPerHalving = secondsPerMonth * 12
const deflationaryPhaseDaaScore = secondsPerMonth * 6
const deflationaryPhaseBaseSubsidy = 440 * constants.SompiPerKaspa
coinbaseManagerInterface := New(
nil,
0,
0,
0,
&externalapi.DomainHash{},
deflationaryPhaseDaaScore,
deflationaryPhaseBaseSubsidy,
nil,
nil,
nil,
nil,
nil,
nil,
nil)
coinbaseManagerInstance := coinbaseManagerInterface.(*coinbaseManager)
tests := []struct {
name string
blockDaaScore uint64
expectedBlockSubsidy uint64
}{
{
name: "start of deflationary phase",
blockDaaScore: deflationaryPhaseDaaScore,
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy,
},
{
name: "after one halving",
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving,
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 2,
},
{
name: "after two halvings",
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*2,
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 4,
},
{
name: "after five halvings",
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*5,
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 32,
},
{
name: "after 32 halvings",
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*32,
expectedBlockSubsidy: deflationaryPhaseBaseSubsidy / 4294967296,
},
{
name: "just before subsidy depleted",
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*35,
expectedBlockSubsidy: 1,
},
{
name: "after subsidy depleted",
blockDaaScore: deflationaryPhaseDaaScore + secondsPerHalving*36,
expectedBlockSubsidy: 0,
},
}
for _, test := range tests {
blockSubsidy := coinbaseManagerInstance.calcDeflationaryPeriodBlockSubsidy(test.blockDaaScore)
if blockSubsidy != test.expectedBlockSubsidy {
t.Errorf("TestCalcDeflationaryPeriodBlockSubsidy: test '%s' failed. Want: %d, got: %d",
test.name, test.expectedBlockSubsidy, blockSubsidy)
}
}
}
func TestBuildSubsidyTable(t *testing.T) {
deflationaryPhaseBaseSubsidy := dagconfig.MainnetParams.DeflationaryPhaseBaseSubsidy
if deflationaryPhaseBaseSubsidy != 440*constants.SompiPerKaspa {
t.Errorf("TestBuildSubsidyTable: table generation function was not updated to reflect "+
"the new base subsidy %d. Please fix the constant above and replace subsidyByDeflationaryMonthTable "+
"in coinbasemanager.go with the printed table", deflationaryPhaseBaseSubsidy)
}
coinbaseManagerInterface := New(
nil,
0,
0,
0,
&externalapi.DomainHash{},
0,
deflationaryPhaseBaseSubsidy,
nil,
nil,
nil,
nil,
nil,
nil,
nil)
coinbaseManagerInstance := coinbaseManagerInterface.(*coinbaseManager)
var subsidyTable []uint64
for M := uint64(0); ; M++ {
subsidy := coinbaseManagerInstance.calcDeflationaryPeriodBlockSubsidyFloatCalc(M)
subsidyTable = append(subsidyTable, subsidy)
if subsidy == 0 {
break
}
}
tableStr := "\n{\t"
for i := 0; i < len(subsidyTable); i++ {
tableStr += strconv.FormatUint(subsidyTable[i], 10) + ", "
if (i+1)%25 == 0 {
tableStr += "\n\t"
}
}
tableStr += "\n}"
t.Logf(tableStr)
}

View File

@ -46,13 +46,11 @@ const (
// Should be at least an order of magnitude smaller than defaultFinalityDuration/defaultTargetTimePerBlock. // Should be at least an order of magnitude smaller than defaultFinalityDuration/defaultTargetTimePerBlock.
// (Higher values make pruning attacks easier by a constant, lower values make merging after a split or a spike // (Higher values make pruning attacks easier by a constant, lower values make merging after a split or a spike
// in block take longer) // in block take longer)
defaultMergeSetSizeLimit = defaultGHOSTDAGK * 10 defaultMergeSetSizeLimit = defaultGHOSTDAGK * 10
defaultSubsidyGenesisReward = 1 * constants.SompiPerKaspa defaultSubsidyGenesisReward = 1 * constants.SompiPerKaspa
defaultMinSubsidy = 1 * constants.SompiPerKaspa defaultPreDeflationaryPhaseBaseSubsidy = 500 * constants.SompiPerKaspa
defaultMaxSubsidy = 500 * constants.SompiPerKaspa defaultDeflationaryPhaseBaseSubsidy = 440 * constants.SompiPerKaspa
defaultBaseSubsidy = 50 * constants.SompiPerKaspa defaultCoinbasePayloadScriptPublicKeyMaxLength = 150
defaultFixedSubsidySwitchPruningPointInterval uint64 = 7
defaultCoinbasePayloadScriptPublicKeyMaxLength = 150
// defaultDifficultyAdjustmentWindowSize is the number of blocks in a block's past used to calculate its difficulty // defaultDifficultyAdjustmentWindowSize is the number of blocks in a block's past used to calculate its difficulty
// target. // target.
// The DAA should take the median of 2640 blocks, so in order to do that we need 2641 window size. // The DAA should take the median of 2640 blocks, so in order to do that we need 2641 window size.
@ -76,4 +74,12 @@ const (
defaultTargetTimePerBlock = 1 * time.Second defaultTargetTimePerBlock = 1 * time.Second
defaultPruningProofM = 1000 defaultPruningProofM = 1000
// defaultDeflationaryPhaseDaaScore is the DAA score after which the pre-deflationary period
// switches to the deflationary period. This number is calculated as follows:
// We define a year as 365.25 days
// Half a year in seconds = 365.25 / 2 * 24 * 60 * 60 = 15778800
// The network was down for three days shortly after launch
// Three days in seconds = 3 * 24 * 60 * 60 = 259200
defaultDeflationaryPhaseDaaScore = 15778800 - 259200
) )

View File

@ -92,10 +92,8 @@ type Params struct {
// SubsidyPastRewardMultiplier are part of the block subsidy equation. // SubsidyPastRewardMultiplier are part of the block subsidy equation.
// Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6 // Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6
SubsidyGenesisReward uint64 SubsidyGenesisReward uint64
MinSubsidy uint64 PreDeflationaryPhaseBaseSubsidy uint64
MaxSubsidy uint64 DeflationaryPhaseBaseSubsidy uint64
SubsidyPastRewardMultiplier *big.Rat
SubsidyMergeSetRewardMultiplier *big.Rat
// TargetTimePerBlock is the desired amount of time to generate each // TargetTimePerBlock is the desired amount of time to generate each
// block. // block.
@ -180,12 +178,9 @@ type Params struct {
// PruningProofM is the 'm' constant in the pruning proof. For more details see: https://github.com/kaspanet/research/issues/3 // PruningProofM is the 'm' constant in the pruning proof. For more details see: https://github.com/kaspanet/research/issues/3
PruningProofM uint64 PruningProofM uint64
// BaseSubsidy is the starting subsidy amount for mined blocks. // DeflationaryPhaseDaaScore is the DAA score after which the monetary policy switches
BaseSubsidy uint64 // to its deflationary phase
DeflationaryPhaseDaaScore uint64
FixedSubsidySwitchPruningPointInterval uint64
FixedSubsidySwitchHashRateThreshold *big.Int
DisallowDirectBlocksOnTopOfGenesis bool DisallowDirectBlocksOnTopOfGenesis bool
@ -243,10 +238,8 @@ var MainnetParams = Params{
PowMax: mainPowMax, PowMax: mainPowMax,
BlockCoinbaseMaturity: 100, BlockCoinbaseMaturity: 100,
SubsidyGenesisReward: defaultSubsidyGenesisReward, SubsidyGenesisReward: defaultSubsidyGenesisReward,
MinSubsidy: defaultMinSubsidy, PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
MaxSubsidy: defaultMaxSubsidy, DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
TargetTimePerBlock: defaultTargetTimePerBlock, TargetTimePerBlock: defaultTargetTimePerBlock,
FinalityDuration: defaultFinalityDuration, FinalityDuration: defaultFinalityDuration,
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
@ -286,8 +279,7 @@ var MainnetParams = Params{
MergeSetSizeLimit: defaultMergeSetSizeLimit, MergeSetSizeLimit: defaultMergeSetSizeLimit,
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
PruningProofM: defaultPruningProofM, PruningProofM: defaultPruningProofM,
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
DisallowDirectBlocksOnTopOfGenesis: true, DisallowDirectBlocksOnTopOfGenesis: true,
} }
@ -306,10 +298,8 @@ var TestnetParams = Params{
PowMax: testnetPowMax, PowMax: testnetPowMax,
BlockCoinbaseMaturity: 100, BlockCoinbaseMaturity: 100,
SubsidyGenesisReward: defaultSubsidyGenesisReward, SubsidyGenesisReward: defaultSubsidyGenesisReward,
MinSubsidy: defaultMinSubsidy, PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
MaxSubsidy: defaultMaxSubsidy, DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
TargetTimePerBlock: defaultTargetTimePerBlock, TargetTimePerBlock: defaultTargetTimePerBlock,
FinalityDuration: defaultFinalityDuration, FinalityDuration: defaultFinalityDuration,
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
@ -349,8 +339,7 @@ var TestnetParams = Params{
MergeSetSizeLimit: defaultMergeSetSizeLimit, MergeSetSizeLimit: defaultMergeSetSizeLimit,
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
PruningProofM: defaultPruningProofM, PruningProofM: defaultPruningProofM,
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
IgnoreHeaderMass: true, IgnoreHeaderMass: true,
} }
@ -375,10 +364,8 @@ var SimnetParams = Params{
PowMax: simnetPowMax, PowMax: simnetPowMax,
BlockCoinbaseMaturity: 100, BlockCoinbaseMaturity: 100,
SubsidyGenesisReward: defaultSubsidyGenesisReward, SubsidyGenesisReward: defaultSubsidyGenesisReward,
MinSubsidy: defaultMinSubsidy, PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
MaxSubsidy: defaultMaxSubsidy, DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
TargetTimePerBlock: time.Millisecond, TargetTimePerBlock: time.Millisecond,
FinalityDuration: time.Minute, FinalityDuration: time.Minute,
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
@ -416,8 +403,7 @@ var SimnetParams = Params{
MergeSetSizeLimit: defaultMergeSetSizeLimit, MergeSetSizeLimit: defaultMergeSetSizeLimit,
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
PruningProofM: defaultPruningProofM, PruningProofM: defaultPruningProofM,
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
} }
// DevnetParams defines the network parameters for the development Kaspa network. // DevnetParams defines the network parameters for the development Kaspa network.
@ -435,10 +421,8 @@ var DevnetParams = Params{
PowMax: devnetPowMax, PowMax: devnetPowMax,
BlockCoinbaseMaturity: 100, BlockCoinbaseMaturity: 100,
SubsidyGenesisReward: defaultSubsidyGenesisReward, SubsidyGenesisReward: defaultSubsidyGenesisReward,
MinSubsidy: defaultMinSubsidy, PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy,
MaxSubsidy: defaultMaxSubsidy, DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy,
SubsidyPastRewardMultiplier: big.NewRat(9, 10),
SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10),
TargetTimePerBlock: defaultTargetTimePerBlock, TargetTimePerBlock: defaultTargetTimePerBlock,
FinalityDuration: defaultFinalityDuration, FinalityDuration: defaultFinalityDuration,
DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize,
@ -478,8 +462,7 @@ var DevnetParams = Params{
MergeSetSizeLimit: defaultMergeSetSizeLimit, MergeSetSizeLimit: defaultMergeSetSizeLimit,
CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength,
PruningProofM: defaultPruningProofM, PruningProofM: defaultPruningProofM,
FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore,
FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000),
IgnoreHeaderMass: true, IgnoreHeaderMass: true,
} }