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 }