github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/beacon/light/sync/update_sync.go (about) 1 // Copyright 2023 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package sync 18 19 import ( 20 "sort" 21 22 "github.com/ethereum/go-ethereum/beacon/light" 23 "github.com/ethereum/go-ethereum/beacon/light/request" 24 "github.com/ethereum/go-ethereum/beacon/params" 25 "github.com/ethereum/go-ethereum/beacon/types" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/log" 28 ) 29 30 const maxUpdateRequest = 8 // maximum number of updates requested in a single request 31 32 type committeeChain interface { 33 CheckpointInit(bootstrap types.BootstrapData) error 34 InsertUpdate(update *types.LightClientUpdate, nextCommittee *types.SerializedSyncCommittee) error 35 NextSyncPeriod() (uint64, bool) 36 } 37 38 // CheckpointInit implements request.Module; it fetches the light client bootstrap 39 // data belonging to the given checkpoint hash and initializes the committee chain 40 // if successful. 41 type CheckpointInit struct { 42 chain committeeChain 43 checkpointHash common.Hash 44 locked request.ServerAndID 45 initialized bool 46 // per-server state is used to track the state of requesting checkpoint header 47 // info. Part of this info (canonical and finalized state) is not validated 48 // and therefore it is requested from each server separately after it has 49 // reported a missing checkpoint (which is also not validated info). 50 serverState map[request.Server]serverState 51 // the following fields are used to determine whether the checkpoint is on 52 // epoch boundary. This information is validated and therefore stored globally. 53 parentHash common.Hash 54 hasEpochInfo, epochBoundary bool 55 cpSlot, parentSlot uint64 56 } 57 58 const ( 59 ssDefault = iota // no action yet or checkpoint requested 60 ssNeedHeader // checkpoint req failed, need cp header 61 ssHeaderRequested // cp header requested 62 ssNeedParent // cp header slot %32 != 0, need parent to check epoch boundary 63 ssParentRequested // cp parent header requested 64 ssPrintStatus // has all necessary info, print log message if init still not successful 65 ssDone // log message printed, no more action required 66 ) 67 68 type serverState struct { 69 state int 70 hasHeader, canonical, finalized bool // stored per server because not validated 71 } 72 73 // NewCheckpointInit creates a new CheckpointInit. 74 func NewCheckpointInit(chain committeeChain, checkpointHash common.Hash) *CheckpointInit { 75 return &CheckpointInit{ 76 chain: chain, 77 checkpointHash: checkpointHash, 78 serverState: make(map[request.Server]serverState), 79 } 80 } 81 82 // Process implements request.Module. 83 func (s *CheckpointInit) Process(requester request.Requester, events []request.Event) { 84 if s.initialized { 85 return 86 } 87 88 for _, event := range events { 89 switch event.Type { 90 case request.EvResponse, request.EvFail, request.EvTimeout: 91 sid, req, resp := event.RequestInfo() 92 if s.locked == sid { 93 s.locked = request.ServerAndID{} 94 } 95 if event.Type == request.EvTimeout { 96 continue 97 } 98 switch s.serverState[sid.Server].state { 99 case ssDefault: 100 if resp != nil { 101 if checkpoint := resp.(*types.BootstrapData); checkpoint.Header.Hash() == common.Hash(req.(ReqCheckpointData)) { 102 s.chain.CheckpointInit(*checkpoint) 103 s.initialized = true 104 return 105 } 106 requester.Fail(event.Server, "invalid checkpoint data") 107 } 108 s.serverState[sid.Server] = serverState{state: ssNeedHeader} 109 case ssHeaderRequested: 110 if resp == nil { 111 s.serverState[sid.Server] = serverState{state: ssPrintStatus} 112 continue 113 } 114 newState := serverState{ 115 hasHeader: true, 116 canonical: resp.(RespHeader).Canonical, 117 finalized: resp.(RespHeader).Finalized, 118 } 119 s.cpSlot, s.parentHash = resp.(RespHeader).Header.Slot, resp.(RespHeader).Header.ParentRoot 120 if s.cpSlot%params.EpochLength == 0 { 121 s.hasEpochInfo, s.epochBoundary = true, true 122 } 123 if s.hasEpochInfo { 124 newState.state = ssPrintStatus 125 } else { 126 newState.state = ssNeedParent 127 } 128 s.serverState[sid.Server] = newState 129 case ssParentRequested: 130 s.parentSlot = resp.(RespHeader).Header.Slot 131 s.hasEpochInfo, s.epochBoundary = true, s.cpSlot/params.EpochLength > s.parentSlot/params.EpochLength 132 newState := s.serverState[sid.Server] 133 newState.state = ssPrintStatus 134 s.serverState[sid.Server] = newState 135 } 136 137 case request.EvUnregistered: 138 delete(s.serverState, event.Server) 139 } 140 } 141 142 // start a request if possible 143 for _, server := range requester.CanSendTo() { 144 switch s.serverState[server].state { 145 case ssDefault: 146 if s.locked == (request.ServerAndID{}) { 147 id := requester.Send(server, ReqCheckpointData(s.checkpointHash)) 148 s.locked = request.ServerAndID{Server: server, ID: id} 149 } 150 case ssNeedHeader: 151 requester.Send(server, ReqHeader(s.checkpointHash)) 152 newState := s.serverState[server] 153 newState.state = ssHeaderRequested 154 s.serverState[server] = newState 155 case ssNeedParent: 156 requester.Send(server, ReqHeader(s.parentHash)) 157 newState := s.serverState[server] 158 newState.state = ssParentRequested 159 s.serverState[server] = newState 160 } 161 } 162 163 // print log message if necessary 164 for server, state := range s.serverState { 165 if state.state != ssPrintStatus { 166 continue 167 } 168 switch { 169 case !state.hasHeader: 170 log.Error("blsync: checkpoint block is not available, reported as unknown", "server", server.Name()) 171 case !state.canonical: 172 log.Error("blsync: checkpoint block is not available, reported as non-canonical", "server", server.Name()) 173 case !s.hasEpochInfo: 174 // should be available if hasHeader is true and state is ssPrintStatus 175 panic("checkpoint epoch info not available when printing retrieval status") 176 case !s.epochBoundary: 177 log.Error("blsync: checkpoint block is not first of epoch", "slot", s.cpSlot, "parent", s.parentSlot, "server", server.Name()) 178 case !state.finalized: 179 log.Error("blsync: checkpoint block is reported as non-finalized", "server", server.Name()) 180 default: 181 log.Error("blsync: checkpoint not available, but reported as finalized; specified checkpoint hash might be too old", "server", server.Name()) 182 } 183 s.serverState[server] = serverState{state: ssDone} 184 } 185 } 186 187 // ForwardUpdateSync implements request.Module; it fetches updates between the 188 // committee chain head and each server's announced head. Updates are fetched 189 // in batches and multiple batches can also be requested in parallel. 190 // Out of order responses are also handled; if a batch of updates cannot be added 191 // to the chain immediately because of a gap then the future updates are 192 // remembered until they can be processed. 193 type ForwardUpdateSync struct { 194 chain committeeChain 195 rangeLock rangeLock 196 lockedIDs map[request.ServerAndID]struct{} 197 processQueue []updateResponse 198 nextSyncPeriod map[request.Server]uint64 199 } 200 201 // NewForwardUpdateSync creates a new ForwardUpdateSync. 202 func NewForwardUpdateSync(chain committeeChain) *ForwardUpdateSync { 203 return &ForwardUpdateSync{ 204 chain: chain, 205 rangeLock: make(rangeLock), 206 lockedIDs: make(map[request.ServerAndID]struct{}), 207 nextSyncPeriod: make(map[request.Server]uint64), 208 } 209 } 210 211 // rangeLock allows locking sections of an integer space, preventing the syncing 212 // mechanism from making requests again for sections where a not timed out request 213 // is already pending or where already fetched and unprocessed data is available. 214 type rangeLock map[uint64]int 215 216 // lock locks or unlocks the given section, depending on the sign of the add parameter. 217 func (r rangeLock) lock(first, count uint64, add int) { 218 for i := first; i < first+count; i++ { 219 if v := r[i] + add; v > 0 { 220 r[i] = v 221 } else { 222 delete(r, i) 223 } 224 } 225 } 226 227 // firstUnlocked returns the first unlocked section starting at or after start 228 // and not longer than maxCount. 229 func (r rangeLock) firstUnlocked(start, maxCount uint64) (first, count uint64) { 230 first = start 231 for { 232 if _, ok := r[first]; !ok { 233 break 234 } 235 first++ 236 } 237 for { 238 count++ 239 if count == maxCount { 240 break 241 } 242 if _, ok := r[first+count]; ok { 243 break 244 } 245 } 246 return 247 } 248 249 // lockRange locks the range belonging to the given update request, unless the 250 // same request has already been locked 251 func (s *ForwardUpdateSync) lockRange(sid request.ServerAndID, req ReqUpdates) { 252 if _, ok := s.lockedIDs[sid]; ok { 253 return 254 } 255 s.lockedIDs[sid] = struct{}{} 256 s.rangeLock.lock(req.FirstPeriod, req.Count, 1) 257 } 258 259 // unlockRange unlocks the range belonging to the given update request, unless 260 // same request has already been unlocked 261 func (s *ForwardUpdateSync) unlockRange(sid request.ServerAndID, req ReqUpdates) { 262 if _, ok := s.lockedIDs[sid]; !ok { 263 return 264 } 265 delete(s.lockedIDs, sid) 266 s.rangeLock.lock(req.FirstPeriod, req.Count, -1) 267 } 268 269 // verifyRange returns true if the number of updates and the individual update 270 // periods in the response match the requested section. 271 func (s *ForwardUpdateSync) verifyRange(request ReqUpdates, response RespUpdates) bool { 272 if uint64(len(response.Updates)) != request.Count || uint64(len(response.Committees)) != request.Count { 273 return false 274 } 275 for i, update := range response.Updates { 276 if update.AttestedHeader.Header.SyncPeriod() != request.FirstPeriod+uint64(i) { 277 return false 278 } 279 } 280 return true 281 } 282 283 // updateResponse is a response that has passed initial verification and has been 284 // queued for processing. Note that an update response cannot be processed until 285 // the previous updates have also been added to the chain. 286 type updateResponse struct { 287 sid request.ServerAndID 288 request ReqUpdates 289 response RespUpdates 290 } 291 292 // updateResponseList implements sort.Sort and sorts update request/response events by FirstPeriod. 293 type updateResponseList []updateResponse 294 295 func (u updateResponseList) Len() int { return len(u) } 296 func (u updateResponseList) Swap(i, j int) { u[i], u[j] = u[j], u[i] } 297 func (u updateResponseList) Less(i, j int) bool { 298 return u[i].request.FirstPeriod < u[j].request.FirstPeriod 299 } 300 301 // Process implements request.Module. 302 func (s *ForwardUpdateSync) Process(requester request.Requester, events []request.Event) { 303 for _, event := range events { 304 switch event.Type { 305 case request.EvResponse, request.EvFail, request.EvTimeout: 306 sid, rq, rs := event.RequestInfo() 307 req := rq.(ReqUpdates) 308 var queued bool 309 if event.Type == request.EvResponse { 310 resp := rs.(RespUpdates) 311 if s.verifyRange(req, resp) { 312 // there is a response with a valid format; put it in the process queue 313 s.processQueue = append(s.processQueue, updateResponse{sid: sid, request: req, response: resp}) 314 s.lockRange(sid, req) 315 queued = true 316 } else { 317 requester.Fail(event.Server, "invalid update range") 318 } 319 } 320 if !queued { 321 s.unlockRange(sid, req) 322 } 323 case EvNewOptimisticUpdate: 324 update := event.Data.(types.OptimisticUpdate) 325 s.nextSyncPeriod[event.Server] = types.SyncPeriod(update.SignatureSlot + 256) 326 case request.EvUnregistered: 327 delete(s.nextSyncPeriod, event.Server) 328 } 329 } 330 331 // try processing ordered list of available responses 332 sort.Sort(updateResponseList(s.processQueue)) 333 for s.processQueue != nil { 334 u := s.processQueue[0] 335 if !s.processResponse(requester, u) { 336 break 337 } 338 s.unlockRange(u.sid, u.request) 339 s.processQueue = s.processQueue[1:] 340 if len(s.processQueue) == 0 { 341 s.processQueue = nil 342 } 343 } 344 345 // start new requests if possible 346 startPeriod, chainInit := s.chain.NextSyncPeriod() 347 if !chainInit { 348 return 349 } 350 for { 351 firstPeriod, maxCount := s.rangeLock.firstUnlocked(startPeriod, maxUpdateRequest) 352 var ( 353 sendTo request.Server 354 bestCount uint64 355 ) 356 for _, server := range requester.CanSendTo() { 357 nextPeriod := s.nextSyncPeriod[server] 358 if nextPeriod <= firstPeriod { 359 continue 360 } 361 count := maxCount 362 if nextPeriod < firstPeriod+maxCount { 363 count = nextPeriod - firstPeriod 364 } 365 if count > bestCount { 366 sendTo, bestCount = server, count 367 } 368 } 369 if sendTo == nil { 370 return 371 } 372 req := ReqUpdates{FirstPeriod: firstPeriod, Count: bestCount} 373 id := requester.Send(sendTo, req) 374 s.lockRange(request.ServerAndID{Server: sendTo, ID: id}, req) 375 } 376 } 377 378 // processResponse adds the fetched updates and committees to the committee chain. 379 // Returns true in case of full or partial success. 380 func (s *ForwardUpdateSync) processResponse(requester request.Requester, u updateResponse) (success bool) { 381 for i, update := range u.response.Updates { 382 if err := s.chain.InsertUpdate(update, u.response.Committees[i]); err != nil { 383 if err == light.ErrInvalidPeriod { 384 // there is a gap in the update periods; stop processing without 385 // failing and try again next time 386 return 387 } 388 if err == light.ErrInvalidUpdate || err == light.ErrWrongCommitteeRoot || err == light.ErrCannotReorg { 389 requester.Fail(u.sid.Server, "invalid update received") 390 } else { 391 log.Error("Unexpected InsertUpdate error", "error", err) 392 } 393 return 394 } 395 success = true 396 } 397 return 398 }