github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/module/mempool/stdmap/chunk_requests.go (about) 1 package stdmap 2 3 import ( 4 "fmt" 5 "time" 6 7 "github.com/onflow/flow-go/model/chunks" 8 "github.com/onflow/flow-go/model/flow" 9 "github.com/onflow/flow-go/model/verification" 10 "github.com/onflow/flow-go/module/mempool" 11 ) 12 13 // ChunkRequests is an implementation of in-memory storage for maintaining chunk requests data objects. 14 // 15 // In this implementation, the ChunkRequests 16 // wraps the ChunkDataPackRequests around an internal ChunkRequestStatus data object, and maintains the wrapped 17 // version in memory. 18 type ChunkRequests struct { 19 *Backend 20 } 21 22 func NewChunkRequests(limit uint) *ChunkRequests { 23 return &ChunkRequests{ 24 Backend: NewBackend(WithLimit(limit)), 25 } 26 } 27 28 func toChunkRequestStatus(entity flow.Entity) *chunkRequestStatus { 29 status, ok := entity.(*chunkRequestStatus) 30 if !ok { 31 panic(fmt.Sprintf("could not convert the entity into chunk status from the mempool: %v", entity)) 32 } 33 return status 34 } 35 36 // RequestHistory returns the number of times the chunk has been requested, 37 // last time the chunk has been requested, and the retryAfter duration of the 38 // underlying request status of this chunk. 39 // 40 // The last boolean parameter returns whether a chunk request for this chunk ID 41 // exists in memory-pool. 42 func (cs *ChunkRequests) RequestHistory(chunkID flow.Identifier) (uint64, time.Time, time.Duration, bool) { 43 var lastAttempt time.Time 44 var retryAfter time.Duration 45 var attempts uint64 46 47 err := cs.Backend.Run(func(backdata mempool.BackData) error { 48 entity, ok := backdata.ByID(chunkID) 49 if !ok { 50 return fmt.Errorf("request does not exist for chunk %x", chunkID) 51 } 52 53 request := toChunkRequestStatus(entity) 54 lastAttempt = request.LastAttempt 55 retryAfter = request.RetryAfter 56 attempts = request.Attempt 57 return nil 58 }) 59 60 return attempts, lastAttempt, retryAfter, err == nil 61 } 62 63 // Add provides insertion functionality into the memory pool. 64 // The insertion is only successful if there is no duplicate chunk request for the same 65 // tuple of (chunkID, resultID, chunkIndex). 66 func (cs *ChunkRequests) Add(request *verification.ChunkDataPackRequest) bool { 67 err := cs.Backend.Run(func(backdata mempool.BackData) error { 68 entity, exists := backdata.ByID(request.ChunkID) 69 chunkLocatorID := request.Locator.ID() 70 71 if !exists { 72 locators := make(chunks.LocatorMap) 73 locators[chunkLocatorID] = &request.Locator 74 75 // no chunk request status exists for this chunk ID, hence initiating one. 76 status := &chunkRequestStatus{ 77 Locators: locators, 78 RequestInfo: request.ChunkDataPackRequestInfo, 79 } 80 added := backdata.Add(request.ChunkID, status) 81 if !added { 82 return fmt.Errorf("potential race condition in adding chunk requests") 83 } 84 return nil 85 } 86 87 status := toChunkRequestStatus(entity) 88 if _, ok := status.Locators[chunkLocatorID]; ok { 89 return fmt.Errorf("chunk request exists with same locator (result_id=%x, chunk_index=%d)", request.Locator.ResultID, request.Locator.Index) 90 } 91 92 status.Locators[chunkLocatorID] = &request.Locator 93 status.RequestInfo.Agrees = status.RequestInfo.Agrees.Union(request.Agrees) 94 status.RequestInfo.Disagrees = status.RequestInfo.Disagrees.Union(request.Disagrees) 95 status.RequestInfo.Targets = status.RequestInfo.Targets.Union(request.Targets) 96 97 backdata.Add(request.ChunkID, status) 98 return nil 99 }) 100 101 return err == nil 102 } 103 104 // Remove provides deletion functionality from the memory pool. 105 // If there is a chunk request with this ID, Remove removes it and returns true. 106 // Otherwise it returns false. 107 func (cs *ChunkRequests) Remove(chunkID flow.Identifier) bool { 108 return cs.Backend.Remove(chunkID) 109 } 110 111 // PopAll atomically returns all locators associated with this chunk ID while clearing out the 112 // chunk request status for this chunk id. 113 // Boolean return value indicates whether there are requests in the memory pool associated 114 // with chunk ID. 115 func (cs *ChunkRequests) PopAll(chunkID flow.Identifier) (chunks.LocatorMap, bool) { 116 var locators map[flow.Identifier]*chunks.Locator 117 118 err := cs.Backend.Run(func(backdata mempool.BackData) error { 119 entity, exists := backdata.ByID(chunkID) 120 if !exists { 121 return fmt.Errorf("not exist") 122 } 123 locators = toChunkRequestStatus(entity).Locators 124 125 _, removed := backdata.Remove(chunkID) 126 if !removed { 127 return fmt.Errorf("potential race condition on removing chunk request from mempool") 128 } 129 130 return nil 131 }) 132 133 if err != nil { 134 return nil, false 135 } 136 137 return locators, true 138 } 139 140 // IncrementAttempt increments the Attempt field of the corresponding status of the 141 // chunk request in memory pool that has the specified chunk ID. 142 // If such chunk ID does not exist in the memory pool, it returns false. 143 // 144 // The increments are done atomically, thread-safe, and in isolation. 145 func (cs *ChunkRequests) IncrementAttempt(chunkID flow.Identifier) bool { 146 err := cs.Backend.Run(func(backdata mempool.BackData) error { 147 entity, exists := backdata.ByID(chunkID) 148 if !exists { 149 return fmt.Errorf("not exist") 150 } 151 chunk := toChunkRequestStatus(entity) 152 chunk.Attempt++ 153 chunk.LastAttempt = time.Now() 154 return nil 155 }) 156 157 return err == nil 158 } 159 160 // All returns all chunk requests stored in this memory pool. 161 func (cs *ChunkRequests) All() verification.ChunkDataPackRequestInfoList { 162 all := cs.Backend.All() 163 requestInfoList := verification.ChunkDataPackRequestInfoList{} 164 for _, entity := range all { 165 requestInfo := toChunkRequestStatus(entity).RequestInfo 166 requestInfoList = append(requestInfoList, &requestInfo) 167 } 168 return requestInfoList 169 } 170 171 // UpdateRequestHistory updates the request history of the specified chunk ID. If the update was successful, i.e., 172 // the updater returns true, the result of update is committed to the mempool, and the time stamp of the chunk request 173 // is updated to the current time. Otherwise, it aborts and returns false. 174 // 175 // It returns the updated request history values. 176 // 177 // The updates under this method are atomic, thread-safe, and done in isolation. 178 func (cs *ChunkRequests) UpdateRequestHistory(chunkID flow.Identifier, updater mempool.ChunkRequestHistoryUpdaterFunc) (uint64, time.Time, time.Duration, bool) { 179 var lastAttempt time.Time 180 var retryAfter time.Duration 181 var attempts uint64 182 183 err := cs.Backend.Run(func(backdata mempool.BackData) error { 184 entity, exists := backdata.ByID(chunkID) 185 if !exists { 186 return fmt.Errorf("not exist") 187 } 188 status := toChunkRequestStatus(entity) 189 190 var ok bool 191 attempts, retryAfter, ok = updater(status.Attempt, status.RetryAfter) 192 if !ok { 193 return fmt.Errorf("updater failed") 194 } 195 lastAttempt = time.Now() 196 197 // updates underlying request 198 status.LastAttempt = lastAttempt 199 status.RetryAfter = retryAfter 200 status.Attempt = attempts 201 202 return nil 203 }) 204 205 return attempts, lastAttempt, retryAfter, err == nil 206 } 207 208 // Size returns total number of chunk requests in the memory pool. 209 func (cs ChunkRequests) Size() uint { 210 return cs.Backend.Size() 211 } 212 213 // chunkRequestStatus is an internal data type for ChunkRequests mempool. It acts as a wrapper for ChunkDataRequests, maintaining 214 // some auxiliary attributes that are internal to ChunkRequests. 215 type chunkRequestStatus struct { 216 Locators map[flow.Identifier]*chunks.Locator // keeps locators by their chunk id. 217 RequestInfo verification.ChunkDataPackRequestInfo 218 LastAttempt time.Time // timestamp of last request dispatched for this chunk id. 219 RetryAfter time.Duration // interval until request should be retried. 220 Attempt uint64 // number of times this chunk request has been dispatched in the network. 221 } 222 223 func (c chunkRequestStatus) ID() flow.Identifier { 224 return c.RequestInfo.ChunkID 225 } 226 227 func (c chunkRequestStatus) Checksum() flow.Identifier { 228 return c.RequestInfo.ChunkID 229 }