github.com/prysmaticlabs/prysm@v1.4.4/shared/slotutil/slotticker.go (about)

     1  // Package slotutil includes ticker and timer-related functions for Ethereum consensus.
     2  package slotutil
     3  
     4  import (
     5  	"time"
     6  
     7  	types "github.com/prysmaticlabs/eth2-types"
     8  	"github.com/prysmaticlabs/prysm/shared/timeutils"
     9  )
    10  
    11  // The Ticker interface defines a type which can expose a
    12  // receive-only channel firing slot events.
    13  type Ticker interface {
    14  	C() <-chan types.Slot
    15  	Done()
    16  }
    17  
    18  // SlotTicker is a special ticker for the beacon chain block.
    19  // The channel emits over the slot interval, and ensures that
    20  // the ticks are in line with the genesis time. This means that
    21  // the duration between the ticks and the genesis time are always a
    22  // multiple of the slot duration.
    23  // In addition, the channel returns the new slot number.
    24  type SlotTicker struct {
    25  	c    chan types.Slot
    26  	done chan struct{}
    27  }
    28  
    29  // C returns the ticker channel. Call Cancel afterwards to ensure
    30  // that the goroutine exits cleanly.
    31  func (s *SlotTicker) C() <-chan types.Slot {
    32  	return s.c
    33  }
    34  
    35  // Done should be called to clean up the ticker.
    36  func (s *SlotTicker) Done() {
    37  	go func() {
    38  		s.done <- struct{}{}
    39  	}()
    40  }
    41  
    42  // NewSlotTicker starts and returns a new SlotTicker instance.
    43  func NewSlotTicker(genesisTime time.Time, secondsPerSlot uint64) *SlotTicker {
    44  	if genesisTime.IsZero() {
    45  		panic("zero genesis time")
    46  	}
    47  	ticker := &SlotTicker{
    48  		c:    make(chan types.Slot),
    49  		done: make(chan struct{}),
    50  	}
    51  	ticker.start(genesisTime, secondsPerSlot, timeutils.Since, timeutils.Until, time.After)
    52  	return ticker
    53  }
    54  
    55  // NewSlotTickerWithOffset starts and returns a SlotTicker instance that allows a offset of time from genesis,
    56  // entering a offset greater than secondsPerSlot is not allowed.
    57  func NewSlotTickerWithOffset(genesisTime time.Time, offset time.Duration, secondsPerSlot uint64) *SlotTicker {
    58  	if genesisTime.Unix() == 0 {
    59  		panic("zero genesis time")
    60  	}
    61  	if offset > time.Duration(secondsPerSlot)*time.Second {
    62  		panic("invalid ticker offset")
    63  	}
    64  	ticker := &SlotTicker{
    65  		c:    make(chan types.Slot),
    66  		done: make(chan struct{}),
    67  	}
    68  	ticker.start(genesisTime.Add(offset), secondsPerSlot, timeutils.Since, timeutils.Until, time.After)
    69  	return ticker
    70  }
    71  
    72  func (s *SlotTicker) start(
    73  	genesisTime time.Time,
    74  	secondsPerSlot uint64,
    75  	since, until func(time.Time) time.Duration,
    76  	after func(time.Duration) <-chan time.Time) {
    77  
    78  	d := time.Duration(secondsPerSlot) * time.Second
    79  
    80  	go func() {
    81  		sinceGenesis := since(genesisTime)
    82  
    83  		var nextTickTime time.Time
    84  		var slot types.Slot
    85  		if sinceGenesis < d {
    86  			// Handle when the current time is before the genesis time.
    87  			nextTickTime = genesisTime
    88  			slot = 0
    89  		} else {
    90  			nextTick := sinceGenesis.Truncate(d) + d
    91  			nextTickTime = genesisTime.Add(nextTick)
    92  			slot = types.Slot(nextTick / d)
    93  		}
    94  
    95  		for {
    96  			waitTime := until(nextTickTime)
    97  			select {
    98  			case <-after(waitTime):
    99  				s.c <- slot
   100  				slot++
   101  				nextTickTime = nextTickTime.Add(d)
   102  			case <-s.done:
   103  				return
   104  			}
   105  		}
   106  	}()
   107  }