github.com/ethereum/go-ethereum@v1.16.1/eth/downloader/beaconsync.go (about) 1 // Copyright 2022 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 downloader 18 19 import ( 20 "fmt" 21 "sync" 22 "time" 23 24 "github.com/ethereum/go-ethereum/common" 25 "github.com/ethereum/go-ethereum/core/rawdb" 26 "github.com/ethereum/go-ethereum/core/types" 27 "github.com/ethereum/go-ethereum/eth/ethconfig" 28 "github.com/ethereum/go-ethereum/log" 29 ) 30 31 // beaconBackfiller is the chain and state backfilling that can be commenced once 32 // the skeleton syncer has successfully reverse downloaded all the headers up to 33 // the genesis block or an existing header in the database. Its operation is fully 34 // directed by the skeleton sync's head/tail events. 35 type beaconBackfiller struct { 36 downloader *Downloader // Downloader to direct via this callback implementation 37 syncMode SyncMode // Sync mode to use for backfilling the skeleton chains 38 success func() // Callback to run on successful sync cycle completion 39 filling bool // Flag whether the downloader is backfilling or not 40 filled *types.Header // Last header filled by the last terminated sync loop 41 started chan struct{} // Notification channel whether the downloader inited 42 lock sync.Mutex // Mutex protecting the sync lock 43 } 44 45 // newBeaconBackfiller is a helper method to create the backfiller. 46 func newBeaconBackfiller(dl *Downloader, success func()) backfiller { 47 return &beaconBackfiller{ 48 downloader: dl, 49 success: success, 50 } 51 } 52 53 // suspend cancels any background downloader threads and returns the last header 54 // that has been successfully backfilled (potentially in a previous run), or the 55 // genesis. 56 func (b *beaconBackfiller) suspend() *types.Header { 57 // If no filling is running, don't waste cycles 58 b.lock.Lock() 59 filling := b.filling 60 filled := b.filled 61 started := b.started 62 b.lock.Unlock() 63 64 if !filling { 65 return filled // Return the filled header on the previous sync completion 66 } 67 // A previous filling should be running, though it may happen that it hasn't 68 // yet started (being done on a new goroutine). Many concurrent beacon head 69 // announcements can lead to sync start/stop thrashing. In that case we need 70 // to wait for initialization before we can safely cancel it. It is safe to 71 // read this channel multiple times, it gets closed on startup. 72 <-started 73 74 // Now that we're sure the downloader successfully started up, we can cancel 75 // it safely without running the risk of data races. 76 b.downloader.Cancel() 77 78 // Sync cycle was just terminated, retrieve and return the last filled header. 79 // Can't use `filled` as that contains a stale value from before cancellation. 80 return b.downloader.blockchain.CurrentSnapBlock() 81 } 82 83 // resume starts the downloader threads for backfilling state and chain data. 84 func (b *beaconBackfiller) resume() { 85 b.lock.Lock() 86 if b.filling { 87 // If a previous filling cycle is still running, just ignore this start 88 // request. // TODO(karalabe): We should make this channel driven 89 b.lock.Unlock() 90 return 91 } 92 b.filling = true 93 b.filled = nil 94 b.started = make(chan struct{}) 95 mode := b.syncMode 96 b.lock.Unlock() 97 98 // Start the backfilling on its own thread since the downloader does not have 99 // its own lifecycle runloop. 100 go func() { 101 // Set the backfiller to non-filling when download completes 102 defer func() { 103 b.lock.Lock() 104 b.filling = false 105 b.filled = b.downloader.blockchain.CurrentSnapBlock() 106 b.lock.Unlock() 107 }() 108 // If the downloader fails, report an error as in beacon chain mode there 109 // should be no errors as long as the chain we're syncing to is valid. 110 if err := b.downloader.synchronise(mode, b.started); err != nil { 111 log.Error("Beacon backfilling failed", "err", err) 112 return 113 } 114 // Synchronization succeeded. Since this happens async, notify the outer 115 // context to disable snap syncing and enable transaction propagation. 116 if b.success != nil { 117 b.success() 118 } 119 }() 120 } 121 122 // setMode updates the sync mode from the current one to the requested one. If 123 // there's an active sync in progress, it will be cancelled and restarted. 124 func (b *beaconBackfiller) setMode(mode SyncMode) { 125 // Update the old sync mode and track if it was changed 126 b.lock.Lock() 127 oldMode := b.syncMode 128 updated := oldMode != mode 129 filling := b.filling 130 b.syncMode = mode 131 b.lock.Unlock() 132 133 // If the sync mode was changed mid-sync, restart. This should never ever 134 // really happen, we just handle it to detect programming errors. 135 if !updated || !filling { 136 return 137 } 138 log.Error("Downloader sync mode changed mid-run", "old", oldMode.String(), "new", mode.String()) 139 b.suspend() 140 b.resume() 141 } 142 143 // SetBadBlockCallback sets the callback to run when a bad block is hit by the 144 // block processor. This method is not thread safe and should be set only once 145 // on startup before system events are fired. 146 func (d *Downloader) SetBadBlockCallback(onBadBlock badBlockFn) { 147 d.badBlock = onBadBlock 148 } 149 150 // BeaconSync is the post-merge version of the chain synchronization, where the 151 // chain is not downloaded from genesis onward, rather from trusted head announces 152 // backwards. 153 // 154 // Internally backfilling and state sync is done the same way, but the header 155 // retrieval and scheduling is replaced. 156 func (d *Downloader) BeaconSync(mode SyncMode, head *types.Header, final *types.Header) error { 157 return d.beaconSync(mode, head, final, true) 158 } 159 160 // BeaconExtend is an optimistic version of BeaconSync, where an attempt is made 161 // to extend the current beacon chain with a new header, but in case of a mismatch, 162 // the old sync will not be terminated and reorged, rather the new head is dropped. 163 // 164 // This is useful if a beacon client is feeding us large chunks of payloads to run, 165 // but is not setting the head after each. 166 func (d *Downloader) BeaconExtend(mode SyncMode, head *types.Header) error { 167 return d.beaconSync(mode, head, nil, false) 168 } 169 170 // beaconSync is the post-merge version of the chain synchronization, where the 171 // chain is not downloaded from genesis onward, rather from trusted head announces 172 // backwards. 173 // 174 // Internally backfilling and state sync is done the same way, but the header 175 // retrieval and scheduling is replaced. 176 func (d *Downloader) beaconSync(mode SyncMode, head *types.Header, final *types.Header, force bool) error { 177 // When the downloader starts a sync cycle, it needs to be aware of the sync 178 // mode to use (full, snap). To keep the skeleton chain oblivious, inject the 179 // mode into the backfiller directly. 180 // 181 // Super crazy dangerous type cast. Should be fine (TM), we're only using a 182 // different backfiller implementation for skeleton tests. 183 d.skeleton.filler.(*beaconBackfiller).setMode(mode) 184 185 // Signal the skeleton sync to switch to a new head, however it wants 186 if err := d.skeleton.Sync(head, final, force); err != nil { 187 return err 188 } 189 return nil 190 } 191 192 // findBeaconAncestor tries to locate the common ancestor link of the local chain 193 // and the beacon chain just requested. In the general case when our node was in 194 // sync and on the correct chain, checking the top N links should already get us 195 // a match. In the rare scenario when we ended up on a long reorganisation (i.e. 196 // none of the head links match), we do a binary search to find the ancestor. 197 func (d *Downloader) findBeaconAncestor() (uint64, error) { 198 // Figure out the current local head position 199 var chainHead *types.Header 200 201 switch d.getMode() { 202 case ethconfig.FullSync: 203 chainHead = d.blockchain.CurrentBlock() 204 case ethconfig.SnapSync: 205 chainHead = d.blockchain.CurrentSnapBlock() 206 default: 207 panic("unknown sync mode") 208 } 209 number := chainHead.Number.Uint64() 210 211 // Retrieve the skeleton bounds and ensure they are linked to the local chain 212 beaconHead, beaconTail, _, err := d.skeleton.Bounds() 213 if err != nil { 214 // This is a programming error. The chain backfiller was called with an 215 // invalid beacon sync state. Ideally we would panic here, but erroring 216 // gives us at least a remote chance to recover. It's still a big fault! 217 log.Error("Failed to retrieve beacon bounds", "err", err) 218 return 0, err 219 } 220 var linked bool 221 switch d.getMode() { 222 case ethconfig.FullSync: 223 linked = d.blockchain.HasBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1) 224 case ethconfig.SnapSync: 225 linked = d.blockchain.HasFastBlock(beaconTail.ParentHash, beaconTail.Number.Uint64()-1) 226 default: 227 panic("unknown sync mode") 228 } 229 if !linked { 230 // This is a programming error. The chain backfiller was called with a 231 // tail that's not linked to the local chain. Whilst this should never 232 // happen, there might be some weirdnesses if beacon sync backfilling 233 // races with the user (or beacon client) calling setHead. Whilst panic 234 // would be the ideal thing to do, it is safer long term to attempt a 235 // recovery and fix any noticed issue after the fact. 236 log.Error("Beacon sync linkup unavailable", "number", beaconTail.Number.Uint64()-1, "hash", beaconTail.ParentHash) 237 return 0, fmt.Errorf("beacon linkup unavailable locally: %d [%x]", beaconTail.Number.Uint64()-1, beaconTail.ParentHash) 238 } 239 // Binary search to find the ancestor 240 start, end := beaconTail.Number.Uint64()-1, number 241 if number := beaconHead.Number.Uint64(); end > number { 242 // This shouldn't really happen in a healthy network, but if the consensus 243 // clients feeds us a shorter chain as the canonical, we should not attempt 244 // to access non-existent skeleton items. 245 log.Warn("Beacon head lower than local chain", "beacon", number, "local", end) 246 end = number 247 } 248 for start+1 < end { 249 // Split our chain interval in two, and request the hash to cross check 250 check := (start + end) / 2 251 252 h := d.skeleton.Header(check) 253 n := h.Number.Uint64() 254 255 var known bool 256 switch d.getMode() { 257 case ethconfig.FullSync: 258 known = d.blockchain.HasBlock(h.Hash(), n) 259 case ethconfig.SnapSync: 260 known = d.blockchain.HasFastBlock(h.Hash(), n) 261 default: 262 panic("unknown sync mode") 263 } 264 if !known { 265 end = check 266 continue 267 } 268 start = check 269 } 270 return start, nil 271 } 272 273 // fetchHeaders feeds skeleton headers to the downloader queue for scheduling 274 // until sync errors or is finished. 275 func (d *Downloader) fetchHeaders(from uint64) error { 276 head, tail, _, err := d.skeleton.Bounds() 277 if err != nil { 278 return err 279 } 280 // A part of headers are not in the skeleton space, try to resolve 281 // them from the local chain. Note the range should be very short 282 // and it should only happen when there are less than 64 post-merge 283 // blocks in the network. 284 var localHeaders []*types.Header 285 if from < tail.Number.Uint64() { 286 count := tail.Number.Uint64() - from 287 if count > uint64(fsMinFullBlocks) { 288 return fmt.Errorf("invalid origin (%d) of beacon sync (%d)", from, tail.Number) 289 } 290 localHeaders = d.readHeaderRange(tail, int(count)) 291 log.Warn("Retrieved beacon headers from local", "from", from, "count", count) 292 } 293 fsHeaderContCheckTimer := time.NewTimer(fsHeaderContCheck) 294 defer fsHeaderContCheckTimer.Stop() 295 296 // Verify the header at configured chain cutoff, ensuring it's matched with 297 // the configured hash. Skip the check if the configured cutoff is even higher 298 // than the sync target, which is definitely not a common case. 299 if d.chainCutoffNumber != 0 && d.chainCutoffNumber >= from && d.chainCutoffNumber <= head.Number.Uint64() { 300 h := d.skeleton.Header(d.chainCutoffNumber) 301 if h == nil { 302 if d.chainCutoffNumber < tail.Number.Uint64() { 303 dist := tail.Number.Uint64() - d.chainCutoffNumber 304 if len(localHeaders) >= int(dist) { 305 h = localHeaders[dist-1] 306 } 307 } 308 } 309 if h == nil { 310 return fmt.Errorf("header at chain cutoff is not available, cutoff: %d", d.chainCutoffNumber) 311 } 312 if h.Hash() != d.chainCutoffHash { 313 return fmt.Errorf("header at chain cutoff mismatched, want: %v, got: %v", d.chainCutoffHash, h.Hash()) 314 } 315 } 316 317 for { 318 // Some beacon headers might have appeared since the last cycle, make 319 // sure we're always syncing to all available ones 320 head, _, _, err = d.skeleton.Bounds() 321 if err != nil { 322 return err 323 } 324 // If the pivot became stale (older than 2*64-8 (bit of wiggle room)), 325 // move it ahead to HEAD-64 326 d.pivotLock.Lock() 327 if d.pivotHeader != nil { 328 if head.Number.Uint64() > d.pivotHeader.Number.Uint64()+2*uint64(fsMinFullBlocks)-8 { 329 // Retrieve the next pivot header, either from skeleton chain 330 // or the filled chain 331 number := head.Number.Uint64() - uint64(fsMinFullBlocks) 332 333 log.Warn("Pivot seemingly stale, moving", "old", d.pivotHeader.Number, "new", number) 334 if d.pivotHeader = d.skeleton.Header(number); d.pivotHeader == nil { 335 if number < tail.Number.Uint64() { 336 dist := tail.Number.Uint64() - number 337 if len(localHeaders) >= int(dist) { 338 d.pivotHeader = localHeaders[dist-1] 339 log.Warn("Retrieved pivot header from local", "number", d.pivotHeader.Number, "hash", d.pivotHeader.Hash(), "latest", head.Number, "oldest", tail.Number) 340 } 341 } 342 } 343 // Print an error log and return directly in case the pivot header 344 // is still not found. It means the skeleton chain is not linked 345 // correctly with local chain. 346 if d.pivotHeader == nil { 347 log.Error("Pivot header is not found", "number", number) 348 d.pivotLock.Unlock() 349 return errNoPivotHeader 350 } 351 // Write out the pivot into the database so a rollback beyond 352 // it will reenable snap sync and update the state root that 353 // the state syncer will be downloading 354 rawdb.WriteLastPivotNumber(d.stateDB, d.pivotHeader.Number.Uint64()) 355 } 356 } 357 d.pivotLock.Unlock() 358 359 // Retrieve a batch of headers and feed it to the header processor 360 var ( 361 headers = make([]*types.Header, 0, maxHeadersProcess) 362 hashes = make([]common.Hash, 0, maxHeadersProcess) 363 ) 364 for i := 0; i < maxHeadersProcess && from <= head.Number.Uint64(); i++ { 365 header := d.skeleton.Header(from) 366 367 // The header is not found in skeleton space, try to find it in local chain. 368 if header == nil && from < tail.Number.Uint64() { 369 dist := tail.Number.Uint64() - from 370 if len(localHeaders) >= int(dist) { 371 header = localHeaders[dist-1] 372 } 373 } 374 // The header is still missing, the beacon sync is corrupted and bail out 375 // the error here. 376 if header == nil { 377 return fmt.Errorf("missing beacon header %d", from) 378 } 379 headers = append(headers, header) 380 hashes = append(hashes, headers[i].Hash()) 381 from++ 382 } 383 if len(headers) > 0 { 384 log.Trace("Scheduling new beacon headers", "count", len(headers), "from", from-uint64(len(headers))) 385 select { 386 case d.headerProcCh <- &headerTask{ 387 headers: headers, 388 hashes: hashes, 389 }: 390 case <-d.cancelCh: 391 return errCanceled 392 } 393 } 394 // If we still have headers to import, loop and keep pushing them 395 if from <= head.Number.Uint64() { 396 continue 397 } 398 // If the pivot block is committed, signal header sync termination 399 if d.committed.Load() { 400 select { 401 case d.headerProcCh <- nil: 402 return nil 403 case <-d.cancelCh: 404 return errCanceled 405 } 406 } 407 // State sync still going, wait a bit for new headers and retry 408 log.Trace("Pivot not yet committed, waiting...") 409 fsHeaderContCheckTimer.Reset(fsHeaderContCheck) 410 select { 411 case <-fsHeaderContCheckTimer.C: 412 case <-d.cancelCh: 413 return errCanceled 414 } 415 } 416 }