github.com/core-coin/go-core/v2@v2.1.9/cmd/devp2p/crawl.go (about) 1 // Copyright 2019 by the Authors 2 // This file is part of go-core. 3 // 4 // go-core 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-core 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-core. If not, see <http://www.gnu.org/licenses/>. 16 17 package main 18 19 import ( 20 "time" 21 22 "github.com/core-coin/go-core/v2/log" 23 "github.com/core-coin/go-core/v2/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 CIP-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 }