github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/staker_tx_verification.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  	"errors"
     8  	"fmt"
     9  	"math"
    10  	"time"
    11  
    12  	"github.com/ava-labs/avalanchego/database"
    13  	"github.com/ava-labs/avalanchego/ids"
    14  	"github.com/ava-labs/avalanchego/utils/constants"
    15  	"github.com/ava-labs/avalanchego/vms/components/avax"
    16  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    17  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    18  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/fee"
    19  
    20  	safemath "github.com/ava-labs/avalanchego/utils/math"
    21  )
    22  
    23  var (
    24  	ErrWeightTooSmall                  = errors.New("weight of this validator is too low")
    25  	ErrWeightTooLarge                  = errors.New("weight of this validator is too large")
    26  	ErrInsufficientDelegationFee       = errors.New("staker charges an insufficient delegation fee")
    27  	ErrStakeTooShort                   = errors.New("staking period is too short")
    28  	ErrStakeTooLong                    = errors.New("staking period is too long")
    29  	ErrFlowCheckFailed                 = errors.New("flow check failed")
    30  	ErrNotValidator                    = errors.New("isn't a current or pending validator")
    31  	ErrRemovePermissionlessValidator   = errors.New("attempting to remove permissionless validator")
    32  	ErrStakeOverflow                   = errors.New("validator stake exceeds limit")
    33  	ErrPeriodMismatch                  = errors.New("proposed staking period is not inside dependant staking period")
    34  	ErrOverDelegated                   = errors.New("validator would be over delegated")
    35  	ErrIsNotTransformSubnetTx          = errors.New("is not a transform subnet tx")
    36  	ErrTimestampNotBeforeStartTime     = errors.New("chain timestamp not before start time")
    37  	ErrAlreadyValidator                = errors.New("already a validator")
    38  	ErrDuplicateValidator              = errors.New("duplicate validator")
    39  	ErrDelegateToPermissionedValidator = errors.New("delegation to permissioned validator")
    40  	ErrWrongStakedAssetID              = errors.New("incorrect staked assetID")
    41  	ErrDurangoUpgradeNotActive         = errors.New("attempting to use a Durango-upgrade feature prior to activation")
    42  	ErrAddValidatorTxPostDurango       = errors.New("AddValidatorTx is not permitted post-Durango")
    43  	ErrAddDelegatorTxPostDurango       = errors.New("AddDelegatorTx is not permitted post-Durango")
    44  	ErrRemoveValidatorManagedSubnet    = errors.New("RemoveSubnetValidatorTx cannot be used to remove a validator from a Subnet with a manager")
    45  )
    46  
    47  // verifySubnetValidatorPrimaryNetworkRequirements verifies the primary
    48  // network requirements for [subnetValidator]. An error is returned if they
    49  // are not fulfilled.
    50  func verifySubnetValidatorPrimaryNetworkRequirements(
    51  	isDurangoActive bool,
    52  	chainState state.Chain,
    53  	subnetValidator txs.Validator,
    54  ) error {
    55  	primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, subnetValidator.NodeID)
    56  	if err == database.ErrNotFound {
    57  		return fmt.Errorf(
    58  			"%s %w of the primary network",
    59  			subnetValidator.NodeID,
    60  			ErrNotValidator,
    61  		)
    62  	}
    63  	if err != nil {
    64  		return fmt.Errorf(
    65  			"failed to fetch the primary network validator for %s: %w",
    66  			subnetValidator.NodeID,
    67  			err,
    68  		)
    69  	}
    70  
    71  	// Ensure that the period this validator validates the specified subnet
    72  	// is a subset of the time they validate the primary network.
    73  	startTime := chainState.GetTimestamp()
    74  	if !isDurangoActive {
    75  		startTime = subnetValidator.StartTime()
    76  	}
    77  	if !txs.BoundedBy(
    78  		startTime,
    79  		subnetValidator.EndTime(),
    80  		primaryNetworkValidator.StartTime,
    81  		primaryNetworkValidator.EndTime,
    82  	) {
    83  		return ErrPeriodMismatch
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  // verifyAddValidatorTx carries out the validation for an AddValidatorTx.
    90  // It returns the tx outputs that should be returned if this validator is not
    91  // added to the staking set.
    92  func verifyAddValidatorTx(
    93  	backend *Backend,
    94  	feeCalculator fee.Calculator,
    95  	chainState state.Chain,
    96  	sTx *txs.Tx,
    97  	tx *txs.AddValidatorTx,
    98  ) (
    99  	[]*avax.TransferableOutput,
   100  	error,
   101  ) {
   102  	currentTimestamp := chainState.GetTimestamp()
   103  	if backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) {
   104  		return nil, ErrAddValidatorTxPostDurango
   105  	}
   106  
   107  	// Verify the tx is well-formed
   108  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   109  		return nil, err
   110  	}
   111  
   112  	if err := avax.VerifyMemoFieldLength(tx.Memo, false /*=isDurangoActive*/); err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	startTime := tx.StartTime()
   117  	duration := tx.EndTime().Sub(startTime)
   118  	switch {
   119  	case tx.Validator.Wght < backend.Config.MinValidatorStake:
   120  		// Ensure validator is staking at least the minimum amount
   121  		return nil, ErrWeightTooSmall
   122  
   123  	case tx.Validator.Wght > backend.Config.MaxValidatorStake:
   124  		// Ensure validator isn't staking too much
   125  		return nil, ErrWeightTooLarge
   126  
   127  	case tx.DelegationShares < backend.Config.MinDelegationFee:
   128  		// Ensure the validator fee is at least the minimum amount
   129  		return nil, ErrInsufficientDelegationFee
   130  
   131  	case duration < backend.Config.MinStakeDuration:
   132  		// Ensure staking length is not too short
   133  		return nil, ErrStakeTooShort
   134  
   135  	case duration > backend.Config.MaxStakeDuration:
   136  		// Ensure staking length is not too long
   137  		return nil, ErrStakeTooLong
   138  	}
   139  
   140  	outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts))
   141  	copy(outs, tx.Outs)
   142  	copy(outs[len(tx.Outs):], tx.StakeOuts)
   143  
   144  	if !backend.Bootstrapped.Get() {
   145  		return outs, nil
   146  	}
   147  
   148  	if err := verifyStakerStartTime(false /*=isDurangoActive*/, currentTimestamp, startTime); err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	_, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID)
   153  	if err == nil {
   154  		return nil, fmt.Errorf(
   155  			"%s is %w of the primary network",
   156  			tx.Validator.NodeID,
   157  			ErrAlreadyValidator,
   158  		)
   159  	}
   160  	if err != database.ErrNotFound {
   161  		return nil, fmt.Errorf(
   162  			"failed to find whether %s is a primary network validator: %w",
   163  			tx.Validator.NodeID,
   164  			err,
   165  		)
   166  	}
   167  
   168  	// Verify the flowcheck
   169  	fee, err := feeCalculator.CalculateFee(tx)
   170  	if err != nil {
   171  		return nil, err
   172  	}
   173  	if err := backend.FlowChecker.VerifySpend(
   174  		tx,
   175  		chainState,
   176  		tx.Ins,
   177  		outs,
   178  		sTx.Creds,
   179  		map[ids.ID]uint64{
   180  			backend.Ctx.AVAXAssetID: fee,
   181  		},
   182  	); err != nil {
   183  		return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   184  	}
   185  
   186  	return outs, nil
   187  }
   188  
   189  // verifyAddSubnetValidatorTx carries out the validation for an
   190  // AddSubnetValidatorTx.
   191  func verifyAddSubnetValidatorTx(
   192  	backend *Backend,
   193  	feeCalculator fee.Calculator,
   194  	chainState state.Chain,
   195  	sTx *txs.Tx,
   196  	tx *txs.AddSubnetValidatorTx,
   197  ) error {
   198  	// Verify the tx is well-formed
   199  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   200  		return err
   201  	}
   202  
   203  	var (
   204  		currentTimestamp = chainState.GetTimestamp()
   205  		isDurangoActive  = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
   206  	)
   207  	if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
   208  		return err
   209  	}
   210  
   211  	startTime := currentTimestamp
   212  	if !isDurangoActive {
   213  		startTime = tx.StartTime()
   214  	}
   215  	duration := tx.EndTime().Sub(startTime)
   216  
   217  	switch {
   218  	case duration < backend.Config.MinStakeDuration:
   219  		// Ensure staking length is not too short
   220  		return ErrStakeTooShort
   221  
   222  	case duration > backend.Config.MaxStakeDuration:
   223  		// Ensure staking length is not too long
   224  		return ErrStakeTooLong
   225  	}
   226  
   227  	if !backend.Bootstrapped.Get() {
   228  		return nil
   229  	}
   230  
   231  	if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil {
   232  		return err
   233  	}
   234  
   235  	_, err := GetValidator(chainState, tx.SubnetValidator.Subnet, tx.Validator.NodeID)
   236  	if err == nil {
   237  		return fmt.Errorf(
   238  			"attempted to issue %w for %s on subnet %s",
   239  			ErrDuplicateValidator,
   240  			tx.Validator.NodeID,
   241  			tx.SubnetValidator.Subnet,
   242  		)
   243  	}
   244  	if err != database.ErrNotFound {
   245  		return fmt.Errorf(
   246  			"failed to find whether %s is a subnet validator: %w",
   247  			tx.Validator.NodeID,
   248  			err,
   249  		)
   250  	}
   251  
   252  	if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil {
   253  		return err
   254  	}
   255  
   256  	baseTxCreds, err := verifyPoASubnetAuthorization(backend, chainState, sTx, tx.SubnetValidator.Subnet, tx.SubnetAuth)
   257  	if err != nil {
   258  		return err
   259  	}
   260  
   261  	// Verify the flowcheck
   262  	fee, err := feeCalculator.CalculateFee(tx)
   263  	if err != nil {
   264  		return err
   265  	}
   266  	if err := backend.FlowChecker.VerifySpend(
   267  		tx,
   268  		chainState,
   269  		tx.Ins,
   270  		tx.Outs,
   271  		baseTxCreds,
   272  		map[ids.ID]uint64{
   273  			backend.Ctx.AVAXAssetID: fee,
   274  		},
   275  	); err != nil {
   276  		return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   277  	}
   278  
   279  	return nil
   280  }
   281  
   282  // Returns the representation of [tx.NodeID] validating [tx.Subnet].
   283  // Returns true if [tx.NodeID] is a current validator of [tx.Subnet].
   284  // Returns an error if the given tx is invalid.
   285  // The transaction is valid if:
   286  // * [tx.NodeID] is a current/pending PoA validator of [tx.Subnet].
   287  // * [sTx]'s creds authorize it to spend the stated inputs.
   288  // * [sTx]'s creds authorize it to remove a validator from [tx.Subnet].
   289  // * The flow checker passes.
   290  func verifyRemoveSubnetValidatorTx(
   291  	backend *Backend,
   292  	feeCalculator fee.Calculator,
   293  	chainState state.Chain,
   294  	sTx *txs.Tx,
   295  	tx *txs.RemoveSubnetValidatorTx,
   296  ) (*state.Staker, bool, error) {
   297  	// Verify the tx is well-formed
   298  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   299  		return nil, false, err
   300  	}
   301  
   302  	var (
   303  		currentTimestamp = chainState.GetTimestamp()
   304  		isDurangoActive  = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
   305  	)
   306  	if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
   307  		return nil, false, err
   308  	}
   309  
   310  	if backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) {
   311  		_, _, err := chainState.GetSubnetManager(tx.Subnet)
   312  		if err == nil {
   313  			return nil, false, fmt.Errorf("%w: %q", ErrRemoveValidatorManagedSubnet, tx.Subnet)
   314  		}
   315  		if err != database.ErrNotFound {
   316  			return nil, false, err
   317  		}
   318  	}
   319  
   320  	isCurrentValidator := true
   321  	vdr, err := chainState.GetCurrentValidator(tx.Subnet, tx.NodeID)
   322  	if err == database.ErrNotFound {
   323  		vdr, err = chainState.GetPendingValidator(tx.Subnet, tx.NodeID)
   324  		isCurrentValidator = false
   325  	}
   326  	if err != nil {
   327  		// It isn't a current or pending validator.
   328  		return nil, false, fmt.Errorf(
   329  			"%s %w of %s: %w",
   330  			tx.NodeID,
   331  			ErrNotValidator,
   332  			tx.Subnet,
   333  			err,
   334  		)
   335  	}
   336  
   337  	if !vdr.Priority.IsPermissionedValidator() {
   338  		return nil, false, ErrRemovePermissionlessValidator
   339  	}
   340  
   341  	if !backend.Bootstrapped.Get() {
   342  		// Not bootstrapped yet -- don't need to do full verification.
   343  		return vdr, isCurrentValidator, nil
   344  	}
   345  
   346  	baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth)
   347  	if err != nil {
   348  		return nil, false, err
   349  	}
   350  
   351  	// Verify the flowcheck
   352  	fee, err := feeCalculator.CalculateFee(tx)
   353  	if err != nil {
   354  		return nil, false, err
   355  	}
   356  	if err := backend.FlowChecker.VerifySpend(
   357  		tx,
   358  		chainState,
   359  		tx.Ins,
   360  		tx.Outs,
   361  		baseTxCreds,
   362  		map[ids.ID]uint64{
   363  			backend.Ctx.AVAXAssetID: fee,
   364  		},
   365  	); err != nil {
   366  		return nil, false, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   367  	}
   368  
   369  	return vdr, isCurrentValidator, nil
   370  }
   371  
   372  // verifyAddDelegatorTx carries out the validation for an AddDelegatorTx.
   373  // It returns the tx outputs that should be returned if this delegator is not
   374  // added to the staking set.
   375  func verifyAddDelegatorTx(
   376  	backend *Backend,
   377  	feeCalculator fee.Calculator,
   378  	chainState state.Chain,
   379  	sTx *txs.Tx,
   380  	tx *txs.AddDelegatorTx,
   381  ) (
   382  	[]*avax.TransferableOutput,
   383  	error,
   384  ) {
   385  	currentTimestamp := chainState.GetTimestamp()
   386  	if backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) {
   387  		return nil, ErrAddDelegatorTxPostDurango
   388  	}
   389  
   390  	// Verify the tx is well-formed
   391  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   392  		return nil, err
   393  	}
   394  
   395  	if err := avax.VerifyMemoFieldLength(tx.Memo, false /*=isDurangoActive*/); err != nil {
   396  		return nil, err
   397  	}
   398  
   399  	var (
   400  		endTime   = tx.EndTime()
   401  		startTime = tx.StartTime()
   402  		duration  = endTime.Sub(startTime)
   403  	)
   404  	switch {
   405  	case duration < backend.Config.MinStakeDuration:
   406  		// Ensure staking length is not too short
   407  		return nil, ErrStakeTooShort
   408  
   409  	case duration > backend.Config.MaxStakeDuration:
   410  		// Ensure staking length is not too long
   411  		return nil, ErrStakeTooLong
   412  
   413  	case tx.Validator.Wght < backend.Config.MinDelegatorStake:
   414  		// Ensure validator is staking at least the minimum amount
   415  		return nil, ErrWeightTooSmall
   416  	}
   417  
   418  	outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts))
   419  	copy(outs, tx.Outs)
   420  	copy(outs[len(tx.Outs):], tx.StakeOuts)
   421  
   422  	if !backend.Bootstrapped.Get() {
   423  		return outs, nil
   424  	}
   425  
   426  	if err := verifyStakerStartTime(false /*=isDurangoActive*/, currentTimestamp, startTime); err != nil {
   427  		return nil, err
   428  	}
   429  
   430  	primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID)
   431  	if err != nil {
   432  		return nil, fmt.Errorf(
   433  			"failed to fetch the primary network validator for %s: %w",
   434  			tx.Validator.NodeID,
   435  			err,
   436  		)
   437  	}
   438  
   439  	maximumWeight, err := safemath.Mul(MaxValidatorWeightFactor, primaryNetworkValidator.Weight)
   440  	if err != nil {
   441  		return nil, ErrStakeOverflow
   442  	}
   443  
   444  	if backend.Config.UpgradeConfig.IsApricotPhase3Activated(currentTimestamp) {
   445  		maximumWeight = min(maximumWeight, backend.Config.MaxValidatorStake)
   446  	}
   447  
   448  	if !txs.BoundedBy(
   449  		startTime,
   450  		endTime,
   451  		primaryNetworkValidator.StartTime,
   452  		primaryNetworkValidator.EndTime,
   453  	) {
   454  		return nil, ErrPeriodMismatch
   455  	}
   456  	overDelegated, err := overDelegated(
   457  		chainState,
   458  		primaryNetworkValidator,
   459  		maximumWeight,
   460  		tx.Validator.Wght,
   461  		startTime,
   462  		endTime,
   463  	)
   464  	if err != nil {
   465  		return nil, err
   466  	}
   467  	if overDelegated {
   468  		return nil, ErrOverDelegated
   469  	}
   470  
   471  	// Verify the flowcheck
   472  	fee, err := feeCalculator.CalculateFee(tx)
   473  	if err != nil {
   474  		return nil, err
   475  	}
   476  	if err := backend.FlowChecker.VerifySpend(
   477  		tx,
   478  		chainState,
   479  		tx.Ins,
   480  		outs,
   481  		sTx.Creds,
   482  		map[ids.ID]uint64{
   483  			backend.Ctx.AVAXAssetID: fee,
   484  		},
   485  	); err != nil {
   486  		return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   487  	}
   488  
   489  	return outs, nil
   490  }
   491  
   492  // verifyAddPermissionlessValidatorTx carries out the validation for an
   493  // AddPermissionlessValidatorTx.
   494  func verifyAddPermissionlessValidatorTx(
   495  	backend *Backend,
   496  	feeCalculator fee.Calculator,
   497  	chainState state.Chain,
   498  	sTx *txs.Tx,
   499  	tx *txs.AddPermissionlessValidatorTx,
   500  ) error {
   501  	// Verify the tx is well-formed
   502  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   503  		return err
   504  	}
   505  
   506  	var (
   507  		currentTimestamp = chainState.GetTimestamp()
   508  		isDurangoActive  = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
   509  	)
   510  	if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
   511  		return err
   512  	}
   513  
   514  	if !backend.Bootstrapped.Get() {
   515  		return nil
   516  	}
   517  
   518  	startTime := currentTimestamp
   519  	if !isDurangoActive {
   520  		startTime = tx.StartTime()
   521  	}
   522  	duration := tx.EndTime().Sub(startTime)
   523  
   524  	if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil {
   525  		return err
   526  	}
   527  
   528  	validatorRules, err := getValidatorRules(backend, chainState, tx.Subnet)
   529  	if err != nil {
   530  		return err
   531  	}
   532  
   533  	stakedAssetID := tx.StakeOuts[0].AssetID()
   534  	switch {
   535  	case tx.Validator.Wght < validatorRules.minValidatorStake:
   536  		// Ensure validator is staking at least the minimum amount
   537  		return ErrWeightTooSmall
   538  
   539  	case tx.Validator.Wght > validatorRules.maxValidatorStake:
   540  		// Ensure validator isn't staking too much
   541  		return ErrWeightTooLarge
   542  
   543  	case tx.DelegationShares < validatorRules.minDelegationFee:
   544  		// Ensure the validator fee is at least the minimum amount
   545  		return ErrInsufficientDelegationFee
   546  
   547  	case duration < validatorRules.minStakeDuration:
   548  		// Ensure staking length is not too short
   549  		return ErrStakeTooShort
   550  
   551  	case duration > validatorRules.maxStakeDuration:
   552  		// Ensure staking length is not too long
   553  		return ErrStakeTooLong
   554  
   555  	case stakedAssetID != validatorRules.assetID:
   556  		// Wrong assetID used
   557  		return fmt.Errorf(
   558  			"%w: %s != %s",
   559  			ErrWrongStakedAssetID,
   560  			validatorRules.assetID,
   561  			stakedAssetID,
   562  		)
   563  	}
   564  
   565  	_, err = GetValidator(chainState, tx.Subnet, tx.Validator.NodeID)
   566  	if err == nil {
   567  		return fmt.Errorf(
   568  			"%w: %s on %s",
   569  			ErrDuplicateValidator,
   570  			tx.Validator.NodeID,
   571  			tx.Subnet,
   572  		)
   573  	}
   574  	if err != database.ErrNotFound {
   575  		return fmt.Errorf(
   576  			"failed to find whether %s is a validator on %s: %w",
   577  			tx.Validator.NodeID,
   578  			tx.Subnet,
   579  			err,
   580  		)
   581  	}
   582  
   583  	if tx.Subnet != constants.PrimaryNetworkID {
   584  		if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil {
   585  			return err
   586  		}
   587  	}
   588  
   589  	outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts))
   590  	copy(outs, tx.Outs)
   591  	copy(outs[len(tx.Outs):], tx.StakeOuts)
   592  
   593  	// Verify the flowcheck
   594  	fee, err := feeCalculator.CalculateFee(tx)
   595  	if err != nil {
   596  		return err
   597  	}
   598  	if err := backend.FlowChecker.VerifySpend(
   599  		tx,
   600  		chainState,
   601  		tx.Ins,
   602  		outs,
   603  		sTx.Creds,
   604  		map[ids.ID]uint64{
   605  			backend.Ctx.AVAXAssetID: fee,
   606  		},
   607  	); err != nil {
   608  		return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   609  	}
   610  
   611  	return nil
   612  }
   613  
   614  // verifyAddPermissionlessDelegatorTx carries out the validation for an
   615  // AddPermissionlessDelegatorTx.
   616  func verifyAddPermissionlessDelegatorTx(
   617  	backend *Backend,
   618  	feeCalculator fee.Calculator,
   619  	chainState state.Chain,
   620  	sTx *txs.Tx,
   621  	tx *txs.AddPermissionlessDelegatorTx,
   622  ) error {
   623  	// Verify the tx is well-formed
   624  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   625  		return err
   626  	}
   627  
   628  	var (
   629  		currentTimestamp = chainState.GetTimestamp()
   630  		isDurangoActive  = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp)
   631  	)
   632  	if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil {
   633  		return err
   634  	}
   635  
   636  	if !backend.Bootstrapped.Get() {
   637  		return nil
   638  	}
   639  
   640  	var (
   641  		endTime   = tx.EndTime()
   642  		startTime = currentTimestamp
   643  	)
   644  	if !isDurangoActive {
   645  		startTime = tx.StartTime()
   646  	}
   647  	duration := endTime.Sub(startTime)
   648  
   649  	if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil {
   650  		return err
   651  	}
   652  
   653  	delegatorRules, err := getDelegatorRules(backend, chainState, tx.Subnet)
   654  	if err != nil {
   655  		return err
   656  	}
   657  
   658  	stakedAssetID := tx.StakeOuts[0].AssetID()
   659  	switch {
   660  	case tx.Validator.Wght < delegatorRules.minDelegatorStake:
   661  		// Ensure delegator is staking at least the minimum amount
   662  		return ErrWeightTooSmall
   663  
   664  	case duration < delegatorRules.minStakeDuration:
   665  		// Ensure staking length is not too short
   666  		return ErrStakeTooShort
   667  
   668  	case duration > delegatorRules.maxStakeDuration:
   669  		// Ensure staking length is not too long
   670  		return ErrStakeTooLong
   671  
   672  	case stakedAssetID != delegatorRules.assetID:
   673  		// Wrong assetID used
   674  		return fmt.Errorf(
   675  			"%w: %s != %s",
   676  			ErrWrongStakedAssetID,
   677  			delegatorRules.assetID,
   678  			stakedAssetID,
   679  		)
   680  	}
   681  
   682  	validator, err := GetValidator(chainState, tx.Subnet, tx.Validator.NodeID)
   683  	if err != nil {
   684  		return fmt.Errorf(
   685  			"failed to fetch the validator for %s on %s: %w",
   686  			tx.Validator.NodeID,
   687  			tx.Subnet,
   688  			err,
   689  		)
   690  	}
   691  
   692  	maximumWeight, err := safemath.Mul(
   693  		uint64(delegatorRules.maxValidatorWeightFactor),
   694  		validator.Weight,
   695  	)
   696  	if err != nil {
   697  		maximumWeight = math.MaxUint64
   698  	}
   699  	maximumWeight = min(maximumWeight, delegatorRules.maxValidatorStake)
   700  
   701  	if !txs.BoundedBy(
   702  		startTime,
   703  		endTime,
   704  		validator.StartTime,
   705  		validator.EndTime,
   706  	) {
   707  		return ErrPeriodMismatch
   708  	}
   709  	overDelegated, err := overDelegated(
   710  		chainState,
   711  		validator,
   712  		maximumWeight,
   713  		tx.Validator.Wght,
   714  		startTime,
   715  		endTime,
   716  	)
   717  	if err != nil {
   718  		return err
   719  	}
   720  	if overDelegated {
   721  		return ErrOverDelegated
   722  	}
   723  
   724  	outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts))
   725  	copy(outs, tx.Outs)
   726  	copy(outs[len(tx.Outs):], tx.StakeOuts)
   727  
   728  	if tx.Subnet != constants.PrimaryNetworkID {
   729  		// Invariant: Delegators must only be able to reference validator
   730  		//            transactions that implement [txs.ValidatorTx]. All
   731  		//            validator transactions implement this interface except the
   732  		//            AddSubnetValidatorTx. AddSubnetValidatorTx is the only
   733  		//            permissioned validator, so we verify this delegator is
   734  		//            pointing to a permissionless validator.
   735  		if validator.Priority.IsPermissionedValidator() {
   736  			return ErrDelegateToPermissionedValidator
   737  		}
   738  	}
   739  
   740  	// Verify the flowcheck
   741  	fee, err := feeCalculator.CalculateFee(tx)
   742  	if err != nil {
   743  		return err
   744  	}
   745  	if err := backend.FlowChecker.VerifySpend(
   746  		tx,
   747  		chainState,
   748  		tx.Ins,
   749  		outs,
   750  		sTx.Creds,
   751  		map[ids.ID]uint64{
   752  			backend.Ctx.AVAXAssetID: fee,
   753  		},
   754  	); err != nil {
   755  		return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   756  	}
   757  
   758  	return nil
   759  }
   760  
   761  // Returns an error if the given tx is invalid.
   762  // The transaction is valid if:
   763  // * [sTx]'s creds authorize it to spend the stated inputs.
   764  // * [sTx]'s creds authorize it to transfer ownership of [tx.Subnet].
   765  // * The flow checker passes.
   766  func verifyTransferSubnetOwnershipTx(
   767  	backend *Backend,
   768  	feeCalculator fee.Calculator,
   769  	chainState state.Chain,
   770  	sTx *txs.Tx,
   771  	tx *txs.TransferSubnetOwnershipTx,
   772  ) error {
   773  	var (
   774  		currentTimestamp = chainState.GetTimestamp()
   775  		upgrades         = backend.Config.UpgradeConfig
   776  	)
   777  	if !upgrades.IsDurangoActivated(currentTimestamp) {
   778  		return ErrDurangoUpgradeNotActive
   779  	}
   780  
   781  	// Verify the tx is well-formed
   782  	if err := sTx.SyntacticVerify(backend.Ctx); err != nil {
   783  		return err
   784  	}
   785  
   786  	if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil {
   787  		return err
   788  	}
   789  
   790  	if !backend.Bootstrapped.Get() {
   791  		// Not bootstrapped yet -- don't need to do full verification.
   792  		return nil
   793  	}
   794  
   795  	baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth)
   796  	if err != nil {
   797  		return err
   798  	}
   799  
   800  	// Verify the flowcheck
   801  	fee, err := feeCalculator.CalculateFee(tx)
   802  	if err != nil {
   803  		return err
   804  	}
   805  	if err := backend.FlowChecker.VerifySpend(
   806  		tx,
   807  		chainState,
   808  		tx.Ins,
   809  		tx.Outs,
   810  		baseTxCreds,
   811  		map[ids.ID]uint64{
   812  			backend.Ctx.AVAXAssetID: fee,
   813  		},
   814  	); err != nil {
   815  		return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err)
   816  	}
   817  
   818  	return nil
   819  }
   820  
   821  // Ensure the proposed validator starts after the current time
   822  func verifyStakerStartTime(isDurangoActive bool, chainTime, stakerTime time.Time) error {
   823  	// Pre Durango activation, start time must be after current chain time.
   824  	// Post Durango activation, start time is not validated
   825  	if isDurangoActive {
   826  		return nil
   827  	}
   828  
   829  	if !chainTime.Before(stakerTime) {
   830  		return fmt.Errorf(
   831  			"%w: %s >= %s",
   832  			ErrTimestampNotBeforeStartTime,
   833  			chainTime,
   834  			stakerTime,
   835  		)
   836  	}
   837  	return nil
   838  }