mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-13 13:00:10 +00:00

* UTXO dump of block 0fca37ca667c2d550a6c4416dad9717e50927128c424fa4edbebc436ab13aeef * Activate HF immediately and change reward to 1000 * Change protocol version and datadir location * Delete comments * Fix zero hash to muhash zero hash in genesis utxo dump check * Don't omit genesis as direct parent * Fix tests * Change subsidy to 500 * Dont assume genesis multiset is empty * Fix BlockReward test * Fix TestValidateAndInsertImportedPruningPoint test * Fix pruning point genesis utxo set * Fix tests related to mainnet utxo set * Dont change the difficulty before you have a full window * Fix TestBlockWindow tests * Remove global utxo set variable, and persist mainnetnet utxo deserialization between runs * Fix last tests * Make peer banning opt-in * small fix for a test * Fix go lint * Fix Ori's review comments * Change DAA score of genesis to checkpoint DAA score and fix all tests * Fix the BlockLevel bits counting * Fix some tests and make them run a little faster * Change datadir name back to kaspa-mainnet and change db path from /data to /datadir * Last changes for the release and change the version to 0.11.5 Co-authored-by: Ori Newman <orinewman1@gmail.com> Co-authored-by: Ori Newman <> Co-authored-by: msutton <mikisiton2@gmail.com>
356 lines
12 KiB
Go
356 lines
12 KiB
Go
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"
|
|
"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"
|
|
)
|
|
|
|
type coinbaseManager struct {
|
|
subsidyGenesisReward uint64
|
|
minSubsidy uint64
|
|
maxSubsidy uint64
|
|
subsidyPastRewardMultiplier *big.Rat
|
|
subsidyMergeSetRewardMultiplier *big.Rat
|
|
coinbasePayloadScriptPublicKeyMaxLength uint8
|
|
genesisHash *externalapi.DomainHash
|
|
fixedSubsidySwitchPruningPointInterval uint64
|
|
fixedSubsidySwitchHashRateThreshold *big.Int
|
|
|
|
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, blockPruningPoint *externalapi.DomainHash) (*externalapi.DomainTransaction, 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
|
|
}
|
|
}
|
|
|
|
acceptanceData, err := c.acceptanceDataStore.Get(c.databaseContext, stagingArea, blockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
daaAddedBlocksSet, err := c.daaAddedBlocksSet(stagingArea, blockHash)
|
|
if err != nil {
|
|
return nil, 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, err
|
|
}
|
|
|
|
if hasReward {
|
|
txOuts = append(txOuts, txOut)
|
|
}
|
|
}
|
|
|
|
txOut, hasReward, err := c.coinbaseOutputForRewardFromRedBlocks(
|
|
stagingArea, ghostdagData, acceptanceData, daaAddedBlocksSet, coinbaseData)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if hasReward {
|
|
txOuts = append(txOuts, txOut)
|
|
}
|
|
|
|
subsidy, err := c.CalcBlockSubsidy(stagingArea, blockHash, blockPruningPoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payload, err := c.serializeCoinbasePayload(ghostdagData.BlueScore(), coinbaseData, subsidy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &externalapi.DomainTransaction{
|
|
Version: constants.MaxTransactionVersion,
|
|
Inputs: []*externalapi.DomainTransactionInput{},
|
|
Outputs: txOuts,
|
|
LockTime: 0,
|
|
SubnetworkID: subnetworks.SubnetworkIDCoinbase,
|
|
Gas: 0,
|
|
Payload: payload,
|
|
}, 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.
|
|
//
|
|
// Further details: https://hashdag.medium.com/kaspa-launch-plan-9a63f4d754a6
|
|
func (c *coinbaseManager) CalcBlockSubsidy(stagingArea *model.StagingArea,
|
|
blockHash *externalapi.DomainHash, blockPruningPoint *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)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
selectedParentHash := ghostdagData.SelectedParent()
|
|
if selectedParentHash == nil {
|
|
return 0, 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)
|
|
}
|
|
return binomialSum - (binomialSteps / 2), nil
|
|
}
|
|
|
|
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,
|
|
minSubsidy uint64,
|
|
maxSubsidy uint64,
|
|
subsidyPastRewardMultiplier *big.Rat,
|
|
subsidyMergeSetRewardMultiplier *big.Rat,
|
|
coinbasePayloadScriptPublicKeyMaxLength uint8,
|
|
genesisHash *externalapi.DomainHash,
|
|
fixedSubsidySwitchPruningPointInterval uint64,
|
|
fixedSubsidySwitchHashRateThreshold *big.Int,
|
|
|
|
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,
|
|
minSubsidy: minSubsidy,
|
|
maxSubsidy: maxSubsidy,
|
|
subsidyPastRewardMultiplier: subsidyPastRewardMultiplier,
|
|
subsidyMergeSetRewardMultiplier: subsidyMergeSetRewardMultiplier,
|
|
coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength,
|
|
genesisHash: genesisHash,
|
|
fixedSubsidySwitchPruningPointInterval: fixedSubsidySwitchPruningPointInterval,
|
|
fixedSubsidySwitchHashRateThreshold: fixedSubsidySwitchHashRateThreshold,
|
|
|
|
dagTraversalManager: dagTraversalManager,
|
|
ghostdagDataStore: ghostdagDataStore,
|
|
acceptanceDataStore: acceptanceDataStore,
|
|
daaBlocksStore: daaBlocksStore,
|
|
blockStore: blockStore,
|
|
pruningStore: pruningStore,
|
|
blockHeaderStore: blockHeaderStore,
|
|
}
|
|
}
|