github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/executor/staker_tx_verification_helpers.go (about)

     1  // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved.
     2  // See the file LICENSE for licensing terms.
     3  
     4  package executor
     5  
     6  import (
     7  	"time"
     8  
     9  	"github.com/MetalBlockchain/metalgo/database"
    10  	"github.com/MetalBlockchain/metalgo/ids"
    11  	"github.com/MetalBlockchain/metalgo/utils/constants"
    12  	"github.com/MetalBlockchain/metalgo/utils/math"
    13  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    14  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    15  )
    16  
    17  type addValidatorRules struct {
    18  	assetID           ids.ID
    19  	minValidatorStake uint64
    20  	maxValidatorStake uint64
    21  	minStakeDuration  time.Duration
    22  	maxStakeDuration  time.Duration
    23  	minDelegationFee  uint32
    24  }
    25  
    26  func getValidatorRules(
    27  	backend *Backend,
    28  	chainState state.Chain,
    29  	subnetID ids.ID,
    30  ) (*addValidatorRules, error) {
    31  	if subnetID == constants.PrimaryNetworkID {
    32  		return &addValidatorRules{
    33  			assetID:           backend.Ctx.AVAXAssetID,
    34  			minValidatorStake: backend.Config.MinValidatorStake,
    35  			maxValidatorStake: backend.Config.MaxValidatorStake,
    36  			minStakeDuration:  backend.Config.MinStakeDuration,
    37  			maxStakeDuration:  backend.Config.MaxStakeDuration,
    38  			minDelegationFee:  backend.Config.MinDelegationFee,
    39  		}, nil
    40  	}
    41  
    42  	transformSubnet, err := GetTransformSubnetTx(chainState, subnetID)
    43  	if err != nil {
    44  		return nil, err
    45  	}
    46  
    47  	return &addValidatorRules{
    48  		assetID:           transformSubnet.AssetID,
    49  		minValidatorStake: transformSubnet.MinValidatorStake,
    50  		maxValidatorStake: transformSubnet.MaxValidatorStake,
    51  		minStakeDuration:  time.Duration(transformSubnet.MinStakeDuration) * time.Second,
    52  		maxStakeDuration:  time.Duration(transformSubnet.MaxStakeDuration) * time.Second,
    53  		minDelegationFee:  transformSubnet.MinDelegationFee,
    54  	}, nil
    55  }
    56  
    57  type addDelegatorRules struct {
    58  	assetID                  ids.ID
    59  	minDelegatorStake        uint64
    60  	maxValidatorStake        uint64
    61  	minStakeDuration         time.Duration
    62  	maxStakeDuration         time.Duration
    63  	maxValidatorWeightFactor byte
    64  }
    65  
    66  func getDelegatorRules(
    67  	backend *Backend,
    68  	chainState state.Chain,
    69  	subnetID ids.ID,
    70  ) (*addDelegatorRules, error) {
    71  	if subnetID == constants.PrimaryNetworkID {
    72  		return &addDelegatorRules{
    73  			assetID:                  backend.Ctx.AVAXAssetID,
    74  			minDelegatorStake:        backend.Config.MinDelegatorStake,
    75  			maxValidatorStake:        backend.Config.MaxValidatorStake,
    76  			minStakeDuration:         backend.Config.MinStakeDuration,
    77  			maxStakeDuration:         backend.Config.MaxStakeDuration,
    78  			maxValidatorWeightFactor: MaxValidatorWeightFactor,
    79  		}, nil
    80  	}
    81  
    82  	transformSubnet, err := GetTransformSubnetTx(chainState, subnetID)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	return &addDelegatorRules{
    88  		assetID:                  transformSubnet.AssetID,
    89  		minDelegatorStake:        transformSubnet.MinDelegatorStake,
    90  		maxValidatorStake:        transformSubnet.MaxValidatorStake,
    91  		minStakeDuration:         time.Duration(transformSubnet.MinStakeDuration) * time.Second,
    92  		maxStakeDuration:         time.Duration(transformSubnet.MaxStakeDuration) * time.Second,
    93  		maxValidatorWeightFactor: transformSubnet.MaxValidatorWeightFactor,
    94  	}, nil
    95  }
    96  
    97  // GetValidator returns information about the given validator, which may be a
    98  // current validator or pending validator.
    99  func GetValidator(state state.Chain, subnetID ids.ID, nodeID ids.NodeID) (*state.Staker, error) {
   100  	validator, err := state.GetCurrentValidator(subnetID, nodeID)
   101  	if err == nil {
   102  		// This node is currently validating the subnet.
   103  		return validator, nil
   104  	}
   105  	if err != database.ErrNotFound {
   106  		// Unexpected error occurred.
   107  		return nil, err
   108  	}
   109  	return state.GetPendingValidator(subnetID, nodeID)
   110  }
   111  
   112  // overDelegated returns true if [validator] will be overdelegated when adding [delegator].
   113  //
   114  // A [validator] would become overdelegated if:
   115  // - the maximum total weight on [validator] exceeds [weightLimit]
   116  func overDelegated(
   117  	state state.Chain,
   118  	validator *state.Staker,
   119  	weightLimit uint64,
   120  	delegatorWeight uint64,
   121  	delegatorStartTime time.Time,
   122  	delegatorEndTime time.Time,
   123  ) (bool, error) {
   124  	maxWeight, err := GetMaxWeight(state, validator, delegatorStartTime, delegatorEndTime)
   125  	if err != nil {
   126  		return true, err
   127  	}
   128  	newMaxWeight, err := math.Add64(maxWeight, delegatorWeight)
   129  	if err != nil {
   130  		return true, err
   131  	}
   132  	return newMaxWeight > weightLimit, nil
   133  }
   134  
   135  // GetMaxWeight returns the maximum total weight of the [validator], including
   136  // its own weight, between [startTime] and [endTime].
   137  // The weight changes are applied in the order they will be applied as chain
   138  // time advances.
   139  // Invariant:
   140  // - [validator.StartTime] <= [startTime] < [endTime] <= [validator.EndTime]
   141  func GetMaxWeight(
   142  	chainState state.Chain,
   143  	validator *state.Staker,
   144  	startTime time.Time,
   145  	endTime time.Time,
   146  ) (uint64, error) {
   147  	currentDelegatorIterator, err := chainState.GetCurrentDelegatorIterator(validator.SubnetID, validator.NodeID)
   148  	if err != nil {
   149  		return 0, err
   150  	}
   151  
   152  	// TODO: We can optimize this by moving the current total weight to be
   153  	//       stored in the validator state.
   154  	//
   155  	// Calculate the current total weight on this validator, including the
   156  	// weight of the actual validator and the sum of the weights of all of the
   157  	// currently active delegators.
   158  	currentWeight := validator.Weight
   159  	for currentDelegatorIterator.Next() {
   160  		currentDelegator := currentDelegatorIterator.Value()
   161  
   162  		currentWeight, err = math.Add64(currentWeight, currentDelegator.Weight)
   163  		if err != nil {
   164  			currentDelegatorIterator.Release()
   165  			return 0, err
   166  		}
   167  	}
   168  	currentDelegatorIterator.Release()
   169  
   170  	currentDelegatorIterator, err = chainState.GetCurrentDelegatorIterator(validator.SubnetID, validator.NodeID)
   171  	if err != nil {
   172  		return 0, err
   173  	}
   174  	pendingDelegatorIterator, err := chainState.GetPendingDelegatorIterator(validator.SubnetID, validator.NodeID)
   175  	if err != nil {
   176  		currentDelegatorIterator.Release()
   177  		return 0, err
   178  	}
   179  	delegatorChangesIterator := state.NewStakerDiffIterator(currentDelegatorIterator, pendingDelegatorIterator)
   180  	defer delegatorChangesIterator.Release()
   181  
   182  	// Iterate over the future stake weight changes and calculate the maximum
   183  	// total weight on the validator, only including the points in the time
   184  	// range [startTime, endTime].
   185  	var currentMax uint64
   186  	for delegatorChangesIterator.Next() {
   187  		delegator, isAdded := delegatorChangesIterator.Value()
   188  		// [delegator.NextTime] > [endTime]
   189  		if delegator.NextTime.After(endTime) {
   190  			// This delegation change (and all following changes) occurs after
   191  			// [endTime]. Since we're calculating the max amount staked in
   192  			// [startTime, endTime], we can stop.
   193  			break
   194  		}
   195  
   196  		// [delegator.NextTime] >= [startTime]
   197  		if !delegator.NextTime.Before(startTime) {
   198  			// We have advanced time to be at the inside of the delegation
   199  			// window. Make sure that the max weight is updated accordingly.
   200  			currentMax = max(currentMax, currentWeight)
   201  		}
   202  
   203  		var op func(uint64, uint64) (uint64, error)
   204  		if isAdded {
   205  			op = math.Add64
   206  		} else {
   207  			op = math.Sub[uint64]
   208  		}
   209  		currentWeight, err = op(currentWeight, delegator.Weight)
   210  		if err != nil {
   211  			return 0, err
   212  		}
   213  	}
   214  	// Because we assume [startTime] < [endTime], we have advanced time to
   215  	// be at the end of the delegation window. Make sure that the max weight is
   216  	// updated accordingly.
   217  	return max(currentMax, currentWeight), nil
   218  }
   219  
   220  func GetTransformSubnetTx(chain state.Chain, subnetID ids.ID) (*txs.TransformSubnetTx, error) {
   221  	transformSubnetIntf, err := chain.GetSubnetTransformation(subnetID)
   222  	if err != nil {
   223  		return nil, err
   224  	}
   225  
   226  	transformSubnet, ok := transformSubnetIntf.Unsigned.(*txs.TransformSubnetTx)
   227  	if !ok {
   228  		return nil, ErrIsNotTransformSubnetTx
   229  	}
   230  
   231  	return transformSubnet, nil
   232  }