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  }