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 }