1 package chaincfg
2 3 import (
4 "fmt"
5 "time"
6 7 "next.orly.dev/pkg/nostr/crypto/ec/wire"
8 )
9 10 var (
11 // ErrNoBlockClock is returned when an operation fails due to lack of
12 // synchornization with the current up to date block clock.
13 ErrNoBlockClock = fmt.Errorf("no block clock synchronized")
14 )
15 16 // ConsensusDeploymentStarter determines if a given consensus deployment has
17 // started. A deployment has started once according to the current "time", the
18 // deployment is eligible for activation once a perquisite condition has
19 // passed.
20 type ConsensusDeploymentStarter interface {
21 // HasStarted returns true if the consensus deployment has started.
22 HasStarted(*wire.BlockHeader) (bool, error)
23 }
24 25 // ConsensusDeploymentEnder determines if a given consensus deployment has
26 // ended. A deployment has ended once according got eh current "time", the
27 // deployment is no longer eligible for activation.
28 type ConsensusDeploymentEnder interface {
29 // HasEnded returns true if the consensus deployment has ended.
30 HasEnded(*wire.BlockHeader) (bool, error)
31 }
32 33 // BlockClock is an abstraction over the past median time computation. The past
34 // median time computation is used in several consensus checks such as CSV, and
35 // also BIP 9 version bits. This interface allows callers to abstract away the
36 // computation of the past median time from the perspective of a given block
37 // header.
38 type BlockClock interface {
39 // PastMedianTime returns the past median time from the PoV of the
40 // passed block header. The past median time is the median time of the
41 // 11 blocks prior to the passed block header.
42 PastMedianTime(*wire.BlockHeader) (time.Time, error)
43 }
44 45 // ClockConsensusDeploymentEnder is a more specialized version of the
46 // ConsensusDeploymentEnder that uses a BlockClock in order to determine if a
47 // deployment has started or not.
48 //
49 // NOTE: Any calls to HasEnded will _fail_ with ErrNoBlockClock if they
50 // happen before SynchronizeClock is executed.
51 type ClockConsensusDeploymentEnder interface {
52 ConsensusDeploymentEnder
53 // SynchronizeClock synchronizes the target ConsensusDeploymentStarter
54 // with the current up-to date BlockClock.
55 SynchronizeClock(clock BlockClock)
56 }
57 58 // MedianTimeDeploymentStarter is a ClockConsensusDeploymentStarter that uses
59 // the median time past of a target block node to determine if a deployment has
60 // started.
61 type MedianTimeDeploymentStarter struct {
62 blockClock BlockClock
63 startTime time.Time
64 }
65 66 // NewMedianTimeDeploymentStarter returns a new instance of a
67 // MedianTimeDeploymentStarter for a given start time. Using a time.Time
68 // instance where IsZero() is true, indicates that a deployment should be
69 // considered to always have been started.
70 func NewMedianTimeDeploymentStarter(startTime time.Time) *MedianTimeDeploymentStarter {
71 return &MedianTimeDeploymentStarter{
72 startTime: startTime,
73 }
74 }
75 76 // HasStarted returns true if the consensus deployment has started.
77 func (m *MedianTimeDeploymentStarter) HasStarted(blkHeader *wire.BlockHeader) (
78 bool,
79 error,
80 ) {
81 switch {
82 // If we haven't yet been synchronized with a block clock, then we
83 // can't tell the time, so we'll fail.
84 case m.blockClock == nil:
85 return false, ErrNoBlockClock
86 // If the time is "zero", then the deployment has always started.
87 case m.startTime.IsZero():
88 return true, nil
89 }
90 medianTime, err := m.blockClock.PastMedianTime(blkHeader)
91 if err != nil {
92 return false, err
93 }
94 // We check both after and equal here as after will fail for equivalent
95 // times, and we want to be inclusive.
96 return medianTime.After(m.startTime) || medianTime.Equal(m.startTime), nil
97 }
98 99 // MedianTimeDeploymentEnder is a ClockConsensusDeploymentEnder that uses the
100 // median time past of a target block to determine if a deployment has ended.
101 type MedianTimeDeploymentEnder struct {
102 blockClock BlockClock
103 endTime time.Time
104 }
105 106 // NewMedianTimeDeploymentEnder returns a new instance of the
107 // MedianTimeDeploymentEnder anchored around the passed endTime. Using a
108 // time.Time instance where IsZero() is true, indicates that a deployment
109 // should be considered to never end.
110 func NewMedianTimeDeploymentEnder(endTime time.Time) *MedianTimeDeploymentEnder {
111 return &MedianTimeDeploymentEnder{
112 endTime: endTime,
113 }
114 }
115 116 // HasEnded returns true if the deployment has ended.
117 func (m *MedianTimeDeploymentEnder) HasEnded(blkHeader *wire.BlockHeader) (
118 bool,
119 error,
120 ) {
121 switch {
122 // If we haven't yet been synchronized with a block clock, then we can't tell
123 // the time, so we'll we haven't yet been synchronized with a block
124 // clock, then w can't tell the time, so we'll fail.
125 case m.blockClock == nil:
126 return false, ErrNoBlockClock
127 // If the time is "zero", then the deployment never ends.
128 case m.endTime.IsZero():
129 return false, nil
130 }
131 medianTime, err := m.blockClock.PastMedianTime(blkHeader)
132 if err != nil {
133 return false, err
134 }
135 // We check both after and equal here as after will fail for equivalent
136 // times, and we want to be inclusive.
137 return medianTime.After(m.endTime) || medianTime.Equal(m.endTime), nil
138 }
139 140 // EndTime returns the raw end time of the deployment.
141 func (m *MedianTimeDeploymentEnder) EndTime() time.Time {
142 return m.endTime
143 }
144 145 // SynchronizeClock synchronizes the target ConsensusDeploymentEnder with the
146 // current up-to date BlockClock.
147 func (m *MedianTimeDeploymentEnder) SynchronizeClock(clock BlockClock) {
148 m.blockClock = clock
149 }
150 151 // A compile-time assertion to ensure MedianTimeDeploymentEnder implements the
152 // ClockConsensusDeploymentStarter interface.
153 var _ ClockConsensusDeploymentEnder = (*MedianTimeDeploymentEnder)(nil)
154