This implements a new stake difficulty algorithm along with a voting
agenda for all networks to change and a comprehensive set of tests. It
also implements estimation using the new algorithm for the
estimatestakediff RPC.
The following is an overview of the changes:
- Add new agenda to vote on changing the stake difficulty algorithm to
all networks
- The version on mainnet is version 4
- The version on testnet and simnet is version 5
- Modifies the stake difficulty calculation function to calculate the
difficulty based on the result of the vote
- Modifies the stake difficulty estimation function to calculate the
difficulty based on the result of the vote
- Makes the stake difficulty estimation function concurrent safe
- Calls it directly from the RPC server instead of going through block
manager
- Removes no longer needed code from the block manager
- Generate new version blocks and reject old version blocks after a
super majority has been reached
- New block version on mainnet is version 4
- New block version on testnet and simnet is version 5
- Add tests for the supply estimation used in the new algorithm
- Add tests for the new algorithm calculations
- Add tests for the estimation based on the new algorithm
778 lines
24 KiB
Go
778 lines
24 KiB
Go
// Copyright (c) 2014 The btcsuite developers
|
|
// Copyright (c) 2015-2017 The Decred developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockchain
|
|
|
|
import (
|
|
"math/big"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/decred/dcrd/chaincfg"
|
|
"github.com/decred/dcrd/wire"
|
|
)
|
|
|
|
func TestBigToCompact(t *testing.T) {
|
|
tests := []struct {
|
|
in int64
|
|
out uint32
|
|
}{
|
|
{0, 0},
|
|
{-1, 25231360},
|
|
}
|
|
|
|
for x, test := range tests {
|
|
n := big.NewInt(test.in)
|
|
r := BigToCompact(n)
|
|
if r != test.out {
|
|
t.Errorf("TestBigToCompact test #%d failed: got %d want %d\n",
|
|
x, r, test.out)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompactToBig(t *testing.T) {
|
|
tests := []struct {
|
|
in uint32
|
|
out int64
|
|
}{
|
|
{10000000, 0},
|
|
}
|
|
|
|
for x, test := range tests {
|
|
n := CompactToBig(test.in)
|
|
want := big.NewInt(test.out)
|
|
if n.Cmp(want) != 0 {
|
|
t.Errorf("TestCompactToBig test #%d failed: got %d want %d\n",
|
|
x, n.Int64(), want.Int64())
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCalcWork(t *testing.T) {
|
|
tests := []struct {
|
|
in uint32
|
|
out int64
|
|
}{
|
|
{10000000, 0},
|
|
}
|
|
|
|
for x, test := range tests {
|
|
bits := uint32(test.in)
|
|
|
|
r := CalcWork(bits)
|
|
if r.Int64() != test.out {
|
|
t.Errorf("TestCalcWork test #%d failed: got %v want %d\n",
|
|
x, r.Int64(), test.out)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEstimateSupply ensures the supply estimation function used in the stake
|
|
// difficulty algorithm defined by DCP0001 works as expected.
|
|
func TestEstimateSupply(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// The parameters used for the supply estimation.
|
|
params := &chaincfg.MainNetParams
|
|
baseSubsidy := params.BaseSubsidy
|
|
reduxInterval := params.SubsidyReductionInterval
|
|
blockOneSubsidy := params.BlockOneSubsidy()
|
|
|
|
// intervalSubsidy is a helper function to return the full block subsidy
|
|
// for the given reduction interval.
|
|
intervalSubsidy := func(interval int) int64 {
|
|
subsidy := baseSubsidy
|
|
for i := 0; i < interval; i++ {
|
|
subsidy *= params.MulSubsidy
|
|
subsidy /= params.DivSubsidy
|
|
}
|
|
return subsidy
|
|
}
|
|
|
|
// Useful calculations for the tests below.
|
|
intervalOneSubsidy := intervalSubsidy(1)
|
|
intervalTwoSubsidy := intervalSubsidy(2)
|
|
reduxIntervalMinusOneSupply := blockOneSubsidy + (baseSubsidy * (reduxInterval - 2))
|
|
reduxIntervalTwoMinusOneSupply := reduxIntervalMinusOneSupply + (intervalOneSubsidy * reduxInterval)
|
|
|
|
tests := []struct {
|
|
height int64
|
|
expected int64
|
|
}{
|
|
{height: -1, expected: 0},
|
|
{height: 0, expected: 0},
|
|
{height: 1, expected: blockOneSubsidy},
|
|
{height: 2, expected: blockOneSubsidy + baseSubsidy},
|
|
{height: 3, expected: blockOneSubsidy + baseSubsidy*2},
|
|
{height: reduxInterval - 1, expected: reduxIntervalMinusOneSupply},
|
|
{height: reduxInterval, expected: reduxIntervalMinusOneSupply + intervalOneSubsidy},
|
|
{height: reduxInterval + 1, expected: reduxIntervalMinusOneSupply + intervalOneSubsidy*2},
|
|
{height: reduxInterval*2 - 1, expected: reduxIntervalTwoMinusOneSupply},
|
|
{height: reduxInterval * 2, expected: reduxIntervalTwoMinusOneSupply + intervalTwoSubsidy},
|
|
{height: reduxInterval*2 + 1, expected: reduxIntervalTwoMinusOneSupply + intervalTwoSubsidy*2},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
// Ensure the function to calculate the estimated supply is
|
|
// working properly.
|
|
gotSupply := estimateSupply(params, test.height)
|
|
if gotSupply != test.expected {
|
|
t.Errorf("estimateSupply (height %d): did not get "+
|
|
"expected supply - got %d, want %d", test.height,
|
|
gotSupply, test.expected)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// assertStakeDiffParams ensure the passed params have the values used in the
|
|
// tests related to stake difficulty calculation.
|
|
func assertStakeDiffParams(t *testing.T, params *chaincfg.Params) {
|
|
if params.MinimumStakeDiff != 200000000 {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
t.Fatalf("%s:%d -- expect params with minimum stake diff of "+
|
|
"%d, got %d", file, line, 200000000,
|
|
params.MinimumStakeDiff)
|
|
}
|
|
if params.TicketMaturity != 256 {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
t.Fatalf("%s:%d -- expect params with ticket maturity of "+
|
|
"%d, got %d", file, line, 256, params.TicketMaturity)
|
|
}
|
|
if params.StakeValidationHeight != 4096 {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
t.Fatalf("%s:%d -- expect params with stake val height of %d, "+
|
|
"got %d", file, line, 4096, params.StakeValidationHeight)
|
|
}
|
|
if params.StakeDiffWindowSize != 144 {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
t.Fatalf("%s:%d -- expect params with stake diff interval of "+
|
|
"%d, got %d", file, line, 144, params.StakeDiffWindowSize)
|
|
}
|
|
if params.TicketsPerBlock != 5 {
|
|
_, file, line, _ := runtime.Caller(1)
|
|
t.Fatalf("%s:%d -- expect params with tickets per block of "+
|
|
"%d, got %d", file, line, 5, params.TicketsPerBlock)
|
|
}
|
|
}
|
|
|
|
// TestCalcNextRequiredStakeDiffV2 ensure the stake diff calculation function
|
|
// for the algorithm defined by DCP0001 works as expected.
|
|
func TestCalcNextRequiredStakeDiffV2(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// ticketInfo is used to control the tests by specifying the details
|
|
// about how many fake blocks to create with the specified number of
|
|
// ticket and stake difficulty.
|
|
type ticketInfo struct {
|
|
numNodes uint32
|
|
newTickets uint8
|
|
stakeDiff int64
|
|
}
|
|
|
|
// Specify the params used in the tests and assert the values directly
|
|
// used by the tests are the expected ones. All of the test values will
|
|
// need to be updated if these parameters change since they are manually
|
|
// calculated based on them.
|
|
params := &chaincfg.MainNetParams
|
|
assertStakeDiffParams(t, params)
|
|
minStakeDiff := params.MinimumStakeDiff
|
|
ticketMaturity := uint32(params.TicketMaturity)
|
|
stakeValidationHeight := params.StakeValidationHeight
|
|
|
|
tests := []struct {
|
|
name string
|
|
ticketInfo []ticketInfo
|
|
expectedDiff int64
|
|
}{
|
|
{
|
|
// Next retarget is at 144. Prior to coinbase maturity,
|
|
// so will always be the minimum.
|
|
name: "genesis block",
|
|
ticketInfo: []ticketInfo{{0, 0, minStakeDiff}},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 144. Prior to coinbase maturity,
|
|
// so will always be the minimum.
|
|
name: "1st retarget, before coinbase",
|
|
ticketInfo: []ticketInfo{{143, 0, minStakeDiff}},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288.
|
|
//
|
|
// Tickets could not possibly have been bought yet, but
|
|
// ensure the algorithm handles it properly.
|
|
name: "coinbase maturity with impossible num tickets",
|
|
ticketInfo: []ticketInfo{{255, 20, minStakeDiff}},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288.
|
|
//
|
|
// Block 0 has no spendable outputs, so tickets could
|
|
// not have possibly been bought yet.
|
|
name: "coinbase maturity + 1",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff},
|
|
},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288.
|
|
name: "2nd retarget interval - 1, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{30, 20, minStakeDiff}, // 286
|
|
},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288.
|
|
name: "2nd retarget interval, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{31, 20, minStakeDiff}, // 287
|
|
},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 432.
|
|
name: "3rd retarget interval, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{175, 20, minStakeDiff}, // 431
|
|
},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 2304.
|
|
name: "16th retarget interval - 1, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2046, 20, minStakeDiff}, // 2302
|
|
},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 2304.
|
|
name: "16th retarget interval, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
},
|
|
expectedDiff: 208418769,
|
|
},
|
|
{
|
|
// Next retarget is at 2448.
|
|
name: "17th retarget interval - 1, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{143, 20, 208418769}, // 2446
|
|
},
|
|
expectedDiff: 208418769,
|
|
},
|
|
{
|
|
// Next retarget is at 2448.
|
|
name: "17th retarget interval, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{144, 20, 208418769}, // 2447
|
|
},
|
|
expectedDiff: 231326567,
|
|
},
|
|
{
|
|
// Next retarget is at 2592.
|
|
name: "17th retarget interval+1, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{144, 20, 208418769}, // 2447
|
|
{1, 20, 231326567}, // 2448
|
|
},
|
|
expectedDiff: 231326567,
|
|
},
|
|
{
|
|
// Next retarget is at 3456.
|
|
name: "24th retarget interval, varying demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{31, 20, minStakeDiff}, // 287
|
|
{144, 10, minStakeDiff}, // 431
|
|
{144, 20, minStakeDiff}, // 575
|
|
{144, 10, minStakeDiff}, // 719
|
|
{144, 20, minStakeDiff}, // 863
|
|
{144, 10, minStakeDiff}, // 1007
|
|
{144, 20, minStakeDiff}, // 1151
|
|
{144, 10, minStakeDiff}, // 1295
|
|
{144, 20, minStakeDiff}, // 1439
|
|
{144, 10, minStakeDiff}, // 1583
|
|
{144, 20, minStakeDiff}, // 1727
|
|
{144, 10, minStakeDiff}, // 1871
|
|
{144, 20, minStakeDiff}, // 2015
|
|
{144, 10, minStakeDiff}, // 2159
|
|
{144, 20, minStakeDiff}, // 2303
|
|
{144, 10, minStakeDiff}, // 2447
|
|
{144, 20, minStakeDiff}, // 2591
|
|
{144, 10, minStakeDiff}, // 2735
|
|
{144, 20, minStakeDiff}, // 2879
|
|
{144, 9, 201743368}, // 3023
|
|
{144, 20, 201093236}, // 3167
|
|
{144, 8, 222625877}, // 3311
|
|
{144, 20, 242331291}, // 3455
|
|
},
|
|
expectedDiff: 291317641,
|
|
},
|
|
{
|
|
// Next retarget is at 4176. Post stake validation
|
|
// height.
|
|
name: "29th retarget interval, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{144, 20, 208418769}, // 2447
|
|
{144, 20, 231326567}, // 2591
|
|
{144, 20, 272451490}, // 2735
|
|
{144, 20, 339388424}, // 2879
|
|
{144, 20, 445827839}, // 3023
|
|
{144, 20, 615949254}, // 3167
|
|
{144, 20, 892862990}, // 3311
|
|
{144, 20, 1354989669}, // 3455
|
|
{144, 20, 2148473276}, // 3599
|
|
{144, 20, 3552797658}, // 3743
|
|
{144, 20, 6116808441}, // 3887
|
|
{144, 20, 10947547379}, // 4031
|
|
{144, 20, 20338554623}, // 4175
|
|
},
|
|
expectedDiff: 22097687698,
|
|
},
|
|
{
|
|
// Next retarget is at 4176. Post stake validation
|
|
// height.
|
|
name: "29th retarget interval, 50% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{3919, 10, minStakeDiff}, // 4175
|
|
},
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 4464. Post stake validation
|
|
// height.
|
|
name: "31st retarget interval, waning demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{144, 20, 208418769}, // 2447
|
|
{144, 20, 231326567}, // 2591
|
|
{144, 20, 272451490}, // 2735
|
|
{144, 20, 339388424}, // 2879
|
|
{144, 20, 445827839}, // 3023
|
|
{144, 20, 615949254}, // 3167
|
|
{144, 20, 892862990}, // 3311
|
|
{144, 20, 1354989669}, // 3455
|
|
{144, 20, 2148473276}, // 3599
|
|
{144, 20, 3552797658}, // 3743
|
|
{144, 13, 6116808441}, // 3887
|
|
{144, 0, 10645659768}, // 4031
|
|
{144, 0, 18046712136}, // 4175
|
|
{144, 0, 22097687698}, // 4319
|
|
{144, 0, 22152524112}, // 4463
|
|
},
|
|
expectedDiff: 22207360526,
|
|
},
|
|
}
|
|
|
|
nextTest:
|
|
for _, test := range tests {
|
|
bc := newFakeChain(params)
|
|
bc.bestNode = genesisBlockNode(params)
|
|
|
|
// immatureTickets tracks which height the purchased tickets
|
|
// will mature and thus be eligible for admission to the live
|
|
// ticket pool.
|
|
immatureTickets := make(map[uint32]uint8)
|
|
var poolSize uint32
|
|
for _, ticketInfo := range test.ticketInfo {
|
|
// Ensure the test data isn't faking ticket purchases at
|
|
// an incorrect difficulty.
|
|
gotDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestNode)
|
|
if err != nil {
|
|
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
|
|
"unexpected error: %v", test.name, err)
|
|
continue nextTest
|
|
}
|
|
if gotDiff != ticketInfo.stakeDiff {
|
|
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
|
|
"did not get expected stake difficulty -- got "+
|
|
"%d, want %d", test.name, gotDiff,
|
|
ticketInfo.stakeDiff)
|
|
continue nextTest
|
|
}
|
|
|
|
for i := uint32(0); i < ticketInfo.numNodes; i++ {
|
|
// Make up a header.
|
|
nextHeight := bc.bestNode.header.Height + 1
|
|
header := &wire.BlockHeader{
|
|
Version: 4,
|
|
SBits: ticketInfo.stakeDiff,
|
|
Height: nextHeight,
|
|
FreshStake: ticketInfo.newTickets,
|
|
PoolSize: poolSize,
|
|
}
|
|
hash := header.BlockHash()
|
|
node := newBlockNode(header, &hash,
|
|
int64(nextHeight), nil, nil, nil)
|
|
node.parent = bc.bestNode
|
|
|
|
// Update the pool size for the next header.
|
|
// Notice how tickets that mature for this block
|
|
// do not show up in the pool size until the
|
|
// next block. This is correct behavior.
|
|
poolSize += uint32(immatureTickets[nextHeight])
|
|
delete(immatureTickets, nextHeight)
|
|
if int64(nextHeight) >= stakeValidationHeight {
|
|
poolSize -= uint32(params.TicketsPerBlock)
|
|
}
|
|
|
|
// Track maturity height for new ticket
|
|
// purchases.
|
|
maturityHeight := nextHeight + ticketMaturity
|
|
immatureTickets[maturityHeight] = ticketInfo.newTickets
|
|
|
|
// Update the chain to use the new fake node as
|
|
// the new best node.
|
|
bc.bestNode = node
|
|
}
|
|
}
|
|
|
|
// Ensure the calculated difficulty matches the expected value.
|
|
gotDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestNode)
|
|
if err != nil {
|
|
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
|
|
"unexpected error: %v", test.name, err)
|
|
continue
|
|
}
|
|
if gotDiff != test.expectedDiff {
|
|
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
|
|
"did not get expected stake difficulty -- got "+
|
|
"%d, want %d", test.name, gotDiff,
|
|
test.expectedDiff)
|
|
continue
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestEstimateNextStakeDiffV2 ensures the function that estimates the stake
|
|
// diff calculation for the algorithm defined by DCP0001 works as expected.
|
|
func TestEstimateNextStakeDiffV2(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
// ticketInfo is used to control the tests by specifying the details
|
|
// about how many fake blocks to create with the specified number of
|
|
// ticket and stake difficulty.
|
|
type ticketInfo struct {
|
|
numNodes uint32
|
|
newTickets uint8
|
|
stakeDiff int64
|
|
}
|
|
|
|
// Specify the params used in the tests and assert the values directly
|
|
// used by the tests are the expected ones. All of the test values will
|
|
// need to be updated if these parameters change since they are manually
|
|
// calculated based on them.
|
|
params := &chaincfg.MainNetParams
|
|
assertStakeDiffParams(t, params)
|
|
minStakeDiff := params.MinimumStakeDiff
|
|
ticketMaturity := uint32(params.TicketMaturity)
|
|
stakeValidationHeight := params.StakeValidationHeight
|
|
|
|
tests := []struct {
|
|
name string
|
|
ticketInfo []ticketInfo
|
|
newTickets int64
|
|
useMaxTickets bool
|
|
expectedDiff int64
|
|
}{
|
|
{
|
|
// Regardless of claiming tickets will be purchased, the
|
|
// resulting stake difficulty should be the minimum
|
|
// because the first retarget is before the start
|
|
// height.
|
|
name: "genesis block",
|
|
ticketInfo: []ticketInfo{{0, 0, minStakeDiff}},
|
|
newTickets: 2860,
|
|
useMaxTickets: false,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is 144. Resulting stake difficulty
|
|
// should be the minimum regardless of claimed ticket
|
|
// purchases because the previous pool size is still 0.
|
|
name: "during retarget, but before coinbase",
|
|
ticketInfo: []ticketInfo{{140, 0, minStakeDiff}},
|
|
newTickets: 20 * 3, // blocks 141, 142, and 143.
|
|
useMaxTickets: true,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288. Regardless of claiming
|
|
// tickets will be purchased, the resulting stake
|
|
// difficulty should be the min because the previous
|
|
// pool size is still 0.
|
|
name: "at coinbase maturity",
|
|
ticketInfo: []ticketInfo{{256, 0, minStakeDiff}},
|
|
useMaxTickets: true,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288. Regardless of actually
|
|
// purchasing tickets and claiming more tickets will be
|
|
// purchased, the resulting stake difficulty should be
|
|
// the min because the previous pool size is still 0.
|
|
name: "2nd retarget interval - 2, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{30, 20, minStakeDiff}, // 286
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 288. Still expect minimum stake
|
|
// difficulty since the raw result would be lower.
|
|
name: "2nd retarget interval - 1, 100% demand",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{31, 20, minStakeDiff}, // 287
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 432.
|
|
name: "3rd retarget interval, 100% demand, 1st block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{32, 20, minStakeDiff}, // 288
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 2304.
|
|
name: "16th retarget interval, 100% demand, 1st block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{1904, 20, minStakeDiff}, // 2160
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: 208418769,
|
|
},
|
|
{
|
|
// Next retarget is at 2304.
|
|
name: "16th retarget interval, 100% demand, 2nd block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{1905, 20, minStakeDiff}, // 2161
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: 208418769,
|
|
},
|
|
{
|
|
// Next retarget is at 2304.
|
|
name: "16th retarget interval, 100% demand, final block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: 208418769,
|
|
},
|
|
{
|
|
// Next retarget is at 3456.
|
|
name: "24th retarget interval, varying demand, 5th block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{31, 20, minStakeDiff}, // 287
|
|
{144, 10, minStakeDiff}, // 431
|
|
{144, 20, minStakeDiff}, // 575
|
|
{144, 10, minStakeDiff}, // 719
|
|
{144, 20, minStakeDiff}, // 863
|
|
{144, 10, minStakeDiff}, // 1007
|
|
{144, 20, minStakeDiff}, // 1151
|
|
{144, 10, minStakeDiff}, // 1295
|
|
{144, 20, minStakeDiff}, // 1439
|
|
{144, 10, minStakeDiff}, // 1583
|
|
{144, 20, minStakeDiff}, // 1727
|
|
{144, 10, minStakeDiff}, // 1871
|
|
{144, 20, minStakeDiff}, // 2015
|
|
{144, 10, minStakeDiff}, // 2159
|
|
{144, 20, minStakeDiff}, // 2303
|
|
{144, 10, minStakeDiff}, // 2447
|
|
{144, 20, minStakeDiff}, // 2591
|
|
{144, 10, minStakeDiff}, // 2735
|
|
{144, 20, minStakeDiff}, // 2879
|
|
{144, 9, 201743368}, // 3023
|
|
{144, 20, 201093236}, // 3167
|
|
{144, 8, 222625877}, // 3311
|
|
{5, 20, 242331291}, // 3316
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: 291317641,
|
|
},
|
|
{
|
|
// Next retarget is at 4176. Post stake validation
|
|
// height.
|
|
name: "29th retarget interval, 100% demand, 10th block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{144, 20, 208418769}, // 2447
|
|
{144, 20, 231326567}, // 2591
|
|
{144, 20, 272451490}, // 2735
|
|
{144, 20, 339388424}, // 2879
|
|
{144, 20, 445827839}, // 3023
|
|
{144, 20, 615949254}, // 3167
|
|
{144, 20, 892862990}, // 3311
|
|
{144, 20, 1354989669}, // 3455
|
|
{144, 20, 2148473276}, // 3599
|
|
{144, 20, 3552797658}, // 3743
|
|
{144, 20, 6116808441}, // 3887
|
|
{144, 20, 10947547379}, // 4031
|
|
{10, 20, 20338554623}, // 4041
|
|
},
|
|
useMaxTickets: true,
|
|
expectedDiff: 22097687698,
|
|
},
|
|
{
|
|
// Next retarget is at 4176. Post stake validation
|
|
// height.
|
|
name: "29th retarget interval, 50% demand, 23rd block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{3775, 10, minStakeDiff}, // 4031
|
|
{23, 10, minStakeDiff}, // 4054
|
|
},
|
|
newTickets: 1210, // 121 * 10
|
|
useMaxTickets: false,
|
|
expectedDiff: minStakeDiff,
|
|
},
|
|
{
|
|
// Next retarget is at 4464. Post stake validation
|
|
// height.
|
|
name: "31st retarget interval, waning demand, 117th block",
|
|
ticketInfo: []ticketInfo{
|
|
{256, 0, minStakeDiff}, // 256
|
|
{2047, 20, minStakeDiff}, // 2303
|
|
{144, 20, 208418769}, // 2447
|
|
{144, 20, 231326567}, // 2591
|
|
{144, 20, 272451490}, // 2735
|
|
{144, 20, 339388424}, // 2879
|
|
{144, 20, 445827839}, // 3023
|
|
{144, 20, 615949254}, // 3167
|
|
{144, 20, 892862990}, // 3311
|
|
{144, 20, 1354989669}, // 3455
|
|
{144, 20, 2148473276}, // 3599
|
|
{144, 20, 3552797658}, // 3743
|
|
{144, 13, 6116808441}, // 3887
|
|
{144, 0, 10645659768}, // 4031
|
|
{144, 0, 18046712136}, // 4175
|
|
{144, 0, 22097687698}, // 4319
|
|
{117, 0, 22152524112}, // 4436
|
|
},
|
|
useMaxTickets: false,
|
|
newTickets: 0,
|
|
expectedDiff: 22207360526,
|
|
},
|
|
}
|
|
|
|
nextTest:
|
|
for _, test := range tests {
|
|
bc := newFakeChain(params)
|
|
bc.bestNode = genesisBlockNode(params)
|
|
|
|
// immatureTickets track which height the purchased tickets will
|
|
// mature and thus be eligible for admission to the live ticket
|
|
// pool.
|
|
immatureTickets := make(map[uint32]uint8)
|
|
var poolSize uint32
|
|
for _, ticketInfo := range test.ticketInfo {
|
|
// Ensure the test data isn't faking ticket purchases at
|
|
// an incorrect difficulty.
|
|
reqDiff, err := bc.calcNextRequiredStakeDifficultyV2(bc.bestNode)
|
|
if err != nil {
|
|
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
|
|
"unexpected error: %v", test.name, err)
|
|
continue nextTest
|
|
}
|
|
if ticketInfo.stakeDiff != reqDiff {
|
|
t.Errorf("calcNextRequiredStakeDifficultyV2 (%s): "+
|
|
"test data has incorrect stake difficulty: "+
|
|
"has %d, requires %d", test.name,
|
|
ticketInfo.stakeDiff, reqDiff)
|
|
continue nextTest
|
|
}
|
|
|
|
for i := uint32(0); i < ticketInfo.numNodes; i++ {
|
|
// Make up a header.
|
|
nextHeight := bc.bestNode.header.Height + 1
|
|
header := &wire.BlockHeader{
|
|
Version: 4,
|
|
SBits: ticketInfo.stakeDiff,
|
|
Height: nextHeight,
|
|
FreshStake: ticketInfo.newTickets,
|
|
PoolSize: poolSize,
|
|
}
|
|
hash := header.BlockHash()
|
|
node := newBlockNode(header, &hash,
|
|
int64(nextHeight), nil, nil, nil)
|
|
node.parent = bc.bestNode
|
|
|
|
// Update the pool size for the next header.
|
|
// Notice how tickets that mature for this block
|
|
// do not show up in the pool size until the
|
|
// next block. This is correct behavior.
|
|
poolSize += uint32(immatureTickets[nextHeight])
|
|
delete(immatureTickets, nextHeight)
|
|
if int64(nextHeight) >= stakeValidationHeight {
|
|
poolSize -= uint32(params.TicketsPerBlock)
|
|
}
|
|
|
|
// Track maturity height for new ticket
|
|
// purchases.
|
|
maturityHeight := nextHeight + ticketMaturity
|
|
immatureTickets[maturityHeight] = ticketInfo.newTickets
|
|
|
|
// Update the chain to use the new fake node as
|
|
// the new best node.
|
|
bc.bestNode = node
|
|
}
|
|
}
|
|
|
|
// Ensure the calculated difficulty matches the expected value.
|
|
gotDiff, err := bc.estimateNextStakeDifficultyV2(bc.bestNode,
|
|
test.newTickets, test.useMaxTickets)
|
|
if err != nil {
|
|
t.Errorf("estimateNextStakeDifficultyV2 (%s): "+
|
|
"unexpected error: %v", test.name, err)
|
|
continue
|
|
}
|
|
if gotDiff != test.expectedDiff {
|
|
t.Errorf("estimateNextStakeDifficultyV2 (%s): did not "+
|
|
"get expected stake difficulty -- got %d, "+
|
|
"want %d", test.name, gotDiff, test.expectedDiff)
|
|
continue
|
|
}
|
|
}
|
|
}
|