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