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