github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/block/builder/builder_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 builder
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/stretchr/testify/require"
    13  	"go.uber.org/mock/gomock"
    14  
    15  	"github.com/MetalBlockchain/metalgo/ids"
    16  	"github.com/MetalBlockchain/metalgo/snow/consensus/snowman"
    17  	"github.com/MetalBlockchain/metalgo/utils/constants"
    18  	"github.com/MetalBlockchain/metalgo/utils/crypto/bls"
    19  	"github.com/MetalBlockchain/metalgo/utils/timer/mockable"
    20  	"github.com/MetalBlockchain/metalgo/utils/units"
    21  	"github.com/MetalBlockchain/metalgo/vms/platformvm/block"
    22  	"github.com/MetalBlockchain/metalgo/vms/platformvm/reward"
    23  	"github.com/MetalBlockchain/metalgo/vms/platformvm/signer"
    24  	"github.com/MetalBlockchain/metalgo/vms/platformvm/state"
    25  	"github.com/MetalBlockchain/metalgo/vms/platformvm/txs"
    26  	"github.com/MetalBlockchain/metalgo/vms/secp256k1fx"
    27  	"github.com/MetalBlockchain/metalgo/wallet/subnet/primary/common"
    28  
    29  	blockexecutor "github.com/MetalBlockchain/metalgo/vms/platformvm/block/executor"
    30  	txexecutor "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/executor"
    31  	walletsigner "github.com/MetalBlockchain/metalgo/wallet/chain/p/signer"
    32  )
    33  
    34  func TestBuildBlockBasic(t *testing.T) {
    35  	require := require.New(t)
    36  
    37  	env := newEnvironment(t, latestFork)
    38  	env.ctx.Lock.Lock()
    39  	defer env.ctx.Lock.Unlock()
    40  
    41  	// Create a valid transaction
    42  	builder, signer := env.factory.NewWallet(testSubnet1ControlKeys[0], testSubnet1ControlKeys[1])
    43  	utx, err := builder.NewCreateChainTx(
    44  		testSubnet1.ID(),
    45  		nil,
    46  		constants.AVMID,
    47  		nil,
    48  		"chain name",
    49  	)
    50  	require.NoError(err)
    51  	tx, err := walletsigner.SignUnsigned(context.Background(), signer, utx)
    52  	require.NoError(err)
    53  	txID := tx.ID()
    54  
    55  	// Issue the transaction
    56  	env.ctx.Lock.Unlock()
    57  	require.NoError(env.network.IssueTxFromRPC(tx))
    58  	env.ctx.Lock.Lock()
    59  	_, ok := env.mempool.Get(txID)
    60  	require.True(ok)
    61  
    62  	// [BuildBlock] should build a block with the transaction
    63  	blkIntf, err := env.Builder.BuildBlock(context.Background())
    64  	require.NoError(err)
    65  
    66  	require.IsType(&blockexecutor.Block{}, blkIntf)
    67  	blk := blkIntf.(*blockexecutor.Block)
    68  	require.Len(blk.Txs(), 1)
    69  	require.Equal(txID, blk.Txs()[0].ID())
    70  
    71  	// Mempool should not contain the transaction or have marked it as dropped
    72  	_, ok = env.mempool.Get(txID)
    73  	require.False(ok)
    74  	require.NoError(env.mempool.GetDropReason(txID))
    75  }
    76  
    77  func TestBuildBlockDoesNotBuildWithEmptyMempool(t *testing.T) {
    78  	require := require.New(t)
    79  
    80  	env := newEnvironment(t, latestFork)
    81  	env.ctx.Lock.Lock()
    82  	defer env.ctx.Lock.Unlock()
    83  
    84  	tx, exists := env.mempool.Peek()
    85  	require.False(exists)
    86  	require.Nil(tx)
    87  
    88  	// [BuildBlock] should not build an empty block
    89  	blk, err := env.Builder.BuildBlock(context.Background())
    90  	require.ErrorIs(err, ErrNoPendingBlocks)
    91  	require.Nil(blk)
    92  }
    93  
    94  func TestBuildBlockShouldReward(t *testing.T) {
    95  	require := require.New(t)
    96  
    97  	env := newEnvironment(t, latestFork)
    98  	env.ctx.Lock.Lock()
    99  	defer env.ctx.Lock.Unlock()
   100  
   101  	var (
   102  		now    = env.backend.Clk.Time()
   103  		nodeID = ids.GenerateTestNodeID()
   104  
   105  		defaultValidatorStake = 100 * units.MilliAvax
   106  		validatorStartTime    = now.Add(2 * txexecutor.SyncBound)
   107  		validatorEndTime      = validatorStartTime.Add(360 * 24 * time.Hour)
   108  	)
   109  
   110  	sk, err := bls.NewSecretKey()
   111  	require.NoError(err)
   112  
   113  	// Create a valid [AddPermissionlessValidatorTx]
   114  	builder, txSigner := env.factory.NewWallet(preFundedKeys[0])
   115  	utx, err := builder.NewAddPermissionlessValidatorTx(
   116  		&txs.SubnetValidator{
   117  			Validator: txs.Validator{
   118  				NodeID: nodeID,
   119  				Start:  uint64(validatorStartTime.Unix()),
   120  				End:    uint64(validatorEndTime.Unix()),
   121  				Wght:   defaultValidatorStake,
   122  			},
   123  			Subnet: constants.PrimaryNetworkID,
   124  		},
   125  		signer.NewProofOfPossession(sk),
   126  		env.ctx.AVAXAssetID,
   127  		&secp256k1fx.OutputOwners{
   128  			Threshold: 1,
   129  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   130  		},
   131  		&secp256k1fx.OutputOwners{
   132  			Threshold: 1,
   133  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   134  		},
   135  		reward.PercentDenominator,
   136  		common.WithChangeOwner(&secp256k1fx.OutputOwners{
   137  			Threshold: 1,
   138  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   139  		}),
   140  	)
   141  	require.NoError(err)
   142  	tx, err := walletsigner.SignUnsigned(context.Background(), txSigner, utx)
   143  	require.NoError(err)
   144  	txID := tx.ID()
   145  
   146  	// Issue the transaction
   147  	env.ctx.Lock.Unlock()
   148  	require.NoError(env.network.IssueTxFromRPC(tx))
   149  	env.ctx.Lock.Lock()
   150  	_, ok := env.mempool.Get(txID)
   151  	require.True(ok)
   152  
   153  	// Build and accept a block with the tx
   154  	blk, err := env.Builder.BuildBlock(context.Background())
   155  	require.NoError(err)
   156  	require.IsType(&block.BanffStandardBlock{}, blk.(*blockexecutor.Block).Block)
   157  	require.Equal([]*txs.Tx{tx}, blk.(*blockexecutor.Block).Block.Txs())
   158  	require.NoError(blk.Verify(context.Background()))
   159  	require.NoError(blk.Accept(context.Background()))
   160  	require.True(env.blkManager.SetPreference(blk.ID()))
   161  
   162  	// Validator should now be current
   163  	staker, err := env.state.GetCurrentValidator(constants.PrimaryNetworkID, nodeID)
   164  	require.NoError(err)
   165  	require.Equal(txID, staker.TxID)
   166  
   167  	// Should be rewarded at the end of staking period
   168  	env.backend.Clk.Set(validatorEndTime)
   169  
   170  	for {
   171  		iter, err := env.state.GetCurrentStakerIterator()
   172  		require.NoError(err)
   173  		require.True(iter.Next())
   174  		staker := iter.Value()
   175  		iter.Release()
   176  
   177  		// Check that the right block was built
   178  		blk, err := env.Builder.BuildBlock(context.Background())
   179  		require.NoError(err)
   180  		require.NoError(blk.Verify(context.Background()))
   181  		require.IsType(&block.BanffProposalBlock{}, blk.(*blockexecutor.Block).Block)
   182  
   183  		expectedTx, err := NewRewardValidatorTx(env.ctx, staker.TxID)
   184  		require.NoError(err)
   185  		require.Equal([]*txs.Tx{expectedTx}, blk.(*blockexecutor.Block).Block.Txs())
   186  
   187  		// Commit the [ProposalBlock] with a [CommitBlock]
   188  		proposalBlk, ok := blk.(snowman.OracleBlock)
   189  		require.True(ok)
   190  		options, err := proposalBlk.Options(context.Background())
   191  		require.NoError(err)
   192  
   193  		commit := options[0].(*blockexecutor.Block)
   194  		require.IsType(&block.BanffCommitBlock{}, commit.Block)
   195  
   196  		require.NoError(blk.Accept(context.Background()))
   197  		require.NoError(commit.Verify(context.Background()))
   198  		require.NoError(commit.Accept(context.Background()))
   199  		require.True(env.blkManager.SetPreference(commit.ID()))
   200  
   201  		// Stop rewarding once our staker is rewarded
   202  		if staker.TxID == txID {
   203  			break
   204  		}
   205  	}
   206  
   207  	// Staking rewards should have been issued
   208  	rewardUTXOs, err := env.state.GetRewardUTXOs(txID)
   209  	require.NoError(err)
   210  	require.NotEmpty(rewardUTXOs)
   211  }
   212  
   213  func TestBuildBlockAdvanceTime(t *testing.T) {
   214  	require := require.New(t)
   215  
   216  	env := newEnvironment(t, latestFork)
   217  	env.ctx.Lock.Lock()
   218  	defer env.ctx.Lock.Unlock()
   219  
   220  	var (
   221  		now      = env.backend.Clk.Time()
   222  		nextTime = now.Add(2 * txexecutor.SyncBound)
   223  	)
   224  
   225  	// Add a staker to [env.state]
   226  	env.state.PutCurrentValidator(&state.Staker{
   227  		NextTime: nextTime,
   228  		Priority: txs.PrimaryNetworkValidatorCurrentPriority,
   229  	})
   230  
   231  	// Advance wall clock to [nextTime]
   232  	env.backend.Clk.Set(nextTime)
   233  
   234  	// [BuildBlock] should build a block advancing the time to [NextTime]
   235  	blkIntf, err := env.Builder.BuildBlock(context.Background())
   236  	require.NoError(err)
   237  
   238  	require.IsType(&blockexecutor.Block{}, blkIntf)
   239  	blk := blkIntf.(*blockexecutor.Block)
   240  	require.Empty(blk.Txs())
   241  	require.IsType(&block.BanffStandardBlock{}, blk.Block)
   242  	standardBlk := blk.Block.(*block.BanffStandardBlock)
   243  	require.Equal(nextTime.Unix(), standardBlk.Timestamp().Unix())
   244  }
   245  
   246  func TestBuildBlockForceAdvanceTime(t *testing.T) {
   247  	require := require.New(t)
   248  
   249  	env := newEnvironment(t, latestFork)
   250  	env.ctx.Lock.Lock()
   251  	defer env.ctx.Lock.Unlock()
   252  
   253  	// Create a valid transaction
   254  	builder, signer := env.factory.NewWallet(testSubnet1ControlKeys[0], testSubnet1ControlKeys[1])
   255  	utx, err := builder.NewCreateChainTx(
   256  		testSubnet1.ID(),
   257  		nil,
   258  		constants.AVMID,
   259  		nil,
   260  		"chain name",
   261  	)
   262  	require.NoError(err)
   263  	tx, err := walletsigner.SignUnsigned(context.Background(), signer, utx)
   264  	require.NoError(err)
   265  	txID := tx.ID()
   266  
   267  	// Issue the transaction
   268  	env.ctx.Lock.Unlock()
   269  	require.NoError(env.network.IssueTxFromRPC(tx))
   270  	env.ctx.Lock.Lock()
   271  	_, ok := env.mempool.Get(txID)
   272  	require.True(ok)
   273  
   274  	var (
   275  		now      = env.backend.Clk.Time()
   276  		nextTime = now.Add(2 * txexecutor.SyncBound)
   277  	)
   278  
   279  	// Add a staker to [env.state]
   280  	env.state.PutCurrentValidator(&state.Staker{
   281  		NextTime: nextTime,
   282  		Priority: txs.PrimaryNetworkValidatorCurrentPriority,
   283  	})
   284  
   285  	// Advance wall clock to [nextTime] + [txexecutor.SyncBound]
   286  	env.backend.Clk.Set(nextTime.Add(txexecutor.SyncBound))
   287  
   288  	// [BuildBlock] should build a block advancing the time to [nextTime],
   289  	// not the current wall clock.
   290  	blkIntf, err := env.Builder.BuildBlock(context.Background())
   291  	require.NoError(err)
   292  
   293  	require.IsType(&blockexecutor.Block{}, blkIntf)
   294  	blk := blkIntf.(*blockexecutor.Block)
   295  	require.Equal([]*txs.Tx{tx}, blk.Txs())
   296  	require.IsType(&block.BanffStandardBlock{}, blk.Block)
   297  	standardBlk := blk.Block.(*block.BanffStandardBlock)
   298  	require.Equal(nextTime.Unix(), standardBlk.Timestamp().Unix())
   299  }
   300  
   301  func TestBuildBlockInvalidStakingDurations(t *testing.T) {
   302  	require := require.New(t)
   303  
   304  	env := newEnvironment(t, latestFork)
   305  	env.ctx.Lock.Lock()
   306  	defer env.ctx.Lock.Unlock()
   307  
   308  	// Post-Durango, [StartTime] is no longer validated. Staking durations are
   309  	// based on the current chain timestamp and must be validated.
   310  	env.config.UpgradeConfig.DurangoTime = time.Time{}
   311  
   312  	var (
   313  		now                   = env.backend.Clk.Time()
   314  		defaultValidatorStake = 100 * units.MilliAvax
   315  
   316  		// Add a validator ending in [MaxStakeDuration]
   317  		validatorEndTime = now.Add(env.config.MaxStakeDuration)
   318  	)
   319  
   320  	sk, err := bls.NewSecretKey()
   321  	require.NoError(err)
   322  
   323  	builder1, signer1 := env.factory.NewWallet(preFundedKeys[0])
   324  	utx1, err := builder1.NewAddPermissionlessValidatorTx(
   325  		&txs.SubnetValidator{
   326  			Validator: txs.Validator{
   327  				NodeID: ids.GenerateTestNodeID(),
   328  				Start:  uint64(now.Unix()),
   329  				End:    uint64(validatorEndTime.Unix()),
   330  				Wght:   defaultValidatorStake,
   331  			},
   332  			Subnet: constants.PrimaryNetworkID,
   333  		},
   334  		signer.NewProofOfPossession(sk),
   335  		env.ctx.AVAXAssetID,
   336  		&secp256k1fx.OutputOwners{
   337  			Threshold: 1,
   338  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   339  		},
   340  		&secp256k1fx.OutputOwners{
   341  			Threshold: 1,
   342  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   343  		},
   344  		reward.PercentDenominator,
   345  		common.WithChangeOwner(&secp256k1fx.OutputOwners{
   346  			Threshold: 1,
   347  			Addrs:     []ids.ShortID{preFundedKeys[0].PublicKey().Address()},
   348  		}),
   349  	)
   350  	require.NoError(err)
   351  	tx1, err := walletsigner.SignUnsigned(context.Background(), signer1, utx1)
   352  	require.NoError(err)
   353  	require.NoError(env.mempool.Add(tx1))
   354  	tx1ID := tx1.ID()
   355  	_, ok := env.mempool.Get(tx1ID)
   356  	require.True(ok)
   357  
   358  	// Add a validator ending past [MaxStakeDuration]
   359  	validator2EndTime := now.Add(env.config.MaxStakeDuration + time.Second)
   360  
   361  	sk, err = bls.NewSecretKey()
   362  	require.NoError(err)
   363  
   364  	builder2, signer2 := env.factory.NewWallet(preFundedKeys[2])
   365  	utx2, err := builder2.NewAddPermissionlessValidatorTx(
   366  		&txs.SubnetValidator{
   367  			Validator: txs.Validator{
   368  				NodeID: ids.GenerateTestNodeID(),
   369  				Start:  uint64(now.Unix()),
   370  				End:    uint64(validator2EndTime.Unix()),
   371  				Wght:   defaultValidatorStake,
   372  			},
   373  			Subnet: constants.PrimaryNetworkID,
   374  		},
   375  		signer.NewProofOfPossession(sk),
   376  		env.ctx.AVAXAssetID,
   377  		&secp256k1fx.OutputOwners{
   378  			Threshold: 1,
   379  			Addrs:     []ids.ShortID{preFundedKeys[2].PublicKey().Address()},
   380  		},
   381  		&secp256k1fx.OutputOwners{
   382  			Threshold: 1,
   383  			Addrs:     []ids.ShortID{preFundedKeys[2].PublicKey().Address()},
   384  		},
   385  		reward.PercentDenominator,
   386  		common.WithChangeOwner(&secp256k1fx.OutputOwners{
   387  			Threshold: 1,
   388  			Addrs:     []ids.ShortID{preFundedKeys[2].PublicKey().Address()},
   389  		}),
   390  	)
   391  	require.NoError(err)
   392  	tx2, err := walletsigner.SignUnsigned(context.Background(), signer2, utx2)
   393  	require.NoError(err)
   394  	require.NoError(env.mempool.Add(tx2))
   395  	tx2ID := tx2.ID()
   396  	_, ok = env.mempool.Get(tx2ID)
   397  	require.True(ok)
   398  
   399  	// Only tx1 should be in a built block since [MaxStakeDuration] is satisfied.
   400  	blkIntf, err := env.Builder.BuildBlock(context.Background())
   401  	require.NoError(err)
   402  
   403  	require.IsType(&blockexecutor.Block{}, blkIntf)
   404  	blk := blkIntf.(*blockexecutor.Block)
   405  	require.Len(blk.Txs(), 1)
   406  	require.Equal(tx1ID, blk.Txs()[0].ID())
   407  
   408  	// Mempool should have none of the txs
   409  	_, ok = env.mempool.Get(tx1ID)
   410  	require.False(ok)
   411  	_, ok = env.mempool.Get(tx2ID)
   412  	require.False(ok)
   413  
   414  	// Only tx2 should be dropped
   415  	require.NoError(env.mempool.GetDropReason(tx1ID))
   416  
   417  	tx2DropReason := env.mempool.GetDropReason(tx2ID)
   418  	require.ErrorIs(tx2DropReason, txexecutor.ErrStakeTooLong)
   419  }
   420  
   421  func TestPreviouslyDroppedTxsCannotBeReAddedToMempool(t *testing.T) {
   422  	require := require.New(t)
   423  
   424  	env := newEnvironment(t, latestFork)
   425  	env.ctx.Lock.Lock()
   426  	defer env.ctx.Lock.Unlock()
   427  
   428  	// Create a valid transaction
   429  	builder, signer := env.factory.NewWallet(testSubnet1ControlKeys[0], testSubnet1ControlKeys[1])
   430  	utx, err := builder.NewCreateChainTx(
   431  		testSubnet1.ID(),
   432  		nil,
   433  		constants.AVMID,
   434  		nil,
   435  		"chain name",
   436  	)
   437  	require.NoError(err)
   438  	tx, err := walletsigner.SignUnsigned(context.Background(), signer, utx)
   439  	require.NoError(err)
   440  	txID := tx.ID()
   441  
   442  	// Transaction should not be marked as dropped before being added to the
   443  	// mempool
   444  	require.NoError(env.mempool.GetDropReason(txID))
   445  
   446  	// Mark the transaction as dropped
   447  	errTestingDropped := errors.New("testing dropped")
   448  	env.mempool.MarkDropped(txID, errTestingDropped)
   449  	err = env.mempool.GetDropReason(txID)
   450  	require.ErrorIs(err, errTestingDropped)
   451  
   452  	// Issue the transaction
   453  	env.ctx.Lock.Unlock()
   454  	err = env.network.IssueTxFromRPC(tx)
   455  	require.ErrorIs(err, errTestingDropped)
   456  	env.ctx.Lock.Lock()
   457  	_, ok := env.mempool.Get(txID)
   458  	require.False(ok)
   459  
   460  	// When issued again, the mempool should still be marked as dropped
   461  	err = env.mempool.GetDropReason(txID)
   462  	require.ErrorIs(err, errTestingDropped)
   463  }
   464  
   465  func TestNoErrorOnUnexpectedSetPreferenceDuringBootstrapping(t *testing.T) {
   466  	require := require.New(t)
   467  
   468  	env := newEnvironment(t, latestFork)
   469  	env.ctx.Lock.Lock()
   470  	defer env.ctx.Lock.Unlock()
   471  
   472  	env.isBootstrapped.Set(false)
   473  
   474  	require.True(env.blkManager.SetPreference(ids.GenerateTestID())) // should not panic
   475  }
   476  
   477  func TestGetNextStakerToReward(t *testing.T) {
   478  	var (
   479  		now  = time.Now()
   480  		txID = ids.GenerateTestID()
   481  	)
   482  
   483  	type test struct {
   484  		name                 string
   485  		timestamp            time.Time
   486  		stateF               func(*gomock.Controller) state.Chain
   487  		expectedTxID         ids.ID
   488  		expectedShouldReward bool
   489  		expectedErr          error
   490  	}
   491  
   492  	tests := []test{
   493  		{
   494  			name:      "end of time",
   495  			timestamp: mockable.MaxTime,
   496  			stateF: func(ctrl *gomock.Controller) state.Chain {
   497  				return state.NewMockChain(ctrl)
   498  			},
   499  			expectedErr: ErrEndOfTime,
   500  		},
   501  		{
   502  			name:      "no stakers",
   503  			timestamp: now,
   504  			stateF: func(ctrl *gomock.Controller) state.Chain {
   505  				currentStakerIter := state.NewMockStakerIterator(ctrl)
   506  				currentStakerIter.EXPECT().Next().Return(false)
   507  				currentStakerIter.EXPECT().Release()
   508  
   509  				s := state.NewMockChain(ctrl)
   510  				s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil)
   511  
   512  				return s
   513  			},
   514  		},
   515  		{
   516  			name:      "expired subnet validator/delegator",
   517  			timestamp: now,
   518  			stateF: func(ctrl *gomock.Controller) state.Chain {
   519  				currentStakerIter := state.NewMockStakerIterator(ctrl)
   520  
   521  				currentStakerIter.EXPECT().Next().Return(true)
   522  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   523  					Priority: txs.SubnetPermissionedValidatorCurrentPriority,
   524  					EndTime:  now,
   525  				})
   526  				currentStakerIter.EXPECT().Next().Return(true)
   527  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   528  					TxID:     txID,
   529  					Priority: txs.SubnetPermissionlessDelegatorCurrentPriority,
   530  					EndTime:  now,
   531  				})
   532  				currentStakerIter.EXPECT().Release()
   533  
   534  				s := state.NewMockChain(ctrl)
   535  				s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil)
   536  
   537  				return s
   538  			},
   539  			expectedTxID:         txID,
   540  			expectedShouldReward: true,
   541  		},
   542  		{
   543  			name:      "expired primary network validator after subnet expired subnet validator",
   544  			timestamp: now,
   545  			stateF: func(ctrl *gomock.Controller) state.Chain {
   546  				currentStakerIter := state.NewMockStakerIterator(ctrl)
   547  
   548  				currentStakerIter.EXPECT().Next().Return(true)
   549  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   550  					Priority: txs.SubnetPermissionedValidatorCurrentPriority,
   551  					EndTime:  now,
   552  				})
   553  				currentStakerIter.EXPECT().Next().Return(true)
   554  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   555  					TxID:     txID,
   556  					Priority: txs.PrimaryNetworkValidatorCurrentPriority,
   557  					EndTime:  now,
   558  				})
   559  				currentStakerIter.EXPECT().Release()
   560  
   561  				s := state.NewMockChain(ctrl)
   562  				s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil)
   563  
   564  				return s
   565  			},
   566  			expectedTxID:         txID,
   567  			expectedShouldReward: true,
   568  		},
   569  		{
   570  			name:      "expired primary network delegator after subnet expired subnet validator",
   571  			timestamp: now,
   572  			stateF: func(ctrl *gomock.Controller) state.Chain {
   573  				currentStakerIter := state.NewMockStakerIterator(ctrl)
   574  
   575  				currentStakerIter.EXPECT().Next().Return(true)
   576  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   577  					Priority: txs.SubnetPermissionedValidatorCurrentPriority,
   578  					EndTime:  now,
   579  				})
   580  				currentStakerIter.EXPECT().Next().Return(true)
   581  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   582  					TxID:     txID,
   583  					Priority: txs.PrimaryNetworkDelegatorCurrentPriority,
   584  					EndTime:  now,
   585  				})
   586  				currentStakerIter.EXPECT().Release()
   587  
   588  				s := state.NewMockChain(ctrl)
   589  				s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil)
   590  
   591  				return s
   592  			},
   593  			expectedTxID:         txID,
   594  			expectedShouldReward: true,
   595  		},
   596  		{
   597  			name:      "non-expired primary network delegator",
   598  			timestamp: now,
   599  			stateF: func(ctrl *gomock.Controller) state.Chain {
   600  				currentStakerIter := state.NewMockStakerIterator(ctrl)
   601  
   602  				currentStakerIter.EXPECT().Next().Return(true)
   603  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   604  					TxID:     txID,
   605  					Priority: txs.PrimaryNetworkDelegatorCurrentPriority,
   606  					EndTime:  now.Add(time.Second),
   607  				})
   608  				currentStakerIter.EXPECT().Release()
   609  
   610  				s := state.NewMockChain(ctrl)
   611  				s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil)
   612  
   613  				return s
   614  			},
   615  			expectedTxID:         txID,
   616  			expectedShouldReward: false,
   617  		},
   618  		{
   619  			name:      "non-expired primary network validator",
   620  			timestamp: now,
   621  			stateF: func(ctrl *gomock.Controller) state.Chain {
   622  				currentStakerIter := state.NewMockStakerIterator(ctrl)
   623  
   624  				currentStakerIter.EXPECT().Next().Return(true)
   625  				currentStakerIter.EXPECT().Value().Return(&state.Staker{
   626  					TxID:     txID,
   627  					Priority: txs.PrimaryNetworkValidatorCurrentPriority,
   628  					EndTime:  now.Add(time.Second),
   629  				})
   630  				currentStakerIter.EXPECT().Release()
   631  
   632  				s := state.NewMockChain(ctrl)
   633  				s.EXPECT().GetCurrentStakerIterator().Return(currentStakerIter, nil)
   634  
   635  				return s
   636  			},
   637  			expectedTxID:         txID,
   638  			expectedShouldReward: false,
   639  		},
   640  	}
   641  
   642  	for _, tt := range tests {
   643  		t.Run(tt.name, func(t *testing.T) {
   644  			require := require.New(t)
   645  			ctrl := gomock.NewController(t)
   646  
   647  			state := tt.stateF(ctrl)
   648  			txID, shouldReward, err := getNextStakerToReward(tt.timestamp, state)
   649  			require.ErrorIs(err, tt.expectedErr)
   650  			if tt.expectedErr != nil {
   651  				return
   652  			}
   653  			require.Equal(tt.expectedTxID, txID)
   654  			require.Equal(tt.expectedShouldReward, shouldReward)
   655  		})
   656  	}
   657  }