github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/state/protocol/events/gadgets/heights.go (about) 1 package gadgets 2 3 import ( 4 "sync" 5 6 "github.com/onflow/flow-go/model/flow" 7 "github.com/onflow/flow-go/state/protocol/events" 8 ) 9 10 // Heights is a protocol events consumer that provides an interface to 11 // subscribe to callbacks when chain state reaches a particular height. 12 type Heights struct { 13 events.Noop 14 mu sync.Mutex 15 heights map[uint64][]func() 16 finalizedHeight uint64 17 } 18 19 // NewHeights returns a new Heights events gadget. 20 func NewHeights() *Heights { 21 heights := &Heights{ 22 heights: make(map[uint64][]func()), 23 finalizedHeight: 0, 24 } 25 return heights 26 } 27 28 // BlockFinalized handles block finalized protocol events, triggering height 29 // callbacks as needed. 30 func (g *Heights) BlockFinalized(block *flow.Header) { 31 g.mu.Lock() 32 defer g.mu.Unlock() 33 34 lastFinalizedHeight := g.finalizedHeight 35 final := block.Height 36 g.finalizedHeight = final 37 38 callbacks := g.heights[final] 39 for _, callback := range callbacks { // safe when callbacks is nil 40 callback() 41 } 42 43 // to safely and efficiently prune the height callbacks, only prune 44 // potentially stale (below latest finalized) heights the first time 45 // we observe a finalized block 46 47 // typical case, we are finalized the child of the last observed - we 48 // only need to clear the callbacks for the current height 49 if final == lastFinalizedHeight+1 { 50 delete(g.heights, final) 51 return 52 } 53 54 // non-typical case - there is a gap between our "last finalized height" 55 // and this block - this means this is the first block we are observing. 56 // this is the only time it possible to have stale heights, therefore prune 57 // the whole heights map 58 for height := range g.heights { 59 if height <= final { 60 delete(g.heights, height) 61 } 62 } 63 } 64 65 // OnHeight registers the callback for the given height, only if the height has 66 // not already been finalized. 67 func (g *Heights) OnHeight(height uint64, callback func()) { 68 g.mu.Lock() 69 defer g.mu.Unlock() 70 71 // skip already finalized heights - they will never be invoked 72 if height <= g.finalizedHeight { 73 return 74 } 75 g.heights[height] = append(g.heights[height], callback) 76 }