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 }