mirror of
https://github.com/btcsuite/btcd.git
synced 2024-11-19 01:40:07 +01:00
2b6370dfd7
In this commit, we utilize the recently added ConsensusDeploymentStarter and ConsensusDeploymentEnder interfaces. Concrete implementations of this interface based on the median time past comparison are used now in the ConsensusDeployment struct instead of hard coded start/end times. Along the way, we had to switch to using the "zero time": time.Time{}, in place of 0 and math.MaxInt64 as comparison (After/Before) seems to be broken in the Go stdlib for times very far in the future. It appears Go isn't ready to handle the heat death of the universe.
186 lines
6.5 KiB
Go
186 lines
6.5 KiB
Go
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)
|