-
Notifications
You must be signed in to change notification settings - Fork 237
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Switch to a fixed block subsidy after a certain work threshold (#1831)
* 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
1 parent
99aaacd
commit 2dddb65
Showing
11 changed files
with
284 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
86 changes: 86 additions & 0 deletions
86
domain/consensus/processes/coinbasemanager/block_reward_switch.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
121
domain/consensus/processes/coinbasemanager/block_reward_switch_test.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
}) | ||
} |
Oops, something went wrong.