github.com/aidoskuneen/adk-node@v0.0.0-20220315131952-2e32567cb7f4/les/fetcher.go (about)

     1  // Copyright 2021 The adkgo Authors
     2  // This file is part of the adkgo library (adapted for adkgo from go--ethereum v1.10.8).
     3  //
     4  // the adkgo 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 adkgo 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 adkgo 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/aidoskuneen/adk-node/common"
    26  	"github.com/aidoskuneen/adk-node/consensus"
    27  	"github.com/aidoskuneen/adk-node/core"
    28  	"github.com/aidoskuneen/adk-node/core/rawdb"
    29  	"github.com/aidoskuneen/adk-node/core/types"
    30  	"github.com/aidoskuneen/adk-node/eth/fetcher"
    31  	"github.com/aidoskuneen/adk-node/ethdb"
    32  	"github.com/aidoskuneen/adk-node/light"
    33  	"github.com/aidoskuneen/adk-node/log"
    34  	"github.com/aidoskuneen/adk-node/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), rand.Uint64()
   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  }