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