github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/block/executor/proposal_block_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  	"fmt"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/require"
    13  	"go.uber.org/mock/gomock"
    14  
    15  	"github.com/ava-labs/avalanchego/database"
    16  	"github.com/ava-labs/avalanchego/ids"
    17  	"github.com/ava-labs/avalanchego/snow/consensus/snowman"
    18  	"github.com/ava-labs/avalanchego/snow/snowtest"
    19  	"github.com/ava-labs/avalanchego/upgrade/upgradetest"
    20  	"github.com/ava-labs/avalanchego/utils/constants"
    21  	"github.com/ava-labs/avalanchego/utils/crypto/bls"
    22  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    23  	"github.com/ava-labs/avalanchego/utils/iterator/iteratormock"
    24  	"github.com/ava-labs/avalanchego/vms/components/avax"
    25  	"github.com/ava-labs/avalanchego/vms/components/gas"
    26  	"github.com/ava-labs/avalanchego/vms/platformvm/block"
    27  	"github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest"
    28  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    29  	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
    30  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    31  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    32  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    33  	"github.com/ava-labs/avalanchego/vms/platformvm/txs/executor"
    34  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    35  
    36  	walletcommon "github.com/ava-labs/avalanchego/wallet/subnet/primary/common"
    37  )
    38  
    39  func TestApricotProposalBlockTimeVerification(t *testing.T) {
    40  	require := require.New(t)
    41  	ctrl := gomock.NewController(t)
    42  
    43  	env := newEnvironment(t, ctrl, upgradetest.ApricotPhase5)
    44  
    45  	// create apricotParentBlk. It's a standard one for simplicity
    46  	parentHeight := uint64(2022)
    47  
    48  	apricotParentBlk, err := block.NewApricotStandardBlock(
    49  		ids.Empty, // does not matter
    50  		parentHeight,
    51  		nil, // txs do not matter in this test
    52  	)
    53  	require.NoError(err)
    54  	parentID := apricotParentBlk.ID()
    55  
    56  	// store parent block, with relevant quantities
    57  	onParentAccept := state.NewMockDiff(ctrl)
    58  	env.blkManager.(*manager).blkIDToState[parentID] = &blockState{
    59  		statelessBlock: apricotParentBlk,
    60  		onAcceptState:  onParentAccept,
    61  	}
    62  	env.blkManager.(*manager).lastAccepted = parentID
    63  	chainTime := env.clk.Time().Truncate(time.Second)
    64  
    65  	// create a proposal transaction to be included into proposal block
    66  	utx := &txs.AddValidatorTx{
    67  		BaseTx:    txs.BaseTx{},
    68  		Validator: txs.Validator{End: uint64(chainTime.Unix())},
    69  		StakeOuts: []*avax.TransferableOutput{
    70  			{
    71  				Asset: avax.Asset{
    72  					ID: env.ctx.AVAXAssetID,
    73  				},
    74  				Out: &secp256k1fx.TransferOutput{
    75  					Amt: 1,
    76  				},
    77  			},
    78  		},
    79  		RewardsOwner:     &secp256k1fx.OutputOwners{},
    80  		DelegationShares: uint32(defaultTxFee),
    81  	}
    82  	addValTx := &txs.Tx{Unsigned: utx}
    83  	require.NoError(addValTx.Initialize(txs.Codec))
    84  	blkTx := &txs.Tx{
    85  		Unsigned: &txs.RewardValidatorTx{
    86  			TxID: addValTx.ID(),
    87  		},
    88  	}
    89  
    90  	// setup state to validate proposal block transaction
    91  	onParentAccept.EXPECT().GetTimestamp().Return(chainTime).AnyTimes()
    92  	onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes()
    93  
    94  	currentStakersIt := iteratormock.NewIterator[*state.Staker](ctrl)
    95  	currentStakersIt.EXPECT().Next().Return(true)
    96  	currentStakersIt.EXPECT().Value().Return(&state.Staker{
    97  		TxID:      addValTx.ID(),
    98  		NodeID:    utx.NodeID(),
    99  		SubnetID:  utx.SubnetID(),
   100  		StartTime: utx.StartTime(),
   101  		NextTime:  chainTime,
   102  		EndTime:   chainTime,
   103  	}).Times(2)
   104  	currentStakersIt.EXPECT().Release()
   105  	onParentAccept.EXPECT().GetCurrentStakerIterator().Return(currentStakersIt, nil)
   106  	onParentAccept.EXPECT().GetTx(addValTx.ID()).Return(addValTx, status.Committed, nil)
   107  	onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes()
   108  	onParentAccept.EXPECT().GetDelegateeReward(constants.PrimaryNetworkID, utx.NodeID()).Return(uint64(0), nil).AnyTimes()
   109  
   110  	env.mockedState.EXPECT().GetUptime(gomock.Any(), constants.PrimaryNetworkID).Return(
   111  		time.Microsecond, /*upDuration*/
   112  		time.Time{},      /*lastUpdated*/
   113  		nil,              /*err*/
   114  	).AnyTimes()
   115  
   116  	// wrong height
   117  	statelessProposalBlock, err := block.NewApricotProposalBlock(
   118  		parentID,
   119  		parentHeight,
   120  		blkTx,
   121  	)
   122  	require.NoError(err)
   123  
   124  	proposalBlock := env.blkManager.NewBlock(statelessProposalBlock)
   125  
   126  	err = proposalBlock.Verify(context.Background())
   127  	require.ErrorIs(err, errIncorrectBlockHeight)
   128  
   129  	// valid
   130  	statelessProposalBlock, err = block.NewApricotProposalBlock(
   131  		parentID,
   132  		parentHeight+1,
   133  		blkTx,
   134  	)
   135  	require.NoError(err)
   136  
   137  	proposalBlock = env.blkManager.NewBlock(statelessProposalBlock)
   138  	require.NoError(proposalBlock.Verify(context.Background()))
   139  }
   140  
   141  func TestBanffProposalBlockTimeVerification(t *testing.T) {
   142  	require := require.New(t)
   143  	ctrl := gomock.NewController(t)
   144  
   145  	env := newEnvironment(t, ctrl, upgradetest.Banff)
   146  
   147  	// create parentBlock. It's a standard one for simplicity
   148  	parentTime := genesistest.DefaultValidatorStartTime
   149  	parentHeight := uint64(2022)
   150  
   151  	banffParentBlk, err := block.NewApricotStandardBlock(
   152  		ids.GenerateTestID(), // does not matter
   153  		parentHeight,
   154  		nil, // txs do not matter in this test
   155  	)
   156  	require.NoError(err)
   157  	parentID := banffParentBlk.ID()
   158  
   159  	// store parent block, with relevant quantities
   160  	chainTime := parentTime
   161  	onParentAccept := state.NewMockDiff(ctrl)
   162  	onParentAccept.EXPECT().GetTimestamp().Return(parentTime).AnyTimes()
   163  	onParentAccept.EXPECT().GetFeeState().Return(gas.State{}).AnyTimes()
   164  	onParentAccept.EXPECT().GetCurrentSupply(constants.PrimaryNetworkID).Return(uint64(1000), nil).AnyTimes()
   165  
   166  	env.blkManager.(*manager).blkIDToState[parentID] = &blockState{
   167  		statelessBlock: banffParentBlk,
   168  		onAcceptState:  onParentAccept,
   169  		timestamp:      parentTime,
   170  	}
   171  	env.blkManager.(*manager).lastAccepted = parentID
   172  	env.mockedState.EXPECT().GetLastAccepted().Return(parentID).AnyTimes()
   173  	env.mockedState.EXPECT().GetStatelessBlock(gomock.Any()).DoAndReturn(
   174  		func(blockID ids.ID) (block.Block, error) {
   175  			if blockID == parentID {
   176  				return banffParentBlk, nil
   177  			}
   178  			return nil, database.ErrNotFound
   179  		}).AnyTimes()
   180  
   181  	// setup state to validate proposal block transaction
   182  	nextStakerTime := chainTime.Add(executor.SyncBound).Add(-1 * time.Second)
   183  	unsignedNextStakerTx := &txs.AddValidatorTx{
   184  		BaseTx:    txs.BaseTx{},
   185  		Validator: txs.Validator{End: uint64(nextStakerTime.Unix())},
   186  		StakeOuts: []*avax.TransferableOutput{
   187  			{
   188  				Asset: avax.Asset{
   189  					ID: env.ctx.AVAXAssetID,
   190  				},
   191  				Out: &secp256k1fx.TransferOutput{
   192  					Amt: 1,
   193  				},
   194  			},
   195  		},
   196  		RewardsOwner:     &secp256k1fx.OutputOwners{},
   197  		DelegationShares: uint32(defaultTxFee),
   198  	}
   199  	nextStakerTx := &txs.Tx{Unsigned: unsignedNextStakerTx}
   200  	require.NoError(nextStakerTx.Initialize(txs.Codec))
   201  
   202  	nextStakerTxID := nextStakerTx.ID()
   203  	onParentAccept.EXPECT().GetTx(nextStakerTxID).Return(nextStakerTx, status.Processing, nil)
   204  
   205  	currentStakersIt := iteratormock.NewIterator[*state.Staker](ctrl)
   206  	currentStakersIt.EXPECT().Next().Return(true).AnyTimes()
   207  	currentStakersIt.EXPECT().Value().Return(&state.Staker{
   208  		TxID:     nextStakerTxID,
   209  		EndTime:  nextStakerTime,
   210  		NextTime: nextStakerTime,
   211  		Priority: txs.PrimaryNetworkValidatorCurrentPriority,
   212  	}).AnyTimes()
   213  	currentStakersIt.EXPECT().Release().AnyTimes()
   214  	onParentAccept.EXPECT().GetCurrentStakerIterator().Return(currentStakersIt, nil).AnyTimes()
   215  
   216  	onParentAccept.EXPECT().GetDelegateeReward(constants.PrimaryNetworkID, unsignedNextStakerTx.NodeID()).Return(uint64(0), nil).AnyTimes()
   217  
   218  	pendingStakersIt := iteratormock.NewIterator[*state.Staker](ctrl)
   219  	pendingStakersIt.EXPECT().Next().Return(false).AnyTimes() // no pending stakers
   220  	pendingStakersIt.EXPECT().Release().AnyTimes()
   221  	onParentAccept.EXPECT().GetPendingStakerIterator().Return(pendingStakersIt, nil).AnyTimes()
   222  
   223  	env.mockedState.EXPECT().GetUptime(gomock.Any(), gomock.Any()).Return(
   224  		time.Microsecond, /*upDuration*/
   225  		time.Time{},      /*lastUpdated*/
   226  		nil,              /*err*/
   227  	).AnyTimes()
   228  
   229  	// create proposal tx to be included in the proposal block
   230  	blkTx := &txs.Tx{
   231  		Unsigned: &txs.RewardValidatorTx{
   232  			TxID: nextStakerTxID,
   233  		},
   234  	}
   235  	require.NoError(blkTx.Initialize(txs.Codec))
   236  
   237  	{
   238  		// wrong height
   239  		statelessProposalBlock, err := block.NewBanffProposalBlock(
   240  			parentTime.Add(time.Second),
   241  			parentID,
   242  			banffParentBlk.Height(),
   243  			blkTx,
   244  			[]*txs.Tx{},
   245  		)
   246  		require.NoError(err)
   247  
   248  		block := env.blkManager.NewBlock(statelessProposalBlock)
   249  		err = block.Verify(context.Background())
   250  		require.ErrorIs(err, errIncorrectBlockHeight)
   251  	}
   252  
   253  	{
   254  		// wrong block version
   255  		statelessProposalBlock, err := block.NewApricotProposalBlock(
   256  			parentID,
   257  			banffParentBlk.Height()+1,
   258  			blkTx,
   259  		)
   260  		require.NoError(err)
   261  
   262  		block := env.blkManager.NewBlock(statelessProposalBlock)
   263  		err = block.Verify(context.Background())
   264  		require.ErrorIs(err, errApricotBlockIssuedAfterFork)
   265  	}
   266  
   267  	{
   268  		// wrong timestamp, earlier than parent
   269  		statelessProposalBlock, err := block.NewBanffProposalBlock(
   270  			parentTime.Add(-1*time.Second),
   271  			parentID,
   272  			banffParentBlk.Height()+1,
   273  			blkTx,
   274  			[]*txs.Tx{},
   275  		)
   276  		require.NoError(err)
   277  
   278  		block := env.blkManager.NewBlock(statelessProposalBlock)
   279  		err = block.Verify(context.Background())
   280  		require.ErrorIs(err, errChildBlockEarlierThanParent)
   281  	}
   282  
   283  	{
   284  		// wrong timestamp, violated synchrony bound
   285  		initClkTime := env.clk.Time()
   286  		env.clk.Set(parentTime.Add(-executor.SyncBound))
   287  		statelessProposalBlock, err := block.NewBanffProposalBlock(
   288  			parentTime.Add(time.Second),
   289  			parentID,
   290  			banffParentBlk.Height()+1,
   291  			blkTx,
   292  			[]*txs.Tx{},
   293  		)
   294  		require.NoError(err)
   295  
   296  		block := env.blkManager.NewBlock(statelessProposalBlock)
   297  		err = block.Verify(context.Background())
   298  		require.ErrorIs(err, executor.ErrChildBlockBeyondSyncBound)
   299  		env.clk.Set(initClkTime)
   300  	}
   301  
   302  	{
   303  		// wrong timestamp, skipped staker set change event
   304  		skippedStakerEventTimeStamp := nextStakerTime.Add(time.Second)
   305  		statelessProposalBlock, err := block.NewBanffProposalBlock(
   306  			skippedStakerEventTimeStamp,
   307  			parentID,
   308  			banffParentBlk.Height()+1,
   309  			blkTx,
   310  			[]*txs.Tx{},
   311  		)
   312  		require.NoError(err)
   313  
   314  		block := env.blkManager.NewBlock(statelessProposalBlock)
   315  		err = block.Verify(context.Background())
   316  		require.ErrorIs(err, executor.ErrChildBlockAfterStakerChangeTime)
   317  	}
   318  
   319  	{
   320  		// wrong tx content (no advance time txs)
   321  		invalidTx := &txs.Tx{
   322  			Unsigned: &txs.AdvanceTimeTx{
   323  				Time: uint64(nextStakerTime.Unix()),
   324  			},
   325  		}
   326  		require.NoError(invalidTx.Initialize(txs.Codec))
   327  		statelessProposalBlock, err := block.NewBanffProposalBlock(
   328  			parentTime.Add(time.Second),
   329  			parentID,
   330  			banffParentBlk.Height()+1,
   331  			invalidTx,
   332  			[]*txs.Tx{},
   333  		)
   334  		require.NoError(err)
   335  
   336  		block := env.blkManager.NewBlock(statelessProposalBlock)
   337  		err = block.Verify(context.Background())
   338  		require.ErrorIs(err, executor.ErrAdvanceTimeTxIssuedAfterBanff)
   339  	}
   340  
   341  	{
   342  		// valid
   343  		statelessProposalBlock, err := block.NewBanffProposalBlock(
   344  			nextStakerTime,
   345  			parentID,
   346  			banffParentBlk.Height()+1,
   347  			blkTx,
   348  			[]*txs.Tx{},
   349  		)
   350  		require.NoError(err)
   351  
   352  		block := env.blkManager.NewBlock(statelessProposalBlock)
   353  		require.NoError(block.Verify(context.Background()))
   354  	}
   355  }
   356  
   357  func TestBanffProposalBlockUpdateStakers(t *testing.T) {
   358  	// Chronological order (not in scale):
   359  	// Staker0:    |--- ??? // Staker0 end time depends on the test
   360  	// Staker1:        |------------------------------------------------------|
   361  	// Staker2:            |------------------------|
   362  	// Staker3:                |------------------------|
   363  	// Staker3sub:                 |----------------|
   364  	// Staker4:                |------------------------|
   365  	// Staker5:                                     |--------------------|
   366  
   367  	// Staker0 it's here just to allow to issue a proposal block with the chosen endTime.
   368  
   369  	// In this test multiple stakers may join and leave the staker set at the same time.
   370  	// The order in which they do it is asserted; the order may depend on the staker.TxID,
   371  	// which in turns depend on every feature of the transaction creating the staker.
   372  	// So in this test we avoid ids.GenerateTestNodeID, in favour of ids.BuildTestNodeID
   373  	// so that TxID does not depend on the order we run tests. We also explicitly declare
   374  	// the change address, to avoid picking a random one in case multiple funding keys are set.
   375  	staker0 := staker{
   376  		nodeID:        ids.BuildTestNodeID([]byte{0xf0}),
   377  		rewardAddress: ids.ShortID{0xf0},
   378  		startTime:     genesistest.DefaultValidatorStartTime,
   379  		endTime:       time.Time{}, // actual endTime depends on specific test
   380  	}
   381  
   382  	staker1 := staker{
   383  		nodeID:        ids.BuildTestNodeID([]byte{0xf1}),
   384  		rewardAddress: ids.ShortID{0xf1},
   385  		startTime:     genesistest.DefaultValidatorStartTime.Add(1 * time.Minute),
   386  		endTime:       genesistest.DefaultValidatorStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute),
   387  	}
   388  	staker2 := staker{
   389  		nodeID:        ids.BuildTestNodeID([]byte{0xf2}),
   390  		rewardAddress: ids.ShortID{0xf2},
   391  		startTime:     staker1.startTime.Add(1 * time.Minute),
   392  		endTime:       staker1.startTime.Add(1 * time.Minute).Add(defaultMinStakingDuration),
   393  	}
   394  	staker3 := staker{
   395  		nodeID:        ids.BuildTestNodeID([]byte{0xf3}),
   396  		rewardAddress: ids.ShortID{0xf3},
   397  		startTime:     staker2.startTime.Add(1 * time.Minute),
   398  		endTime:       staker2.endTime.Add(1 * time.Minute),
   399  	}
   400  	staker3Sub := staker{
   401  		nodeID:        ids.BuildTestNodeID([]byte{0xf3}),
   402  		rewardAddress: ids.ShortID{0xff},
   403  		startTime:     staker3.startTime.Add(1 * time.Minute),
   404  		endTime:       staker3.endTime.Add(-1 * time.Minute),
   405  	}
   406  	staker4 := staker{
   407  		nodeID:        ids.BuildTestNodeID([]byte{0xf4}),
   408  		rewardAddress: ids.ShortID{0xf4},
   409  		startTime:     staker3.startTime,
   410  		endTime:       staker3.endTime,
   411  	}
   412  	staker5 := staker{
   413  		nodeID:        ids.BuildTestNodeID([]byte{0xf5}),
   414  		rewardAddress: ids.ShortID{0xf5},
   415  		startTime:     staker2.endTime,
   416  		endTime:       staker2.endTime.Add(defaultMinStakingDuration),
   417  	}
   418  
   419  	tests := []test{
   420  		{
   421  			description:   "advance time to before staker1 start with subnet",
   422  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   423  			subnetStakers: []staker{staker1, staker2, staker3, staker4, staker5},
   424  			advanceTimeTo: []time.Time{staker1.startTime.Add(-1 * time.Second)},
   425  			expectedStakers: map[ids.NodeID]stakerStatus{
   426  				staker1.nodeID: pending,
   427  				staker2.nodeID: pending,
   428  				staker3.nodeID: pending,
   429  				staker4.nodeID: pending,
   430  				staker5.nodeID: pending,
   431  			},
   432  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   433  				staker1.nodeID: pending,
   434  				staker2.nodeID: pending,
   435  				staker3.nodeID: pending,
   436  				staker4.nodeID: pending,
   437  				staker5.nodeID: pending,
   438  			},
   439  		},
   440  		{
   441  			description:   "advance time to staker 1 start with subnet",
   442  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   443  			subnetStakers: []staker{staker1},
   444  			advanceTimeTo: []time.Time{staker1.startTime},
   445  			expectedStakers: map[ids.NodeID]stakerStatus{
   446  				staker1.nodeID: current,
   447  				staker2.nodeID: pending,
   448  				staker3.nodeID: pending,
   449  				staker4.nodeID: pending,
   450  				staker5.nodeID: pending,
   451  			},
   452  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   453  				staker1.nodeID: current,
   454  				staker2.nodeID: pending,
   455  				staker3.nodeID: pending,
   456  				staker4.nodeID: pending,
   457  				staker5.nodeID: pending,
   458  			},
   459  		},
   460  		{
   461  			description:   "advance time to the staker2 start",
   462  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   463  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime},
   464  			expectedStakers: map[ids.NodeID]stakerStatus{
   465  				staker1.nodeID: current,
   466  				staker2.nodeID: current,
   467  				staker3.nodeID: pending,
   468  				staker4.nodeID: pending,
   469  				staker5.nodeID: pending,
   470  			},
   471  		},
   472  		{
   473  			description:   "staker3 should validate only primary network",
   474  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   475  			subnetStakers: []staker{staker1, staker2, staker3Sub, staker4, staker5},
   476  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime},
   477  			expectedStakers: map[ids.NodeID]stakerStatus{
   478  				staker1.nodeID: current,
   479  				staker2.nodeID: current,
   480  				staker3.nodeID: current,
   481  				staker4.nodeID: current,
   482  				staker5.nodeID: pending,
   483  			},
   484  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   485  				staker1.nodeID:    current,
   486  				staker2.nodeID:    current,
   487  				staker3Sub.nodeID: pending,
   488  				staker4.nodeID:    current,
   489  				staker5.nodeID:    pending,
   490  			},
   491  		},
   492  		{
   493  			description:   "advance time to staker3 start with subnet",
   494  			stakers:       []staker{staker1, staker2, staker3, staker4, staker5},
   495  			subnetStakers: []staker{staker1, staker2, staker3Sub, staker4, staker5},
   496  			advanceTimeTo: []time.Time{staker1.startTime, staker2.startTime, staker3.startTime, staker3Sub.startTime},
   497  			expectedStakers: map[ids.NodeID]stakerStatus{
   498  				staker1.nodeID: current,
   499  				staker2.nodeID: current,
   500  				staker3.nodeID: current,
   501  				staker4.nodeID: current,
   502  				staker5.nodeID: pending,
   503  			},
   504  			expectedSubnetStakers: map[ids.NodeID]stakerStatus{
   505  				staker1.nodeID: current,
   506  				staker2.nodeID: current,
   507  				staker3.nodeID: current,
   508  				staker4.nodeID: current,
   509  				staker5.nodeID: pending,
   510  			},
   511  		},
   512  	}
   513  
   514  	for _, test := range tests {
   515  		t.Run(test.description, func(t *testing.T) {
   516  			require := require.New(t)
   517  			env := newEnvironment(t, nil, upgradetest.Banff)
   518  
   519  			subnetID := testSubnet1.ID()
   520  			env.config.TrackedSubnets.Add(subnetID)
   521  
   522  			for _, staker := range test.stakers {
   523  				wallet := newWallet(t, env, walletConfig{})
   524  
   525  				tx, err := wallet.IssueAddValidatorTx(
   526  					&txs.Validator{
   527  						NodeID: staker.nodeID,
   528  						Start:  uint64(staker.startTime.Unix()),
   529  						End:    uint64(staker.endTime.Unix()),
   530  						Wght:   env.config.MinValidatorStake,
   531  					},
   532  					&secp256k1fx.OutputOwners{
   533  						Threshold: 1,
   534  						Addrs:     []ids.ShortID{staker.rewardAddress},
   535  					},
   536  					reward.PercentDenominator,
   537  				)
   538  				require.NoError(err)
   539  
   540  				staker, err := state.NewPendingStaker(
   541  					tx.ID(),
   542  					tx.Unsigned.(*txs.AddValidatorTx),
   543  				)
   544  				require.NoError(err)
   545  
   546  				require.NoError(env.state.PutPendingValidator(staker))
   547  				env.state.AddTx(tx, status.Committed)
   548  				require.NoError(env.state.Commit())
   549  			}
   550  
   551  			for _, subStaker := range test.subnetStakers {
   552  				wallet := newWallet(t, env, walletConfig{
   553  					subnetIDs: []ids.ID{subnetID},
   554  				})
   555  
   556  				tx, err := wallet.IssueAddSubnetValidatorTx(
   557  					&txs.SubnetValidator{
   558  						Validator: txs.Validator{
   559  							NodeID: subStaker.nodeID,
   560  							Start:  uint64(subStaker.startTime.Unix()),
   561  							End:    uint64(subStaker.endTime.Unix()),
   562  							Wght:   10,
   563  						},
   564  						Subnet: subnetID,
   565  					},
   566  				)
   567  				require.NoError(err)
   568  
   569  				subnetStaker, err := state.NewPendingStaker(
   570  					tx.ID(),
   571  					tx.Unsigned.(*txs.AddSubnetValidatorTx),
   572  				)
   573  				require.NoError(err)
   574  
   575  				require.NoError(env.state.PutPendingValidator(subnetStaker))
   576  				env.state.AddTx(tx, status.Committed)
   577  				require.NoError(env.state.Commit())
   578  			}
   579  
   580  			for _, newTime := range test.advanceTimeTo {
   581  				env.clk.Set(newTime)
   582  
   583  				// add Staker0 (with the right end time) to state
   584  				// so to allow proposalBlk issuance
   585  				staker0.endTime = newTime
   586  
   587  				wallet := newWallet(t, env, walletConfig{})
   588  
   589  				addStaker0, err := wallet.IssueAddValidatorTx(
   590  					&txs.Validator{
   591  						NodeID: staker0.nodeID,
   592  						Start:  uint64(staker0.startTime.Unix()),
   593  						End:    uint64(staker0.endTime.Unix()),
   594  						Wght:   10,
   595  					},
   596  					&secp256k1fx.OutputOwners{
   597  						Threshold: 1,
   598  						Addrs:     []ids.ShortID{staker0.rewardAddress},
   599  					},
   600  					reward.PercentDenominator,
   601  				)
   602  				require.NoError(err)
   603  
   604  				// store Staker0 to state
   605  				addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
   606  				staker0, err := state.NewCurrentStaker(
   607  					addStaker0.ID(),
   608  					addValTx,
   609  					addValTx.StartTime(),
   610  					0,
   611  				)
   612  				require.NoError(err)
   613  
   614  				require.NoError(env.state.PutCurrentValidator(staker0))
   615  				env.state.AddTx(addStaker0, status.Committed)
   616  				require.NoError(env.state.Commit())
   617  
   618  				s0RewardTx := &txs.Tx{
   619  					Unsigned: &txs.RewardValidatorTx{
   620  						TxID: staker0.TxID,
   621  					},
   622  				}
   623  				require.NoError(s0RewardTx.Initialize(txs.Codec))
   624  
   625  				// build proposal block moving ahead chain time
   626  				// as well as rewarding staker0
   627  				preferredID := env.state.GetLastAccepted()
   628  				parentBlk, err := env.state.GetStatelessBlock(preferredID)
   629  				require.NoError(err)
   630  				statelessProposalBlock, err := block.NewBanffProposalBlock(
   631  					newTime,
   632  					parentBlk.ID(),
   633  					parentBlk.Height()+1,
   634  					s0RewardTx,
   635  					[]*txs.Tx{},
   636  				)
   637  				require.NoError(err)
   638  
   639  				// verify and accept the block
   640  				block := env.blkManager.NewBlock(statelessProposalBlock)
   641  				require.NoError(block.Verify(context.Background()))
   642  				options, err := block.(snowman.OracleBlock).Options(context.Background())
   643  				require.NoError(err)
   644  
   645  				require.NoError(options[0].Verify(context.Background()))
   646  
   647  				require.NoError(block.Accept(context.Background()))
   648  				require.NoError(options[0].Accept(context.Background()))
   649  			}
   650  			require.NoError(env.state.Commit())
   651  
   652  			for stakerNodeID, status := range test.expectedStakers {
   653  				switch status {
   654  				case pending:
   655  					_, err := env.state.GetPendingValidator(constants.PrimaryNetworkID, stakerNodeID)
   656  					require.NoError(err)
   657  					_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, stakerNodeID)
   658  					require.False(ok)
   659  				case current:
   660  					_, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, stakerNodeID)
   661  					require.NoError(err)
   662  					_, ok := env.config.Validators.GetValidator(constants.PrimaryNetworkID, stakerNodeID)
   663  					require.True(ok)
   664  				}
   665  			}
   666  
   667  			for stakerNodeID, status := range test.expectedSubnetStakers {
   668  				switch status {
   669  				case pending:
   670  					_, ok := env.config.Validators.GetValidator(subnetID, stakerNodeID)
   671  					require.False(ok)
   672  				case current:
   673  					_, ok := env.config.Validators.GetValidator(subnetID, stakerNodeID)
   674  					require.True(ok)
   675  				}
   676  			}
   677  		})
   678  	}
   679  }
   680  
   681  func TestBanffProposalBlockRemoveSubnetValidator(t *testing.T) {
   682  	require := require.New(t)
   683  	env := newEnvironment(t, nil, upgradetest.Banff)
   684  
   685  	subnetID := testSubnet1.ID()
   686  	wallet := newWallet(t, env, walletConfig{
   687  		subnetIDs: []ids.ID{subnetID},
   688  	})
   689  
   690  	env.config.TrackedSubnets.Add(subnetID)
   691  
   692  	// Add a subnet validator to the staker set
   693  	subnetValidatorNodeID := genesistest.DefaultNodeIDs[0]
   694  	subnetVdr1EndTime := genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration)
   695  	tx, err := wallet.IssueAddSubnetValidatorTx(
   696  		&txs.SubnetValidator{
   697  			Validator: txs.Validator{
   698  				NodeID: subnetValidatorNodeID,
   699  				Start:  genesistest.DefaultValidatorStartTimeUnix,
   700  				End:    uint64(subnetVdr1EndTime.Unix()),
   701  				Wght:   1,
   702  			},
   703  			Subnet: subnetID,
   704  		},
   705  	)
   706  	require.NoError(err)
   707  
   708  	addSubnetValTx := tx.Unsigned.(*txs.AddSubnetValidatorTx)
   709  	staker, err := state.NewCurrentStaker(
   710  		tx.ID(),
   711  		addSubnetValTx,
   712  		addSubnetValTx.StartTime(),
   713  		0,
   714  	)
   715  	require.NoError(err)
   716  
   717  	require.NoError(env.state.PutCurrentValidator(staker))
   718  	env.state.AddTx(tx, status.Committed)
   719  	require.NoError(env.state.Commit())
   720  
   721  	// The above validator is now part of the staking set
   722  
   723  	// Queue a staker that joins the staker set after the above validator leaves
   724  	subnetVdr2NodeID := genesistest.DefaultNodeIDs[1]
   725  	tx, err = wallet.IssueAddSubnetValidatorTx(
   726  		&txs.SubnetValidator{
   727  			Validator: txs.Validator{
   728  				NodeID: subnetVdr2NodeID,
   729  				Start:  uint64(subnetVdr1EndTime.Add(time.Second).Unix()),
   730  				End:    uint64(subnetVdr1EndTime.Add(time.Second).Add(defaultMinStakingDuration).Unix()),
   731  				Wght:   1,
   732  			},
   733  			Subnet: subnetID,
   734  		},
   735  	)
   736  	require.NoError(err)
   737  
   738  	staker, err = state.NewPendingStaker(
   739  		tx.ID(),
   740  		tx.Unsigned.(*txs.AddSubnetValidatorTx),
   741  	)
   742  	require.NoError(err)
   743  
   744  	require.NoError(env.state.PutPendingValidator(staker))
   745  	env.state.AddTx(tx, status.Committed)
   746  	require.NoError(env.state.Commit())
   747  
   748  	// The above validator is now in the pending staker set
   749  
   750  	// Advance time to the first staker's end time.
   751  	env.clk.Set(subnetVdr1EndTime)
   752  
   753  	// add Staker0 (with the right end time) to state
   754  	// so to allow proposalBlk issuance
   755  	staker0EndTime := subnetVdr1EndTime
   756  	addStaker0, err := wallet.IssueAddValidatorTx(
   757  		&txs.Validator{
   758  			NodeID: ids.GenerateTestNodeID(),
   759  			Start:  genesistest.DefaultValidatorStartTimeUnix,
   760  			End:    uint64(staker0EndTime.Unix()),
   761  			Wght:   10,
   762  		},
   763  		&secp256k1fx.OutputOwners{
   764  			Threshold: 1,
   765  			Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   766  		},
   767  		reward.PercentDenominator,
   768  		walletcommon.WithChangeOwner(&secp256k1fx.OutputOwners{
   769  			Threshold: 1,
   770  			Addrs:     []ids.ShortID{ids.ShortEmpty},
   771  		}),
   772  	)
   773  	require.NoError(err)
   774  
   775  	// store Staker0 to state
   776  	addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
   777  	staker, err = state.NewCurrentStaker(
   778  		addStaker0.ID(),
   779  		addValTx,
   780  		addValTx.StartTime(),
   781  		0,
   782  	)
   783  	require.NoError(err)
   784  
   785  	require.NoError(env.state.PutCurrentValidator(staker))
   786  	env.state.AddTx(addStaker0, status.Committed)
   787  	require.NoError(env.state.Commit())
   788  
   789  	// create rewardTx for staker0
   790  	s0RewardTx := &txs.Tx{
   791  		Unsigned: &txs.RewardValidatorTx{
   792  			TxID: addStaker0.ID(),
   793  		},
   794  	}
   795  	require.NoError(s0RewardTx.Initialize(txs.Codec))
   796  
   797  	// build proposal block moving ahead chain time
   798  	preferredID := env.state.GetLastAccepted()
   799  	parentBlk, err := env.state.GetStatelessBlock(preferredID)
   800  	require.NoError(err)
   801  	statelessProposalBlock, err := block.NewBanffProposalBlock(
   802  		subnetVdr1EndTime,
   803  		parentBlk.ID(),
   804  		parentBlk.Height()+1,
   805  		s0RewardTx,
   806  		[]*txs.Tx{},
   807  	)
   808  	require.NoError(err)
   809  	propBlk := env.blkManager.NewBlock(statelessProposalBlock)
   810  	require.NoError(propBlk.Verify(context.Background())) // verify and update staker set
   811  
   812  	options, err := propBlk.(snowman.OracleBlock).Options(context.Background())
   813  	require.NoError(err)
   814  	commitBlk := options[0]
   815  	require.NoError(commitBlk.Verify(context.Background()))
   816  
   817  	blkStateMap := env.blkManager.(*manager).blkIDToState
   818  	updatedState := blkStateMap[commitBlk.ID()].onAcceptState
   819  	_, err = updatedState.GetCurrentValidator(subnetID, subnetValidatorNodeID)
   820  	require.ErrorIs(err, database.ErrNotFound)
   821  
   822  	// Check VM Validators are removed successfully
   823  	require.NoError(propBlk.Accept(context.Background()))
   824  	require.NoError(commitBlk.Accept(context.Background()))
   825  	_, ok := env.config.Validators.GetValidator(subnetID, subnetVdr2NodeID)
   826  	require.False(ok)
   827  	_, ok = env.config.Validators.GetValidator(subnetID, subnetValidatorNodeID)
   828  	require.False(ok)
   829  }
   830  
   831  func TestBanffProposalBlockTrackedSubnet(t *testing.T) {
   832  	for _, tracked := range []bool{true, false} {
   833  		t.Run(fmt.Sprintf("tracked %t", tracked), func(t *testing.T) {
   834  			require := require.New(t)
   835  			env := newEnvironment(t, nil, upgradetest.Banff)
   836  
   837  			subnetID := testSubnet1.ID()
   838  			if tracked {
   839  				env.config.TrackedSubnets.Add(subnetID)
   840  			}
   841  
   842  			wallet := newWallet(t, env, walletConfig{
   843  				subnetIDs: []ids.ID{subnetID},
   844  			})
   845  
   846  			// Add a subnet validator to the staker set
   847  			subnetValidatorNodeID := genesistest.DefaultNodeIDs[0]
   848  			subnetVdr1StartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Minute)
   849  			subnetVdr1EndTime := genesistest.DefaultValidatorStartTime.Add(10 * defaultMinStakingDuration).Add(1 * time.Minute)
   850  
   851  			tx, err := wallet.IssueAddSubnetValidatorTx(
   852  				&txs.SubnetValidator{
   853  					Validator: txs.Validator{
   854  						NodeID: subnetValidatorNodeID,
   855  						Start:  uint64(subnetVdr1StartTime.Unix()),
   856  						End:    uint64(subnetVdr1EndTime.Unix()),
   857  						Wght:   1,
   858  					},
   859  					Subnet: subnetID,
   860  				},
   861  			)
   862  			require.NoError(err)
   863  
   864  			staker, err := state.NewPendingStaker(
   865  				tx.ID(),
   866  				tx.Unsigned.(*txs.AddSubnetValidatorTx),
   867  			)
   868  			require.NoError(err)
   869  
   870  			require.NoError(env.state.PutPendingValidator(staker))
   871  			env.state.AddTx(tx, status.Committed)
   872  			require.NoError(env.state.Commit())
   873  
   874  			// Advance time to the staker's start time.
   875  			env.clk.Set(subnetVdr1StartTime)
   876  
   877  			// add Staker0 (with the right end time) to state
   878  			// so to allow proposalBlk issuance
   879  			staker0StartTime := genesistest.DefaultValidatorStartTime
   880  			staker0EndTime := subnetVdr1StartTime
   881  
   882  			addStaker0, err := wallet.IssueAddValidatorTx(
   883  				&txs.Validator{
   884  					NodeID: ids.GenerateTestNodeID(),
   885  					Start:  uint64(staker0StartTime.Unix()),
   886  					End:    uint64(staker0EndTime.Unix()),
   887  					Wght:   10,
   888  				},
   889  				&secp256k1fx.OutputOwners{
   890  					Threshold: 1,
   891  					Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   892  				},
   893  				reward.PercentDenominator,
   894  			)
   895  			require.NoError(err)
   896  
   897  			// store Staker0 to state
   898  			addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
   899  			staker, err = state.NewCurrentStaker(
   900  				addStaker0.ID(),
   901  				addValTx,
   902  				addValTx.StartTime(),
   903  				0,
   904  			)
   905  			require.NoError(err)
   906  
   907  			require.NoError(env.state.PutCurrentValidator(staker))
   908  			env.state.AddTx(addStaker0, status.Committed)
   909  			require.NoError(env.state.Commit())
   910  
   911  			// create rewardTx for staker0
   912  			s0RewardTx := &txs.Tx{
   913  				Unsigned: &txs.RewardValidatorTx{
   914  					TxID: addStaker0.ID(),
   915  				},
   916  			}
   917  			require.NoError(s0RewardTx.Initialize(txs.Codec))
   918  
   919  			// build proposal block moving ahead chain time
   920  			preferredID := env.state.GetLastAccepted()
   921  			parentBlk, err := env.state.GetStatelessBlock(preferredID)
   922  			require.NoError(err)
   923  			statelessProposalBlock, err := block.NewBanffProposalBlock(
   924  				subnetVdr1StartTime,
   925  				parentBlk.ID(),
   926  				parentBlk.Height()+1,
   927  				s0RewardTx,
   928  				[]*txs.Tx{},
   929  			)
   930  			require.NoError(err)
   931  			propBlk := env.blkManager.NewBlock(statelessProposalBlock)
   932  			require.NoError(propBlk.Verify(context.Background())) // verify update staker set
   933  			options, err := propBlk.(snowman.OracleBlock).Options(context.Background())
   934  			require.NoError(err)
   935  			commitBlk := options[0]
   936  			require.NoError(commitBlk.Verify(context.Background()))
   937  
   938  			require.NoError(propBlk.Accept(context.Background()))
   939  			require.NoError(commitBlk.Accept(context.Background()))
   940  			_, ok := env.config.Validators.GetValidator(subnetID, subnetValidatorNodeID)
   941  			require.True(ok)
   942  		})
   943  	}
   944  }
   945  
   946  func TestBanffProposalBlockDelegatorStakerWeight(t *testing.T) {
   947  	require := require.New(t)
   948  	env := newEnvironment(t, nil, upgradetest.Banff)
   949  
   950  	// Case: Timestamp is after next validator start time
   951  	// Add a pending validator
   952  	pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
   953  	pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMaxStakingDuration)
   954  	nodeID := ids.GenerateTestNodeID()
   955  	rewardAddress := ids.GenerateTestShortID()
   956  	addPendingValidator(
   957  		t,
   958  		env,
   959  		pendingValidatorStartTime,
   960  		pendingValidatorEndTime,
   961  		nodeID,
   962  		rewardAddress,
   963  		[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
   964  	)
   965  
   966  	wallet := newWallet(t, env, walletConfig{})
   967  
   968  	rewardsOwner := &secp256k1fx.OutputOwners{
   969  		Threshold: 1,
   970  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
   971  	}
   972  
   973  	// add Staker0 (with the right end time) to state
   974  	// just to allow proposalBlk issuance (with a reward Tx)
   975  	staker0StartTime := genesistest.DefaultValidatorStartTime
   976  	staker0EndTime := pendingValidatorStartTime
   977  	addStaker0, err := wallet.IssueAddValidatorTx(
   978  		&txs.Validator{
   979  			NodeID: ids.GenerateTestNodeID(),
   980  			Start:  uint64(staker0StartTime.Unix()),
   981  			End:    uint64(staker0EndTime.Unix()),
   982  			Wght:   10,
   983  		},
   984  		rewardsOwner,
   985  		reward.PercentDenominator,
   986  	)
   987  	require.NoError(err)
   988  
   989  	// store Staker0 to state
   990  	addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
   991  	staker, err := state.NewCurrentStaker(
   992  		addStaker0.ID(),
   993  		addValTx,
   994  		addValTx.StartTime(),
   995  		0,
   996  	)
   997  	require.NoError(err)
   998  
   999  	require.NoError(env.state.PutCurrentValidator(staker))
  1000  	env.state.AddTx(addStaker0, status.Committed)
  1001  	require.NoError(env.state.Commit())
  1002  
  1003  	// create rewardTx for staker0
  1004  	s0RewardTx := &txs.Tx{
  1005  		Unsigned: &txs.RewardValidatorTx{
  1006  			TxID: addStaker0.ID(),
  1007  		},
  1008  	}
  1009  	require.NoError(s0RewardTx.Initialize(txs.Codec))
  1010  
  1011  	// build proposal block moving ahead chain time
  1012  	preferredID := env.state.GetLastAccepted()
  1013  	parentBlk, err := env.state.GetStatelessBlock(preferredID)
  1014  	require.NoError(err)
  1015  	statelessProposalBlock, err := block.NewBanffProposalBlock(
  1016  		pendingValidatorStartTime,
  1017  		parentBlk.ID(),
  1018  		parentBlk.Height()+1,
  1019  		s0RewardTx,
  1020  		[]*txs.Tx{},
  1021  	)
  1022  	require.NoError(err)
  1023  	propBlk := env.blkManager.NewBlock(statelessProposalBlock)
  1024  	require.NoError(propBlk.Verify(context.Background()))
  1025  
  1026  	options, err := propBlk.(snowman.OracleBlock).Options(context.Background())
  1027  	require.NoError(err)
  1028  	commitBlk := options[0]
  1029  	require.NoError(commitBlk.Verify(context.Background()))
  1030  
  1031  	require.NoError(propBlk.Accept(context.Background()))
  1032  	require.NoError(commitBlk.Accept(context.Background()))
  1033  
  1034  	// Test validator weight before delegation
  1035  	vdrWeight := env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
  1036  	require.Equal(env.config.MinValidatorStake, vdrWeight)
  1037  
  1038  	// Add delegator
  1039  	pendingDelegatorStartTime := pendingValidatorStartTime.Add(1 * time.Second)
  1040  	pendingDelegatorEndTime := pendingDelegatorStartTime.Add(1 * time.Second)
  1041  	addDelegatorTx, err := wallet.IssueAddDelegatorTx(
  1042  		&txs.Validator{
  1043  			NodeID: nodeID,
  1044  			Start:  uint64(pendingDelegatorStartTime.Unix()),
  1045  			End:    uint64(pendingDelegatorEndTime.Unix()),
  1046  			Wght:   env.config.MinDelegatorStake,
  1047  		},
  1048  		rewardsOwner,
  1049  	)
  1050  	require.NoError(err)
  1051  
  1052  	staker, err = state.NewPendingStaker(
  1053  		addDelegatorTx.ID(),
  1054  		addDelegatorTx.Unsigned.(*txs.AddDelegatorTx),
  1055  	)
  1056  	require.NoError(err)
  1057  
  1058  	env.state.PutPendingDelegator(staker)
  1059  	env.state.AddTx(addDelegatorTx, status.Committed)
  1060  	env.state.SetHeight( /*dummyHeight*/ uint64(1))
  1061  	require.NoError(env.state.Commit())
  1062  
  1063  	// add Staker0 (with the right end time) to state
  1064  	// so to allow proposalBlk issuance
  1065  	staker0EndTime = pendingDelegatorStartTime
  1066  	addStaker0, err = wallet.IssueAddValidatorTx(
  1067  		&txs.Validator{
  1068  			NodeID: ids.GenerateTestNodeID(),
  1069  			Start:  uint64(staker0StartTime.Unix()),
  1070  			End:    uint64(staker0EndTime.Unix()),
  1071  			Wght:   10,
  1072  		},
  1073  		rewardsOwner,
  1074  		reward.PercentDenominator,
  1075  	)
  1076  	require.NoError(err)
  1077  
  1078  	// store Staker0 to state
  1079  	addValTx = addStaker0.Unsigned.(*txs.AddValidatorTx)
  1080  	staker, err = state.NewCurrentStaker(
  1081  		addStaker0.ID(),
  1082  		addValTx,
  1083  		addValTx.StartTime(),
  1084  		0,
  1085  	)
  1086  	require.NoError(err)
  1087  
  1088  	require.NoError(env.state.PutCurrentValidator(staker))
  1089  	env.state.AddTx(addStaker0, status.Committed)
  1090  	require.NoError(env.state.Commit())
  1091  
  1092  	// create rewardTx for staker0
  1093  	s0RewardTx = &txs.Tx{
  1094  		Unsigned: &txs.RewardValidatorTx{
  1095  			TxID: addStaker0.ID(),
  1096  		},
  1097  	}
  1098  	require.NoError(s0RewardTx.Initialize(txs.Codec))
  1099  
  1100  	// Advance Time
  1101  	preferredID = env.state.GetLastAccepted()
  1102  	parentBlk, err = env.state.GetStatelessBlock(preferredID)
  1103  	require.NoError(err)
  1104  	statelessProposalBlock, err = block.NewBanffProposalBlock(
  1105  		pendingDelegatorStartTime,
  1106  		parentBlk.ID(),
  1107  		parentBlk.Height()+1,
  1108  		s0RewardTx,
  1109  		[]*txs.Tx{},
  1110  	)
  1111  	require.NoError(err)
  1112  
  1113  	propBlk = env.blkManager.NewBlock(statelessProposalBlock)
  1114  	require.NoError(propBlk.Verify(context.Background()))
  1115  
  1116  	options, err = propBlk.(snowman.OracleBlock).Options(context.Background())
  1117  	require.NoError(err)
  1118  	commitBlk = options[0]
  1119  	require.NoError(commitBlk.Verify(context.Background()))
  1120  
  1121  	require.NoError(propBlk.Accept(context.Background()))
  1122  	require.NoError(commitBlk.Accept(context.Background()))
  1123  
  1124  	// Test validator weight after delegation
  1125  	vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
  1126  	require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight)
  1127  }
  1128  
  1129  func TestBanffProposalBlockDelegatorStakers(t *testing.T) {
  1130  	require := require.New(t)
  1131  	env := newEnvironment(t, nil, upgradetest.Banff)
  1132  
  1133  	// Case: Timestamp is after next validator start time
  1134  	// Add a pending validator
  1135  	pendingValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second)
  1136  	pendingValidatorEndTime := pendingValidatorStartTime.Add(defaultMinStakingDuration)
  1137  	nodeIDKey, _ := secp256k1.NewPrivateKey()
  1138  	rewardAddress := nodeIDKey.Address()
  1139  	nodeID := ids.BuildTestNodeID(rewardAddress[:])
  1140  
  1141  	addPendingValidator(
  1142  		t,
  1143  		env,
  1144  		pendingValidatorStartTime,
  1145  		pendingValidatorEndTime,
  1146  		nodeID,
  1147  		rewardAddress,
  1148  		[]*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]},
  1149  	)
  1150  
  1151  	wallet := newWallet(t, env, walletConfig{})
  1152  
  1153  	rewardsOwner := &secp256k1fx.OutputOwners{
  1154  		Threshold: 1,
  1155  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
  1156  	}
  1157  
  1158  	// add Staker0 (with the right end time) to state
  1159  	// so to allow proposalBlk issuance
  1160  	staker0StartTime := genesistest.DefaultValidatorStartTime
  1161  	staker0EndTime := pendingValidatorStartTime
  1162  	addStaker0, err := wallet.IssueAddValidatorTx(
  1163  		&txs.Validator{
  1164  			NodeID: ids.GenerateTestNodeID(),
  1165  			Start:  uint64(staker0StartTime.Unix()),
  1166  			End:    uint64(staker0EndTime.Unix()),
  1167  			Wght:   10,
  1168  		},
  1169  		rewardsOwner,
  1170  		reward.PercentDenominator,
  1171  	)
  1172  	require.NoError(err)
  1173  
  1174  	// store Staker0 to state
  1175  	addValTx := addStaker0.Unsigned.(*txs.AddValidatorTx)
  1176  	staker, err := state.NewCurrentStaker(
  1177  		addStaker0.ID(),
  1178  		addValTx,
  1179  		addValTx.StartTime(),
  1180  		0,
  1181  	)
  1182  	require.NoError(err)
  1183  
  1184  	require.NoError(env.state.PutCurrentValidator(staker))
  1185  	env.state.AddTx(addStaker0, status.Committed)
  1186  	require.NoError(env.state.Commit())
  1187  
  1188  	// create rewardTx for staker0
  1189  	s0RewardTx := &txs.Tx{
  1190  		Unsigned: &txs.RewardValidatorTx{
  1191  			TxID: addStaker0.ID(),
  1192  		},
  1193  	}
  1194  	require.NoError(s0RewardTx.Initialize(txs.Codec))
  1195  
  1196  	// build proposal block moving ahead chain time
  1197  	preferredID := env.state.GetLastAccepted()
  1198  	parentBlk, err := env.state.GetStatelessBlock(preferredID)
  1199  	require.NoError(err)
  1200  	statelessProposalBlock, err := block.NewBanffProposalBlock(
  1201  		pendingValidatorStartTime,
  1202  		parentBlk.ID(),
  1203  		parentBlk.Height()+1,
  1204  		s0RewardTx,
  1205  		[]*txs.Tx{},
  1206  	)
  1207  	require.NoError(err)
  1208  	propBlk := env.blkManager.NewBlock(statelessProposalBlock)
  1209  	require.NoError(propBlk.Verify(context.Background()))
  1210  
  1211  	options, err := propBlk.(snowman.OracleBlock).Options(context.Background())
  1212  	require.NoError(err)
  1213  	commitBlk := options[0]
  1214  	require.NoError(commitBlk.Verify(context.Background()))
  1215  
  1216  	require.NoError(propBlk.Accept(context.Background()))
  1217  	require.NoError(commitBlk.Accept(context.Background()))
  1218  
  1219  	// Test validator weight before delegation
  1220  	vdrWeight := env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
  1221  	require.Equal(env.config.MinValidatorStake, vdrWeight)
  1222  
  1223  	// Add delegator
  1224  	pendingDelegatorStartTime := pendingValidatorStartTime.Add(1 * time.Second)
  1225  	pendingDelegatorEndTime := pendingDelegatorStartTime.Add(defaultMinStakingDuration)
  1226  	addDelegatorTx, err := wallet.IssueAddDelegatorTx(
  1227  		&txs.Validator{
  1228  			NodeID: nodeID,
  1229  			Start:  uint64(pendingDelegatorStartTime.Unix()),
  1230  			End:    uint64(pendingDelegatorEndTime.Unix()),
  1231  			Wght:   env.config.MinDelegatorStake,
  1232  		},
  1233  		rewardsOwner,
  1234  	)
  1235  	require.NoError(err)
  1236  
  1237  	staker, err = state.NewPendingStaker(
  1238  		addDelegatorTx.ID(),
  1239  		addDelegatorTx.Unsigned.(*txs.AddDelegatorTx),
  1240  	)
  1241  	require.NoError(err)
  1242  
  1243  	env.state.PutPendingDelegator(staker)
  1244  	env.state.AddTx(addDelegatorTx, status.Committed)
  1245  	env.state.SetHeight( /*dummyHeight*/ uint64(1))
  1246  	require.NoError(env.state.Commit())
  1247  
  1248  	// add Staker0 (with the right end time) to state
  1249  	// so to allow proposalBlk issuance
  1250  	staker0EndTime = pendingDelegatorStartTime
  1251  	addStaker0, err = wallet.IssueAddValidatorTx(
  1252  		&txs.Validator{
  1253  			NodeID: ids.GenerateTestNodeID(),
  1254  			Start:  uint64(staker0StartTime.Unix()),
  1255  			End:    uint64(staker0EndTime.Unix()),
  1256  			Wght:   10,
  1257  		},
  1258  		rewardsOwner,
  1259  		reward.PercentDenominator,
  1260  	)
  1261  	require.NoError(err)
  1262  
  1263  	// store Staker0 to state
  1264  	addValTx = addStaker0.Unsigned.(*txs.AddValidatorTx)
  1265  	staker, err = state.NewCurrentStaker(
  1266  		addStaker0.ID(),
  1267  		addValTx,
  1268  		addValTx.StartTime(),
  1269  		0,
  1270  	)
  1271  	require.NoError(err)
  1272  
  1273  	require.NoError(env.state.PutCurrentValidator(staker))
  1274  	env.state.AddTx(addStaker0, status.Committed)
  1275  	require.NoError(env.state.Commit())
  1276  
  1277  	// create rewardTx for staker0
  1278  	s0RewardTx = &txs.Tx{
  1279  		Unsigned: &txs.RewardValidatorTx{
  1280  			TxID: addStaker0.ID(),
  1281  		},
  1282  	}
  1283  	require.NoError(s0RewardTx.Initialize(txs.Codec))
  1284  
  1285  	// Advance Time
  1286  	preferredID = env.state.GetLastAccepted()
  1287  	parentBlk, err = env.state.GetStatelessBlock(preferredID)
  1288  	require.NoError(err)
  1289  	statelessProposalBlock, err = block.NewBanffProposalBlock(
  1290  		pendingDelegatorStartTime,
  1291  		parentBlk.ID(),
  1292  		parentBlk.Height()+1,
  1293  		s0RewardTx,
  1294  		[]*txs.Tx{},
  1295  	)
  1296  	require.NoError(err)
  1297  	propBlk = env.blkManager.NewBlock(statelessProposalBlock)
  1298  	require.NoError(propBlk.Verify(context.Background()))
  1299  
  1300  	options, err = propBlk.(snowman.OracleBlock).Options(context.Background())
  1301  	require.NoError(err)
  1302  	commitBlk = options[0]
  1303  	require.NoError(commitBlk.Verify(context.Background()))
  1304  
  1305  	require.NoError(propBlk.Accept(context.Background()))
  1306  	require.NoError(commitBlk.Accept(context.Background()))
  1307  
  1308  	// Test validator weight after delegation
  1309  	vdrWeight = env.config.Validators.GetWeight(constants.PrimaryNetworkID, nodeID)
  1310  	require.Equal(env.config.MinDelegatorStake+env.config.MinValidatorStake, vdrWeight)
  1311  }
  1312  
  1313  func TestAddValidatorProposalBlock(t *testing.T) {
  1314  	require := require.New(t)
  1315  	env := newEnvironment(t, nil, upgradetest.Durango)
  1316  
  1317  	wallet := newWallet(t, env, walletConfig{})
  1318  
  1319  	now := env.clk.Time()
  1320  
  1321  	// Create validator tx
  1322  	var (
  1323  		validatorStartTime = now.Add(2 * executor.SyncBound)
  1324  		validatorEndTime   = validatorStartTime.Add(env.config.MinStakeDuration)
  1325  		nodeID             = ids.GenerateTestNodeID()
  1326  	)
  1327  
  1328  	sk, err := bls.NewSecretKey()
  1329  	require.NoError(err)
  1330  
  1331  	rewardsOwner := &secp256k1fx.OutputOwners{
  1332  		Threshold: 1,
  1333  		Addrs:     []ids.ShortID{ids.GenerateTestShortID()},
  1334  	}
  1335  
  1336  	addValidatorTx, err := wallet.IssueAddPermissionlessValidatorTx(
  1337  		&txs.SubnetValidator{
  1338  			Validator: txs.Validator{
  1339  				NodeID: nodeID,
  1340  				Start:  uint64(validatorStartTime.Unix()),
  1341  				End:    uint64(validatorEndTime.Unix()),
  1342  				Wght:   env.config.MinValidatorStake,
  1343  			},
  1344  			Subnet: constants.PrimaryNetworkID,
  1345  		},
  1346  		signer.NewProofOfPossession(sk),
  1347  		env.ctx.AVAXAssetID,
  1348  		rewardsOwner,
  1349  		rewardsOwner,
  1350  		10000,
  1351  	)
  1352  	require.NoError(err)
  1353  
  1354  	// Add validator through a [StandardBlock]
  1355  	preferredID := env.blkManager.Preferred()
  1356  	preferred, err := env.blkManager.GetStatelessBlock(preferredID)
  1357  	require.NoError(err)
  1358  
  1359  	statelessBlk, err := block.NewBanffStandardBlock(
  1360  		now.Add(executor.SyncBound),
  1361  		preferredID,
  1362  		preferred.Height()+1,
  1363  		[]*txs.Tx{addValidatorTx},
  1364  	)
  1365  	require.NoError(err)
  1366  	blk := env.blkManager.NewBlock(statelessBlk)
  1367  	require.NoError(blk.Verify(context.Background()))
  1368  	require.NoError(blk.Accept(context.Background()))
  1369  	require.True(env.blkManager.SetPreference(statelessBlk.ID()))
  1370  
  1371  	// Should be current
  1372  	staker, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
  1373  	require.NoError(err)
  1374  	require.NotNil(staker)
  1375  
  1376  	// Advance time until next staker change time is [validatorEndTime]
  1377  	for {
  1378  		nextStakerChangeTime, err := state.GetNextStakerChangeTime(env.state)
  1379  		require.NoError(err)
  1380  		if nextStakerChangeTime.Equal(validatorEndTime) {
  1381  			break
  1382  		}
  1383  
  1384  		preferredID = env.blkManager.Preferred()
  1385  		preferred, err = env.blkManager.GetStatelessBlock(preferredID)
  1386  		require.NoError(err)
  1387  
  1388  		statelessBlk, err = block.NewBanffStandardBlock(
  1389  			nextStakerChangeTime,
  1390  			preferredID,
  1391  			preferred.Height()+1,
  1392  			nil,
  1393  		)
  1394  		require.NoError(err)
  1395  		blk = env.blkManager.NewBlock(statelessBlk)
  1396  		require.NoError(blk.Verify(context.Background()))
  1397  		require.NoError(blk.Accept(context.Background()))
  1398  		require.True(env.blkManager.SetPreference(statelessBlk.ID()))
  1399  	}
  1400  
  1401  	env.clk.Set(validatorEndTime)
  1402  	now = env.clk.Time()
  1403  
  1404  	// Create another validator tx
  1405  	validatorStartTime = now.Add(2 * executor.SyncBound)
  1406  	validatorEndTime = validatorStartTime.Add(env.config.MinStakeDuration)
  1407  	nodeID = ids.GenerateTestNodeID()
  1408  
  1409  	sk, err = bls.NewSecretKey()
  1410  	require.NoError(err)
  1411  
  1412  	addValidatorTx2, err := wallet.IssueAddPermissionlessValidatorTx(
  1413  		&txs.SubnetValidator{
  1414  			Validator: txs.Validator{
  1415  				NodeID: nodeID,
  1416  				Start:  uint64(validatorStartTime.Unix()),
  1417  				End:    uint64(validatorEndTime.Unix()),
  1418  				Wght:   env.config.MinValidatorStake,
  1419  			},
  1420  			Subnet: constants.PrimaryNetworkID,
  1421  		},
  1422  		signer.NewProofOfPossession(sk),
  1423  		env.ctx.AVAXAssetID,
  1424  		rewardsOwner,
  1425  		rewardsOwner,
  1426  		10000,
  1427  	)
  1428  	require.NoError(err)
  1429  
  1430  	// Add validator through a [ProposalBlock] and reward the last one
  1431  	preferredID = env.blkManager.Preferred()
  1432  	preferred, err = env.blkManager.GetStatelessBlock(preferredID)
  1433  	require.NoError(err)
  1434  
  1435  	rewardValidatorTx, err := newRewardValidatorTx(t, addValidatorTx.ID())
  1436  	require.NoError(err)
  1437  
  1438  	statelessProposalBlk, err := block.NewBanffProposalBlock(
  1439  		now,
  1440  		preferredID,
  1441  		preferred.Height()+1,
  1442  		rewardValidatorTx,
  1443  		[]*txs.Tx{addValidatorTx2},
  1444  	)
  1445  	require.NoError(err)
  1446  	blk = env.blkManager.NewBlock(statelessProposalBlk)
  1447  	require.NoError(blk.Verify(context.Background()))
  1448  
  1449  	options, err := blk.(snowman.OracleBlock).Options(context.Background())
  1450  	require.NoError(err)
  1451  	commitBlk := options[0]
  1452  	require.NoError(commitBlk.Verify(context.Background()))
  1453  
  1454  	require.NoError(blk.Accept(context.Background()))
  1455  	require.NoError(commitBlk.Accept(context.Background()))
  1456  
  1457  	// Should be current
  1458  	staker, err = env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
  1459  	require.NoError(err)
  1460  	require.NotNil(staker)
  1461  
  1462  	rewardUTXOs, err := env.state.GetRewardUTXOs(addValidatorTx.ID())
  1463  	require.NoError(err)
  1464  	require.NotEmpty(rewardUTXOs)
  1465  }
  1466  
  1467  func newRewardValidatorTx(t testing.TB, txID ids.ID) (*txs.Tx, error) {
  1468  	utx := &txs.RewardValidatorTx{TxID: txID}
  1469  	tx, err := txs.NewSigned(utx, txs.Codec, nil)
  1470  	if err != nil {
  1471  		return nil, err
  1472  	}
  1473  	return tx, tx.SyntacticVerify(snowtest.Context(t, snowtest.PChainID))
  1474  }