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