github.1485827954.workers.dev/ethereum/go-ethereum@v1.14.3/cmd/devp2p/crawl.go (about)

     1  // Copyright 2019 The go-ethereum Authors
     2  // This file is part of go-ethereum.
     3  //
     4  // go-ethereum is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU 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  // go-ethereum 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 General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU General Public License
    15  // along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
    16  
    17  package main
    18  
    19  import (
    20  	"errors"
    21  	"sync"
    22  	"sync/atomic"
    23  	"time"
    24  
    25  	"github.com/ethereum/go-ethereum/log"
    26  	"github.com/ethereum/go-ethereum/p2p/enode"
    27  )
    28  
    29  type crawler struct {
    30  	input     nodeSet
    31  	output    nodeSet
    32  	disc      resolver
    33  	iters     []enode.Iterator
    34  	inputIter enode.Iterator
    35  	ch        chan *enode.Node
    36  	closed    chan struct{}
    37  
    38  	// settings
    39  	revalidateInterval time.Duration
    40  	mu                 sync.RWMutex
    41  }
    42  
    43  const (
    44  	nodeRemoved = iota
    45  	nodeSkipRecent
    46  	nodeSkipIncompat
    47  	nodeAdded
    48  	nodeUpdated
    49  )
    50  
    51  type resolver interface {
    52  	RequestENR(*enode.Node) (*enode.Node, error)
    53  }
    54  
    55  func newCrawler(input nodeSet, bootnodes []*enode.Node, disc resolver, iters ...enode.Iterator) (*crawler, error) {
    56  	if len(input) == 0 {
    57  		input.add(bootnodes...)
    58  	}
    59  	if len(input) == 0 {
    60  		return nil, errors.New("no input nodes to start crawling")
    61  	}
    62  
    63  	c := &crawler{
    64  		input:     input,
    65  		output:    make(nodeSet, len(input)),
    66  		disc:      disc,
    67  		iters:     iters,
    68  		inputIter: enode.IterNodes(input.nodes()),
    69  		ch:        make(chan *enode.Node),
    70  		closed:    make(chan struct{}),
    71  	}
    72  	c.iters = append(c.iters, c.inputIter)
    73  	// Copy input to output initially. Any nodes that fail validation
    74  	// will be dropped from output during the run.
    75  	for id, n := range input {
    76  		c.output[id] = n
    77  	}
    78  	return c, nil
    79  }
    80  
    81  func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet {
    82  	var (
    83  		timeoutTimer = time.NewTimer(timeout)
    84  		timeoutCh    <-chan time.Time
    85  		statusTicker = time.NewTicker(time.Second * 8)
    86  		doneCh       = make(chan enode.Iterator, len(c.iters))
    87  		liveIters    = len(c.iters)
    88  	)
    89  	if nthreads < 1 {
    90  		nthreads = 1
    91  	}
    92  	defer timeoutTimer.Stop()
    93  	defer statusTicker.Stop()
    94  	for _, it := range c.iters {
    95  		go c.runIterator(doneCh, it)
    96  	}
    97  	var (
    98  		added   atomic.Uint64
    99  		updated atomic.Uint64
   100  		skipped atomic.Uint64
   101  		recent  atomic.Uint64
   102  		removed atomic.Uint64
   103  		wg      sync.WaitGroup
   104  	)
   105  	wg.Add(nthreads)
   106  	for i := 0; i < nthreads; i++ {
   107  		go func() {
   108  			defer wg.Done()
   109  			for {
   110  				select {
   111  				case n := <-c.ch:
   112  					switch c.updateNode(n) {
   113  					case nodeSkipIncompat:
   114  						skipped.Add(1)
   115  					case nodeSkipRecent:
   116  						recent.Add(1)
   117  					case nodeRemoved:
   118  						removed.Add(1)
   119  					case nodeAdded:
   120  						added.Add(1)
   121  					default:
   122  						updated.Add(1)
   123  					}
   124  				case <-c.closed:
   125  					return
   126  				}
   127  			}
   128  		}()
   129  	}
   130  
   131  loop:
   132  	for {
   133  		select {
   134  		case it := <-doneCh:
   135  			if it == c.inputIter {
   136  				// Enable timeout when we're done revalidating the input nodes.
   137  				log.Info("Revalidation of input set is done", "len", len(c.input))
   138  				if timeout > 0 {
   139  					timeoutCh = timeoutTimer.C
   140  				}
   141  			}
   142  			if liveIters--; liveIters == 0 {
   143  				break loop
   144  			}
   145  		case <-timeoutCh:
   146  			break loop
   147  		case <-statusTicker.C:
   148  			log.Info("Crawling in progress",
   149  				"added", added.Load(),
   150  				"updated", updated.Load(),
   151  				"removed", removed.Load(),
   152  				"ignored(recent)", recent.Load(),
   153  				"ignored(incompatible)", skipped.Load())
   154  		}
   155  	}
   156  
   157  	close(c.closed)
   158  	for _, it := range c.iters {
   159  		it.Close()
   160  	}
   161  	for ; liveIters > 0; liveIters-- {
   162  		<-doneCh
   163  	}
   164  	wg.Wait()
   165  	return c.output
   166  }
   167  
   168  func (c *crawler) runIterator(done chan<- enode.Iterator, it enode.Iterator) {
   169  	defer func() { done <- it }()
   170  	for it.Next() {
   171  		select {
   172  		case c.ch <- it.Node():
   173  		case <-c.closed:
   174  			return
   175  		}
   176  	}
   177  }
   178  
   179  // updateNode updates the info about the given node, and returns a status
   180  // about what changed
   181  func (c *crawler) updateNode(n *enode.Node) int {
   182  	c.mu.RLock()
   183  	node, ok := c.output[n.ID()]
   184  	c.mu.RUnlock()
   185  
   186  	// Skip validation of recently-seen nodes.
   187  	if ok && time.Since(node.LastCheck) < c.revalidateInterval {
   188  		return nodeSkipRecent
   189  	}
   190  
   191  	// Request the node record.
   192  	status := nodeUpdated
   193  	node.LastCheck = truncNow()
   194  	if nn, err := c.disc.RequestENR(n); err != nil {
   195  		if node.Score == 0 {
   196  			// Node doesn't implement EIP-868.
   197  			log.Debug("Skipping node", "id", n.ID())
   198  			return nodeSkipIncompat
   199  		}
   200  		node.Score /= 2
   201  	} else {
   202  		node.N = nn
   203  		node.Seq = nn.Seq()
   204  		node.Score++
   205  		if node.FirstResponse.IsZero() {
   206  			node.FirstResponse = node.LastCheck
   207  			status = nodeAdded
   208  		}
   209  		node.LastResponse = node.LastCheck
   210  	}
   211  	// Store/update node in output set.
   212  	c.mu.Lock()
   213  	defer c.mu.Unlock()
   214  	if node.Score <= 0 {
   215  		log.Debug("Removing node", "id", n.ID())
   216  		delete(c.output, n.ID())
   217  		return nodeRemoved
   218  	}
   219  	log.Debug("Updating node", "id", n.ID(), "seq", n.Seq(), "score", node.Score)
   220  	c.output[n.ID()] = node
   221  	return status
   222  }
   223  
   224  func truncNow() time.Time {
   225  	return time.Now().UTC().Truncate(1 * time.Second)
   226  }