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 }