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 }