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