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
- 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 Jun 7, 2023
1 parent 2db55eb commit 85e3701
Show file tree
Hide file tree
Showing 17 changed files with 1,187 additions and 85 deletions.
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.2.1
)

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.2.1 // indirect
)

replace (
Expand Down
170 changes: 170 additions & 0 deletions internal/blockchain/agendas_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1017,6 +1017,176 @@ 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

// blake3AnchorHeight is the height of the expected blake3 anchor block
// given the test conditions below.
blake3AnchorHeight := int64(stakeValidationHeight +
ruleChangeActivationInterval*3 - 1)

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
resetAnchor bool // whether or not to reset cached blake3 anchors
}{{
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,
}, {
name: "one before next rcai after active",
numNodes: ruleChangeActivationInterval - 2,
curActive: true,
nextActive: true,
}, {
name: "exactly next rcai after active",
numNodes: 1,
curActive: true,
nextActive: true,
}, {
name: "one after next rcai after active",
numNodes: 1,
curActive: true,
nextActive: true,
}, {
name: "one before 2nd rcai after active with anchor reset",
numNodes: ruleChangeActivationInterval - 2,
curActive: true,
nextActive: true,
resetAnchor: true,
}, {
name: "exactly 2nd rcai after active with anchor reset",
numNodes: 1,
curActive: true,
nextActive: true,
resetAnchor: true,
}, {
name: "one after 2nd rcai after active with anchor reset",
numNodes: 1,
curActive: true,
nextActive: true,
resetAnchor: 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
}

// Reset the cached blake3 anchor block when requested by the test flag.
// This helps ensure the logic that walks backwards to find the anchor
// works as intended.
if test.resetAnchor {
bc.cachedBlake3WorkDiffCandidateAnchor.Store(nil)
bc.cachedBlake3WorkDiffAnchor.Store(nil)
}

// Ensure the blake3 anchor block is the expected value once the agenda
// is active.
if test.nextActive {
wantAnchor := bc.bestChain.nodeByHeight(blake3AnchorHeight)
gotAnchor := bc.blake3WorkDiffAnchor(node)
if gotAnchor != wantAnchor {
t.Errorf("%s: mistmatched blake3 anchor - got: %s, want %s",
test.name, gotAnchor, wantAnchor)
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
23 changes: 23 additions & 0 deletions internal/blockchain/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"math/bits"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/decred/dcrd/blockchain/stake/v5"
Expand Down Expand Up @@ -167,6 +168,7 @@ type BlockChain struct {
deploymentData map[string]deploymentInfo
minKnownWork *uint256.Uint256
minTestNetTarget *big.Int
minTestNetDiffBits uint32
db database.DB
dbInfo *databaseInfo
chainParams *chaincfg.Params
Expand Down Expand Up @@ -271,6 +273,24 @@ type BlockChain struct {
calcVoterVersionIntervalCache map[[chainhash.HashSize]byte]uint32
calcStakeVersionCache map[[chainhash.HashSize]byte]uint32

// cachedBlake3WorkDiffCandidateAnchor houses a cached candidate anchor
// point to use for the difficulty algorithm defined in DCP0011 before the
// state of the blake3 proof of work agenda can be determined definitively.
//
// This is set based on heuristics that make it very likely to be the
// correct anchor point and exists to avoid additional work that would
// otherwise be required during the initial header sync.
cachedBlake3WorkDiffCandidateAnchor atomic.Pointer[blockNode]

// cachedBlake3WorkDiffAnchor houses a cached anchor point to use for the
// difficulty algorithm defined in DCP0011.
//
// It is only set when the blake3 proof of work agenda has been determined
// to be active and will be the block just prior to the activation of the
// agenda. It will not be set for networks where the agenda is always
// active such as the simulation network.
cachedBlake3WorkDiffAnchor atomic.Pointer[blockNode]

// bulkImportMode provides a mechanism to indicate that several validation
// checks can be avoided when bulk importing blocks already known to be valid.
// It is protected by the chain lock.
Expand Down Expand Up @@ -2372,10 +2392,12 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
// difficulty on testnet by ASICs and GPUs since it's not reasonable to
// require high-powered hardware to keep the test network running smoothly.
var minTestNetTarget *big.Int
var minTestNetDiffBits uint32
if params.Net == wire.TestNet3 {
// This equates to a maximum difficulty of 2^6 = 64.
const maxTestDiffShift = 6
minTestNetTarget = new(big.Int).Rsh(params.PowLimit, maxTestDiffShift)
minTestNetDiffBits = standalone.BigToCompact(minTestNetTarget)
}

// Either use the subsidy cache provided by the caller or create a new
Expand Down Expand Up @@ -2405,6 +2427,7 @@ func New(ctx context.Context, config *Config) (*BlockChain, error) {
deploymentData: deploymentData,
minKnownWork: minKnownWork,
minTestNetTarget: minTestNetTarget,
minTestNetDiffBits: minTestNetDiffBits,
db: config.DB,
chainParams: params,
timeSource: config.TimeSource,
Expand Down
Loading

0 comments on commit 85e3701

Please sign in to comment.