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