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 }