kaspad/domain/consensus/processes/difficultymanager/difficultymanager_test.go
Elichai Turkel 68bd8330ac
Log the networks hashrate (#1406)
* Log the hashrate of each block

* Add a test for GetHashrateString

* Move difficulty related functions to its own package

* Convert the validated log in validateAndInsertBlock to a log function

* Add tests for max/min int
2021-01-13 12:51:23 +02:00

220 lines
7.4 KiB
Go

package difficultymanager_test
import (
"github.com/kaspanet/kaspad/util/difficulty"
"testing"
"time"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus"
"github.com/kaspanet/kaspad/domain/consensus/model"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
"github.com/kaspanet/kaspad/domain/dagconfig"
)
func TestDifficulty(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, params *dagconfig.Params) {
if params.DisableDifficultyAdjustment {
return
}
// This test generates 3066 blocks above genesis with at least 1 second between each block, amounting to
// a bit less then an hour of timestamps.
// To prevent rejected blocks due to timestamps in the future, the following safeguard makes sure
// the genesis block is at least 1 hour in the past.
if params.GenesisBlock.Header.TimeInMilliseconds() > mstime.ToMSTime(time.Now().Add(-time.Hour)).UnixMilliseconds() {
t.Fatalf("TestDifficulty requires the GenesisBlock to be at least 1 hour old to pass")
}
params.K = 1
params.DifficultyAdjustmentWindowSize = 264
factory := consensus.NewFactory()
tc, teardown, err := factory.NewTestConsensus(params, false, "TestDifficulty")
if err != nil {
t.Fatalf("Error setting up consensus: %+v", err)
}
defer teardown(false)
addBlock := func(blockTime int64, parents ...*externalapi.DomainHash) (*externalapi.DomainBlock, *externalapi.DomainHash) {
bluestParent, err := tc.GHOSTDAGManager().ChooseSelectedParent(parents...)
if err != nil {
t.Fatalf("ChooseSelectedParent: %+v", err)
}
if blockTime == 0 {
header, err := tc.BlockHeaderStore().BlockHeader(tc.DatabaseContext(), bluestParent)
if err != nil {
t.Fatalf("BlockHeader: %+v", err)
}
blockTime = header.TimeInMilliseconds() + params.TargetTimePerBlock.Milliseconds()
}
block, _, err := tc.BuildBlockWithParents(parents, nil, nil)
if err != nil {
t.Fatalf("BuildBlockWithParents: %+v", err)
}
newHeader := block.Header.ToMutable()
newHeader.SetTimeInMilliseconds(blockTime)
block.Header = newHeader.ToImmutable()
_, err = tc.ValidateAndInsertBlock(block)
if err != nil {
t.Fatalf("ValidateAndInsertBlock: %+v", err)
}
return block, consensushashing.BlockHash(block)
}
minimumTime := func(parents ...*externalapi.DomainHash) int64 {
var tempHash externalapi.DomainHash
tc.BlockRelationStore().StageBlockRelation(&tempHash, &model.BlockRelations{
Parents: parents,
Children: nil,
})
defer tc.BlockRelationStore().Discard()
err = tc.GHOSTDAGManager().GHOSTDAG(&tempHash)
if err != nil {
t.Fatalf("GHOSTDAG: %+v", err)
}
defer tc.GHOSTDAGDataStore().Discard()
pastMedianTime, err := tc.PastMedianTimeManager().PastMedianTime(&tempHash)
if err != nil {
t.Fatalf("PastMedianTime: %+v", err)
}
return pastMedianTime + 1
}
addBlockWithMinimumTime := func(parents ...*externalapi.DomainHash) (*externalapi.DomainBlock, *externalapi.DomainHash) {
minTime := minimumTime(parents...)
return addBlock(minTime, parents...)
}
tipHash := params.GenesisHash
tip := params.GenesisBlock
for i := 0; i < params.DifficultyAdjustmentWindowSize; i++ {
tip, tipHash = addBlock(0, tipHash)
if tip.Header.Bits() != params.GenesisBlock.Header.Bits() {
t.Fatalf("As long as the bluest parent's blue score is less then the difficulty adjustment " +
"window size, the difficulty should be the same as genesis'")
}
}
for i := 0; i < params.DifficultyAdjustmentWindowSize+100; i++ {
tip, tipHash = addBlock(0, tipHash)
if tip.Header.Bits() != params.GenesisBlock.Header.Bits() {
t.Fatalf("As long as the block rate remains the same, the difficulty shouldn't change")
}
}
blockInThePast, tipHash := addBlockWithMinimumTime(tipHash)
if blockInThePast.Header.Bits() != tip.Header.Bits() {
t.Fatalf("The difficulty should only change when blockInThePast is in the past of a block bluest parent")
}
tip = blockInThePast
tip, tipHash = addBlock(0, tipHash)
if tip.Header.Bits() != blockInThePast.Header.Bits() {
t.Fatalf("The difficulty should only change when blockInThePast is in the past of a block bluest parent")
}
tip, tipHash = addBlock(0, tipHash)
if compareBits(tip.Header.Bits(), blockInThePast.Header.Bits()) >= 0 {
t.Fatalf("tip.bits should be smaller than blockInThePast.bits because blockInThePast increased the " +
"block rate, so the difficulty should increase as well")
}
var expectedBits uint32
switch params.Name {
case "kaspa-testnet", "kaspa-devnet":
expectedBits = uint32(0x1e7f83df)
case "kaspa-mainnet":
expectedBits = uint32(0x207f83df)
}
if tip.Header.Bits() != expectedBits {
t.Errorf("tip.bits was expected to be %x but got %x", expectedBits, tip.Header.Bits())
}
// Increase block rate to increase difficulty
for i := 0; i < params.DifficultyAdjustmentWindowSize; i++ {
tip, tipHash = addBlockWithMinimumTime(tipHash)
tipGHOSTDAGData, err := tc.GHOSTDAGDataStore().Get(tc.DatabaseContext(), tipHash)
if err != nil {
t.Fatalf("GHOSTDAGDataStore: %+v", err)
}
selectedParentHeader, err := tc.BlockHeaderStore().BlockHeader(tc.DatabaseContext(),
tipGHOSTDAGData.SelectedParent())
if err != nil {
t.Fatalf("BlockHeader: %+v", err)
}
if compareBits(tip.Header.Bits(), selectedParentHeader.Bits()) > 0 {
t.Fatalf("Because we're increasing the block rate, the difficulty can't decrease")
}
}
// Add blocks until difficulty stabilizes
lastBits := tip.Header.Bits()
sameBitsCount := 0
for sameBitsCount < params.DifficultyAdjustmentWindowSize+1 {
tip, tipHash = addBlock(0, tipHash)
if tip.Header.Bits() == lastBits {
sameBitsCount++
} else {
lastBits = tip.Header.Bits()
sameBitsCount = 0
}
}
slowBlockTime := tip.Header.TimeInMilliseconds() + params.TargetTimePerBlock.Milliseconds() + 1000
slowBlock, tipHash := addBlock(slowBlockTime, tipHash)
if slowBlock.Header.Bits() != tip.Header.Bits() {
t.Fatalf("The difficulty should only change when slowBlock is in the past of a block bluest parent")
}
tip = slowBlock
tip, tipHash = addBlock(0, tipHash)
if tip.Header.Bits() != slowBlock.Header.Bits() {
t.Fatalf("The difficulty should only change when slowBlock is in the past of a block bluest parent")
}
tip, tipHash = addBlock(0, tipHash)
if compareBits(tip.Header.Bits(), slowBlock.Header.Bits()) <= 0 {
t.Fatalf("tip.bits should be smaller than slowBlock.bits because slowBlock decreased the block" +
" rate, so the difficulty should decrease as well")
}
_, tipHash = addBlock(0, tipHash)
splitBlockHash := tipHash
for i := 0; i < 100; i++ {
_, tipHash = addBlock(0, tipHash)
}
blueTipHash := tipHash
redChainTipHash := splitBlockHash
for i := 0; i < 10; i++ {
_, redChainTipHash = addBlockWithMinimumTime(redChainTipHash)
}
tipWithRedPast, _ := addBlock(0, redChainTipHash, blueTipHash)
tipWithoutRedPast, _ := addBlock(0, blueTipHash)
if tipWithoutRedPast.Header.Bits() != tipWithRedPast.Header.Bits() {
t.Fatalf("tipWithoutRedPast.bits should be the same as tipWithRedPast.bits because red blocks" +
" shouldn't affect the difficulty")
}
})
}
func compareBits(a uint32, b uint32) int {
aTarget := difficulty.CompactToBig(a)
bTarget := difficulty.CompactToBig(b)
return aTarget.Cmp(bTarget)
}