github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/stdmap/incorporated_result_seals.go (about)

     1  package stdmap
     2  
     3  import (
     4  	"log"
     5  
     6  	"github.com/onflow/flow-go/model/flow"
     7  	"github.com/onflow/flow-go/module/mempool"
     8  )
     9  
    10  type sealSet map[flow.Identifier]*flow.IncorporatedResultSeal
    11  
    12  // IncorporatedResultSeals implements the incorporated result seals memory pool
    13  // of the consensus nodes, used to store seals that need to be added to blocks.
    14  // ATTENTION: This data structure should NEVER eject seals because it can break liveness.
    15  // Modules that are using this structure expect that it NEVER ejects a seal.
    16  type IncorporatedResultSeals struct {
    17  	*Backend
    18  	// index the seals by the height of the executed block
    19  	byHeight     map[uint64]sealSet
    20  	lowestHeight uint64
    21  }
    22  
    23  func indexByHeight(seal *flow.IncorporatedResultSeal) uint64 {
    24  	return seal.Header.Height
    25  }
    26  
    27  // NewIncorporatedResultSeals creates a mempool for the incorporated result seals
    28  func NewIncorporatedResultSeals(limit uint) *IncorporatedResultSeals {
    29  	byHeight := make(map[uint64]sealSet)
    30  
    31  	// This mempool implementation supports pruning by height, meaning that as soon as sealing advances
    32  	// seals will be gradually removed from mempool
    33  	// ejecting a seal from mempool means that we have reached our limit and something is very bad, meaning that sealing
    34  	// is not actually happening.
    35  	// By setting high limit ~12 hours we ensure that we have some safety window for sealing to recover and make progress
    36  	ejector := func(b *Backend) (flow.Identifier, flow.Entity, bool) {
    37  		log.Fatalf("incorporated result seals reached max capacity %d", limit)
    38  		panic("incorporated result seals reached max capacity")
    39  	}
    40  
    41  	r := &IncorporatedResultSeals{
    42  		Backend:  NewBackend(WithLimit(limit), WithEject(ejector)),
    43  		byHeight: byHeight,
    44  	}
    45  
    46  	return r
    47  }
    48  
    49  func (ir *IncorporatedResultSeals) removeFromIndex(id flow.Identifier, height uint64) {
    50  	sealsAtHeight := ir.byHeight[height]
    51  	delete(sealsAtHeight, id)
    52  	if len(sealsAtHeight) == 0 {
    53  		delete(ir.byHeight, height)
    54  	}
    55  }
    56  
    57  func (ir *IncorporatedResultSeals) removeByHeight(height uint64) {
    58  	for sealID := range ir.byHeight[height] {
    59  		ir.backData.Remove(sealID)
    60  	}
    61  	delete(ir.byHeight, height)
    62  }
    63  
    64  // Add adds an IncorporatedResultSeal to the mempool
    65  func (ir *IncorporatedResultSeals) Add(seal *flow.IncorporatedResultSeal) (bool, error) {
    66  	added := false
    67  	sealID := seal.ID()
    68  	err := ir.Backend.Run(func(_ mempool.BackData) error {
    69  		// skip elements below the pruned
    70  		if seal.Header.Height < ir.lowestHeight {
    71  			return nil
    72  		}
    73  
    74  		added = ir.backData.Add(sealID, seal)
    75  		if !added {
    76  			return nil
    77  		}
    78  
    79  		height := indexByHeight(seal)
    80  		sameHeight, ok := ir.byHeight[height]
    81  		if !ok {
    82  			sameHeight = make(sealSet)
    83  			ir.byHeight[height] = sameHeight
    84  		}
    85  		sameHeight[sealID] = seal
    86  		return nil
    87  	})
    88  
    89  	return added, err
    90  }
    91  
    92  // Size returns the size of the underlying backing store
    93  func (ir *IncorporatedResultSeals) Size() uint {
    94  	return ir.Backend.Size()
    95  }
    96  
    97  // All returns all the items in the mempool
    98  func (ir *IncorporatedResultSeals) All() []*flow.IncorporatedResultSeal {
    99  	entities := ir.Backend.All()
   100  	res := make([]*flow.IncorporatedResultSeal, 0, ir.backData.Size())
   101  	for _, entity := range entities {
   102  		// uncaught type assertion; should never panic as the mempool only stores IncorporatedResultSeal:
   103  		res = append(res, entity.(*flow.IncorporatedResultSeal))
   104  	}
   105  	return res
   106  }
   107  
   108  // ByID gets an IncorporatedResultSeal by IncorporatedResult ID
   109  func (ir *IncorporatedResultSeals) ByID(id flow.Identifier) (*flow.IncorporatedResultSeal, bool) {
   110  	entity, ok := ir.Backend.ByID(id)
   111  	if !ok {
   112  		return nil, false
   113  	}
   114  	// uncaught type assertion; should never panic as the mempool only stores IncorporatedResultSeal:
   115  	return entity.(*flow.IncorporatedResultSeal), true
   116  }
   117  
   118  // Remove removes an IncorporatedResultSeal from the mempool
   119  func (ir *IncorporatedResultSeals) Remove(id flow.Identifier) bool {
   120  	removed := false
   121  	err := ir.Backend.Run(func(_ mempool.BackData) error {
   122  		var entity flow.Entity
   123  		entity, removed = ir.backData.Remove(id)
   124  		if !removed {
   125  			return nil
   126  		}
   127  		seal := entity.(*flow.IncorporatedResultSeal)
   128  		ir.removeFromIndex(id, seal.Header.Height)
   129  		return nil
   130  	})
   131  	if err != nil {
   132  		panic(err)
   133  	}
   134  	return removed
   135  }
   136  
   137  func (ir *IncorporatedResultSeals) Clear() {
   138  	err := ir.Backend.Run(func(_ mempool.BackData) error {
   139  		ir.backData.Clear()
   140  		ir.byHeight = make(map[uint64]sealSet)
   141  		return nil
   142  	})
   143  	if err != nil {
   144  		panic(err)
   145  	}
   146  }
   147  
   148  // PruneUpToHeight remove all seals for blocks whose height is strictly
   149  // smaller that height. Note: seals for blocks at height are retained.
   150  // After pruning, seals below for blocks below the given height are dropped.
   151  //
   152  // Monotonicity Requirement:
   153  // The pruned height cannot decrease, as we cannot recover already pruned elements.
   154  // If `height` is smaller than the previous value, the previous value is kept
   155  // and the sentinel mempool.BelowPrunedThresholdError is returned.
   156  func (ir *IncorporatedResultSeals) PruneUpToHeight(height uint64) error {
   157  	return ir.Backend.Run(func(backData mempool.BackData) error {
   158  		if height < ir.lowestHeight {
   159  			return mempool.NewBelowPrunedThresholdErrorf(
   160  				"pruning height: %d, existing height: %d", height, ir.lowestHeight)
   161  		}
   162  
   163  		if backData.Size() == 0 {
   164  			ir.lowestHeight = height
   165  			return nil
   166  		}
   167  		// Optimization: if there are less height in the index than the height range to prune,
   168  		// range to prune, then just go through each seal.
   169  		// Otherwise, go through each height to prune.
   170  		if uint64(len(ir.byHeight)) < height-ir.lowestHeight {
   171  			for h := range ir.byHeight {
   172  				if h < height {
   173  					ir.removeByHeight(h)
   174  				}
   175  			}
   176  		} else {
   177  			for h := ir.lowestHeight; h < height; h++ {
   178  				ir.removeByHeight(h)
   179  			}
   180  		}
   181  		ir.lowestHeight = height
   182  		return nil
   183  	})
   184  }