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

     1  package stdmap
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/onflow/flow-go/model/flow"
     7  	"github.com/onflow/flow-go/module/mempool"
     8  	"github.com/onflow/flow-go/storage"
     9  )
    10  
    11  type receiptsSet map[flow.Identifier]struct{}
    12  
    13  // PendingReceipts stores pending receipts indexed by the id.
    14  // It also maintains a secondary index on the previous result id.
    15  // in order to allow to find receipts by the previous result id.
    16  type PendingReceipts struct {
    17  	*Backend
    18  	headers storage.Headers // used to query headers of executed blocks
    19  	// secondary index by parent result id, since multiple receipts could
    20  	// have the same parent result, (even if they have different result)
    21  	byPreviousResultID map[flow.Identifier]receiptsSet
    22  	// secondary index by height, we need this to prune pending receipts to some height
    23  	// it's safe to cleanup this index only when pruning. Even if some receipts are deleted manually,
    24  	// eventually index will be cleaned up.
    25  	byHeight     map[uint64]receiptsSet
    26  	lowestHeight uint64
    27  }
    28  
    29  func indexByPreviousResultID(receipt *flow.ExecutionReceipt) flow.Identifier {
    30  	return receipt.ExecutionResult.PreviousResultID
    31  }
    32  
    33  func (r *PendingReceipts) indexByHeight(receipt *flow.ExecutionReceipt) (uint64, error) {
    34  	header, err := r.headers.ByBlockID(receipt.ExecutionResult.BlockID)
    35  	if err != nil {
    36  		return 0, fmt.Errorf("could not retreieve block by ID %v: %w", receipt.ExecutionResult.BlockID, err)
    37  	}
    38  	return header.Height, nil
    39  }
    40  
    41  // NewPendingReceipts creates a new memory pool for execution receipts.
    42  func NewPendingReceipts(headers storage.Headers, limit uint) *PendingReceipts {
    43  	// create the receipts memory pool with the lookup maps
    44  	r := &PendingReceipts{
    45  		Backend:            NewBackend(WithLimit(limit)),
    46  		headers:            headers,
    47  		byPreviousResultID: make(map[flow.Identifier]receiptsSet),
    48  		byHeight:           make(map[uint64]receiptsSet),
    49  	}
    50  	// TODO: there is smarter eject exists. For instance:
    51  	// if the mempool fills up, we want to eject the receipts for the highest blocks
    52  	// See https://github.com/onflow/flow-go/pull/387/files#r574228078
    53  	r.RegisterEjectionCallbacks(func(entity flow.Entity) {
    54  		receipt := entity.(*flow.ExecutionReceipt)
    55  		removeReceipt(receipt, r.backData, r.byPreviousResultID)
    56  	})
    57  	return r
    58  }
    59  
    60  func removeReceipt(
    61  	receipt *flow.ExecutionReceipt,
    62  	entities mempool.BackData,
    63  	byPreviousResultID map[flow.Identifier]receiptsSet) {
    64  
    65  	receiptID := receipt.ID()
    66  	entities.Remove(receiptID)
    67  
    68  	index := indexByPreviousResultID(receipt)
    69  	siblings := byPreviousResultID[index]
    70  	delete(siblings, receiptID)
    71  	if len(siblings) == 0 {
    72  		delete(byPreviousResultID, index)
    73  	}
    74  }
    75  
    76  // Add adds an execution receipt to the mempool.
    77  func (r *PendingReceipts) Add(receipt *flow.ExecutionReceipt) bool {
    78  	added := false
    79  	err := r.Backend.Run(func(backData mempool.BackData) error {
    80  		receiptID := receipt.ID()
    81  		_, exists := backData.ByID(receiptID)
    82  		if exists {
    83  			// duplication
    84  			return nil
    85  		}
    86  
    87  		height, err := r.indexByHeight(receipt)
    88  		if err != nil {
    89  			return err
    90  		}
    91  
    92  		// skip elements below the pruned
    93  		if height < r.lowestHeight {
    94  			return nil
    95  		}
    96  
    97  		backData.Add(receiptID, receipt)
    98  
    99  		// update index AND the backdata in one "transaction"
   100  		previousResultID := indexByPreviousResultID(receipt)
   101  		siblings, ok := r.byPreviousResultID[previousResultID]
   102  		if !ok {
   103  			siblings = make(receiptsSet)
   104  			r.byPreviousResultID[previousResultID] = siblings
   105  		}
   106  		siblings[receiptID] = struct{}{}
   107  
   108  		sameHeight, ok := r.byHeight[height]
   109  		if !ok {
   110  			sameHeight = make(receiptsSet)
   111  			r.byHeight[height] = sameHeight
   112  		}
   113  		sameHeight[receiptID] = struct{}{}
   114  
   115  		added = true
   116  		return nil
   117  	})
   118  	if err != nil {
   119  		panic(err)
   120  	}
   121  
   122  	return added
   123  }
   124  
   125  // Remove will remove a receipt by ID.
   126  func (r *PendingReceipts) Remove(receiptID flow.Identifier) bool {
   127  	removed := false
   128  	err := r.Backend.Run(func(backData mempool.BackData) error {
   129  		entity, ok := backData.ByID(receiptID)
   130  		if ok {
   131  			receipt := entity.(*flow.ExecutionReceipt)
   132  			removeReceipt(receipt, r.backData, r.byPreviousResultID)
   133  			removed = true
   134  		}
   135  		return nil
   136  	})
   137  	if err != nil {
   138  		panic(err)
   139  	}
   140  	return removed
   141  }
   142  
   143  // ByPreviousResultID returns receipts whose previous result ID matches the given ID
   144  func (r *PendingReceipts) ByPreviousResultID(previousResultID flow.Identifier) []*flow.ExecutionReceipt {
   145  	var receipts []*flow.ExecutionReceipt
   146  	err := r.Backend.Run(func(backData mempool.BackData) error {
   147  		siblings, foundIndex := r.byPreviousResultID[previousResultID]
   148  		if !foundIndex {
   149  			return nil
   150  		}
   151  		for receiptID := range siblings {
   152  			entity, ok := backData.ByID(receiptID)
   153  			if !ok {
   154  				return fmt.Errorf("inconsistent index. can not find entity by id: %v", receiptID)
   155  			}
   156  			receipt, ok := entity.(*flow.ExecutionReceipt)
   157  			if !ok {
   158  				return fmt.Errorf("could not convert entity to receipt: %v", receiptID)
   159  			}
   160  			receipts = append(receipts, receipt)
   161  		}
   162  		return nil
   163  	})
   164  	if err != nil {
   165  		panic(err)
   166  	}
   167  
   168  	return receipts
   169  }
   170  
   171  // Size will return the total number of pending receipts
   172  func (r *PendingReceipts) Size() uint {
   173  	return r.Backend.Size()
   174  }
   175  
   176  // PruneUpToHeight remove all receipts for blocks whose height is strictly
   177  // smaller that height. Note: receipts for blocks at height are retained.
   178  // After pruning, receipts below for blocks below the given height are dropped.
   179  //
   180  // Monotonicity Requirement:
   181  // The pruned height cannot decrease, as we cannot recover already pruned elements.
   182  // If `height` is smaller than the previous value, the previous value is kept
   183  // and the sentinel mempool.BelowPrunedThresholdError is returned.
   184  func (r *PendingReceipts) PruneUpToHeight(height uint64) error {
   185  	return r.Backend.Run(func(backData mempool.BackData) error {
   186  		if height < r.lowestHeight {
   187  			return mempool.NewBelowPrunedThresholdErrorf(
   188  				"pruning height: %d, existing height: %d", height, r.lowestHeight)
   189  		}
   190  
   191  		if backData.Size() == 0 {
   192  			r.lowestHeight = height
   193  			return nil
   194  		}
   195  		// Optimization: if there are less height in the index than the height range to prune,
   196  		// range to prune, then just go through each seal.
   197  		// Otherwise, go through each height to prune.
   198  		if uint64(len(r.byHeight)) < height-r.lowestHeight {
   199  			for h := range r.byHeight {
   200  				if h < height {
   201  					r.removeByHeight(h, backData)
   202  				}
   203  			}
   204  		} else {
   205  			for h := r.lowestHeight; h < height; h++ {
   206  				r.removeByHeight(h, backData)
   207  			}
   208  		}
   209  		r.lowestHeight = height
   210  		return nil
   211  	})
   212  }
   213  
   214  func (r *PendingReceipts) removeByHeight(height uint64, backData mempool.BackData) {
   215  	for receiptID := range r.byHeight[height] {
   216  		entity, ok := backData.ByID(receiptID)
   217  		if ok {
   218  			removeReceipt(entity.(*flow.ExecutionReceipt), r.backData, r.byPreviousResultID)
   219  		}
   220  	}
   221  	delete(r.byHeight, height)
   222  }