diff --git a/blockchain/chain.go b/blockchain/chain.go index 92bfb26876f..4d1a8394419 100644 --- a/blockchain/chain.go +++ b/blockchain/chain.go @@ -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. diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index 5da74a95af6..6f3841bbe1d 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -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. @@ -121,6 +122,27 @@ 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 +} + // 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 +172,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 } @@ -192,9 +210,7 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit 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() { + if checker.HasEnded(prevNode) { state = ThresholdFailed break } @@ -202,15 +218,14 @@ func (b *BlockChain) thresholdState(prevNode *blockNode, checker thresholdCondit // 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() { + 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. - medianTime := prevNode.CalcPastMedianTime() - if uint64(medianTime.Unix()) >= checker.EndTime() { + if checker.HasEnded(prevNode) { state = ThresholdFailed break } diff --git a/blockchain/versionbits.go b/blockchain/versionbits.go index 28fcde7b69a..dbe4eb1d612 100644 --- a/blockchain/versionbits.go +++ b/blockchain/versionbits.go @@ -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 @@ -123,27 +120,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