github.com/ethw3/go-ethereuma@v0.0.0-20221013053120-c14602a4c23c/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  	"time"
    21  
    22  	"github.com/ethw3/go-ethereuma/log"
    23  	"github.com/ethw3/go-ethereuma/p2p/enode"
    24  )
    25  
    26  type crawler struct {
    27  	input     nodeSet
    28  	output    nodeSet
    29  	disc      resolver
    30  	iters     []enode.Iterator
    31  	inputIter enode.Iterator
    32  	ch        chan *enode.Node
    33  	closed    chan struct{}
    34  
    35  	// settings
    36  	revalidateInterval time.Duration
    37  }
    38  
    39  type resolver interface {
    40  	RequestENR(*enode.Node) (*enode.Node, error)
    41  }
    42  
    43  func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler {
    44  	c := &crawler{
    45  		input:     input,
    46  		output:    make(nodeSet, len(input)),
    47  		disc:      disc,
    48  		iters:     iters,
    49  		inputIter: enode.IterNodes(input.nodes()),
    50  		ch:        make(chan *enode.Node),
    51  		closed:    make(chan struct{}),
    52  	}
    53  	c.iters = append(c.iters, c.inputIter)
    54  	// Copy input to output initially. Any nodes that fail validation
    55  	// will be dropped from output during the run.
    56  	for id, n := range input {
    57  		c.output[id] = n
    58  	}
    59  	return c
    60  }
    61  
    62  func (c *crawler) run(timeout time.Duration) nodeSet {
    63  	var (
    64  		timeoutTimer = time.NewTimer(timeout)
    65  		timeoutCh    <-chan time.Time
    66  		doneCh       = make(chan enode.Iterator, len(c.iters))
    67  		liveIters    = len(c.iters)
    68  	)
    69  	defer timeoutTimer.Stop()
    70  	for _, it := range c.iters {
    71  		go c.runIterator(doneCh, it)
    72  	}
    73  
    74  loop:
    75  	for {
    76  		select {
    77  		case n := <-c.ch:
    78  			c.updateNode(n)
    79  		case it := <-doneCh:
    80  			if it == c.inputIter {
    81  				// Enable timeout when we're done revalidating the input nodes.
    82  				log.Info("Revalidation of input set is done", "len", len(c.input))
    83  				if timeout > 0 {
    84  					timeoutCh = timeoutTimer.C
    85  				}
    86  			}
    87  			if liveIters--; liveIters == 0 {
    88  				break loop
    89  			}
    90  		case <-timeoutCh:
    91  			break loop
    92  		}
    93  	}
    94  
    95  	close(c.closed)
    96  	for _, it := range c.iters {
    97  		it.Close()
    98  	}
    99  	for ; liveIters > 0; liveIters-- {
   100  		<-doneCh
   101  	}
   102  	return c.output
   103  }
   104  
   105  func (c *crawler) runIterator(done chan<- enode.Iterator, it enode.Iterator) {
   106  	defer func() { done <- it }()
   107  	for it.Next() {
   108  		select {
   109  		case c.ch <- it.Node():
   110  		case <-c.closed:
   111  			return
   112  		}
   113  	}
   114  }
   115  
   116  func (c *crawler) updateNode(n *enode.Node) {
   117  	node, ok := c.output[n.ID()]
   118  
   119  	// Skip validation of recently-seen nodes.
   120  	if ok && time.Since(node.LastCheck) < c.revalidateInterval {
   121  		return
   122  	}
   123  
   124  	// Request the node record.
   125  	nn, err := c.disc.RequestENR(n)
   126  	node.LastCheck = truncNow()
   127  	if err != nil {
   128  		if node.Score == 0 {
   129  			// Node doesn't implement EIP-868.
   130  			log.Debug("Skipping node", "id", n.ID())
   131  			return
   132  		}
   133  		node.Score /= 2
   134  	} else {
   135  		node.N = nn
   136  		node.Seq = nn.Seq()
   137  		node.Score++
   138  		if node.FirstResponse.IsZero() {
   139  			node.FirstResponse = node.LastCheck
   140  		}
   141  		node.LastResponse = node.LastCheck
   142  	}
   143  
   144  	// Store/update node in output set.
   145  	if node.Score <= 0 {
   146  		log.Info("Removing node", "id", n.ID())
   147  		delete(c.output, n.ID())
   148  	} else {
   149  		log.Info("Updating node", "id", n.ID(), "seq", n.Seq(), "score", node.Score)
   150  		c.output[n.ID()] = node
   151  	}
   152  }
   153  
   154  func truncNow() time.Time {
   155  	return time.Now().UTC().Truncate(1 * time.Second)
   156  }