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