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 }