github.com/cryptogateway/go-paymex@v0.0.0-20210204174735-96277fb1e602/les/fetcher.go (about) 1 // Copyright 2016 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 les 18 19 import ( 20 "math/big" 21 "math/rand" 22 "sync" 23 "time" 24 25 "github.com/cryptogateway/go-paymex/common" 26 "github.com/cryptogateway/go-paymex/consensus" 27 "github.com/cryptogateway/go-paymex/core" 28 "github.com/cryptogateway/go-paymex/core/rawdb" 29 "github.com/cryptogateway/go-paymex/core/types" 30 "github.com/cryptogateway/go-paymex/eth/fetcher" 31 "github.com/cryptogateway/go-paymex/ethdb" 32 "github.com/cryptogateway/go-paymex/light" 33 "github.com/cryptogateway/go-paymex/log" 34 "github.com/cryptogateway/go-paymex/p2p/enode" 35 ) 36 37 const ( 38 blockDelayTimeout = 10 * time.Second // Timeout for retrieving the headers from the peer 39 gatherSlack = 100 * time.Millisecond // Interval used to collate almost-expired requests 40 cachedAnnosThreshold = 64 // The maximum queued announcements 41 ) 42 43 // announce represents an new block announcement from the les server. 44 type announce struct { 45 data *announceData 46 trust bool 47 peerid enode.ID 48 } 49 50 // request represents a record when the header request is sent. 51 type request struct { 52 reqid uint64 53 peerid enode.ID 54 sendAt time.Time 55 hash common.Hash 56 } 57 58 // response represents a response packet from network as well as a channel 59 // to return all un-requested data. 60 type response struct { 61 reqid uint64 62 headers []*types.Header 63 peerid enode.ID 64 remain chan []*types.Header 65 } 66 67 // fetcherPeer holds the fetcher-specific information for each active peer 68 type fetcherPeer struct { 69 latest *announceData // The latest announcement sent from the peer 70 71 // These following two fields can track the latest announces 72 // from the peer with limited size for caching. We hold the 73 // assumption that all enqueued announces are td-monotonic. 74 announces map[common.Hash]*announce // Announcement map 75 announcesList []common.Hash // FIFO announces list 76 } 77 78 // addAnno enqueues an new trusted announcement. If the queued announces overflow, 79 // evict from the oldest. 80 func (fp *fetcherPeer) addAnno(anno *announce) { 81 // Short circuit if the anno already exists. In normal case it should 82 // never happen since only monotonic anno is accepted. But the adversary 83 // may feed us fake announces with higher td but same hash. In this case, 84 // ignore the anno anyway. 85 hash := anno.data.Hash 86 if _, exist := fp.announces[hash]; exist { 87 return 88 } 89 fp.announces[hash] = anno 90 fp.announcesList = append(fp.announcesList, hash) 91 92 // Evict oldest if the announces are oversized. 93 if len(fp.announcesList)-cachedAnnosThreshold > 0 { 94 for i := 0; i < len(fp.announcesList)-cachedAnnosThreshold; i++ { 95 delete(fp.announces, fp.announcesList[i]) 96 } 97 copy(fp.announcesList, fp.announcesList[len(fp.announcesList)-cachedAnnosThreshold:]) 98 fp.announcesList = fp.announcesList[:cachedAnnosThreshold] 99 } 100 } 101 102 // forwardAnno removes all announces from the map with a number lower than 103 // the provided threshold. 104 func (fp *fetcherPeer) forwardAnno(td *big.Int) []*announce { 105 var ( 106 cutset int 107 evicted []*announce 108 ) 109 for ; cutset < len(fp.announcesList); cutset++ { 110 anno := fp.announces[fp.announcesList[cutset]] 111 if anno == nil { 112 continue // In theory it should never ever happen 113 } 114 if anno.data.Td.Cmp(td) > 0 { 115 break 116 } 117 evicted = append(evicted, anno) 118 delete(fp.announces, anno.data.Hash) 119 } 120 if cutset > 0 { 121 copy(fp.announcesList, fp.announcesList[cutset:]) 122 fp.announcesList = fp.announcesList[:len(fp.announcesList)-cutset] 123 } 124 return evicted 125 } 126 127 // lightFetcher implements retrieval of newly announced headers. It reuses 128 // the eth.BlockFetcher as the underlying fetcher but adding more additional 129 // rules: e.g. evict "timeout" peers. 130 type lightFetcher struct { 131 // Various handlers 132 ulc *ulc 133 chaindb ethdb.Database 134 reqDist *requestDistributor 135 peerset *serverPeerSet // The global peerset of light client which shared by all components 136 chain *light.LightChain // The local light chain which maintains the canonical header chain. 137 fetcher *fetcher.BlockFetcher // The underlying fetcher which takes care block header retrieval. 138 139 // Peerset maintained by fetcher 140 plock sync.RWMutex 141 peers map[enode.ID]*fetcherPeer 142 143 // Various channels 144 announceCh chan *announce 145 requestCh chan *request 146 deliverCh chan *response 147 syncDone chan *types.Header 148 149 closeCh chan struct{} 150 wg sync.WaitGroup 151 152 // Callback 153 synchronise func(peer *serverPeer) 154 155 // Test fields or hooks 156 noAnnounce bool 157 newHeadHook func(*types.Header) 158 newAnnounce func(*serverPeer, *announceData) 159 } 160 161 // newLightFetcher creates a light fetcher instance. 162 func newLightFetcher(chain *light.LightChain, engine consensus.Engine, peers *serverPeerSet, ulc *ulc, chaindb ethdb.Database, reqDist *requestDistributor, syncFn func(p *serverPeer)) *lightFetcher { 163 // Construct the fetcher by offering all necessary APIs 164 validator := func(header *types.Header) error { 165 // Disable seal verification explicitly if we are running in ulc mode. 166 return engine.VerifyHeader(chain, header, ulc == nil) 167 } 168 heighter := func() uint64 { return chain.CurrentHeader().Number.Uint64() } 169 dropper := func(id string) { peers.unregister(id) } 170 inserter := func(headers []*types.Header) (int, error) { 171 // Disable PoW checking explicitly if we are running in ulc mode. 172 checkFreq := 1 173 if ulc != nil { 174 checkFreq = 0 175 } 176 return chain.InsertHeaderChain(headers, checkFreq) 177 } 178 f := &lightFetcher{ 179 ulc: ulc, 180 peerset: peers, 181 chaindb: chaindb, 182 chain: chain, 183 reqDist: reqDist, 184 fetcher: fetcher.NewBlockFetcher(true, chain.GetHeaderByHash, nil, validator, nil, heighter, inserter, nil, dropper), 185 peers: make(map[enode.ID]*fetcherPeer), 186 synchronise: syncFn, 187 announceCh: make(chan *announce), 188 requestCh: make(chan *request), 189 deliverCh: make(chan *response), 190 syncDone: make(chan *types.Header), 191 closeCh: make(chan struct{}), 192 } 193 peers.subscribe(f) 194 return f 195 } 196 197 func (f *lightFetcher) start() { 198 f.wg.Add(1) 199 f.fetcher.Start() 200 go f.mainloop() 201 } 202 203 func (f *lightFetcher) stop() { 204 close(f.closeCh) 205 f.fetcher.Stop() 206 f.wg.Wait() 207 } 208 209 // registerPeer adds an new peer to the fetcher's peer set 210 func (f *lightFetcher) registerPeer(p *serverPeer) { 211 f.plock.Lock() 212 defer f.plock.Unlock() 213 214 f.peers[p.ID()] = &fetcherPeer{announces: make(map[common.Hash]*announce)} 215 } 216 217 // unregisterPeer removes the specified peer from the fetcher's peer set 218 func (f *lightFetcher) unregisterPeer(p *serverPeer) { 219 f.plock.Lock() 220 defer f.plock.Unlock() 221 222 delete(f.peers, p.ID()) 223 } 224 225 // peer returns the peer from the fetcher peerset. 226 func (f *lightFetcher) peer(id enode.ID) *fetcherPeer { 227 f.plock.RLock() 228 defer f.plock.RUnlock() 229 230 return f.peers[id] 231 } 232 233 // forEachPeer iterates the fetcher peerset, abort the iteration if the 234 // callback returns false. 235 func (f *lightFetcher) forEachPeer(check func(id enode.ID, p *fetcherPeer) bool) { 236 f.plock.RLock() 237 defer f.plock.RUnlock() 238 239 for id, peer := range f.peers { 240 if !check(id, peer) { 241 return 242 } 243 } 244 } 245 246 // mainloop is the main event loop of the light fetcher, which is responsible for 247 // - announcement maintenance(ulc) 248 // If we are running in ultra light client mode, then all announcements from 249 // the trusted servers are maintained. If the same announcements from trusted 250 // servers reach the threshold, then the relevant header is requested for retrieval. 251 // 252 // - block header retrieval 253 // Whenever we receive announce with higher td compared with local chain, the 254 // request will be made for header retrieval. 255 // 256 // - re-sync trigger 257 // If the local chain lags too much, then the fetcher will enter "synnchronise" 258 // mode to retrieve missing headers in batch. 259 func (f *lightFetcher) mainloop() { 260 defer f.wg.Done() 261 262 var ( 263 syncInterval = uint64(1) // Interval used to trigger a light resync. 264 syncing bool // Indicator whether the client is syncing 265 266 ulc = f.ulc != nil 267 headCh = make(chan core.ChainHeadEvent, 100) 268 fetching = make(map[uint64]*request) 269 requestTimer = time.NewTimer(0) 270 271 // Local status 272 localHead = f.chain.CurrentHeader() 273 localTd = f.chain.GetTd(localHead.Hash(), localHead.Number.Uint64()) 274 ) 275 sub := f.chain.SubscribeChainHeadEvent(headCh) 276 defer sub.Unsubscribe() 277 278 // reset updates the local status with given header. 279 reset := func(header *types.Header) { 280 localHead = header 281 localTd = f.chain.GetTd(header.Hash(), header.Number.Uint64()) 282 } 283 // trustedHeader returns an indicator whether the header is regarded as 284 // trusted. If we are running in the ulc mode, only when we receive enough 285 // same announcement from trusted server, the header will be trusted. 286 trustedHeader := func(hash common.Hash, number uint64) (bool, []enode.ID) { 287 var ( 288 agreed []enode.ID 289 trusted bool 290 ) 291 f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { 292 if anno := p.announces[hash]; anno != nil && anno.trust && anno.data.Number == number { 293 agreed = append(agreed, id) 294 if 100*len(agreed)/len(f.ulc.keys) >= f.ulc.fraction { 295 trusted = true 296 return false // abort iteration 297 } 298 } 299 return true 300 }) 301 return trusted, agreed 302 } 303 for { 304 select { 305 case anno := <-f.announceCh: 306 peerid, data := anno.peerid, anno.data 307 log.Debug("Received new announce", "peer", peerid, "number", data.Number, "hash", data.Hash, "reorg", data.ReorgDepth) 308 309 peer := f.peer(peerid) 310 if peer == nil { 311 log.Debug("Receive announce from unknown peer", "peer", peerid) 312 continue 313 } 314 // Announced tds should be strictly monotonic, drop the peer if 315 // the announce is out-of-order. 316 if peer.latest != nil && data.Td.Cmp(peer.latest.Td) <= 0 { 317 f.peerset.unregister(peerid.String()) 318 log.Debug("Non-monotonic td", "peer", peerid, "current", data.Td, "previous", peer.latest.Td) 319 continue 320 } 321 peer.latest = data 322 323 // Filter out any stale announce, the local chain is ahead of announce 324 if localTd != nil && data.Td.Cmp(localTd) <= 0 { 325 continue 326 } 327 peer.addAnno(anno) 328 329 // If we are not syncing, try to trigger a single retrieval or re-sync 330 if !ulc && !syncing { 331 // Two scenarios lead to re-sync: 332 // - reorg happens 333 // - local chain lags 334 // We can't retrieve the parent of the announce by single retrieval 335 // in both cases, so resync is necessary. 336 if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 { 337 syncing = true 338 go f.startSync(peerid) 339 log.Debug("Trigger light sync", "peer", peerid, "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) 340 continue 341 } 342 f.fetcher.Notify(peerid.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(peerid), nil) 343 log.Debug("Trigger header retrieval", "peer", peerid, "number", data.Number, "hash", data.Hash) 344 } 345 // Keep collecting announces from trusted server even we are syncing. 346 if ulc && anno.trust { 347 // Notify underlying fetcher to retrieve header or trigger a resync if 348 // we have receive enough announcements from trusted server. 349 trusted, agreed := trustedHeader(data.Hash, data.Number) 350 if trusted && !syncing { 351 if data.Number > localHead.Number.Uint64()+syncInterval || data.ReorgDepth > 0 { 352 syncing = true 353 go f.startSync(peerid) 354 log.Debug("Trigger trusted light sync", "local", localHead.Number, "localhash", localHead.Hash(), "remote", data.Number, "remotehash", data.Hash) 355 continue 356 } 357 p := agreed[rand.Intn(len(agreed))] 358 f.fetcher.Notify(p.String(), data.Hash, data.Number, time.Now(), f.requestHeaderByHash(p), nil) 359 log.Debug("Trigger trusted header retrieval", "number", data.Number, "hash", data.Hash) 360 } 361 } 362 363 case req := <-f.requestCh: 364 fetching[req.reqid] = req // Tracking all in-flight requests for response latency statistic. 365 if len(fetching) == 1 { 366 f.rescheduleTimer(fetching, requestTimer) 367 } 368 369 case <-requestTimer.C: 370 for reqid, request := range fetching { 371 if time.Since(request.sendAt) > blockDelayTimeout-gatherSlack { 372 delete(fetching, reqid) 373 f.peerset.unregister(request.peerid.String()) 374 log.Debug("Request timeout", "peer", request.peerid, "reqid", reqid) 375 } 376 } 377 f.rescheduleTimer(fetching, requestTimer) 378 379 case resp := <-f.deliverCh: 380 if req := fetching[resp.reqid]; req != nil { 381 delete(fetching, resp.reqid) 382 f.rescheduleTimer(fetching, requestTimer) 383 384 // The underlying fetcher does not check the consistency of request and response. 385 // The adversary can send the fake announces with invalid hash and number but always 386 // delivery some mismatched header. So it can't be punished by the underlying fetcher. 387 // We have to add two more rules here to detect. 388 if len(resp.headers) != 1 { 389 f.peerset.unregister(req.peerid.String()) 390 log.Debug("Deliver more than requested", "peer", req.peerid, "reqid", req.reqid) 391 continue 392 } 393 if resp.headers[0].Hash() != req.hash { 394 f.peerset.unregister(req.peerid.String()) 395 log.Debug("Deliver invalid header", "peer", req.peerid, "reqid", req.reqid) 396 continue 397 } 398 resp.remain <- f.fetcher.FilterHeaders(resp.peerid.String(), resp.headers, time.Now()) 399 } else { 400 // Discard the entire packet no matter it's a timeout response or unexpected one. 401 resp.remain <- resp.headers 402 } 403 404 case ev := <-headCh: 405 // Short circuit if we are still syncing. 406 if syncing { 407 continue 408 } 409 reset(ev.Block.Header()) 410 411 // Clean stale announcements from les-servers. 412 var droplist []enode.ID 413 f.forEachPeer(func(id enode.ID, p *fetcherPeer) bool { 414 removed := p.forwardAnno(localTd) 415 for _, anno := range removed { 416 if header := f.chain.GetHeaderByHash(anno.data.Hash); header != nil { 417 if header.Number.Uint64() != anno.data.Number { 418 droplist = append(droplist, id) 419 break 420 } 421 // In theory td should exists. 422 td := f.chain.GetTd(anno.data.Hash, anno.data.Number) 423 if td != nil && td.Cmp(anno.data.Td) != 0 { 424 droplist = append(droplist, id) 425 break 426 } 427 } 428 } 429 return true 430 }) 431 for _, id := range droplist { 432 f.peerset.unregister(id.String()) 433 log.Debug("Kicked out peer for invalid announcement") 434 } 435 if f.newHeadHook != nil { 436 f.newHeadHook(localHead) 437 } 438 439 case origin := <-f.syncDone: 440 syncing = false // Reset the status 441 442 // Rewind all untrusted headers for ulc mode. 443 if ulc { 444 head := f.chain.CurrentHeader() 445 ancestor := rawdb.FindCommonAncestor(f.chaindb, origin, head) 446 var untrusted []common.Hash 447 for head.Number.Cmp(ancestor.Number) > 0 { 448 hash, number := head.Hash(), head.Number.Uint64() 449 if trusted, _ := trustedHeader(hash, number); trusted { 450 break 451 } 452 untrusted = append(untrusted, hash) 453 head = f.chain.GetHeader(head.ParentHash, number-1) 454 } 455 if len(untrusted) > 0 { 456 for i, j := 0, len(untrusted)-1; i < j; i, j = i+1, j-1 { 457 untrusted[i], untrusted[j] = untrusted[j], untrusted[i] 458 } 459 f.chain.Rollback(untrusted) 460 } 461 } 462 // Reset local status. 463 reset(f.chain.CurrentHeader()) 464 if f.newHeadHook != nil { 465 f.newHeadHook(localHead) 466 } 467 log.Debug("light sync finished", "number", localHead.Number, "hash", localHead.Hash()) 468 469 case <-f.closeCh: 470 return 471 } 472 } 473 } 474 475 // announce processes a new announcement message received from a peer. 476 func (f *lightFetcher) announce(p *serverPeer, head *announceData) { 477 if f.newAnnounce != nil { 478 f.newAnnounce(p, head) 479 } 480 if f.noAnnounce { 481 return 482 } 483 select { 484 case f.announceCh <- &announce{peerid: p.ID(), trust: p.trusted, data: head}: 485 case <-f.closeCh: 486 return 487 } 488 } 489 490 // trackRequest sends a reqID to main loop for in-flight request tracking. 491 func (f *lightFetcher) trackRequest(peerid enode.ID, reqid uint64, hash common.Hash) { 492 select { 493 case f.requestCh <- &request{reqid: reqid, peerid: peerid, sendAt: time.Now(), hash: hash}: 494 case <-f.closeCh: 495 } 496 } 497 498 // requestHeaderByHash constructs a header retrieval request and sends it to 499 // local request distributor. 500 // 501 // Note, we rely on the underlying eth/fetcher to retrieve and validate the 502 // response, so that we have to obey the rule of eth/fetcher which only accepts 503 // the response from given peer. 504 func (f *lightFetcher) requestHeaderByHash(peerid enode.ID) func(common.Hash) error { 505 return func(hash common.Hash) error { 506 req := &distReq{ 507 getCost: func(dp distPeer) uint64 { return dp.(*serverPeer).getRequestCost(GetBlockHeadersMsg, 1) }, 508 canSend: func(dp distPeer) bool { return dp.(*serverPeer).ID() == peerid }, 509 request: func(dp distPeer) func() { 510 peer, id := dp.(*serverPeer), genReqID() 511 cost := peer.getRequestCost(GetBlockHeadersMsg, 1) 512 peer.fcServer.QueuedRequest(id, cost) 513 514 return func() { 515 f.trackRequest(peer.ID(), id, hash) 516 peer.requestHeadersByHash(id, hash, 1, 0, false) 517 } 518 }, 519 } 520 f.reqDist.queue(req) 521 return nil 522 } 523 } 524 525 // requestResync invokes synchronisation callback to start syncing. 526 func (f *lightFetcher) startSync(id enode.ID) { 527 defer func(header *types.Header) { 528 f.syncDone <- header 529 }(f.chain.CurrentHeader()) 530 531 peer := f.peerset.peer(id.String()) 532 if peer == nil || peer.onlyAnnounce { 533 return 534 } 535 f.synchronise(peer) 536 } 537 538 // deliverHeaders delivers header download request responses for processing 539 func (f *lightFetcher) deliverHeaders(peer *serverPeer, reqid uint64, headers []*types.Header) []*types.Header { 540 remain := make(chan []*types.Header, 1) 541 select { 542 case f.deliverCh <- &response{reqid: reqid, headers: headers, peerid: peer.ID(), remain: remain}: 543 case <-f.closeCh: 544 return nil 545 } 546 return <-remain 547 } 548 549 // rescheduleTimer resets the specified timeout timer to the next request timeout. 550 func (f *lightFetcher) rescheduleTimer(requests map[uint64]*request, timer *time.Timer) { 551 // Short circuit if no inflight requests 552 if len(requests) == 0 { 553 timer.Stop() 554 return 555 } 556 // Otherwise find the earliest expiring request 557 earliest := time.Now() 558 for _, req := range requests { 559 if earliest.After(req.sendAt) { 560 earliest = req.sendAt 561 } 562 } 563 timer.Reset(blockDelayTimeout - time.Since(earliest)) 564 }