github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/advance_time_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  	"fmt"
     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/snow/snowtest"
    16  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    17  	"github.com/ava-labs/avalanchego/utils/constants"
    18  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    19  	"github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest"
    20  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    21  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    22  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    23  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    24  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    25  )
    26  
    27  func newAdvanceTimeTx(t testing.TB, timestamp time.Time) (*txs.Tx, error) {
    28  	utx := &txs.AdvanceTimeTx{Time: uint64(timestamp.Unix())}
    29  	tx, err := txs.NewSigned(utx, txs.Codec, nil)
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	return tx, tx.SyntacticVerify(snowtest.Context(t, snowtest.PChainID))
    34  }
    35  
    36  // Ensure semantic verification updates the current and pending staker set
    37  // for the primary network
    38  func TestAdvanceTimeTxUpdatePrimaryNetworkStakers(t *testing.T) {
    39  	require := require.New(t)
    40  	env := newEnvironment(t, upgradetest.ApricotPhase5)
    41  	env.ctx.Lock.Lock()
    42  	defer env.ctx.Lock.Unlock()
    43  	dummyHeight := uint64(1)
    44  
    45  	// Case: Timestamp is after next validator start time
    46  	// Add a pending validator
    47  	pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
    48  	pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration)
    49  	nodeID := ids.GenerateTestNodeID()
    50  	addPendingValidatorTx := addPendingValidator(
    51  		t,
    52  		env,
    53  		pendingValidatorStartTime,
    54  		pendingValidatorEndTime,
    55  		nodeID,
    56  		[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
    57  	)
    58  
    59  	tx, err := newAdvanceTimeTx(t, pendingValidatorStartTime)
    60  	require.NoError(err)
    61  
    62  	onCommitState, err := state.NewDiff(lastAcceptedID, env)
    63  	require.NoError(err)
    64  
    65  	onAbortState, err := state.NewDiff(lastAcceptedID, env)
    66  	require.NoError(err)
    67  
    68  	feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
    69  	executor := ProposalTxExecutor{
    70  		OnCommitState: onCommitState,
    71  		OnAbortState:  onAbortState,
    72  		Backend:       &env.backend,
    73  		FeeCalculator: feeCalculator,
    74  		Tx:            tx,
    75  	}
    76  	require.NoError(tx.Unsigned.Visit(&executor))
    77  
    78  	validatorStaker, err := executor.OnCommitState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
    79  	require.NoError(err)
    80  	require.Equal(addPendingValidatorTx.ID(), validatorStaker.TxID)
    81  	require.Equal(uint64(1370), validatorStaker.PotentialReward) // See rewards tests to explain why 1370
    82  
    83  	_, err = executor.OnCommitState.GetPendingValidator(constants.PrimaryNetworkID, nodeID)
    84  	require.ErrorIs(err, database.ErrNotFound)
    85  
    86  	_, err = executor.OnAbortState.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
    87  	require.ErrorIs(err, database.ErrNotFound)
    88  
    89  	validatorStaker, err = executor.OnAbortState.GetPendingValidator(constants.PrimaryNetworkID, nodeID)
    90  	require.NoError(err)
    91  	require.Equal(addPendingValidatorTx.ID(), validatorStaker.TxID)
    92  
    93  	// Test VM validators
    94  	require.NoError(executor.OnCommitState.Apply(env.state))
    95  
    96  	env.state.SetHeight(dummyHeight)
    97  	require.NoError(env.state.Commit())
    98  	_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, nodeID)
    99  	require.True(ok)
   100  }
   101  
   102  // Ensure semantic verification fails when proposed timestamp is at or before current timestamp
   103  func TestAdvanceTimeTxTimestampTooEarly(t *testing.T) {
   104  	require := require.New(t)
   105  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   106  
   107  	tx, err := newAdvanceTimeTx(t, env.state.GetTimestamp())
   108  	require.NoError(err)
   109  
   110  	onCommitState, err := state.NewDiff(lastAcceptedID, env)
   111  	require.NoError(err)
   112  
   113  	onAbortState, err := state.NewDiff(lastAcceptedID, env)
   114  	require.NoError(err)
   115  
   116  	feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   117  	executor := ProposalTxExecutor{
   118  		OnCommitState: onCommitState,
   119  		OnAbortState:  onAbortState,
   120  		Backend:       &env.backend,
   121  		FeeCalculator: feeCalculator,
   122  		Tx:            tx,
   123  	}
   124  	err = tx.Unsigned.Visit(&executor)
   125  	require.ErrorIs(err, ErrChildBlockNotAfterParent)
   126  }
   127  
   128  // Ensure semantic verification fails when proposed timestamp is after next validator set change time
   129  func TestAdvanceTimeTxTimestampTooLate(t *testing.T) {
   130  	require := require.New(t)
   131  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   132  	env.ctx.Lock.Lock()
   133  	defer env.ctx.Lock.Unlock()
   134  
   135  	// Case: Timestamp is after next validator start time
   136  	// Add a pending validator
   137  	pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
   138  	pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration)
   139  	nodeID := ids.GenerateTestNodeID()
   140  	addPendingValidator(t, env, pendingValidatorStartTime, pendingValidatorEndTime, nodeID, []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]})
   141  
   142  	{
   143  		tx, err := newAdvanceTimeTx(t, pendingValidatorStartTime.Add(1*time.Second))
   144  		require.NoError(err)
   145  
   146  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   147  		require.NoError(err)
   148  
   149  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   150  		require.NoError(err)
   151  
   152  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   153  		executor := ProposalTxExecutor{
   154  			OnCommitState: onCommitState,
   155  			OnAbortState:  onAbortState,
   156  			Backend:       &env.backend,
   157  			FeeCalculator: feeCalculator,
   158  			Tx:            tx,
   159  		}
   160  		err = tx.Unsigned.Visit(&executor)
   161  		require.ErrorIs(err, ErrChildBlockAfterStakerChangeTime)
   162  	}
   163  
   164  	// Case: Timestamp is after next validator end time
   165  	env = newEnvironment(t, upgradetest.ApricotPhase5)
   166  	env.ctx.Lock.Lock()
   167  	defer env.ctx.Lock.Unlock()
   168  
   169  	// fast forward clock to 10 seconds before genesis validators stop validating
   170  	env.clk.Set(genesistest.DefaultValidatorEndTime.Add(-10 * time.Second))
   171  
   172  	{
   173  		// Proposes advancing timestamp to 1 second after genesis validators stop validating
   174  		tx, err := newAdvanceTimeTx(t, genesistest.DefaultValidatorEndTime.Add(1*time.Second))
   175  		require.NoError(err)
   176  
   177  		onCommitState, err := state.NewDiff(lastAcceptedID, env)
   178  		require.NoError(err)
   179  
   180  		onAbortState, err := state.NewDiff(lastAcceptedID, env)
   181  		require.NoError(err)
   182  
   183  		feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   184  		executor := ProposalTxExecutor{
   185  			OnCommitState: onCommitState,
   186  			OnAbortState:  onAbortState,
   187  			Backend:       &env.backend,
   188  			FeeCalculator: feeCalculator,
   189  			Tx:            tx,
   190  		}
   191  		err = tx.Unsigned.Visit(&executor)
   192  		require.ErrorIs(err, ErrChildBlockAfterStakerChangeTime)
   193  	}
   194  }
   195  
   196  // Ensure semantic verification updates the current and pending staker sets correctly.
   197  // Namely, it should add pending stakers whose start time is at or before the timestamp.
   198  // It will not remove primary network stakers; that happens in rewardTxs.
   199  func TestAdvanceTimeTxUpdateStakers(t *testing.T) {
   200  	type stakerStatus uint
   201  	const (
   202  		pending stakerStatus = iota
   203  		current
   204  	)
   205  
   206  	type staker struct {
   207  		nodeID             ids.NodeID
   208  		startTime, endTime time.Time
   209  	}
   210  	type test struct {
   211  		description           string
   212  		stakers               []staker
   213  		subnetStakers         []staker
   214  		advanceTimeTo         []time.Time
   215  		expectedStakers       map[ids.NodeID]stakerStatus
   216  		expectedSubnetStakers map[ids.NodeID]stakerStatus
   217  	}
   218  
   219  	// Chronological order (not in scale):
   220  	// Staker1:    |----------------------------------------------------------|
   221  	// Staker2:        |------------------------|
   222  	// Staker3:            |------------------------|
   223  	// Staker3sub:             |----------------|
   224  	// Staker4:            |------------------------|
   225  	// Staker5:                                 |--------------------|
   226  	staker1 := staker{
   227  		nodeID:    ids.GenerateTestNodeID(),
   228  		startTime: genesistest.DefaultValidatorStartTime.Add(1 * time.Minute),
   229  		endTime:   genesistest.DefaultValidatorStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute),
   230  	}
   231  	staker2 := staker{
   232  		nodeID:    ids.GenerateTestNodeID(),
   233  		startTime: staker1.startTime.Add(1 * time.Minute),
   234  		endTime:   staker1.startTime.Add(1 * time.Minute).Add(defaultMinStakingDuration),
   235  	}
   236  	staker3 := staker{
   237  		nodeID:    ids.GenerateTestNodeID(),
   238  		startTime: staker2.startTime.Add(1 * time.Minute),
   239  		endTime:   staker2.endTime.Add(1 * time.Minute),
   240  	}
   241  	staker3Sub := staker{
   242  		nodeID:    staker3.nodeID,
   243  		startTime: staker3.startTime.Add(1 * time.Minute),
   244  		endTime:   staker3.endTime.Add(-1 * time.Minute),
   245  	}
   246  	staker4 := staker{
   247  		nodeID:    ids.GenerateTestNodeID(),
   248  		startTime: staker3.startTime,
   249  		endTime:   staker3.endTime,
   250  	}
   251  	staker5 := staker{
   252  		nodeID:    ids.GenerateTestNodeID(),
   253  		startTime: staker2.endTime,
   254  		endTime:   staker2.endTime.Add(defaultMinStakingDuration),
   255  	}
   256  
   257  	tests := []test{
   258  		{
   259  			description:   "advance time to before staker1 start with subnet",
   260  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   261  			subnetStakers: []staker{staker1, staker2, staker3, staker4, staker5},
   262  			advanceTimeTo: []time.Time{staker1.startTime.Add(-1 * time.Second)},
   263  			expectedStakers: map[ids.NodeID]stakerStatus{
   264  				staker1.nodeID: pending,
   265  				staker2.nodeID: pending,
   266  				staker3.nodeID: pending,
   267  				staker4.nodeID: pending,
   268  				staker5.nodeID: pending,
   269  			},
   270  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   271  				staker1.nodeID: pending,
   272  				staker2.nodeID: pending,
   273  				staker3.nodeID: pending,
   274  				staker4.nodeID: pending,
   275  				staker5.nodeID: pending,
   276  			},
   277  		},
   278  		{
   279  			description:   "advance time to staker 1 start with subnet",
   280  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   281  			subnetStakers: []staker{staker1},
   282  			advanceTimeTo: []time.Time{staker1.startTime},
   283  			expectedStakers: map[ids.NodeID]stakerStatus{
   284  				staker1.nodeID: current,
   285  				staker2.nodeID: pending,
   286  				staker3.nodeID: pending,
   287  				staker4.nodeID: pending,
   288  				staker5.nodeID: pending,
   289  			},
   290  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   291  				staker1.nodeID: current,
   292  				staker2.nodeID: pending,
   293  				staker3.nodeID: pending,
   294  				staker4.nodeID: pending,
   295  				staker5.nodeID: pending,
   296  			},
   297  		},
   298  		{
   299  			description:   "advance time to the staker2 start",
   300  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   301  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime},
   302  			expectedStakers: map[ids.NodeID]stakerStatus{
   303  				staker1.nodeID: current,
   304  				staker2.nodeID: current,
   305  				staker3.nodeID: pending,
   306  				staker4.nodeID: pending,
   307  				staker5.nodeID: pending,
   308  			},
   309  		},
   310  		{
   311  			description:   "staker3 should validate only primary network",
   312  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   313  			subnetStakers: []staker{staker1, staker2, staker3Sub, staker4, staker5},
   314  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime},
   315  			expectedStakers: map[ids.NodeID]stakerStatus{
   316  				staker1.nodeID: current,
   317  				staker2.nodeID: current,
   318  				staker3.nodeID: current,
   319  				staker4.nodeID: current,
   320  				staker5.nodeID: pending,
   321  			},
   322  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   323  				staker1.nodeID:    current,
   324  				staker2.nodeID:    current,
   325  				staker3Sub.nodeID: pending,
   326  				staker4.nodeID:    current,
   327  				staker5.nodeID:    pending,
   328  			},
   329  		},
   330  		{
   331  			description:   "advance time to staker3 start with subnet",
   332  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   333  			subnetStakers: []staker{staker1, staker2, staker3Sub, staker4, staker5},
   334  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime, staker3Sub.startTime},
   335  			expectedStakers: map[ids.NodeID]stakerStatus{
   336  				staker1.nodeID: current,
   337  				staker2.nodeID: current,
   338  				staker3.nodeID: current,
   339  				staker4.nodeID: current,
   340  				staker5.nodeID: pending,
   341  			},
   342  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   343  				staker1.nodeID: current,
   344  				staker2.nodeID: current,
   345  				staker3.nodeID: current,
   346  				staker4.nodeID: current,
   347  				staker5.nodeID: pending,
   348  			},
   349  		},
   350  		{
   351  			description:   "advance time to staker5 end",
   352  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   353  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime, staker5.startTime},
   354  			expectedStakers: map[ids.NodeID]stakerStatus{
   355  				staker1.nodeID: current,
   356  				staker2.nodeID: current,
   357  				staker3.nodeID: current,
   358  				staker4.nodeID: current,
   359  				staker5.nodeID: current,
   360  			},
   361  		},
   362  	}
   363  
   364  	for _, test := range tests {
   365  		t.Run(test.description, func(t *testing.T) {
   366  			require := require.New(t)
   367  			env := newEnvironment(t, upgradetest.ApricotPhase5)
   368  			env.ctx.Lock.Lock()
   369  			defer env.ctx.Lock.Unlock()
   370  
   371  			dummyHeight := uint64(1)
   372  
   373  			subnetID := testSubnet1.ID()
   374  			env.config.TrackedSubnets.Add(subnetID)
   375  
   376  			for _, staker := range test.stakers {
   377  				addPendingValidator(
   378  					t,
   379  					env,
   380  					staker.startTime,
   381  					staker.endTime,
   382  					staker.nodeID,
   383  					[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   384  				)
   385  			}
   386  
   387  			for _, staker := range test.subnetStakers {
   388  				wallet := newWallet(t, env, walletConfig{
   389  					subnetIDs: []ids.ID{subnetID},
   390  				})
   391  
   392  				tx, err := wallet.IssueAddSubnetValidatorTx(
   393  					&txs.SubnetValidator{
   394  						Validator: txs.Validator{
   395  							NodeID: staker.nodeID,
   396  							Start:  uint64(staker.startTime.Unix()),
   397  							End:    uint64(staker.endTime.Unix()),
   398  							Wght:   10,
   399  						},
   400  						Subnet: subnetID,
   401  					},
   402  				)
   403  				require.NoError(err)
   404  
   405  				staker, err := state.NewPendingStaker(
   406  					tx.ID(),
   407  					tx.Unsigned.(*txs.AddSubnetValidatorTx),
   408  				)
   409  				require.NoError(err)
   410  
   411  				require.NoError(env.state.PutPendingValidator(staker))
   412  				env.state.AddTx(tx, status.Committed)
   413  			}
   414  			env.state.SetHeight(dummyHeight)
   415  			require.NoError(env.state.Commit())
   416  
   417  			for _, newTime := range test.advanceTimeTo {
   418  				env.clk.Set(newTime)
   419  				tx, err := newAdvanceTimeTx(t, newTime)
   420  				require.NoError(err)
   421  
   422  				onCommitState, err := state.NewDiff(lastAcceptedID, env)
   423  				require.NoError(err)
   424  
   425  				onAbortState, err := state.NewDiff(lastAcceptedID, env)
   426  				require.NoError(err)
   427  
   428  				feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   429  				executor := ProposalTxExecutor{
   430  					OnCommitState: onCommitState,
   431  					OnAbortState:  onAbortState,
   432  					Backend:       &env.backend,
   433  					FeeCalculator: feeCalculator,
   434  					Tx:            tx,
   435  				}
   436  				require.NoError(tx.Unsigned.Visit(&executor))
   437  
   438  				require.NoError(executor.OnCommitState.Apply(env.state))
   439  			}
   440  			env.state.SetHeight(dummyHeight)
   441  			require.NoError(env.state.Commit())
   442  
   443  			for stakerNodeID, status := range test.expectedStakers {
   444  				switch status {
   445  				case pending:
   446  					_, err := env.state.GetPendingValidator(constants.PrimaryNetworkID, stakerNodeID)
   447  					require.NoError(err)
   448  					_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, stakerNodeID)
   449  					require.False(ok)
   450  				case current:
   451  					_, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, stakerNodeID)
   452  					require.NoError(err)
   453  					_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, stakerNodeID)
   454  					require.True(ok)
   455  				}
   456  			}
   457  
   458  			for stakerNodeID, status := range test.expectedSubnetStakers {
   459  				switch status {
   460  				case pending:
   461  					_, ok := env.config.Validators.GetValidator(subnetID, stakerNodeID)
   462  					require.False(ok)
   463  				case current:
   464  					_, ok := env.config.Validators.GetValidator(subnetID, stakerNodeID)
   465  					require.True(ok)
   466  				}
   467  			}
   468  		})
   469  	}
   470  }
   471  
   472  // Regression test for https://github.com/ava-labs/avalanchego/pull/584
   473  // that ensures it fixes a bug where subnet validators are not removed
   474  // when timestamp is advanced and there is a pending staker whose start time
   475  // is after the new timestamp
   476  func TestAdvanceTimeTxRemoveSubnetValidator(t *testing.T) {
   477  	require := require.New(t)
   478  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   479  	env.ctx.Lock.Lock()
   480  	defer env.ctx.Lock.Unlock()
   481  
   482  	subnetID := testSubnet1.ID()
   483  	env.config.TrackedSubnets.Add(subnetID)
   484  
   485  	wallet := newWallet(t, env, walletConfig{
   486  		subnetIDs: []ids.ID{subnetID},
   487  	})
   488  
   489  	dummyHeight := uint64(1)
   490  	// Add a subnet validator to the staker set
   491  	subnetValidatorNodeID := genesistest.DefaultNodeIDs[0]
   492  	subnetVdr1EndTime := genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration)
   493  
   494  	tx, err := wallet.IssueAddSubnetValidatorTx(
   495  		&txs.SubnetValidator{
   496  			Validator: txs.Validator{
   497  				NodeID: subnetValidatorNodeID,
   498  				Start:  genesistest.DefaultValidatorStartTimeUnix,
   499  				End:    uint64(subnetVdr1EndTime.Unix()),
   500  				Wght:   1,
   501  			},
   502  			Subnet: subnetID,
   503  		},
   504  	)
   505  	require.NoError(err)
   506  
   507  	addSubnetValTx := tx.Unsigned.(*txs.AddSubnetValidatorTx)
   508  	staker, err := state.NewCurrentStaker(
   509  		tx.ID(),
   510  		addSubnetValTx,
   511  		addSubnetValTx.StartTime(),
   512  		0,
   513  	)
   514  	require.NoError(err)
   515  
   516  	require.NoError(env.state.PutCurrentValidator(staker))
   517  	env.state.AddTx(tx, status.Committed)
   518  	env.state.SetHeight(dummyHeight)
   519  	require.NoError(env.state.Commit())
   520  
   521  	// The above validator is now part of the staking set
   522  
   523  	// Queue a staker that joins the staker set after the above validator leaves
   524  	subnetVdr2NodeID := genesistest.DefaultNodeIDs[1]
   525  	tx, err = wallet.IssueAddSubnetValidatorTx(
   526  		&txs.SubnetValidator{
   527  			Validator: txs.Validator{
   528  				NodeID: subnetVdr2NodeID,
   529  				Start:  uint64(subnetVdr1EndTime.Add(time.Second).Unix()),
   530  				End:    uint64(subnetVdr1EndTime.Add(time.Second).Add(defaultMinStakingDuration).Unix()),
   531  				Wght:   1,
   532  			},
   533  			Subnet: subnetID,
   534  		},
   535  	)
   536  	require.NoError(err)
   537  
   538  	staker, err = state.NewPendingStaker(
   539  		tx.ID(),
   540  		tx.Unsigned.(*txs.AddSubnetValidatorTx),
   541  	)
   542  	require.NoError(err)
   543  
   544  	require.NoError(env.state.PutPendingValidator(staker))
   545  	env.state.AddTx(tx, status.Committed)
   546  	env.state.SetHeight(dummyHeight)
   547  	require.NoError(env.state.Commit())
   548  
   549  	// The above validator is now in the pending staker set
   550  
   551  	// Advance time to the first staker's end time.
   552  	env.clk.Set(subnetVdr1EndTime)
   553  	tx, err = newAdvanceTimeTx(t, subnetVdr1EndTime)
   554  	require.NoError(err)
   555  
   556  	onCommitState, err := state.NewDiff(lastAcceptedID, env)
   557  	require.NoError(err)
   558  
   559  	onAbortState, err := state.NewDiff(lastAcceptedID, env)
   560  	require.NoError(err)
   561  
   562  	feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   563  	executor := ProposalTxExecutor{
   564  		OnCommitState: onCommitState,
   565  		OnAbortState:  onAbortState,
   566  		Backend:       &env.backend,
   567  		FeeCalculator: feeCalculator,
   568  		Tx:            tx,
   569  	}
   570  	require.NoError(tx.Unsigned.Visit(&executor))
   571  
   572  	_, err = executor.OnCommitState.GetCurrentValidator(subnetID, subnetValidatorNodeID)
   573  	require.ErrorIs(err, database.ErrNotFound)
   574  
   575  	// Check VM Validators are removed successfully
   576  	require.NoError(executor.OnCommitState.Apply(env.state))
   577  
   578  	env.state.SetHeight(dummyHeight)
   579  	require.NoError(env.state.Commit())
   580  	_, ok := env.config.Validators.GetValidator(subnetID, subnetVdr2NodeID)
   581  	require.False(ok)
   582  	_, ok = env.config.Validators.GetValidator(subnetID, subnetValidatorNodeID)
   583  	require.False(ok)
   584  }
   585  
   586  func TestTrackedSubnet(t *testing.T) {
   587  	for _, tracked := range []bool{true, false} {
   588  		t.Run(fmt.Sprintf("tracked %t", tracked), func(t *testing.T) {
   589  			require := require.New(t)
   590  			env := newEnvironment(t, upgradetest.ApricotPhase5)
   591  			env.ctx.Lock.Lock()
   592  			defer env.ctx.Lock.Unlock()
   593  			dummyHeight := uint64(1)
   594  
   595  			subnetID := testSubnet1.ID()
   596  			if tracked {
   597  				env.config.TrackedSubnets.Add(subnetID)
   598  			}
   599  
   600  			wallet := newWallet(t, env, walletConfig{
   601  				subnetIDs: []ids.ID{subnetID},
   602  			})
   603  
   604  			// Add a subnet validator to the staker set
   605  			subnetValidatorNodeID := genesistest.DefaultNodeIDs[0]
   606  
   607  			subnetVdr1StartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Minute)
   608  			subnetVdr1EndTime := genesistest.DefaultValidatorStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute)
   609  			tx, err := wallet.IssueAddSubnetValidatorTx(
   610  				&txs.SubnetValidator{
   611  					Validator: txs.Validator{
   612  						NodeID: subnetValidatorNodeID,
   613  						Start:  uint64(subnetVdr1StartTime.Unix()),
   614  						End:    uint64(subnetVdr1EndTime.Unix()),
   615  						Wght:   1,
   616  					},
   617  					Subnet: subnetID,
   618  				},
   619  			)
   620  			require.NoError(err)
   621  
   622  			staker, err := state.NewPendingStaker(
   623  				tx.ID(),
   624  				tx.Unsigned.(*txs.AddSubnetValidatorTx),
   625  			)
   626  			require.NoError(err)
   627  
   628  			require.NoError(env.state.PutPendingValidator(staker))
   629  			env.state.AddTx(tx, status.Committed)
   630  			env.state.SetHeight(dummyHeight)
   631  			require.NoError(env.state.Commit())
   632  
   633  			// Advance time to the staker's start time.
   634  			env.clk.Set(subnetVdr1StartTime)
   635  			tx, err = newAdvanceTimeTx(t, subnetVdr1StartTime)
   636  			require.NoError(err)
   637  
   638  			onCommitState, err := state.NewDiff(lastAcceptedID, env)
   639  			require.NoError(err)
   640  
   641  			onAbortState, err := state.NewDiff(lastAcceptedID, env)
   642  			require.NoError(err)
   643  
   644  			feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   645  			executor := ProposalTxExecutor{
   646  				OnCommitState: onCommitState,
   647  				OnAbortState:  onAbortState,
   648  				Backend:       &env.backend,
   649  				FeeCalculator: feeCalculator,
   650  				Tx:            tx,
   651  			}
   652  			require.NoError(tx.Unsigned.Visit(&executor))
   653  
   654  			require.NoError(executor.OnCommitState.Apply(env.state))
   655  
   656  			env.state.SetHeight(dummyHeight)
   657  			require.NoError(env.state.Commit())
   658  			_, ok := env.config.Validators.GetValidator(subnetID, subnetValidatorNodeID)
   659  			require.True(ok)
   660  		})
   661  	}
   662  }
   663  
   664  func TestAdvanceTimeTxDelegatorStakerWeight(t *testing.T) {
   665  	require := require.New(t)
   666  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   667  	env.ctx.Lock.Lock()
   668  	defer env.ctx.Lock.Unlock()
   669  	dummyHeight := uint64(1)
   670  
   671  	// Case: Timestamp is after next validator start time
   672  	// Add a pending validator
   673  	pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
   674  	pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMaxStakingDuration)
   675  	nodeID := ids.GenerateTestNodeID()
   676  	addPendingValidator(
   677  		t,
   678  		env,
   679  		pendingValidatorStartTime,
   680  		pendingValidatorEndTime,
   681  		nodeID,
   682  		[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   683  	)
   684  
   685  	tx, err := newAdvanceTimeTx(t, pendingValidatorStartTime)
   686  	require.NoError(err)
   687  
   688  	onCommitState, err := state.NewDiff(lastAcceptedID, env)
   689  	require.NoError(err)
   690  
   691  	onAbortState, err := state.NewDiff(lastAcceptedID, env)
   692  	require.NoError(err)
   693  
   694  	feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   695  	executor := ProposalTxExecutor{
   696  		OnCommitState: onCommitState,
   697  		OnAbortState:  onAbortState,
   698  		Backend:       &env.backend,
   699  		FeeCalculator: feeCalculator,
   700  		Tx:            tx,
   701  	}
   702  	require.NoError(tx.Unsigned.Visit(&executor))
   703  
   704  	require.NoError(executor.OnCommitState.Apply(env.state))
   705  
   706  	env.state.SetHeight(dummyHeight)
   707  	require.NoError(env.state.Commit())
   708  
   709  	wallet := newWallet(t, env, walletConfig{})
   710  
   711  	// Test validator weight before delegation
   712  	vdrWeight := env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
   713  	require.Equal(env.config.MinValidatorStake, vdrWeight)
   714  
   715  	// Add delegator
   716  	pendingDelegatorStartTime := pendingValidatorStartTime.Add(1 * time.Second)
   717  	pendingDelegatorEndTime := pendingDelegatorStartTime.Add(1 * time.Second)
   718  
   719  	addDelegatorTx, err := wallet.IssueAddDelegatorTx(
   720  		&txs.Validator{
   721  			NodeID: nodeID,
   722  			Start:  uint64(pendingDelegatorStartTime.Unix()),
   723  			End:    uint64(pendingDelegatorEndTime.Unix()),
   724  			Wght:   env.config.MinDelegatorStake,
   725  		},
   726  		&secp256k1fx.OutputOwners{
   727  			Threshold: 1,
   728  			Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   729  		},
   730  	)
   731  	require.NoError(err)
   732  
   733  	staker, err := state.NewPendingStaker(
   734  		addDelegatorTx.ID(),
   735  		addDelegatorTx.Unsigned.(*txs.AddDelegatorTx),
   736  	)
   737  	require.NoError(err)
   738  
   739  	env.state.PutPendingDelegator(staker)
   740  	env.state.AddTx(addDelegatorTx, status.Committed)
   741  	env.state.SetHeight(dummyHeight)
   742  	require.NoError(env.state.Commit())
   743  
   744  	// Advance Time
   745  	tx, err = newAdvanceTimeTx(t, pendingDelegatorStartTime)
   746  	require.NoError(err)
   747  
   748  	onCommitState, err = state.NewDiff(lastAcceptedID, env)
   749  	require.NoError(err)
   750  
   751  	onAbortState, err = state.NewDiff(lastAcceptedID, env)
   752  	require.NoError(err)
   753  
   754  	executor = ProposalTxExecutor{
   755  		OnCommitState: onCommitState,
   756  		OnAbortState:  onAbortState,
   757  		Backend:       &env.backend,
   758  		FeeCalculator: feeCalculator,
   759  		Tx:            tx,
   760  	}
   761  	require.NoError(tx.Unsigned.Visit(&executor))
   762  
   763  	require.NoError(executor.OnCommitState.Apply(env.state))
   764  
   765  	env.state.SetHeight(dummyHeight)
   766  	require.NoError(env.state.Commit())
   767  
   768  	// Test validator weight after delegation
   769  	vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
   770  	require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight)
   771  }
   772  
   773  func TestAdvanceTimeTxDelegatorStakers(t *testing.T) {
   774  	require := require.New(t)
   775  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   776  	env.ctx.Lock.Lock()
   777  	defer env.ctx.Lock.Unlock()
   778  	dummyHeight := uint64(1)
   779  
   780  	// Case: Timestamp is after next validator start time
   781  	// Add a pending validator
   782  	pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
   783  	pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration)
   784  	nodeID := ids.GenerateTestNodeID()
   785  	addPendingValidator(t, env, pendingValidatorStartTime, pendingValidatorEndTime, nodeID, []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]})
   786  
   787  	tx, err := newAdvanceTimeTx(t, pendingValidatorStartTime)
   788  	require.NoError(err)
   789  
   790  	onCommitState, err := state.NewDiff(lastAcceptedID, env)
   791  	require.NoError(err)
   792  
   793  	onAbortState, err := state.NewDiff(lastAcceptedID, env)
   794  	require.NoError(err)
   795  
   796  	feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   797  	executor := ProposalTxExecutor{
   798  		OnCommitState: onCommitState,
   799  		OnAbortState:  onAbortState,
   800  		Backend:       &env.backend,
   801  		FeeCalculator: feeCalculator,
   802  		Tx:            tx,
   803  	}
   804  	require.NoError(tx.Unsigned.Visit(&executor))
   805  
   806  	require.NoError(executor.OnCommitState.Apply(env.state))
   807  
   808  	env.state.SetHeight(dummyHeight)
   809  	require.NoError(env.state.Commit())
   810  
   811  	wallet := newWallet(t, env, walletConfig{})
   812  
   813  	// Test validator weight before delegation
   814  	vdrWeight := env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
   815  	require.Equal(env.config.MinValidatorStake, vdrWeight)
   816  
   817  	// Add delegator
   818  	pendingDelegatorStartTime := pendingValidatorStartTime.Add(1 * time.Second)
   819  	pendingDelegatorEndTime := pendingDelegatorStartTime.Add(defaultMinStakingDuration)
   820  	addDelegatorTx, err := wallet.IssueAddDelegatorTx(
   821  		&txs.Validator{
   822  			NodeID: nodeID,
   823  			Start:  uint64(pendingDelegatorStartTime.Unix()),
   824  			End:    uint64(pendingDelegatorEndTime.Unix()),
   825  			Wght:   env.config.MinDelegatorStake,
   826  		},
   827  		&secp256k1fx.OutputOwners{
   828  			Threshold: 1,
   829  			Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   830  		},
   831  	)
   832  	require.NoError(err)
   833  
   834  	staker, err := state.NewPendingStaker(
   835  		addDelegatorTx.ID(),
   836  		addDelegatorTx.Unsigned.(*txs.AddDelegatorTx),
   837  	)
   838  	require.NoError(err)
   839  
   840  	env.state.PutPendingDelegator(staker)
   841  	env.state.AddTx(addDelegatorTx, status.Committed)
   842  	env.state.SetHeight(dummyHeight)
   843  	require.NoError(env.state.Commit())
   844  
   845  	// Advance Time
   846  	tx, err = newAdvanceTimeTx(t, pendingDelegatorStartTime)
   847  	require.NoError(err)
   848  
   849  	onCommitState, err = state.NewDiff(lastAcceptedID, env)
   850  	require.NoError(err)
   851  
   852  	onAbortState, err = state.NewDiff(lastAcceptedID, env)
   853  	require.NoError(err)
   854  
   855  	executor = ProposalTxExecutor{
   856  		OnCommitState: onCommitState,
   857  		OnAbortState:  onAbortState,
   858  		Backend:       &env.backend,
   859  		FeeCalculator: feeCalculator,
   860  		Tx:            tx,
   861  	}
   862  	require.NoError(tx.Unsigned.Visit(&executor))
   863  
   864  	require.NoError(executor.OnCommitState.Apply(env.state))
   865  
   866  	env.state.SetHeight(dummyHeight)
   867  	require.NoError(env.state.Commit())
   868  
   869  	// Test validator weight after delegation
   870  	vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
   871  	require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight)
   872  }
   873  
   874  func TestAdvanceTimeTxAfterBanff(t *testing.T) {
   875  	require := require.New(t)
   876  	env := newEnvironment(t, upgradetest.Durango)
   877  	env.ctx.Lock.Lock()
   878  	defer env.ctx.Lock.Unlock()
   879  	env.clk.Set(genesistest.DefaultValidatorStartTime) // VM's clock reads the genesis time
   880  	upgradeTime := env.clk.Time().Add(SyncBound)
   881  	env.config.UpgradeConfig.BanffTime = upgradeTime
   882  	env.config.UpgradeConfig.CortinaTime = upgradeTime
   883  	env.config.UpgradeConfig.DurangoTime = upgradeTime
   884  
   885  	// Proposed advancing timestamp to the banff timestamp
   886  	tx, err := newAdvanceTimeTx(t, upgradeTime)
   887  	require.NoError(err)
   888  
   889  	onCommitState, err := state.NewDiff(lastAcceptedID, env)
   890  	require.NoError(err)
   891  
   892  	onAbortState, err := state.NewDiff(lastAcceptedID, env)
   893  	require.NoError(err)
   894  
   895  	feeCalculator := state.PickFeeCalculator(env.config, onCommitState)
   896  	executor := ProposalTxExecutor{
   897  		OnCommitState: onCommitState,
   898  		OnAbortState:  onAbortState,
   899  		Backend:       &env.backend,
   900  		FeeCalculator: feeCalculator,
   901  		Tx:            tx,
   902  	}
   903  	err = tx.Unsigned.Visit(&executor)
   904  	require.ErrorIs(err, ErrAdvanceTimeTxIssuedAfterBanff)
   905  }
   906  
   907  // Ensure marshaling/unmarshaling works
   908  func TestAdvanceTimeTxUnmarshal(t *testing.T) {
   909  	require := require.New(t)
   910  	env := newEnvironment(t, upgradetest.ApricotPhase5)
   911  	env.ctx.Lock.Lock()
   912  	defer env.ctx.Lock.Unlock()
   913  
   914  	chainTime := env.state.GetTimestamp()
   915  	tx, err := newAdvanceTimeTx(t, chainTime.Add(time.Second))
   916  	require.NoError(err)
   917  
   918  	bytes, err := txs.Codec.Marshal(txs.CodecVersion, tx)
   919  	require.NoError(err)
   920  
   921  	var unmarshaledTx txs.Tx
   922  	_, err = txs.Codec.Unmarshal(bytes, &unmarshaledTx)
   923  	require.NoError(err)
   924  
   925  	require.Equal(
   926  		tx.Unsigned.(*txs.AdvanceTimeTx).Time,
   927  		unmarshaledTx.Unsigned.(*txs.AdvanceTimeTx).Time,
   928  	)
   929  }
   930  
   931  func addPendingValidator(
   932  	t testing.TB,
   933  	env *environment,
   934  	startTime time.Time,
   935  	endTime time.Time,
   936  	nodeID ids.NodeID,
   937  	keys []*secp256k1.PrivateKey,
   938  ) *txs.Tx {
   939  	require := require.New(t)
   940  
   941  	wallet := newWallet(t, env, walletConfig{
   942  		keys: keys,
   943  	})
   944  	addPendingValidatorTx, err := wallet.IssueAddValidatorTx(
   945  		&txs.Validator{
   946  			NodeID: nodeID,
   947  			Start:  uint64(startTime.Unix()),
   948  			End:    uint64(endTime.Unix()),
   949  			Wght:   env.config.MinValidatorStake,
   950  		},
   951  		&secp256k1fx.OutputOwners{
   952  			Threshold: 1,
   953  			Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   954  		},
   955  		reward.PercentDenominator,
   956  	)
   957  	require.NoError(err)
   958  
   959  	staker, err := state.NewPendingStaker(
   960  		addPendingValidatorTx.ID(),
   961  		addPendingValidatorTx.Unsigned.(*txs.AddValidatorTx),
   962  	)
   963  	require.NoError(err)
   964  
   965  	require.NoError(env.state.PutPendingValidator(staker))
   966  	env.state.AddTx(addPendingValidatorTx, status.Committed)
   967  	dummyHeight := uint64(1)
   968  	env.state.SetHeight(dummyHeight)
   969  	require.NoError(env.state.Commit())
   970  	return addPendingValidatorTx
   971  }