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  }