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 }