blockchain: Modify diff redux logic for testnet.

This modifies the logic which allows the reduction of difficulty on
testnet to match the upstream code such that after a block has not been
produced for a long period of time, as configured by the parameters, the
difficulty drops to the minimum allowed by the configured proof-of-work
limit for the network.

It should be noted that this would be a hard fork if the test network
had the code path enabled, however, since the code is disabled for
testnet2, it does not technically change the consensus rules for it.

The modified code will be active for testnet3.
This commit is contained in:
Dave Collins 2018-08-08 06:22:40 -05:00
parent 96a1a8546d
commit 74c9622e22
No known key found for this signature in database
GPG Key ID: B8904D9D9C93D1F2
2 changed files with 161 additions and 28 deletions

View File

@ -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

View File

@ -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(&params)
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)
}
}
}