github.com/dim4egster/coreth@v0.10.2/peer/peer_tracker.go (about)

     1  // (c) 2019-2022, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package peer
     5  
     6  import (
     7  	"math"
     8  	"math/rand"
     9  	"time"
    10  
    11  	"github.com/dim4egster/qmallgo/ids"
    12  	utils_math "github.com/dim4egster/qmallgo/utils/math"
    13  	"github.com/dim4egster/qmallgo/version"
    14  
    15  	"github.com/ethereum/go-ethereum/log"
    16  
    17  	"github.com/dim4egster/coreth/metrics"
    18  )
    19  
    20  const (
    21  	bandwidthHalflife = 5 * time.Minute
    22  
    23  	// controls how eagerly we connect to new peers vs. using
    24  	// peers with known good response bandwidth.
    25  	desiredMinResponsivePeers = 20
    26  	newPeerConnectFactor      = 0.1
    27  
    28  	// controls how often we prefer a random responsive peer over the most
    29  	// performant peer.
    30  	randomPeerProbability = 0.2
    31  )
    32  
    33  // information we track on a given peer
    34  type peerInfo struct {
    35  	version   *version.Application
    36  	bandwidth utils_math.Averager
    37  }
    38  
    39  // peerTracker tracks the bandwidth of responses coming from peers,
    40  // preferring to contact peers with known good bandwidth, connecting
    41  // to new peers with an exponentially decaying probability.
    42  // Note: is not thread safe, caller must handle synchronization.
    43  type peerTracker struct {
    44  	peers                  map[ids.NodeID]*peerInfo // all peers we are connected to
    45  	numTrackedPeers        metrics.Gauge
    46  	trackedPeers           ids.NodeIDSet // peers that we have sent a request to
    47  	numResponsivePeers     metrics.Gauge
    48  	responsivePeers        ids.NodeIDSet           // peers that responded to the last request they were sent
    49  	bandwidthHeap          utils_math.AveragerHeap // tracks bandwidth peers are responding with
    50  	averageBandwidthMetric metrics.GaugeFloat64
    51  	averageBandwidth       utils_math.Averager
    52  }
    53  
    54  func NewPeerTracker() *peerTracker {
    55  	return &peerTracker{
    56  		peers:                  make(map[ids.NodeID]*peerInfo),
    57  		numTrackedPeers:        metrics.GetOrRegisterGauge("net_tracked_peers", nil),
    58  		trackedPeers:           make(ids.NodeIDSet),
    59  		numResponsivePeers:     metrics.GetOrRegisterGauge("net_responsive_peers", nil),
    60  		responsivePeers:        make(ids.NodeIDSet),
    61  		bandwidthHeap:          utils_math.NewMaxAveragerHeap(),
    62  		averageBandwidthMetric: metrics.GetOrRegisterGaugeFloat64("net_average_bandwidth", nil),
    63  		averageBandwidth:       utils_math.NewAverager(0, bandwidthHalflife, time.Now()),
    64  	}
    65  }
    66  
    67  // shouldTrackNewPeer returns true if we are not connected to enough peers.
    68  // otherwise returns true probabilistically based on the number of tracked peers.
    69  func (p *peerTracker) shouldTrackNewPeer() bool {
    70  	numResponsivePeers := p.responsivePeers.Len()
    71  	if numResponsivePeers < desiredMinResponsivePeers {
    72  		return true
    73  	}
    74  	if len(p.trackedPeers) >= len(p.peers) {
    75  		// already tracking all the peers
    76  		return false
    77  	}
    78  	newPeerProbability := math.Exp(-float64(numResponsivePeers) * newPeerConnectFactor)
    79  	return rand.Float64() < newPeerProbability
    80  }
    81  
    82  // getResponsivePeer returns a random [ids.NodeID] of a peer that has responded
    83  // to a request.
    84  func (p *peerTracker) getResponsivePeer() (ids.NodeID, utils_math.Averager, bool) {
    85  	nodeID, ok := p.responsivePeers.Peek()
    86  	if !ok {
    87  		return ids.NodeID{}, nil, false
    88  	}
    89  	averager, ok := p.bandwidthHeap.Remove(nodeID)
    90  	if ok {
    91  		return nodeID, averager, true
    92  	}
    93  	peer := p.peers[nodeID]
    94  	return nodeID, peer.bandwidth, true
    95  }
    96  
    97  func (p *peerTracker) GetAnyPeer(minVersion *version.Application) (ids.NodeID, bool) {
    98  	if p.shouldTrackNewPeer() {
    99  		for nodeID := range p.peers {
   100  			// if minVersion is specified and peer's version is less, skip
   101  			if minVersion != nil && p.peers[nodeID].version.Compare(minVersion) < 0 {
   102  				continue
   103  			}
   104  			// skip peers already tracked
   105  			if p.trackedPeers.Contains(nodeID) {
   106  				continue
   107  			}
   108  			log.Debug("peer tracking: connecting to new peer", "trackedPeers", len(p.trackedPeers), "nodeID", nodeID)
   109  			return nodeID, true
   110  		}
   111  	}
   112  	var (
   113  		nodeID   ids.NodeID
   114  		ok       bool
   115  		random   bool
   116  		averager utils_math.Averager
   117  	)
   118  	if rand.Float64() < randomPeerProbability {
   119  		random = true
   120  		nodeID, averager, ok = p.getResponsivePeer()
   121  	} else {
   122  		nodeID, averager, ok = p.bandwidthHeap.Pop()
   123  	}
   124  	if ok {
   125  		log.Debug("peer tracking: popping peer", "nodeID", nodeID, "bandwidth", averager.Read(), "random", random)
   126  		return nodeID, true
   127  	}
   128  	// if no nodes found in the bandwidth heap, return a tracked node at random
   129  	return p.trackedPeers.Peek()
   130  }
   131  
   132  func (p *peerTracker) TrackPeer(nodeID ids.NodeID) {
   133  	p.trackedPeers.Add(nodeID)
   134  	p.numTrackedPeers.Update(int64(p.trackedPeers.Len()))
   135  }
   136  
   137  func (p *peerTracker) TrackBandwidth(nodeID ids.NodeID, bandwidth float64) {
   138  	peer := p.peers[nodeID]
   139  	if peer == nil {
   140  		// we're not connected to this peer, nothing to do here
   141  		log.Debug("tracking bandwidth for untracked peer", "nodeID", nodeID)
   142  		return
   143  	}
   144  
   145  	now := time.Now()
   146  	if peer.bandwidth == nil {
   147  		peer.bandwidth = utils_math.NewAverager(bandwidth, bandwidthHalflife, now)
   148  	} else {
   149  		peer.bandwidth.Observe(bandwidth, now)
   150  	}
   151  	p.bandwidthHeap.Add(nodeID, peer.bandwidth)
   152  
   153  	if bandwidth == 0 {
   154  		p.responsivePeers.Remove(nodeID)
   155  	} else {
   156  		p.responsivePeers.Add(nodeID)
   157  		p.averageBandwidth.Observe(bandwidth, now)
   158  		p.averageBandwidthMetric.Update(p.averageBandwidth.Read())
   159  	}
   160  	p.numResponsivePeers.Update(int64(p.responsivePeers.Len()))
   161  }
   162  
   163  // Connected should be called when [nodeID] connects to this node
   164  func (p *peerTracker) Connected(nodeID ids.NodeID, nodeVersion *version.Application) {
   165  	if peer := p.peers[nodeID]; peer != nil {
   166  		// Peer is already connected, update the version if it has changed.
   167  		// Log a warning message since the consensus engine should never call Connected on a peer
   168  		// that we have already marked as Connected.
   169  		if nodeVersion.Compare(peer.version) != 0 {
   170  			p.peers[nodeID] = &peerInfo{
   171  				version:   nodeVersion,
   172  				bandwidth: peer.bandwidth,
   173  			}
   174  			log.Warn("updating node version of already connected peer", "nodeID", nodeID, "storedVersion", peer.version, "nodeVersion", nodeVersion)
   175  		} else {
   176  			log.Warn("ignoring peer connected event for already connected peer with identical version", "nodeID", nodeID)
   177  		}
   178  		return
   179  	}
   180  
   181  	p.peers[nodeID] = &peerInfo{
   182  		version: nodeVersion,
   183  	}
   184  }
   185  
   186  // Disconnected should be called when [nodeID] disconnects from this node
   187  func (p *peerTracker) Disconnected(nodeID ids.NodeID) {
   188  	p.bandwidthHeap.Remove(nodeID)
   189  	p.trackedPeers.Remove(nodeID)
   190  	p.numTrackedPeers.Update(int64(p.trackedPeers.Len()))
   191  	p.responsivePeers.Remove(nodeID)
   192  	p.numResponsivePeers.Update(int64(p.responsivePeers.Len()))
   193  	delete(p.peers, nodeID)
   194  }
   195  
   196  // Size returns the number of peers the node is connected to
   197  func (p *peerTracker) Size() int {
   198  	return len(p.peers)
   199  }