github.com/linapex/ethereum-go-chinese@v0.0.0-20190316121929-f8b7a73c3fa1/swarm/network/fetcher.go (about) 1 2 //<developer> 3 // <name>linapex 曹一峰</name> 4 // <email>linapex@163.com</email> 5 // <wx>superexc</wx> 6 // <qqgroup>128148617</qqgroup> 7 // <url>https://jsq.ink</url> 8 // <role>pku engineer</role> 9 // <date>2019-03-16 19:16:43</date> 10 //</624450113619628032> 11 12 13 package network 14 15 import ( 16 "context" 17 "sync" 18 "time" 19 20 "github.com/ethereum/go-ethereum/log" 21 "github.com/ethereum/go-ethereum/p2p/enode" 22 "github.com/ethereum/go-ethereum/swarm/storage" 23 ) 24 25 const ( 26 defaultSearchTimeout = 1 * time.Second 27 //最大转发请求数(跃点),以确保请求不 28 //在对等循环中永久转发 29 maxHopCount uint8 = 20 30 ) 31 32 //考虑跳过对等机的时间。 33 //也用于流传送。 34 var RequestTimeout = 10 * time.Second 35 36 type RequestFunc func(context.Context, *Request) (*enode.ID, chan struct{}, error) 37 38 //当在本地找不到块时,将创建提取程序。它启动一次请求处理程序循环,然后 39 //在所有活动请求完成之前保持活动状态。这可能发生: 40 //1。或者因为块被传递 41 //2。或者因为请求者取消/超时 42 //获取器在完成后自行销毁。 43 //TODO:取消终止后的所有转发请求 44 type Fetcher struct { 45 protoRequestFunc RequestFunc //请求函数获取器调用以发出对块的检索请求 46 addr storage.Address //要获取的块的地址 47 offerC chan *enode.ID //源通道(对等节点ID字符串) 48 requestC chan uint8 //接收请求的通道(其中包含hopCount值) 49 searchTimeout time.Duration 50 skipCheck bool 51 } 52 53 type Request struct { 54 Addr storage.Address //块地址 55 Source *enode.ID //请求方的节点ID(可以为零) 56 SkipCheck bool //是先提供块还是直接交付 57 peersToSkip *sync.Map //不从中请求块的对等方(仅当源为零时才有意义) 58 HopCount uint8 //转发请求数(跃点) 59 } 60 61 //NewRequest返回基于块地址跳过检查和 62 //要跳过的对等映射。 63 func NewRequest(addr storage.Address, skipCheck bool, peersToSkip *sync.Map) *Request { 64 return &Request{ 65 Addr: addr, 66 SkipCheck: skipCheck, 67 peersToSkip: peersToSkip, 68 } 69 } 70 71 //如果不应请求具有nodeid的对等端传递块,则skippeer返回。 72 //要跳过的对等点在每个请求和请求超时的一段时间内保持不变。 73 //此函数用于delivery.requestfrompeers中的流包中以优化 74 //请求块。 75 func (r *Request) SkipPeer(nodeID string) bool { 76 val, ok := r.peersToSkip.Load(nodeID) 77 if !ok { 78 return false 79 } 80 t, ok := val.(time.Time) 81 if ok && time.Now().After(t.Add(RequestTimeout)) { 82 //截止日期已过期 83 r.peersToSkip.Delete(nodeID) 84 return false 85 } 86 return true 87 } 88 89 //FetcherFactory是用请求函数初始化的,可以创建Fetcher 90 type FetcherFactory struct { 91 request RequestFunc 92 skipCheck bool 93 } 94 95 //NewFetcherFactory接受请求函数并跳过检查参数并创建FetcherFactory 96 func NewFetcherFactory(request RequestFunc, skipCheck bool) *FetcherFactory { 97 return &FetcherFactory{ 98 request: request, 99 skipCheck: skipCheck, 100 } 101 } 102 103 //new为给定的块构造一个新的获取器。PeersToSkip中的所有对等机 104 //不要求传递给定的块。PeersToSkip应该始终 105 //包含主动请求此块的对等方,以确保 106 //不要向他们要求回块。 107 //创建的获取器将启动并返回。 108 func (f *FetcherFactory) New(ctx context.Context, source storage.Address, peersToSkip *sync.Map) storage.NetFetcher { 109 fetcher := NewFetcher(source, f.request, f.skipCheck) 110 go fetcher.run(ctx, peersToSkip) 111 return fetcher 112 } 113 114 //new fetcher使用给定的请求函数为给定的块地址创建一个新的fetcher。 115 func NewFetcher(addr storage.Address, rf RequestFunc, skipCheck bool) *Fetcher { 116 return &Fetcher{ 117 addr: addr, 118 protoRequestFunc: rf, 119 offerC: make(chan *enode.ID), 120 requestC: make(chan uint8), 121 searchTimeout: defaultSearchTimeout, 122 skipCheck: skipCheck, 123 } 124 } 125 126 //当上游对等端通过同步作为“offeredhashemsg”的一部分来提供区块,并且节点在本地没有区块时,调用offer。 127 func (f *Fetcher) Offer(ctx context.Context, source *enode.ID) { 128 //首先,我们需要进行此选择,以确保在上下文完成时返回 129 select { 130 case <-ctx.Done(): 131 return 132 default: 133 } 134 135 //仅此选择并不能保证返回上下文已完成,它可能 136 //如果提供offerc,则推送至offerc(请参阅https://golang.org/ref/spec select_语句中的数字2) 137 select { 138 case f.offerC <- source: 139 case <-ctx.Done(): 140 } 141 } 142 143 //当上游对等端作为“retrieverequestmsg”的一部分或通过filestore从本地请求请求请求块,并且节点在本地没有块时,调用请求。 144 func (f *Fetcher) Request(ctx context.Context, hopCount uint8) { 145 //首先,我们需要进行此选择,以确保在上下文完成时返回 146 select { 147 case <-ctx.Done(): 148 return 149 default: 150 } 151 152 if hopCount >= maxHopCount { 153 log.Debug("fetcher request hop count limit reached", "hops", hopCount) 154 return 155 } 156 157 //仅此选择并不能保证返回上下文已完成,它可能 158 //如果提供offerc,则推送至offerc(请参阅https://golang.org/ref/spec select_语句中的数字2) 159 select { 160 case f.requestC <- hopCount + 1: 161 case <-ctx.Done(): 162 } 163 } 164 165 //开始准备获取程序 166 //它在传递的上下文的生命周期内保持提取程序的活动状态 167 func (f *Fetcher) run(ctx context.Context, peers *sync.Map) { 168 var ( 169 doRequest bool //确定是否在当前迭代中启动检索 170 wait *time.Timer //搜索超时计时器 171 waitC <-chan time.Time //计时器通道 172 sources []*enode.ID //已知来源,即提供数据块的对等方 173 requested bool //如果块是实际请求的,则为true 174 hopCount uint8 175 ) 176 gone := make(chan *enode.ID) //向我们请求的对等机发出信号的通道 177 178 //保持提取进程活动的循环 179 //每次请求后,都会设置一个计时器。如果发生这种情况,我们会再次向另一位同行请求 180 //请注意,上一个请求仍然有效,并且有机会传递,因此 181 //再次请求将扩展搜索范围。I. 182 //如果我们请求的对等点不在,我们将发出一个新的请求,因此活动的 183 //请求从不减少 184 for { 185 select { 186 187 //来料报价 188 case source := <-f.offerC: 189 log.Trace("new source", "peer addr", source, "request addr", f.addr) 190 //1)块由同步对等提供 191 //添加到已知源 192 sources = append(sources, source) 193 //向源iff发送请求请求块被请求(不仅仅是因为同步对等提供了块) 194 doRequest = requested 195 196 //传入请求 197 case hopCount = <-f.requestC: 198 log.Trace("new request", "request addr", f.addr) 199 //2)请求块,设置请求标志 200 //启动一个请求如果还没有启动 201 doRequest = !requested 202 requested = true 203 204 //我们请求的同伴不见了。回到另一个 205 //从对等映射中删除对等 206 case id := <-gone: 207 log.Trace("peer gone", "peer id", id.String(), "request addr", f.addr) 208 peers.Delete(id.String()) 209 doRequest = requested 210 211 //搜索超时:自上次请求以来经过的时间太长, 212 //如果我们能找到一个新的对等点,就把搜索扩展到一个新的对等点。 213 case <-waitC: 214 log.Trace("search timed out: requesting", "request addr", f.addr) 215 doRequest = requested 216 217 //所有提取程序上下文都已关闭,无法退出 218 case <-ctx.Done(): 219 log.Trace("terminate fetcher", "request addr", f.addr) 220 //TODO:向对等映射中剩余的所有对等发送取消通知(即,我们请求的那些对等) 221 return 222 } 223 224 //需要发出新请求 225 if doRequest { 226 var err error 227 sources, err = f.doRequest(ctx, gone, peers, sources, hopCount) 228 if err != nil { 229 log.Info("unable to request", "request addr", f.addr, "err", err) 230 } 231 } 232 233 //如果未设置等待通道,则将其设置为计时器。 234 if requested { 235 if wait == nil { 236 wait = time.NewTimer(f.searchTimeout) 237 defer wait.Stop() 238 waitC = wait.C 239 } else { 240 //如果之前没有排空,请停止计时器并排空通道。 241 if !wait.Stop() { 242 select { 243 case <-wait.C: 244 default: 245 } 246 } 247 //将计时器重置为在默认搜索超时后关闭 248 wait.Reset(f.searchTimeout) 249 } 250 } 251 doRequest = false 252 } 253 } 254 255 //Dorequest试图找到一个对等机来请求块 256 //*首先,它尝试从已知提供了块的对等方显式请求 257 //*如果没有这样的对等点(可用),它会尝试从最接近块地址的对等点请求它。 258 //排除PeersToSkip地图中的那些 259 //*如果未找到此类对等机,则返回错误。 260 // 261 //如果请求成功, 262 //*将对等机的地址添加到要跳过的对等机集合中。 263 //*从预期来源中删除对等方的地址,以及 264 //*如果对等端断开连接(或终止其拖缆),将启动一个Go例行程序,报告消失的通道。 265 func (f *Fetcher) doRequest(ctx context.Context, gone chan *enode.ID, peersToSkip *sync.Map, sources []*enode.ID, hopCount uint8) ([]*enode.ID, error) { 266 var i int 267 var sourceID *enode.ID 268 var quit chan struct{} 269 270 req := &Request{ 271 Addr: f.addr, 272 SkipCheck: f.skipCheck, 273 peersToSkip: peersToSkip, 274 HopCount: hopCount, 275 } 276 277 foundSource := false 278 //迭代已知源 279 for i = 0; i < len(sources); i++ { 280 req.Source = sources[i] 281 var err error 282 sourceID, quit, err = f.protoRequestFunc(ctx, req) 283 if err == nil { 284 //从已知源中删除对等机 285 //注意:我们可以修改源代码,尽管我们在它上面循环,因为我们会立即从循环中断。 286 sources = append(sources[:i], sources[i+1:]...) 287 foundSource = true 288 break 289 } 290 } 291 292 //如果没有已知的源,或者没有可用的源,我们尝试从最近的节点请求。 293 if !foundSource { 294 req.Source = nil 295 var err error 296 sourceID, quit, err = f.protoRequestFunc(ctx, req) 297 if err != nil { 298 //如果找不到对等方请求 299 return sources, err 300 } 301 } 302 //将对等添加到要从现在开始跳过的对等集 303 peersToSkip.Store(sourceID.String(), time.Now()) 304 305 //如果退出通道关闭,则表示我们请求的源对等端 306 //断开或终止拖缆 307 //这里开始一个执行例程,监视这个通道并报告消失通道上的源对等点。 308 //如果已完成获取器全局上下文以防止进程泄漏,则此go例程将退出。 309 go func() { 310 select { 311 case <-quit: 312 gone <- sourceID 313 case <-ctx.Done(): 314 } 315 }() 316 return sources, nil 317 } 318