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