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  }