github.com/theQRL/go-zond@v0.1.1/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 "sync" 21 "sync/atomic" 22 "time" 23 24 "github.com/theQRL/go-zond/log" 25 "github.com/theQRL/go-zond/p2p/enode" 26 ) 27 28 type crawler struct { 29 input nodeSet 30 output nodeSet 31 disc resolver 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 mu sync.RWMutex 40 } 41 42 const ( 43 nodeRemoved = iota 44 nodeSkipRecent 45 nodeSkipIncompat 46 nodeAdded 47 nodeUpdated 48 ) 49 50 type resolver interface { 51 RequestENR(*enode.Node) (*enode.Node, error) 52 } 53 54 func newCrawler(input nodeSet, disc resolver, iters ...enode.Iterator) *crawler { 55 c := &crawler{ 56 input: input, 57 output: make(nodeSet, len(input)), 58 disc: disc, 59 iters: iters, 60 inputIter: enode.IterNodes(input.nodes()), 61 ch: make(chan *enode.Node), 62 closed: make(chan struct{}), 63 } 64 c.iters = append(c.iters, c.inputIter) 65 // Copy input to output initially. Any nodes that fail validation 66 // will be dropped from output during the run. 67 for id, n := range input { 68 c.output[id] = n 69 } 70 return c 71 } 72 73 func (c *crawler) run(timeout time.Duration, nthreads int) nodeSet { 74 var ( 75 timeoutTimer = time.NewTimer(timeout) 76 timeoutCh <-chan time.Time 77 statusTicker = time.NewTicker(time.Second * 8) 78 doneCh = make(chan enode.Iterator, len(c.iters)) 79 liveIters = len(c.iters) 80 ) 81 if nthreads < 1 { 82 nthreads = 1 83 } 84 defer timeoutTimer.Stop() 85 defer statusTicker.Stop() 86 for _, it := range c.iters { 87 go c.runIterator(doneCh, it) 88 } 89 var ( 90 added atomic.Uint64 91 updated atomic.Uint64 92 skipped atomic.Uint64 93 recent atomic.Uint64 94 removed atomic.Uint64 95 wg sync.WaitGroup 96 ) 97 wg.Add(nthreads) 98 for i := 0; i < nthreads; i++ { 99 go func() { 100 defer wg.Done() 101 for { 102 select { 103 case n := <-c.ch: 104 switch c.updateNode(n) { 105 case nodeSkipIncompat: 106 skipped.Add(1) 107 case nodeSkipRecent: 108 recent.Add(1) 109 case nodeRemoved: 110 removed.Add(1) 111 case nodeAdded: 112 added.Add(1) 113 default: 114 updated.Add(1) 115 } 116 case <-c.closed: 117 return 118 } 119 } 120 }() 121 } 122 123 loop: 124 for { 125 select { 126 case it := <-doneCh: 127 if it == c.inputIter { 128 // Enable timeout when we're done revalidating the input nodes. 129 log.Info("Revalidation of input set is done", "len", len(c.input)) 130 if timeout > 0 { 131 timeoutCh = timeoutTimer.C 132 } 133 } 134 if liveIters--; liveIters == 0 { 135 break loop 136 } 137 case <-timeoutCh: 138 break loop 139 case <-statusTicker.C: 140 log.Info("Crawling in progress", 141 "added", added.Load(), 142 "updated", updated.Load(), 143 "removed", removed.Load(), 144 "ignored(recent)", recent.Load(), 145 "ignored(incompatible)", skipped.Load()) 146 } 147 } 148 149 close(c.closed) 150 for _, it := range c.iters { 151 it.Close() 152 } 153 for ; liveIters > 0; liveIters-- { 154 <-doneCh 155 } 156 wg.Wait() 157 return c.output 158 } 159 160 func (c *crawler) runIterator(done chan<- enode.Iterator, it enode.Iterator) { 161 defer func() { done <- it }() 162 for it.Next() { 163 select { 164 case c.ch <- it.Node(): 165 case <-c.closed: 166 return 167 } 168 } 169 } 170 171 // updateNode updates the info about the given node, and returns a status 172 // about what changed 173 func (c *crawler) updateNode(n *enode.Node) int { 174 c.mu.RLock() 175 node, ok := c.output[n.ID()] 176 c.mu.RUnlock() 177 178 // Skip validation of recently-seen nodes. 179 if ok && time.Since(node.LastCheck) < c.revalidateInterval { 180 return nodeSkipRecent 181 } 182 183 // Request the node record. 184 status := nodeUpdated 185 node.LastCheck = truncNow() 186 if nn, err := c.disc.RequestENR(n); err != nil { 187 if node.Score == 0 { 188 // Node doesn't implement EIP-868. 189 log.Debug("Skipping node", "id", n.ID()) 190 return nodeSkipIncompat 191 } 192 node.Score /= 2 193 } else { 194 node.N = nn 195 node.Seq = nn.Seq() 196 node.Score++ 197 if node.FirstResponse.IsZero() { 198 node.FirstResponse = node.LastCheck 199 status = nodeAdded 200 } 201 node.LastResponse = node.LastCheck 202 } 203 // Store/update node in output set. 204 c.mu.Lock() 205 defer c.mu.Unlock() 206 if node.Score <= 0 { 207 log.Debug("Removing node", "id", n.ID()) 208 delete(c.output, n.ID()) 209 return nodeRemoved 210 } 211 log.Debug("Updating node", "id", n.ID(), "seq", n.Seq(), "score", node.Score) 212 c.output[n.ID()] = node 213 return status 214 } 215 216 func truncNow() time.Time { 217 return time.Now().UTC().Truncate(1 * time.Second) 218 }