mirror of
https://github.com/btcsuite/btcd.git
synced 2025-03-10 17:26:07 +01:00
Merge pull request #1700 from Roasbeef/bip-8-prep
chaincfg+blockchain: abstract/refactor BIP 9 version bits implementation to work w/ BIP 8 block heights
This commit is contained in:
commit
588c0714c3
11 changed files with 896 additions and 186 deletions
9
Makefile
9
Makefile
|
@ -15,7 +15,8 @@ DEPGET := cd /tmp && GO111MODULE=on go get -v
|
|||
GOBUILD := GO111MODULE=on go build -v
|
||||
GOINSTALL := GO111MODULE=on go install -v
|
||||
DEV_TAGS := rpctest
|
||||
GOTEST := GO111MODULE=on go test -v -tags=$(DEV_TAGS)
|
||||
GOTEST_DEV = GO111MODULE=on go test -v -tags=$(DEV_TAGS)
|
||||
GOTEST := GO111MODULE=on go test -v
|
||||
|
||||
GOFILES_NOVENDOR = $(shell find . -type f -name '*.go' -not -path "./vendor/*")
|
||||
|
||||
|
@ -78,9 +79,9 @@ check: unit
|
|||
|
||||
unit:
|
||||
@$(call print, "Running unit tests.")
|
||||
$(GOTEST) ./... -test.timeout=20m
|
||||
cd btcutil; $(GOTEST) ./... -test.timeout=20m
|
||||
cd btcutil/psbt; $(GOTEST) ./... -test.timeout=20m
|
||||
$(GOTEST_DEV) ./... -test.timeout=20m
|
||||
cd btcutil; $(GOTEST_DEV) ./... -test.timeout=20m
|
||||
cd btcutil/psbt; $(GOTEST_DEV) ./... -test.timeout=20m
|
||||
|
||||
unit-cover: $(GOACC_BIN)
|
||||
@$(call print, "Running unit coverage tests.")
|
||||
|
|
|
@ -11,12 +11,12 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -1757,6 +1757,20 @@ func New(config *Config) (*BlockChain, error) {
|
|||
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
|
||||
}
|
||||
|
||||
// Ensure all the deployments are synchronized with our clock if
|
||||
// needed.
|
||||
for _, deployment := range b.chainParams.Deployments {
|
||||
deploymentStarter := deployment.DeploymentStarter
|
||||
if clockStarter, ok := deploymentStarter.(chaincfg.ClockConsensusDeploymentStarter); ok {
|
||||
clockStarter.SynchronizeClock(&b)
|
||||
}
|
||||
|
||||
deploymentEnder := deployment.DeploymentEnder
|
||||
if clockEnder, ok := deploymentEnder.(chaincfg.ClockConsensusDeploymentEnder); ok {
|
||||
clockEnder.SynchronizeClock(&b)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the chain state from the passed database. When the db
|
||||
// does not yet contain any chain state, both it and the chain state
|
||||
// will be initialized to contain only the genesis block.
|
||||
|
|
|
@ -14,13 +14,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
_ "github.com/btcsuite/btcd/database/ffldb"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -357,7 +357,7 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
|
|||
targetTimespan := int64(params.TargetTimespan / time.Second)
|
||||
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
|
||||
adjustmentFactor := params.RetargetAdjustmentFactor
|
||||
return &BlockChain{
|
||||
b := &BlockChain{
|
||||
chainParams: params,
|
||||
timeSource: NewMedianTime(),
|
||||
minRetargetTimespan: targetTimespan / adjustmentFactor,
|
||||
|
@ -368,6 +368,20 @@ func newFakeChain(params *chaincfg.Params) *BlockChain {
|
|||
warningCaches: newThresholdCaches(vbNumBits),
|
||||
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
|
||||
}
|
||||
|
||||
for _, deployment := range params.Deployments {
|
||||
deploymentStarter := deployment.DeploymentStarter
|
||||
if clockStarter, ok := deploymentStarter.(chaincfg.ClockConsensusDeploymentStarter); ok {
|
||||
clockStarter.SynchronizeClock(b)
|
||||
}
|
||||
|
||||
deploymentEnder := deployment.DeploymentEnder
|
||||
if clockEnder, ok := deploymentEnder.(chaincfg.ClockConsensusDeploymentEnder); ok {
|
||||
clockEnder.SynchronizeClock(b)
|
||||
}
|
||||
}
|
||||
|
||||
return b
|
||||
}
|
||||
|
||||
// newFakeNode creates a block node connected to the passed parent with the
|
||||
|
|
|
@ -6,8 +6,10 @@ package blockchain
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// ThresholdState define the various threshold states used when voting on
|
||||
|
@ -66,14 +68,13 @@ func (t ThresholdState) String() string {
|
|||
// thresholdConditionChecker provides a generic interface that is invoked to
|
||||
// determine when a consensus rule change threshold should be changed.
|
||||
type thresholdConditionChecker interface {
|
||||
// BeginTime returns the unix timestamp for the median block time after
|
||||
// which voting on a rule change starts (at the next window).
|
||||
BeginTime() uint64
|
||||
// HasStarted returns true if based on the passed block blockNode the
|
||||
// consensus is eligible for deployment.
|
||||
HasStarted(*blockNode) bool
|
||||
|
||||
// EndTime returns the unix timestamp for the median block time after
|
||||
// which an attempted rule change fails if it has not already been
|
||||
// locked in or activated.
|
||||
EndTime() uint64
|
||||
// HasEnded returns true if the target consensus rule change has
|
||||
// expired or timed out.
|
||||
HasEnded(*blockNode) bool
|
||||
|
||||
// RuleChangeActivationThreshold is the number of blocks for which the
|
||||
// condition must be true in order to lock in a rule change.
|
||||
|
@ -83,10 +84,23 @@ type thresholdConditionChecker interface {
|
|||
// state retarget window.
|
||||
MinerConfirmationWindow() uint32
|
||||
|
||||
// Condition returns whether or not the rule change activation condition
|
||||
// has been met. This typically involves checking whether or not the
|
||||
// bit associated with the condition is set, but can be more complex as
|
||||
// needed.
|
||||
// EligibleToActivate returns true if a custom deployment can
|
||||
// transition from the LockedIn to the Active state. For normal
|
||||
// deployments, this always returns true. However, some deployments add
|
||||
// extra rules like a minimum activation height, which can be
|
||||
// abstracted into a generic arbitrary check at the final state via
|
||||
// this method.
|
||||
EligibleToActivate(*blockNode) bool
|
||||
|
||||
// IsSpeedy returns true if this is to be a "speedy" deployment. A
|
||||
// speedy deployment differs from a regular one in that only after a
|
||||
// miner block confirmation window can the deployment expire.
|
||||
IsSpeedy() bool
|
||||
|
||||
// Condition returns whether or not the rule change activation
|
||||
// condition has been met. This typically involves checking whether or
|
||||
// not the bit associated with the condition is set, but can be more
|
||||
// complex as needed.
|
||||
Condition(*blockNode) (bool, error)
|
||||
}
|
||||
|
||||
|
@ -121,6 +135,120 @@ func newThresholdCaches(numCaches uint32) []thresholdStateCache {
|
|||
return caches
|
||||
}
|
||||
|
||||
// PastMedianTime returns the past median time from the PoV of the passed block
|
||||
// header. The past median time is the median time of the 11 blocks prior to
|
||||
// the passed block header.
|
||||
//
|
||||
// NOTE: This is part of the chainfg.BlockClock interface
|
||||
func (b *BlockChain) PastMedianTime(blockHeader *wire.BlockHeader) (time.Time, error) {
|
||||
prevHash := blockHeader.PrevBlock
|
||||
prevNode := b.index.LookupNode(&prevHash)
|
||||
|
||||
// If we can't find the previous node, then we can't compute the block
|
||||
// time since it requires us to walk backwards from this node.
|
||||
if prevNode == nil {
|
||||
return time.Time{}, fmt.Errorf("blockHeader(%v) has no "+
|
||||
"previous node", blockHeader.BlockHash())
|
||||
}
|
||||
|
||||
blockNode := newBlockNode(blockHeader, prevNode)
|
||||
|
||||
return blockNode.CalcPastMedianTime(), nil
|
||||
}
|
||||
|
||||
// thresholdStateTransition given a state, a previous node, and a toeholds
|
||||
// checker, this function transitions to the next state as defined by BIP 009.
|
||||
// This state transition function is also aware of the "speedy trial"
|
||||
// modifications made to BIP 0009 as part of the taproot softfork activation.
|
||||
func thresholdStateTransition(state ThresholdState, prevNode *blockNode,
|
||||
checker thresholdConditionChecker,
|
||||
confirmationWindow int32) (ThresholdState, error) {
|
||||
|
||||
switch state {
|
||||
case ThresholdDefined:
|
||||
// The deployment of the rule change fails if it
|
||||
// expires before it is accepted and locked in. However
|
||||
// speed deployments can only transition to failed
|
||||
// after a confirmation window.
|
||||
if !checker.IsSpeedy() && checker.HasEnded(prevNode) {
|
||||
state = ThresholdFailed
|
||||
break
|
||||
}
|
||||
|
||||
// The state for the rule moves to the started state
|
||||
// once its start time has been reached (and it hasn't
|
||||
// already expired per the above).
|
||||
if checker.HasStarted(prevNode) {
|
||||
state = ThresholdStarted
|
||||
}
|
||||
|
||||
case ThresholdStarted:
|
||||
// The deployment of the rule change fails if it
|
||||
// expires before it is accepted and locked in, but
|
||||
// only if this deployment isn't speedy.
|
||||
if !checker.IsSpeedy() && checker.HasEnded(prevNode) {
|
||||
state = ThresholdFailed
|
||||
break
|
||||
}
|
||||
|
||||
// At this point, the rule change is still being voted
|
||||
// on by the miners, so iterate backwards through the
|
||||
// confirmation window to count all of the votes in it.
|
||||
var count uint32
|
||||
countNode := prevNode
|
||||
for i := int32(0); i < confirmationWindow; i++ {
|
||||
condition, err := checker.Condition(countNode)
|
||||
if err != nil {
|
||||
return ThresholdFailed, err
|
||||
}
|
||||
if condition {
|
||||
count++
|
||||
}
|
||||
|
||||
// Get the previous block node.
|
||||
countNode = countNode.parent
|
||||
}
|
||||
|
||||
switch {
|
||||
// The state is locked in if the number of blocks in the
|
||||
// period that voted for the rule change meets the
|
||||
// activation threshold.
|
||||
case count >= checker.RuleChangeActivationThreshold():
|
||||
state = ThresholdLockedIn
|
||||
|
||||
// If this is a speedy deployment, we didn't meet the
|
||||
// threshold above, and the deployment has expired, then
|
||||
// we transition to failed.
|
||||
case checker.IsSpeedy() && checker.HasEnded(prevNode):
|
||||
state = ThresholdFailed
|
||||
}
|
||||
|
||||
case ThresholdLockedIn:
|
||||
// At this point, we'll consult the deployment see if a
|
||||
// custom deployment has any other arbitrary conditions
|
||||
// that need to pass before execution. This might be a
|
||||
// minimum activation height or another policy.
|
||||
//
|
||||
// If we aren't eligible to active yet, then we'll just
|
||||
// stay in the locked in position.
|
||||
if !checker.EligibleToActivate(prevNode) {
|
||||
state = ThresholdLockedIn
|
||||
} else {
|
||||
// The new rule becomes active when its
|
||||
// previous state was locked in assuming it's
|
||||
// now eligible to activate.
|
||||
state = ThresholdActive
|
||||
}
|
||||
|
||||
// Nothing to do if the previous state is active or failed since
|
||||
// they are both terminal states.
|
||||
case ThresholdActive:
|
||||
case ThresholdFailed:
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// thresholdState returns the current rule change threshold state for the block
|
||||
// AFTER the given node and deployment ID. The cache is used to ensure the
|
||||
// threshold states for previous windows are only calculated once.
|
||||
|
@ -150,13 +278,9 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit
|
|||
break
|
||||
}
|
||||
|
||||
// The start and expiration times are based on the median block
|
||||
// time, so calculate it now.
|
||||
medianTime := prevNode.CalcPastMedianTime()
|
||||
|
||||
// The state is simply defined if the start time hasn't been
|
||||
// been reached yet.
|
||||
if uint64(medianTime.Unix()) < checker.BeginTime() {
|
||||
if !checker.HasStarted(prevNode) {
|
||||
cache.Update(&prevNode.hash, ThresholdDefined)
|
||||
break
|
||||
}
|
||||
|
@ -185,70 +309,17 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit
|
|||
|
||||
// Since each threshold state depends on the state of the previous
|
||||
// window, iterate starting from the oldest unknown window.
|
||||
var err error
|
||||
for neededNum := len(neededStates) - 1; neededNum >= 0; neededNum-- {
|
||||
prevNode := neededStates[neededNum]
|
||||
|
||||
switch state {
|
||||
case ThresholdDefined:
|
||||
// The deployment of the rule change fails if it expires
|
||||
// before it is accepted and locked in.
|
||||
medianTime := prevNode.CalcPastMedianTime()
|
||||
medianTimeUnix := uint64(medianTime.Unix())
|
||||
if medianTimeUnix >= checker.EndTime() {
|
||||
state = ThresholdFailed
|
||||
break
|
||||
}
|
||||
|
||||
// The state for the rule moves to the started state
|
||||
// once its start time has been reached (and it hasn't
|
||||
// already expired per the above).
|
||||
if medianTimeUnix >= checker.BeginTime() {
|
||||
state = ThresholdStarted
|
||||
}
|
||||
|
||||
case ThresholdStarted:
|
||||
// The deployment of the rule change fails if it expires
|
||||
// before it is accepted and locked in.
|
||||
medianTime := prevNode.CalcPastMedianTime()
|
||||
if uint64(medianTime.Unix()) >= checker.EndTime() {
|
||||
state = ThresholdFailed
|
||||
break
|
||||
}
|
||||
|
||||
// At this point, the rule change is still being voted
|
||||
// on by the miners, so iterate backwards through the
|
||||
// confirmation window to count all of the votes in it.
|
||||
var count uint32
|
||||
countNode := prevNode
|
||||
for i := int32(0); i < confirmationWindow; i++ {
|
||||
condition, err := checker.Condition(countNode)
|
||||
if err != nil {
|
||||
return ThresholdFailed, err
|
||||
}
|
||||
if condition {
|
||||
count++
|
||||
}
|
||||
|
||||
// Get the previous block node.
|
||||
countNode = countNode.parent
|
||||
}
|
||||
|
||||
// The state is locked in if the number of blocks in the
|
||||
// period that voted for the rule change meets the
|
||||
// activation threshold.
|
||||
if count >= checker.RuleChangeActivationThreshold() {
|
||||
state = ThresholdLockedIn
|
||||
}
|
||||
|
||||
case ThresholdLockedIn:
|
||||
// The new rule becomes active when its previous state
|
||||
// was locked in.
|
||||
state = ThresholdActive
|
||||
|
||||
// Nothing to do if the previous state is active or failed since
|
||||
// they are both terminal states.
|
||||
case ThresholdActive:
|
||||
case ThresholdFailed:
|
||||
// Based on the current state, the previous node, and the
|
||||
// condition checker, transition to the next threshold state.
|
||||
state, err = thresholdStateTransition(
|
||||
state, prevNode, checker, confirmationWindow,
|
||||
)
|
||||
if err != nil {
|
||||
return state, err
|
||||
}
|
||||
|
||||
// Update the cache to avoid recalculating the state in the
|
||||
|
|
|
@ -132,3 +132,187 @@ nextTest:
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
type customDeploymentChecker struct {
|
||||
started bool
|
||||
ended bool
|
||||
|
||||
eligible bool
|
||||
|
||||
isSpeedy bool
|
||||
|
||||
conditionTrue bool
|
||||
|
||||
activationThreshold uint32
|
||||
minerWindow uint32
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) HasStarted(_ *blockNode) bool {
|
||||
return c.started
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) HasEnded(_ *blockNode) bool {
|
||||
return c.ended
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) RuleChangeActivationThreshold() uint32 {
|
||||
return c.activationThreshold
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) MinerConfirmationWindow() uint32 {
|
||||
return c.minerWindow
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) EligibleToActivate(_ *blockNode) bool {
|
||||
return c.eligible
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) IsSpeedy() bool {
|
||||
return c.isSpeedy
|
||||
}
|
||||
|
||||
func (c customDeploymentChecker) Condition(_ *blockNode) (bool, error) {
|
||||
return c.conditionTrue, nil
|
||||
}
|
||||
|
||||
// TestThresholdStateTransition tests that the thresholdStateTransition
|
||||
// properly implements the BIP 009 state machine, along with the speedy trial
|
||||
// augments.
|
||||
func TestThresholdStateTransition(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Prev node always points back to itself, effectively creating an
|
||||
// infinite chain for the purposes of this test.
|
||||
prevNode := &blockNode{}
|
||||
prevNode.parent = prevNode
|
||||
|
||||
window := int32(2016)
|
||||
|
||||
testCases := []struct {
|
||||
currentState ThresholdState
|
||||
nextState ThresholdState
|
||||
|
||||
checker thresholdConditionChecker
|
||||
}{
|
||||
// From defined, we stay there if we haven't started the
|
||||
// window, and the window hasn't ended.
|
||||
{
|
||||
currentState: ThresholdDefined,
|
||||
nextState: ThresholdDefined,
|
||||
|
||||
checker: &customDeploymentChecker{},
|
||||
},
|
||||
|
||||
// From defined, we go to failed if the window has ended, and
|
||||
// this isn't a speedy trial.
|
||||
{
|
||||
currentState: ThresholdDefined,
|
||||
nextState: ThresholdFailed,
|
||||
|
||||
checker: &customDeploymentChecker{
|
||||
ended: true,
|
||||
},
|
||||
},
|
||||
|
||||
// From defined, even if the window has ended, we go to started
|
||||
// if this isn't a speedy trial.
|
||||
{
|
||||
currentState: ThresholdDefined,
|
||||
nextState: ThresholdStarted,
|
||||
|
||||
checker: &customDeploymentChecker{
|
||||
started: true,
|
||||
},
|
||||
},
|
||||
|
||||
// From started, we go to failed if this isn't speed, and the
|
||||
// deployment has ended.
|
||||
{
|
||||
currentState: ThresholdStarted,
|
||||
nextState: ThresholdFailed,
|
||||
|
||||
checker: &customDeploymentChecker{
|
||||
ended: true,
|
||||
},
|
||||
},
|
||||
|
||||
// From started, we go to locked in if the window passed the
|
||||
// condition.
|
||||
{
|
||||
currentState: ThresholdStarted,
|
||||
nextState: ThresholdLockedIn,
|
||||
|
||||
checker: &customDeploymentChecker{
|
||||
started: true,
|
||||
conditionTrue: true,
|
||||
},
|
||||
},
|
||||
|
||||
// From started, we go to failed if this is a speedy trial, and
|
||||
// the condition wasn't met in the window.
|
||||
{
|
||||
currentState: ThresholdStarted,
|
||||
nextState: ThresholdFailed,
|
||||
|
||||
checker: &customDeploymentChecker{
|
||||
started: true,
|
||||
ended: true,
|
||||
isSpeedy: true,
|
||||
conditionTrue: false,
|
||||
activationThreshold: 1815,
|
||||
},
|
||||
},
|
||||
|
||||
// From locked in, we go straight to active is this isn't a
|
||||
// speedy trial.
|
||||
{
|
||||
currentState: ThresholdLockedIn,
|
||||
nextState: ThresholdActive,
|
||||
|
||||
checker: &customDeploymentChecker{
|
||||
eligible: true,
|
||||
},
|
||||
},
|
||||
|
||||
// From locked in, we remain in locked in if we're not yet
|
||||
// eligible to activate.
|
||||
{
|
||||
currentState: ThresholdLockedIn,
|
||||
nextState: ThresholdLockedIn,
|
||||
|
||||
checker: &customDeploymentChecker{},
|
||||
},
|
||||
|
||||
// From active, we always stay here.
|
||||
{
|
||||
currentState: ThresholdActive,
|
||||
nextState: ThresholdActive,
|
||||
|
||||
checker: &customDeploymentChecker{},
|
||||
},
|
||||
|
||||
// From failed, we always stay here.
|
||||
{
|
||||
currentState: ThresholdFailed,
|
||||
nextState: ThresholdFailed,
|
||||
|
||||
checker: &customDeploymentChecker{},
|
||||
},
|
||||
}
|
||||
for i, testCase := range testCases {
|
||||
nextState, err := thresholdStateTransition(
|
||||
testCase.currentState, prevNode, testCase.checker,
|
||||
window,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("#%v: unable to transition to next "+
|
||||
"state: %v", i, err)
|
||||
}
|
||||
|
||||
if nextState != testCase.nextState {
|
||||
t.Fatalf("#%v: incorrect state transition: "+
|
||||
"expected %v got %v", i, testCase.nextState,
|
||||
nextState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
)
|
||||
|
||||
|
@ -42,27 +40,26 @@ type bitConditionChecker struct {
|
|||
// interface.
|
||||
var _ thresholdConditionChecker = bitConditionChecker{}
|
||||
|
||||
// BeginTime returns the unix timestamp for the median block time after which
|
||||
// voting on a rule change starts (at the next window).
|
||||
// HasStarted returns true if based on the passed block blockNode the consensus
|
||||
// is eligible for deployment.
|
||||
//
|
||||
// Since this implementation checks for unknown rules, it returns 0 so the rule
|
||||
// Since this implementation checks for unknown rules, it returns true so
|
||||
// is always treated as active.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) BeginTime() uint64 {
|
||||
return 0
|
||||
func (c bitConditionChecker) HasStarted(_ *blockNode) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// EndTime returns the unix timestamp for the median block time after which an
|
||||
// attempted rule change fails if it has not already been locked in or
|
||||
// activated.
|
||||
// HasStarted returns true if based on the passed block blockNode the consensus
|
||||
// is eligible for deployment.
|
||||
//
|
||||
// Since this implementation checks for unknown rules, it returns the maximum
|
||||
// possible timestamp so the rule is always treated as active.
|
||||
// Since this implementation checks for unknown rules, it returns false so the
|
||||
// rule is always treated as active.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) EndTime() uint64 {
|
||||
return math.MaxUint64
|
||||
func (c bitConditionChecker) HasEnded(_ *blockNode) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// RuleChangeActivationThreshold is the number of blocks for which the condition
|
||||
|
@ -111,6 +108,32 @@ func (c bitConditionChecker) Condition(node *blockNode) (bool, error) {
|
|||
return uint32(expectedVersion)&conditionMask == 0, nil
|
||||
}
|
||||
|
||||
// EligibleToActivate returns true if a custom deployment can transition from
|
||||
// the LockedIn to the Active state. For normal deployments, this always
|
||||
// returns true. However, some deployments add extra rules like a minimum
|
||||
// activation height, which can be abstracted into a generic arbitrary check at
|
||||
// the final state via this method.
|
||||
//
|
||||
// This implementation always returns true, as it's used to warn about other
|
||||
// unknown deployments.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) EligibleToActivate(blkNode *blockNode) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// IsSpeedy returns true if this is to be a "speedy" deployment. A speedy
|
||||
// deployment differs from a regular one in that only after a miner block
|
||||
// confirmation window can the deployment expire.
|
||||
//
|
||||
// This implementation returns false, as we want to always be warned if
|
||||
// something is about to activate.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) IsSpeedy() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// deploymentChecker provides a thresholdConditionChecker which can be used to
|
||||
// test a specific deployment rule. This is required for properly detecting
|
||||
// and activating consensus rule changes.
|
||||
|
@ -123,27 +146,36 @@ type deploymentChecker struct {
|
|||
// interface.
|
||||
var _ thresholdConditionChecker = deploymentChecker{}
|
||||
|
||||
// BeginTime returns the unix timestamp for the median block time after which
|
||||
// voting on a rule change starts (at the next window).
|
||||
// HasEnded returns true if the target consensus rule change has expired
|
||||
// or timed out (at the next window).
|
||||
//
|
||||
// This implementation returns the value defined by the specific deployment the
|
||||
// checker is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) BeginTime() uint64 {
|
||||
return c.deployment.StartTime
|
||||
func (c deploymentChecker) HasStarted(blkNode *blockNode) bool {
|
||||
// Can't fail as we make sure to set the clock above when we
|
||||
// instantiate *BlockChain.
|
||||
header := blkNode.Header()
|
||||
started, _ := c.deployment.DeploymentStarter.HasStarted(&header)
|
||||
|
||||
return started
|
||||
}
|
||||
|
||||
// EndTime returns the unix timestamp for the median block time after which an
|
||||
// attempted rule change fails if it has not already been locked in or
|
||||
// activated.
|
||||
// HasEnded returns true if the target consensus rule change has expired
|
||||
// or timed out.
|
||||
//
|
||||
// This implementation returns the value defined by the specific deployment the
|
||||
// checker is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) EndTime() uint64 {
|
||||
return c.deployment.ExpireTime
|
||||
func (c deploymentChecker) HasEnded(blkNode *blockNode) bool {
|
||||
// Can't fail as we make sure to set the clock above when we
|
||||
// instantiate *BlockChain.
|
||||
header := blkNode.Header()
|
||||
ended, _ := c.deployment.DeploymentEnder.HasEnded(&header)
|
||||
|
||||
return ended
|
||||
}
|
||||
|
||||
// RuleChangeActivationThreshold is the number of blocks for which the condition
|
||||
|
@ -154,6 +186,12 @@ func (c deploymentChecker) EndTime() uint64 {
|
|||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) RuleChangeActivationThreshold() uint32 {
|
||||
// Some deployments like taproot used a custom activation threshold
|
||||
// that ovverides the network level threshold.
|
||||
if c.deployment.CustomActivationThreshold != 0 {
|
||||
return c.deployment.CustomActivationThreshold
|
||||
}
|
||||
|
||||
return c.chain.chainParams.RuleChangeActivationThreshold
|
||||
}
|
||||
|
||||
|
@ -168,6 +206,37 @@ func (c deploymentChecker) MinerConfirmationWindow() uint32 {
|
|||
return c.chain.chainParams.MinerConfirmationWindow
|
||||
}
|
||||
|
||||
// EligibleToActivate returns true if a custom deployment can transition from
|
||||
// the LockedIn to the Active state. For normal deployments, this always
|
||||
// returns true. However, some deployments add extra rules like a minimum
|
||||
// activation height, which can be abstracted into a generic arbitrary check at
|
||||
// the final state via this method.
|
||||
//
|
||||
// This implementation always returns true, unless a minimum activation height
|
||||
// is specified.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) EligibleToActivate(blkNode *blockNode) bool {
|
||||
// No activation height, so it's always ready to go.
|
||||
if c.deployment.MinActivationHeight == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// If the _next_ block (as this is the prior block to the one being
|
||||
// connected is the min height or beyond, then this can activate.
|
||||
return uint32(blkNode.height)+1 >= c.deployment.MinActivationHeight
|
||||
}
|
||||
|
||||
// IsSpeedy returns true if this is to be a "speedy" deployment. A speedy
|
||||
// deployment differs from a regular one in that only after a miner block
|
||||
// confirmation window can the deployment expire. This implementation returns
|
||||
// true if a min activation height is set.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) IsSpeedy() bool {
|
||||
return c.deployment.MinActivationHeight != 0
|
||||
}
|
||||
|
||||
// Condition returns true when the specific bit defined by the deployment
|
||||
// associated with the checker is set.
|
||||
//
|
||||
|
|
|
@ -11,8 +11,8 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// GetBlockHeaderVerboseResult models the data from the getblockheader command when
|
||||
|
@ -172,12 +172,13 @@ type SoftForkDescription struct {
|
|||
// Bip9SoftForkDescription describes the current state of a defined BIP0009
|
||||
// version bits soft-fork.
|
||||
type Bip9SoftForkDescription struct {
|
||||
Status string `json:"status"`
|
||||
Bit uint8 `json:"bit"`
|
||||
StartTime1 int64 `json:"startTime"`
|
||||
StartTime2 int64 `json:"start_time"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
Since int32 `json:"since"`
|
||||
Status string `json:"status"`
|
||||
Bit uint8 `json:"bit"`
|
||||
StartTime1 int64 `json:"startTime"`
|
||||
StartTime2 int64 `json:"start_time"`
|
||||
Timeout int64 `json:"timeout"`
|
||||
Since int32 `json:"since"`
|
||||
MinActivationHeight int32 `json:"min_activation_height"`
|
||||
}
|
||||
|
||||
// StartTime returns the starting time of the softfork as a Unix epoch.
|
||||
|
|
185
chaincfg/deployment_time_frame.go
Normal file
185
chaincfg/deployment_time_frame.go
Normal file
|
@ -0,0 +1,185 @@
|
|||
package chaincfg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrNoBlockClock is returned when an operation fails due to lack of
|
||||
// synchornization with the current up to date block clock.
|
||||
ErrNoBlockClock = fmt.Errorf("no block clock synchronized")
|
||||
)
|
||||
|
||||
// BlockClock is an abstraction over the past median time computation. The past
|
||||
// median time computation is used in several consensus checks such as CSV, and
|
||||
// also BIP 9 version bits. This interface allows callers to abstract away the
|
||||
// computation of the past median time from the perspective of a given block
|
||||
// header.
|
||||
type BlockClock interface {
|
||||
// PastMedianTime returns the past median time from the PoV of the
|
||||
// passed block header. The past median time is the median time of the
|
||||
// 11 blocks prior to the passed block header.
|
||||
PastMedianTime(*wire.BlockHeader) (time.Time, error)
|
||||
}
|
||||
|
||||
// ConsensusDeploymentStarter determines if a given consensus deployment has
|
||||
// started. A deployment has started once according to the current "time", the
|
||||
// deployment is eligible for activation once a perquisite condition has
|
||||
// passed.
|
||||
type ConsensusDeploymentStarter interface {
|
||||
// HasStarted returns true if the consensus deployment has started.
|
||||
HasStarted(*wire.BlockHeader) (bool, error)
|
||||
}
|
||||
|
||||
// ClockConsensusDeploymentStarter is a more specialized version of the
|
||||
// ConsensusDeploymentStarter that uses a BlockClock in order to determine if a
|
||||
// deployment has started or not.
|
||||
//
|
||||
// NOTE: Any calls to HasStarted will _fail_ with ErrNoBlockClock if they
|
||||
// happen before SynchronizeClock is executed.
|
||||
type ClockConsensusDeploymentStarter interface {
|
||||
ConsensusDeploymentStarter
|
||||
|
||||
// SynchronizeClock synchronizes the target ConsensusDeploymentStarter
|
||||
// with the current up-to date BlockClock.
|
||||
SynchronizeClock(clock BlockClock)
|
||||
}
|
||||
|
||||
// ConsensusDeploymentEnder determines if a given consensus deployment has
|
||||
// ended. A deployment has ended once according got eh current "time", the
|
||||
// deployment is no longer eligible for activation.
|
||||
type ConsensusDeploymentEnder interface {
|
||||
// HasEnded returns true if the consensus deployment has ended.
|
||||
HasEnded(*wire.BlockHeader) (bool, error)
|
||||
}
|
||||
|
||||
// ClockConsensusDeploymentEnder is a more specialized version of the
|
||||
// ConsensusDeploymentEnder that uses a BlockClock in order to determine if a
|
||||
// deployment has started or not.
|
||||
//
|
||||
// NOTE: Any calls to HasEnded will _fail_ with ErrNoBlockClock if they
|
||||
// happen before SynchronizeClock is executed.
|
||||
type ClockConsensusDeploymentEnder interface {
|
||||
ConsensusDeploymentEnder
|
||||
|
||||
// SynchronizeClock synchronizes the target ConsensusDeploymentStarter
|
||||
// with the current up-to date BlockClock.
|
||||
SynchronizeClock(clock BlockClock)
|
||||
}
|
||||
|
||||
// MedianTimeDeploymentStarter is a ClockConsensusDeploymentStarter that uses
|
||||
// the median time past of a target block node to determine if a deployment has
|
||||
// started.
|
||||
type MedianTimeDeploymentStarter struct {
|
||||
blockClock BlockClock
|
||||
|
||||
startTime time.Time
|
||||
}
|
||||
|
||||
// NewMedianTimeDeploymentStarter returns a new instance of a
|
||||
// MedianTimeDeploymentStarter for a given start time. Using a time.Time
|
||||
// instance where IsZero() is true, indicates that a deployment should be
|
||||
// considered to always have been started.
|
||||
func NewMedianTimeDeploymentStarter(startTime time.Time) *MedianTimeDeploymentStarter {
|
||||
return &MedianTimeDeploymentStarter{
|
||||
startTime: startTime,
|
||||
}
|
||||
}
|
||||
|
||||
// SynchronizeClock synchronizes the target ConsensusDeploymentStarter with the
|
||||
// current up-to date BlockClock.
|
||||
func (m *MedianTimeDeploymentStarter) SynchronizeClock(clock BlockClock) {
|
||||
m.blockClock = clock
|
||||
}
|
||||
|
||||
// HasStarted returns true if the consensus deployment has started.
|
||||
func (m *MedianTimeDeploymentStarter) HasStarted(blkHeader *wire.BlockHeader) (bool, error) {
|
||||
switch {
|
||||
// If we haven't yet been synchronized with a block clock, then we
|
||||
// can't tell the time, so we'll fail.
|
||||
case m.blockClock == nil:
|
||||
return false, ErrNoBlockClock
|
||||
|
||||
// If the time is "zero", then the deployment has always started.
|
||||
case m.startTime.IsZero():
|
||||
return true, nil
|
||||
}
|
||||
|
||||
medianTime, err := m.blockClock.PastMedianTime(blkHeader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// We check both after and equal here as after will fail for equivalent
|
||||
// times, and we want to be inclusive.
|
||||
return medianTime.After(m.startTime) || medianTime.Equal(m.startTime), nil
|
||||
}
|
||||
|
||||
// StartTime returns the raw start time of the deployment.
|
||||
func (m *MedianTimeDeploymentStarter) StartTime() time.Time {
|
||||
return m.startTime
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure MedianTimeDeploymentStarter implements
|
||||
// the ClockConsensusDeploymentStarter interface.
|
||||
var _ ClockConsensusDeploymentStarter = (*MedianTimeDeploymentStarter)(nil)
|
||||
|
||||
// MedianTimeDeploymentEnder is a ClockConsensusDeploymentEnder that uses the
|
||||
// median time past of a target block to determine if a deployment has ended.
|
||||
type MedianTimeDeploymentEnder struct {
|
||||
blockClock BlockClock
|
||||
|
||||
endTime time.Time
|
||||
}
|
||||
|
||||
// NewMedianTimeDeploymentEnder returns a new instance of the
|
||||
// MedianTimeDeploymentEnder anchored around the passed endTime. Using a
|
||||
// time.Time instance where IsZero() is true, indicates that a deployment
|
||||
// should be considered to never end.
|
||||
func NewMedianTimeDeploymentEnder(endTime time.Time) *MedianTimeDeploymentEnder {
|
||||
return &MedianTimeDeploymentEnder{
|
||||
endTime: endTime,
|
||||
}
|
||||
}
|
||||
|
||||
// HasEnded returns true if the deployment has ended.
|
||||
func (m *MedianTimeDeploymentEnder) HasEnded(blkHeader *wire.BlockHeader) (bool, error) {
|
||||
switch {
|
||||
// If we haven't yet been synchronized with a block clock, then we can't tell
|
||||
// the time, so we'll we haven't yet been synchronized with a block
|
||||
// clock, then w can't tell the time, so we'll fail.
|
||||
case m.blockClock == nil:
|
||||
return false, ErrNoBlockClock
|
||||
|
||||
// If the time is "zero", then the deployment never ends.
|
||||
case m.endTime.IsZero():
|
||||
return false, nil
|
||||
}
|
||||
|
||||
medianTime, err := m.blockClock.PastMedianTime(blkHeader)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// We check both after and equal here as after will fail for equivalent
|
||||
// times, and we want to be inclusive.
|
||||
return medianTime.After(m.endTime) || medianTime.Equal(m.endTime), nil
|
||||
}
|
||||
|
||||
// MedianTimeDeploymentEnder returns the raw end time of the deployment.
|
||||
func (m *MedianTimeDeploymentEnder) EndTime() time.Time {
|
||||
return m.endTime
|
||||
}
|
||||
|
||||
// SynchronizeClock synchronizes the target ConsensusDeploymentEnder with the
|
||||
// current up-to date BlockClock.
|
||||
func (m *MedianTimeDeploymentEnder) SynchronizeClock(clock BlockClock) {
|
||||
m.blockClock = clock
|
||||
}
|
||||
|
||||
// A compile-time assertion to ensure MedianTimeDeploymentEnder implements the
|
||||
// ClockConsensusDeploymentStarter interface.
|
||||
var _ ClockConsensusDeploymentEnder = (*MedianTimeDeploymentEnder)(nil)
|
|
@ -8,7 +8,6 @@ import (
|
|||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"math"
|
||||
"math/big"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -95,13 +94,26 @@ type ConsensusDeployment struct {
|
|||
// this particular soft-fork deployment refers to.
|
||||
BitNumber uint8
|
||||
|
||||
// StartTime is the median block time after which voting on the
|
||||
// deployment starts.
|
||||
StartTime uint64
|
||||
// MinActivationHeight is an optional field that when set (default
|
||||
// value being zero), modifies the traditional BIP 9 state machine by
|
||||
// only transitioning from LockedIn to Active once the block height is
|
||||
// greater than (or equal to) thus specified height.
|
||||
MinActivationHeight uint32
|
||||
|
||||
// ExpireTime is the median block time after which the attempted
|
||||
// deployment expires.
|
||||
ExpireTime uint64
|
||||
// CustomActivationThreshold if set (non-zero), will _override_ the
|
||||
// existing RuleChangeActivationThreshold value set at the
|
||||
// network/chain level. This value divided by the active
|
||||
// MinerConfirmationWindow denotes the threshold required for
|
||||
// activation. A value of 1815 block denotes a 90% threshold.
|
||||
CustomActivationThreshold uint32
|
||||
|
||||
// DeploymentStarter is used to determine if the given
|
||||
// ConsensusDeployment has started or not.
|
||||
DeploymentStarter ConsensusDeploymentStarter
|
||||
|
||||
// DeploymentEnder is used to determine if the given
|
||||
// ConsensusDeployment has ended or not.
|
||||
DeploymentEnder ConsensusDeploymentEnder
|
||||
}
|
||||
|
||||
// Constants that define the deployment offset in the deployments field of the
|
||||
|
@ -112,6 +124,12 @@ const (
|
|||
// purposes.
|
||||
DeploymentTestDummy = iota
|
||||
|
||||
// DeploymentTestDummyMinActivation defines the rule change deployment
|
||||
// ID for testing purposes. This differs from the DeploymentTestDummy
|
||||
// in that it specifies the newer params the taproot fork used for
|
||||
// activation: a custom threshold and a min activation height.
|
||||
DeploymentTestDummyMinActivation
|
||||
|
||||
// DeploymentCSV defines the rule change deployment ID for the CSV
|
||||
// soft-fork package. The CSV package includes the deployment of BIPS
|
||||
// 68, 112, and 113.
|
||||
|
@ -122,11 +140,6 @@ const (
|
|||
// includes the deployment of BIPS 141, 142, 144, 145, 147 and 173.
|
||||
DeploymentSegwit
|
||||
|
||||
// DeploymentTaproot defines the rule change deployment ID for the
|
||||
// Taproot (+Schnorr) soft-fork package. The taproot package includes
|
||||
// the deployment of BIPS 340, 341 and 342.
|
||||
DeploymentTaproot
|
||||
|
||||
// NOTE: DefinedDeployments must always come last since it is used to
|
||||
// determine how many defined deployments there currently are.
|
||||
|
||||
|
@ -320,19 +333,42 @@ var MainNetParams = Params{
|
|||
MinerConfirmationWindow: 2016, //
|
||||
Deployments: [DefinedDeployments]ConsensusDeployment{
|
||||
DeploymentTestDummy: {
|
||||
BitNumber: 28,
|
||||
StartTime: 1199145601, // January 1, 2008 UTC
|
||||
ExpireTime: 1230767999, // December 31, 2008 UTC
|
||||
BitNumber: 28,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(11991456010, 0), // January 1, 2008 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1230767999, 0), // December 31, 2008 UTC
|
||||
),
|
||||
},
|
||||
DeploymentTestDummyMinActivation: {
|
||||
BitNumber: 22,
|
||||
CustomActivationThreshold: 1815, // Only needs 90% hash rate.
|
||||
MinActivationHeight: 10_0000, // Can only activate after height 10k.
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentCSV: {
|
||||
BitNumber: 0,
|
||||
StartTime: 1462060800, // May 1st, 2016
|
||||
ExpireTime: 1493596800, // May 1st, 2017
|
||||
BitNumber: 0,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1462060800, 0), // May 1st, 2016
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1493596800, 0), // May 1st, 2017
|
||||
),
|
||||
},
|
||||
DeploymentSegwit: {
|
||||
BitNumber: 1,
|
||||
StartTime: 1479168000, // November 15, 2016 UTC
|
||||
ExpireTime: 1510704000, // November 15, 2017 UTC.
|
||||
BitNumber: 1,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1479168000, 0), // November 15, 2016 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1510704000, 0), // November 15, 2017 UTC.
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -396,19 +432,42 @@ var RegressionNetParams = Params{
|
|||
MinerConfirmationWindow: 144,
|
||||
Deployments: [DefinedDeployments]ConsensusDeployment{
|
||||
DeploymentTestDummy: {
|
||||
BitNumber: 28,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires
|
||||
BitNumber: 28,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentTestDummyMinActivation: {
|
||||
BitNumber: 22,
|
||||
CustomActivationThreshold: 72, // Only needs 50% hash rate.
|
||||
MinActivationHeight: 600, // Can only activate after height 600.
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentCSV: {
|
||||
BitNumber: 0,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires
|
||||
BitNumber: 0,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentSegwit: {
|
||||
BitNumber: 1,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires.
|
||||
BitNumber: 1,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires.
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -490,19 +549,42 @@ var TestNet3Params = Params{
|
|||
MinerConfirmationWindow: 2016,
|
||||
Deployments: [DefinedDeployments]ConsensusDeployment{
|
||||
DeploymentTestDummy: {
|
||||
BitNumber: 28,
|
||||
StartTime: 1199145601, // January 1, 2008 UTC
|
||||
ExpireTime: 1230767999, // December 31, 2008 UTC
|
||||
BitNumber: 28,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1199145601, 0), // January 1, 2008 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1230767999, 0), // December 31, 2008 UTC
|
||||
),
|
||||
},
|
||||
DeploymentTestDummyMinActivation: {
|
||||
BitNumber: 22,
|
||||
CustomActivationThreshold: 1815, // Only needs 90% hash rate.
|
||||
MinActivationHeight: 10_0000, // Can only activate after height 10k.
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentCSV: {
|
||||
BitNumber: 0,
|
||||
StartTime: 1456790400, // March 1st, 2016
|
||||
ExpireTime: 1493596800, // May 1st, 2017
|
||||
BitNumber: 0,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1456790400, 0), // March 1st, 2016
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1493596800, 0), // May 1st, 2017
|
||||
),
|
||||
},
|
||||
DeploymentSegwit: {
|
||||
BitNumber: 1,
|
||||
StartTime: 1462060800, // May 1, 2016 UTC
|
||||
ExpireTime: 1493596800, // May 1, 2017 UTC.
|
||||
BitNumber: 1,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1462060800, 0), // May 1, 2016 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1493596800, 0), // May 1, 2017 UTC.
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -570,19 +652,42 @@ var SimNetParams = Params{
|
|||
MinerConfirmationWindow: 100,
|
||||
Deployments: [DefinedDeployments]ConsensusDeployment{
|
||||
DeploymentTestDummy: {
|
||||
BitNumber: 28,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires
|
||||
BitNumber: 28,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentTestDummyMinActivation: {
|
||||
BitNumber: 22,
|
||||
CustomActivationThreshold: 50, // Only needs 50% hash rate.
|
||||
MinActivationHeight: 600, // Can only activate after height 600.
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentCSV: {
|
||||
BitNumber: 0,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires
|
||||
BitNumber: 0,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentSegwit: {
|
||||
BitNumber: 1,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires.
|
||||
BitNumber: 1,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires.
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -665,24 +770,42 @@ func CustomSignetParams(challenge []byte, dnsSeeds []DNSSeed) Params {
|
|||
MinerConfirmationWindow: 2016,
|
||||
Deployments: [DefinedDeployments]ConsensusDeployment{
|
||||
DeploymentTestDummy: {
|
||||
BitNumber: 28,
|
||||
StartTime: 1199145601, // January 1, 2008 UTC
|
||||
ExpireTime: 1230767999, // December 31, 2008 UTC
|
||||
BitNumber: 28,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Unix(1199145601, 0), // January 1, 2008 UTC
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Unix(1230767999, 0), // December 31, 2008 UTC
|
||||
),
|
||||
},
|
||||
DeploymentTestDummyMinActivation: {
|
||||
BitNumber: 22,
|
||||
CustomActivationThreshold: 1815, // Only needs 90% hash rate.
|
||||
MinActivationHeight: 10_0000, // Can only activate after height 10k.
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentCSV: {
|
||||
BitNumber: 29,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires
|
||||
BitNumber: 29,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
DeploymentSegwit: {
|
||||
BitNumber: 29,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires.
|
||||
},
|
||||
DeploymentTaproot: {
|
||||
BitNumber: 29,
|
||||
StartTime: 0, // Always available for vote
|
||||
ExpireTime: math.MaxInt64, // Never expires.
|
||||
BitNumber: 29,
|
||||
DeploymentStarter: NewMedianTimeDeploymentStarter(
|
||||
time.Time{}, // Always available for vote
|
||||
),
|
||||
DeploymentEnder: NewMedianTimeDeploymentEnder(
|
||||
time.Time{}, // Never expires
|
||||
),
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is ignored during the regular tests due to the following build tag.
|
||||
//go:build rpctest
|
||||
// +build rpctest
|
||||
|
||||
package integration
|
||||
|
@ -196,6 +197,9 @@ func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) {
|
|||
}
|
||||
deployment := &r.ActiveNet.Deployments[deploymentID]
|
||||
activationThreshold := r.ActiveNet.RuleChangeActivationThreshold
|
||||
if deployment.CustomActivationThreshold != 0 {
|
||||
activationThreshold = deployment.CustomActivationThreshold
|
||||
}
|
||||
signalForkVersion := int32(1<<deployment.BitNumber) | vbTopBits
|
||||
for i := uint32(0); i < activationThreshold-1; i++ {
|
||||
_, err := r.GenerateAndSubmitBlock(nil, signalForkVersion,
|
||||
|
@ -268,7 +272,42 @@ func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) {
|
|||
if err != nil {
|
||||
t.Fatalf("failed to generated block: %v", err)
|
||||
}
|
||||
assertChainHeight(r, t, (confirmationWindow*4)-1)
|
||||
expectedChainHeight := (confirmationWindow * 4) - 1
|
||||
assertChainHeight(r, t, expectedChainHeight)
|
||||
|
||||
// If this isn't a fork that has a min activation height set, then it
|
||||
// should be active at this point.
|
||||
if deployment.MinActivationHeight == 0 {
|
||||
assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdActive)
|
||||
return
|
||||
}
|
||||
|
||||
// Otherwise, we'll need to mine additional blocks to pass the min
|
||||
// activation height and ensure the rule set applies. For regtest the
|
||||
// deployment can only activate after height 600, and at this point
|
||||
// we've mined 4*144 blocks, so another confirmation window will put us
|
||||
// over.
|
||||
numBlocksLeft := confirmationWindow
|
||||
for i := uint32(0); i < numBlocksLeft; i++ {
|
||||
// Ensure that we're always in the locked in state right up
|
||||
// until after we mine the very last block.
|
||||
if i < numBlocksLeft {
|
||||
assertSoftForkStatus(
|
||||
r, t, forkKey, blockchain.ThresholdLockedIn,
|
||||
)
|
||||
}
|
||||
|
||||
_, err := r.GenerateAndSubmitBlock(
|
||||
nil, signalForkVersion, time.Time{},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generated block %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, the soft fork should now be shown as active.
|
||||
expectedChainHeight = (confirmationWindow * 5) - 1
|
||||
assertChainHeight(r, t, expectedChainHeight)
|
||||
assertSoftForkStatus(r, t, forkKey, blockchain.ThresholdActive)
|
||||
}
|
||||
|
||||
|
@ -299,6 +338,7 @@ func TestBIP0009(t *testing.T) {
|
|||
t.Parallel()
|
||||
|
||||
testBIP0009(t, "dummy", chaincfg.DeploymentTestDummy)
|
||||
testBIP0009(t, "dummy-min-activation", chaincfg.DeploymentTestDummyMinActivation)
|
||||
testBIP0009(t, "segwit", chaincfg.DeploymentSegwit)
|
||||
}
|
||||
|
||||
|
@ -329,7 +369,7 @@ func TestBIP0009Mining(t *testing.T) {
|
|||
}
|
||||
defer r.TearDown()
|
||||
|
||||
// Assert the chain only consists of the gensis block.
|
||||
// Assert the chain only consists of the genesis block.
|
||||
assertChainHeight(r, t, 0)
|
||||
|
||||
// *** ThresholdDefined ***
|
||||
|
|
24
rpcserver.go
24
rpcserver.go
|
@ -31,6 +31,7 @@ import (
|
|||
"github.com/btcsuite/btcd/blockchain/indexers"
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/btcjson"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/database"
|
||||
|
@ -40,7 +41,6 @@ import (
|
|||
"github.com/btcsuite/btcd/peer"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/btcsuite/btcd/btcutil"
|
||||
"github.com/btcsuite/websocket"
|
||||
)
|
||||
|
||||
|
@ -1250,15 +1250,15 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str
|
|||
case chaincfg.DeploymentTestDummy:
|
||||
forkName = "dummy"
|
||||
|
||||
case chaincfg.DeploymentTestDummyMinActivation:
|
||||
forkName = "dummy-min-activation"
|
||||
|
||||
case chaincfg.DeploymentCSV:
|
||||
forkName = "csv"
|
||||
|
||||
case chaincfg.DeploymentSegwit:
|
||||
forkName = "segwit"
|
||||
|
||||
case chaincfg.DeploymentTaproot:
|
||||
forkName = "taproot"
|
||||
|
||||
default:
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInternal.Code,
|
||||
|
@ -1289,11 +1289,19 @@ func handleGetBlockChainInfo(s *rpcServer, cmd interface{}, closeChan <-chan str
|
|||
|
||||
// Finally, populate the soft-fork description with all the
|
||||
// information gathered above.
|
||||
var startTime, endTime int64
|
||||
if starter, ok := deploymentDetails.DeploymentStarter.(*chaincfg.MedianTimeDeploymentStarter); ok {
|
||||
startTime = starter.StartTime().Unix()
|
||||
}
|
||||
if ender, ok := deploymentDetails.DeploymentEnder.(*chaincfg.MedianTimeDeploymentEnder); ok {
|
||||
endTime = ender.EndTime().Unix()
|
||||
}
|
||||
chainInfo.SoftForks.Bip9SoftForks[forkName] = &btcjson.Bip9SoftForkDescription{
|
||||
Status: strings.ToLower(statusString),
|
||||
Bit: deploymentDetails.BitNumber,
|
||||
StartTime2: int64(deploymentDetails.StartTime),
|
||||
Timeout: int64(deploymentDetails.ExpireTime),
|
||||
Status: strings.ToLower(statusString),
|
||||
Bit: deploymentDetails.BitNumber,
|
||||
StartTime2: startTime,
|
||||
Timeout: endTime,
|
||||
MinActivationHeight: int32(deploymentDetails.MinActivationHeight),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue