github.com/Debrief-BC/go-debrief@v0.0.0-20200420203408-0c26ca968123/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/Debrief-BC/go-debrief/log" 23 "github.com/Debrief-BC/go-debrief/p2p/discover" 24 "github.com/Debrief-BC/go-debrief/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 defer timeoutTimer.Stop() 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 }