mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
314 lines
14 KiB
Go
314 lines
14 KiB
Go
package coinbasemanager
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/hashset"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
|
"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
|
|
preDeflationaryPhaseBaseSubsidy uint64
|
|
coinbasePayloadScriptPublicKeyMaxLength uint8
|
|
genesisHash *externalapi.DomainHash
|
|
deflationaryPhaseDaaScore uint64
|
|
deflationaryPhaseBaseSubsidy uint64
|
|
|
|
databaseContext model.DBReader
|
|
dagTraversalManager model.DAGTraversalManager
|
|
ghostdagDataStore model.GHOSTDAGDataStore
|
|
acceptanceDataStore model.AcceptanceDataStore
|
|
daaBlocksStore model.DAABlocksStore
|
|
blockStore model.BlockStore
|
|
pruningStore model.PruningStore
|
|
blockHeaderStore model.BlockHeaderStore
|
|
}
|
|
|
|
func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
|
|
coinbaseData *externalapi.DomainCoinbaseData) (expectedTransaction *externalapi.DomainTransaction, hasRedReward bool, err error) {
|
|
|
|
ghostdagData, err := c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash, true)
|
|
if !database.IsNotFoundError(err) && err != nil {
|
|
return nil, false, 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, false, err
|
|
}
|
|
}
|
|
|
|
acceptanceData, err := c.acceptanceDataStore.Get(c.databaseContext, stagingArea, blockHash)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
daaAddedBlocksSet, err := c.daaAddedBlocksSet(stagingArea, blockHash)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
txOuts := make([]*externalapi.DomainTransactionOutput, 0, len(ghostdagData.MergeSetBlues()))
|
|
acceptanceDataMap := acceptanceDataFromArrayToMap(acceptanceData)
|
|
for _, blue := range ghostdagData.MergeSetBlues() {
|
|
txOut, hasReward, err := c.coinbaseOutputForBlueBlock(stagingArea, blue, acceptanceDataMap[*blue], daaAddedBlocksSet)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if hasReward {
|
|
txOuts = append(txOuts, txOut)
|
|
}
|
|
}
|
|
|
|
txOut, hasRedReward, err := c.coinbaseOutputForRewardFromRedBlocks(
|
|
stagingArea, ghostdagData, acceptanceData, daaAddedBlocksSet, coinbaseData)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if hasRedReward {
|
|
txOuts = append(txOuts, txOut)
|
|
}
|
|
|
|
subsidy, err := c.CalcBlockSubsidy(stagingArea, blockHash)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
payload, err := c.serializeCoinbasePayload(ghostdagData.BlueScore(), coinbaseData, subsidy)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
return &externalapi.DomainTransaction{
|
|
Version: constants.MaxTransactionVersion,
|
|
Inputs: []*externalapi.DomainTransactionInput{},
|
|
Outputs: txOuts,
|
|
LockTime: 0,
|
|
SubnetworkID: subnetworks.SubnetworkIDCoinbase,
|
|
Gas: 0,
|
|
Payload: payload,
|
|
}, hasRedReward, nil
|
|
}
|
|
|
|
func (c *coinbaseManager) daaAddedBlocksSet(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (
|
|
hashset.HashSet, error) {
|
|
|
|
daaAddedBlocks, err := c.daaBlocksStore.DAAAddedBlocks(c.databaseContext, stagingArea, blockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return hashset.NewFromSlice(daaAddedBlocks...), nil
|
|
}
|
|
|
|
// coinbaseOutputForBlueBlock calculates the output that should go into the coinbase transaction of blueBlock
|
|
// If blueBlock gets no fee - returns nil for txOut
|
|
func (c *coinbaseManager) coinbaseOutputForBlueBlock(stagingArea *model.StagingArea,
|
|
blueBlock *externalapi.DomainHash, blockAcceptanceData *externalapi.BlockAcceptanceData,
|
|
mergingBlockDAAAddedBlocksSet hashset.HashSet) (*externalapi.DomainTransactionOutput, bool, error) {
|
|
|
|
blockReward, err := c.calcMergedBlockReward(stagingArea, blueBlock, blockAcceptanceData, mergingBlockDAAAddedBlocksSet)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
if blockReward == 0 {
|
|
return nil, false, nil
|
|
}
|
|
|
|
// the ScriptPublicKey for the coinbase is parsed from the coinbase payload
|
|
_, coinbaseData, _, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(blockAcceptanceData.TransactionAcceptanceData[0].Transaction)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
txOut := &externalapi.DomainTransactionOutput{
|
|
Value: blockReward,
|
|
ScriptPublicKey: coinbaseData.ScriptPublicKey,
|
|
}
|
|
|
|
return txOut, true, nil
|
|
}
|
|
|
|
func (c *coinbaseManager) coinbaseOutputForRewardFromRedBlocks(stagingArea *model.StagingArea,
|
|
ghostdagData *externalapi.BlockGHOSTDAGData, acceptanceData externalapi.AcceptanceData, daaAddedBlocksSet hashset.HashSet,
|
|
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransactionOutput, bool, error) {
|
|
|
|
acceptanceDataMap := acceptanceDataFromArrayToMap(acceptanceData)
|
|
totalReward := uint64(0)
|
|
for _, red := range ghostdagData.MergeSetReds() {
|
|
reward, err := c.calcMergedBlockReward(stagingArea, red, acceptanceDataMap[*red], daaAddedBlocksSet)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
totalReward += reward
|
|
}
|
|
|
|
if totalReward == 0 {
|
|
return nil, false, nil
|
|
}
|
|
|
|
return &externalapi.DomainTransactionOutput{
|
|
Value: totalReward,
|
|
ScriptPublicKey: coinbaseData.ScriptPublicKey,
|
|
}, true, nil
|
|
}
|
|
|
|
func acceptanceDataFromArrayToMap(acceptanceData externalapi.AcceptanceData) map[externalapi.DomainHash]*externalapi.BlockAcceptanceData {
|
|
acceptanceDataMap := make(map[externalapi.DomainHash]*externalapi.BlockAcceptanceData, len(acceptanceData))
|
|
for _, blockAcceptanceData := range acceptanceData {
|
|
acceptanceDataMap[*blockAcceptanceData.BlockHash] = blockAcceptanceData
|
|
}
|
|
return acceptanceDataMap
|
|
}
|
|
|
|
// CalcBlockSubsidy returns the subsidy amount a block at the provided blue score
|
|
// 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.
|
|
func (c *coinbaseManager) CalcBlockSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint64, error) {
|
|
if blockHash.Equal(c.genesisHash) {
|
|
return c.subsidyGenesisReward, nil
|
|
}
|
|
blockDaaScore, err := c.daaBlocksStore.DAAScore(c.databaseContext, stagingArea, blockHash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if blockDaaScore < c.deflationaryPhaseDaaScore {
|
|
return c.preDeflationaryPhaseBaseSubsidy, nil
|
|
}
|
|
|
|
blockSubsidy := c.calcDeflationaryPeriodBlockSubsidy(blockDaaScore)
|
|
return blockSubsidy, 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,
|
|
blockAcceptanceData *externalapi.BlockAcceptanceData, mergingBlockDAAAddedBlocksSet hashset.HashSet) (uint64, error) {
|
|
|
|
if !blockHash.Equal(blockAcceptanceData.BlockHash) {
|
|
return 0, errors.Errorf("blockAcceptanceData.BlockHash is expected to be %s but got %s",
|
|
blockHash, blockAcceptanceData.BlockHash)
|
|
}
|
|
|
|
if !mergingBlockDAAAddedBlocksSet.Contains(blockHash) {
|
|
return 0, nil
|
|
}
|
|
|
|
totalFees := uint64(0)
|
|
for _, txAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData {
|
|
if txAcceptanceData.IsAccepted {
|
|
totalFees += txAcceptanceData.Fee
|
|
}
|
|
}
|
|
|
|
block, err := c.blockStore.Block(c.databaseContext, stagingArea, blockHash)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
_, _, subsidy, err := c.ExtractCoinbaseDataBlueScoreAndSubsidy(block.Transactions[transactionhelper.CoinbaseTransactionIndex])
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return subsidy + totalFees, nil
|
|
}
|
|
|
|
// New instantiates a new CoinbaseManager
|
|
func New(
|
|
databaseContext model.DBReader,
|
|
|
|
subsidyGenesisReward uint64,
|
|
preDeflationaryPhaseBaseSubsidy uint64,
|
|
coinbasePayloadScriptPublicKeyMaxLength uint8,
|
|
genesisHash *externalapi.DomainHash,
|
|
deflationaryPhaseDaaScore uint64,
|
|
deflationaryPhaseBaseSubsidy uint64,
|
|
|
|
dagTraversalManager model.DAGTraversalManager,
|
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
|
acceptanceDataStore model.AcceptanceDataStore,
|
|
daaBlocksStore model.DAABlocksStore,
|
|
blockStore model.BlockStore,
|
|
pruningStore model.PruningStore,
|
|
blockHeaderStore model.BlockHeaderStore) model.CoinbaseManager {
|
|
|
|
return &coinbaseManager{
|
|
databaseContext: databaseContext,
|
|
|
|
subsidyGenesisReward: subsidyGenesisReward,
|
|
preDeflationaryPhaseBaseSubsidy: preDeflationaryPhaseBaseSubsidy,
|
|
coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength,
|
|
genesisHash: genesisHash,
|
|
deflationaryPhaseDaaScore: deflationaryPhaseDaaScore,
|
|
deflationaryPhaseBaseSubsidy: deflationaryPhaseBaseSubsidy,
|
|
|
|
dagTraversalManager: dagTraversalManager,
|
|
ghostdagDataStore: ghostdagDataStore,
|
|
acceptanceDataStore: acceptanceDataStore,
|
|
daaBlocksStore: daaBlocksStore,
|
|
blockStore: blockStore,
|
|
pruningStore: pruningStore,
|
|
blockHeaderStore: blockHeaderStore,
|
|
}
|
|
}
|