github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/cmd/devp2p/crawl.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  package main
    19  
    20  import (
    21  	"time"
    22  
    23  	"github.com/AigarNetwork/aigar/log"
    24  	"github.com/AigarNetwork/aigar/p2p/discover"
    25  	"github.com/AigarNetwork/aigar/p2p/enode"
    26  )
    27  
    28  type crawler struct {
    29  	input     nodeSet
    30  	output    nodeSet
    31  	disc      *discover.UDPv4
    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  }
    40  
    41  func newCrawler(input nodeSet, disc *discover.UDPv4, iters ...enode.Iterator) *crawler {
    42  	c := &crawler{
    43  		input:     input,
    44  		output:    make(nodeSet, len(input)),
    45  		disc:      disc,
    46  		iters:     iters,
    47  		inputIter: enode.IterNodes(input.nodes()),
    48  		ch:        make(chan *enode.Node),
    49  		closed:    make(chan struct{}),
    50  	}
    51  	c.iters = append(c.iters, c.inputIter)
    52  	// Copy input to output initially. Any nodes that fail validation
    53  	// will be dropped from output during the run.
    54  	for id, n := range input {
    55  		c.output[id] = n
    56  	}
    57  	return c
    58  }
    59  
    60  func (c *crawler) run(timeout time.Duration) nodeSet {
    61  	var (
    62  		timeoutTimer = time.NewTimer(timeout)
    63  		timeoutCh    <-chan time.Time
    64  		doneCh       = make(chan enode.Iterator, len(c.iters))
    65  		liveIters    = len(c.iters)
    66  	)
    67  	for _, it := range c.iters {
    68  		go c.runIterator(doneCh, it)
    69  	}
    70  
    71  loop:
    72  	for {
    73  		select {
    74  		case n := <-c.ch:
    75  			c.updateNode(n)
    76  		case it := <-doneCh:
    77  			if it == c.inputIter {
    78  				// Enable timeout when we're done revalidating the input nodes.
    79  				log.Info("Revalidation of input set is done", "len", len(c.input))
    80  				if timeout > 0 {
    81  					timeoutCh = timeoutTimer.C
    82  				}
    83  			}
    84  			if liveIters--; liveIters == 0 {
    85  				break loop
    86  			}
    87  		case <-timeoutCh:
    88  			break loop
    89  		}
    90  	}
    91  
    92  	close(c.closed)
    93  	for _, it := range c.iters {
    94  		it.Close()
    95  	}
    96  	for ; liveIters > 0; liveIters-- {
    97  		<-doneCh
    98  	}
    99  	return c.output
   100  }
   101  
   102  func (c *crawler) runIterator(done chan<- enode.Iterator, it enode.Iterator) {
   103  	defer func() { done <- it }()
   104  	for it.Next() {
   105  		select {
   106  		case c.ch <- it.Node():
   107  		case <-c.closed:
   108  			return
   109  		}
   110  	}
   111  }
   112  
   113  func (c *crawler) updateNode(n *enode.Node) {
   114  	node, ok := c.output[n.ID()]
   115  
   116  	// Skip validation of recently-seen nodes.
   117  	if ok && time.Since(node.LastCheck) < c.revalidateInterval {
   118  		return
   119  	}
   120  
   121  	// Request the node record.
   122  	nn, err := c.disc.RequestENR(n)
   123  	node.LastCheck = truncNow()
   124  	if err != nil {
   125  		if node.Score == 0 {
   126  			// Node doesn't implement EIP-868.
   127  			log.Debug("Skipping node", "id", n.ID())
   128  			return
   129  		}
   130  		node.Score /= 2
   131  	} else {
   132  		node.N = nn
   133  		node.Seq = nn.Seq()
   134  		node.Score++
   135  		if node.FirstResponse.IsZero() {
   136  			node.FirstResponse = node.LastCheck
   137  		}
   138  		node.LastResponse = node.LastCheck
   139  	}
   140  
   141  	// Store/update node in output set.
   142  	if node.Score <= 0 {
   143  		log.Info("Removing node", "id", n.ID())
   144  		delete(c.output, n.ID())
   145  	} else {
   146  		log.Info("Updating node", "id", n.ID(), "seq", n.Seq(), "score", node.Score)
   147  		c.output[n.ID()] = node
   148  	}
   149  }
   150  
   151  func truncNow() time.Time {
   152  	return time.Now().UTC().Truncate(1 * time.Second)
   153  }