talelbaz d7f2cf81c0
Change merge set order to topological order (#1654)
* Change mergeSet to be ordered topologically.

* Add special condition for genesis.

* Add check that the coinbase is validated.

* Change names of variables(old: chainHash, blueHash).

* Fix the DAG diagram in the comment above the function.

* Fix variables names.

Co-authored-by: tal <tal@daglabs.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
2021-05-19 14:40:55 +03:00

228 lines
7.4 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/pkg/errors"
)
type coinbaseManager struct {
subsidyReductionInterval uint64
baseSubsidy uint64
coinbasePayloadScriptPublicKeyMaxLength uint8
databaseContext model.DBReader
ghostdagDataStore model.GHOSTDAGDataStore
acceptanceDataStore model.AcceptanceDataStore
daaBlocksStore model.DAABlocksStore
}
func (c *coinbaseManager) ExpectedCoinbaseTransaction(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash,
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) {
ghostdagData, err := c.ghostdagDataStore.Get(c.databaseContext, stagingArea, blockHash)
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)
}
payload, err := c.serializeCoinbasePayload(ghostdagData.BlueScore(), coinbaseData)
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) {
totalReward, err := c.calcMergedBlockReward(stagingArea, blueBlock, blockAcceptanceData, mergingBlockDAAAddedBlocksSet)
if err != nil {
return nil, false, err
}
if totalReward == 0 {
return nil, false, nil
}
// the ScriptPublicKey for the coinbase is parsed from the coinbase payload
_, coinbaseData, err := c.ExtractCoinbaseDataAndBlueScore(blockAcceptanceData.TransactionAcceptanceData[0].Transaction)
if err != nil {
return nil, false, err
}
txOut := &externalapi.DomainTransactionOutput{
Value: totalReward,
ScriptPublicKey: coinbaseData.ScriptPublicKey,
}
return txOut, true, nil
}
func (c *coinbaseManager) coinbaseOutputForRewardFromRedBlocks(stagingArea *model.StagingArea,
ghostdagData *model.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.
//
// The subsidy is halved every SubsidyReductionInterval blocks. Mathematically
// this is: baseSubsidy / 2^(blueScore/SubsidyReductionInterval)
//
// At the target block generation rate for the main network, this is
// approximately every 4 years.
func (c *coinbaseManager) calcBlockSubsidy(stagingArea *model.StagingArea, blockHash *externalapi.DomainHash) (uint64, error) {
if c.subsidyReductionInterval == 0 {
return c.baseSubsidy, nil
}
daaScore, err := c.daaBlocksStore.DAAScore(c.databaseContext, stagingArea, blockHash)
if err != nil {
return 0, err
}
// Equivalent to: baseSubsidy / 2^(daaScore/subsidyHalvingInterval)
return c.baseSubsidy >> uint(daaScore/c.subsidyReductionInterval), 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
}
}
subsidy, err := c.calcBlockSubsidy(stagingArea, blockHash)
if err != nil {
return 0, err
}
return subsidy + totalFees, nil
}
// New instantiates a new CoinbaseManager
func New(
databaseContext model.DBReader,
subsidyReductionInterval uint64,
baseSubsidy uint64,
coinbasePayloadScriptPublicKeyMaxLength uint8,
ghostdagDataStore model.GHOSTDAGDataStore,
acceptanceDataStore model.AcceptanceDataStore,
daaBlocksStore model.DAABlocksStore) model.CoinbaseManager {
return &coinbaseManager{
databaseContext: databaseContext,
subsidyReductionInterval: subsidyReductionInterval,
baseSubsidy: baseSubsidy,
coinbasePayloadScriptPublicKeyMaxLength: coinbasePayloadScriptPublicKeyMaxLength,
ghostdagDataStore: ghostdagDataStore,
acceptanceDataStore: acceptanceDataStore,
daaBlocksStore: daaBlocksStore,
}
}