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