mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 21:56:50 +00:00
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:
parent
7c1cddff11
commit
0bdd19136f
@ -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,
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
@ -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)
|
||||
|
||||
// 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)
|
||||
blockSubsidy := c.calcDeflationaryPeriodBlockSubsidy(blockDaaScore)
|
||||
return blockSubsidy, nil
|
||||
}
|
||||
return binomialSum - (binomialSteps / 2), nil
|
||||
|
||||
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 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,
|
||||
|
@ -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)
|
||||
}
|
@ -48,10 +48,8 @@ const (
|
||||
// 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
|
||||
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.
|
||||
@ -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
|
||||
)
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user