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  }