github.com/koko1123/flow-go-1@v0.29.6/engine/common/synchronization/finalized_snapshot.go (about)

     1  package synchronization
     2  
     3  import (
     4  	"fmt"
     5  	"sync"
     6  
     7  	"github.com/rs/zerolog"
     8  
     9  	"github.com/koko1123/flow-go-1/consensus/hotstuff/model"
    10  	"github.com/koko1123/flow-go-1/consensus/hotstuff/notifications/pubsub"
    11  	"github.com/koko1123/flow-go-1/engine"
    12  	"github.com/koko1123/flow-go-1/model/flow"
    13  	"github.com/koko1123/flow-go-1/module/lifecycle"
    14  	"github.com/koko1123/flow-go-1/state/protocol"
    15  )
    16  
    17  // FinalizedHeaderCache represents the cached value of the latest finalized header.
    18  // It is used in Engine to access latest valid data.
    19  type FinalizedHeaderCache struct {
    20  	mu sync.RWMutex
    21  
    22  	log                       zerolog.Logger
    23  	state                     protocol.State
    24  	lastFinalizedHeader       *flow.Header
    25  	finalizationEventNotifier engine.Notifier // notifier for finalization events
    26  
    27  	lm      *lifecycle.LifecycleManager
    28  	stopped chan struct{}
    29  }
    30  
    31  // NewFinalizedHeaderCache creates a new finalized header cache.
    32  func NewFinalizedHeaderCache(log zerolog.Logger, state protocol.State, finalizationDistributor *pubsub.FinalizationDistributor) (*FinalizedHeaderCache, error) {
    33  	cache := &FinalizedHeaderCache{
    34  		state:                     state,
    35  		lm:                        lifecycle.NewLifecycleManager(),
    36  		log:                       log.With().Str("component", "finalized_snapshot_cache").Logger(),
    37  		finalizationEventNotifier: engine.NewNotifier(),
    38  		stopped:                   make(chan struct{}),
    39  	}
    40  
    41  	snapshot, err := cache.getHeader()
    42  	if err != nil {
    43  		return nil, fmt.Errorf("could not apply last finalized state")
    44  	}
    45  
    46  	cache.lastFinalizedHeader = snapshot
    47  
    48  	finalizationDistributor.AddOnBlockFinalizedConsumer(cache.onFinalizedBlock)
    49  
    50  	return cache, nil
    51  }
    52  
    53  // Get returns the last locally cached finalized header.
    54  func (f *FinalizedHeaderCache) Get() *flow.Header {
    55  	f.mu.RLock()
    56  	defer f.mu.RUnlock()
    57  	return f.lastFinalizedHeader
    58  }
    59  
    60  func (f *FinalizedHeaderCache) getHeader() (*flow.Header, error) {
    61  	finalSnapshot := f.state.Final()
    62  	head, err := finalSnapshot.Head()
    63  	if err != nil {
    64  		return nil, fmt.Errorf("could not get last finalized header: %w", err)
    65  	}
    66  
    67  	return head, nil
    68  }
    69  
    70  // updateHeader updates latest locally cached finalized header.
    71  func (f *FinalizedHeaderCache) updateHeader() error {
    72  	f.log.Debug().Msg("updating header")
    73  
    74  	head, err := f.getHeader()
    75  	if err != nil {
    76  		f.log.Err(err).Msg("failed to get header")
    77  		return err
    78  	}
    79  
    80  	f.log.Debug().
    81  		Str("block_id", head.ID().String()).
    82  		Uint64("height", head.Height).
    83  		Msg("got new header")
    84  
    85  	f.mu.Lock()
    86  	defer f.mu.Unlock()
    87  
    88  	if f.lastFinalizedHeader.Height < head.Height {
    89  		f.lastFinalizedHeader = head
    90  	}
    91  
    92  	return nil
    93  }
    94  
    95  func (f *FinalizedHeaderCache) Ready() <-chan struct{} {
    96  	f.lm.OnStart(func() {
    97  		go f.finalizationProcessingLoop()
    98  	})
    99  	return f.lm.Started()
   100  }
   101  
   102  func (f *FinalizedHeaderCache) Done() <-chan struct{} {
   103  	f.lm.OnStop(func() {
   104  		<-f.stopped
   105  	})
   106  	return f.lm.Stopped()
   107  }
   108  
   109  // onFinalizedBlock implements the `OnFinalizedBlock` callback from the `hotstuff.FinalizationConsumer`
   110  // (1) Updates local state of last finalized snapshot.
   111  //
   112  // CAUTION: the input to this callback is treated as trusted; precautions should be taken that messages
   113  // from external nodes cannot be considered as inputs to this function
   114  func (f *FinalizedHeaderCache) onFinalizedBlock(block *model.Block) {
   115  	f.log.Debug().Str("block_id", block.BlockID.String()).Msg("received new block finalization callback")
   116  	// notify that there is new finalized block
   117  	f.finalizationEventNotifier.Notify()
   118  }
   119  
   120  // finalizationProcessingLoop is a separate goroutine that performs processing of finalization events
   121  func (f *FinalizedHeaderCache) finalizationProcessingLoop() {
   122  	defer close(f.stopped)
   123  
   124  	f.log.Debug().Msg("starting finalization processing loop")
   125  	notifier := f.finalizationEventNotifier.Channel()
   126  	for {
   127  		select {
   128  		case <-f.lm.ShutdownSignal():
   129  			return
   130  		case <-notifier:
   131  			err := f.updateHeader()
   132  			if err != nil {
   133  				f.log.Fatal().Err(err).Msg("could not process latest finalized block")
   134  			}
   135  		}
   136  	}
   137  }