github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/events/finalized_header_cache.go (about) 1 package events 2 3 import ( 4 "fmt" 5 "sync/atomic" 6 7 "github.com/onflow/flow-go/consensus/hotstuff" 8 "github.com/onflow/flow-go/consensus/hotstuff/model" 9 "github.com/onflow/flow-go/model/flow" 10 "github.com/onflow/flow-go/module" 11 "github.com/onflow/flow-go/module/component" 12 "github.com/onflow/flow-go/state/protocol" 13 ) 14 15 // FinalizedHeaderCache caches a copy of the most recently finalized block header by 16 // consuming BlockFinalized events from HotStuff, using a FinalizationActor. 17 // The constructor returns both the cache and a worker function. 18 // 19 // NOTE: The protocol state already guarantees that state.Final().Head() will be cached, however, 20 // since the protocol state is shared among many components, there may be high contention on its cache. 21 // The FinalizedHeaderCache can be used in place of state.Final().Head() to avoid read contention with other components. 22 type FinalizedHeaderCache struct { 23 state protocol.State 24 val *atomic.Pointer[flow.Header] 25 *FinalizationActor // implement hotstuff.FinalizationConsumer 26 } 27 28 var _ module.FinalizedHeaderCache = (*FinalizedHeaderCache)(nil) 29 var _ hotstuff.FinalizationConsumer = (*FinalizedHeaderCache)(nil) 30 31 // Get returns the most recently finalized block. 32 // Guaranteed to be non-nil after construction. 33 func (cache *FinalizedHeaderCache) Get() *flow.Header { 34 return cache.val.Load() 35 } 36 37 // update reads the latest finalized header and updates the cache. 38 // No errors are expected during normal operation. 39 func (cache *FinalizedHeaderCache) update() error { 40 final, err := cache.state.Final().Head() 41 if err != nil { 42 return fmt.Errorf("could not retrieve latest finalized header: %w", err) 43 } 44 cache.val.Store(final) 45 return nil 46 } 47 48 // NewFinalizedHeaderCache returns a new FinalizedHeaderCache and the ComponentWorker function. 49 // The caller MUST: 50 // - subscribe the `FinalizedHeaderCache` (first return value) to the `FinalizationDistributor` 51 // that is distributing the consensus logic's `OnFinalizedBlock` events 52 // - start the returned ComponentWorker logic (second return value) in a goroutine to maintain the cache. 53 func NewFinalizedHeaderCache(state protocol.State) (*FinalizedHeaderCache, component.ComponentWorker, error) { 54 cache := &FinalizedHeaderCache{ 55 state: state, 56 val: new(atomic.Pointer[flow.Header]), 57 } 58 // initialize the cache with the current finalized header 59 if err := cache.update(); err != nil { 60 return nil, nil, fmt.Errorf("could not initialize cache: %w", err) 61 } 62 63 // create a worker to continuously track the latest finalized header 64 actor, worker := NewFinalizationActor(func(_ *model.Block) error { 65 return cache.update() 66 }) 67 cache.FinalizationActor = actor 68 69 return cache, worker, nil 70 }