github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/consensus/recovery/recover.go (about)

     1  package recovery
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/rs/zerolog"
     7  
     8  	"github.com/onflow/flow-go/consensus/hotstuff"
     9  	"github.com/onflow/flow-go/consensus/hotstuff/model"
    10  	"github.com/onflow/flow-go/model/flow"
    11  )
    12  
    13  // BlockScanner describes a function for ingesting pending blocks.
    14  // Any returned errors are considered fatal.
    15  type BlockScanner func(proposal *model.Proposal) error
    16  
    17  // Recover is a utility method for recovering the HotStuff state after a restart.
    18  // It receives the list `pending` containing _all_ blocks that
    19  //   - have passed the compliance layer and stored in the protocol state
    20  //   - descend from the latest finalized block
    21  //   - are listed in ancestor-first order (i.e. for any block B ∈ pending, B's parent must
    22  //     be listed before B, unless B's parent is the latest finalized block)
    23  //
    24  // CAUTION: all pending blocks are required to be valid (guaranteed if the block passed the compliance layer)
    25  func Recover(log zerolog.Logger, pending []*flow.Header, scanners ...BlockScanner) error {
    26  	log.Info().Int("total", len(pending)).Msgf("recovery started")
    27  
    28  	// add all pending blocks to forks
    29  	for _, header := range pending {
    30  		proposal := model.ProposalFromFlow(header) // convert the header into a proposal
    31  		for _, s := range scanners {
    32  			err := s(proposal)
    33  			if err != nil {
    34  				return fmt.Errorf("scanner failed to ingest proposal: %w", err)
    35  			}
    36  		}
    37  		log.Debug().
    38  			Uint64("view", proposal.Block.View).
    39  			Hex("block_id", proposal.Block.BlockID[:]).
    40  			Msg("block recovered")
    41  	}
    42  
    43  	log.Info().Msgf("recovery completed")
    44  	return nil
    45  }
    46  
    47  // ForksState recovers Forks' internal state of blocks descending from the latest
    48  // finalized block. Caution, input blocks must be valid and in parent-first order
    49  // (unless parent is the latest finalized block).
    50  func ForksState(forks hotstuff.Forks) BlockScanner {
    51  	return func(proposal *model.Proposal) error {
    52  		err := forks.AddValidatedBlock(proposal.Block)
    53  		if err != nil {
    54  			return fmt.Errorf("could not add block %v to forks: %w", proposal.Block.BlockID, err)
    55  		}
    56  		return nil
    57  	}
    58  }
    59  
    60  // VoteAggregatorState recovers the VoteAggregator's internal state as follows:
    61  //   - Add all blocks descending from the latest finalized block to accept votes.
    62  //     Those blocks should be rapidly pruned as the node catches up.
    63  //
    64  // Caution: input blocks must be valid.
    65  func VoteAggregatorState(voteAggregator hotstuff.VoteAggregator) BlockScanner {
    66  	return func(proposal *model.Proposal) error {
    67  		voteAggregator.AddBlock(proposal)
    68  		return nil
    69  	}
    70  }
    71  
    72  // CollectParentQCs collects all parent QCs included in the blocks descending from the
    73  // latest finalized block. Caution, input blocks must be valid.
    74  func CollectParentQCs(collector Collector[*flow.QuorumCertificate]) BlockScanner {
    75  	return func(proposal *model.Proposal) error {
    76  		qc := proposal.Block.QC
    77  		if qc != nil {
    78  			collector.Append(qc)
    79  		}
    80  		return nil
    81  	}
    82  }
    83  
    84  // CollectTCs collect all TCs included in the blocks descending from the
    85  // latest finalized block. Caution, input blocks must be valid.
    86  func CollectTCs(collector Collector[*flow.TimeoutCertificate]) BlockScanner {
    87  	return func(proposal *model.Proposal) error {
    88  		tc := proposal.LastViewTC
    89  		if tc != nil {
    90  			collector.Append(tc)
    91  		}
    92  		return nil
    93  	}
    94  }
    95  
    96  // Collector for objects of generic type. Essentially, it is a stateful list.
    97  // Safe to be passed by value. Retrieve() returns the current state of the list
    98  // and is unaffected by subsequent appends.
    99  type Collector[T any] struct {
   100  	list *[]T
   101  }
   102  
   103  func NewCollector[T any]() Collector[T] {
   104  	list := make([]T, 0, 5) // heuristic: pre-allocate with some basic capacity
   105  	return Collector[T]{list: &list}
   106  }
   107  
   108  // Append adds new elements to the end of the list.
   109  func (c Collector[T]) Append(t ...T) {
   110  	*c.list = append(*c.list, t...)
   111  }
   112  
   113  // Retrieve returns the current state of the list (unaffected by subsequent append)
   114  func (c Collector[T]) Retrieve() []T {
   115  	// Under the hood, the slice is a struct containing a pointer to an underlying array and a
   116  	// `len` variable indicating how many of the array elements are occupied. Here, we are
   117  	// returning the slice struct by value, i.e. we _copy_ the array pointer and the `len` value
   118  	// and return the copy. Therefore, the returned slice is unaffected by subsequent append.
   119  	return *c.list
   120  }