github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/proposal_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  	"math"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/stretchr/testify/require"
    12  
    13  	"github.com/ava-labs/avalanchego/database"
    14  	"github.com/ava-labs/avalanchego/ids"
    15  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    16  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    17  	"github.com/ava-labs/avalanchego/utils/hashing"
    18  	"github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest"
    19  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    20  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    21  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    22  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    23  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    24  )
    25  
    26  func TestProposalTxExecuteAddDelegator(t *testing.T) {
    27  	dummyHeight := uint64(1)
    28  	rewardsOwner := &secp256k1fx.OutputOwners{
    29  		Threshold: 1,
    30  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
    31  	}
    32  	nodeID := genesistest.DefaultNodeIDs[0]
    33  
    34  	newValidatorID := ids.GenerateTestNodeID()
    35  	newValidatorStartTime := uint64(genesistest.DefaultValidatorStartTime.Add(5 * time.Second).Unix())
    36  	newValidatorEndTime := uint64(genesistest.DefaultValidatorEndTime.Add(-5 * time.Second).Unix())
    37  
    38  	// [addMinStakeValidator] adds a new validator to the primary network's
    39  	// pending validator set with the minimum staking amount
    40  	addMinStakeValidator := func(env *environment) {
    41  		require := require.New(t)
    42  
    43  		wallet := newWallet(t, env, walletConfig{
    44  			keys: genesistest.DefaultFundedKeys[:1],
    45  		})
    46  
    47  		tx, err := wallet.IssueAddValidatorTx(
    48  			&txs.Validator{
    49  				NodeID: newValidatorID,
    50  				Start:  newValidatorStartTime,
    51  				End:    newValidatorEndTime,
    52  				Wght:   env.config.MinValidatorStake,
    53  			},
    54  			rewardsOwner,
    55  			reward.PercentDenominator,
    56  		)
    57  		require.NoError(err)
    58  
    59  		addValTx := tx.Unsigned.(*txs.AddValidatorTx)
    60  		staker, err := state.NewCurrentStaker(
    61  			tx.ID(),
    62  			addValTx,
    63  			addValTx.StartTime(),
    64  			0,
    65  		)
    66  		require.NoError(err)
    67  
    68  		require.NoError(env.state.PutCurrentValidator(staker))
    69  		env.state.AddTx(tx, status.Committed)
    70  		env.state.SetHeight(dummyHeight)
    71  		require.NoError(env.state.Commit())
    72  	}
    73  
    74  	// [addMaxStakeValidator] adds a new validator to the primary network's
    75  	// pending validator set with the maximum staking amount
    76  	addMaxStakeValidator := func(env *environment) {
    77  		require := require.New(t)
    78  
    79  		wallet := newWallet(t, env, walletConfig{
    80  			keys: genesistest.DefaultFundedKeys[:1],
    81  		})
    82  
    83  		tx, err := wallet.IssueAddValidatorTx(
    84  			&txs.Validator{
    85  				NodeID: newValidatorID,
    86  				Start:  newValidatorStartTime,
    87  				End:    newValidatorEndTime,
    88  				Wght:   env.config.MaxValidatorStake,
    89  			},
    90  			rewardsOwner,
    91  			reward.PercentDenominator,
    92  		)
    93  		require.NoError(err)
    94  
    95  		addValTx := tx.Unsigned.(*txs.AddValidatorTx)
    96  		staker, err := state.NewCurrentStaker(
    97  			tx.ID(),
    98  			addValTx,
    99  			addValTx.StartTime(),
   100  			0,
   101  		)
   102  		require.NoError(err)
   103  
   104  		require.NoError(env.state.PutCurrentValidator(staker))
   105  		env.state.AddTx(tx, status.Committed)
   106  		env.state.SetHeight(dummyHeight)
   107  		require.NoError(env.state.Commit())
   108  	}
   109  
   110  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   111  	currentTimestamp := env.state.GetTimestamp()
   112  
   113  	type test struct {
   114  		description string
   115  		stakeAmount uint64
   116  		startTime   uint64
   117  		endTime     uint64
   118  		nodeID      ids.NodeID
   119  		feeKeys     []*secp256k1.PrivateKey
   120  		setup       func(*environment)
   121  		AP3Time     time.Time
   122  		expectedErr error
   123  	}
   124  
   125  	tests := []test{
   126  		{
   127  			description: "validator stops validating earlier than delegator",
   128  			stakeAmount: env.config.MinDelegatorStake,
   129  			startTime:   genesistest.DefaultValidatorStartTimeUnix + 1,
   130  			endTime:     genesistest.DefaultValidatorEndTimeUnix + 1,
   131  			nodeID:      nodeID,
   132  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   133  			setup:       nil,
   134  			AP3Time:     genesistest.DefaultValidatorStartTime,
   135  			expectedErr: ErrPeriodMismatch,
   136  		},
   137  		{
   138  			description: "validator not in the current or pending validator sets",
   139  			stakeAmount: env.config.MinDelegatorStake,
   140  			startTime:   uint64(genesistest.DefaultValidatorStartTime.Add(5 * time.Second).Unix()),
   141  			endTime:     uint64(genesistest.DefaultValidatorEndTime.Add(-5 * time.Second).Unix()),
   142  			nodeID:      newValidatorID,
   143  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   144  			setup:       nil,
   145  			AP3Time:     genesistest.DefaultValidatorStartTime,
   146  			expectedErr: database.ErrNotFound,
   147  		},
   148  		{
   149  			description: "delegator starts before validator",
   150  			stakeAmount: env.config.MinDelegatorStake,
   151  			startTime:   newValidatorStartTime - 1, // start validating subnet before primary network
   152  			endTime:     newValidatorEndTime,
   153  			nodeID:      newValidatorID,
   154  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   155  			setup:       addMinStakeValidator,
   156  			AP3Time:     genesistest.DefaultValidatorStartTime,
   157  			expectedErr: ErrPeriodMismatch,
   158  		},
   159  		{
   160  			description: "delegator stops before validator",
   161  			stakeAmount: env.config.MinDelegatorStake,
   162  			startTime:   newValidatorStartTime,
   163  			endTime:     newValidatorEndTime + 1, // stop validating subnet after stopping validating primary network
   164  			nodeID:      newValidatorID,
   165  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   166  			setup:       addMinStakeValidator,
   167  			AP3Time:     genesistest.DefaultValidatorStartTime,
   168  			expectedErr: ErrPeriodMismatch,
   169  		},
   170  		{
   171  			description: "valid",
   172  			stakeAmount: env.config.MinDelegatorStake,
   173  			startTime:   newValidatorStartTime, // same start time as for primary network
   174  			endTime:     newValidatorEndTime,   // same end time as for primary network
   175  			nodeID:      newValidatorID,
   176  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   177  			setup:       addMinStakeValidator,
   178  			AP3Time:     genesistest.DefaultValidatorStartTime,
   179  			expectedErr: nil,
   180  		},
   181  		{
   182  			description: "starts delegating at current timestamp",
   183  			stakeAmount: env.config.MinDelegatorStake,
   184  			startTime:   uint64(currentTimestamp.Unix()),
   185  			endTime:     genesistest.DefaultValidatorEndTimeUnix,
   186  			nodeID:      nodeID,
   187  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   188  			setup:       nil,
   189  			AP3Time:     genesistest.DefaultValidatorStartTime,
   190  			expectedErr: ErrTimestampNotBeforeStartTime,
   191  		},
   192  		{
   193  			description: "tx fee paying key has no funds",
   194  			stakeAmount: env.config.MinDelegatorStake,
   195  			startTime:   genesistest.DefaultValidatorStartTimeUnix + 1,
   196  			endTime:     genesistest.DefaultValidatorEndTimeUnix,
   197  			nodeID:      nodeID,
   198  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[1]},
   199  			setup: func(env *environment) { // Remove all UTXOs owned by keys[1]
   200  				utxoIDs, err := env.state.UTXOIDs(
   201  					genesistest.DefaultFundedKeys[1].Address().Bytes(),
   202  					ids.Empty,
   203  					math.MaxInt32)
   204  				require.NoError(t, err)
   205  
   206  				for _, utxoID := range utxoIDs {
   207  					env.state.DeleteUTXO(utxoID)
   208  				}
   209  				env.state.SetHeight(dummyHeight)
   210  				require.NoError(t, env.state.Commit())
   211  			},
   212  			AP3Time:     genesistest.DefaultValidatorStartTime,
   213  			expectedErr: ErrFlowCheckFailed,
   214  		},
   215  		{
   216  			description: "over delegation before AP3",
   217  			stakeAmount: env.config.MinDelegatorStake,
   218  			startTime:   newValidatorStartTime, // same start time as for primary network
   219  			endTime:     newValidatorEndTime,   // same end time as for primary network
   220  			nodeID:      newValidatorID,
   221  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   222  			setup:       addMaxStakeValidator,
   223  			AP3Time:     genesistest.DefaultValidatorEndTime,
   224  			expectedErr: nil,
   225  		},
   226  		{
   227  			description: "over delegation after AP3",
   228  			stakeAmount: env.config.MinDelegatorStake,
   229  			startTime:   newValidatorStartTime, // same start time as for primary network
   230  			endTime:     newValidatorEndTime,   // same end time as for primary network
   231  			nodeID:      newValidatorID,
   232  			feeKeys:     []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   233  			setup:       addMaxStakeValidator,
   234  			AP3Time:     genesistest.DefaultValidatorStartTime,
   235  			expectedErr: ErrOverDelegated,
   236  		},
   237  	}
   238  
   239  	for _, tt := range tests {
   240  		t.Run(tt.description, func(t *testing.T) {
   241  			require := require.New(t)
   242  			env := newEnvironment(t, upgradetest.ApricotPhase5)
   243  			env.config.UpgradeConfig.ApricotPhase3Time = tt.AP3Time
   244  
   245  			wallet := newWallet(t, env, walletConfig{
   246  				keys: tt.feeKeys,
   247  			})
   248  
   249  			tx, err := wallet.IssueAddDelegatorTx(
   250  				&txs.Validator{
   251  					NodeID: tt.nodeID,
   252  					Start:  tt.startTime,
   253  					End:    tt.endTime,
   254  					Wght:   tt.stakeAmount,
   255  				},
   256  				rewardsOwner,
   257  			)
   258  			require.NoError(err)
   259  
   260  			if tt.setup != nil {
   261  				tt.setup(env)
   262  			}
   263  
   264  			onCommitState, err := state.NewDiff(lastAcceptedID, env)
   265  			require.NoError(err)
   266  
   267  			onAbortState, err := state.NewDiff(lastAcceptedID, env)
   268  			require.NoError(err)
   269  
   270  			feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   271  			executor := ProposalTxExecutor{
   272  				OnCommitState: onCommitState,
   273  				OnAbortState:  onAbortState,
   274  				Backend:       &env.backend,
   275  				FeeCalculator: feeCalculator,
   276  				Tx:            tx,
   277  			}
   278  			err = tx.Unsigned.Visit(&executor)
   279  			require.ErrorIs(err, tt.expectedErr)
   280  		})
   281  	}
   282  }
   283  
   284  func TestProposalTxExecuteAddSubnetValidator(t *testing.T) {
   285  	require := require.New(t)
   286  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   287  	env.ctx.Lock.Lock()
   288  	defer env.ctx.Lock.Unlock()
   289  
   290  	nodeID := genesistest.DefaultNodeIDs[0]
   291  	subnetID := testSubnet1.ID()
   292  	{
   293  		// Case: Proposed validator currently validating primary network
   294  		// but stops validating subnet after stops validating primary network
   295  		// (note that keys[0] is a genesis validator)
   296  		wallet := newWallet(t, env, walletConfig{
   297  			subnetIDs: []ids.ID{subnetID},
   298  		})
   299  		tx, err := wallet.IssueAddSubnetValidatorTx(
   300  			&txs.SubnetValidator{
   301  				Validator: txs.Validator{
   302  					NodeID: nodeID,
   303  					Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   304  					End:    genesistest.DefaultValidatorEndTimeUnix + 1,
   305  					Wght:   genesistest.DefaultValidatorWeight,
   306  				},
   307  				Subnet: subnetID,
   308  			},
   309  		)
   310  		require.NoError(err)
   311  
   312  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   313  		require.NoError(err)
   314  
   315  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   316  		require.NoError(err)
   317  
   318  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   319  		executor := ProposalTxExecutor{
   320  			OnCommitState: onCommitState,
   321  			OnAbortState:  onAbortState,
   322  			Backend:       &env.backend,
   323  			FeeCalculator: feeCalculator,
   324  			Tx:            tx,
   325  		}
   326  		err = tx.Unsigned.Visit(&executor)
   327  		require.ErrorIs(err, ErrPeriodMismatch)
   328  	}
   329  
   330  	{
   331  		// Case: Proposed validator currently validating primary network
   332  		// and proposed subnet validation period is subset of
   333  		// primary network validation period
   334  		// (note that keys[0] is a genesis validator)
   335  		wallet := newWallet(t, env, walletConfig{
   336  			subnetIDs: []ids.ID{subnetID},
   337  		})
   338  		tx, err := wallet.IssueAddSubnetValidatorTx(
   339  			&txs.SubnetValidator{
   340  				Validator: txs.Validator{
   341  					NodeID: nodeID,
   342  					Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   343  					End:    genesistest.DefaultValidatorEndTimeUnix,
   344  					Wght:   genesistest.DefaultValidatorWeight,
   345  				},
   346  				Subnet: subnetID,
   347  			},
   348  		)
   349  		require.NoError(err)
   350  
   351  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   352  		require.NoError(err)
   353  
   354  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   355  		require.NoError(err)
   356  
   357  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   358  		executor := ProposalTxExecutor{
   359  			OnCommitState: onCommitState,
   360  			OnAbortState:  onAbortState,
   361  			Backend:       &env.backend,
   362  			FeeCalculator: feeCalculator,
   363  			Tx:            tx,
   364  		}
   365  		require.NoError(tx.Unsigned.Visit(&executor))
   366  	}
   367  
   368  	// Add a validator to pending validator set of primary network
   369  	// Starts validating primary network 10 seconds after genesis
   370  	pendingDSValidatorID := ids.GenerateTestNodeID()
   371  	dsStartTime := genesistest.DefaultValidatorStartTime.Add(10 * time.Second)
   372  	dsEndTime := dsStartTime.Add(5 * defaultMinStakingDuration)
   373  
   374  	wallet := newWallet(t, env, walletConfig{
   375  		keys: genesistest.DefaultFundedKeys[:1],
   376  	})
   377  	addDSTx, err := wallet.IssueAddValidatorTx(
   378  		&txs.Validator{
   379  			NodeID: pendingDSValidatorID,
   380  			Start:  uint64(dsStartTime.Unix()),
   381  			End:    uint64(dsEndTime.Unix()),
   382  			Wght:   env.config.MinValidatorStake,
   383  		},
   384  		&secp256k1fx.OutputOwners{
   385  			Threshold: 1,
   386  			Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   387  		},
   388  		reward.PercentDenominator,
   389  	)
   390  	require.NoError(err)
   391  
   392  	{
   393  		// Case: Proposed validator isn't in pending or current validator sets
   394  		wallet := newWallet(t, env, walletConfig{
   395  			subnetIDs: []ids.ID{subnetID},
   396  		})
   397  		tx, err := wallet.IssueAddSubnetValidatorTx(
   398  			&txs.SubnetValidator{
   399  				Validator: txs.Validator{
   400  					NodeID: pendingDSValidatorID,
   401  					Start:  uint64(dsStartTime.Unix()), // start validating subnet before primary network
   402  					End:    uint64(dsEndTime.Unix()),
   403  					Wght:   genesistest.DefaultValidatorWeight,
   404  				},
   405  				Subnet: subnetID,
   406  			},
   407  		)
   408  		require.NoError(err)
   409  
   410  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   411  		require.NoError(err)
   412  
   413  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   414  		require.NoError(err)
   415  
   416  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   417  		executor := ProposalTxExecutor{
   418  			OnCommitState: onCommitState,
   419  			OnAbortState:  onAbortState,
   420  			Backend:       &env.backend,
   421  			FeeCalculator: feeCalculator,
   422  			Tx:            tx,
   423  		}
   424  		err = tx.Unsigned.Visit(&executor)
   425  		require.ErrorIs(err, ErrNotValidator)
   426  	}
   427  
   428  	addValTx := addDSTx.Unsigned.(*txs.AddValidatorTx)
   429  	staker, err := state.NewCurrentStaker(
   430  		addDSTx.ID(),
   431  		addValTx,
   432  		addValTx.StartTime(),
   433  		0,
   434  	)
   435  	require.NoError(err)
   436  
   437  	require.NoError(env.state.PutCurrentValidator(staker))
   438  	env.state.AddTx(addDSTx, status.Committed)
   439  	dummyHeight := uint64(1)
   440  	env.state.SetHeight(dummyHeight)
   441  	require.NoError(env.state.Commit())
   442  
   443  	// Node with ID key.Address() now a pending validator for primary network
   444  
   445  	{
   446  		// Case: Proposed validator is pending validator of primary network
   447  		// but starts validating subnet before primary network
   448  		wallet := newWallet(t, env, walletConfig{
   449  			subnetIDs: []ids.ID{subnetID},
   450  		})
   451  		tx, err := wallet.IssueAddSubnetValidatorTx(
   452  			&txs.SubnetValidator{
   453  				Validator: txs.Validator{
   454  					NodeID: pendingDSValidatorID,
   455  					Start:  uint64(dsStartTime.Unix()) - 1, // start validating subnet before primary network
   456  					End:    uint64(dsEndTime.Unix()),
   457  					Wght:   genesistest.DefaultValidatorWeight,
   458  				},
   459  				Subnet: subnetID,
   460  			},
   461  		)
   462  		require.NoError(err)
   463  
   464  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   465  		require.NoError(err)
   466  
   467  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   468  		require.NoError(err)
   469  
   470  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   471  		executor := ProposalTxExecutor{
   472  			OnCommitState: onCommitState,
   473  			OnAbortState:  onAbortState,
   474  			Backend:       &env.backend,
   475  			FeeCalculator: feeCalculator,
   476  			Tx:            tx,
   477  		}
   478  		err = tx.Unsigned.Visit(&executor)
   479  		require.ErrorIs(err, ErrPeriodMismatch)
   480  	}
   481  
   482  	{
   483  		// Case: Proposed validator is pending validator of primary network
   484  		// but stops validating subnet after primary network
   485  		wallet := newWallet(t, env, walletConfig{
   486  			subnetIDs: []ids.ID{subnetID},
   487  		})
   488  		tx, err := wallet.IssueAddSubnetValidatorTx(
   489  			&txs.SubnetValidator{
   490  				Validator: txs.Validator{
   491  					NodeID: pendingDSValidatorID,
   492  					Start:  uint64(dsStartTime.Unix()),
   493  					End:    uint64(dsEndTime.Unix()) + 1, // stop validating subnet after stopping validating primary network
   494  					Wght:   genesistest.DefaultValidatorWeight,
   495  				},
   496  				Subnet: subnetID,
   497  			},
   498  		)
   499  		require.NoError(err)
   500  
   501  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   502  		require.NoError(err)
   503  
   504  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   505  		require.NoError(err)
   506  
   507  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   508  		executor := ProposalTxExecutor{
   509  			OnCommitState: onCommitState,
   510  			OnAbortState:  onAbortState,
   511  			Backend:       &env.backend,
   512  			FeeCalculator: feeCalculator,
   513  			Tx:            tx,
   514  		}
   515  		err = tx.Unsigned.Visit(&executor)
   516  		require.ErrorIs(err, ErrPeriodMismatch)
   517  	}
   518  
   519  	{
   520  		// Case: Proposed validator is pending validator of primary network and
   521  		// period validating subnet is subset of time validating primary network
   522  		wallet := newWallet(t, env, walletConfig{
   523  			subnetIDs: []ids.ID{subnetID},
   524  		})
   525  		tx, err := wallet.IssueAddSubnetValidatorTx(
   526  			&txs.SubnetValidator{
   527  				Validator: txs.Validator{
   528  					NodeID: pendingDSValidatorID,
   529  					Start:  uint64(dsStartTime.Unix()), // same start time as for primary network
   530  					End:    uint64(dsEndTime.Unix()),   // same end time as for primary network
   531  					Wght:   genesistest.DefaultValidatorWeight,
   532  				},
   533  				Subnet: subnetID,
   534  			},
   535  		)
   536  		require.NoError(err)
   537  
   538  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   539  		require.NoError(err)
   540  
   541  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   542  		require.NoError(err)
   543  
   544  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   545  		executor := ProposalTxExecutor{
   546  			OnCommitState: onCommitState,
   547  			OnAbortState:  onAbortState,
   548  			Backend:       &env.backend,
   549  			FeeCalculator: feeCalculator,
   550  			Tx:            tx,
   551  		}
   552  		require.NoError(tx.Unsigned.Visit(&executor))
   553  	}
   554  
   555  	// Case: Proposed validator start validating at/before current timestamp
   556  	// First, advance the timestamp
   557  	newTimestamp := genesistest.DefaultValidatorStartTime.Add(2 * time.Second)
   558  	env.state.SetTimestamp(newTimestamp)
   559  
   560  	{
   561  		wallet := newWallet(t, env, walletConfig{
   562  			subnetIDs: []ids.ID{subnetID},
   563  		})
   564  		tx, err := wallet.IssueAddSubnetValidatorTx(
   565  			&txs.SubnetValidator{
   566  				Validator: txs.Validator{
   567  					NodeID: nodeID,
   568  					Start:  uint64(newTimestamp.Unix()),
   569  					End:    uint64(newTimestamp.Add(defaultMinStakingDuration).Unix()),
   570  					Wght:   genesistest.DefaultValidatorWeight,
   571  				},
   572  				Subnet: subnetID,
   573  			},
   574  		)
   575  		require.NoError(err)
   576  
   577  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   578  		require.NoError(err)
   579  
   580  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   581  		require.NoError(err)
   582  
   583  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   584  		executor := ProposalTxExecutor{
   585  			OnCommitState: onCommitState,
   586  			OnAbortState:  onAbortState,
   587  			Backend:       &env.backend,
   588  			FeeCalculator: feeCalculator,
   589  			Tx:            tx,
   590  		}
   591  		err = tx.Unsigned.Visit(&executor)
   592  		require.ErrorIs(err, ErrTimestampNotBeforeStartTime)
   593  	}
   594  
   595  	// reset the timestamp
   596  	env.state.SetTimestamp(genesistest.DefaultValidatorStartTime)
   597  
   598  	// Case: Proposed validator already validating the subnet
   599  	// First, add validator as validator of subnet
   600  	wallet = newWallet(t, env, walletConfig{
   601  		subnetIDs: []ids.ID{subnetID},
   602  	})
   603  	subnetTx, err := wallet.IssueAddSubnetValidatorTx(
   604  		&txs.SubnetValidator{
   605  			Validator: txs.Validator{
   606  				NodeID: nodeID,
   607  				Start:  genesistest.DefaultValidatorStartTimeUnix,
   608  				End:    genesistest.DefaultValidatorEndTimeUnix,
   609  				Wght:   genesistest.DefaultValidatorWeight,
   610  			},
   611  			Subnet: subnetID,
   612  		},
   613  	)
   614  	require.NoError(err)
   615  
   616  	addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx)
   617  	staker, err = state.NewCurrentStaker(
   618  		subnetTx.ID(),
   619  		addSubnetValTx,
   620  		addSubnetValTx.StartTime(),
   621  		0,
   622  	)
   623  	require.NoError(err)
   624  
   625  	require.NoError(env.state.PutCurrentValidator(staker))
   626  	env.state.AddTx(subnetTx, status.Committed)
   627  	env.state.SetHeight(dummyHeight)
   628  	require.NoError(env.state.Commit())
   629  
   630  	{
   631  		// Node with ID nodeIDKey.Address() now validating subnet with ID testSubnet1.ID
   632  		wallet = newWallet(t, env, walletConfig{
   633  			subnetIDs: []ids.ID{subnetID},
   634  		})
   635  		duplicateSubnetTx, err := wallet.IssueAddSubnetValidatorTx(
   636  			&txs.SubnetValidator{
   637  				Validator: txs.Validator{
   638  					NodeID: nodeID,
   639  					Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   640  					End:    genesistest.DefaultValidatorEndTimeUnix,
   641  					Wght:   genesistest.DefaultValidatorWeight,
   642  				},
   643  				Subnet: subnetID,
   644  			},
   645  		)
   646  		require.NoError(err)
   647  
   648  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   649  		require.NoError(err)
   650  
   651  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   652  		require.NoError(err)
   653  
   654  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   655  		executor := ProposalTxExecutor{
   656  			OnCommitState: onCommitState,
   657  			OnAbortState:  onAbortState,
   658  			Backend:       &env.backend,
   659  			FeeCalculator: feeCalculator,
   660  			Tx:            duplicateSubnetTx,
   661  		}
   662  		err = duplicateSubnetTx.Unsigned.Visit(&executor)
   663  		require.ErrorIs(err, ErrDuplicateValidator)
   664  	}
   665  
   666  	env.state.DeleteCurrentValidator(staker)
   667  	env.state.SetHeight(dummyHeight)
   668  	require.NoError(env.state.Commit())
   669  
   670  	{
   671  		// Case: Too few signatures
   672  		wallet = newWallet(t, env, walletConfig{
   673  			subnetIDs: []ids.ID{subnetID},
   674  		})
   675  		tx, err := wallet.IssueAddSubnetValidatorTx(
   676  			&txs.SubnetValidator{
   677  				Validator: txs.Validator{
   678  					NodeID: nodeID,
   679  					Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   680  					End:    uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) + 1,
   681  					Wght:   genesistest.DefaultValidatorWeight,
   682  				},
   683  				Subnet: subnetID,
   684  			},
   685  		)
   686  		require.NoError(err)
   687  
   688  		// Remove a signature
   689  		addSubnetValidatorTx := tx.Unsigned.(*txs.AddSubnetValidatorTx)
   690  		input := addSubnetValidatorTx.SubnetAuth.(*secp256k1fx.Input)
   691  		input.SigIndices = input.SigIndices[1:]
   692  		// This tx was syntactically verified when it was created...pretend it wasn't so we don't use cache
   693  		addSubnetValidatorTx.SyntacticallyVerified = false
   694  
   695  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   696  		require.NoError(err)
   697  
   698  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   699  		require.NoError(err)
   700  
   701  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   702  		executor := ProposalTxExecutor{
   703  			OnCommitState: onCommitState,
   704  			OnAbortState:  onAbortState,
   705  			Backend:       &env.backend,
   706  			FeeCalculator: feeCalculator,
   707  			Tx:            tx,
   708  		}
   709  		err = tx.Unsigned.Visit(&executor)
   710  		require.ErrorIs(err, errUnauthorizedSubnetModification)
   711  	}
   712  
   713  	{
   714  		// Case: Control Signature from invalid key (keys[3] is not a control key)
   715  		wallet = newWallet(t, env, walletConfig{
   716  			subnetIDs: []ids.ID{subnetID},
   717  		})
   718  		tx, err := wallet.IssueAddSubnetValidatorTx(
   719  			&txs.SubnetValidator{
   720  				Validator: txs.Validator{
   721  					NodeID: nodeID,
   722  					Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   723  					End:    uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) + 1,
   724  					Wght:   genesistest.DefaultValidatorWeight,
   725  				},
   726  				Subnet: subnetID,
   727  			},
   728  		)
   729  		require.NoError(err)
   730  
   731  		// Replace a valid signature with one from keys[3]
   732  		sig, err := genesistest.DefaultFundedKeys[3].SignHash(hashing.ComputeHash256(tx.Unsigned.Bytes()))
   733  		require.NoError(err)
   734  		copy(tx.Creds[0].(*secp256k1fx.Credential).Sigs[0][:], sig)
   735  
   736  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   737  		require.NoError(err)
   738  
   739  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   740  		require.NoError(err)
   741  
   742  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   743  		executor := ProposalTxExecutor{
   744  			OnCommitState: onCommitState,
   745  			OnAbortState:  onAbortState,
   746  			Backend:       &env.backend,
   747  			FeeCalculator: feeCalculator,
   748  			Tx:            tx,
   749  		}
   750  		err = tx.Unsigned.Visit(&executor)
   751  		require.ErrorIs(err, errUnauthorizedSubnetModification)
   752  	}
   753  
   754  	{
   755  		// Case: Proposed validator in pending validator set for subnet
   756  		// First, add validator to pending validator set of subnet
   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:  genesistest.DefaultValidatorStartTimeUnix + 1,
   765  					End:    uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) + 1,
   766  					Wght:   genesistest.DefaultValidatorWeight,
   767  				},
   768  				Subnet: subnetID,
   769  			},
   770  		)
   771  		require.NoError(err)
   772  
   773  		addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx)
   774  		staker, err = state.NewCurrentStaker(
   775  			subnetTx.ID(),
   776  			addSubnetValTx,
   777  			addSubnetValTx.StartTime(),
   778  			0,
   779  		)
   780  		require.NoError(err)
   781  
   782  		require.NoError(env.state.PutCurrentValidator(staker))
   783  		env.state.AddTx(tx, status.Committed)
   784  		env.state.SetHeight(dummyHeight)
   785  		require.NoError(env.state.Commit())
   786  
   787  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   788  		require.NoError(err)
   789  
   790  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   791  		require.NoError(err)
   792  
   793  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   794  		executor := ProposalTxExecutor{
   795  			OnCommitState: onCommitState,
   796  			OnAbortState:  onAbortState,
   797  			Backend:       &env.backend,
   798  			FeeCalculator: feeCalculator,
   799  			Tx:            tx,
   800  		}
   801  		err = tx.Unsigned.Visit(&executor)
   802  		require.ErrorIs(err, ErrDuplicateValidator)
   803  	}
   804  }
   805  
   806  func TestProposalTxExecuteAddValidator(t *testing.T) {
   807  	require := require.New(t)
   808  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   809  	env.ctx.Lock.Lock()
   810  	defer env.ctx.Lock.Unlock()
   811  
   812  	nodeID := ids.GenerateTestNodeID()
   813  	chainTime := env.state.GetTimestamp()
   814  	rewardsOwner := &secp256k1fx.OutputOwners{
   815  		Threshold: 1,
   816  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   817  	}
   818  
   819  	{
   820  		// Case: Validator's start time too early
   821  		wallet := newWallet(t, env, walletConfig{})
   822  		tx, err := wallet.IssueAddValidatorTx(
   823  			&txs.Validator{
   824  				NodeID: nodeID,
   825  				Start:  uint64(chainTime.Unix()),
   826  				End:    genesistest.DefaultValidatorEndTimeUnix,
   827  				Wght:   env.config.MinValidatorStake,
   828  			},
   829  			rewardsOwner,
   830  			reward.PercentDenominator,
   831  		)
   832  		require.NoError(err)
   833  
   834  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   835  		require.NoError(err)
   836  
   837  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   838  		require.NoError(err)
   839  
   840  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   841  		executor := ProposalTxExecutor{
   842  			OnCommitState: onCommitState,
   843  			OnAbortState:  onAbortState,
   844  			Backend:       &env.backend,
   845  			FeeCalculator: feeCalculator,
   846  			Tx:            tx,
   847  		}
   848  		err = tx.Unsigned.Visit(&executor)
   849  		require.ErrorIs(err, ErrTimestampNotBeforeStartTime)
   850  	}
   851  
   852  	{
   853  		nodeID := genesistest.DefaultNodeIDs[0]
   854  
   855  		// Case: Validator already validating primary network
   856  		wallet := newWallet(t, env, walletConfig{})
   857  		tx, err := wallet.IssueAddValidatorTx(
   858  			&txs.Validator{
   859  				NodeID: nodeID,
   860  				Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   861  				End:    genesistest.DefaultValidatorEndTimeUnix,
   862  				Wght:   env.config.MinValidatorStake,
   863  			},
   864  			rewardsOwner,
   865  			reward.PercentDenominator,
   866  		)
   867  		require.NoError(err)
   868  
   869  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   870  		require.NoError(err)
   871  
   872  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   873  		require.NoError(err)
   874  
   875  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   876  		executor := ProposalTxExecutor{
   877  			OnCommitState: onCommitState,
   878  			OnAbortState:  onAbortState,
   879  			Backend:       &env.backend,
   880  			FeeCalculator: feeCalculator,
   881  			Tx:            tx,
   882  		}
   883  		err = tx.Unsigned.Visit(&executor)
   884  		require.ErrorIs(err, ErrAlreadyValidator)
   885  	}
   886  
   887  	{
   888  		// Case: Validator in pending validator set of primary network
   889  		startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
   890  		wallet := newWallet(t, env, walletConfig{})
   891  		tx, err := wallet.IssueAddValidatorTx(
   892  			&txs.Validator{
   893  				NodeID: nodeID,
   894  				Start:  uint64(startTime.Unix()),
   895  				End:    uint64(startTime.Add(defaultMinStakingDuration).Unix()),
   896  				Wght:   env.config.MinValidatorStake,
   897  			},
   898  			rewardsOwner,
   899  			reward.PercentDenominator,
   900  		)
   901  		require.NoError(err)
   902  
   903  		addValTx := tx.Unsigned.(*txs.AddValidatorTx)
   904  		staker, err := state.NewCurrentStaker(
   905  			tx.ID(),
   906  			addValTx,
   907  			addValTx.StartTime(),
   908  			0,
   909  		)
   910  		require.NoError(err)
   911  
   912  		require.NoError(env.state.PutPendingValidator(staker))
   913  		env.state.AddTx(tx, status.Committed)
   914  		dummyHeight := uint64(1)
   915  		env.state.SetHeight(dummyHeight)
   916  		require.NoError(env.state.Commit())
   917  
   918  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   919  		require.NoError(err)
   920  
   921  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   922  		require.NoError(err)
   923  
   924  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   925  		executor := ProposalTxExecutor{
   926  			OnCommitState: onCommitState,
   927  			OnAbortState:  onAbortState,
   928  			Backend:       &env.backend,
   929  			FeeCalculator: feeCalculator,
   930  			Tx:            tx,
   931  		}
   932  		err = tx.Unsigned.Visit(&executor)
   933  		require.ErrorIs(err, ErrAlreadyValidator)
   934  	}
   935  
   936  	{
   937  		// Case: Validator doesn't have enough tokens to cover stake amount
   938  		wallet := newWallet(t, env, walletConfig{
   939  			keys: genesistest.DefaultFundedKeys[:1],
   940  		})
   941  		tx, err := wallet.IssueAddValidatorTx(
   942  			&txs.Validator{
   943  				NodeID: ids.GenerateTestNodeID(),
   944  				Start:  genesistest.DefaultValidatorStartTimeUnix + 1,
   945  				End:    genesistest.DefaultValidatorEndTimeUnix,
   946  				Wght:   env.config.MinValidatorStake,
   947  			},
   948  			rewardsOwner,
   949  			reward.PercentDenominator,
   950  		)
   951  		require.NoError(err)
   952  
   953  		// Remove all UTXOs owned by preFundedKeys[0]
   954  		utxoIDs, err := env.state.UTXOIDs(genesistest.DefaultFundedKeys[0].Address().Bytes(), ids.Empty, math.MaxInt32)
   955  		require.NoError(err)
   956  
   957  		for _, utxoID := range utxoIDs {
   958  			env.state.DeleteUTXO(utxoID)
   959  		}
   960  
   961  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   962  		require.NoError(err)
   963  
   964  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   965  		require.NoError(err)
   966  
   967  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   968  		executor := ProposalTxExecutor{
   969  			OnCommitState: onCommitState,
   970  			OnAbortState:  onAbortState,
   971  			Backend:       &env.backend,
   972  			FeeCalculator: feeCalculator,
   973  			Tx:            tx,
   974  		}
   975  		err = tx.Unsigned.Visit(&executor)
   976  		require.ErrorIs(err, ErrFlowCheckFailed)
   977  	}
   978  }