github.com/koko1123/flow-go-1@v0.29.6/engine/consensus/approvals/request_tracker.go (about) 1 package approvals 2 3 import ( 4 "fmt" 5 "math/rand" 6 "sync" 7 "time" 8 9 "github.com/koko1123/flow-go-1/model/flow" 10 "github.com/koko1123/flow-go-1/module/mempool" 11 "github.com/koko1123/flow-go-1/storage" 12 ) 13 14 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 15 RequestTrackerItem 16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ 17 18 // RequestTrackerItem is an object that keeps track of how many times a request 19 // has been made, as well as the time until a new request can be made. 20 // It is not concurrency-safe. 21 type RequestTrackerItem struct { 22 Requests uint 23 NextTimeout time.Time 24 blackoutPeriodMin int 25 blackoutPeriodMax int 26 } 27 28 // NewRequestTrackerItem instantiates a new RequestTrackerItem where the 29 // NextTimeout is evaluated to the current time plus a random blackout period 30 // contained between min and max. 31 func NewRequestTrackerItem(blackoutPeriodMin, blackoutPeriodMax int) RequestTrackerItem { 32 item := RequestTrackerItem{ 33 blackoutPeriodMin: blackoutPeriodMin, 34 blackoutPeriodMax: blackoutPeriodMax, 35 } 36 item.NextTimeout = randBlackout(blackoutPeriodMin, blackoutPeriodMax) 37 return item 38 } 39 40 // Update creates a _new_ RequestTrackerItem with incremented request number and updated NextTimeout. 41 func (i RequestTrackerItem) Update() RequestTrackerItem { 42 i.Requests++ 43 i.NextTimeout = randBlackout(i.blackoutPeriodMin, i.blackoutPeriodMax) 44 return i 45 } 46 47 func (i RequestTrackerItem) IsBlackout() bool { 48 return time.Now().Before(i.NextTimeout) 49 } 50 51 func randBlackout(min int, max int) time.Time { 52 blackoutSeconds := rand.Intn(max-min+1) + min 53 blackout := time.Now().Add(time.Duration(blackoutSeconds) * time.Second) 54 return blackout 55 } 56 57 /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 58 RequestTracker 59 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ 60 61 // RequestTracker is an index of RequestTrackerItems indexed by execution result 62 // Index on result ID, incorporated block ID and chunk index. 63 // Is concurrency-safe. 64 type RequestTracker struct { 65 headers storage.Headers 66 index map[flow.Identifier]map[flow.Identifier]map[uint64]RequestTrackerItem 67 blackoutPeriodMin int 68 blackoutPeriodMax int 69 lock sync.Mutex 70 byHeight map[uint64]flow.IdentifierList 71 lowestHeight uint64 72 } 73 74 // NewRequestTracker instantiates a new RequestTracker with blackout periods 75 // between min and max seconds. 76 func NewRequestTracker(headers storage.Headers, blackoutPeriodMin, blackoutPeriodMax int) *RequestTracker { 77 return &RequestTracker{ 78 headers: headers, 79 index: make(map[flow.Identifier]map[flow.Identifier]map[uint64]RequestTrackerItem), 80 byHeight: make(map[uint64]flow.IdentifierList), 81 blackoutPeriodMin: blackoutPeriodMin, 82 blackoutPeriodMax: blackoutPeriodMax, 83 } 84 } 85 86 // TryUpdate tries to update tracker item if it's not in blackout period. Returns the tracker item for a specific chunk 87 // (creates it if it doesn't exists) and whenever request item was successfully updated or not. 88 // Since RequestTracker prunes items by height it can't accept items for height lower than cached lowest height. 89 // If height of executed block pointed by execution result is smaller than the lowest height, sentinel mempool.DecreasingPruningHeightError is returned. 90 // In case execution result points to unknown executed block exception will be returned. 91 func (rt *RequestTracker) TryUpdate(result *flow.ExecutionResult, incorporatedBlockID flow.Identifier, chunkIndex uint64) (RequestTrackerItem, bool, error) { 92 resultID := result.ID() 93 rt.lock.Lock() 94 defer rt.lock.Unlock() 95 item, ok := rt.index[resultID][incorporatedBlockID][chunkIndex] 96 97 if !ok { 98 item = NewRequestTrackerItem(rt.blackoutPeriodMin, rt.blackoutPeriodMax) 99 err := rt.set(resultID, result.BlockID, incorporatedBlockID, chunkIndex, item) 100 if err != nil { 101 return item, false, fmt.Errorf("could not set created tracker item: %w", err) 102 } 103 } 104 105 canUpdate := !item.IsBlackout() 106 if canUpdate { 107 item = item.Update() 108 rt.index[resultID][incorporatedBlockID][chunkIndex] = item 109 } 110 111 return item, canUpdate, nil 112 } 113 114 // set inserts or updates the tracker item for a specific chunk. 115 func (rt *RequestTracker) set(resultID, executedBlockID, incorporatedBlockID flow.Identifier, chunkIndex uint64, item RequestTrackerItem) error { 116 executedBlock, err := rt.headers.ByBlockID(executedBlockID) 117 if err != nil { 118 return fmt.Errorf("could not retrieve block by id %v: %w", executedBlockID, err) 119 } 120 121 if executedBlock.Height < rt.lowestHeight { 122 return mempool.NewDecreasingPruningHeightErrorf( 123 "adding height: %d, existing height: %d", executedBlock.Height, rt.lowestHeight) 124 } 125 126 level1, level1found := rt.index[resultID] 127 if !level1found { 128 level1 = make(map[flow.Identifier]map[uint64]RequestTrackerItem) 129 rt.index[resultID] = level1 130 } 131 level2, level2found := level1[incorporatedBlockID] 132 if !level2found { 133 level2 = make(map[uint64]RequestTrackerItem) 134 level1[incorporatedBlockID] = level2 135 } 136 level2[chunkIndex] = item 137 138 // update secondary height based index for correct pruning 139 rt.byHeight[executedBlock.Height] = append(rt.byHeight[executedBlock.Height], resultID) 140 141 return nil 142 } 143 144 // GetAllIds returns all result IDs that we are indexing 145 func (rt *RequestTracker) GetAllIds() []flow.Identifier { 146 rt.lock.Lock() 147 defer rt.lock.Unlock() 148 ids := make([]flow.Identifier, 0, len(rt.index)) 149 for resultID := range rt.index { 150 ids = append(ids, resultID) 151 } 152 return ids 153 } 154 155 // Remove removes all entries pertaining to an execution result 156 func (rt *RequestTracker) Remove(resultIDs ...flow.Identifier) { 157 if len(resultIDs) == 0 { 158 return 159 } 160 rt.lock.Lock() 161 defer rt.lock.Unlock() 162 for _, resultID := range resultIDs { 163 delete(rt.index, resultID) 164 } 165 } 166 167 // PruneUpToHeight remove all tracker items for blocks whose height is strictly 168 // smaller that height. Note: items for blocks at height are retained. 169 // After pruning, items for blocks below the given height are dropped. 170 // 171 // Monotonicity Requirement: 172 // The pruned height cannot decrease, as we cannot recover already pruned elements. 173 // If `height` is smaller than the previous value, the previous value is kept 174 // and the sentinel mempool.DecreasingPruningHeightError is returned. 175 func (rt *RequestTracker) PruneUpToHeight(height uint64) error { 176 rt.lock.Lock() 177 defer rt.lock.Unlock() 178 if height < rt.lowestHeight { 179 return mempool.NewDecreasingPruningHeightErrorf( 180 "pruning height: %d, existing height: %d", height, rt.lowestHeight) 181 } 182 183 if len(rt.index) == 0 { 184 rt.lowestHeight = height 185 return nil 186 } 187 188 // Optimization: if there are less elements in the `byHeight` map 189 // than the height range to prune: inspect each map element. 190 // Otherwise, go through each height to prune. 191 if uint64(len(rt.byHeight)) < height-rt.lowestHeight { 192 for h := range rt.byHeight { 193 if h < height { 194 rt.removeByHeight(h) 195 } 196 } 197 } else { 198 for h := rt.lowestHeight; h < height; h++ { 199 rt.removeByHeight(h) 200 } 201 } 202 rt.lowestHeight = height 203 return nil 204 } 205 206 func (rt *RequestTracker) removeByHeight(height uint64) { 207 for _, resultID := range rt.byHeight[height] { 208 delete(rt.index, resultID) 209 } 210 delete(rt.byHeight, height) 211 }