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 }