github.com/jimmyx0x/go-ethereum@v1.10.28/p2p/discover/lookup.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser 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 // The go-ethereum library 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 Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package discover 18 19 import ( 20 "context" 21 "errors" 22 "time" 23 24 "github.com/ethereum/go-ethereum/p2p/enode" 25 ) 26 27 // lookup performs a network search for nodes close to the given target. It approaches the 28 // target by querying nodes that are closer to it on each iteration. The given target does 29 // not need to be an actual node identifier. 30 type lookup struct { 31 tab *Table 32 queryfunc func(*node) ([]*node, error) 33 replyCh chan []*node 34 cancelCh <-chan struct{} 35 asked, seen map[enode.ID]bool 36 result nodesByDistance 37 replyBuffer []*node 38 queries int 39 } 40 41 type queryFunc func(*node) ([]*node, error) 42 43 func newLookup(ctx context.Context, tab *Table, target enode.ID, q queryFunc) *lookup { 44 it := &lookup{ 45 tab: tab, 46 queryfunc: q, 47 asked: make(map[enode.ID]bool), 48 seen: make(map[enode.ID]bool), 49 result: nodesByDistance{target: target}, 50 replyCh: make(chan []*node, alpha), 51 cancelCh: ctx.Done(), 52 queries: -1, 53 } 54 // Don't query further if we hit ourself. 55 // Unlikely to happen often in practice. 56 it.asked[tab.self().ID()] = true 57 return it 58 } 59 60 // run runs the lookup to completion and returns the closest nodes found. 61 func (it *lookup) run() []*enode.Node { 62 for it.advance() { 63 } 64 return unwrapNodes(it.result.entries) 65 } 66 67 // advance advances the lookup until any new nodes have been found. 68 // It returns false when the lookup has ended. 69 func (it *lookup) advance() bool { 70 for it.startQueries() { 71 select { 72 case nodes := <-it.replyCh: 73 it.replyBuffer = it.replyBuffer[:0] 74 for _, n := range nodes { 75 if n != nil && !it.seen[n.ID()] { 76 it.seen[n.ID()] = true 77 it.result.push(n, bucketSize) 78 it.replyBuffer = append(it.replyBuffer, n) 79 } 80 } 81 it.queries-- 82 if len(it.replyBuffer) > 0 { 83 return true 84 } 85 case <-it.cancelCh: 86 it.shutdown() 87 } 88 } 89 return false 90 } 91 92 func (it *lookup) shutdown() { 93 for it.queries > 0 { 94 <-it.replyCh 95 it.queries-- 96 } 97 it.queryfunc = nil 98 it.replyBuffer = nil 99 } 100 101 func (it *lookup) startQueries() bool { 102 if it.queryfunc == nil { 103 return false 104 } 105 106 // The first query returns nodes from the local table. 107 if it.queries == -1 { 108 closest := it.tab.findnodeByID(it.result.target, bucketSize, false) 109 // Avoid finishing the lookup too quickly if table is empty. It'd be better to wait 110 // for the table to fill in this case, but there is no good mechanism for that 111 // yet. 112 if len(closest.entries) == 0 { 113 it.slowdown() 114 } 115 it.queries = 1 116 it.replyCh <- closest.entries 117 return true 118 } 119 120 // Ask the closest nodes that we haven't asked yet. 121 for i := 0; i < len(it.result.entries) && it.queries < alpha; i++ { 122 n := it.result.entries[i] 123 if !it.asked[n.ID()] { 124 it.asked[n.ID()] = true 125 it.queries++ 126 go it.query(n, it.replyCh) 127 } 128 } 129 // The lookup ends when no more nodes can be asked. 130 return it.queries > 0 131 } 132 133 func (it *lookup) slowdown() { 134 sleep := time.NewTimer(1 * time.Second) 135 defer sleep.Stop() 136 select { 137 case <-sleep.C: 138 case <-it.tab.closeReq: 139 } 140 } 141 142 func (it *lookup) query(n *node, reply chan<- []*node) { 143 fails := it.tab.db.FindFails(n.ID(), n.IP()) 144 r, err := it.queryfunc(n) 145 if errors.Is(err, errClosed) { 146 // Avoid recording failures on shutdown. 147 reply <- nil 148 return 149 } else if len(r) == 0 { 150 fails++ 151 it.tab.db.UpdateFindFails(n.ID(), n.IP(), fails) 152 // Remove the node from the local table if it fails to return anything useful too 153 // many times, but only if there are enough other nodes in the bucket. 154 dropped := false 155 if fails >= maxFindnodeFailures && it.tab.bucketLen(n.ID()) >= bucketSize/2 { 156 dropped = true 157 it.tab.delete(n) 158 } 159 it.tab.log.Trace("FINDNODE failed", "id", n.ID(), "failcount", fails, "dropped", dropped, "err", err) 160 } else if fails > 0 { 161 // Reset failure counter because it counts _consecutive_ failures. 162 it.tab.db.UpdateFindFails(n.ID(), n.IP(), 0) 163 } 164 165 // Grab as many nodes as possible. Some of them might not be alive anymore, but we'll 166 // just remove those again during revalidation. 167 for _, n := range r { 168 it.tab.addSeenNode(n) 169 } 170 reply <- r 171 } 172 173 // lookupIterator performs lookup operations and iterates over all seen nodes. 174 // When a lookup finishes, a new one is created through nextLookup. 175 type lookupIterator struct { 176 buffer []*node 177 nextLookup lookupFunc 178 ctx context.Context 179 cancel func() 180 lookup *lookup 181 } 182 183 type lookupFunc func(ctx context.Context) *lookup 184 185 func newLookupIterator(ctx context.Context, next lookupFunc) *lookupIterator { 186 ctx, cancel := context.WithCancel(ctx) 187 return &lookupIterator{ctx: ctx, cancel: cancel, nextLookup: next} 188 } 189 190 // Node returns the current node. 191 func (it *lookupIterator) Node() *enode.Node { 192 if len(it.buffer) == 0 { 193 return nil 194 } 195 return unwrapNode(it.buffer[0]) 196 } 197 198 // Next moves to the next node. 199 func (it *lookupIterator) Next() bool { 200 // Consume next node in buffer. 201 if len(it.buffer) > 0 { 202 it.buffer = it.buffer[1:] 203 } 204 // Advance the lookup to refill the buffer. 205 for len(it.buffer) == 0 { 206 if it.ctx.Err() != nil { 207 it.lookup = nil 208 it.buffer = nil 209 return false 210 } 211 if it.lookup == nil { 212 it.lookup = it.nextLookup(it.ctx) 213 continue 214 } 215 if !it.lookup.advance() { 216 it.lookup = nil 217 continue 218 } 219 it.buffer = it.lookup.replyBuffer 220 } 221 return true 222 } 223 224 // Close ends the iterator. 225 func (it *lookupIterator) Close() { 226 it.cancel() 227 }