github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/standard_tx_executor_test.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  	"math"
     9  	"math/rand"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/stretchr/testify/require"
    14  	"go.uber.org/mock/gomock"
    15  
    16  	"github.com/ava-labs/avalanchego/database"
    17  	"github.com/ava-labs/avalanchego/ids"
    18  	"github.com/ava-labs/avalanchego/snow"
    19  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    20  	"github.com/ava-labs/avalanchego/utils"
    21  	"github.com/ava-labs/avalanchego/utils/constants"
    22  	"github.com/ava-labs/avalanchego/utils/crypto/bls"
    23  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    24  	"github.com/ava-labs/avalanchego/utils/hashing"
    25  	"github.com/ava-labs/avalanchego/utils/units"
    26  	"github.com/ava-labs/avalanchego/vms/components/avax"
    27  	"github.com/ava-labs/avalanchego/vms/components/verify"
    28  	"github.com/ava-labs/avalanchego/vms/platformvm/config"
    29  	"github.com/ava-labs/avalanchego/vms/platformvm/fx/fxmock"
    30  	"github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest"
    31  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    32  	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
    33  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    34  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    35  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    36  	"github.com/ava-labs/avalanchego/vms/platformvm/utxo/utxomock"
    37  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    38  	"github.com/ava-labs/avalanchego/wallet/subnet/primary/common"
    39  )
    40  
    41  // This tests that the math performed during TransformSubnetTx execution can
    42  // never overflow
    43  const _ time.Duration = math.MaxUint32 * time.Second
    44  
    45  var errTest = errors.New("non-nil error")
    46  
    47  func TestStandardTxExecutorAddValidatorTxEmptyID(t *testing.T) {
    48  	require := require.New(t)
    49  	env := newEnvironment(t, upgradetest.ApricotPhase5)
    50  	env.ctx.Lock.Lock()
    51  	defer env.ctx.Lock.Unlock()
    52  
    53  	chainTime := env.state.GetTimestamp()
    54  	startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
    55  
    56  	tests := []struct {
    57  		banffTime time.Time
    58  	}{
    59  		{ // Case: Before banff
    60  			banffTime: chainTime.Add(1),
    61  		},
    62  		{ // Case: At banff
    63  			banffTime: chainTime,
    64  		},
    65  		{ // Case: After banff
    66  			banffTime: chainTime.Add(-1),
    67  		},
    68  	}
    69  	for _, test := range tests {
    70  		// Case: Empty validator node ID after banff
    71  		env.config.UpgradeConfig.BanffTime = test.banffTime
    72  
    73  		wallet := newWallet(t, env, walletConfig{})
    74  
    75  		tx, err := wallet.IssueAddValidatorTx(
    76  			&txs.Validator{
    77  				NodeID: ids.EmptyNodeID,
    78  				Start:  uint64(startTime.Unix()),
    79  				End:    genesistest.DefaultValidatorEndTimeUnix,
    80  				Wght:   env.config.MinValidatorStake,
    81  			},
    82  			&secp256k1fx.OutputOwners{
    83  				Threshold: 1,
    84  				Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
    85  			},
    86  			reward.PercentDenominator,
    87  		)
    88  		require.NoError(err)
    89  
    90  		stateDiff, err := state.NewDiff(lastAcceptedID, env)
    91  		require.NoError(err)
    92  
    93  		feeCalculator := state.PickFeeCalculator(env.config, stateDiff)
    94  		executor := StandardTxExecutor{
    95  			Backend:       &env.backend,
    96  			State:         stateDiff,
    97  			FeeCalculator: feeCalculator,
    98  			Tx:            tx,
    99  		}
   100  		err = tx.Unsigned.Visit(&executor)
   101  		require.ErrorIs(err, errEmptyNodeID)
   102  	}
   103  }
   104  
   105  func TestStandardTxExecutorAddDelegator(t *testing.T) {
   106  	dummyHeight := uint64(1)
   107  	rewardsOwner := &secp256k1fx.OutputOwners{
   108  		Threshold: 1,
   109  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   110  	}
   111  	nodeID := genesistest.DefaultNodeIDs[0]
   112  
   113  	newValidatorID := ids.GenerateTestNodeID()
   114  	newValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(5 * time.Second)
   115  	newValidatorEndTime := genesistest.DefaultValidatorEndTime.Add(-5 * time.Second)
   116  
   117  	// [addMinStakeValidator] adds a new validator to the primary network's
   118  	// pending validator set with the minimum staking amount
   119  	addMinStakeValidator := func(env *environment) {
   120  		require := require.New(t)
   121  
   122  		wallet := newWallet(t, env, walletConfig{
   123  			keys: genesistest.DefaultFundedKeys[:1],
   124  		})
   125  		tx, err := wallet.IssueAddValidatorTx(
   126  			&txs.Validator{
   127  				NodeID: newValidatorID,
   128  				Start:  uint64(newValidatorStartTime.Unix()),
   129  				End:    uint64(newValidatorEndTime.Unix()),
   130  				Wght:   env.config.MinValidatorStake,
   131  			},
   132  			rewardsOwner,
   133  			reward.PercentDenominator,
   134  		)
   135  		require.NoError(err)
   136  
   137  		addValTx := tx.Unsigned.(*txs.AddValidatorTx)
   138  		staker, err := state.NewCurrentStaker(
   139  			tx.ID(),
   140  			addValTx,
   141  			newValidatorStartTime,
   142  			0,
   143  		)
   144  		require.NoError(err)
   145  
   146  		require.NoError(env.state.PutCurrentValidator(staker))
   147  		env.state.AddTx(tx, status.Committed)
   148  		env.state.SetHeight(dummyHeight)
   149  		require.NoError(env.state.Commit())
   150  	}
   151  
   152  	// [addMaxStakeValidator] adds a new validator to the primary network's
   153  	// pending validator set with the maximum staking amount
   154  	addMaxStakeValidator := func(env *environment) {
   155  		require := require.New(t)
   156  
   157  		wallet := newWallet(t, env, walletConfig{
   158  			keys: genesistest.DefaultFundedKeys[:1],
   159  		})
   160  		tx, err := wallet.IssueAddValidatorTx(
   161  			&txs.Validator{
   162  				NodeID: newValidatorID,
   163  				Start:  uint64(newValidatorStartTime.Unix()),
   164  				End:    uint64(newValidatorEndTime.Unix()),
   165  				Wght:   env.config.MaxValidatorStake,
   166  			},
   167  			rewardsOwner,
   168  			reward.PercentDenominator,
   169  		)
   170  		require.NoError(err)
   171  
   172  		addValTx := tx.Unsigned.(*txs.AddValidatorTx)
   173  		staker, err := state.NewCurrentStaker(
   174  			tx.ID(),
   175  			addValTx,
   176  			newValidatorStartTime,
   177  			0,
   178  		)
   179  		require.NoError(err)
   180  
   181  		require.NoError(env.state.PutCurrentValidator(staker))
   182  		env.state.AddTx(tx, status.Committed)
   183  		env.state.SetHeight(dummyHeight)
   184  		require.NoError(env.state.Commit())
   185  	}
   186  
   187  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   188  	currentTimestamp := env.state.GetTimestamp()
   189  
   190  	type test struct {
   191  		description          string
   192  		stakeAmount          uint64
   193  		startTime            time.Time
   194  		endTime              time.Time
   195  		nodeID               ids.NodeID
   196  		feeKeys              []*secp256k1.PrivateKey
   197  		setup                func(*environment)
   198  		AP3Time              time.Time
   199  		expectedExecutionErr error
   200  	}
   201  
   202  	tests := []test{
   203  		{
   204  			description:          "validator stops validating earlier than delegator",
   205  			stakeAmount:          env.config.MinDelegatorStake,
   206  			startTime:            genesistest.DefaultValidatorStartTime.Add(time.Second),
   207  			endTime:              genesistest.DefaultValidatorEndTime.Add(time.Second),
   208  			nodeID:               nodeID,
   209  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   210  			setup:                nil,
   211  			AP3Time:              genesistest.DefaultValidatorStartTime,
   212  			expectedExecutionErr: ErrPeriodMismatch,
   213  		},
   214  		{
   215  			description:          "validator not in the current or pending validator sets",
   216  			stakeAmount:          env.config.MinDelegatorStake,
   217  			startTime:            genesistest.DefaultValidatorStartTime.Add(5 * time.Second),
   218  			endTime:              genesistest.DefaultValidatorEndTime.Add(-5 * time.Second),
   219  			nodeID:               newValidatorID,
   220  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   221  			setup:                nil,
   222  			AP3Time:              genesistest.DefaultValidatorStartTime,
   223  			expectedExecutionErr: database.ErrNotFound,
   224  		},
   225  		{
   226  			description:          "delegator starts before validator",
   227  			stakeAmount:          env.config.MinDelegatorStake,
   228  			startTime:            newValidatorStartTime.Add(-1 * time.Second), // start validating subnet before primary network
   229  			endTime:              newValidatorEndTime,
   230  			nodeID:               newValidatorID,
   231  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   232  			setup:                addMinStakeValidator,
   233  			AP3Time:              genesistest.DefaultValidatorStartTime,
   234  			expectedExecutionErr: ErrPeriodMismatch,
   235  		},
   236  		{
   237  			description:          "delegator stops before validator",
   238  			stakeAmount:          env.config.MinDelegatorStake,
   239  			startTime:            newValidatorStartTime,
   240  			endTime:              newValidatorEndTime.Add(time.Second), // stop validating subnet after stopping validating primary network
   241  			nodeID:               newValidatorID,
   242  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   243  			setup:                addMinStakeValidator,
   244  			AP3Time:              genesistest.DefaultValidatorStartTime,
   245  			expectedExecutionErr: ErrPeriodMismatch,
   246  		},
   247  		{
   248  			description:          "valid",
   249  			stakeAmount:          env.config.MinDelegatorStake,
   250  			startTime:            newValidatorStartTime, // same start time as for primary network
   251  			endTime:              newValidatorEndTime,   // same end time as for primary network
   252  			nodeID:               newValidatorID,
   253  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   254  			setup:                addMinStakeValidator,
   255  			AP3Time:              genesistest.DefaultValidatorStartTime,
   256  			expectedExecutionErr: nil,
   257  		},
   258  		{
   259  			description:          "starts delegating at current timestamp",
   260  			stakeAmount:          env.config.MinDelegatorStake,                              // weight
   261  			startTime:            currentTimestamp,                                          // start time
   262  			endTime:              genesistest.DefaultValidatorEndTime,                       // end time
   263  			nodeID:               nodeID,                                                    // node ID
   264  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, // tx fee payer
   265  			setup:                nil,
   266  			AP3Time:              genesistest.DefaultValidatorStartTime,
   267  			expectedExecutionErr: ErrTimestampNotBeforeStartTime,
   268  		},
   269  		{
   270  			description: "tx fee paying key has no funds",
   271  			stakeAmount: env.config.MinDelegatorStake,                              // weight
   272  			startTime:   genesistest.DefaultValidatorStartTime.Add(time.Second),    // start time
   273  			endTime:     genesistest.DefaultValidatorEndTime,                       // end time
   274  			nodeID:      nodeID,                                                    // node ID
   275  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[1]}, // tx fee payer
   276  			setup: func(env *environment) { // Remove all UTXOs owned by keys[1]
   277  				utxoIDs, err := env.state.UTXOIDs(
   278  					genesistest.DefaultFundedKeys[1].Address().Bytes(),
   279  					ids.Empty,
   280  					math.MaxInt32)
   281  				require.NoError(t, err)
   282  
   283  				for _, utxoID := range utxoIDs {
   284  					env.state.DeleteUTXO(utxoID)
   285  				}
   286  				env.state.SetHeight(dummyHeight)
   287  				require.NoError(t, env.state.Commit())
   288  			},
   289  			AP3Time:              genesistest.DefaultValidatorStartTime,
   290  			expectedExecutionErr: ErrFlowCheckFailed,
   291  		},
   292  		{
   293  			description:          "over delegation before AP3",
   294  			stakeAmount:          env.config.MinDelegatorStake,
   295  			startTime:            newValidatorStartTime, // same start time as for primary network
   296  			endTime:              newValidatorEndTime,   // same end time as for primary network
   297  			nodeID:               newValidatorID,
   298  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   299  			setup:                addMaxStakeValidator,
   300  			AP3Time:              genesistest.DefaultValidatorEndTime,
   301  			expectedExecutionErr: nil,
   302  		},
   303  		{
   304  			description:          "over delegation after AP3",
   305  			stakeAmount:          env.config.MinDelegatorStake,
   306  			startTime:            newValidatorStartTime, // same start time as for primary network
   307  			endTime:              newValidatorEndTime,   // same end time as for primary network
   308  			nodeID:               newValidatorID,
   309  			feeKeys:              []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   310  			setup:                addMaxStakeValidator,
   311  			AP3Time:              genesistest.DefaultValidatorStartTime,
   312  			expectedExecutionErr: ErrOverDelegated,
   313  		},
   314  	}
   315  
   316  	for _, tt := range tests {
   317  		t.Run(tt.description, func(t *testing.T) {
   318  			require := require.New(t)
   319  			env := newEnvironment(t, upgradetest.ApricotPhase5)
   320  			env.config.UpgradeConfig.ApricotPhase3Time = tt.AP3Time
   321  
   322  			wallet := newWallet(t, env, walletConfig{
   323  				keys: tt.feeKeys,
   324  			})
   325  			tx, err := wallet.IssueAddDelegatorTx(
   326  				&txs.Validator{
   327  					NodeID: tt.nodeID,
   328  					Start:  uint64(tt.startTime.Unix()),
   329  					End:    uint64(tt.endTime.Unix()),
   330  					Wght:   tt.stakeAmount,
   331  				},
   332  				rewardsOwner,
   333  			)
   334  			require.NoError(err)
   335  
   336  			if tt.setup != nil {
   337  				tt.setup(env)
   338  			}
   339  
   340  			onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   341  			require.NoError(err)
   342  
   343  			env.config.UpgradeConfig.BanffTime = onAcceptState.GetTimestamp()
   344  
   345  			feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   346  			executor := StandardTxExecutor{
   347  				Backend:       &env.backend,
   348  				State:         onAcceptState,
   349  				FeeCalculator: feeCalculator,
   350  				Tx:            tx,
   351  			}
   352  			err = tx.Unsigned.Visit(&executor)
   353  			require.ErrorIs(err, tt.expectedExecutionErr)
   354  		})
   355  	}
   356  }
   357  
   358  func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) {
   359  	require := require.New(t)
   360  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   361  	env.ctx.Lock.Lock()
   362  	defer env.ctx.Lock.Unlock()
   363  
   364  	nodeID := genesistest.DefaultNodeIDs[0]
   365  	subnetID := testSubnet1.ID()
   366  
   367  	{
   368  		// Case: Proposed validator currently validating primary network
   369  		// but stops validating subnet after stops validating primary network
   370  		// (note that keys[0] is a genesis validator)
   371  		startTime := genesistest.DefaultValidatorStartTime.Add(time.Second)
   372  
   373  		wallet := newWallet(t, env, walletConfig{
   374  			subnetIDs: []ids.ID{subnetID},
   375  		})
   376  		tx, err := wallet.IssueAddSubnetValidatorTx(
   377  			&txs.SubnetValidator{
   378  				Validator: txs.Validator{
   379  					NodeID: nodeID,
   380  					Start:  uint64(startTime.Unix()),
   381  					End:    genesistest.DefaultValidatorEndTimeUnix + 1,
   382  					Wght:   genesistest.DefaultValidatorWeight,
   383  				},
   384  				Subnet: subnetID,
   385  			},
   386  		)
   387  		require.NoError(err)
   388  
   389  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   390  		require.NoError(err)
   391  
   392  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   393  		executor := StandardTxExecutor{
   394  			Backend:       &env.backend,
   395  			State:         onAcceptState,
   396  			FeeCalculator: feeCalculator,
   397  			Tx:            tx,
   398  		}
   399  		err = tx.Unsigned.Visit(&executor)
   400  		require.ErrorIs(err, ErrPeriodMismatch)
   401  	}
   402  
   403  	{
   404  		// Case: Proposed validator currently validating primary network
   405  		// and proposed subnet validation period is subset of
   406  		// primary network validation period
   407  		// (note that keys[0] is a genesis validator)
   408  		wallet := newWallet(t, env, walletConfig{
   409  			subnetIDs: []ids.ID{subnetID},
   410  		})
   411  		tx, err := wallet.IssueAddSubnetValidatorTx(
   412  			&txs.SubnetValidator{
   413  				Validator: txs.Validator{
   414  					NodeID: nodeID,
   415  					Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   416  					End:    genesistest.DefaultValidatorEndTimeUnix,
   417  					Wght:   genesistest.DefaultValidatorWeight,
   418  				},
   419  				Subnet: subnetID,
   420  			},
   421  		)
   422  		require.NoError(err)
   423  
   424  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   425  		require.NoError(err)
   426  
   427  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   428  		executor := StandardTxExecutor{
   429  			Backend:       &env.backend,
   430  			State:         onAcceptState,
   431  			FeeCalculator: feeCalculator,
   432  			Tx:            tx,
   433  		}
   434  		require.NoError(tx.Unsigned.Visit(&executor))
   435  	}
   436  
   437  	// Add a validator to pending validator set of primary network
   438  	// Starts validating primary network 10 seconds after genesis
   439  	pendingDSValidatorID := ids.GenerateTestNodeID()
   440  	dsStartTime := genesistest.DefaultValidatorStartTime.Add(10 * time.Second)
   441  	dsEndTime := dsStartTime.Add(5 * defaultMinStakingDuration)
   442  
   443  	wallet := newWallet(t, env, walletConfig{})
   444  	addDSTx, err := wallet.IssueAddValidatorTx(
   445  		&txs.Validator{
   446  			NodeID: pendingDSValidatorID,
   447  			Start:  uint64(dsStartTime.Unix()),
   448  			End:    uint64(dsEndTime.Unix()),
   449  			Wght:   env.config.MinValidatorStake,
   450  		},
   451  		&secp256k1fx.OutputOwners{
   452  			Threshold: 1,
   453  			Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   454  		},
   455  		reward.PercentDenominator,
   456  	)
   457  	require.NoError(err)
   458  
   459  	{
   460  		// Case: Proposed validator isn't in pending or current validator sets
   461  		wallet := newWallet(t, env, walletConfig{
   462  			subnetIDs: []ids.ID{subnetID},
   463  		})
   464  		tx, err := wallet.IssueAddSubnetValidatorTx(
   465  			&txs.SubnetValidator{
   466  				Validator: txs.Validator{
   467  					NodeID: pendingDSValidatorID,
   468  					Start:  uint64(dsStartTime.Unix()), // start validating subnet before primary network
   469  					End:    uint64(dsEndTime.Unix()),
   470  					Wght:   genesistest.DefaultValidatorWeight,
   471  				},
   472  				Subnet: subnetID,
   473  			},
   474  		)
   475  		require.NoError(err)
   476  
   477  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   478  		require.NoError(err)
   479  
   480  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   481  		executor := StandardTxExecutor{
   482  			Backend:       &env.backend,
   483  			State:         onAcceptState,
   484  			FeeCalculator: feeCalculator,
   485  			Tx:            tx,
   486  		}
   487  		err = tx.Unsigned.Visit(&executor)
   488  		require.ErrorIs(err, ErrNotValidator)
   489  	}
   490  
   491  	addValTx := addDSTx.Unsigned.(*txs.AddValidatorTx)
   492  	staker, err := state.NewCurrentStaker(
   493  		addDSTx.ID(),
   494  		addValTx,
   495  		dsStartTime,
   496  		0,
   497  	)
   498  	require.NoError(err)
   499  
   500  	require.NoError(env.state.PutCurrentValidator(staker))
   501  	env.state.AddTx(addDSTx, status.Committed)
   502  	dummyHeight := uint64(1)
   503  	env.state.SetHeight(dummyHeight)
   504  	require.NoError(env.state.Commit())
   505  
   506  	// Node with ID key.Address() now a pending validator for primary network
   507  
   508  	{
   509  		// Case: Proposed validator is pending validator of primary network
   510  		// but starts validating subnet before primary network
   511  		wallet := newWallet(t, env, walletConfig{
   512  			subnetIDs: []ids.ID{subnetID},
   513  		})
   514  		tx, err := wallet.IssueAddSubnetValidatorTx(
   515  			&txs.SubnetValidator{
   516  				Validator: txs.Validator{
   517  					NodeID: pendingDSValidatorID,
   518  					Start:  uint64(dsStartTime.Unix()) - 1, // start validating subnet before primary network
   519  					End:    uint64(dsEndTime.Unix()),
   520  					Wght:   genesistest.DefaultValidatorWeight,
   521  				},
   522  				Subnet: subnetID,
   523  			},
   524  		)
   525  		require.NoError(err)
   526  
   527  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   528  		require.NoError(err)
   529  
   530  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   531  		executor := StandardTxExecutor{
   532  			Backend:       &env.backend,
   533  			State:         onAcceptState,
   534  			FeeCalculator: feeCalculator,
   535  			Tx:            tx,
   536  		}
   537  		err = tx.Unsigned.Visit(&executor)
   538  		require.ErrorIs(err, ErrPeriodMismatch)
   539  	}
   540  
   541  	{
   542  		// Case: Proposed validator is pending validator of primary network
   543  		// but stops validating subnet after primary network
   544  		wallet := newWallet(t, env, walletConfig{
   545  			subnetIDs: []ids.ID{subnetID},
   546  		})
   547  		tx, err := wallet.IssueAddSubnetValidatorTx(
   548  			&txs.SubnetValidator{
   549  				Validator: txs.Validator{
   550  					NodeID: pendingDSValidatorID,
   551  					Start:  uint64(dsStartTime.Unix()),
   552  					End:    uint64(dsEndTime.Unix()) + 1, // stop validating subnet after stopping validating primary network
   553  					Wght:   genesistest.DefaultValidatorWeight,
   554  				},
   555  				Subnet: subnetID,
   556  			},
   557  		)
   558  		require.NoError(err)
   559  
   560  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   561  		require.NoError(err)
   562  
   563  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   564  		executor := StandardTxExecutor{
   565  			Backend:       &env.backend,
   566  			State:         onAcceptState,
   567  			FeeCalculator: feeCalculator,
   568  			Tx:            tx,
   569  		}
   570  		err = tx.Unsigned.Visit(&executor)
   571  		require.ErrorIs(err, ErrPeriodMismatch)
   572  	}
   573  
   574  	{
   575  		// Case: Proposed validator is pending validator of primary network and
   576  		// period validating subnet is subset of time validating primary network
   577  		wallet := newWallet(t, env, walletConfig{
   578  			subnetIDs: []ids.ID{subnetID},
   579  		})
   580  		tx, err := wallet.IssueAddSubnetValidatorTx(
   581  			&txs.SubnetValidator{
   582  				Validator: txs.Validator{
   583  					NodeID: pendingDSValidatorID,
   584  					Start:  uint64(dsStartTime.Unix()), // same start time as for primary network
   585  					End:    uint64(dsEndTime.Unix()),   // same end time as for primary network
   586  					Wght:   genesistest.DefaultValidatorWeight,
   587  				},
   588  				Subnet: subnetID,
   589  			},
   590  		)
   591  		require.NoError(err)
   592  
   593  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   594  		require.NoError(err)
   595  
   596  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   597  		executor := StandardTxExecutor{
   598  			Backend:       &env.backend,
   599  			State:         onAcceptState,
   600  			FeeCalculator: feeCalculator,
   601  			Tx:            tx,
   602  		}
   603  		require.NoError(tx.Unsigned.Visit(&executor))
   604  	}
   605  
   606  	// Case: Proposed validator start validating at/before current timestamp
   607  	// First, advance the timestamp
   608  	newTimestamp := genesistest.DefaultValidatorStartTime.Add(2 * time.Second)
   609  	env.state.SetTimestamp(newTimestamp)
   610  
   611  	{
   612  		wallet := newWallet(t, env, walletConfig{
   613  			subnetIDs: []ids.ID{subnetID},
   614  		})
   615  		tx, err := wallet.IssueAddSubnetValidatorTx(
   616  			&txs.SubnetValidator{
   617  				Validator: txs.Validator{
   618  					NodeID: nodeID,
   619  					Start:  uint64(newTimestamp.Unix()),
   620  					End:    uint64(newTimestamp.Add(defaultMinStakingDuration).Unix()),
   621  					Wght:   genesistest.DefaultValidatorWeight,
   622  				},
   623  				Subnet: subnetID,
   624  			},
   625  		)
   626  		require.NoError(err)
   627  
   628  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   629  		require.NoError(err)
   630  
   631  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   632  		executor := StandardTxExecutor{
   633  			Backend:       &env.backend,
   634  			State:         onAcceptState,
   635  			FeeCalculator: feeCalculator,
   636  			Tx:            tx,
   637  		}
   638  		err = tx.Unsigned.Visit(&executor)
   639  		require.ErrorIs(err, ErrTimestampNotBeforeStartTime)
   640  	}
   641  
   642  	// reset the timestamp
   643  	env.state.SetTimestamp(genesistest.DefaultValidatorStartTime)
   644  
   645  	// Case: Proposed validator already validating the subnet
   646  	// First, add validator as validator of subnet
   647  	wallet = newWallet(t, env, walletConfig{
   648  		subnetIDs: []ids.ID{subnetID},
   649  	})
   650  	subnetTx, err := wallet.IssueAddSubnetValidatorTx(
   651  		&txs.SubnetValidator{
   652  			Validator: txs.Validator{
   653  				NodeID: nodeID,
   654  				Start:  genesistest.DefaultValidatorStartTimeUnix,
   655  				End:    genesistest.DefaultValidatorEndTimeUnix,
   656  				Wght:   genesistest.DefaultValidatorWeight,
   657  			},
   658  			Subnet: subnetID,
   659  		},
   660  	)
   661  	require.NoError(err)
   662  
   663  	addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx)
   664  	staker, err = state.NewCurrentStaker(
   665  		subnetTx.ID(),
   666  		addSubnetValTx,
   667  		genesistest.DefaultValidatorStartTime,
   668  		0,
   669  	)
   670  	require.NoError(err)
   671  
   672  	require.NoError(env.state.PutCurrentValidator(staker))
   673  	env.state.AddTx(subnetTx, status.Committed)
   674  	env.state.SetHeight(dummyHeight)
   675  	require.NoError(env.state.Commit())
   676  
   677  	{
   678  		// Node with ID nodeIDKey.Address() now validating subnet with ID testSubnet1.ID
   679  		startTime := genesistest.DefaultValidatorStartTime.Add(time.Second)
   680  		wallet := newWallet(t, env, walletConfig{
   681  			subnetIDs: []ids.ID{subnetID},
   682  		})
   683  		tx, err := wallet.IssueAddSubnetValidatorTx(
   684  			&txs.SubnetValidator{
   685  				Validator: txs.Validator{
   686  					NodeID: nodeID,
   687  					Start:  uint64(startTime.Unix()),
   688  					End:    genesistest.DefaultValidatorEndTimeUnix,
   689  					Wght:   genesistest.DefaultValidatorWeight,
   690  				},
   691  				Subnet: subnetID,
   692  			},
   693  		)
   694  		require.NoError(err)
   695  
   696  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   697  		require.NoError(err)
   698  
   699  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   700  		executor := StandardTxExecutor{
   701  			Backend:       &env.backend,
   702  			State:         onAcceptState,
   703  			FeeCalculator: feeCalculator,
   704  			Tx:            tx,
   705  		}
   706  		err = tx.Unsigned.Visit(&executor)
   707  		require.ErrorIs(err, ErrDuplicateValidator)
   708  	}
   709  
   710  	env.state.DeleteCurrentValidator(staker)
   711  	env.state.SetHeight(dummyHeight)
   712  	require.NoError(env.state.Commit())
   713  
   714  	{
   715  		// Case: Duplicate signatures
   716  		startTime := genesistest.DefaultValidatorStartTime.Add(time.Second)
   717  		wallet := newWallet(t, env, walletConfig{
   718  			subnetIDs: []ids.ID{subnetID},
   719  		})
   720  		tx, err := wallet.IssueAddSubnetValidatorTx(
   721  			&txs.SubnetValidator{
   722  				Validator: txs.Validator{
   723  					NodeID: nodeID,
   724  					Start:  uint64(startTime.Unix()),
   725  					End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()) + 1,
   726  					Wght:   genesistest.DefaultValidatorWeight,
   727  				},
   728  				Subnet: subnetID,
   729  			},
   730  		)
   731  		require.NoError(err)
   732  
   733  		// Duplicate a signature
   734  		addSubnetValidatorTx := tx.Unsigned.(*txs.AddSubnetValidatorTx)
   735  		input := addSubnetValidatorTx.SubnetAuth.(*secp256k1fx.Input)
   736  		input.SigIndices = append(input.SigIndices, input.SigIndices[0])
   737  		// This tx was syntactically verified when it was created...pretend it wasn't so we don't use cache
   738  		addSubnetValidatorTx.SyntacticallyVerified = false
   739  
   740  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   741  		require.NoError(err)
   742  
   743  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   744  		executor := StandardTxExecutor{
   745  			Backend:       &env.backend,
   746  			State:         onAcceptState,
   747  			FeeCalculator: feeCalculator,
   748  			Tx:            tx,
   749  		}
   750  		err = tx.Unsigned.Visit(&executor)
   751  		require.ErrorIs(err, secp256k1fx.ErrInputIndicesNotSortedUnique)
   752  	}
   753  
   754  	{
   755  		// Case: Too few signatures
   756  		startTime := genesistest.DefaultValidatorStartTime.Add(time.Second)
   757  		wallet := newWallet(t, env, walletConfig{
   758  			subnetIDs: []ids.ID{subnetID},
   759  		})
   760  		tx, err := wallet.IssueAddSubnetValidatorTx(
   761  			&txs.SubnetValidator{
   762  				Validator: txs.Validator{
   763  					NodeID: nodeID,
   764  					Start:  uint64(startTime.Unix()),
   765  					End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()),
   766  					Wght:   genesistest.DefaultValidatorWeight,
   767  				},
   768  				Subnet: subnetID,
   769  			},
   770  		)
   771  		require.NoError(err)
   772  
   773  		// Remove a signature
   774  		addSubnetValidatorTx := tx.Unsigned.(*txs.AddSubnetValidatorTx)
   775  		input := addSubnetValidatorTx.SubnetAuth.(*secp256k1fx.Input)
   776  		input.SigIndices = input.SigIndices[1:]
   777  		// This tx was syntactically verified when it was created...pretend it wasn't so we don't use cache
   778  		addSubnetValidatorTx.SyntacticallyVerified = false
   779  
   780  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   781  		require.NoError(err)
   782  
   783  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   784  		executor := StandardTxExecutor{
   785  			Backend:       &env.backend,
   786  			State:         onAcceptState,
   787  			FeeCalculator: feeCalculator,
   788  			Tx:            tx,
   789  		}
   790  		err = tx.Unsigned.Visit(&executor)
   791  		require.ErrorIs(err, errUnauthorizedSubnetModification)
   792  	}
   793  
   794  	{
   795  		// Case: Control Signature from invalid key (keys[3] is not a control key)
   796  		startTime := genesistest.DefaultValidatorStartTime.Add(time.Second)
   797  		wallet := newWallet(t, env, walletConfig{
   798  			subnetIDs: []ids.ID{subnetID},
   799  		})
   800  		tx, err := wallet.IssueAddSubnetValidatorTx(
   801  			&txs.SubnetValidator{
   802  				Validator: txs.Validator{
   803  					NodeID: nodeID,
   804  					Start:  uint64(startTime.Unix()),
   805  					End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()),
   806  					Wght:   genesistest.DefaultValidatorWeight,
   807  				},
   808  				Subnet: subnetID,
   809  			},
   810  		)
   811  		require.NoError(err)
   812  
   813  		// Replace a valid signature with one from keys[3]
   814  		sig, err := genesistest.DefaultFundedKeys[3].SignHash(hashing.ComputeHash256(tx.Unsigned.Bytes()))
   815  		require.NoError(err)
   816  		copy(tx.Creds[0].(*secp256k1fx.Credential).Sigs[0][:], sig)
   817  
   818  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   819  		require.NoError(err)
   820  
   821  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   822  		executor := StandardTxExecutor{
   823  			Backend:       &env.backend,
   824  			State:         onAcceptState,
   825  			FeeCalculator: feeCalculator,
   826  			Tx:            tx,
   827  		}
   828  		err = tx.Unsigned.Visit(&executor)
   829  		require.ErrorIs(err, errUnauthorizedSubnetModification)
   830  	}
   831  
   832  	{
   833  		// Case: Proposed validator in pending validator set for subnet
   834  		// First, add validator to pending validator set of subnet
   835  		startTime := genesistest.DefaultValidatorStartTime.Add(time.Second)
   836  		wallet := newWallet(t, env, walletConfig{
   837  			subnetIDs: []ids.ID{subnetID},
   838  		})
   839  		tx, err := wallet.IssueAddSubnetValidatorTx(
   840  			&txs.SubnetValidator{
   841  				Validator: txs.Validator{
   842  					NodeID: nodeID,
   843  					Start:  uint64(startTime.Unix()) + 1,
   844  					End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()) + 1,
   845  					Wght:   genesistest.DefaultValidatorWeight,
   846  				},
   847  				Subnet: subnetID,
   848  			},
   849  		)
   850  		require.NoError(err)
   851  
   852  		addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx)
   853  		staker, err = state.NewCurrentStaker(
   854  			subnetTx.ID(),
   855  			addSubnetValTx,
   856  			genesistest.DefaultValidatorStartTime,
   857  			0,
   858  		)
   859  		require.NoError(err)
   860  
   861  		require.NoError(env.state.PutCurrentValidator(staker))
   862  		env.state.AddTx(tx, status.Committed)
   863  		env.state.SetHeight(dummyHeight)
   864  		require.NoError(env.state.Commit())
   865  
   866  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   867  		require.NoError(err)
   868  
   869  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   870  		executor := StandardTxExecutor{
   871  			Backend:       &env.backend,
   872  			State:         onAcceptState,
   873  			FeeCalculator: feeCalculator,
   874  			Tx:            tx,
   875  		}
   876  		err = tx.Unsigned.Visit(&executor)
   877  		require.ErrorIs(err, ErrDuplicateValidator)
   878  	}
   879  }
   880  
   881  func TestEtnaStandardTxExecutorAddSubnetValidator(t *testing.T) {
   882  	require := require.New(t)
   883  	env := newEnvironment(t, upgradetest.Etna)
   884  	env.ctx.Lock.Lock()
   885  	defer env.ctx.Lock.Unlock()
   886  
   887  	nodeID := genesistest.DefaultNodeIDs[0]
   888  	subnetID := testSubnet1.ID()
   889  
   890  	wallet := newWallet(t, env, walletConfig{
   891  		subnetIDs: []ids.ID{subnetID},
   892  	})
   893  	tx, err := wallet.IssueAddSubnetValidatorTx(
   894  		&txs.SubnetValidator{
   895  			Validator: txs.Validator{
   896  				NodeID: nodeID,
   897  				Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   898  				End:    genesistest.DefaultValidatorEndTimeUnix,
   899  				Wght:   genesistest.DefaultValidatorWeight,
   900  			},
   901  			Subnet: subnetID,
   902  		},
   903  	)
   904  	require.NoError(err)
   905  
   906  	onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   907  	require.NoError(err)
   908  
   909  	onAcceptState.SetSubnetManager(subnetID, ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'})
   910  
   911  	executor := StandardTxExecutor{
   912  		Backend: &env.backend,
   913  		State:   onAcceptState,
   914  		Tx:      tx,
   915  	}
   916  	err = tx.Unsigned.Visit(&executor)
   917  	require.ErrorIs(err, errIsImmutable)
   918  }
   919  
   920  func TestBanffStandardTxExecutorAddValidator(t *testing.T) {
   921  	require := require.New(t)
   922  	env := newEnvironment(t, upgradetest.Banff)
   923  	env.ctx.Lock.Lock()
   924  	defer env.ctx.Lock.Unlock()
   925  
   926  	nodeID := ids.GenerateTestNodeID()
   927  	rewardsOwner := &secp256k1fx.OutputOwners{
   928  		Threshold: 1,
   929  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   930  	}
   931  
   932  	{
   933  		// Case: Validator's start time too early
   934  		wallet := newWallet(t, env, walletConfig{})
   935  		tx, err := wallet.IssueAddValidatorTx(
   936  			&txs.Validator{
   937  				NodeID: nodeID,
   938  				Start:  genesistest.DefaultValidatorStartTimeUnix - 1,
   939  				End:    genesistest.DefaultValidatorEndTimeUnix,
   940  				Wght:   env.config.MinValidatorStake,
   941  			},
   942  			rewardsOwner,
   943  			reward.PercentDenominator,
   944  		)
   945  		require.NoError(err)
   946  
   947  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   948  		require.NoError(err)
   949  
   950  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   951  		executor := StandardTxExecutor{
   952  			Backend:       &env.backend,
   953  			State:         onAcceptState,
   954  			FeeCalculator: feeCalculator,
   955  			Tx:            tx,
   956  		}
   957  		err = tx.Unsigned.Visit(&executor)
   958  		require.ErrorIs(err, ErrTimestampNotBeforeStartTime)
   959  	}
   960  
   961  	{
   962  		// Case: Validator in current validator set of primary network
   963  		wallet := newWallet(t, env, walletConfig{})
   964  
   965  		startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
   966  		tx, err := wallet.IssueAddValidatorTx(
   967  			&txs.Validator{
   968  				NodeID: nodeID,
   969  				Start:  uint64(startTime.Unix()),
   970  				End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()),
   971  				Wght:   env.config.MinValidatorStake,
   972  			},
   973  			rewardsOwner,
   974  			reward.PercentDenominator,
   975  		)
   976  		require.NoError(err)
   977  
   978  		addValTx := tx.Unsigned.(*txs.AddValidatorTx)
   979  		staker, err := state.NewCurrentStaker(
   980  			tx.ID(),
   981  			addValTx,
   982  			startTime,
   983  			0,
   984  		)
   985  		require.NoError(err)
   986  
   987  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
   988  		require.NoError(err)
   989  
   990  		require.NoError(onAcceptState.PutCurrentValidator(staker))
   991  		onAcceptState.AddTx(tx, status.Committed)
   992  
   993  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
   994  		executor := StandardTxExecutor{
   995  			Backend:       &env.backend,
   996  			State:         onAcceptState,
   997  			FeeCalculator: feeCalculator,
   998  			Tx:            tx,
   999  		}
  1000  		err = tx.Unsigned.Visit(&executor)
  1001  		require.ErrorIs(err, ErrAlreadyValidator)
  1002  	}
  1003  
  1004  	{
  1005  		// Case: Validator in pending validator set of primary network
  1006  		wallet := newWallet(t, env, walletConfig{})
  1007  
  1008  		startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
  1009  		tx, err := wallet.IssueAddValidatorTx(
  1010  			&txs.Validator{
  1011  				NodeID: nodeID,
  1012  				Start:  uint64(startTime.Unix()),
  1013  				End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()),
  1014  				Wght:   env.config.MinValidatorStake,
  1015  			},
  1016  			rewardsOwner,
  1017  			reward.PercentDenominator,
  1018  		)
  1019  		require.NoError(err)
  1020  
  1021  		staker, err := state.NewPendingStaker(
  1022  			tx.ID(),
  1023  			tx.Unsigned.(*txs.AddValidatorTx),
  1024  		)
  1025  		require.NoError(err)
  1026  
  1027  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
  1028  		require.NoError(err)
  1029  
  1030  		require.NoError(onAcceptState.PutPendingValidator(staker))
  1031  		onAcceptState.AddTx(tx, status.Committed)
  1032  
  1033  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
  1034  		executor := StandardTxExecutor{
  1035  			Backend:       &env.backend,
  1036  			State:         onAcceptState,
  1037  			FeeCalculator: feeCalculator,
  1038  			Tx:            tx,
  1039  		}
  1040  		err = tx.Unsigned.Visit(&executor)
  1041  		require.ErrorIs(err, ErrAlreadyValidator)
  1042  	}
  1043  
  1044  	{
  1045  		// Case: Validator doesn't have enough tokens to cover stake amount
  1046  		wallet := newWallet(t, env, walletConfig{
  1047  			keys: genesistest.DefaultFundedKeys[:1],
  1048  		})
  1049  
  1050  		startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
  1051  		tx, err := wallet.IssueAddValidatorTx(
  1052  			&txs.Validator{
  1053  				NodeID: nodeID,
  1054  				Start:  uint64(startTime.Unix()),
  1055  				End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()),
  1056  				Wght:   env.config.MinValidatorStake,
  1057  			},
  1058  			rewardsOwner,
  1059  			reward.PercentDenominator,
  1060  		)
  1061  		require.NoError(err)
  1062  
  1063  		// Remove all UTXOs owned by preFundedKeys[0]
  1064  		utxoIDs, err := env.state.UTXOIDs(genesistest.DefaultFundedKeys[0].Address().Bytes(), ids.Empty, math.MaxInt32)
  1065  		require.NoError(err)
  1066  
  1067  		onAcceptState, err := state.NewDiff(lastAcceptedID, env)
  1068  		require.NoError(err)
  1069  
  1070  		for _, utxoID := range utxoIDs {
  1071  			onAcceptState.DeleteUTXO(utxoID)
  1072  		}
  1073  
  1074  		feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
  1075  		executor := StandardTxExecutor{
  1076  			Backend:       &env.backend,
  1077  			FeeCalculator: feeCalculator,
  1078  			State:         onAcceptState,
  1079  			Tx:            tx,
  1080  		}
  1081  		err = tx.Unsigned.Visit(&executor)
  1082  		require.ErrorIs(err, ErrFlowCheckFailed)
  1083  	}
  1084  }
  1085  
  1086  // Verifies that [AddValidatorTx] and [AddDelegatorTx] are disabled post-Durango
  1087  func TestDurangoDisabledTransactions(t *testing.T) {
  1088  	type test struct {
  1089  		name        string
  1090  		buildTx     func(t *testing.T, env *environment) *txs.Tx
  1091  		expectedErr error
  1092  	}
  1093  
  1094  	rewardsOwner := &secp256k1fx.OutputOwners{
  1095  		Threshold: 1,
  1096  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
  1097  	}
  1098  
  1099  	tests := []test{
  1100  		{
  1101  			name: "AddValidatorTx",
  1102  			buildTx: func(t *testing.T, env *environment) *txs.Tx {
  1103  				var (
  1104  					nodeID    = ids.GenerateTestNodeID()
  1105  					chainTime = env.state.GetTimestamp()
  1106  					endTime   = chainTime.Add(defaultMaxStakingDuration)
  1107  				)
  1108  
  1109  				wallet := newWallet(t, env, walletConfig{})
  1110  				tx, err := wallet.IssueAddValidatorTx(
  1111  					&txs.Validator{
  1112  						NodeID: nodeID,
  1113  						Start:  0,
  1114  						End:    uint64(endTime.Unix()),
  1115  						Wght:   defaultMinValidatorStake,
  1116  					},
  1117  					rewardsOwner,
  1118  					reward.PercentDenominator,
  1119  				)
  1120  				require.NoError(t, err)
  1121  
  1122  				return tx
  1123  			},
  1124  			expectedErr: ErrAddValidatorTxPostDurango,
  1125  		},
  1126  		{
  1127  			name: "AddDelegatorTx",
  1128  			buildTx: func(t *testing.T, env *environment) *txs.Tx {
  1129  				require := require.New(t)
  1130  
  1131  				var primaryValidator *state.Staker
  1132  				it, err := env.state.GetCurrentStakerIterator()
  1133  				require.NoError(err)
  1134  				for it.Next() {
  1135  					staker := it.Value()
  1136  					if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority {
  1137  						continue
  1138  					}
  1139  					primaryValidator = staker
  1140  					break
  1141  				}
  1142  				it.Release()
  1143  
  1144  				wallet := newWallet(t, env, walletConfig{})
  1145  				tx, err := wallet.IssueAddDelegatorTx(
  1146  					&txs.Validator{
  1147  						NodeID: primaryValidator.NodeID,
  1148  						Start:  0,
  1149  						End:    uint64(primaryValidator.EndTime.Unix()),
  1150  						Wght:   defaultMinValidatorStake,
  1151  					},
  1152  					rewardsOwner,
  1153  				)
  1154  				require.NoError(err)
  1155  
  1156  				return tx
  1157  			},
  1158  			expectedErr: ErrAddDelegatorTxPostDurango,
  1159  		},
  1160  	}
  1161  
  1162  	for _, tt := range tests {
  1163  		t.Run(tt.name, func(t *testing.T) {
  1164  			require := require.New(t)
  1165  
  1166  			env := newEnvironment(t, upgradetest.Durango)
  1167  			env.ctx.Lock.Lock()
  1168  			defer env.ctx.Lock.Unlock()
  1169  
  1170  			onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1171  			require.NoError(err)
  1172  
  1173  			tx := tt.buildTx(t, env)
  1174  
  1175  			feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
  1176  			err = tx.Unsigned.Visit(&StandardTxExecutor{
  1177  				Backend:       &env.backend,
  1178  				State:         onAcceptState,
  1179  				FeeCalculator: feeCalculator,
  1180  				Tx:            tx,
  1181  			})
  1182  			require.ErrorIs(err, tt.expectedErr)
  1183  		})
  1184  	}
  1185  }
  1186  
  1187  // Verifies that the Memo field is required to be empty post-Durango
  1188  func TestDurangoMemoField(t *testing.T) {
  1189  	type test struct {
  1190  		name      string
  1191  		setupTest func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff)
  1192  	}
  1193  
  1194  	owners := &secp256k1fx.OutputOwners{
  1195  		Threshold: 1,
  1196  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
  1197  	}
  1198  
  1199  	tests := []test{
  1200  		{
  1201  			name: "AddSubnetValidatorTx",
  1202  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1203  				require := require.New(t)
  1204  
  1205  				var primaryValidator *state.Staker
  1206  				it, err := env.state.GetCurrentStakerIterator()
  1207  				require.NoError(err)
  1208  				for it.Next() {
  1209  					staker := it.Value()
  1210  					if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority {
  1211  						continue
  1212  					}
  1213  					primaryValidator = staker
  1214  					break
  1215  				}
  1216  				it.Release()
  1217  
  1218  				subnetID := testSubnet1.ID()
  1219  				wallet := newWallet(t, env, walletConfig{
  1220  					subnetIDs: []ids.ID{subnetID},
  1221  				})
  1222  				tx, err := wallet.IssueAddSubnetValidatorTx(
  1223  					&txs.SubnetValidator{
  1224  						Validator: txs.Validator{
  1225  							NodeID: primaryValidator.NodeID,
  1226  							Start:  0,
  1227  							End:    uint64(primaryValidator.EndTime.Unix()),
  1228  							Wght:   defaultMinValidatorStake,
  1229  						},
  1230  						Subnet: subnetID,
  1231  					},
  1232  					common.WithMemo(memoField),
  1233  				)
  1234  				require.NoError(err)
  1235  
  1236  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1237  				require.NoError(err)
  1238  				return tx, onAcceptState
  1239  			},
  1240  		},
  1241  		{
  1242  			name: "CreateChainTx",
  1243  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1244  				require := require.New(t)
  1245  
  1246  				subnetID := testSubnet1.ID()
  1247  				wallet := newWallet(t, env, walletConfig{
  1248  					subnetIDs: []ids.ID{subnetID},
  1249  				})
  1250  
  1251  				tx, err := wallet.IssueCreateChainTx(
  1252  					subnetID,
  1253  					[]byte{},
  1254  					ids.GenerateTestID(),
  1255  					[]ids.ID{},
  1256  					"aaa",
  1257  					common.WithMemo(memoField),
  1258  				)
  1259  				require.NoError(err)
  1260  
  1261  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1262  				require.NoError(err)
  1263  				return tx, onAcceptState
  1264  			},
  1265  		},
  1266  		{
  1267  			name: "CreateSubnetTx",
  1268  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1269  				require := require.New(t)
  1270  
  1271  				wallet := newWallet(t, env, walletConfig{})
  1272  				tx, err := wallet.IssueCreateSubnetTx(
  1273  					owners,
  1274  					common.WithMemo(memoField),
  1275  				)
  1276  				require.NoError(err)
  1277  
  1278  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1279  				require.NoError(err)
  1280  				return tx, onAcceptState
  1281  			},
  1282  		},
  1283  		{
  1284  			name: "ImportTx",
  1285  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1286  				require := require.New(t)
  1287  
  1288  				var (
  1289  					sourceChain  = env.ctx.XChainID
  1290  					sourceKey    = genesistest.DefaultFundedKeys[1]
  1291  					sourceAmount = 10 * units.Avax
  1292  				)
  1293  
  1294  				sharedMemory := fundedSharedMemory(
  1295  					t,
  1296  					env,
  1297  					sourceKey,
  1298  					sourceChain,
  1299  					map[ids.ID]uint64{
  1300  						env.ctx.AVAXAssetID: sourceAmount,
  1301  					},
  1302  					rand.NewSource(0),
  1303  				)
  1304  				env.msm.SharedMemory = sharedMemory
  1305  
  1306  				wallet := newWallet(t, env, walletConfig{
  1307  					chainIDs: []ids.ID{sourceChain},
  1308  				})
  1309  
  1310  				tx, err := wallet.IssueImportTx(
  1311  					sourceChain,
  1312  					owners,
  1313  					common.WithMemo(memoField),
  1314  				)
  1315  				require.NoError(err)
  1316  
  1317  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1318  				require.NoError(err)
  1319  				return tx, onAcceptState
  1320  			},
  1321  		},
  1322  		{
  1323  			name: "ExportTx",
  1324  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1325  				require := require.New(t)
  1326  
  1327  				wallet := newWallet(t, env, walletConfig{})
  1328  				tx, err := wallet.IssueExportTx(
  1329  					env.ctx.XChainID,
  1330  					[]*avax.TransferableOutput{{
  1331  						Asset: avax.Asset{ID: env.ctx.AVAXAssetID},
  1332  						Out: &secp256k1fx.TransferOutput{
  1333  							Amt:          units.Avax,
  1334  							OutputOwners: *owners,
  1335  						},
  1336  					}},
  1337  					common.WithMemo(memoField),
  1338  				)
  1339  				require.NoError(err)
  1340  
  1341  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1342  				require.NoError(err)
  1343  				return tx, onAcceptState
  1344  			},
  1345  		},
  1346  		{
  1347  			name: "RemoveSubnetValidatorTx",
  1348  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1349  				require := require.New(t)
  1350  
  1351  				var primaryValidator *state.Staker
  1352  				it, err := env.state.GetCurrentStakerIterator()
  1353  				require.NoError(err)
  1354  				for it.Next() {
  1355  					staker := it.Value()
  1356  					if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority {
  1357  						continue
  1358  					}
  1359  					primaryValidator = staker
  1360  					break
  1361  				}
  1362  				it.Release()
  1363  
  1364  				endTime := primaryValidator.EndTime
  1365  
  1366  				subnetID := testSubnet1.ID()
  1367  				wallet := newWallet(t, env, walletConfig{
  1368  					subnetIDs: []ids.ID{subnetID},
  1369  				})
  1370  				subnetValTx, err := wallet.IssueAddSubnetValidatorTx(
  1371  					&txs.SubnetValidator{
  1372  						Validator: txs.Validator{
  1373  							NodeID: primaryValidator.NodeID,
  1374  							Start:  0,
  1375  							End:    uint64(endTime.Unix()),
  1376  							Wght:   genesistest.DefaultValidatorWeight,
  1377  						},
  1378  						Subnet: subnetID,
  1379  					},
  1380  				)
  1381  				require.NoError(err)
  1382  
  1383  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1384  				require.NoError(err)
  1385  
  1386  				feeCalculator := state.PickFeeCalculator(env.config, onAcceptState)
  1387  				require.NoError(subnetValTx.Unsigned.Visit(&StandardTxExecutor{
  1388  					Backend:       &env.backend,
  1389  					State:         onAcceptState,
  1390  					FeeCalculator: feeCalculator,
  1391  					Tx:            subnetValTx,
  1392  				}))
  1393  
  1394  				tx, err := wallet.IssueRemoveSubnetValidatorTx(
  1395  					primaryValidator.NodeID,
  1396  					subnetID,
  1397  					common.WithMemo(memoField),
  1398  				)
  1399  				require.NoError(err)
  1400  				return tx, onAcceptState
  1401  			},
  1402  		},
  1403  		{
  1404  			name: "TransformSubnetTx",
  1405  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1406  				require := require.New(t)
  1407  
  1408  				subnetID := testSubnet1.ID()
  1409  				wallet := newWallet(t, env, walletConfig{
  1410  					subnetIDs: []ids.ID{subnetID},
  1411  				})
  1412  
  1413  				tx, err := wallet.IssueTransformSubnetTx(
  1414  					subnetID,                  // subnetID
  1415  					ids.GenerateTestID(),      // assetID
  1416  					10,                        // initial supply
  1417  					10,                        // max supply
  1418  					0,                         // min consumption rate
  1419  					reward.PercentDenominator, // max consumption rate
  1420  					2,                         // min validator stake
  1421  					10,                        // max validator stake
  1422  					time.Minute,               // min stake duration
  1423  					time.Hour,                 // max stake duration
  1424  					1,                         // min delegation fees
  1425  					10,                        // min delegator stake
  1426  					1,                         // max validator weight factor
  1427  					80,                        // uptime requirement
  1428  					common.WithMemo(memoField),
  1429  				)
  1430  				require.NoError(err)
  1431  
  1432  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1433  				require.NoError(err)
  1434  				return tx, onAcceptState
  1435  			},
  1436  		},
  1437  		{
  1438  			name: "AddPermissionlessValidatorTx",
  1439  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1440  				require := require.New(t)
  1441  				var (
  1442  					nodeID    = ids.GenerateTestNodeID()
  1443  					chainTime = env.state.GetTimestamp()
  1444  					endTime   = chainTime.Add(defaultMaxStakingDuration)
  1445  				)
  1446  				sk, err := bls.NewSecretKey()
  1447  				require.NoError(err)
  1448  
  1449  				wallet := newWallet(t, env, walletConfig{})
  1450  				tx, err := wallet.IssueAddPermissionlessValidatorTx(
  1451  					&txs.SubnetValidator{
  1452  						Validator: txs.Validator{
  1453  							NodeID: nodeID,
  1454  							Start:  0,
  1455  							End:    uint64(endTime.Unix()),
  1456  							Wght:   env.config.MinValidatorStake,
  1457  						},
  1458  						Subnet: constants.PrimaryNetworkID,
  1459  					},
  1460  					signer.NewProofOfPossession(sk),
  1461  					env.ctx.AVAXAssetID,
  1462  					owners,
  1463  					owners,
  1464  					reward.PercentDenominator,
  1465  					common.WithMemo(memoField),
  1466  				)
  1467  				require.NoError(err)
  1468  
  1469  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1470  				require.NoError(err)
  1471  				return tx, onAcceptState
  1472  			},
  1473  		},
  1474  		{
  1475  			name: "AddPermissionlessDelegatorTx",
  1476  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1477  				require := require.New(t)
  1478  
  1479  				var primaryValidator *state.Staker
  1480  				it, err := env.state.GetCurrentStakerIterator()
  1481  				require.NoError(err)
  1482  				for it.Next() {
  1483  					staker := it.Value()
  1484  					if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority {
  1485  						continue
  1486  					}
  1487  					primaryValidator = staker
  1488  					break
  1489  				}
  1490  				it.Release()
  1491  
  1492  				wallet := newWallet(t, env, walletConfig{})
  1493  				tx, err := wallet.IssueAddPermissionlessDelegatorTx(
  1494  					&txs.SubnetValidator{
  1495  						Validator: txs.Validator{
  1496  							NodeID: primaryValidator.NodeID,
  1497  							Start:  0,
  1498  							End:    uint64(primaryValidator.EndTime.Unix()),
  1499  							Wght:   defaultMinValidatorStake,
  1500  						},
  1501  						Subnet: constants.PrimaryNetworkID,
  1502  					},
  1503  					env.ctx.AVAXAssetID,
  1504  					owners,
  1505  					common.WithMemo(memoField),
  1506  				)
  1507  				require.NoError(err)
  1508  
  1509  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1510  				require.NoError(err)
  1511  				return tx, onAcceptState
  1512  			},
  1513  		},
  1514  		{
  1515  			name: "TransferSubnetOwnershipTx",
  1516  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1517  				require := require.New(t)
  1518  
  1519  				subnetID := testSubnet1.ID()
  1520  				wallet := newWallet(t, env, walletConfig{
  1521  					subnetIDs: []ids.ID{subnetID},
  1522  				})
  1523  
  1524  				tx, err := wallet.IssueTransferSubnetOwnershipTx(
  1525  					subnetID,
  1526  					owners,
  1527  					common.WithMemo(memoField),
  1528  				)
  1529  				require.NoError(err)
  1530  
  1531  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1532  				require.NoError(err)
  1533  				return tx, onAcceptState
  1534  			},
  1535  		},
  1536  		{
  1537  			name: "BaseTx",
  1538  			setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) {
  1539  				require := require.New(t)
  1540  
  1541  				wallet := newWallet(t, env, walletConfig{})
  1542  				tx, err := wallet.IssueBaseTx(
  1543  					[]*avax.TransferableOutput{
  1544  						{
  1545  							Asset: avax.Asset{ID: env.ctx.AVAXAssetID},
  1546  							Out: &secp256k1fx.TransferOutput{
  1547  								Amt: 1,
  1548  								OutputOwners: secp256k1fx.OutputOwners{
  1549  									Threshold: 1,
  1550  									Addrs:     []ids.ShortID{ids.ShortEmpty},
  1551  								},
  1552  							},
  1553  						},
  1554  					},
  1555  					common.WithMemo(memoField),
  1556  				)
  1557  				require.NoError(err)
  1558  
  1559  				onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1560  				require.NoError(err)
  1561  				return tx, onAcceptState
  1562  			},
  1563  		},
  1564  	}
  1565  
  1566  	for _, tt := range tests {
  1567  		t.Run(tt.name, func(t *testing.T) {
  1568  			require := require.New(t)
  1569  
  1570  			env := newEnvironment(t, upgradetest.Durango)
  1571  			env.ctx.Lock.Lock()
  1572  			defer env.ctx.Lock.Unlock()
  1573  
  1574  			feeCalculator := state.PickFeeCalculator(env.config, env.state)
  1575  
  1576  			// Populated memo field should error
  1577  			tx, onAcceptState := tt.setupTest(t, env, []byte{'m', 'e', 'm', 'o'})
  1578  			err := tx.Unsigned.Visit(&StandardTxExecutor{
  1579  				Backend:       &env.backend,
  1580  				State:         onAcceptState,
  1581  				FeeCalculator: feeCalculator,
  1582  				Tx:            tx,
  1583  			})
  1584  			require.ErrorIs(err, avax.ErrMemoTooLarge)
  1585  
  1586  			// Empty memo field should not error
  1587  			tx, onAcceptState = tt.setupTest(t, env, []byte{})
  1588  			require.NoError(tx.Unsigned.Visit(&StandardTxExecutor{
  1589  				Backend:       &env.backend,
  1590  				State:         onAcceptState,
  1591  				FeeCalculator: feeCalculator,
  1592  				Tx:            tx,
  1593  			}))
  1594  		})
  1595  	}
  1596  }
  1597  
  1598  // Verifies that [TransformSubnetTx] is disabled post-Etna
  1599  func TestEtnaDisabledTransactions(t *testing.T) {
  1600  	require := require.New(t)
  1601  
  1602  	env := newEnvironment(t, upgradetest.Etna)
  1603  	env.ctx.Lock.Lock()
  1604  	defer env.ctx.Lock.Unlock()
  1605  
  1606  	onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env)
  1607  	require.NoError(err)
  1608  
  1609  	tx := &txs.Tx{
  1610  		Unsigned: &txs.TransformSubnetTx{},
  1611  	}
  1612  
  1613  	err = tx.Unsigned.Visit(&StandardTxExecutor{
  1614  		Backend: &env.backend,
  1615  		State:   onAcceptState,
  1616  		Tx:      tx,
  1617  	})
  1618  	require.ErrorIs(err, errTransformSubnetTxPostEtna)
  1619  }
  1620  
  1621  // Returns a RemoveSubnetValidatorTx that passes syntactic verification.
  1622  // Memo field is empty as required post Durango activation
  1623  func newRemoveSubnetValidatorTx(t *testing.T) (*txs.RemoveSubnetValidatorTx, *txs.Tx) {
  1624  	t.Helper()
  1625  
  1626  	creds := []verify.Verifiable{
  1627  		&secp256k1fx.Credential{
  1628  			Sigs: make([][65]byte, 1),
  1629  		},
  1630  		&secp256k1fx.Credential{
  1631  			Sigs: make([][65]byte, 1),
  1632  		},
  1633  	}
  1634  	unsignedTx := &txs.RemoveSubnetValidatorTx{
  1635  		BaseTx: txs.BaseTx{
  1636  			BaseTx: avax.BaseTx{
  1637  				Ins: []*avax.TransferableInput{{
  1638  					UTXOID: avax.UTXOID{
  1639  						TxID: ids.GenerateTestID(),
  1640  					},
  1641  					Asset: avax.Asset{
  1642  						ID: ids.GenerateTestID(),
  1643  					},
  1644  					In: &secp256k1fx.TransferInput{
  1645  						Amt: 1,
  1646  						Input: secp256k1fx.Input{
  1647  							SigIndices: []uint32{0, 1},
  1648  						},
  1649  					},
  1650  				}},
  1651  				Outs: []*avax.TransferableOutput{
  1652  					{
  1653  						Asset: avax.Asset{
  1654  							ID: ids.GenerateTestID(),
  1655  						},
  1656  						Out: &secp256k1fx.TransferOutput{
  1657  							Amt: 1,
  1658  							OutputOwners: secp256k1fx.OutputOwners{
  1659  								Threshold: 1,
  1660  								Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
  1661  							},
  1662  						},
  1663  					},
  1664  				},
  1665  			},
  1666  		},
  1667  		Subnet: ids.GenerateTestID(),
  1668  		NodeID: ids.GenerateTestNodeID(),
  1669  		SubnetAuth: &secp256k1fx.Credential{
  1670  			Sigs: make([][65]byte, 1),
  1671  		},
  1672  	}
  1673  	tx := &txs.Tx{
  1674  		Unsigned: unsignedTx,
  1675  		Creds:    creds,
  1676  	}
  1677  	require.NoError(t, tx.Initialize(txs.Codec))
  1678  	return unsignedTx, tx
  1679  }
  1680  
  1681  // mock implementations that can be used in tests
  1682  // for verifying RemoveSubnetValidatorTx.
  1683  type removeSubnetValidatorTxVerifyEnv struct {
  1684  	latestForkTime time.Time
  1685  	fx             *fxmock.Fx
  1686  	flowChecker    *utxomock.Verifier
  1687  	unsignedTx     *txs.RemoveSubnetValidatorTx
  1688  	tx             *txs.Tx
  1689  	state          *state.MockDiff
  1690  	staker         *state.Staker
  1691  }
  1692  
  1693  // Returns mock implementations that can be used in tests
  1694  // for verifying RemoveSubnetValidatorTx.
  1695  func newValidRemoveSubnetValidatorTxVerifyEnv(t *testing.T, ctrl *gomock.Controller) removeSubnetValidatorTxVerifyEnv {
  1696  	t.Helper()
  1697  
  1698  	now := time.Now()
  1699  	mockFx := fxmock.NewFx(ctrl)
  1700  	mockFlowChecker := utxomock.NewVerifier(ctrl)
  1701  	unsignedTx, tx := newRemoveSubnetValidatorTx(t)
  1702  	mockState := state.NewMockDiff(ctrl)
  1703  	return removeSubnetValidatorTxVerifyEnv{
  1704  		latestForkTime: now,
  1705  		fx:             mockFx,
  1706  		flowChecker:    mockFlowChecker,
  1707  		unsignedTx:     unsignedTx,
  1708  		tx:             tx,
  1709  		state:          mockState,
  1710  		staker: &state.Staker{
  1711  			TxID:     ids.GenerateTestID(),
  1712  			NodeID:   ids.GenerateTestNodeID(),
  1713  			Priority: txs.SubnetPermissionedValidatorCurrentPriority,
  1714  		},
  1715  	}
  1716  }
  1717  
  1718  func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) {
  1719  	type test struct {
  1720  		name        string
  1721  		newExecutor func(*gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor)
  1722  		expectedErr error
  1723  	}
  1724  
  1725  	tests := []test{
  1726  		{
  1727  			name: "valid tx",
  1728  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1729  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1730  
  1731  				// Set dependency expectations.
  1732  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1733  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil).Times(1)
  1734  				subnetOwner := fxmock.NewOwner(ctrl)
  1735  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1)
  1736  				env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1)
  1737  				env.flowChecker.EXPECT().VerifySpend(
  1738  					env.unsignedTx, env.state, env.unsignedTx.Ins, env.unsignedTx.Outs, env.tx.Creds[:len(env.tx.Creds)-1], gomock.Any(),
  1739  				).Return(nil).Times(1)
  1740  				env.state.EXPECT().DeleteCurrentValidator(env.staker)
  1741  				env.state.EXPECT().DeleteUTXO(gomock.Any()).Times(len(env.unsignedTx.Ins))
  1742  				env.state.EXPECT().AddUTXO(gomock.Any()).Times(len(env.unsignedTx.Outs))
  1743  
  1744  				cfg := &config.Config{
  1745  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1746  				}
  1747  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1748  				e := &StandardTxExecutor{
  1749  					Backend: &Backend{
  1750  						Config:       cfg,
  1751  						Bootstrapped: &utils.Atomic[bool]{},
  1752  						Fx:           env.fx,
  1753  						FlowChecker:  env.flowChecker,
  1754  						Ctx:          &snow.Context{},
  1755  					},
  1756  					FeeCalculator: feeCalculator,
  1757  					Tx:            env.tx,
  1758  					State:         env.state,
  1759  				}
  1760  				e.Bootstrapped.Set(true)
  1761  				return env.unsignedTx, e
  1762  			},
  1763  			expectedErr: nil,
  1764  		},
  1765  		{
  1766  			name: "tx fails syntactic verification",
  1767  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1768  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1769  				// Setting the subnet ID to the Primary Network ID makes the tx fail syntactic verification
  1770  				env.tx.Unsigned.(*txs.RemoveSubnetValidatorTx).Subnet = constants.PrimaryNetworkID
  1771  				env.state = state.NewMockDiff(ctrl)
  1772  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1773  
  1774  				cfg := &config.Config{
  1775  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1776  				}
  1777  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1778  				e := &StandardTxExecutor{
  1779  					Backend: &Backend{
  1780  						Config:       cfg,
  1781  						Bootstrapped: &utils.Atomic[bool]{},
  1782  						Fx:           env.fx,
  1783  						FlowChecker:  env.flowChecker,
  1784  						Ctx:          &snow.Context{},
  1785  					},
  1786  					FeeCalculator: feeCalculator,
  1787  					Tx:            env.tx,
  1788  					State:         env.state,
  1789  				}
  1790  				e.Bootstrapped.Set(true)
  1791  				return env.unsignedTx, e
  1792  			},
  1793  			expectedErr: txs.ErrRemovePrimaryNetworkValidator,
  1794  		},
  1795  		{
  1796  			name: "node isn't a validator of the subnet",
  1797  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1798  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1799  				env.state = state.NewMockDiff(ctrl)
  1800  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1801  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(nil, database.ErrNotFound)
  1802  				env.state.EXPECT().GetPendingValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(nil, database.ErrNotFound)
  1803  
  1804  				cfg := &config.Config{
  1805  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1806  				}
  1807  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1808  				e := &StandardTxExecutor{
  1809  					Backend: &Backend{
  1810  						Config:       cfg,
  1811  						Bootstrapped: &utils.Atomic[bool]{},
  1812  						Fx:           env.fx,
  1813  						FlowChecker:  env.flowChecker,
  1814  						Ctx:          &snow.Context{},
  1815  					},
  1816  					FeeCalculator: feeCalculator,
  1817  					Tx:            env.tx,
  1818  					State:         env.state,
  1819  				}
  1820  				e.Bootstrapped.Set(true)
  1821  				return env.unsignedTx, e
  1822  			},
  1823  			expectedErr: ErrNotValidator,
  1824  		},
  1825  		{
  1826  			name: "validator is permissionless",
  1827  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1828  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1829  
  1830  				staker := *env.staker
  1831  				staker.Priority = txs.SubnetPermissionlessValidatorCurrentPriority
  1832  
  1833  				// Set dependency expectations.
  1834  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1835  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(&staker, nil).Times(1)
  1836  
  1837  				cfg := &config.Config{
  1838  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1839  				}
  1840  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1841  				e := &StandardTxExecutor{
  1842  					Backend: &Backend{
  1843  						Config:       cfg,
  1844  						Bootstrapped: &utils.Atomic[bool]{},
  1845  						Fx:           env.fx,
  1846  						FlowChecker:  env.flowChecker,
  1847  						Ctx:          &snow.Context{},
  1848  					},
  1849  					FeeCalculator: feeCalculator,
  1850  					Tx:            env.tx,
  1851  					State:         env.state,
  1852  				}
  1853  				e.Bootstrapped.Set(true)
  1854  				return env.unsignedTx, e
  1855  			},
  1856  			expectedErr: ErrRemovePermissionlessValidator,
  1857  		},
  1858  		{
  1859  			name: "tx has no credentials",
  1860  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1861  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1862  				// Remove credentials
  1863  				env.tx.Creds = nil
  1864  				env.state = state.NewMockDiff(ctrl)
  1865  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1866  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil)
  1867  
  1868  				cfg := &config.Config{
  1869  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1870  				}
  1871  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1872  				e := &StandardTxExecutor{
  1873  					Backend: &Backend{
  1874  						Config:       cfg,
  1875  						Bootstrapped: &utils.Atomic[bool]{},
  1876  						Fx:           env.fx,
  1877  						FlowChecker:  env.flowChecker,
  1878  						Ctx:          &snow.Context{},
  1879  					},
  1880  					FeeCalculator: feeCalculator,
  1881  					Tx:            env.tx,
  1882  					State:         env.state,
  1883  				}
  1884  				e.Bootstrapped.Set(true)
  1885  				return env.unsignedTx, e
  1886  			},
  1887  			expectedErr: errWrongNumberOfCredentials,
  1888  		},
  1889  		{
  1890  			name: "can't find subnet",
  1891  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1892  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1893  				env.state = state.NewMockDiff(ctrl)
  1894  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1895  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil)
  1896  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound)
  1897  
  1898  				cfg := &config.Config{
  1899  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1900  				}
  1901  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1902  				e := &StandardTxExecutor{
  1903  					Backend: &Backend{
  1904  						Config:       cfg,
  1905  						Bootstrapped: &utils.Atomic[bool]{},
  1906  						Fx:           env.fx,
  1907  						FlowChecker:  env.flowChecker,
  1908  						Ctx:          &snow.Context{},
  1909  					},
  1910  					FeeCalculator: feeCalculator,
  1911  					Tx:            env.tx,
  1912  					State:         env.state,
  1913  				}
  1914  				e.Bootstrapped.Set(true)
  1915  				return env.unsignedTx, e
  1916  			},
  1917  			expectedErr: database.ErrNotFound,
  1918  		},
  1919  		{
  1920  			name: "no permission to remove validator",
  1921  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1922  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1923  				env.state = state.NewMockDiff(ctrl)
  1924  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1925  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil)
  1926  				subnetOwner := fxmock.NewOwner(ctrl)
  1927  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil)
  1928  				env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(errTest)
  1929  
  1930  				cfg := &config.Config{
  1931  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1932  				}
  1933  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1934  				e := &StandardTxExecutor{
  1935  					Backend: &Backend{
  1936  						Config:       cfg,
  1937  						Bootstrapped: &utils.Atomic[bool]{},
  1938  						Fx:           env.fx,
  1939  						FlowChecker:  env.flowChecker,
  1940  						Ctx:          &snow.Context{},
  1941  					},
  1942  					FeeCalculator: feeCalculator,
  1943  					Tx:            env.tx,
  1944  					State:         env.state,
  1945  				}
  1946  				e.Bootstrapped.Set(true)
  1947  				return env.unsignedTx, e
  1948  			},
  1949  			expectedErr: errUnauthorizedSubnetModification,
  1950  		},
  1951  		{
  1952  			name: "flow checker failed",
  1953  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1954  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1955  				env.state = state.NewMockDiff(ctrl)
  1956  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1957  				env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil)
  1958  				subnetOwner := fxmock.NewOwner(ctrl)
  1959  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil)
  1960  				env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil)
  1961  				env.flowChecker.EXPECT().VerifySpend(
  1962  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
  1963  				).Return(errTest)
  1964  
  1965  				cfg := &config.Config{
  1966  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  1967  				}
  1968  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  1969  				e := &StandardTxExecutor{
  1970  					Backend: &Backend{
  1971  						Config:       cfg,
  1972  						Bootstrapped: &utils.Atomic[bool]{},
  1973  						Fx:           env.fx,
  1974  						FlowChecker:  env.flowChecker,
  1975  						Ctx:          &snow.Context{},
  1976  					},
  1977  					FeeCalculator: feeCalculator,
  1978  					Tx:            env.tx,
  1979  					State:         env.state,
  1980  				}
  1981  				e.Bootstrapped.Set(true)
  1982  				return env.unsignedTx, e
  1983  			},
  1984  			expectedErr: ErrFlowCheckFailed,
  1985  		},
  1986  		{
  1987  			name: "attempted to remove subnet validator after subnet manager is set",
  1988  			newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) {
  1989  				env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl)
  1990  				env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, nil).AnyTimes()
  1991  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  1992  
  1993  				cfg := &config.Config{
  1994  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Etna, env.latestForkTime),
  1995  				}
  1996  				e := &StandardTxExecutor{
  1997  					Backend: &Backend{
  1998  						Config:       cfg,
  1999  						Bootstrapped: &utils.Atomic[bool]{},
  2000  						Fx:           env.fx,
  2001  						FlowChecker:  env.flowChecker,
  2002  						Ctx:          &snow.Context{},
  2003  					},
  2004  					Tx:    env.tx,
  2005  					State: env.state,
  2006  				}
  2007  				e.Bootstrapped.Set(true)
  2008  				return env.unsignedTx, e
  2009  			},
  2010  			expectedErr: ErrRemoveValidatorManagedSubnet,
  2011  		},
  2012  	}
  2013  
  2014  	for _, tt := range tests {
  2015  		t.Run(tt.name, func(t *testing.T) {
  2016  			require := require.New(t)
  2017  			ctrl := gomock.NewController(t)
  2018  
  2019  			unsignedTx, executor := tt.newExecutor(ctrl)
  2020  			err := executor.RemoveSubnetValidatorTx(unsignedTx)
  2021  			require.ErrorIs(err, tt.expectedErr)
  2022  		})
  2023  	}
  2024  }
  2025  
  2026  // Returns a TransformSubnetTx that passes syntactic verification.
  2027  // Memo field is empty as required post Durango activation
  2028  func newTransformSubnetTx(t *testing.T) (*txs.TransformSubnetTx, *txs.Tx) {
  2029  	t.Helper()
  2030  
  2031  	creds := []verify.Verifiable{
  2032  		&secp256k1fx.Credential{
  2033  			Sigs: make([][65]byte, 1),
  2034  		},
  2035  		&secp256k1fx.Credential{
  2036  			Sigs: make([][65]byte, 1),
  2037  		},
  2038  	}
  2039  	unsignedTx := &txs.TransformSubnetTx{
  2040  		BaseTx: txs.BaseTx{
  2041  			BaseTx: avax.BaseTx{
  2042  				Ins: []*avax.TransferableInput{{
  2043  					UTXOID: avax.UTXOID{
  2044  						TxID: ids.GenerateTestID(),
  2045  					},
  2046  					Asset: avax.Asset{
  2047  						ID: ids.GenerateTestID(),
  2048  					},
  2049  					In: &secp256k1fx.TransferInput{
  2050  						Amt: 1,
  2051  						Input: secp256k1fx.Input{
  2052  							SigIndices: []uint32{0, 1},
  2053  						},
  2054  					},
  2055  				}},
  2056  				Outs: []*avax.TransferableOutput{
  2057  					{
  2058  						Asset: avax.Asset{
  2059  							ID: ids.GenerateTestID(),
  2060  						},
  2061  						Out: &secp256k1fx.TransferOutput{
  2062  							Amt: 1,
  2063  							OutputOwners: secp256k1fx.OutputOwners{
  2064  								Threshold: 1,
  2065  								Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
  2066  							},
  2067  						},
  2068  					},
  2069  				},
  2070  			},
  2071  		},
  2072  		Subnet:                   ids.GenerateTestID(),
  2073  		AssetID:                  ids.GenerateTestID(),
  2074  		InitialSupply:            10,
  2075  		MaximumSupply:            10,
  2076  		MinConsumptionRate:       0,
  2077  		MaxConsumptionRate:       reward.PercentDenominator,
  2078  		MinValidatorStake:        2,
  2079  		MaxValidatorStake:        10,
  2080  		MinStakeDuration:         1,
  2081  		MaxStakeDuration:         2,
  2082  		MinDelegationFee:         reward.PercentDenominator,
  2083  		MinDelegatorStake:        1,
  2084  		MaxValidatorWeightFactor: 1,
  2085  		UptimeRequirement:        reward.PercentDenominator,
  2086  		SubnetAuth: &secp256k1fx.Credential{
  2087  			Sigs: make([][65]byte, 1),
  2088  		},
  2089  	}
  2090  	tx := &txs.Tx{
  2091  		Unsigned: unsignedTx,
  2092  		Creds:    creds,
  2093  	}
  2094  	require.NoError(t, tx.Initialize(txs.Codec))
  2095  	return unsignedTx, tx
  2096  }
  2097  
  2098  // mock implementations that can be used in tests
  2099  // for verifying TransformSubnetTx.
  2100  type transformSubnetTxVerifyEnv struct {
  2101  	latestForkTime time.Time
  2102  	fx             *fxmock.Fx
  2103  	flowChecker    *utxomock.Verifier
  2104  	unsignedTx     *txs.TransformSubnetTx
  2105  	tx             *txs.Tx
  2106  	state          *state.MockDiff
  2107  	staker         *state.Staker
  2108  }
  2109  
  2110  // Returns mock implementations that can be used in tests
  2111  // for verifying TransformSubnetTx.
  2112  func newValidTransformSubnetTxVerifyEnv(t *testing.T, ctrl *gomock.Controller) transformSubnetTxVerifyEnv {
  2113  	t.Helper()
  2114  
  2115  	now := time.Now()
  2116  	mockFx := fxmock.NewFx(ctrl)
  2117  	mockFlowChecker := utxomock.NewVerifier(ctrl)
  2118  	unsignedTx, tx := newTransformSubnetTx(t)
  2119  	mockState := state.NewMockDiff(ctrl)
  2120  	return transformSubnetTxVerifyEnv{
  2121  		latestForkTime: now,
  2122  		fx:             mockFx,
  2123  		flowChecker:    mockFlowChecker,
  2124  		unsignedTx:     unsignedTx,
  2125  		tx:             tx,
  2126  		state:          mockState,
  2127  		staker: &state.Staker{
  2128  			TxID:   ids.GenerateTestID(),
  2129  			NodeID: ids.GenerateTestNodeID(),
  2130  		},
  2131  	}
  2132  }
  2133  
  2134  func TestStandardExecutorTransformSubnetTx(t *testing.T) {
  2135  	type test struct {
  2136  		name        string
  2137  		newExecutor func(*gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor)
  2138  		err         error
  2139  	}
  2140  
  2141  	tests := []test{
  2142  		{
  2143  			name: "tx fails syntactic verification",
  2144  			newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) {
  2145  				env := newValidTransformSubnetTxVerifyEnv(t, ctrl)
  2146  				// Setting the tx to nil makes the tx fail syntactic verification
  2147  				env.tx.Unsigned = (*txs.TransformSubnetTx)(nil)
  2148  				env.state = state.NewMockDiff(ctrl)
  2149  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  2150  
  2151  				cfg := &config.Config{
  2152  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  2153  				}
  2154  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  2155  				e := &StandardTxExecutor{
  2156  					Backend: &Backend{
  2157  						Config:       cfg,
  2158  						Bootstrapped: &utils.Atomic[bool]{},
  2159  						Fx:           env.fx,
  2160  						FlowChecker:  env.flowChecker,
  2161  						Ctx:          &snow.Context{},
  2162  					},
  2163  					FeeCalculator: feeCalculator,
  2164  					Tx:            env.tx,
  2165  					State:         env.state,
  2166  				}
  2167  				e.Bootstrapped.Set(true)
  2168  				return env.unsignedTx, e
  2169  			},
  2170  			err: txs.ErrNilTx,
  2171  		},
  2172  		{
  2173  			name: "max stake duration too large",
  2174  			newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) {
  2175  				env := newValidTransformSubnetTxVerifyEnv(t, ctrl)
  2176  				env.unsignedTx.MaxStakeDuration = math.MaxUint32
  2177  				env.state = state.NewMockDiff(ctrl)
  2178  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  2179  
  2180  				cfg := &config.Config{
  2181  					UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  2182  				}
  2183  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  2184  				e := &StandardTxExecutor{
  2185  					Backend: &Backend{
  2186  						Config:       cfg,
  2187  						Bootstrapped: &utils.Atomic[bool]{},
  2188  						Fx:           env.fx,
  2189  						FlowChecker:  env.flowChecker,
  2190  						Ctx:          &snow.Context{},
  2191  					},
  2192  					FeeCalculator: feeCalculator,
  2193  					Tx:            env.tx,
  2194  					State:         env.state,
  2195  				}
  2196  				e.Bootstrapped.Set(true)
  2197  				return env.unsignedTx, e
  2198  			},
  2199  			err: errMaxStakeDurationTooLarge,
  2200  		},
  2201  		{
  2202  			name: "fail subnet authorization",
  2203  			newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) {
  2204  				env := newValidTransformSubnetTxVerifyEnv(t, ctrl)
  2205  				// Remove credentials
  2206  				env.tx.Creds = nil
  2207  				env.state = state.NewMockDiff(ctrl)
  2208  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  2209  
  2210  				cfg := &config.Config{
  2211  					UpgradeConfig:    upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  2212  					MaxStakeDuration: math.MaxInt64,
  2213  				}
  2214  
  2215  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  2216  				e := &StandardTxExecutor{
  2217  					Backend: &Backend{
  2218  						Config:       cfg,
  2219  						Bootstrapped: &utils.Atomic[bool]{},
  2220  						Fx:           env.fx,
  2221  						FlowChecker:  env.flowChecker,
  2222  						Ctx:          &snow.Context{},
  2223  					},
  2224  					FeeCalculator: feeCalculator,
  2225  					Tx:            env.tx,
  2226  					State:         env.state,
  2227  				}
  2228  				e.Bootstrapped.Set(true)
  2229  				return env.unsignedTx, e
  2230  			},
  2231  			err: errWrongNumberOfCredentials,
  2232  		},
  2233  		{
  2234  			name: "flow checker failed",
  2235  			newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) {
  2236  				env := newValidTransformSubnetTxVerifyEnv(t, ctrl)
  2237  				env.state = state.NewMockDiff(ctrl)
  2238  				subnetOwner := fxmock.NewOwner(ctrl)
  2239  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  2240  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil)
  2241  				env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.Empty, nil, database.ErrNotFound).Times(1)
  2242  				env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1)
  2243  				env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil)
  2244  				env.flowChecker.EXPECT().VerifySpend(
  2245  					gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(),
  2246  				).Return(ErrFlowCheckFailed)
  2247  
  2248  				cfg := &config.Config{
  2249  					UpgradeConfig:    upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  2250  					MaxStakeDuration: math.MaxInt64,
  2251  				}
  2252  
  2253  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  2254  				e := &StandardTxExecutor{
  2255  					Backend: &Backend{
  2256  						Config:       cfg,
  2257  						Bootstrapped: &utils.Atomic[bool]{},
  2258  						Fx:           env.fx,
  2259  						FlowChecker:  env.flowChecker,
  2260  						Ctx:          &snow.Context{},
  2261  					},
  2262  					FeeCalculator: feeCalculator,
  2263  					Tx:            env.tx,
  2264  					State:         env.state,
  2265  				}
  2266  				e.Bootstrapped.Set(true)
  2267  				return env.unsignedTx, e
  2268  			},
  2269  			err: ErrFlowCheckFailed,
  2270  		},
  2271  		{
  2272  			name: "invalid if subnet manager is set",
  2273  			newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) {
  2274  				env := newValidTransformSubnetTxVerifyEnv(t, ctrl)
  2275  
  2276  				// Set dependency expectations.
  2277  				subnetOwner := fxmock.NewOwner(ctrl)
  2278  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  2279  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1)
  2280  				env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), make([]byte, 20), nil)
  2281  				env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1)
  2282  				env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1)
  2283  
  2284  				cfg := &config.Config{
  2285  					UpgradeConfig:    upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  2286  					MaxStakeDuration: math.MaxInt64,
  2287  				}
  2288  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  2289  				e := &StandardTxExecutor{
  2290  					Backend: &Backend{
  2291  						Config:       cfg,
  2292  						Bootstrapped: &utils.Atomic[bool]{},
  2293  						Fx:           env.fx,
  2294  						FlowChecker:  env.flowChecker,
  2295  						Ctx:          &snow.Context{},
  2296  					},
  2297  					FeeCalculator: feeCalculator,
  2298  					Tx:            env.tx,
  2299  					State:         env.state,
  2300  				}
  2301  				e.Bootstrapped.Set(true)
  2302  				return env.unsignedTx, e
  2303  			},
  2304  			err: errIsImmutable,
  2305  		},
  2306  		{
  2307  			name: "valid tx",
  2308  			newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) {
  2309  				env := newValidTransformSubnetTxVerifyEnv(t, ctrl)
  2310  
  2311  				// Set dependency expectations.
  2312  				subnetOwner := fxmock.NewOwner(ctrl)
  2313  				env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes()
  2314  				env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1)
  2315  				env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.Empty, nil, database.ErrNotFound).Times(1)
  2316  				env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1)
  2317  				env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1)
  2318  				env.flowChecker.EXPECT().VerifySpend(
  2319  					env.unsignedTx, env.state, env.unsignedTx.Ins, env.unsignedTx.Outs, env.tx.Creds[:len(env.tx.Creds)-1], gomock.Any(),
  2320  				).Return(nil).Times(1)
  2321  				env.state.EXPECT().AddSubnetTransformation(env.tx)
  2322  				env.state.EXPECT().SetCurrentSupply(env.unsignedTx.Subnet, env.unsignedTx.InitialSupply)
  2323  				env.state.EXPECT().DeleteUTXO(gomock.Any()).Times(len(env.unsignedTx.Ins))
  2324  				env.state.EXPECT().AddUTXO(gomock.Any()).Times(len(env.unsignedTx.Outs))
  2325  
  2326  				cfg := &config.Config{
  2327  					UpgradeConfig:    upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime),
  2328  					MaxStakeDuration: math.MaxInt64,
  2329  				}
  2330  
  2331  				feeCalculator := state.PickFeeCalculator(cfg, env.state)
  2332  				e := &StandardTxExecutor{
  2333  					Backend: &Backend{
  2334  						Config:       cfg,
  2335  						Bootstrapped: &utils.Atomic[bool]{},
  2336  						Fx:           env.fx,
  2337  						FlowChecker:  env.flowChecker,
  2338  						Ctx:          &snow.Context{},
  2339  					},
  2340  					FeeCalculator: feeCalculator,
  2341  					Tx:            env.tx,
  2342  					State:         env.state,
  2343  				}
  2344  				e.Bootstrapped.Set(true)
  2345  				return env.unsignedTx, e
  2346  			},
  2347  			err: nil,
  2348  		},
  2349  	}
  2350  
  2351  	for _, tt := range tests {
  2352  		t.Run(tt.name, func(t *testing.T) {
  2353  			ctrl := gomock.NewController(t)
  2354  
  2355  			unsignedTx, executor := tt.newExecutor(ctrl)
  2356  			err := executor.TransformSubnetTx(unsignedTx)
  2357  			require.ErrorIs(t, err, tt.err)
  2358  		})
  2359  	}
  2360  }