github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/proposal_tx_executor_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 "math" 8 "testing" 9 "time" 10 11 "github.com/stretchr/testify/require" 12 13 "github.com/ava-labs/avalanchego/database" 14 "github.com/ava-labs/avalanchego/ids" 15 "github.com/ava-labs/avalanchego/upgrade/upgradetest" 16 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" 17 "github.com/ava-labs/avalanchego/utils/hashing" 18 "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest" 19 "github.com/ava-labs/avalanchego/vms/platformvm/reward" 20 "github.com/ava-labs/avalanchego/vms/platformvm/state" 21 "github.com/ava-labs/avalanchego/vms/platformvm/status" 22 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 23 "github.com/ava-labs/avalanchego/vms/secp256k1fx" 24 ) 25 26 func TestProposalTxExecuteAddDelegator(t *testing.T) { 27 dummyHeight := uint64(1) 28 rewardsOwner := &secp256k1fx.OutputOwners{ 29 Threshold: 1, 30 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 31 } 32 nodeID := genesistest.DefaultNodeIDs[0] 33 34 newValidatorID := ids.GenerateTestNodeID() 35 newValidatorStartTime := uint64(genesistest.DefaultValidatorStartTime.Add(5 * time.Second).Unix()) 36 newValidatorEndTime := uint64(genesistest.DefaultValidatorEndTime.Add(-5 * time.Second).Unix()) 37 38 // [addMinStakeValidator] adds a new validator to the primary network's 39 // pending validator set with the minimum staking amount 40 addMinStakeValidator := func(env *environment) { 41 require := require.New(t) 42 43 wallet := newWallet(t, env, walletConfig{ 44 keys: genesistest.DefaultFundedKeys[:1], 45 }) 46 47 tx, err := wallet.IssueAddValidatorTx( 48 &txs.Validator{ 49 NodeID: newValidatorID, 50 Start: newValidatorStartTime, 51 End: newValidatorEndTime, 52 Wght: env.config.MinValidatorStake, 53 }, 54 rewardsOwner, 55 reward.PercentDenominator, 56 ) 57 require.NoError(err) 58 59 addValTx := tx.Unsigned.(*txs.AddValidatorTx) 60 staker, err := state.NewCurrentStaker( 61 tx.ID(), 62 addValTx, 63 addValTx.StartTime(), 64 0, 65 ) 66 require.NoError(err) 67 68 require.NoError(env.state.PutCurrentValidator(staker)) 69 env.state.AddTx(tx, status.Committed) 70 env.state.SetHeight(dummyHeight) 71 require.NoError(env.state.Commit()) 72 } 73 74 // [addMaxStakeValidator] adds a new validator to the primary network's 75 // pending validator set with the maximum staking amount 76 addMaxStakeValidator := func(env *environment) { 77 require := require.New(t) 78 79 wallet := newWallet(t, env, walletConfig{ 80 keys: genesistest.DefaultFundedKeys[:1], 81 }) 82 83 tx, err := wallet.IssueAddValidatorTx( 84 &txs.Validator{ 85 NodeID: newValidatorID, 86 Start: newValidatorStartTime, 87 End: newValidatorEndTime, 88 Wght: env.config.MaxValidatorStake, 89 }, 90 rewardsOwner, 91 reward.PercentDenominator, 92 ) 93 require.NoError(err) 94 95 addValTx := tx.Unsigned.(*txs.AddValidatorTx) 96 staker, err := state.NewCurrentStaker( 97 tx.ID(), 98 addValTx, 99 addValTx.StartTime(), 100 0, 101 ) 102 require.NoError(err) 103 104 require.NoError(env.state.PutCurrentValidator(staker)) 105 env.state.AddTx(tx, status.Committed) 106 env.state.SetHeight(dummyHeight) 107 require.NoError(env.state.Commit()) 108 } 109 110 env := newEnvironment(t, upgradetest.ApricotPhase5) 111 currentTimestamp := env.state.GetTimestamp() 112 113 type test struct { 114 description string 115 stakeAmount uint64 116 startTime uint64 117 endTime uint64 118 nodeID ids.NodeID 119 feeKeys []*secp256k1.PrivateKey 120 setup func(*environment) 121 AP3Time time.Time 122 expectedErr error 123 } 124 125 tests := []test{ 126 { 127 description: "validator stops validating earlier than delegator", 128 stakeAmount: env.config.MinDelegatorStake, 129 startTime: genesistest.DefaultValidatorStartTimeUnix + 1, 130 endTime: genesistest.DefaultValidatorEndTimeUnix + 1, 131 nodeID: nodeID, 132 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 133 setup: nil, 134 AP3Time: genesistest.DefaultValidatorStartTime, 135 expectedErr: ErrPeriodMismatch, 136 }, 137 { 138 description: "validator not in the current or pending validator sets", 139 stakeAmount: env.config.MinDelegatorStake, 140 startTime: uint64(genesistest.DefaultValidatorStartTime.Add(5 * time.Second).Unix()), 141 endTime: uint64(genesistest.DefaultValidatorEndTime.Add(-5 * time.Second).Unix()), 142 nodeID: newValidatorID, 143 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 144 setup: nil, 145 AP3Time: genesistest.DefaultValidatorStartTime, 146 expectedErr: database.ErrNotFound, 147 }, 148 { 149 description: "delegator starts before validator", 150 stakeAmount: env.config.MinDelegatorStake, 151 startTime: newValidatorStartTime - 1, // start validating subnet before primary network 152 endTime: newValidatorEndTime, 153 nodeID: newValidatorID, 154 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 155 setup: addMinStakeValidator, 156 AP3Time: genesistest.DefaultValidatorStartTime, 157 expectedErr: ErrPeriodMismatch, 158 }, 159 { 160 description: "delegator stops before validator", 161 stakeAmount: env.config.MinDelegatorStake, 162 startTime: newValidatorStartTime, 163 endTime: newValidatorEndTime + 1, // stop validating subnet after stopping validating primary network 164 nodeID: newValidatorID, 165 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 166 setup: addMinStakeValidator, 167 AP3Time: genesistest.DefaultValidatorStartTime, 168 expectedErr: ErrPeriodMismatch, 169 }, 170 { 171 description: "valid", 172 stakeAmount: env.config.MinDelegatorStake, 173 startTime: newValidatorStartTime, // same start time as for primary network 174 endTime: newValidatorEndTime, // same end time as for primary network 175 nodeID: newValidatorID, 176 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 177 setup: addMinStakeValidator, 178 AP3Time: genesistest.DefaultValidatorStartTime, 179 expectedErr: nil, 180 }, 181 { 182 description: "starts delegating at current timestamp", 183 stakeAmount: env.config.MinDelegatorStake, 184 startTime: uint64(currentTimestamp.Unix()), 185 endTime: genesistest.DefaultValidatorEndTimeUnix, 186 nodeID: nodeID, 187 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 188 setup: nil, 189 AP3Time: genesistest.DefaultValidatorStartTime, 190 expectedErr: ErrTimestampNotBeforeStartTime, 191 }, 192 { 193 description: "tx fee paying key has no funds", 194 stakeAmount: env.config.MinDelegatorStake, 195 startTime: genesistest.DefaultValidatorStartTimeUnix + 1, 196 endTime: genesistest.DefaultValidatorEndTimeUnix, 197 nodeID: nodeID, 198 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[1]}, 199 setup: func(env *environment) { // Remove all UTXOs owned by keys[1] 200 utxoIDs, err := env.state.UTXOIDs( 201 genesistest.DefaultFundedKeys[1].Address().Bytes(), 202 ids.Empty, 203 math.MaxInt32) 204 require.NoError(t, err) 205 206 for _, utxoID := range utxoIDs { 207 env.state.DeleteUTXO(utxoID) 208 } 209 env.state.SetHeight(dummyHeight) 210 require.NoError(t, env.state.Commit()) 211 }, 212 AP3Time: genesistest.DefaultValidatorStartTime, 213 expectedErr: ErrFlowCheckFailed, 214 }, 215 { 216 description: "over delegation before AP3", 217 stakeAmount: env.config.MinDelegatorStake, 218 startTime: newValidatorStartTime, // same start time as for primary network 219 endTime: newValidatorEndTime, // same end time as for primary network 220 nodeID: newValidatorID, 221 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 222 setup: addMaxStakeValidator, 223 AP3Time: genesistest.DefaultValidatorEndTime, 224 expectedErr: nil, 225 }, 226 { 227 description: "over delegation after AP3", 228 stakeAmount: env.config.MinDelegatorStake, 229 startTime: newValidatorStartTime, // same start time as for primary network 230 endTime: newValidatorEndTime, // same end time as for primary network 231 nodeID: newValidatorID, 232 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 233 setup: addMaxStakeValidator, 234 AP3Time: genesistest.DefaultValidatorStartTime, 235 expectedErr: ErrOverDelegated, 236 }, 237 } 238 239 for _, tt := range tests { 240 t.Run(tt.description, func(t *testing.T) { 241 require := require.New(t) 242 env := newEnvironment(t, upgradetest.ApricotPhase5) 243 env.config.UpgradeConfig.ApricotPhase3Time = tt.AP3Time 244 245 wallet := newWallet(t, env, walletConfig{ 246 keys: tt.feeKeys, 247 }) 248 249 tx, err := wallet.IssueAddDelegatorTx( 250 &txs.Validator{ 251 NodeID: tt.nodeID, 252 Start: tt.startTime, 253 End: tt.endTime, 254 Wght: tt.stakeAmount, 255 }, 256 rewardsOwner, 257 ) 258 require.NoError(err) 259 260 if tt.setup != nil { 261 tt.setup(env) 262 } 263 264 onCommitState, err := state.NewDiff(lastAcceptedID, env) 265 require.NoError(err) 266 267 onAbortState, err := state.NewDiff(lastAcceptedID, env) 268 require.NoError(err) 269 270 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 271 executor := ProposalTxExecutor{ 272 OnCommitState: onCommitState, 273 OnAbortState: onAbortState, 274 Backend: &env.backend, 275 FeeCalculator: feeCalculator, 276 Tx: tx, 277 } 278 err = tx.Unsigned.Visit(&executor) 279 require.ErrorIs(err, tt.expectedErr) 280 }) 281 } 282 } 283 284 func TestProposalTxExecuteAddSubnetValidator(t *testing.T) { 285 require := require.New(t) 286 env := newEnvironment(t, upgradetest.ApricotPhase5) 287 env.ctx.Lock.Lock() 288 defer env.ctx.Lock.Unlock() 289 290 nodeID := genesistest.DefaultNodeIDs[0] 291 subnetID := testSubnet1.ID() 292 { 293 // Case: Proposed validator currently validating primary network 294 // but stops validating subnet after stops validating primary network 295 // (note that keys[0] is a genesis validator) 296 wallet := newWallet(t, env, walletConfig{ 297 subnetIDs: []ids.ID{subnetID}, 298 }) 299 tx, err := wallet.IssueAddSubnetValidatorTx( 300 &txs.SubnetValidator{ 301 Validator: txs.Validator{ 302 NodeID: nodeID, 303 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 304 End: genesistest.DefaultValidatorEndTimeUnix + 1, 305 Wght: genesistest.DefaultValidatorWeight, 306 }, 307 Subnet: subnetID, 308 }, 309 ) 310 require.NoError(err) 311 312 onCommitState, err := state.NewDiff(lastAcceptedID, env) 313 require.NoError(err) 314 315 onAbortState, err := state.NewDiff(lastAcceptedID, env) 316 require.NoError(err) 317 318 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 319 executor := ProposalTxExecutor{ 320 OnCommitState: onCommitState, 321 OnAbortState: onAbortState, 322 Backend: &env.backend, 323 FeeCalculator: feeCalculator, 324 Tx: tx, 325 } 326 err = tx.Unsigned.Visit(&executor) 327 require.ErrorIs(err, ErrPeriodMismatch) 328 } 329 330 { 331 // Case: Proposed validator currently validating primary network 332 // and proposed subnet validation period is subset of 333 // primary network validation period 334 // (note that keys[0] is a genesis validator) 335 wallet := newWallet(t, env, walletConfig{ 336 subnetIDs: []ids.ID{subnetID}, 337 }) 338 tx, err := wallet.IssueAddSubnetValidatorTx( 339 &txs.SubnetValidator{ 340 Validator: txs.Validator{ 341 NodeID: nodeID, 342 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 343 End: genesistest.DefaultValidatorEndTimeUnix, 344 Wght: genesistest.DefaultValidatorWeight, 345 }, 346 Subnet: subnetID, 347 }, 348 ) 349 require.NoError(err) 350 351 onCommitState, err := state.NewDiff(lastAcceptedID, env) 352 require.NoError(err) 353 354 onAbortState, err := state.NewDiff(lastAcceptedID, env) 355 require.NoError(err) 356 357 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 358 executor := ProposalTxExecutor{ 359 OnCommitState: onCommitState, 360 OnAbortState: onAbortState, 361 Backend: &env.backend, 362 FeeCalculator: feeCalculator, 363 Tx: tx, 364 } 365 require.NoError(tx.Unsigned.Visit(&executor)) 366 } 367 368 // Add a validator to pending validator set of primary network 369 // Starts validating primary network 10 seconds after genesis 370 pendingDSValidatorID := ids.GenerateTestNodeID() 371 dsStartTime := genesistest.DefaultValidatorStartTime.Add(10 * time.Second) 372 dsEndTime := dsStartTime.Add(5 * defaultMinStakingDuration) 373 374 wallet := newWallet(t, env, walletConfig{ 375 keys: genesistest.DefaultFundedKeys[:1], 376 }) 377 addDSTx, err := wallet.IssueAddValidatorTx( 378 &txs.Validator{ 379 NodeID: pendingDSValidatorID, 380 Start: uint64(dsStartTime.Unix()), 381 End: uint64(dsEndTime.Unix()), 382 Wght: env.config.MinValidatorStake, 383 }, 384 &secp256k1fx.OutputOwners{ 385 Threshold: 1, 386 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 387 }, 388 reward.PercentDenominator, 389 ) 390 require.NoError(err) 391 392 { 393 // Case: Proposed validator isn't in pending or current validator sets 394 wallet := newWallet(t, env, walletConfig{ 395 subnetIDs: []ids.ID{subnetID}, 396 }) 397 tx, err := wallet.IssueAddSubnetValidatorTx( 398 &txs.SubnetValidator{ 399 Validator: txs.Validator{ 400 NodeID: pendingDSValidatorID, 401 Start: uint64(dsStartTime.Unix()), // start validating subnet before primary network 402 End: uint64(dsEndTime.Unix()), 403 Wght: genesistest.DefaultValidatorWeight, 404 }, 405 Subnet: subnetID, 406 }, 407 ) 408 require.NoError(err) 409 410 onCommitState, err := state.NewDiff(lastAcceptedID, env) 411 require.NoError(err) 412 413 onAbortState, err := state.NewDiff(lastAcceptedID, env) 414 require.NoError(err) 415 416 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 417 executor := ProposalTxExecutor{ 418 OnCommitState: onCommitState, 419 OnAbortState: onAbortState, 420 Backend: &env.backend, 421 FeeCalculator: feeCalculator, 422 Tx: tx, 423 } 424 err = tx.Unsigned.Visit(&executor) 425 require.ErrorIs(err, ErrNotValidator) 426 } 427 428 addValTx := addDSTx.Unsigned.(*txs.AddValidatorTx) 429 staker, err := state.NewCurrentStaker( 430 addDSTx.ID(), 431 addValTx, 432 addValTx.StartTime(), 433 0, 434 ) 435 require.NoError(err) 436 437 require.NoError(env.state.PutCurrentValidator(staker)) 438 env.state.AddTx(addDSTx, status.Committed) 439 dummyHeight := uint64(1) 440 env.state.SetHeight(dummyHeight) 441 require.NoError(env.state.Commit()) 442 443 // Node with ID key.Address() now a pending validator for primary network 444 445 { 446 // Case: Proposed validator is pending validator of primary network 447 // but starts validating subnet before primary network 448 wallet := newWallet(t, env, walletConfig{ 449 subnetIDs: []ids.ID{subnetID}, 450 }) 451 tx, err := wallet.IssueAddSubnetValidatorTx( 452 &txs.SubnetValidator{ 453 Validator: txs.Validator{ 454 NodeID: pendingDSValidatorID, 455 Start: uint64(dsStartTime.Unix()) - 1, // start validating subnet before primary network 456 End: uint64(dsEndTime.Unix()), 457 Wght: genesistest.DefaultValidatorWeight, 458 }, 459 Subnet: subnetID, 460 }, 461 ) 462 require.NoError(err) 463 464 onCommitState, err := state.NewDiff(lastAcceptedID, env) 465 require.NoError(err) 466 467 onAbortState, err := state.NewDiff(lastAcceptedID, env) 468 require.NoError(err) 469 470 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 471 executor := ProposalTxExecutor{ 472 OnCommitState: onCommitState, 473 OnAbortState: onAbortState, 474 Backend: &env.backend, 475 FeeCalculator: feeCalculator, 476 Tx: tx, 477 } 478 err = tx.Unsigned.Visit(&executor) 479 require.ErrorIs(err, ErrPeriodMismatch) 480 } 481 482 { 483 // Case: Proposed validator is pending validator of primary network 484 // but stops validating subnet after primary network 485 wallet := newWallet(t, env, walletConfig{ 486 subnetIDs: []ids.ID{subnetID}, 487 }) 488 tx, err := wallet.IssueAddSubnetValidatorTx( 489 &txs.SubnetValidator{ 490 Validator: txs.Validator{ 491 NodeID: pendingDSValidatorID, 492 Start: uint64(dsStartTime.Unix()), 493 End: uint64(dsEndTime.Unix()) + 1, // stop validating subnet after stopping validating primary network 494 Wght: genesistest.DefaultValidatorWeight, 495 }, 496 Subnet: subnetID, 497 }, 498 ) 499 require.NoError(err) 500 501 onCommitState, err := state.NewDiff(lastAcceptedID, env) 502 require.NoError(err) 503 504 onAbortState, err := state.NewDiff(lastAcceptedID, env) 505 require.NoError(err) 506 507 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 508 executor := ProposalTxExecutor{ 509 OnCommitState: onCommitState, 510 OnAbortState: onAbortState, 511 Backend: &env.backend, 512 FeeCalculator: feeCalculator, 513 Tx: tx, 514 } 515 err = tx.Unsigned.Visit(&executor) 516 require.ErrorIs(err, ErrPeriodMismatch) 517 } 518 519 { 520 // Case: Proposed validator is pending validator of primary network and 521 // period validating subnet is subset of time validating primary network 522 wallet := newWallet(t, env, walletConfig{ 523 subnetIDs: []ids.ID{subnetID}, 524 }) 525 tx, err := wallet.IssueAddSubnetValidatorTx( 526 &txs.SubnetValidator{ 527 Validator: txs.Validator{ 528 NodeID: pendingDSValidatorID, 529 Start: uint64(dsStartTime.Unix()), // same start time as for primary network 530 End: uint64(dsEndTime.Unix()), // same end time as for primary network 531 Wght: genesistest.DefaultValidatorWeight, 532 }, 533 Subnet: subnetID, 534 }, 535 ) 536 require.NoError(err) 537 538 onCommitState, err := state.NewDiff(lastAcceptedID, env) 539 require.NoError(err) 540 541 onAbortState, err := state.NewDiff(lastAcceptedID, env) 542 require.NoError(err) 543 544 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 545 executor := ProposalTxExecutor{ 546 OnCommitState: onCommitState, 547 OnAbortState: onAbortState, 548 Backend: &env.backend, 549 FeeCalculator: feeCalculator, 550 Tx: tx, 551 } 552 require.NoError(tx.Unsigned.Visit(&executor)) 553 } 554 555 // Case: Proposed validator start validating at/before current timestamp 556 // First, advance the timestamp 557 newTimestamp := genesistest.DefaultValidatorStartTime.Add(2 * time.Second) 558 env.state.SetTimestamp(newTimestamp) 559 560 { 561 wallet := newWallet(t, env, walletConfig{ 562 subnetIDs: []ids.ID{subnetID}, 563 }) 564 tx, err := wallet.IssueAddSubnetValidatorTx( 565 &txs.SubnetValidator{ 566 Validator: txs.Validator{ 567 NodeID: nodeID, 568 Start: uint64(newTimestamp.Unix()), 569 End: uint64(newTimestamp.Add(defaultMinStakingDuration).Unix()), 570 Wght: genesistest.DefaultValidatorWeight, 571 }, 572 Subnet: subnetID, 573 }, 574 ) 575 require.NoError(err) 576 577 onCommitState, err := state.NewDiff(lastAcceptedID, env) 578 require.NoError(err) 579 580 onAbortState, err := state.NewDiff(lastAcceptedID, env) 581 require.NoError(err) 582 583 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 584 executor := ProposalTxExecutor{ 585 OnCommitState: onCommitState, 586 OnAbortState: onAbortState, 587 Backend: &env.backend, 588 FeeCalculator: feeCalculator, 589 Tx: tx, 590 } 591 err = tx.Unsigned.Visit(&executor) 592 require.ErrorIs(err, ErrTimestampNotBeforeStartTime) 593 } 594 595 // reset the timestamp 596 env.state.SetTimestamp(genesistest.DefaultValidatorStartTime) 597 598 // Case: Proposed validator already validating the subnet 599 // First, add validator as validator of subnet 600 wallet = newWallet(t, env, walletConfig{ 601 subnetIDs: []ids.ID{subnetID}, 602 }) 603 subnetTx, err := wallet.IssueAddSubnetValidatorTx( 604 &txs.SubnetValidator{ 605 Validator: txs.Validator{ 606 NodeID: nodeID, 607 Start: genesistest.DefaultValidatorStartTimeUnix, 608 End: genesistest.DefaultValidatorEndTimeUnix, 609 Wght: genesistest.DefaultValidatorWeight, 610 }, 611 Subnet: subnetID, 612 }, 613 ) 614 require.NoError(err) 615 616 addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) 617 staker, err = state.NewCurrentStaker( 618 subnetTx.ID(), 619 addSubnetValTx, 620 addSubnetValTx.StartTime(), 621 0, 622 ) 623 require.NoError(err) 624 625 require.NoError(env.state.PutCurrentValidator(staker)) 626 env.state.AddTx(subnetTx, status.Committed) 627 env.state.SetHeight(dummyHeight) 628 require.NoError(env.state.Commit()) 629 630 { 631 // Node with ID nodeIDKey.Address() now validating subnet with ID testSubnet1.ID 632 wallet = newWallet(t, env, walletConfig{ 633 subnetIDs: []ids.ID{subnetID}, 634 }) 635 duplicateSubnetTx, err := wallet.IssueAddSubnetValidatorTx( 636 &txs.SubnetValidator{ 637 Validator: txs.Validator{ 638 NodeID: nodeID, 639 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 640 End: genesistest.DefaultValidatorEndTimeUnix, 641 Wght: genesistest.DefaultValidatorWeight, 642 }, 643 Subnet: subnetID, 644 }, 645 ) 646 require.NoError(err) 647 648 onCommitState, err := state.NewDiff(lastAcceptedID, env) 649 require.NoError(err) 650 651 onAbortState, err := state.NewDiff(lastAcceptedID, env) 652 require.NoError(err) 653 654 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 655 executor := ProposalTxExecutor{ 656 OnCommitState: onCommitState, 657 OnAbortState: onAbortState, 658 Backend: &env.backend, 659 FeeCalculator: feeCalculator, 660 Tx: duplicateSubnetTx, 661 } 662 err = duplicateSubnetTx.Unsigned.Visit(&executor) 663 require.ErrorIs(err, ErrDuplicateValidator) 664 } 665 666 env.state.DeleteCurrentValidator(staker) 667 env.state.SetHeight(dummyHeight) 668 require.NoError(env.state.Commit()) 669 670 { 671 // Case: Too few signatures 672 wallet = newWallet(t, env, walletConfig{ 673 subnetIDs: []ids.ID{subnetID}, 674 }) 675 tx, err := wallet.IssueAddSubnetValidatorTx( 676 &txs.SubnetValidator{ 677 Validator: txs.Validator{ 678 NodeID: nodeID, 679 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 680 End: uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) + 1, 681 Wght: genesistest.DefaultValidatorWeight, 682 }, 683 Subnet: subnetID, 684 }, 685 ) 686 require.NoError(err) 687 688 // Remove a signature 689 addSubnetValidatorTx := tx.Unsigned.(*txs.AddSubnetValidatorTx) 690 input := addSubnetValidatorTx.SubnetAuth.(*secp256k1fx.Input) 691 input.SigIndices = input.SigIndices[1:] 692 // This tx was syntactically verified when it was created...pretend it wasn't so we don't use cache 693 addSubnetValidatorTx.SyntacticallyVerified = false 694 695 onCommitState, err := state.NewDiff(lastAcceptedID, env) 696 require.NoError(err) 697 698 onAbortState, err := state.NewDiff(lastAcceptedID, env) 699 require.NoError(err) 700 701 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 702 executor := ProposalTxExecutor{ 703 OnCommitState: onCommitState, 704 OnAbortState: onAbortState, 705 Backend: &env.backend, 706 FeeCalculator: feeCalculator, 707 Tx: tx, 708 } 709 err = tx.Unsigned.Visit(&executor) 710 require.ErrorIs(err, errUnauthorizedSubnetModification) 711 } 712 713 { 714 // Case: Control Signature from invalid key (keys[3] is not a control key) 715 wallet = newWallet(t, env, walletConfig{ 716 subnetIDs: []ids.ID{subnetID}, 717 }) 718 tx, err := wallet.IssueAddSubnetValidatorTx( 719 &txs.SubnetValidator{ 720 Validator: txs.Validator{ 721 NodeID: nodeID, 722 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 723 End: uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) + 1, 724 Wght: genesistest.DefaultValidatorWeight, 725 }, 726 Subnet: subnetID, 727 }, 728 ) 729 require.NoError(err) 730 731 // Replace a valid signature with one from keys[3] 732 sig, err := genesistest.DefaultFundedKeys[3].SignHash(hashing.ComputeHash256(tx.Unsigned.Bytes())) 733 require.NoError(err) 734 copy(tx.Creds[0].(*secp256k1fx.Credential).Sigs[0][:], sig) 735 736 onCommitState, err := state.NewDiff(lastAcceptedID, env) 737 require.NoError(err) 738 739 onAbortState, err := state.NewDiff(lastAcceptedID, env) 740 require.NoError(err) 741 742 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 743 executor := ProposalTxExecutor{ 744 OnCommitState: onCommitState, 745 OnAbortState: onAbortState, 746 Backend: &env.backend, 747 FeeCalculator: feeCalculator, 748 Tx: tx, 749 } 750 err = tx.Unsigned.Visit(&executor) 751 require.ErrorIs(err, errUnauthorizedSubnetModification) 752 } 753 754 { 755 // Case: Proposed validator in pending validator set for subnet 756 // First, add validator to pending validator set of subnet 757 wallet = newWallet(t, env, walletConfig{ 758 subnetIDs: []ids.ID{subnetID}, 759 }) 760 tx, err := wallet.IssueAddSubnetValidatorTx( 761 &txs.SubnetValidator{ 762 Validator: txs.Validator{ 763 NodeID: nodeID, 764 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 765 End: uint64(genesistest.DefaultValidatorStartTime.Add(defaultMinStakingDuration).Unix()) + 1, 766 Wght: genesistest.DefaultValidatorWeight, 767 }, 768 Subnet: subnetID, 769 }, 770 ) 771 require.NoError(err) 772 773 addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) 774 staker, err = state.NewCurrentStaker( 775 subnetTx.ID(), 776 addSubnetValTx, 777 addSubnetValTx.StartTime(), 778 0, 779 ) 780 require.NoError(err) 781 782 require.NoError(env.state.PutCurrentValidator(staker)) 783 env.state.AddTx(tx, status.Committed) 784 env.state.SetHeight(dummyHeight) 785 require.NoError(env.state.Commit()) 786 787 onCommitState, err := state.NewDiff(lastAcceptedID, env) 788 require.NoError(err) 789 790 onAbortState, err := state.NewDiff(lastAcceptedID, env) 791 require.NoError(err) 792 793 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 794 executor := ProposalTxExecutor{ 795 OnCommitState: onCommitState, 796 OnAbortState: onAbortState, 797 Backend: &env.backend, 798 FeeCalculator: feeCalculator, 799 Tx: tx, 800 } 801 err = tx.Unsigned.Visit(&executor) 802 require.ErrorIs(err, ErrDuplicateValidator) 803 } 804 } 805 806 func TestProposalTxExecuteAddValidator(t *testing.T) { 807 require := require.New(t) 808 env := newEnvironment(t, upgradetest.ApricotPhase5) 809 env.ctx.Lock.Lock() 810 defer env.ctx.Lock.Unlock() 811 812 nodeID := ids.GenerateTestNodeID() 813 chainTime := env.state.GetTimestamp() 814 rewardsOwner := &secp256k1fx.OutputOwners{ 815 Threshold: 1, 816 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 817 } 818 819 { 820 // Case: Validator's start time too early 821 wallet := newWallet(t, env, walletConfig{}) 822 tx, err := wallet.IssueAddValidatorTx( 823 &txs.Validator{ 824 NodeID: nodeID, 825 Start: uint64(chainTime.Unix()), 826 End: genesistest.DefaultValidatorEndTimeUnix, 827 Wght: env.config.MinValidatorStake, 828 }, 829 rewardsOwner, 830 reward.PercentDenominator, 831 ) 832 require.NoError(err) 833 834 onCommitState, err := state.NewDiff(lastAcceptedID, env) 835 require.NoError(err) 836 837 onAbortState, err := state.NewDiff(lastAcceptedID, env) 838 require.NoError(err) 839 840 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 841 executor := ProposalTxExecutor{ 842 OnCommitState: onCommitState, 843 OnAbortState: onAbortState, 844 Backend: &env.backend, 845 FeeCalculator: feeCalculator, 846 Tx: tx, 847 } 848 err = tx.Unsigned.Visit(&executor) 849 require.ErrorIs(err, ErrTimestampNotBeforeStartTime) 850 } 851 852 { 853 nodeID := genesistest.DefaultNodeIDs[0] 854 855 // Case: Validator already validating primary network 856 wallet := newWallet(t, env, walletConfig{}) 857 tx, err := wallet.IssueAddValidatorTx( 858 &txs.Validator{ 859 NodeID: nodeID, 860 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 861 End: genesistest.DefaultValidatorEndTimeUnix, 862 Wght: env.config.MinValidatorStake, 863 }, 864 rewardsOwner, 865 reward.PercentDenominator, 866 ) 867 require.NoError(err) 868 869 onCommitState, err := state.NewDiff(lastAcceptedID, env) 870 require.NoError(err) 871 872 onAbortState, err := state.NewDiff(lastAcceptedID, env) 873 require.NoError(err) 874 875 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 876 executor := ProposalTxExecutor{ 877 OnCommitState: onCommitState, 878 OnAbortState: onAbortState, 879 Backend: &env.backend, 880 FeeCalculator: feeCalculator, 881 Tx: tx, 882 } 883 err = tx.Unsigned.Visit(&executor) 884 require.ErrorIs(err, ErrAlreadyValidator) 885 } 886 887 { 888 // Case: Validator in pending validator set of primary network 889 startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second) 890 wallet := newWallet(t, env, walletConfig{}) 891 tx, err := wallet.IssueAddValidatorTx( 892 &txs.Validator{ 893 NodeID: nodeID, 894 Start: uint64(startTime.Unix()), 895 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()), 896 Wght: env.config.MinValidatorStake, 897 }, 898 rewardsOwner, 899 reward.PercentDenominator, 900 ) 901 require.NoError(err) 902 903 addValTx := tx.Unsigned.(*txs.AddValidatorTx) 904 staker, err := state.NewCurrentStaker( 905 tx.ID(), 906 addValTx, 907 addValTx.StartTime(), 908 0, 909 ) 910 require.NoError(err) 911 912 require.NoError(env.state.PutPendingValidator(staker)) 913 env.state.AddTx(tx, status.Committed) 914 dummyHeight := uint64(1) 915 env.state.SetHeight(dummyHeight) 916 require.NoError(env.state.Commit()) 917 918 onCommitState, err := state.NewDiff(lastAcceptedID, env) 919 require.NoError(err) 920 921 onAbortState, err := state.NewDiff(lastAcceptedID, env) 922 require.NoError(err) 923 924 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 925 executor := ProposalTxExecutor{ 926 OnCommitState: onCommitState, 927 OnAbortState: onAbortState, 928 Backend: &env.backend, 929 FeeCalculator: feeCalculator, 930 Tx: tx, 931 } 932 err = tx.Unsigned.Visit(&executor) 933 require.ErrorIs(err, ErrAlreadyValidator) 934 } 935 936 { 937 // Case: Validator doesn't have enough tokens to cover stake amount 938 wallet := newWallet(t, env, walletConfig{ 939 keys: genesistest.DefaultFundedKeys[:1], 940 }) 941 tx, err := wallet.IssueAddValidatorTx( 942 &txs.Validator{ 943 NodeID: ids.GenerateTestNodeID(), 944 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 945 End: genesistest.DefaultValidatorEndTimeUnix, 946 Wght: env.config.MinValidatorStake, 947 }, 948 rewardsOwner, 949 reward.PercentDenominator, 950 ) 951 require.NoError(err) 952 953 // Remove all UTXOs owned by preFundedKeys[0] 954 utxoIDs, err := env.state.UTXOIDs(genesistest.DefaultFundedKeys[0].Address().Bytes(), ids.Empty, math.MaxInt32) 955 require.NoError(err) 956 957 for _, utxoID := range utxoIDs { 958 env.state.DeleteUTXO(utxoID) 959 } 960 961 onCommitState, err := state.NewDiff(lastAcceptedID, env) 962 require.NoError(err) 963 964 onAbortState, err := state.NewDiff(lastAcceptedID, env) 965 require.NoError(err) 966 967 feeCalculator := state.PickFeeCalculator(env.config, onCommitState) 968 executor := ProposalTxExecutor{ 969 OnCommitState: onCommitState, 970 OnAbortState: onAbortState, 971 Backend: &env.backend, 972 FeeCalculator: feeCalculator, 973 Tx: tx, 974 } 975 err = tx.Unsigned.Visit(&executor) 976 require.ErrorIs(err, ErrFlowCheckFailed) 977 } 978 }