2018-03-14 09:36:59 +00:00
|
|
|
package network
|
|
|
|
|
|
|
|
import (
|
2022-10-12 19:57:49 +00:00
|
|
|
"math"
|
2019-10-28 04:25:52 +00:00
|
|
|
"sync"
|
2022-10-12 19:57:49 +00:00
|
|
|
"sync/atomic"
|
2018-03-14 09:36:59 +00:00
|
|
|
"time"
|
2020-05-22 09:59:18 +00:00
|
|
|
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/network/capability"
|
2018-03-14 09:36:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
maxPoolSize = 200
|
2019-09-12 13:29:50 +00:00
|
|
|
connRetries = 3
|
2018-03-14 09:36:59 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Discoverer is an interface that is responsible for maintaining
|
2019-02-13 18:01:10 +00:00
|
|
|
// a healthy connection pool.
|
2018-03-14 09:36:59 +00:00
|
|
|
type Discoverer interface {
|
|
|
|
BackFill(...string)
|
2020-02-24 12:39:31 +00:00
|
|
|
Close()
|
2022-10-12 19:57:49 +00:00
|
|
|
GetFanOut() int
|
2018-03-14 09:36:59 +00:00
|
|
|
PoolCount() int
|
|
|
|
RequestRemote(int)
|
2018-03-23 20:36:59 +00:00
|
|
|
RegisterBadAddr(string)
|
2020-05-22 09:59:18 +00:00
|
|
|
RegisterGoodAddr(string, capability.Capabilities)
|
2020-01-28 16:10:13 +00:00
|
|
|
RegisterConnectedAddr(string)
|
2019-09-13 16:51:58 +00:00
|
|
|
UnregisterConnectedAddr(string)
|
2018-03-23 20:36:59 +00:00
|
|
|
UnconnectedPeers() []string
|
|
|
|
BadPeers() []string
|
2020-05-22 09:59:18 +00:00
|
|
|
GoodPeers() []AddressWithCapabilities
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// AddressWithCapabilities represents a node address with its capabilities.
|
2020-05-22 09:59:18 +00:00
|
|
|
type AddressWithCapabilities struct {
|
|
|
|
Address string
|
|
|
|
Capabilities capability.Capabilities
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
|
2018-03-23 20:36:59 +00:00
|
|
|
// DefaultDiscovery default implementation of the Discoverer interface.
|
2018-03-14 09:36:59 +00:00
|
|
|
type DefaultDiscovery struct {
|
2020-10-13 13:30:10 +00:00
|
|
|
seeds []string
|
2018-03-23 20:36:59 +00:00
|
|
|
transport Transporter
|
2019-10-28 04:25:52 +00:00
|
|
|
lock sync.RWMutex
|
2020-03-10 12:40:23 +00:00
|
|
|
closeMtx sync.RWMutex
|
2018-03-23 20:36:59 +00:00
|
|
|
dialTimeout time.Duration
|
|
|
|
badAddrs map[string]bool
|
2019-09-13 16:51:58 +00:00
|
|
|
connectedAddrs map[string]bool
|
2020-05-22 09:59:18 +00:00
|
|
|
goodAddrs map[string]capability.Capabilities
|
2019-09-12 13:29:50 +00:00
|
|
|
unconnectedAddrs map[string]int
|
2021-03-26 09:26:45 +00:00
|
|
|
attempted map[string]bool
|
2022-10-12 19:57:49 +00:00
|
|
|
optimalFanOut int32
|
2020-02-24 12:39:31 +00:00
|
|
|
isDead bool
|
2018-03-23 20:36:59 +00:00
|
|
|
requestCh chan int
|
|
|
|
pool chan string
|
2022-08-19 19:23:47 +00:00
|
|
|
runExit chan struct{}
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewDefaultDiscovery returns a new DefaultDiscovery.
|
2020-10-13 13:30:10 +00:00
|
|
|
func NewDefaultDiscovery(addrs []string, dt time.Duration, ts Transporter) *DefaultDiscovery {
|
2018-03-14 09:36:59 +00:00
|
|
|
d := &DefaultDiscovery{
|
2020-10-13 13:30:10 +00:00
|
|
|
seeds: addrs,
|
2018-03-23 20:36:59 +00:00
|
|
|
transport: ts,
|
|
|
|
dialTimeout: dt,
|
|
|
|
badAddrs: make(map[string]bool),
|
2019-09-13 16:51:58 +00:00
|
|
|
connectedAddrs: make(map[string]bool),
|
2020-05-22 09:59:18 +00:00
|
|
|
goodAddrs: make(map[string]capability.Capabilities),
|
2019-09-12 13:29:50 +00:00
|
|
|
unconnectedAddrs: make(map[string]int),
|
2021-03-26 09:26:45 +00:00
|
|
|
attempted: make(map[string]bool),
|
2018-03-23 20:36:59 +00:00
|
|
|
requestCh: make(chan int),
|
|
|
|
pool: make(chan string, maxPoolSize),
|
2022-08-19 19:23:47 +00:00
|
|
|
runExit: make(chan struct{}),
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
go d.run()
|
|
|
|
return d
|
|
|
|
}
|
|
|
|
|
2020-12-07 09:52:19 +00:00
|
|
|
func newDefaultDiscovery(addrs []string, dt time.Duration, ts Transporter) Discoverer {
|
|
|
|
return NewDefaultDiscovery(addrs, dt, ts)
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// BackFill implements the Discoverer interface and will backfill
|
2018-03-14 09:36:59 +00:00
|
|
|
// the pool with the given addresses.
|
|
|
|
func (d *DefaultDiscovery) BackFill(addrs ...string) {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Lock()
|
2018-03-14 09:36:59 +00:00
|
|
|
for _, addr := range addrs {
|
2019-10-28 04:25:52 +00:00
|
|
|
if d.badAddrs[addr] || d.connectedAddrs[addr] ||
|
|
|
|
d.unconnectedAddrs[addr] > 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
d.unconnectedAddrs[addr] = connRetries
|
|
|
|
d.pushToPoolOrDrop(addr)
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
2022-10-12 19:29:55 +00:00
|
|
|
d.updateNetSize()
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Unlock()
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// PoolCount returns the number of the available node addresses.
|
2018-03-14 09:36:59 +00:00
|
|
|
func (d *DefaultDiscovery) PoolCount() int {
|
|
|
|
return len(d.pool)
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// pushToPoolOrDrop tries to push the address given into the pool, but if the pool
|
2021-05-12 20:17:03 +00:00
|
|
|
// is already full, it just drops it.
|
2019-09-12 13:47:08 +00:00
|
|
|
func (d *DefaultDiscovery) pushToPoolOrDrop(addr string) {
|
|
|
|
select {
|
|
|
|
case d.pool <- addr:
|
2019-10-29 17:51:17 +00:00
|
|
|
updatePoolCountMetric(d.PoolCount())
|
2019-09-12 13:47:08 +00:00
|
|
|
// ok, queued
|
|
|
|
default:
|
|
|
|
// whatever
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-10-22 14:56:03 +00:00
|
|
|
// RequestRemote tries to establish a connection with n nodes.
|
2018-03-14 09:36:59 +00:00
|
|
|
func (d *DefaultDiscovery) RequestRemote(n int) {
|
2020-03-10 12:40:23 +00:00
|
|
|
d.closeMtx.RLock()
|
2020-02-24 12:39:31 +00:00
|
|
|
if !d.isDead {
|
|
|
|
d.requestCh <- n
|
|
|
|
}
|
2020-03-10 12:40:23 +00:00
|
|
|
d.closeMtx.RUnlock()
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
|
2018-03-23 20:36:59 +00:00
|
|
|
// RegisterBadAddr registers the given address as a bad address.
|
|
|
|
func (d *DefaultDiscovery) RegisterBadAddr(addr string) {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Lock()
|
|
|
|
d.unconnectedAddrs[addr]--
|
|
|
|
if d.unconnectedAddrs[addr] > 0 {
|
|
|
|
d.pushToPoolOrDrop(addr)
|
|
|
|
} else {
|
|
|
|
d.badAddrs[addr] = true
|
|
|
|
delete(d.unconnectedAddrs, addr)
|
2020-10-13 11:16:06 +00:00
|
|
|
delete(d.goodAddrs, addr)
|
2019-10-28 04:25:52 +00:00
|
|
|
}
|
2022-10-12 19:29:55 +00:00
|
|
|
d.updateNetSize()
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Unlock()
|
2018-03-23 20:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// UnconnectedPeers returns all addresses of unconnected addrs.
|
|
|
|
func (d *DefaultDiscovery) UnconnectedPeers() []string {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.RLock()
|
2019-02-09 15:53:58 +00:00
|
|
|
addrs := make([]string, 0, len(d.unconnectedAddrs))
|
2018-03-23 20:36:59 +00:00
|
|
|
for addr := range d.unconnectedAddrs {
|
|
|
|
addrs = append(addrs, addr)
|
|
|
|
}
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.RUnlock()
|
2018-03-23 20:36:59 +00:00
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
|
|
|
// BadPeers returns all addresses of bad addrs.
|
|
|
|
func (d *DefaultDiscovery) BadPeers() []string {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.RLock()
|
2019-02-09 15:53:58 +00:00
|
|
|
addrs := make([]string, 0, len(d.badAddrs))
|
2018-03-23 20:36:59 +00:00
|
|
|
for addr := range d.badAddrs {
|
|
|
|
addrs = append(addrs, addr)
|
|
|
|
}
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.RUnlock()
|
2018-03-23 20:36:59 +00:00
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
2019-09-13 16:51:58 +00:00
|
|
|
// GoodPeers returns all addresses of known good peers (that at least once
|
2019-10-17 09:30:24 +00:00
|
|
|
// succeeded handshaking with us).
|
2020-05-22 09:59:18 +00:00
|
|
|
func (d *DefaultDiscovery) GoodPeers() []AddressWithCapabilities {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.RLock()
|
2020-05-22 09:59:18 +00:00
|
|
|
addrs := make([]AddressWithCapabilities, 0, len(d.goodAddrs))
|
|
|
|
for addr, cap := range d.goodAddrs {
|
|
|
|
addrs = append(addrs, AddressWithCapabilities{
|
|
|
|
Address: addr,
|
|
|
|
Capabilities: cap,
|
|
|
|
})
|
2019-09-13 16:51:58 +00:00
|
|
|
}
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.RUnlock()
|
2019-09-13 16:51:58 +00:00
|
|
|
return addrs
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// RegisterGoodAddr registers a known good connected address that has passed
|
2019-10-17 09:30:24 +00:00
|
|
|
// handshake successfully.
|
2020-05-22 09:59:18 +00:00
|
|
|
func (d *DefaultDiscovery) RegisterGoodAddr(s string, c capability.Capabilities) {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Lock()
|
2020-05-22 09:59:18 +00:00
|
|
|
d.goodAddrs[s] = c
|
2020-10-13 11:16:06 +00:00
|
|
|
delete(d.badAddrs, s)
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Unlock()
|
2019-09-13 16:51:58 +00:00
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// UnregisterConnectedAddr tells the discoverer that this address is no longer
|
|
|
|
// connected, but it is still considered a good one.
|
2019-09-13 16:51:58 +00:00
|
|
|
func (d *DefaultDiscovery) UnregisterConnectedAddr(s string) {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Lock()
|
|
|
|
delete(d.connectedAddrs, s)
|
2022-10-12 19:29:55 +00:00
|
|
|
d.updateNetSize()
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Unlock()
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// RegisterConnectedAddr tells discoverer that the given address is now connected.
|
2020-01-28 16:10:13 +00:00
|
|
|
func (d *DefaultDiscovery) RegisterConnectedAddr(addr string) {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Lock()
|
|
|
|
delete(d.unconnectedAddrs, addr)
|
|
|
|
d.connectedAddrs[addr] = true
|
2022-10-12 19:29:55 +00:00
|
|
|
d.updateNetSize()
|
2019-10-28 04:25:52 +00:00
|
|
|
d.lock.Unlock()
|
2019-09-13 16:51:58 +00:00
|
|
|
}
|
|
|
|
|
2022-10-12 19:57:49 +00:00
|
|
|
// GetFanOut returns the optimal number of nodes to broadcast packets to.
|
|
|
|
func (d *DefaultDiscovery) GetFanOut() int {
|
|
|
|
return int(atomic.LoadInt32(&d.optimalFanOut))
|
|
|
|
}
|
|
|
|
|
2022-10-12 19:29:55 +00:00
|
|
|
// updateNetSize updates network size estimation metric. Must be called under read lock.
|
|
|
|
func (d *DefaultDiscovery) updateNetSize() {
|
2022-10-12 19:57:49 +00:00
|
|
|
var netsize = len(d.connectedAddrs) + len(d.unconnectedAddrs) + 1 // 1 for the node itself.
|
|
|
|
var fanOut = 2.5 * math.Log(float64(netsize-1)) // -1 for the number of potential peers.
|
|
|
|
if netsize == 2 { // log(1) == 0.
|
|
|
|
fanOut = 1 // But we still want to push messages to the peer.
|
|
|
|
}
|
|
|
|
|
|
|
|
atomic.StoreInt32(&d.optimalFanOut, int32(fanOut+0.5)) // Truncating conversion, hence +0.5.
|
|
|
|
updateNetworkSizeMetric(netsize)
|
2022-10-12 19:29:55 +00:00
|
|
|
}
|
|
|
|
|
2019-09-13 08:59:16 +00:00
|
|
|
func (d *DefaultDiscovery) tryAddress(addr string) {
|
2021-03-26 09:26:45 +00:00
|
|
|
err := d.transport.Dial(addr, d.dialTimeout)
|
|
|
|
d.lock.Lock()
|
|
|
|
delete(d.attempted, addr)
|
|
|
|
d.lock.Unlock()
|
|
|
|
if err != nil {
|
2019-10-28 04:25:52 +00:00
|
|
|
d.RegisterBadAddr(addr)
|
|
|
|
d.RequestRemote(1)
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-20 18:30:09 +00:00
|
|
|
// Close stops discoverer pool processing, which makes the discoverer almost useless.
|
2020-02-24 12:39:31 +00:00
|
|
|
func (d *DefaultDiscovery) Close() {
|
2020-03-10 12:40:23 +00:00
|
|
|
d.closeMtx.Lock()
|
2020-02-24 12:39:31 +00:00
|
|
|
d.isDead = true
|
2020-03-10 12:40:23 +00:00
|
|
|
d.closeMtx.Unlock()
|
2020-02-24 12:39:31 +00:00
|
|
|
select {
|
|
|
|
case <-d.requestCh: // Drain the channel if there is anything there.
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
close(d.requestCh)
|
2022-08-19 19:23:47 +00:00
|
|
|
<-d.runExit
|
2020-02-24 12:39:31 +00:00
|
|
|
}
|
|
|
|
|
2019-10-28 04:25:52 +00:00
|
|
|
// run is a goroutine that makes DefaultDiscovery process its queue to connect
|
|
|
|
// to other nodes.
|
|
|
|
func (d *DefaultDiscovery) run() {
|
2020-10-13 13:30:10 +00:00
|
|
|
var requested, oldRequest, r int
|
2020-02-24 12:39:31 +00:00
|
|
|
var ok bool
|
2019-09-12 13:19:18 +00:00
|
|
|
|
|
|
|
for {
|
2020-10-13 13:30:10 +00:00
|
|
|
if requested == 0 {
|
|
|
|
requested, ok = <-d.requestCh
|
|
|
|
}
|
|
|
|
oldRequest = requested
|
|
|
|
for ok && requested > 0 {
|
2019-09-12 13:19:18 +00:00
|
|
|
select {
|
2020-02-24 12:39:31 +00:00
|
|
|
case r, ok = <-d.requestCh:
|
2019-10-28 04:25:52 +00:00
|
|
|
if requested <= r {
|
2020-10-13 13:30:10 +00:00
|
|
|
requested = r
|
2019-09-12 13:19:18 +00:00
|
|
|
}
|
|
|
|
case addr := <-d.pool:
|
2019-10-29 17:51:17 +00:00
|
|
|
updatePoolCountMetric(d.PoolCount())
|
2021-03-26 09:26:45 +00:00
|
|
|
d.lock.Lock()
|
|
|
|
if !d.connectedAddrs[addr] && !d.attempted[addr] {
|
|
|
|
d.attempted[addr] = true
|
2019-09-13 16:51:58 +00:00
|
|
|
go d.tryAddress(addr)
|
2020-10-13 13:30:10 +00:00
|
|
|
requested--
|
|
|
|
}
|
2021-03-26 09:26:45 +00:00
|
|
|
d.lock.Unlock()
|
2020-10-13 13:30:10 +00:00
|
|
|
default: // Empty pool
|
2020-12-04 18:39:50 +00:00
|
|
|
var added int
|
2020-10-13 13:30:10 +00:00
|
|
|
d.lock.Lock()
|
|
|
|
for _, addr := range d.seeds {
|
|
|
|
if !d.connectedAddrs[addr] {
|
|
|
|
delete(d.badAddrs, addr)
|
|
|
|
d.unconnectedAddrs[addr] = connRetries
|
|
|
|
d.pushToPoolOrDrop(addr)
|
2020-12-04 18:39:50 +00:00
|
|
|
added++
|
2020-10-13 13:30:10 +00:00
|
|
|
}
|
2019-09-13 16:51:58 +00:00
|
|
|
}
|
2020-10-13 13:30:10 +00:00
|
|
|
d.lock.Unlock()
|
2020-12-04 18:39:50 +00:00
|
|
|
// The pool is empty, but all seed nodes are already connected,
|
|
|
|
// we can end up in an infinite loop here, so drop the request.
|
|
|
|
if added == 0 {
|
|
|
|
requested = 0
|
|
|
|
}
|
2019-09-12 13:19:18 +00:00
|
|
|
}
|
|
|
|
}
|
2020-02-24 12:39:31 +00:00
|
|
|
if !ok {
|
2022-08-19 19:23:47 +00:00
|
|
|
break
|
2020-02-24 12:39:31 +00:00
|
|
|
}
|
2020-10-13 13:30:10 +00:00
|
|
|
// Special case, no connections after all attempts.
|
|
|
|
d.lock.RLock()
|
|
|
|
connected := len(d.connectedAddrs)
|
|
|
|
d.lock.RUnlock()
|
|
|
|
if connected == 0 {
|
|
|
|
time.Sleep(d.dialTimeout)
|
|
|
|
requested = oldRequest
|
|
|
|
}
|
2019-09-12 13:19:18 +00:00
|
|
|
}
|
2022-08-19 19:23:47 +00:00
|
|
|
close(d.runExit)
|
2018-03-14 09:36:59 +00:00
|
|
|
}
|