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