dcrd/chaincfg/init.go
Dave Collins 5218eb1761
multi: Add forced deployment result network param.
Currently, the lack of an entry for a given deployment in the chain
parameters for a given network is treated the same as the deployment
being active.  This behavior is primarily in order to support testing
without needing to go through the entire voting process on testing
networks such as the simulation network as well as allows rule changes
on newer versions of testnet networks that were previously accepted on
prior versions.

However, the existing approach has been pointed out on several occasions
as being surprising behavior by those working on new consensus changes
and it also means testing the voting process itself for new rule changes
prior to their release requires a cumbersome sequence of not so obvious
steps.  Further, it also makes those historical pruned rule changes
invisible to RPCs like getblockchaininfo.

In order to address the aforementioned inconveniences, this adds a new
parameter to the per-network chain parameters to specify a vote choice
that should always be considered as having been the majority result of a
vote and updates the relevant logic in blockchain accordingly to respect
it.

The new approach does not suffer from the aforementioned inconveniences
as it permits a simple single line modification to allow the voting
process to be tested as well as makes automatically activated historical
rules visible to the RPCs like getblockchaininfo.

Finally, this also adds previously pruned deployment parameters back to
the relevant network parameters with the new field set to force them to
be active.
2023-02-27 02:31:31 -06:00

191 lines
5.5 KiB
Go

// Copyright (c) 2017-2023 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package chaincfg
import (
"errors"
"fmt"
"math/bits"
"strings"
)
var (
errDuplicateVoteId = errors.New("duplicate vote id")
errInvalidMask = errors.New("invalid mask")
errNotConsecutive = errors.New("choices not consecutive")
errTooManyChoices = errors.New("too many choices")
errInvalidAbstain = errors.New("invalid abstain bits")
errInvalidBits = errors.New("invalid vote bits")
errMissingAbstain = errors.New("missing abstain choice")
errTooManyAbstain = errors.New("only one choice may have abstain flag")
errMissingNo = errors.New("missing no choice")
errTooManyNo = errors.New("only one choice may have no flag")
errBothFlags = errors.New("abstain and no flags are mutually exclusive")
errDuplicateChoiceId = errors.New("duplicate choice ID")
errMissingForcedChoice = errors.New("choice ID does not exist")
errForcedChoiceAbstain = errors.New("abstain is not a valid forced choice")
)
// consecOnes counts the number of consecutive 1 bits set.
func consecOnes(bits uint16) uint {
c := uint(0)
for v := bits; v != 0; c++ {
v &= (v << 1)
}
return c
}
// validateChoices ensures the provided choices conform to the required voting
// choice semantics.
func validateChoices(mask uint16, choices []Choice) error {
// Ensure the mask only consists of consecutive bits.
maskPopulationCount := uint(bits.OnesCount16(mask))
if consecOnes(mask) != maskPopulationCount {
return errInvalidMask
}
// Ensure there are not more choices than the mask bits can represent.
if len(choices) > 1<<maskPopulationCount {
return errTooManyChoices
}
var numAbstain, numNo int
dups := make(map[string]struct{})
s := uint(bits.TrailingZeros16(mask))
for index, choice := range choices {
// Ensure that choice 0 is the abstain vote.
if mask&choice.Bits == 0 && !choice.IsAbstain {
return errInvalidAbstain
}
// Ensure the bits for the choice are covered by the mask.
if mask&choice.Bits != choice.Bits {
return errInvalidBits
}
// Ensure the index is consecutive. This test is below the mask check
// for testing reasons. Leave it here.
if uint16(index) != choice.Bits>>s {
return errNotConsecutive
}
// Ensure only one of the choice type identification flags are set.
if choice.IsAbstain && choice.IsNo {
return errBothFlags
}
// Count flags.
if choice.IsAbstain {
numAbstain++
}
if choice.IsNo {
numNo++
}
// Ensure there are not any duplicates.
id := strings.ToLower(choice.Id)
if _, found := dups[id]; found {
return errDuplicateChoiceId
}
dups[id] = struct{}{}
}
// Ensure there is one and only one of each choice type identification flag
// set.
switch {
case numAbstain == 0:
return errMissingAbstain
case numAbstain > 1:
return errTooManyAbstain
case numNo == 0:
return errMissingNo
case numNo > 1:
return errTooManyNo
}
return nil
}
// validateForcedChoice ensures the provided forced choice adheres to the
// required semantics for the given choices. For example, it ensures the choice
// exists and that it is not the abstaining choice.
func validateForcedChoice(choiceID string, choices []Choice) error {
// No forced choice.
if choiceID == "" {
return nil
}
// The forced choice must be a choice that exists.
var foundChoice *Choice
for index, choice := range choices {
if choice.Id == choiceID {
foundChoice = &choices[index]
break
}
}
if foundChoice == nil {
str := "forced choice %q: %w"
return fmt.Errorf(str, choiceID, errMissingForcedChoice)
}
// The forced choice must not be the abstain choice.
if foundChoice.IsAbstain {
str := "forced choice %q: %w"
return fmt.Errorf(str, choiceID, errForcedChoiceAbstain)
}
return nil
}
// validateDeployments ensures all of deployments in the provided map adhere to
// the required semantics for deployment definitions. For example, it ensures
// there are no duplicate vote IDs and that all choices conform to the required
// voting choice semantics.
func validateDeployments(allDeployments map[uint32][]ConsensusDeployment) error {
// Ensure there are no duplicate vote IDs across all deployments.
dups := make(map[string]struct{})
for version, deployments := range allDeployments {
for index, deployment := range deployments {
voteID := strings.ToLower(deployment.Vote.Id)
if _, found := dups[voteID]; found {
return fmt.Errorf("version %d deployment index %d id %q: %w",
version, index, deployment.Vote.Id, errDuplicateVoteId)
}
dups[voteID] = struct{}{}
}
}
for version, deployments := range allDeployments {
for index, deployment := range deployments {
// Ensure the vote choices conform to all required semantics.
vote := &deployment.Vote
err := validateChoices(vote.Mask, vote.Choices)
if err != nil {
return fmt.Errorf("version %d deployment index %d id %q: %w",
version, index, vote.Id, err)
}
// Ensure the forced choice is a valid choice id (or unset).
err = validateForcedChoice(deployment.ForcedChoiceID, vote.Choices)
if err != nil {
return fmt.Errorf("version %d deployment index %d id %q: %w",
version, index, vote.Id, err)
}
}
}
return nil
}
func init() {
allParams := []*Params{MainNetParams(), TestNet3Params(), SimNetParams(),
RegNetParams()}
for _, params := range allParams {
if err := validateDeployments(params.Deployments); err != nil {
panic(fmt.Sprintf("invalid agenda on %s: %v", params.Name, err))
}
}
}