github.com/MetalBlockchain/metalgo@v1.11.9/snow/validators/manager.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package validators 5 6 import ( 7 "errors" 8 "fmt" 9 "strings" 10 "sync" 11 12 "golang.org/x/exp/maps" 13 14 "github.com/MetalBlockchain/metalgo/ids" 15 "github.com/MetalBlockchain/metalgo/utils" 16 "github.com/MetalBlockchain/metalgo/utils/crypto/bls" 17 "github.com/MetalBlockchain/metalgo/utils/set" 18 ) 19 20 var ( 21 _ Manager = (*manager)(nil) 22 23 ErrZeroWeight = errors.New("weight must be non-zero") 24 ErrMissingValidators = errors.New("missing validators") 25 ) 26 27 type ManagerCallbackListener interface { 28 OnValidatorAdded(subnetID ids.ID, nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) 29 OnValidatorRemoved(subnetID ids.ID, nodeID ids.NodeID, weight uint64) 30 OnValidatorWeightChanged(subnetID ids.ID, nodeID ids.NodeID, oldWeight, newWeight uint64) 31 } 32 33 type SetCallbackListener interface { 34 OnValidatorAdded(nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) 35 OnValidatorRemoved(nodeID ids.NodeID, weight uint64) 36 OnValidatorWeightChanged(nodeID ids.NodeID, oldWeight, newWeight uint64) 37 } 38 39 // Manager holds the validator set of each subnet 40 type Manager interface { 41 fmt.Stringer 42 43 // Add a new staker to the subnet. 44 // Returns an error if: 45 // - [weight] is 0 46 // - [nodeID] is already in the validator set 47 // If an error is returned, the set will be unmodified. 48 AddStaker(subnetID ids.ID, nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) error 49 50 // AddWeight to an existing staker to the subnet. 51 // Returns an error if: 52 // - [weight] is 0 53 // - [nodeID] is not already in the validator set 54 // If an error is returned, the set will be unmodified. 55 // AddWeight can result in a total weight that overflows uint64. 56 // In this case no error will be returned for this call. 57 // However, the next TotalWeight call will return an error. 58 AddWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error 59 60 // GetWeight retrieves the validator weight from the subnet. 61 GetWeight(subnetID ids.ID, nodeID ids.NodeID) uint64 62 63 // GetValidator returns the validator tied to the specified ID in subnet. 64 // If the validator doesn't exist, returns false. 65 GetValidator(subnetID ids.ID, nodeID ids.NodeID) (*Validator, bool) 66 67 // GetValidatoIDs returns the validator IDs in the subnet. 68 GetValidatorIDs(subnetID ids.ID) []ids.NodeID 69 70 // SubsetWeight returns the sum of the weights of the validators in the subnet. 71 // Returns err if subset weight overflows uint64. 72 SubsetWeight(subnetID ids.ID, validatorIDs set.Set[ids.NodeID]) (uint64, error) 73 74 // RemoveWeight from a staker in the subnet. If the staker's weight becomes 0, the staker 75 // will be removed from the subnet set. 76 // Returns an error if: 77 // - [weight] is 0 78 // - [nodeID] is not already in the subnet set 79 // - the weight of the validator would become negative 80 // If an error is returned, the set will be unmodified. 81 RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error 82 83 // Count returns the number of validators currently in the subnet. 84 Count(subnetID ids.ID) int 85 86 // TotalWeight returns the cumulative weight of all validators in the subnet. 87 // Returns err if total weight overflows uint64. 88 TotalWeight(subnetID ids.ID) (uint64, error) 89 90 // Sample returns a collection of validatorIDs in the subnet, potentially with duplicates. 91 // If sampling the requested size isn't possible, an error will be returned. 92 Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) 93 94 // Map of the validators in this subnet 95 GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput 96 97 // When a validator is added, removed, or its weight changes, the listener 98 // will be notified of the event. 99 RegisterCallbackListener(listener ManagerCallbackListener) 100 101 // When a validator is added, removed, or its weight changes on [subnetID], 102 // the listener will be notified of the event. 103 RegisterSetCallbackListener(subnetID ids.ID, listener SetCallbackListener) 104 } 105 106 // NewManager returns a new, empty manager 107 func NewManager() Manager { 108 return &manager{ 109 subnetToVdrs: make(map[ids.ID]*vdrSet), 110 } 111 } 112 113 type manager struct { 114 lock sync.RWMutex 115 116 // Key: Subnet ID 117 // Value: The validators that validate the subnet 118 subnetToVdrs map[ids.ID]*vdrSet 119 callbackListeners []ManagerCallbackListener 120 } 121 122 func (m *manager) AddStaker(subnetID ids.ID, nodeID ids.NodeID, pk *bls.PublicKey, txID ids.ID, weight uint64) error { 123 if weight == 0 { 124 return ErrZeroWeight 125 } 126 127 m.lock.Lock() 128 defer m.lock.Unlock() 129 130 set, exists := m.subnetToVdrs[subnetID] 131 if !exists { 132 set = newSet(subnetID, m.callbackListeners) 133 m.subnetToVdrs[subnetID] = set 134 } 135 136 return set.Add(nodeID, pk, txID, weight) 137 } 138 139 func (m *manager) AddWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error { 140 if weight == 0 { 141 return ErrZeroWeight 142 } 143 144 // We do not need to grab a write lock here because we never modify the 145 // subnetToVdrs map. However, we must hold the read lock during the entirity 146 // of this function to ensure that errors are returned consistently. 147 // 148 // Consider the case that: 149 // AddStaker(subnetID, nodeID, 1) 150 // go func() { 151 // AddWeight(subnetID, nodeID, 1) 152 // } 153 // go func() { 154 // RemoveWeight(subnetID, nodeID, 1) 155 // } 156 // 157 // In this case, after both goroutines have finished, either AddWeight 158 // should have errored, or the weight of the node should equal 1. It would 159 // be unexpected to not have received an error from AddWeight but for the 160 // node to no longer be tracked as a validator. 161 m.lock.RLock() 162 defer m.lock.RUnlock() 163 164 set, exists := m.subnetToVdrs[subnetID] 165 if !exists { 166 return errMissingValidator 167 } 168 169 return set.AddWeight(nodeID, weight) 170 } 171 172 func (m *manager) GetWeight(subnetID ids.ID, nodeID ids.NodeID) uint64 { 173 m.lock.RLock() 174 set, exists := m.subnetToVdrs[subnetID] 175 m.lock.RUnlock() 176 if !exists { 177 return 0 178 } 179 180 return set.GetWeight(nodeID) 181 } 182 183 func (m *manager) GetValidator(subnetID ids.ID, nodeID ids.NodeID) (*Validator, bool) { 184 m.lock.RLock() 185 set, exists := m.subnetToVdrs[subnetID] 186 m.lock.RUnlock() 187 if !exists { 188 return nil, false 189 } 190 191 return set.Get(nodeID) 192 } 193 194 func (m *manager) SubsetWeight(subnetID ids.ID, validatorIDs set.Set[ids.NodeID]) (uint64, error) { 195 m.lock.RLock() 196 set, exists := m.subnetToVdrs[subnetID] 197 m.lock.RUnlock() 198 if !exists { 199 return 0, nil 200 } 201 202 return set.SubsetWeight(validatorIDs) 203 } 204 205 func (m *manager) RemoveWeight(subnetID ids.ID, nodeID ids.NodeID, weight uint64) error { 206 if weight == 0 { 207 return ErrZeroWeight 208 } 209 210 m.lock.Lock() 211 defer m.lock.Unlock() 212 213 set, exists := m.subnetToVdrs[subnetID] 214 if !exists { 215 return errMissingValidator 216 } 217 218 if err := set.RemoveWeight(nodeID, weight); err != nil { 219 return err 220 } 221 // If this was the last validator in the subnet and no callback listeners 222 // are registered, remove the subnet 223 if set.Len() == 0 && !set.HasCallbackRegistered() { 224 delete(m.subnetToVdrs, subnetID) 225 } 226 227 return nil 228 } 229 230 func (m *manager) Count(subnetID ids.ID) int { 231 m.lock.RLock() 232 set, exists := m.subnetToVdrs[subnetID] 233 m.lock.RUnlock() 234 if !exists { 235 return 0 236 } 237 238 return set.Len() 239 } 240 241 func (m *manager) TotalWeight(subnetID ids.ID) (uint64, error) { 242 m.lock.RLock() 243 set, exists := m.subnetToVdrs[subnetID] 244 m.lock.RUnlock() 245 if !exists { 246 return 0, nil 247 } 248 249 return set.TotalWeight() 250 } 251 252 func (m *manager) Sample(subnetID ids.ID, size int) ([]ids.NodeID, error) { 253 if size == 0 { 254 return nil, nil 255 } 256 257 m.lock.RLock() 258 set, exists := m.subnetToVdrs[subnetID] 259 m.lock.RUnlock() 260 if !exists { 261 return nil, ErrMissingValidators 262 } 263 264 return set.Sample(size) 265 } 266 267 func (m *manager) GetMap(subnetID ids.ID) map[ids.NodeID]*GetValidatorOutput { 268 m.lock.RLock() 269 set, exists := m.subnetToVdrs[subnetID] 270 m.lock.RUnlock() 271 if !exists { 272 return make(map[ids.NodeID]*GetValidatorOutput) 273 } 274 275 return set.Map() 276 } 277 278 func (m *manager) RegisterCallbackListener(listener ManagerCallbackListener) { 279 m.lock.Lock() 280 defer m.lock.Unlock() 281 282 m.callbackListeners = append(m.callbackListeners, listener) 283 for _, set := range m.subnetToVdrs { 284 set.RegisterManagerCallbackListener(listener) 285 } 286 } 287 288 func (m *manager) RegisterSetCallbackListener(subnetID ids.ID, listener SetCallbackListener) { 289 m.lock.Lock() 290 defer m.lock.Unlock() 291 292 set, exists := m.subnetToVdrs[subnetID] 293 if !exists { 294 set = newSet(subnetID, m.callbackListeners) 295 m.subnetToVdrs[subnetID] = set 296 } 297 298 set.RegisterCallbackListener(listener) 299 } 300 301 func (m *manager) String() string { 302 m.lock.RLock() 303 defer m.lock.RUnlock() 304 305 subnets := maps.Keys(m.subnetToVdrs) 306 utils.Sort(subnets) 307 308 sb := strings.Builder{} 309 310 sb.WriteString(fmt.Sprintf("Validator Manager: (Size = %d)", 311 len(subnets), 312 )) 313 for _, subnetID := range subnets { 314 vdrs := m.subnetToVdrs[subnetID] 315 sb.WriteString(fmt.Sprintf( 316 "\n Subnet[%s]: %s", 317 subnetID, 318 vdrs.PrefixedString(" "), 319 )) 320 } 321 322 return sb.String() 323 } 324 325 func (m *manager) GetValidatorIDs(subnetID ids.ID) []ids.NodeID { 326 m.lock.RLock() 327 vdrs, exist := m.subnetToVdrs[subnetID] 328 m.lock.RUnlock() 329 if !exist { 330 return nil 331 } 332 333 return vdrs.GetValidatorIDs() 334 }