Skip to content

Commit

Permalink
multi: Implement DCP0011 PoW hash consensus vote.
Browse files Browse the repository at this point in the history
This implements the agenda for voting on changing the hash function to
BLAKE3, resetting the target difficulty, and changing the difficulty
algorithm to ASERT (Absolutely Scheduled Exponentially weighted Rising
Targets) as defined in DCP0011 along with consensus tests.

In terms of the overall effects, this includes updates to:

- The validation logic for the proof of work hash and required block
  difficulty
- Enforcement of block submission via the getwork and submitblock RPCs
- Mining template generation
- The output of the getwork and notifywork RPCs
- The internal CPU miner

Also note that this does not implement the block version bump that will
ultimately be needed by the mining code since there are multiple
consensus votes gated behind it and will therefore be done separately.

The following is an overview of the changes:

- Introduce convenience function for determining if the vote passed and
  is now active
- Introduce convenience function for determining whether or not the
  agenda is forced active on networks other than the main network
- Add per-network parameters to chaincfg for the new difficulty
  algorithm including the initial starting difficulty for BLAKE3 and the
  half life
- Modify block validation to enforce BLAKE3 as the proof of work hash
  algorithm in accordance with the state of the vote
- Modify block validation to enforce target difficulties per the ASERT
  algorithm based on a reset initial starting difficulty in accordance
  with the state of the vote
- Update internal CPU miner to solve blocks with either BLAKE256 or
  BLAKE3 in accordance with the state of the vote
- Update the getwork, notifywork, and submitblock RPCs to support BLAKE3
  in accordance with the state of the vote
- Add tests for determining if the agenda is active for both mainnet and
  testnet
- Add tests for the getwork RPC including submission of a block solved
  with BLAKE3
- Add tests to ensure proper behavior for the hash function and
  difficulty semantics before and after the vote agenda is active
  • Loading branch information
davecgh committed May 23, 2023
1 parent 42bbbba commit 4a21846
Show file tree
Hide file tree
Showing 23 changed files with 1,117 additions and 125 deletions.
26 changes: 16 additions & 10 deletions chaincfg/mainnetparams.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,28 @@ func MainNetParams() *Params {
},

// Chain parameters
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: mainPowLimit,
PowLimitBits: mainPowLimitBits,
ReduceMinDifficulty: false,
MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false
GenerateSupported: false,
MaximumBlockSizes: []int{393216},
MaxTxSize: 393216,
TargetTimePerBlock: time.Minute * 5,
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: mainPowLimit,
PowLimitBits: mainPowLimitBits,
ReduceMinDifficulty: false,
MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false
GenerateSupported: false,
MaximumBlockSizes: []int{393216},
MaxTxSize: 393216,
TargetTimePerBlock: time.Minute * 5,

// Version 1 difficulty algorithm (EMA + BLAKE256) parameters.
WorkDiffAlpha: 1,
WorkDiffWindowSize: 144,
WorkDiffWindows: 20,
TargetTimespan: time.Minute * 5 * 144, // TimePerBlock * WindowSize
RetargetAdjustmentFactor: 4,

// Version 2 difficulty algorithm (ASERT + BLAKE3) parameters.
WorkDiffV2Blake3StartBits: 0x1b00a5a6,
WorkDiffV2HalfLifeSecs: 43200, // 144 * TimePerBlock (12 hours)

// Subsidy parameters.
BaseSubsidy: 3119582664, // 21m
MulSubsidy: 100,
Expand Down
29 changes: 29 additions & 0 deletions chaincfg/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,30 +297,59 @@ type Params struct {
// block.
TargetTimePerBlock time.Duration

// -------------------------------------------------------------------------
// Version 1 difficulty algorithm (EMA + BLAKE256) parameters.
// -------------------------------------------------------------------------

// WorkDiffAlpha is the stake difficulty EMA calculation alpha (smoothing)
// value. It is different from a normal EMA alpha. Closer to 1 --> smoother.
//
// This only applies to the version 1 difficulty retarget algorithm.
WorkDiffAlpha int64

// WorkDiffWindowSize is the number of windows (intervals) used for calculation
// of the exponentially weighted average.
//
// This only applies to the version 1 difficulty retarget algorithm.
WorkDiffWindowSize int64

// WorkDiffWindows is the number of windows (intervals) used for calculation
// of the exponentially weighted average.
//
// This only applies to the version 1 difficulty retarget algorithm.
WorkDiffWindows int64

// TargetTimespan is the desired amount of time that should elapse
// before the block difficulty requirement is examined to determine how
// it should be changed in order to maintain the desired block
// generation rate. This value should correspond to the product of
// WorkDiffWindowSize and TimePerBlock above.
//
// This only applies to the version 1 difficulty retarget algorithm.
TargetTimespan time.Duration

// RetargetAdjustmentFactor is the adjustment factor used to limit
// the minimum and maximum amount of adjustment that can occur between
// difficulty retargets.
//
// This only applies to the version 1 difficulty retarget algorithm.
RetargetAdjustmentFactor int64

// -------------------------------------------------------------------------
// Version 2 difficulty algorithm (ASERT + BLAKE3) parameters.
// -------------------------------------------------------------------------

// WorkDiffV2Blake3StartBits is the starting difficulty bits to use for
// proof of work under BLAKE3.
WorkDiffV2Blake3StartBits uint32

// WorkDiffV2HalfLife is the number of seconds to use for the relaxation
// time when calculating how difficult it is to solve a block. The
// algorithm sets the difficulty exponentially such that it is halved or
// doubled for every multiple of this value the most recent block is behind
// or ahead of the ideal schedule.
WorkDiffV2HalfLifeSecs int64

// Subsidy parameters.
//
// Subsidy calculation for exponential reductions:
Expand Down
26 changes: 16 additions & 10 deletions chaincfg/regnetparams.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,22 +93,28 @@ func RegNetParams() *Params {
DNSSeeds: nil, // NOTE: There must NOT be any seeds.

// Chain parameters
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: regNetPowLimit,
PowLimitBits: regNetPowLimitBits,
ReduceMinDifficulty: false,
MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false
GenerateSupported: true,
MaximumBlockSizes: []int{1000000, 1310720},
MaxTxSize: 1000000,
TargetTimePerBlock: time.Second,
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: regNetPowLimit,
PowLimitBits: regNetPowLimitBits,
ReduceMinDifficulty: false,
MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false
GenerateSupported: true,
MaximumBlockSizes: []int{1000000, 1310720},
MaxTxSize: 1000000,
TargetTimePerBlock: time.Second,

// Version 1 difficulty algorithm (EMA + BLAKE256) parameters.
WorkDiffAlpha: 1,
WorkDiffWindowSize: 8,
WorkDiffWindows: 4,
TargetTimespan: time.Second * 8, // TimePerBlock * WindowSize
RetargetAdjustmentFactor: 4,

// Version 2 difficulty algorithm (ASERT + BLAKE3) parameters.
WorkDiffV2Blake3StartBits: regNetPowLimitBits,
WorkDiffV2HalfLifeSecs: 6, // 6 * TimePerBlock

// Subsidy parameters.
BaseSubsidy: 50000000000,
MulSubsidy: 100,
Expand Down
28 changes: 17 additions & 11 deletions chaincfg/simnetparams.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func SimNetParams() *Params {
Voters: 0,
FreshStake: 0,
Revocations: 0,
Timestamp: time.Unix(1401292357, 0), // 2009-01-08 20:54:25 -0600 CST
Timestamp: time.Unix(1401292357, 0), // 2014-05-28 15:52:37 +0000 UTC
PoolSize: 0,
Bits: simNetPowLimitBits,
SBits: 0,
Expand Down Expand Up @@ -93,22 +93,28 @@ func SimNetParams() *Params {
DNSSeeds: nil, // NOTE: There must NOT be any seeds.

// Chain parameters
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: simNetPowLimit,
PowLimitBits: simNetPowLimitBits,
ReduceMinDifficulty: false,
MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false
GenerateSupported: true,
MaximumBlockSizes: []int{1000000, 1310720},
MaxTxSize: 1000000,
TargetTimePerBlock: time.Second,
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: simNetPowLimit,
PowLimitBits: simNetPowLimitBits,
ReduceMinDifficulty: false,
MinDiffReductionTime: 0, // Does not apply since ReduceMinDifficulty false
GenerateSupported: true,
MaximumBlockSizes: []int{1000000, 1310720},
MaxTxSize: 1000000,
TargetTimePerBlock: time.Second,

// Version 1 difficulty algorithm (EMA + BLAKE256) parameters.
WorkDiffAlpha: 1,
WorkDiffWindowSize: 8,
WorkDiffWindows: 4,
TargetTimespan: time.Second * 8, // TimePerBlock * WindowSize
RetargetAdjustmentFactor: 4,

// Version 2 difficulty algorithm (ASERT + BLAKE3) parameters.
WorkDiffV2Blake3StartBits: simNetPowLimitBits,
WorkDiffV2HalfLifeSecs: 6, // 6 * TimePerBlock

// Subsidy parameters.
BaseSubsidy: 50000000000,
MulSubsidy: 100,
Expand Down
26 changes: 16 additions & 10 deletions chaincfg/testnetparams.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,22 +91,28 @@ func TestNet3Params() *Params {
//
// Note that the minimum difficulty reduction parameter only applies up
// to and including block height 962927.
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: testNetPowLimit,
PowLimitBits: testNetPowLimitBits,
ReduceMinDifficulty: true,
MinDiffReductionTime: time.Minute * 10, // ~99.3% chance to be mined before reduction
GenerateSupported: true,
MaximumBlockSizes: []int{1310720},
MaxTxSize: 1000000,
TargetTimePerBlock: time.Minute * 2,
GenesisBlock: &genesisBlock,
GenesisHash: genesisBlock.BlockHash(),
PowLimit: testNetPowLimit,
PowLimitBits: testNetPowLimitBits,
ReduceMinDifficulty: true,
MinDiffReductionTime: time.Minute * 10, // ~99.3% chance to be mined before reduction
GenerateSupported: true,
MaximumBlockSizes: []int{1310720},
MaxTxSize: 1000000,
TargetTimePerBlock: time.Minute * 2,

// Version 1 difficulty algorithm (EMA + BLAKE256) parameters.
WorkDiffAlpha: 1,
WorkDiffWindowSize: 144,
WorkDiffWindows: 20,
TargetTimespan: time.Minute * 2 * 144, // TimePerBlock * WindowSize
RetargetAdjustmentFactor: 4,

// Version 2 difficulty algorithm (ASERT + BLAKE3) parameters.
WorkDiffV2Blake3StartBits: testNetPowLimitBits,
WorkDiffV2HalfLifeSecs: 720, // 6 * TimePerBlock (12 minutes)

// Subsidy parameters.
BaseSubsidy: 2500000000, // 25 Coin
MulSubsidy: 100,
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ require (
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
golang.org/x/sys v0.5.0
golang.org/x/term v0.5.0
lukechampine.com/blake3 v1.1.7
)

require (
Expand All @@ -49,7 +50,6 @@ require (
github.com/decred/dcrd/hdkeychain/v3 v3.1.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
lukechampine.com/blake3 v1.1.7 // indirect
)

replace (
Expand Down
111 changes: 111 additions & 0 deletions internal/blockchain/agendas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,117 @@ func TestSubsidySplitDeployment(t *testing.T) {
testSubsidySplitDeployment(t, chaincfg.RegNetParams())
}

// testBlake3PowDeployment ensures the deployment of the blake3 proof of work
// agenda activates for the provided network parameters.
func testBlake3PowDeployment(t *testing.T, params *chaincfg.Params) {
// Clone the parameters so they can be mutated, find the correct deployment
// for the modified subsidy split agenda as well as the yes vote choice
// within it, and, finally, ensure it is always available to vote by
// removing the time constraints to prevent test failures when the real
// expiration time passes.
const voteID = chaincfg.VoteIDBlake3Pow
params = cloneParams(params)
deploymentVer, deployment := findDeployment(t, params, voteID)
yesChoice := findDeploymentChoice(t, deployment, "yes")
removeDeploymentTimeConstraints(deployment)

// Shorter versions of params for convenience.
stakeValidationHeight := uint32(params.StakeValidationHeight)
ruleChangeActivationInterval := params.RuleChangeActivationInterval

tests := []struct {
name string
numNodes uint32 // num fake nodes to create
curActive bool // whether agenda active for current block
nextActive bool // whether agenda active for NEXT block
}{{
name: "stake validation height",
numNodes: stakeValidationHeight,
curActive: false,
nextActive: false,
}, {
name: "started",
numNodes: ruleChangeActivationInterval,
curActive: false,
nextActive: false,
}, {
name: "lockedin",
numNodes: ruleChangeActivationInterval,
curActive: false,
nextActive: false,
}, {
name: "one before active",
numNodes: ruleChangeActivationInterval - 1,
curActive: false,
nextActive: true,
}, {
name: "exactly active",
numNodes: 1,
curActive: true,
nextActive: true,
}, {
name: "one after active",
numNodes: 1,
curActive: true,
nextActive: true,
}}

curTimestamp := time.Now()
bc := newFakeChain(params)
node := bc.bestChain.Tip()
for _, test := range tests {
for i := uint32(0); i < test.numNodes; i++ {
node = newFakeNode(node, int32(deploymentVer), deploymentVer, 0,
curTimestamp)

// Create fake votes that vote yes on the agenda to ensure it is
// activated.
for j := uint16(0); j < params.TicketsPerBlock; j++ {
node.votes = append(node.votes, stake.VoteVersionTuple{
Version: deploymentVer,
Bits: yesChoice.Bits | 0x01,
})
}
bc.index.AddNode(node)
bc.bestChain.SetTip(node)
curTimestamp = curTimestamp.Add(time.Second)
}

// Ensure the agenda reports the expected activation status for the
// current block.
gotActive, err := bc.isBlake3PowAgendaActive(node.parent)
if err != nil {
t.Errorf("%s: unexpected err: %v", test.name, err)
continue
}
if gotActive != test.curActive {
t.Errorf("%s: mismatched current active status - got: %v, want: %v",
test.name, gotActive, test.curActive)
continue
}

// Ensure the agenda reports the expected activation status for the NEXT
// block
gotActive, err = bc.IsBlake3PowAgendaActive(&node.hash)
if err != nil {
t.Errorf("%s: unexpected err: %v", test.name, err)
continue
}
if gotActive != test.nextActive {
t.Errorf("%s: mismatched next active status - got: %v, want: %v",
test.name, gotActive, test.nextActive)
continue
}
}
}

// TestBlake3PowDeployment ensures the deployment of the blake3 proof of work
// agenda activates as expected.
func TestBlake3PowDeployment(t *testing.T) {
testBlake3PowDeployment(t, chaincfg.MainNetParams())
testBlake3PowDeployment(t, chaincfg.RegNetParams())
}

// testSubsidySplitR2Deployment ensures the deployment of the 1/89/10 subsidy
// split agenda activates for the provided network parameters.
func testSubsidySplitR2Deployment(t *testing.T, params *chaincfg.Params) {
Expand Down
Loading

0 comments on commit 4a21846

Please sign in to comment.