Skip to content

Commit

Permalink
chaincfg: create new abstract deployment starter/ender interfaces
Browse files Browse the repository at this point in the history
In this commit, we create a series of new interfaces that'll allow us to
abstract "when" exactly a deployment starts and ends. As is, all
deployments start/end based on a unix timestamp, which is compared
against the MTP of a given block to determine if a new deployment has
started or ended. This works fine for BIP 9 which uses time based
timeouts, but not so much for BIP 8. In order to prep a future refactor
that allows our version bits implementation to support both time and
block based start/end times, this new abstraction has been introduced.
  • Loading branch information
Roasbeef committed Mar 20, 2021
1 parent f86ae60 commit ff78816
Show file tree
Hide file tree
Showing 2 changed files with 173 additions and 0 deletions.
172 changes: 172 additions & 0 deletions chaincfg/deployment_time_frame.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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.
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 w 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 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
}

// 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.
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
}

// 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)
1 change: 1 addition & 0 deletions chaincfg/deployment_time_frame_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package chaincfg

0 comments on commit ff78816

Please sign in to comment.