github.com/nspcc-dev/neo-go@v0.105.2-0.20240517133400-6be757af3eba/pkg/network/discovery.go (about) 1 package network 2 3 import ( 4 "math" 5 "math/rand" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/nspcc-dev/neo-go/pkg/network/capability" 11 ) 12 13 const ( 14 maxPoolSize = 10000 15 connRetries = 3 16 ) 17 18 var ( 19 // Maximum waiting time before connection attempt. 20 tryMaxWait = time.Second / 2 21 ) 22 23 // Discoverer is an interface that is responsible for maintaining 24 // a healthy connection pool. 25 type Discoverer interface { 26 BackFill(...string) 27 GetFanOut() int 28 NetworkSize() int 29 PoolCount() int 30 RequestRemote(int) 31 RegisterSelf(AddressablePeer) 32 RegisterGood(AddressablePeer) 33 RegisterConnected(AddressablePeer) 34 UnregisterConnected(AddressablePeer, bool) 35 UnconnectedPeers() []string 36 BadPeers() []string 37 GoodPeers() []AddressWithCapabilities 38 } 39 40 // AddressWithCapabilities represents a node address with its capabilities. 41 type AddressWithCapabilities struct { 42 Address string 43 Capabilities capability.Capabilities 44 } 45 46 // DefaultDiscovery default implementation of the Discoverer interface. 47 type DefaultDiscovery struct { 48 seeds map[string]string 49 transport Transporter 50 lock sync.RWMutex 51 dialTimeout time.Duration 52 badAddrs map[string]bool 53 connectedAddrs map[string]bool 54 handshakedAddrs map[string]bool 55 goodAddrs map[string]capability.Capabilities 56 unconnectedAddrs map[string]int 57 attempted map[string]bool 58 outstanding int32 59 optimalFanOut int32 60 networkSize int32 61 requestCh chan int 62 } 63 64 // NewDefaultDiscovery returns a new DefaultDiscovery. 65 func NewDefaultDiscovery(addrs []string, dt time.Duration, ts Transporter) *DefaultDiscovery { 66 var seeds = make(map[string]string) 67 for i := range addrs { 68 seeds[addrs[i]] = "" 69 } 70 d := &DefaultDiscovery{ 71 seeds: seeds, 72 transport: ts, 73 dialTimeout: dt, 74 badAddrs: make(map[string]bool), 75 connectedAddrs: make(map[string]bool), 76 handshakedAddrs: make(map[string]bool), 77 goodAddrs: make(map[string]capability.Capabilities), 78 unconnectedAddrs: make(map[string]int), 79 attempted: make(map[string]bool), 80 requestCh: make(chan int), 81 } 82 return d 83 } 84 85 func newDefaultDiscovery(addrs []string, dt time.Duration, ts Transporter) Discoverer { 86 return NewDefaultDiscovery(addrs, dt, ts) 87 } 88 89 // BackFill implements the Discoverer interface and will backfill 90 // the pool with the given addresses. 91 func (d *DefaultDiscovery) BackFill(addrs ...string) { 92 d.lock.Lock() 93 d.backfill(addrs...) 94 d.lock.Unlock() 95 } 96 97 func (d *DefaultDiscovery) backfill(addrs ...string) { 98 for _, addr := range addrs { 99 if d.badAddrs[addr] || d.connectedAddrs[addr] || d.handshakedAddrs[addr] || 100 d.unconnectedAddrs[addr] > 0 { 101 continue 102 } 103 d.pushToPoolOrDrop(addr) 104 } 105 d.updateNetSize() 106 } 107 108 // PoolCount returns the number of the available node addresses. 109 func (d *DefaultDiscovery) PoolCount() int { 110 d.lock.RLock() 111 defer d.lock.RUnlock() 112 return d.poolCount() 113 } 114 115 func (d *DefaultDiscovery) poolCount() int { 116 return len(d.unconnectedAddrs) 117 } 118 119 // pushToPoolOrDrop tries to push the address given into the pool, but if the pool 120 // is already full, it just drops it. 121 func (d *DefaultDiscovery) pushToPoolOrDrop(addr string) { 122 if len(d.unconnectedAddrs) < maxPoolSize { 123 d.unconnectedAddrs[addr] = connRetries 124 } 125 } 126 127 // RequestRemote tries to establish a connection with n nodes. 128 func (d *DefaultDiscovery) RequestRemote(requested int) { 129 outstanding := int(atomic.LoadInt32(&d.outstanding)) 130 requested -= outstanding 131 for ; requested > 0; requested-- { 132 var nextAddr string 133 d.lock.Lock() 134 for addr := range d.unconnectedAddrs { 135 if !d.connectedAddrs[addr] && !d.handshakedAddrs[addr] && !d.attempted[addr] { 136 nextAddr = addr 137 break 138 } 139 } 140 141 if nextAddr == "" { 142 // Empty pool, try seeds. 143 for addr, ip := range d.seeds { 144 if ip == "" && !d.attempted[addr] { 145 nextAddr = addr 146 break 147 } 148 } 149 } 150 if nextAddr == "" { 151 d.lock.Unlock() 152 // The pool is empty, but all seed nodes are already connected (or attempted), 153 // we can end up in an infinite loop here, so drop the request. 154 break 155 } 156 d.attempted[nextAddr] = true 157 d.lock.Unlock() 158 atomic.AddInt32(&d.outstanding, 1) 159 go d.tryAddress(nextAddr) 160 } 161 } 162 163 // RegisterSelf registers the given Peer as a bad one, because it's our own node. 164 func (d *DefaultDiscovery) RegisterSelf(p AddressablePeer) { 165 var connaddr = p.ConnectionAddr() 166 d.lock.Lock() 167 delete(d.connectedAddrs, connaddr) 168 d.registerBad(connaddr, true) 169 d.registerBad(p.PeerAddr().String(), true) 170 d.lock.Unlock() 171 } 172 173 func (d *DefaultDiscovery) registerBad(addr string, force bool) { 174 _, isSeed := d.seeds[addr] 175 if isSeed { 176 if !force { 177 d.seeds[addr] = "" 178 } else { 179 d.seeds[addr] = "forever" // That's our own address, so never try connecting to it. 180 } 181 } else { 182 d.unconnectedAddrs[addr]-- 183 if d.unconnectedAddrs[addr] <= 0 || force { 184 d.badAddrs[addr] = true 185 delete(d.unconnectedAddrs, addr) 186 delete(d.goodAddrs, addr) 187 } 188 } 189 d.updateNetSize() 190 } 191 192 // UnconnectedPeers returns all addresses of unconnected addrs. 193 func (d *DefaultDiscovery) UnconnectedPeers() []string { 194 d.lock.RLock() 195 addrs := make([]string, 0, len(d.unconnectedAddrs)) 196 for addr := range d.unconnectedAddrs { 197 addrs = append(addrs, addr) 198 } 199 d.lock.RUnlock() 200 return addrs 201 } 202 203 // BadPeers returns all addresses of bad addrs. 204 func (d *DefaultDiscovery) BadPeers() []string { 205 d.lock.RLock() 206 addrs := make([]string, 0, len(d.badAddrs)) 207 for addr := range d.badAddrs { 208 addrs = append(addrs, addr) 209 } 210 d.lock.RUnlock() 211 return addrs 212 } 213 214 // GoodPeers returns all addresses of known good peers (that at least once 215 // succeeded handshaking with us). 216 func (d *DefaultDiscovery) GoodPeers() []AddressWithCapabilities { 217 d.lock.RLock() 218 addrs := make([]AddressWithCapabilities, 0, len(d.goodAddrs)) 219 for addr, cap := range d.goodAddrs { 220 addrs = append(addrs, AddressWithCapabilities{ 221 Address: addr, 222 Capabilities: cap, 223 }) 224 } 225 d.lock.RUnlock() 226 return addrs 227 } 228 229 // RegisterGood registers a known good connected peer that has passed 230 // handshake successfully. 231 func (d *DefaultDiscovery) RegisterGood(p AddressablePeer) { 232 var s = p.PeerAddr().String() 233 d.lock.Lock() 234 d.handshakedAddrs[s] = true 235 d.goodAddrs[s] = p.Version().Capabilities 236 delete(d.badAddrs, s) 237 d.lock.Unlock() 238 } 239 240 // UnregisterConnected tells the discoverer that this peer is no longer 241 // connected, but it is still considered a good one. 242 func (d *DefaultDiscovery) UnregisterConnected(p AddressablePeer, duplicate bool) { 243 var ( 244 peeraddr = p.PeerAddr().String() 245 connaddr = p.ConnectionAddr() 246 ) 247 d.lock.Lock() 248 delete(d.connectedAddrs, connaddr) 249 if !duplicate { 250 for addr, ip := range d.seeds { 251 if ip == peeraddr { 252 d.seeds[addr] = "" 253 break 254 } 255 } 256 delete(d.handshakedAddrs, peeraddr) 257 if _, ok := d.goodAddrs[peeraddr]; ok { 258 d.backfill(peeraddr) 259 } 260 } 261 d.lock.Unlock() 262 } 263 264 // RegisterConnected tells discoverer that the given peer is now connected. 265 func (d *DefaultDiscovery) RegisterConnected(p AddressablePeer) { 266 var addr = p.ConnectionAddr() 267 d.lock.Lock() 268 d.registerConnected(addr) 269 d.lock.Unlock() 270 } 271 272 func (d *DefaultDiscovery) registerConnected(addr string) { 273 delete(d.unconnectedAddrs, addr) 274 d.connectedAddrs[addr] = true 275 d.updateNetSize() 276 } 277 278 // GetFanOut returns the optimal number of nodes to broadcast packets to. 279 func (d *DefaultDiscovery) GetFanOut() int { 280 return int(atomic.LoadInt32(&d.optimalFanOut)) 281 } 282 283 // NetworkSize returns the estimated network size. 284 func (d *DefaultDiscovery) NetworkSize() int { 285 return int(atomic.LoadInt32(&d.networkSize)) 286 } 287 288 // updateNetSize updates network size estimation metric. Must be called under read lock. 289 func (d *DefaultDiscovery) updateNetSize() { 290 var netsize = len(d.handshakedAddrs) + len(d.unconnectedAddrs) + 1 // 1 for the node itself. 291 var fanOut = 2.5 * math.Log(float64(netsize-1)) // -1 for the number of potential peers. 292 if netsize == 2 { // log(1) == 0. 293 fanOut = 1 // But we still want to push messages to the peer. 294 } 295 296 atomic.StoreInt32(&d.optimalFanOut, int32(fanOut+0.5)) // Truncating conversion, hence +0.5. 297 atomic.StoreInt32(&d.networkSize, int32(netsize)) 298 updateNetworkSizeMetric(netsize) 299 updatePoolCountMetric(d.poolCount()) 300 } 301 302 func (d *DefaultDiscovery) tryAddress(addr string) { 303 var tout = rand.Int63n(int64(tryMaxWait)) 304 time.Sleep(time.Duration(tout)) // Have a sleep before working hard. 305 p, err := d.transport.Dial(addr, d.dialTimeout) 306 atomic.AddInt32(&d.outstanding, -1) 307 d.lock.Lock() 308 delete(d.attempted, addr) 309 if err == nil { 310 if _, ok := d.seeds[addr]; ok { 311 d.seeds[addr] = p.PeerAddr().String() 312 } 313 d.registerConnected(addr) 314 } else { 315 d.registerBad(addr, false) 316 } 317 d.lock.Unlock() 318 if err != nil { 319 time.Sleep(d.dialTimeout) 320 d.RequestRemote(1) 321 } 322 }