Skip to content

Commit

Permalink
Switch to a fixed block subsidy after a certain work threshold (#1831)
Browse files Browse the repository at this point in the history
* Implement isBlockRewardFixed.

* Fix factory.go.

* Call isBlockRewardFixed from calcBlockSubsidy.

* Fix bad call to ghostdagDataStore.Get.

* Extract blue score and blue work from the header instead of from the ghostdagDataStore.

* Fix coinbasemanager constructor arguments order

* Format consensus_defaults.go

* Check the mainnet switch from the block's point of view rather than the virtual's.

* Don't call newBlockPruningPoint twice in buildBlock.

* Properly handle new pruning point blocks in isBlockRewardFixed.

* Use the correct variable.

* Add a comment explaining what we do when the pruning point is not found in isBlockRewardFixed.

* Implement TestBlockRewardSwitch.

* Add missing error handling.

Co-authored-by: Ori Newman <orinewman1@gmail.com>
  • Loading branch information
stasatdaglabs and someone235 authored Oct 31, 2021
1 parent 99aaacd commit 2dddb65
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 26 deletions.
6 changes: 5 additions & 1 deletion domain/consensus/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,11 +205,15 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
config.SubsidyMergeSetRewardMultiplier,
config.CoinbasePayloadScriptPublicKeyMaxLength,
config.GenesisHash,
config.FixedSubsidySwitchPruningPointInterval,
config.FixedSubsidySwitchHashRateDifference,
dagTraversalManager,
ghostdagDataStore,
acceptanceDataStore,
daaBlocksStore,
blockStore)
blockStore,
pruningStore,
blockHeaderStore)
headerTipsManager := headersselectedtipmanager.New(dbManager, dagTopologyManager, dagTraversalManager,
ghostdagManager, headersSelectedTipStore, headersSelectedChainStore)
genesisHash := config.GenesisHash
Expand Down
4 changes: 2 additions & 2 deletions domain/consensus/model/interface_processes_coinbasemanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
// coinbase transactions
type CoinbaseManager interface {
ExpectedCoinbaseTransaction(stagingArea *StagingArea, blockHash *externalapi.DomainHash,
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error)
CalcBlockSubsidy(stagingArea *StagingArea, blockHash *externalapi.DomainHash) (uint64, error)
coinbaseData *externalapi.DomainCoinbaseData, blockPruningPoint *externalapi.DomainHash) (*externalapi.DomainTransaction, error)
CalcBlockSubsidy(stagingArea *StagingArea, blockHash *externalapi.DomainHash, blockPruningPoint *externalapi.DomainHash) (uint64, error)
ExtractCoinbaseDataBlueScoreAndSubsidy(coinbaseTx *externalapi.DomainTransaction) (blueScore uint64, coinbaseData *externalapi.DomainCoinbaseData, subsidy uint64, err error)
}
22 changes: 11 additions & 11 deletions domain/consensus/processes/blockbuilder/block_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,17 @@ func (bb *blockBuilder) buildBlock(stagingArea *model.StagingArea, coinbaseData
return nil, err
}

coinbase, err := bb.newBlockCoinbaseTransaction(stagingArea, coinbaseData)
newBlockPruningPoint, err := bb.newBlockPruningPoint(stagingArea, model.VirtualBlockHash)
if err != nil {
return nil, err
}
coinbase, err := bb.newBlockCoinbaseTransaction(stagingArea, coinbaseData, newBlockPruningPoint)
if err != nil {
return nil, err
}
transactionsWithCoinbase := append([]*externalapi.DomainTransaction{coinbase}, transactions...)

header, err := bb.buildHeader(stagingArea, transactionsWithCoinbase)
header, err := bb.buildHeader(stagingArea, transactionsWithCoinbase, newBlockPruningPoint)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -171,13 +175,13 @@ func (bb *blockBuilder) validateTransaction(
}

func (bb *blockBuilder) newBlockCoinbaseTransaction(stagingArea *model.StagingArea,
coinbaseData *externalapi.DomainCoinbaseData) (*externalapi.DomainTransaction, error) {
coinbaseData *externalapi.DomainCoinbaseData, blockPruningPoint *externalapi.DomainHash) (*externalapi.DomainTransaction, error) {

return bb.coinbaseManager.ExpectedCoinbaseTransaction(stagingArea, model.VirtualBlockHash, coinbaseData)
return bb.coinbaseManager.ExpectedCoinbaseTransaction(stagingArea, model.VirtualBlockHash, coinbaseData, blockPruningPoint)
}

func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions []*externalapi.DomainTransaction) (
externalapi.BlockHeader, error) {
func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions []*externalapi.DomainTransaction,
newBlockPruningPoint *externalapi.DomainHash) (externalapi.BlockHeader, error) {

parents, err := bb.newBlockParents(stagingArea)
if err != nil {
Expand Down Expand Up @@ -212,10 +216,6 @@ func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions
if err != nil {
return nil, err
}
pruningPoint, err := bb.newBlockPruningPoint(stagingArea, model.VirtualBlockHash)
if err != nil {
return nil, err
}

return blockheader.NewImmutableBlockHeader(
constants.MaxBlockVersion,
Expand All @@ -229,7 +229,7 @@ func (bb *blockBuilder) buildHeader(stagingArea *model.StagingArea, transactions
daaScore,
blueScore,
blueWork,
pruningPoint,
newBlockPruningPoint,
), nil
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,11 @@ func (bb *testBlockBuilder) buildBlockWithParents(stagingArea *model.StagingArea

bb.acceptanceDataStore.Stage(stagingArea, tempBlockHash, acceptanceData)

coinbase, err := bb.coinbaseManager.ExpectedCoinbaseTransaction(stagingArea, tempBlockHash, coinbaseData)
pruningPoint, err := bb.newBlockPruningPoint(stagingArea, tempBlockHash)
if err != nil {
return nil, nil, err
}
coinbase, err := bb.coinbaseManager.ExpectedCoinbaseTransaction(stagingArea, tempBlockHash, coinbaseData, pruningPoint)
if err != nil {
return nil, nil, err
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (v *blockValidator) checkCoinbaseSubsidy(
return err
}

expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(stagingArea, blockHash)
expectedSubsidy, err := v.coinbaseManager.CalcBlockSubsidy(stagingArea, blockHash, block.Header.PruningPoint())
if err != nil {
return err
}
Expand Down
86 changes: 86 additions & 0 deletions domain/consensus/processes/coinbasemanager/block_reward_switch.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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.fixedSubsidySwitchHashRateDifference) >= 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
}
121 changes: 121 additions & 0 deletions domain/consensus/processes/coinbasemanager/block_reward_switch_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
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.FixedSubsidySwitchHashRateDifference = 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)
}
}

// Add another block. We expect it to be another pruning point
lastPruningPointHash, _, err := tc.AddBlock([]*externalapi.DomainHash{tipHash}, nil, nil)
if err != nil {
t.Fatalf("AddBlock: %+v", err)
}

// Make sure that another pruning point had been added
pruningPointHeaders, err = tc.PruningPointHeaders()
if err != nil {
t.Fatalf("PruningPointHeaders: %+v", pruningPointHeaders)
}
expectedPruningPointHeaderAmount = expectedPruningPointHeaderAmount + 1
if uint64(len(pruningPointHeaders)) != expectedPruningPointHeaderAmount {
t.Fatalf("Unexpected amount of pruning point headers. "+
"Want: %d, got: %d", expectedPruningPointHeaderAmount, len(pruningPointHeaders))
}

// Make sure that the last pruning point has a post-switch subsidy
lastPruningPoint, err := tc.GetBlock(lastPruningPointHash)
if err != nil {
t.Fatalf("GetBlock: %+v", err)
}
lastPruningPointCoinbase := lastPruningPoint.Transactions[transactionhelper.CoinbaseTransactionIndex]
_, _, subsidy, err := tc.CoinbaseManager().ExtractCoinbaseDataBlueScoreAndSubsidy(lastPruningPointCoinbase)
if err != nil {
t.Fatalf("ExtractCoinbaseDataBlueScoreAndSubsidy: %+v", err)
}
if subsidy != consensusConfig.SubsidyGenesisReward {
t.Fatalf("Subsidy has unexpected value. Want: %d, got: %d", consensusConfig.SubsidyGenesisReward, subsidy)
}
})
}
Loading

0 comments on commit 2dddb65

Please sign in to comment.