github.com/nitinawathare/ethereumassignment3@v0.0.0-20211021213010-f07344c2b868/go-ethereum/swarm/network/fetcher.go (about)

     1  // Copyright 2018 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 network
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"sync"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/p2p/enode"
    27  	"github.com/ethereum/go-ethereum/swarm/storage"
    28  	"github.com/ethereum/go-ethereum/swarm/tracing"
    29  	olog "github.com/opentracing/opentracing-go/log"
    30  )
    31  
    32  const (
    33  	defaultSearchTimeout = 1 * time.Second
    34  	// maximum number of forwarded requests (hops), to make sure requests are not
    35  	// forwarded forever in peer loops
    36  	maxHopCount uint8 = 20
    37  )
    38  
    39  // Time to consider peer to be skipped.
    40  // Also used in stream delivery.
    41  var RequestTimeout = 10 * time.Second
    42  
    43  type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error)
    44  
    45  // Fetcher is created when a chunk is not found locally. It starts a request handler loop once and
    46  // keeps it alive until all active requests are completed. This can happen:
    47  //     1. either because the chunk is delivered
    48  //     2. or because the requester cancelled/timed out
    49  // Fetcher self destroys itself after it is completed.
    50  // TODO: cancel all forward requests after termination
    51  type Fetcher struct {
    52  	protoRequestFunc RequestFunc     // request function fetcher calls to issue retrieve request for a chunk
    53  	addr             storage.Address // the address of the chunk to be fetched
    54  	offerC           chan *enode.ID  // channel of sources (peer node id strings)
    55  	requestC         chan uint8      // channel for incoming requests (with the hopCount value in it)
    56  	searchTimeout    time.Duration
    57  	skipCheck        bool
    58  	ctx              context.Context
    59  }
    60  
    61  type Request struct {
    62  	Addr        storage.Address // chunk address
    63  	Source      *enode.ID       // nodeID of peer to request from (can be nil)
    64  	SkipCheck   bool            // whether to offer the chunk first or deliver directly
    65  	peersToSkip *sync.Map       // peers not to request chunk from (only makes sense if source is nil)
    66  	HopCount    uint8           // number of forwarded requests (hops)
    67  }
    68  
    69  // NewRequest returns a new instance of Request based on chunk address skip check and
    70  // a map of peers to skip.
    71  func NewRequest(addr storage.Address, skipCheck bool, peersToSkip *sync.Map) *Request {
    72  	return &Request{
    73  		Addr:        addr,
    74  		SkipCheck:   skipCheck,
    75  		peersToSkip: peersToSkip,
    76  	}
    77  }
    78  
    79  // SkipPeer returns if the peer with nodeID should not be requested to deliver a chunk.
    80  // Peers to skip are kept per Request and for a time period of RequestTimeout.
    81  // This function is used in stream package in Delivery.RequestFromPeers to optimize
    82  // requests for chunks.
    83  func (r *Request) SkipPeer(nodeID string) bool {
    84  	val, ok := r.peersToSkip.Load(nodeID)
    85  	if !ok {
    86  		return false
    87  	}
    88  	t, ok := val.(time.Time)
    89  	if ok && time.Now().After(t.Add(RequestTimeout)) {
    90  		// deadline expired
    91  		r.peersToSkip.Delete(nodeID)
    92  		return false
    93  	}
    94  	return true
    95  }
    96  
    97  // FetcherFactory is initialised with a request function and can create fetchers
    98  type FetcherFactory struct {
    99  	request   RequestFunc
   100  	skipCheck bool
   101  }
   102  
   103  // NewFetcherFactory takes a request function and skip check parameter and creates a FetcherFactory
   104  func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory {
   105  	return &FetcherFactory{
   106  		request:   request,
   107  		skipCheck: skipCheck,
   108  	}
   109  }
   110  
   111  // New constructs a new Fetcher, for the given chunk. All peers in peersToSkip
   112  // are not requested to deliver the given chunk. peersToSkip should always
   113  // contain the peers which are actively requesting this chunk, to make sure we
   114  // don't request back the chunks from them.
   115  // The created Fetcher is started and returned.
   116  func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peers *sync.Map) storage.NetFetcher {
   117  	fetcher := NewFetcher(ctx, source, f.request, f.skipCheck)
   118  	go fetcher.run(peers)
   119  	return fetcher
   120  }
   121  
   122  // NewFetcher creates a new Fetcher for the given chunk address using the given request function.
   123  func NewFetcher(ctx context.Context, addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher {
   124  	return &Fetcher{
   125  		addr:             addr,
   126  		protoRequestFunc: rf,
   127  		offerC:           make(chan *enode.ID),
   128  		requestC:         make(chan uint8),
   129  		searchTimeout:    defaultSearchTimeout,
   130  		skipCheck:        skipCheck,
   131  		ctx:              ctx,
   132  	}
   133  }
   134  
   135  // Offer is called when an upstream peer offers the chunk via syncing as part of `OfferedHashesMsg` and the node does not have the chunk locally.
   136  func (f *Fetcher) Offer(source *enode.ID) {
   137  	// First we need to have this select to make sure that we return if context is done
   138  	select {
   139  	case <-f.ctx.Done():
   140  		return
   141  	default:
   142  	}
   143  
   144  	// This select alone would not guarantee that we return of context is done, it could potentially
   145  	// push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements)
   146  	select {
   147  	case f.offerC <- source:
   148  	case <-f.ctx.Done():
   149  	}
   150  }
   151  
   152  // Request is called when an upstream peer request the chunk as part of `RetrieveRequestMsg`, or from a local request through FileStore, and the node does not have the chunk locally.
   153  func (f *Fetcher) Request(hopCount uint8) {
   154  	// First we need to have this select to make sure that we return if context is done
   155  	select {
   156  	case <-f.ctx.Done():
   157  		return
   158  	default:
   159  	}
   160  
   161  	if hopCount >= maxHopCount {
   162  		log.Debug("fetcher request hop count limit reached", "hops", hopCount)
   163  		return
   164  	}
   165  
   166  	// This select alone would not guarantee that we return of context is done, it could potentially
   167  	// push to offerC instead if offerC is available (see number 2 in https://golang.org/ref/spec#Select_statements)
   168  	select {
   169  	case f.requestC <- hopCount + 1:
   170  	case <-f.ctx.Done():
   171  	}
   172  }
   173  
   174  // start prepares the Fetcher
   175  // it keeps the Fetcher alive within the lifecycle of the passed context
   176  func (f *Fetcher) run(peers *sync.Map) {
   177  	var (
   178  		doRequest bool             // determines if retrieval is initiated in the current iteration
   179  		wait      *time.Timer      // timer for search timeout
   180  		waitC     <-chan time.Time // timer channel
   181  		sources   []*enode.ID      // known sources, ie. peers that offered the chunk
   182  		requested bool             // true if the chunk was actually requested
   183  		hopCount  uint8
   184  	)
   185  	gone := make(chan *enode.ID) // channel to signal that a peer we requested from disconnected
   186  
   187  	// loop that keeps the fetching process alive
   188  	// after every request a timer is set. If this goes off we request again from another peer
   189  	// note that the previous request is still alive and has the chance to deliver, so
   190  	// requesting again extends the search. ie.,
   191  	// if a peer we requested from is gone we issue a new request, so the number of active
   192  	// requests never decreases
   193  	for {
   194  		select {
   195  
   196  		// incoming offer
   197  		case source := <-f.offerC:
   198  			log.Trace("new source", "peer addr", source, "request addr", f.addr)
   199  			// 1) the chunk is offered by a syncing peer
   200  			// add to known sources
   201  			sources = append(sources, source)
   202  			// launch a request to the source iff the chunk was requested (not just expected because its offered by a syncing peer)
   203  			doRequest = requested
   204  
   205  		// incoming request
   206  		case hopCount = <-f.requestC:
   207  			// 2) chunk is requested, set requested flag
   208  			// launch a request iff none been launched yet
   209  			doRequest = !requested
   210  			log.Trace("new request", "request addr", f.addr, "doRequest", doRequest)
   211  			requested = true
   212  
   213  			// peer we requested from is gone. fall back to another
   214  			// and remove the peer from the peers map
   215  		case id := <-gone:
   216  			peers.Delete(id.String())
   217  			doRequest = requested
   218  			log.Trace("peer gone", "peer id", id.String(), "request addr", f.addr, "doRequest", doRequest)
   219  
   220  		// search timeout: too much time passed since the last request,
   221  		// extend the search to a new peer if we can find one
   222  		case <-waitC:
   223  			doRequest = requested
   224  			log.Trace("search timed out: requesting", "request addr", f.addr, "doRequest", doRequest)
   225  
   226  			// all Fetcher context closed, can quit
   227  		case <-f.ctx.Done():
   228  			log.Trace("terminate fetcher", "request addr", f.addr)
   229  			// TODO: send cancellations to all peers left over in peers map (i.e., those we requested from)
   230  			return
   231  		}
   232  
   233  		// need to issue a new request
   234  		if doRequest {
   235  			var err error
   236  			sources, err = f.doRequest(gone, peers, sources, hopCount)
   237  			if err != nil {
   238  				log.Info("unable to request", "request addr", f.addr, "err", err)
   239  			}
   240  		}
   241  
   242  		// if wait channel is not set, set it to a timer
   243  		if requested {
   244  			if wait == nil {
   245  				wait = time.NewTimer(f.searchTimeout)
   246  				defer wait.Stop()
   247  				waitC = wait.C
   248  			} else {
   249  				// stop the timer and drain the channel if it was not drained earlier
   250  				if !wait.Stop() {
   251  					select {
   252  					case <-wait.C:
   253  					default:
   254  					}
   255  				}
   256  				// reset the timer to go off after defaultSearchTimeout
   257  				wait.Reset(f.searchTimeout)
   258  			}
   259  		}
   260  		doRequest = false
   261  	}
   262  }
   263  
   264  // doRequest attempts at finding a peer to request the chunk from
   265  // * first it tries to request explicitly from peers that are known to have offered the chunk
   266  // * if there are no such peers (available) it tries to request it from a peer closest to the chunk address
   267  //   excluding those in the peersToSkip map
   268  // * if no such peer is found an error is returned
   269  //
   270  // if a request is successful,
   271  // * the peer's address is added to the set of peers to skip
   272  // * the peer's address is removed from prospective sources, and
   273  // * a go routine is started that reports on the gone channel if the peer is disconnected (or terminated their streamer)
   274  func (f *Fetcher) doRequest(gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) {
   275  	var i int
   276  	var sourceID *enode.ID
   277  	var quit chan struct{}
   278  
   279  	req := &Request{
   280  		Addr:        f.addr,
   281  		SkipCheck:   f.skipCheck,
   282  		peersToSkip: peersToSkip,
   283  		HopCount:    hopCount,
   284  	}
   285  
   286  	foundSource := false
   287  	// iterate over known sources
   288  	for i = 0; i < len(sources); i++ {
   289  		req.Source = sources[i]
   290  		var err error
   291  		log.Trace("fetcher.doRequest", "request addr", f.addr, "peer", req.Source.String())
   292  		sourceID, quit, err = f.protoRequestFunc(f.ctx, req)
   293  		if err == nil {
   294  			// remove the peer from known sources
   295  			// Note: we can modify the source although we are looping on it, because we break from the loop immediately
   296  			sources = append(sources[:i], sources[i+1:]...)
   297  			foundSource = true
   298  			break
   299  		}
   300  	}
   301  
   302  	// if there are no known sources, or none available, we try request from a closest node
   303  	if !foundSource {
   304  		req.Source = nil
   305  		var err error
   306  		sourceID, quit, err = f.protoRequestFunc(f.ctx, req)
   307  		if err != nil {
   308  			// if no peers found to request from
   309  			return sources, err
   310  		}
   311  	}
   312  	// add peer to the set of peers to skip from now
   313  	peersToSkip.Store(sourceID.String(), time.Now())
   314  
   315  	// if the quit channel is closed, it indicates that the source peer we requested from
   316  	// disconnected or terminated its streamer
   317  	// here start a go routine that watches this channel and reports the source peer on the gone channel
   318  	// this go routine quits if the fetcher global context is done to prevent process leak
   319  	go func() {
   320  		select {
   321  		case <-quit:
   322  			gone <- sourceID
   323  		case <-f.ctx.Done():
   324  		}
   325  
   326  		// finish the request span
   327  		spanId := fmt.Sprintf("stream.send.request.%v.%v", *sourceID, req.Addr)
   328  		span := tracing.ShiftSpanByKey(spanId)
   329  
   330  		if span != nil {
   331  			span.LogFields(olog.String("finish", "from doRequest"))
   332  			span.Finish()
   333  		}
   334  	}()
   335  	return sources, nil
   336  }