code.vegaprotocol.io/vega@v0.79.0/core/epochtime/service.go (about) 1 // Copyright (C) 2023 Gobalsky Labs Limited 2 // 3 // This program is free software: you can redistribute it and/or modify 4 // it under the terms of the GNU Affero General Public License as 5 // published by the Free Software Foundation, either version 3 of the 6 // License, or (at your option) any later version. 7 // 8 // This program is distributed in the hope that it will be useful, 9 // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 // GNU Affero General Public License for more details. 12 // 13 // You should have received a copy of the GNU Affero General Public License 14 // along with this program. If not, see <http://www.gnu.org/licenses/>. 15 16 package epochtime 17 18 import ( 19 "context" 20 "time" 21 22 "code.vegaprotocol.io/vega/core/events" 23 "code.vegaprotocol.io/vega/core/types" 24 "code.vegaprotocol.io/vega/libs/proto" 25 "code.vegaprotocol.io/vega/logging" 26 "code.vegaprotocol.io/vega/protos/vega" 27 eventspb "code.vegaprotocol.io/vega/protos/vega/events/v1" 28 ) 29 30 type Broker interface { 31 Send(e events.Event) 32 } 33 34 // Svc represents the Service managing epoch inside Vega. 35 type Svc struct { 36 config Config 37 38 length time.Duration 39 epoch types.Epoch 40 41 listeners []func(context.Context, types.Epoch) // for when the epoch state changes 42 restoreListeners []func(context.Context, types.Epoch) // for when the epoch has been restored from a snapshot 43 44 log *logging.Logger 45 46 broker Broker 47 48 readyToStartNewEpoch bool 49 readyToEndEpoch bool 50 51 // Snapshot state 52 state *types.EpochState 53 pl types.Payload 54 data []byte 55 currentTime time.Time 56 needsFastForward bool 57 } 58 59 // NewService instantiates a new epochtime service. 60 func NewService(l *logging.Logger, conf Config, broker Broker) *Svc { 61 s := &Svc{ 62 config: conf, 63 log: l, 64 broker: broker, 65 readyToStartNewEpoch: false, 66 readyToEndEpoch: false, 67 } 68 69 s.state = &types.EpochState{} 70 s.pl = types.Payload{ 71 Data: &types.PayloadEpoch{ 72 EpochState: s.state, 73 }, 74 } 75 76 return s 77 } 78 79 // ReloadConf reload the configuration for the epochtime service. 80 func (s *Svc) ReloadConf(conf Config) { 81 // do nothing here, conf is not used for now 82 } 83 84 // OnBlockEnd handles a callback from the abci when the block ends. 85 func (s *Svc) OnBlockEnd(ctx context.Context) { 86 if s.readyToEndEpoch { 87 s.readyToStartNewEpoch = true 88 s.readyToEndEpoch = false 89 } 90 } 91 92 // NB: An epoch is ended when the first block that exceeds the expiry of the current epoch ends. As onTick is called from onBlockStart - to make epoch continuous 93 // and avoid no man's epoch - once we get the first block past expiry we mark get ready to end the epoch. Once we get the on block end callback we're setting 94 // the flag to be ready to start a new block on the next onTick (i.e. preceding the beginning of the next block). Once we get the next block's on tick we close 95 // the epoch and notify on its end and start a new epoch (with incremented sequence) and notify about it. 96 func (s *Svc) OnTick(ctx context.Context, t time.Time) { 97 if t.IsZero() { 98 // We haven't got a block time yet, ignore 99 return 100 } 101 102 if s.needsFastForward && t.Equal(s.currentTime) { 103 s.log.Debug("onTick called with the same time again", logging.Time("tick-time", t)) 104 return 105 } 106 107 s.currentTime = t 108 109 if s.needsFastForward { 110 s.log.Info("fast forwarding epoch starts", logging.Uint64("from-epoch", s.epoch.Seq), logging.Time("at", t)) 111 s.needsFastForward = false 112 s.fastForward(ctx) 113 s.currentTime = t 114 s.log.Info("fast forwarding epochs ended", logging.Uint64("current-epoch", s.epoch.Seq)) 115 } 116 117 if s.epoch.StartTime.IsZero() { 118 // First block so let's create our first epoch 119 s.epoch.Seq = 0 120 s.epoch.StartTime = t 121 s.epoch.ExpireTime = t.Add(s.length) // current time + epoch length 122 s.epoch.Action = vega.EpochAction_EPOCH_ACTION_START 123 124 // Send out new epoch event 125 s.notify(ctx, s.epoch) 126 return 127 } 128 129 if s.readyToStartNewEpoch { 130 // close previous epoch and send an event 131 s.epoch.EndTime = t 132 s.epoch.Action = vega.EpochAction_EPOCH_ACTION_END 133 s.notify(ctx, s.epoch) 134 135 // Move the epoch details forward 136 s.epoch.Seq++ 137 s.readyToStartNewEpoch = false 138 139 // Create a new epoch 140 s.epoch.StartTime = t 141 s.epoch.ExpireTime = t.Add(s.length) // now + epoch length 142 s.epoch.EndTime = time.Time{} 143 s.epoch.Action = vega.EpochAction_EPOCH_ACTION_START 144 s.notify(ctx, s.epoch) 145 return 146 } 147 148 // if the block time is past the expiry - this is the last block to go into the epoch - when the block ends we end the epoch and start a new one 149 if s.epoch.ExpireTime.Before(t) { 150 // Set the flag to tell us to end the epoch when the block ends 151 s.readyToEndEpoch = true 152 return 153 } 154 } 155 156 func (*Svc) Name() types.CheckpointName { 157 return types.EpochCheckpoint 158 } 159 160 func (s *Svc) Checkpoint() ([]byte, error) { 161 return proto.Marshal(s.epoch.IntoProto()) 162 } 163 164 func (s *Svc) Load(ctx context.Context, data []byte) error { 165 pb := &eventspb.EpochEvent{} 166 if err := proto.Unmarshal(data, pb); err != nil { 167 return err 168 } 169 e := types.NewEpochFromProto(pb) 170 s.epoch = *e 171 172 // let the time end the epoch organically 173 s.readyToStartNewEpoch = false 174 s.readyToEndEpoch = false 175 s.notify(ctx, s.epoch) 176 s.needsFastForward = true 177 return nil 178 } 179 180 // fastForward advances time and expires/starts any epoch that would have expired/started during the time period. It would trigger the epoch events naturally 181 // so will have a side effect of delegations getting promoted and rewards getting calculated and potentially paid. 182 func (s *Svc) fastForward(ctx context.Context) { 183 tt := s.currentTime 184 for s.epoch.ExpireTime.Before(tt) { 185 s.OnBlockEnd(ctx) 186 s.OnTick(ctx, s.epoch.ExpireTime.Add(1*time.Second)) 187 } 188 s.OnTick(ctx, tt) 189 } 190 191 // NotifyOnEpoch allows other services to register 2 callback functions. 192 // The first will be called once we enter or leave a new epoch, and the second 193 // will be called when the epoch service has been restored from a snapshot. 194 func (s *Svc) NotifyOnEpoch(f func(context.Context, types.Epoch), r func(context.Context, types.Epoch)) { 195 s.listeners = append(s.listeners, f) 196 s.restoreListeners = append(s.restoreListeners, r) 197 } 198 199 func (s *Svc) notify(ctx context.Context, e types.Epoch) { 200 // Push this updated epoch message onto the event bus 201 s.broker.Send(events.NewEpochEvent(ctx, &e)) 202 for _, f := range s.listeners { 203 f(ctx, e) 204 } 205 } 206 207 func (s *Svc) OnEpochLengthUpdate(ctx context.Context, l time.Duration) error { 208 s.length = l 209 // @TODO down the line, we ought to send an event signaling a change in epoch length 210 return nil 211 }