diff --git a/blockchain/difficulty.go b/blockchain/difficulty.go index ecc2ae0e..9b2bd249 100644 --- a/blockchain/difficulty.go +++ b/blockchain/difficulty.go @@ -232,11 +232,6 @@ func (b *BlockChain) findPrevTestNetDifficulty(startNode *blockNode) uint32 { // the exported version uses the current best chain as the previous block node // while this function accepts any block node. func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime time.Time) (uint32, error) { - // Genesis block. - if curNode == nil { - return b.chainParams.PowLimitBits, nil - } - // Get the old difficulty; if we aren't at a block height where it changes, // just return this. oldDiff := curNode.bits @@ -253,30 +248,8 @@ func (b *BlockChain) calcNextRequiredDifficulty(curNode *blockNode, newBlockTime reductionTime := int64(b.chainParams.MinDiffReductionTime / time.Second) allowMinTime := curNode.timestamp + reductionTime - - // For every extra target timespan that passes, we halve the - // difficulty. if newBlockTime.Unix() > allowMinTime { - timePassed := newBlockTime.Unix() - curNode.timestamp - timePassed -= reductionTime - shifts := uint((timePassed / int64(b.chainParams.TargetTimePerBlock/ - time.Second)) + 1) - - // Scale the difficulty with time passed. - oldTarget := CompactToBig(curNode.bits) - newTarget := new(big.Int) - if shifts < maxShift { - newTarget.Lsh(oldTarget, shifts) - } else { - newTarget.Set(oneLsh256) - } - - // Limit new value to the proof of work limit. - if newTarget.Cmp(b.chainParams.PowLimit) > 0 { - newTarget.Set(b.chainParams.PowLimit) - } - - return BigToCompact(newTarget), nil + return b.chainParams.PowLimitBits, nil } // The block was mined within the desired timeframe, so diff --git a/blockchain/difficulty_test.go b/blockchain/difficulty_test.go index a272c624..771fac98 100644 --- a/blockchain/difficulty_test.go +++ b/blockchain/difficulty_test.go @@ -9,6 +9,7 @@ import ( "math/big" "runtime" "testing" + "time" "github.com/decred/dcrd/chaincfg" "github.com/decred/dcrd/wire" @@ -1050,3 +1051,162 @@ nextTest: } } } + +// TestMinDifficultyReduction ensures the code which results in reducing the +// minimum required difficulty, when the network params allow it, works as +// expected. +func TestMinDifficultyReduction(t *testing.T) { + // Create chain params based on simnet params, but set the fields related to + // proof-of-work difficulty to specific values expected by the tests. + params := chaincfg.SimNetParams + params.ReduceMinDifficulty = true + params.TargetTimePerBlock = time.Minute * 2 + params.MinDiffReductionTime = time.Minute * 10 // ~99.3% chance to be mined + params.WorkDiffAlpha = 1 + params.WorkDiffWindowSize = 144 + params.WorkDiffWindows = 20 + params.TargetTimespan = params.TargetTimePerBlock * + time.Duration(params.WorkDiffWindowSize) + params.RetargetAdjustmentFactor = 4 + + tests := []struct { + name string + timeAdjustment func(i int) time.Duration + numBlocks int64 + expectedDiff func(i int) uint32 + }{ + { + name: "genesis block", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: 1, + expectedDiff: func(i int) uint32 { return params.PowLimitBits }, + }, + { + name: "create difficulty spike - part 1", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize - 2, + expectedDiff: func(i int) uint32 { return 545259519 }, + }, + { + name: "create difficulty spike - part 2", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 545259519 }, + }, + { + name: "create difficulty spike - part 3", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 541100164 }, + }, + { + name: "create difficulty spike - part 4", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 537954654 }, + }, + { + name: "create difficulty spike - part 5", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 537141847 }, + }, + { + name: "create difficulty spike - part 6", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 536938645 }, + }, + { + name: "create difficulty spike - part 7", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 524428608 }, + }, + { + name: "create difficulty spike - part 8", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 521177424 }, + }, + { + name: "create difficulty spike - part 9", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 520364628 }, + }, + { + name: "create difficulty spike - part 10", + timeAdjustment: func(i int) time.Duration { return time.Second }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 520161429 }, + }, + { + name: "alternate min diff blocks", + timeAdjustment: func(i int) time.Duration { + if i%2 == 0 { + return params.MinDiffReductionTime + time.Second + } + return params.TargetTimePerBlock + }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { + if i%2 == 0 && i != 0 { + return params.PowLimitBits + } + return 507651392 + }, + }, + { + name: "interval of blocks taking twice the target time - part 1", + timeAdjustment: func(i int) time.Duration { + return params.TargetTimePerBlock * 2 + }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 509850141 }, + }, + { + name: "interval of blocks taking twice the target time - part 2", + timeAdjustment: func(i int) time.Duration { + return params.TargetTimePerBlock * 2 + }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 520138451 }, + }, + { + name: "interval of blocks taking twice the target time - part 3", + timeAdjustment: func(i int) time.Duration { + return params.TargetTimePerBlock * 2 + }, + numBlocks: params.WorkDiffWindowSize, + expectedDiff: func(i int) uint32 { return 520177692 }, + }, + } + + bc := newFakeChain(¶ms) + node := bc.bestChain.Tip() + blockTime := time.Unix(node.timestamp, 0) + for _, test := range tests { + for i := 0; i < int(test.numBlocks); i++ { + // Update the block time according to the test data and calculate + // the difficulty for the next block. + blockTime = blockTime.Add(test.timeAdjustment(i)) + diff, err := bc.calcNextRequiredDifficulty(node, blockTime) + if err != nil { + t.Fatalf("calcNextRequiredDifficulty: unexpected err: %v", err) + } + + // Ensure the calculated difficulty matches the expected value. + expectedDiff := test.expectedDiff(i) + if diff != expectedDiff { + t.Fatalf("calcNextRequiredDifficulty (%s): did not get "+ + "expected difficulty -- got %d, want %d", test.name, diff, + expectedDiff) + } + + node = newFakeNode(node, 1, 1, diff, blockTime) + bc.index.AddNode(node) + bc.bestChain.SetTip(node) + } + } +}