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  }