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