diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index dad3ec59a..8cf924584 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -203,14 +203,11 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas coinbaseManager := coinbasemanager.New( dbManager, config.SubsidyGenesisReward, - config.MinSubsidy, - config.MaxSubsidy, - config.SubsidyPastRewardMultiplier, - config.SubsidyMergeSetRewardMultiplier, + config.PreDeflationaryPhaseBaseSubsidy, config.CoinbasePayloadScriptPublicKeyMaxLength, config.GenesisHash, - config.FixedSubsidySwitchPruningPointInterval, - config.FixedSubsidySwitchHashRateThreshold, + config.DeflationaryPhaseDaaScore, + config.DeflationaryPhaseBaseSubsidy, dagTraversalManager, ghostdagDataStore, acceptanceDataStore, diff --git a/domain/consensus/model/interface_processes_coinbasemanager.go b/domain/consensus/model/interface_processes_coinbasemanager.go index 39db61803..e8635d997 100644 --- a/domain/consensus/model/interface_processes_coinbasemanager.go +++ b/domain/consensus/model/interface_processes_coinbasemanager.go @@ -7,6 +7,6 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" type CoinbaseManager interface { ExpectedCoinbaseTransaction(stagingArea *StagingArea, blockHash *externalapi.DomainHash, 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) } diff --git a/domain/consensus/processes/blockvalidator/block_body_in_context.go b/domain/consensus/processes/blockvalidator/block_body_in_context.go index 015022e74..a652c8d87 100644 --- a/domain/consensus/processes/blockvalidator/block_body_in_context.go +++ b/domain/consensus/processes/blockvalidator/block_body_in_context.go @@ -178,7 +178,7 @@ func (v *blockValidator) checkCoinbaseSubsidy( return err } - expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(blockHash) + expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(stagingArea, blockHash) if err != nil { return err } diff --git a/domain/consensus/processes/coinbasemanager/block_reward_switch.go b/domain/consensus/processes/coinbasemanager/block_reward_switch.go deleted file mode 100644 index 6249f65b5..000000000 --- a/domain/consensus/processes/coinbasemanager/block_reward_switch.go +++ /dev/null @@ -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 -} diff --git a/domain/consensus/processes/coinbasemanager/block_reward_switch_test.go b/domain/consensus/processes/coinbasemanager/block_reward_switch_test.go deleted file mode 100644 index 93a6e7d9e..000000000 --- a/domain/consensus/processes/coinbasemanager/block_reward_switch_test.go +++ /dev/null @@ -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) - } - } - }) -} diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager.go b/domain/consensus/processes/coinbasemanager/coinbasemanager.go index b0c1fff81..ccc702003 100644 --- a/domain/consensus/processes/coinbasemanager/coinbasemanager.go +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager.go @@ -1,9 +1,6 @@ package coinbasemanager import ( - "encoding/binary" - "math/big" - "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "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/infrastructure/db/database" "github.com/pkg/errors" + "math" ) type coinbaseManager struct { subsidyGenesisReward uint64 - minSubsidy uint64 - maxSubsidy uint64 - subsidyPastRewardMultiplier *big.Rat - subsidyMergeSetRewardMultiplier *big.Rat + preDeflationaryPhaseBaseSubsidy uint64 coinbasePayloadScriptPublicKeyMaxLength uint8 genesisHash *externalapi.DomainHash - fixedSubsidySwitchPruningPointInterval uint64 - fixedSubsidySwitchHashRateThreshold *big.Int + deflationaryPhaseDaaScore uint64 + deflationaryPhaseBaseSubsidy uint64 databaseContext model.DBReader dagTraversalManager model.DAGTraversalManager @@ -84,7 +79,7 @@ func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.Staging txOuts = append(txOuts, txOut) } - subsidy, err := c.CalcBlockSubsidy(blockHash) + subsidy, err := c.CalcBlockSubsidy(stagingArea, blockHash) if err != nil { 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 // newly generated blocks awards as well as validating the coinbase for blocks // has the expected value. -// -// Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6 -func (c *coinbaseManager) CalcBlockSubsidy(blockHash *externalapi.DomainHash) (uint64, error) { +func (c *coinbaseManager) CalcBlockSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint64, error) { if blockHash.Equal(c.genesisHash) { return c.subsidyGenesisReward, nil } - - 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) + blockDaaScore, err := c.daaBlocksStore.DAAScore(c.databaseContext, stagingArea, blockHash) if err != nil { return 0, err } - selectedParentHash := ghostdagData.SelectedParent() - if selectedParentHash == nil { - return 0, nil + if blockDaaScore < c.deflationaryPhaseDaaScore { + return c.preDeflationaryPhaseBaseSubsidy, nil } - const binomialSteps = 10 - binomialSum := int64(0) + blockSubsidy := c.calcDeflationaryPeriodBlockSubsidy(blockDaaScore) + return blockSubsidy, nil +} - // The first two bytes of a hash are a good deterministic source - // of randomness, so we use that instead of any rand implementation - firstTwoBytes := binary.LittleEndian.Uint16(selectedParentHash.ByteSlice()[:2]) - for i := 0; i < binomialSteps; i++ { - step := firstTwoBytes & 1 - firstTwoBytes >>= 1 - binomialSum += int64(step) +func (c *coinbaseManager) calcDeflationaryPeriodBlockSubsidy(blockDaaScore uint64) uint64 { + // We define a year as 365.25 days and a month as 365.25 / 12 = 30.4375 + // secondsPerMonth = 30.4375 * 24 * 60 * 60 + const secondsPerMonth = 2629800 + // Note that this calculation implicitly assumes that block per second = 1 (by assuming daa score diff is in second units). + monthsSinceDeflationaryPhaseStarted := (blockDaaScore - c.deflationaryPhaseDaaScore) / secondsPerMonth + // 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, @@ -312,14 +278,11 @@ func New( databaseContext model.DBReader, subsidyGenesisReward uint64, - minSubsidy uint64, - maxSubsidy uint64, - subsidyPastRewardMultiplier *big.Rat, - subsidyMergeSetRewardMultiplier *big.Rat, + preDeflationaryPhaseBaseSubsidy uint64, coinbasePayloadScriptPublicKeyMaxLength uint8, genesisHash *externalapi.DomainHash, - fixedSubsidySwitchPruningPointInterval uint64, - fixedSubsidySwitchHashRateThreshold *big.Int, + deflationaryPhaseDaaScore uint64, + deflationaryPhaseBaseSubsidy uint64, dagTraversalManager model.DAGTraversalManager, ghostdagDataStore model.GHOSTDAGDataStore, @@ -333,14 +296,11 @@ func New( databaseContext: databaseContext, subsidyGenesisReward: subsidyGenesisReward, - minSubsidy: minSubsidy, - maxSubsidy: maxSubsidy, - subsidyPastRewardMultiplier: subsidyPastRewardMultiplier, - subsidyMergeSetRewardMultiplier: subsidyMergeSetRewardMultiplier, + preDeflationaryPhaseBaseSubsidy: preDeflationaryPhaseBaseSubsidy, coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength, genesisHash: genesisHash, - fixedSubsidySwitchPruningPointInterval: fixedSubsidySwitchPruningPointInterval, - fixedSubsidySwitchHashRateThreshold: fixedSubsidySwitchHashRateThreshold, + deflationaryPhaseDaaScore: deflationaryPhaseDaaScore, + deflationaryPhaseBaseSubsidy: deflationaryPhaseBaseSubsidy, dagTraversalManager: dagTraversalManager, ghostdagDataStore: ghostdagDataStore, diff --git a/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go b/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go new file mode 100644 index 000000000..a5e658c08 --- /dev/null +++ b/domain/consensus/processes/coinbasemanager/coinbasemanager_test.go @@ -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) +} diff --git a/domain/dagconfig/consensus_defaults.go b/domain/dagconfig/consensus_defaults.go index 1f25f09b4..97d502b48 100644 --- a/domain/dagconfig/consensus_defaults.go +++ b/domain/dagconfig/consensus_defaults.go @@ -46,13 +46,11 @@ const ( // 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 // in block take longer) - defaultMergeSetSizeLimit = defaultGHOSTDAGK * 10 - defaultSubsidyGenesisReward = 1 * constants.SompiPerKaspa - defaultMinSubsidy = 1 * constants.SompiPerKaspa - defaultMaxSubsidy = 500 * constants.SompiPerKaspa - defaultBaseSubsidy = 50 * constants.SompiPerKaspa - defaultFixedSubsidySwitchPruningPointInterval uint64 = 7 - defaultCoinbasePayloadScriptPublicKeyMaxLength = 150 + defaultMergeSetSizeLimit = defaultGHOSTDAGK * 10 + defaultSubsidyGenesisReward = 1 * constants.SompiPerKaspa + defaultPreDeflationaryPhaseBaseSubsidy = 500 * constants.SompiPerKaspa + defaultDeflationaryPhaseBaseSubsidy = 440 * constants.SompiPerKaspa + defaultCoinbasePayloadScriptPublicKeyMaxLength = 150 // defaultDifficultyAdjustmentWindowSize is the number of blocks in a block's past used to calculate its difficulty // target. // 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 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 ) diff --git a/domain/dagconfig/params.go b/domain/dagconfig/params.go index 6b3d086cc..729a3b348 100644 --- a/domain/dagconfig/params.go +++ b/domain/dagconfig/params.go @@ -92,10 +92,8 @@ type Params struct { // SubsidyPastRewardMultiplier are part of the block subsidy equation. // Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6 SubsidyGenesisReward uint64 - MinSubsidy uint64 - MaxSubsidy uint64 - SubsidyPastRewardMultiplier *big.Rat - SubsidyMergeSetRewardMultiplier *big.Rat + PreDeflationaryPhaseBaseSubsidy uint64 + DeflationaryPhaseBaseSubsidy uint64 // TargetTimePerBlock is the desired amount of time to generate each // 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 uint64 - // BaseSubsidy is the starting subsidy amount for mined blocks. - BaseSubsidy uint64 - - FixedSubsidySwitchPruningPointInterval uint64 - - FixedSubsidySwitchHashRateThreshold *big.Int + // DeflationaryPhaseDaaScore is the DAA score after which the monetary policy switches + // to its deflationary phase + DeflationaryPhaseDaaScore uint64 DisallowDirectBlocksOnTopOfGenesis bool @@ -243,10 +238,8 @@ var MainnetParams = Params{ PowMax: mainPowMax, BlockCoinbaseMaturity: 100, SubsidyGenesisReward: defaultSubsidyGenesisReward, - MinSubsidy: defaultMinSubsidy, - MaxSubsidy: defaultMaxSubsidy, - SubsidyPastRewardMultiplier: big.NewRat(9, 10), - SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10), + PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy, + DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy, TargetTimePerBlock: defaultTargetTimePerBlock, FinalityDuration: defaultFinalityDuration, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, @@ -286,8 +279,7 @@ var MainnetParams = Params{ MergeSetSizeLimit: defaultMergeSetSizeLimit, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, PruningProofM: defaultPruningProofM, - FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, - FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000), + DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore, DisallowDirectBlocksOnTopOfGenesis: true, } @@ -306,10 +298,8 @@ var TestnetParams = Params{ PowMax: testnetPowMax, BlockCoinbaseMaturity: 100, SubsidyGenesisReward: defaultSubsidyGenesisReward, - MinSubsidy: defaultMinSubsidy, - MaxSubsidy: defaultMaxSubsidy, - SubsidyPastRewardMultiplier: big.NewRat(9, 10), - SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10), + PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy, + DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy, TargetTimePerBlock: defaultTargetTimePerBlock, FinalityDuration: defaultFinalityDuration, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, @@ -349,8 +339,7 @@ var TestnetParams = Params{ MergeSetSizeLimit: defaultMergeSetSizeLimit, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, PruningProofM: defaultPruningProofM, - FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, - FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000), + DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore, IgnoreHeaderMass: true, } @@ -375,10 +364,8 @@ var SimnetParams = Params{ PowMax: simnetPowMax, BlockCoinbaseMaturity: 100, SubsidyGenesisReward: defaultSubsidyGenesisReward, - MinSubsidy: defaultMinSubsidy, - MaxSubsidy: defaultMaxSubsidy, - SubsidyPastRewardMultiplier: big.NewRat(9, 10), - SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10), + PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy, + DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy, TargetTimePerBlock: time.Millisecond, FinalityDuration: time.Minute, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, @@ -416,8 +403,7 @@ var SimnetParams = Params{ MergeSetSizeLimit: defaultMergeSetSizeLimit, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, PruningProofM: defaultPruningProofM, - FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, - FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000), + DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore, } // DevnetParams defines the network parameters for the development Kaspa network. @@ -435,10 +421,8 @@ var DevnetParams = Params{ PowMax: devnetPowMax, BlockCoinbaseMaturity: 100, SubsidyGenesisReward: defaultSubsidyGenesisReward, - MinSubsidy: defaultMinSubsidy, - MaxSubsidy: defaultMaxSubsidy, - SubsidyPastRewardMultiplier: big.NewRat(9, 10), - SubsidyMergeSetRewardMultiplier: big.NewRat(1, 10), + PreDeflationaryPhaseBaseSubsidy: defaultPreDeflationaryPhaseBaseSubsidy, + DeflationaryPhaseBaseSubsidy: defaultDeflationaryPhaseBaseSubsidy, TargetTimePerBlock: defaultTargetTimePerBlock, FinalityDuration: defaultFinalityDuration, DifficultyAdjustmentWindowSize: defaultDifficultyAdjustmentWindowSize, @@ -478,8 +462,7 @@ var DevnetParams = Params{ MergeSetSizeLimit: defaultMergeSetSizeLimit, CoinbasePayloadScriptPublicKeyMaxLength: defaultCoinbasePayloadScriptPublicKeyMaxLength, PruningProofM: defaultPruningProofM, - FixedSubsidySwitchPruningPointInterval: defaultFixedSubsidySwitchPruningPointInterval, - FixedSubsidySwitchHashRateThreshold: big.NewInt(150_000_000_000), + DeflationaryPhaseDaaScore: defaultDeflationaryPhaseDaaScore, IgnoreHeaderMass: true, }