Files
proto/p/txs/executor/staker_tx_verification_helpers.go
zeekayandGitHub 7da6754f44 refactor: XAssetID → UTXOAssetID (#7)
Companion to luxfi/{runtime,consensus,node}: rename downstream
consumers in proto so the build agrees post upstream rename.

Includes local replace directives for runtime + consensus; drop
once those modules tag releases.
2026-05-30 14:26:58 -07:00

232 lines
7.2 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package executor
import (
"time"
"github.com/luxfi/constants"
"github.com/luxfi/database"
"github.com/luxfi/ids"
"github.com/luxfi/math"
"github.com/luxfi/proto/p/state"
"github.com/luxfi/proto/p/txs"
)
type addValidatorRules struct {
assetID ids.ID
minValidatorStake uint64
maxValidatorStake uint64
minStakeDuration time.Duration
maxStakeDuration time.Duration
minDelegationFee uint32
}
func getValidatorRules(
backend *Backend,
chainState state.Chain,
netID ids.ID,
) (*addValidatorRules, error) {
if netID == constants.PrimaryNetworkID {
return &addValidatorRules{
assetID: backend.Rt.UTXOAssetID,
minValidatorStake: backend.Config.MinValidatorStake,
maxValidatorStake: backend.Config.MaxValidatorStake,
minStakeDuration: backend.Config.MinStakeDuration,
maxStakeDuration: backend.Config.MaxStakeDuration,
minDelegationFee: backend.Config.MinDelegationFee,
}, nil
}
transformNet, err := GetTransformChainTx(chainState, netID)
if err != nil {
return nil, err
}
return &addValidatorRules{
assetID: transformNet.AssetID,
minValidatorStake: transformNet.MinValidatorStake,
maxValidatorStake: transformNet.MaxValidatorStake,
minStakeDuration: time.Duration(transformNet.MinStakeDuration) * time.Second,
maxStakeDuration: time.Duration(transformNet.MaxStakeDuration) * time.Second,
minDelegationFee: transformNet.MinDelegationFee,
}, nil
}
type addDelegatorRules struct {
assetID ids.ID
minDelegatorStake uint64
maxValidatorStake uint64
minStakeDuration time.Duration
maxStakeDuration time.Duration
maxValidatorWeightFactor byte
}
func getDelegatorRules(
backend *Backend,
chainState state.Chain,
netID ids.ID,
) (*addDelegatorRules, error) {
if netID == constants.PrimaryNetworkID {
return &addDelegatorRules{
assetID: backend.Rt.UTXOAssetID,
minDelegatorStake: backend.Config.MinDelegatorStake,
maxValidatorStake: backend.Config.MaxValidatorStake,
minStakeDuration: backend.Config.MinStakeDuration,
maxStakeDuration: backend.Config.MaxStakeDuration,
maxValidatorWeightFactor: MaxValidatorWeightFactor,
}, nil
}
transformNet, err := GetTransformChainTx(chainState, netID)
if err != nil {
return nil, err
}
return &addDelegatorRules{
assetID: transformNet.AssetID,
minDelegatorStake: transformNet.MinDelegatorStake,
maxValidatorStake: transformNet.MaxValidatorStake,
minStakeDuration: time.Duration(transformNet.MinStakeDuration) * time.Second,
maxStakeDuration: time.Duration(transformNet.MaxStakeDuration) * time.Second,
maxValidatorWeightFactor: transformNet.MaxValidatorWeightFactor,
}, nil
}
// GetValidator returns information about the given validator, which may be a
// current validator or pending validator.
func GetValidator(state state.Chain, netID ids.ID, nodeID ids.NodeID) (*state.Staker, error) {
validator, err := state.GetCurrentValidator(netID, nodeID)
if err == nil {
// This node is currently validating the chain.
return validator, nil
}
if err != database.ErrNotFound {
// Unexpected error occurred.
return nil, err
}
return state.GetPendingValidator(netID, nodeID)
}
// overDelegated returns true if [validator] will be overdelegated when adding [delegator].
//
// A [validator] would become overdelegated if:
// - the maximum total weight on [validator] exceeds [weightLimit]
func overDelegated(
state state.Chain,
validator *state.Staker,
weightLimit uint64,
delegatorWeight uint64,
delegatorStartTime time.Time,
delegatorEndTime time.Time,
) (bool, error) {
maxWeight, err := GetMaxWeight(state, validator, delegatorStartTime, delegatorEndTime)
if err != nil {
return true, err
}
newMaxWeight, err := math.Add(maxWeight, delegatorWeight)
if err != nil {
return true, err
}
return newMaxWeight > weightLimit, nil
}
// GetMaxWeight returns the maximum total weight of the [validator], including
// its own weight, between [startTime] and [endTime].
// The weight changes are applied in the order they will be applied as chain
// time advances.
// Invariant:
// - [validator.StartTime] <= [startTime] < [endTime] <= [validator.EndTime]
func GetMaxWeight(
chainState state.Chain,
validator *state.Staker,
startTime time.Time,
endTime time.Time,
) (uint64, error) {
currentDelegatorIterator, err := chainState.GetCurrentDelegatorIterator(validator.ChainID, validator.NodeID)
if err != nil {
return 0, err
}
// stored in the validator state.
//
// Calculate the current total weight on this validator, including the
// weight of the actual validator and the sum of the weights of all of the
// currently active delegators.
currentWeight := validator.Weight
for currentDelegatorIterator.Next() {
currentDelegator := currentDelegatorIterator.Value()
currentWeight, err = math.Add(currentWeight, currentDelegator.Weight)
if err != nil {
currentDelegatorIterator.Release()
return 0, err
}
}
currentDelegatorIterator.Release()
currentDelegatorIterator, err = chainState.GetCurrentDelegatorIterator(validator.ChainID, validator.NodeID)
if err != nil {
return 0, err
}
pendingDelegatorIterator, err := chainState.GetPendingDelegatorIterator(validator.ChainID, validator.NodeID)
if err != nil {
currentDelegatorIterator.Release()
return 0, err
}
delegatorChangesIterator := state.NewStakerDiffIterator(currentDelegatorIterator, pendingDelegatorIterator)
defer delegatorChangesIterator.Release()
// Iterate over the future stake weight changes and calculate the maximum
// total weight on the validator, only including the points in the time
// range [startTime, endTime].
var currentMax uint64
for delegatorChangesIterator.Next() {
delegator, isAdded := delegatorChangesIterator.Value()
// [delegator.NextTime] > [endTime]
if delegator.NextTime.After(endTime) {
// This delegation change (and all following changes) occurs after
// [endTime]. Since we're calculating the max amount staked in
// [startTime, endTime], we can stop.
break
}
// [delegator.NextTime] >= [startTime]
if !delegator.NextTime.Before(startTime) {
// We have advanced time to be at the inside of the delegation
// window. Make sure that the max weight is updated accordingly.
currentMax = max(currentMax, currentWeight)
}
var op func(uint64, uint64) (uint64, error)
if isAdded {
op = math.Add
} else {
op = math.Sub
}
currentWeight, err = op(currentWeight, delegator.Weight)
if err != nil {
return 0, err
}
}
// Because we assume [startTime] < [endTime], we have advanced time to
// be at the end of the delegation window. Make sure that the max weight is
// updated accordingly.
return max(currentMax, currentWeight), nil
}
func GetTransformChainTx(chain state.Chain, netID ids.ID) (*txs.TransformChainTx, error) {
transformNetIntf, err := chain.GetNetTransformation(netID)
if err != nil {
return nil, err
}
transformNet, ok := transformNetIntf.Unsigned.(*txs.TransformChainTx)
if !ok {
return nil, ErrIsNotTransformChainTx
}
return transformNet, nil
}