github.com/MetalBlockchain/metalgo@v1.11.9/snow/engine/common/tracker/peers.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package tracker
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"sync"
    10  
    11  	"github.com/prometheus/client_golang/prometheus"
    12  
    13  	"github.com/MetalBlockchain/metalgo/ids"
    14  	"github.com/MetalBlockchain/metalgo/snow/validators"
    15  	"github.com/MetalBlockchain/metalgo/utils/crypto/bls"
    16  	"github.com/MetalBlockchain/metalgo/utils/set"
    17  	"github.com/MetalBlockchain/metalgo/version"
    18  )
    19  
    20  var (
    21  	_ Peers = (*lockedPeers)(nil)
    22  	_ Peers = (*meteredPeers)(nil)
    23  	_ Peers = (*peerData)(nil)
    24  )
    25  
    26  type Peers interface {
    27  	validators.SetCallbackListener
    28  	validators.Connector
    29  
    30  	// ConnectedWeight returns the currently connected stake weight
    31  	ConnectedWeight() uint64
    32  	// ConnectedPercent returns the currently connected stake percentage [0, 1]
    33  	ConnectedPercent() float64
    34  	// SampleValidator returns a randomly selected connected validator. If there
    35  	// are no currently connected validators then it will return false.
    36  	SampleValidator() (ids.NodeID, bool)
    37  }
    38  
    39  type lockedPeers struct {
    40  	lock  sync.RWMutex
    41  	peers Peers
    42  }
    43  
    44  func NewPeers() Peers {
    45  	return &lockedPeers{
    46  		peers: &peerData{
    47  			validators: make(map[ids.NodeID]uint64),
    48  		},
    49  	}
    50  }
    51  
    52  func (p *lockedPeers) OnValidatorAdded(nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) {
    53  	p.lock.Lock()
    54  	defer p.lock.Unlock()
    55  
    56  	p.peers.OnValidatorAdded(nodeID, pk, txID, weight)
    57  }
    58  
    59  func (p *lockedPeers) OnValidatorRemoved(nodeID ids.NodeID, weight uint64) {
    60  	p.lock.Lock()
    61  	defer p.lock.Unlock()
    62  
    63  	p.peers.OnValidatorRemoved(nodeID, weight)
    64  }
    65  
    66  func (p *lockedPeers) OnValidatorWeightChanged(nodeID ids.NodeID, oldWeight, newWeight uint64) {
    67  	p.lock.Lock()
    68  	defer p.lock.Unlock()
    69  
    70  	p.peers.OnValidatorWeightChanged(nodeID, oldWeight, newWeight)
    71  }
    72  
    73  func (p *lockedPeers) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error {
    74  	p.lock.Lock()
    75  	defer p.lock.Unlock()
    76  
    77  	return p.peers.Connected(ctx, nodeID, version)
    78  }
    79  
    80  func (p *lockedPeers) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
    81  	p.lock.Lock()
    82  	defer p.lock.Unlock()
    83  
    84  	return p.peers.Disconnected(ctx, nodeID)
    85  }
    86  
    87  func (p *lockedPeers) ConnectedWeight() uint64 {
    88  	p.lock.RLock()
    89  	defer p.lock.RUnlock()
    90  
    91  	return p.peers.ConnectedWeight()
    92  }
    93  
    94  func (p *lockedPeers) ConnectedPercent() float64 {
    95  	p.lock.RLock()
    96  	defer p.lock.RUnlock()
    97  
    98  	return p.peers.ConnectedPercent()
    99  }
   100  
   101  func (p *lockedPeers) SampleValidator() (ids.NodeID, bool) {
   102  	p.lock.RLock()
   103  	defer p.lock.RUnlock()
   104  
   105  	return p.peers.SampleValidator()
   106  }
   107  
   108  type meteredPeers struct {
   109  	Peers
   110  
   111  	percentConnected prometheus.Gauge
   112  	numValidators    prometheus.Gauge
   113  	totalWeight      prometheus.Gauge
   114  }
   115  
   116  func NewMeteredPeers(reg prometheus.Registerer) (Peers, error) {
   117  	percentConnected := prometheus.NewGauge(prometheus.GaugeOpts{
   118  		Name: "percent_connected",
   119  		Help: "Percent of connected stake",
   120  	})
   121  	totalWeight := prometheus.NewGauge(prometheus.GaugeOpts{
   122  		Name: "total_weight",
   123  		Help: "Total stake",
   124  	})
   125  	numValidators := prometheus.NewGauge(prometheus.GaugeOpts{
   126  		Name: "num_validators",
   127  		Help: "Total number of validators",
   128  	})
   129  	err := errors.Join(
   130  		reg.Register(percentConnected),
   131  		reg.Register(totalWeight),
   132  		reg.Register(numValidators),
   133  	)
   134  	return &lockedPeers{
   135  		peers: &meteredPeers{
   136  			Peers: &peerData{
   137  				validators: make(map[ids.NodeID]uint64),
   138  			},
   139  			percentConnected: percentConnected,
   140  			totalWeight:      totalWeight,
   141  			numValidators:    numValidators,
   142  		},
   143  	}, err
   144  }
   145  
   146  func (p *meteredPeers) OnValidatorAdded(nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) {
   147  	p.Peers.OnValidatorAdded(nodeID, pk, txID, weight)
   148  	p.numValidators.Inc()
   149  	p.totalWeight.Add(float64(weight))
   150  	p.percentConnected.Set(p.Peers.ConnectedPercent())
   151  }
   152  
   153  func (p *meteredPeers) OnValidatorRemoved(nodeID ids.NodeID, weight uint64) {
   154  	p.Peers.OnValidatorRemoved(nodeID, weight)
   155  	p.numValidators.Dec()
   156  	p.totalWeight.Sub(float64(weight))
   157  	p.percentConnected.Set(p.Peers.ConnectedPercent())
   158  }
   159  
   160  func (p *meteredPeers) OnValidatorWeightChanged(nodeID ids.NodeID, oldWeight, newWeight uint64) {
   161  	p.Peers.OnValidatorWeightChanged(nodeID, oldWeight, newWeight)
   162  	p.totalWeight.Sub(float64(oldWeight))
   163  	p.totalWeight.Add(float64(newWeight))
   164  	p.percentConnected.Set(p.Peers.ConnectedPercent())
   165  }
   166  
   167  func (p *meteredPeers) Connected(ctx context.Context, nodeID ids.NodeID, version *version.Application) error {
   168  	err := p.Peers.Connected(ctx, nodeID, version)
   169  	p.percentConnected.Set(p.Peers.ConnectedPercent())
   170  	return err
   171  }
   172  
   173  func (p *meteredPeers) Disconnected(ctx context.Context, nodeID ids.NodeID) error {
   174  	err := p.Peers.Disconnected(ctx, nodeID)
   175  	p.percentConnected.Set(p.Peers.ConnectedPercent())
   176  	return err
   177  }
   178  
   179  type peerData struct {
   180  	// validators maps nodeIDs to their current stake weight
   181  	validators map[ids.NodeID]uint64
   182  	// totalWeight is the total weight of all validators
   183  	totalWeight uint64
   184  	// connectedWeight contains the sum of all connected validator weights
   185  	connectedWeight uint64
   186  	// connectedValidators is the set of currently connected peers with a
   187  	// non-zero stake weight
   188  	connectedValidators set.Set[ids.NodeID]
   189  	// connectedPeers is the set of all connected peers
   190  	connectedPeers set.Set[ids.NodeID]
   191  }
   192  
   193  func (p *peerData) OnValidatorAdded(nodeID ids.NodeID, _ *bls.PublicKey, _ ids.ID, weight uint64) {
   194  	p.validators[nodeID] = weight
   195  	p.totalWeight += weight
   196  	if p.connectedPeers.Contains(nodeID) {
   197  		p.connectedWeight += weight
   198  		p.connectedValidators.Add(nodeID)
   199  	}
   200  }
   201  
   202  func (p *peerData) OnValidatorRemoved(nodeID ids.NodeID, weight uint64) {
   203  	delete(p.validators, nodeID)
   204  	p.totalWeight -= weight
   205  	if p.connectedPeers.Contains(nodeID) {
   206  		p.connectedWeight -= weight
   207  		p.connectedValidators.Remove(nodeID)
   208  	}
   209  }
   210  
   211  func (p *peerData) OnValidatorWeightChanged(nodeID ids.NodeID, oldWeight, newWeight uint64) {
   212  	p.validators[nodeID] = newWeight
   213  	p.totalWeight -= oldWeight
   214  	p.totalWeight += newWeight
   215  	if p.connectedPeers.Contains(nodeID) {
   216  		p.connectedWeight -= oldWeight
   217  		p.connectedWeight += newWeight
   218  	}
   219  }
   220  
   221  func (p *peerData) Connected(_ context.Context, nodeID ids.NodeID, _ *version.Application) error {
   222  	if weight, ok := p.validators[nodeID]; ok {
   223  		p.connectedWeight += weight
   224  		p.connectedValidators.Add(nodeID)
   225  	}
   226  	p.connectedPeers.Add(nodeID)
   227  	return nil
   228  }
   229  
   230  func (p *peerData) Disconnected(_ context.Context, nodeID ids.NodeID) error {
   231  	if weight, ok := p.validators[nodeID]; ok {
   232  		p.connectedWeight -= weight
   233  		p.connectedValidators.Remove(nodeID)
   234  	}
   235  	p.connectedPeers.Remove(nodeID)
   236  	return nil
   237  }
   238  
   239  func (p *peerData) ConnectedWeight() uint64 {
   240  	return p.connectedWeight
   241  }
   242  
   243  func (p *peerData) ConnectedPercent() float64 {
   244  	if p.totalWeight == 0 {
   245  		return 1
   246  	}
   247  	return float64(p.connectedWeight) / float64(p.totalWeight)
   248  }
   249  
   250  func (p *peerData) SampleValidator() (ids.NodeID, bool) {
   251  	return p.connectedValidators.Peek()
   252  }