github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/standard_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 "errors" 8 "math" 9 "math/rand" 10 "testing" 11 "time" 12 13 "github.com/stretchr/testify/require" 14 "go.uber.org/mock/gomock" 15 16 "github.com/ava-labs/avalanchego/database" 17 "github.com/ava-labs/avalanchego/ids" 18 "github.com/ava-labs/avalanchego/snow" 19 "github.com/ava-labs/avalanchego/upgrade/upgradetest" 20 "github.com/ava-labs/avalanchego/utils" 21 "github.com/ava-labs/avalanchego/utils/constants" 22 "github.com/ava-labs/avalanchego/utils/crypto/bls" 23 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1" 24 "github.com/ava-labs/avalanchego/utils/hashing" 25 "github.com/ava-labs/avalanchego/utils/units" 26 "github.com/ava-labs/avalanchego/vms/components/avax" 27 "github.com/ava-labs/avalanchego/vms/components/verify" 28 "github.com/ava-labs/avalanchego/vms/platformvm/config" 29 "github.com/ava-labs/avalanchego/vms/platformvm/fx/fxmock" 30 "github.com/ava-labs/avalanchego/vms/platformvm/genesis/genesistest" 31 "github.com/ava-labs/avalanchego/vms/platformvm/reward" 32 "github.com/ava-labs/avalanchego/vms/platformvm/signer" 33 "github.com/ava-labs/avalanchego/vms/platformvm/state" 34 "github.com/ava-labs/avalanchego/vms/platformvm/status" 35 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 36 "github.com/ava-labs/avalanchego/vms/platformvm/utxo/utxomock" 37 "github.com/ava-labs/avalanchego/vms/secp256k1fx" 38 "github.com/ava-labs/avalanchego/wallet/subnet/primary/common" 39 ) 40 41 // This tests that the math performed during TransformSubnetTx execution can 42 // never overflow 43 const _ time.Duration = math.MaxUint32 * time.Second 44 45 var errTest = errors.New("non-nil error") 46 47 func TestStandardTxExecutorAddValidatorTxEmptyID(t *testing.T) { 48 require := require.New(t) 49 env := newEnvironment(t, upgradetest.ApricotPhase5) 50 env.ctx.Lock.Lock() 51 defer env.ctx.Lock.Unlock() 52 53 chainTime := env.state.GetTimestamp() 54 startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second) 55 56 tests := []struct { 57 banffTime time.Time 58 }{ 59 { // Case: Before banff 60 banffTime: chainTime.Add(1), 61 }, 62 { // Case: At banff 63 banffTime: chainTime, 64 }, 65 { // Case: After banff 66 banffTime: chainTime.Add(-1), 67 }, 68 } 69 for _, test := range tests { 70 // Case: Empty validator node ID after banff 71 env.config.UpgradeConfig.BanffTime = test.banffTime 72 73 wallet := newWallet(t, env, walletConfig{}) 74 75 tx, err := wallet.IssueAddValidatorTx( 76 &txs.Validator{ 77 NodeID: ids.EmptyNodeID, 78 Start: uint64(startTime.Unix()), 79 End: genesistest.DefaultValidatorEndTimeUnix, 80 Wght: env.config.MinValidatorStake, 81 }, 82 &secp256k1fx.OutputOwners{ 83 Threshold: 1, 84 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 85 }, 86 reward.PercentDenominator, 87 ) 88 require.NoError(err) 89 90 stateDiff, err := state.NewDiff(lastAcceptedID, env) 91 require.NoError(err) 92 93 feeCalculator := state.PickFeeCalculator(env.config, stateDiff) 94 executor := StandardTxExecutor{ 95 Backend: &env.backend, 96 State: stateDiff, 97 FeeCalculator: feeCalculator, 98 Tx: tx, 99 } 100 err = tx.Unsigned.Visit(&executor) 101 require.ErrorIs(err, errEmptyNodeID) 102 } 103 } 104 105 func TestStandardTxExecutorAddDelegator(t *testing.T) { 106 dummyHeight := uint64(1) 107 rewardsOwner := &secp256k1fx.OutputOwners{ 108 Threshold: 1, 109 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 110 } 111 nodeID := genesistest.DefaultNodeIDs[0] 112 113 newValidatorID := ids.GenerateTestNodeID() 114 newValidatorStartTime := genesistest.DefaultValidatorStartTime.Add(5 * time.Second) 115 newValidatorEndTime := genesistest.DefaultValidatorEndTime.Add(-5 * time.Second) 116 117 // [addMinStakeValidator] adds a new validator to the primary network's 118 // pending validator set with the minimum staking amount 119 addMinStakeValidator := func(env *environment) { 120 require := require.New(t) 121 122 wallet := newWallet(t, env, walletConfig{ 123 keys: genesistest.DefaultFundedKeys[:1], 124 }) 125 tx, err := wallet.IssueAddValidatorTx( 126 &txs.Validator{ 127 NodeID: newValidatorID, 128 Start: uint64(newValidatorStartTime.Unix()), 129 End: uint64(newValidatorEndTime.Unix()), 130 Wght: env.config.MinValidatorStake, 131 }, 132 rewardsOwner, 133 reward.PercentDenominator, 134 ) 135 require.NoError(err) 136 137 addValTx := tx.Unsigned.(*txs.AddValidatorTx) 138 staker, err := state.NewCurrentStaker( 139 tx.ID(), 140 addValTx, 141 newValidatorStartTime, 142 0, 143 ) 144 require.NoError(err) 145 146 require.NoError(env.state.PutCurrentValidator(staker)) 147 env.state.AddTx(tx, status.Committed) 148 env.state.SetHeight(dummyHeight) 149 require.NoError(env.state.Commit()) 150 } 151 152 // [addMaxStakeValidator] adds a new validator to the primary network's 153 // pending validator set with the maximum staking amount 154 addMaxStakeValidator := func(env *environment) { 155 require := require.New(t) 156 157 wallet := newWallet(t, env, walletConfig{ 158 keys: genesistest.DefaultFundedKeys[:1], 159 }) 160 tx, err := wallet.IssueAddValidatorTx( 161 &txs.Validator{ 162 NodeID: newValidatorID, 163 Start: uint64(newValidatorStartTime.Unix()), 164 End: uint64(newValidatorEndTime.Unix()), 165 Wght: env.config.MaxValidatorStake, 166 }, 167 rewardsOwner, 168 reward.PercentDenominator, 169 ) 170 require.NoError(err) 171 172 addValTx := tx.Unsigned.(*txs.AddValidatorTx) 173 staker, err := state.NewCurrentStaker( 174 tx.ID(), 175 addValTx, 176 newValidatorStartTime, 177 0, 178 ) 179 require.NoError(err) 180 181 require.NoError(env.state.PutCurrentValidator(staker)) 182 env.state.AddTx(tx, status.Committed) 183 env.state.SetHeight(dummyHeight) 184 require.NoError(env.state.Commit()) 185 } 186 187 env := newEnvironment(t, upgradetest.ApricotPhase5) 188 currentTimestamp := env.state.GetTimestamp() 189 190 type test struct { 191 description string 192 stakeAmount uint64 193 startTime time.Time 194 endTime time.Time 195 nodeID ids.NodeID 196 feeKeys []*secp256k1.PrivateKey 197 setup func(*environment) 198 AP3Time time.Time 199 expectedExecutionErr error 200 } 201 202 tests := []test{ 203 { 204 description: "validator stops validating earlier than delegator", 205 stakeAmount: env.config.MinDelegatorStake, 206 startTime: genesistest.DefaultValidatorStartTime.Add(time.Second), 207 endTime: genesistest.DefaultValidatorEndTime.Add(time.Second), 208 nodeID: nodeID, 209 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 210 setup: nil, 211 AP3Time: genesistest.DefaultValidatorStartTime, 212 expectedExecutionErr: ErrPeriodMismatch, 213 }, 214 { 215 description: "validator not in the current or pending validator sets", 216 stakeAmount: env.config.MinDelegatorStake, 217 startTime: genesistest.DefaultValidatorStartTime.Add(5 * time.Second), 218 endTime: genesistest.DefaultValidatorEndTime.Add(-5 * time.Second), 219 nodeID: newValidatorID, 220 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 221 setup: nil, 222 AP3Time: genesistest.DefaultValidatorStartTime, 223 expectedExecutionErr: database.ErrNotFound, 224 }, 225 { 226 description: "delegator starts before validator", 227 stakeAmount: env.config.MinDelegatorStake, 228 startTime: newValidatorStartTime.Add(-1 * time.Second), // start validating subnet before primary network 229 endTime: newValidatorEndTime, 230 nodeID: newValidatorID, 231 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 232 setup: addMinStakeValidator, 233 AP3Time: genesistest.DefaultValidatorStartTime, 234 expectedExecutionErr: ErrPeriodMismatch, 235 }, 236 { 237 description: "delegator stops before validator", 238 stakeAmount: env.config.MinDelegatorStake, 239 startTime: newValidatorStartTime, 240 endTime: newValidatorEndTime.Add(time.Second), // stop validating subnet after stopping validating primary network 241 nodeID: newValidatorID, 242 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 243 setup: addMinStakeValidator, 244 AP3Time: genesistest.DefaultValidatorStartTime, 245 expectedExecutionErr: ErrPeriodMismatch, 246 }, 247 { 248 description: "valid", 249 stakeAmount: env.config.MinDelegatorStake, 250 startTime: newValidatorStartTime, // same start time as for primary network 251 endTime: newValidatorEndTime, // same end time as for primary network 252 nodeID: newValidatorID, 253 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 254 setup: addMinStakeValidator, 255 AP3Time: genesistest.DefaultValidatorStartTime, 256 expectedExecutionErr: nil, 257 }, 258 { 259 description: "starts delegating at current timestamp", 260 stakeAmount: env.config.MinDelegatorStake, // weight 261 startTime: currentTimestamp, // start time 262 endTime: genesistest.DefaultValidatorEndTime, // end time 263 nodeID: nodeID, // node ID 264 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, // tx fee payer 265 setup: nil, 266 AP3Time: genesistest.DefaultValidatorStartTime, 267 expectedExecutionErr: ErrTimestampNotBeforeStartTime, 268 }, 269 { 270 description: "tx fee paying key has no funds", 271 stakeAmount: env.config.MinDelegatorStake, // weight 272 startTime: genesistest.DefaultValidatorStartTime.Add(time.Second), // start time 273 endTime: genesistest.DefaultValidatorEndTime, // end time 274 nodeID: nodeID, // node ID 275 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[1]}, // tx fee payer 276 setup: func(env *environment) { // Remove all UTXOs owned by keys[1] 277 utxoIDs, err := env.state.UTXOIDs( 278 genesistest.DefaultFundedKeys[1].Address().Bytes(), 279 ids.Empty, 280 math.MaxInt32) 281 require.NoError(t, err) 282 283 for _, utxoID := range utxoIDs { 284 env.state.DeleteUTXO(utxoID) 285 } 286 env.state.SetHeight(dummyHeight) 287 require.NoError(t, env.state.Commit()) 288 }, 289 AP3Time: genesistest.DefaultValidatorStartTime, 290 expectedExecutionErr: ErrFlowCheckFailed, 291 }, 292 { 293 description: "over delegation before AP3", 294 stakeAmount: env.config.MinDelegatorStake, 295 startTime: newValidatorStartTime, // same start time as for primary network 296 endTime: newValidatorEndTime, // same end time as for primary network 297 nodeID: newValidatorID, 298 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 299 setup: addMaxStakeValidator, 300 AP3Time: genesistest.DefaultValidatorEndTime, 301 expectedExecutionErr: nil, 302 }, 303 { 304 description: "over delegation after AP3", 305 stakeAmount: env.config.MinDelegatorStake, 306 startTime: newValidatorStartTime, // same start time as for primary network 307 endTime: newValidatorEndTime, // same end time as for primary network 308 nodeID: newValidatorID, 309 feeKeys: []*secp256k1.PrivateKey{genesistest.DefaultFundedKeys[0]}, 310 setup: addMaxStakeValidator, 311 AP3Time: genesistest.DefaultValidatorStartTime, 312 expectedExecutionErr: ErrOverDelegated, 313 }, 314 } 315 316 for _, tt := range tests { 317 t.Run(tt.description, func(t *testing.T) { 318 require := require.New(t) 319 env := newEnvironment(t, upgradetest.ApricotPhase5) 320 env.config.UpgradeConfig.ApricotPhase3Time = tt.AP3Time 321 322 wallet := newWallet(t, env, walletConfig{ 323 keys: tt.feeKeys, 324 }) 325 tx, err := wallet.IssueAddDelegatorTx( 326 &txs.Validator{ 327 NodeID: tt.nodeID, 328 Start: uint64(tt.startTime.Unix()), 329 End: uint64(tt.endTime.Unix()), 330 Wght: tt.stakeAmount, 331 }, 332 rewardsOwner, 333 ) 334 require.NoError(err) 335 336 if tt.setup != nil { 337 tt.setup(env) 338 } 339 340 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 341 require.NoError(err) 342 343 env.config.UpgradeConfig.BanffTime = onAcceptState.GetTimestamp() 344 345 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 346 executor := StandardTxExecutor{ 347 Backend: &env.backend, 348 State: onAcceptState, 349 FeeCalculator: feeCalculator, 350 Tx: tx, 351 } 352 err = tx.Unsigned.Visit(&executor) 353 require.ErrorIs(err, tt.expectedExecutionErr) 354 }) 355 } 356 } 357 358 func TestApricotStandardTxExecutorAddSubnetValidator(t *testing.T) { 359 require := require.New(t) 360 env := newEnvironment(t, upgradetest.ApricotPhase5) 361 env.ctx.Lock.Lock() 362 defer env.ctx.Lock.Unlock() 363 364 nodeID := genesistest.DefaultNodeIDs[0] 365 subnetID := testSubnet1.ID() 366 367 { 368 // Case: Proposed validator currently validating primary network 369 // but stops validating subnet after stops validating primary network 370 // (note that keys[0] is a genesis validator) 371 startTime := genesistest.DefaultValidatorStartTime.Add(time.Second) 372 373 wallet := newWallet(t, env, walletConfig{ 374 subnetIDs: []ids.ID{subnetID}, 375 }) 376 tx, err := wallet.IssueAddSubnetValidatorTx( 377 &txs.SubnetValidator{ 378 Validator: txs.Validator{ 379 NodeID: nodeID, 380 Start: uint64(startTime.Unix()), 381 End: genesistest.DefaultValidatorEndTimeUnix + 1, 382 Wght: genesistest.DefaultValidatorWeight, 383 }, 384 Subnet: subnetID, 385 }, 386 ) 387 require.NoError(err) 388 389 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 390 require.NoError(err) 391 392 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 393 executor := StandardTxExecutor{ 394 Backend: &env.backend, 395 State: onAcceptState, 396 FeeCalculator: feeCalculator, 397 Tx: tx, 398 } 399 err = tx.Unsigned.Visit(&executor) 400 require.ErrorIs(err, ErrPeriodMismatch) 401 } 402 403 { 404 // Case: Proposed validator currently validating primary network 405 // and proposed subnet validation period is subset of 406 // primary network validation period 407 // (note that keys[0] is a genesis validator) 408 wallet := newWallet(t, env, walletConfig{ 409 subnetIDs: []ids.ID{subnetID}, 410 }) 411 tx, err := wallet.IssueAddSubnetValidatorTx( 412 &txs.SubnetValidator{ 413 Validator: txs.Validator{ 414 NodeID: nodeID, 415 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 416 End: genesistest.DefaultValidatorEndTimeUnix, 417 Wght: genesistest.DefaultValidatorWeight, 418 }, 419 Subnet: subnetID, 420 }, 421 ) 422 require.NoError(err) 423 424 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 425 require.NoError(err) 426 427 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 428 executor := StandardTxExecutor{ 429 Backend: &env.backend, 430 State: onAcceptState, 431 FeeCalculator: feeCalculator, 432 Tx: tx, 433 } 434 require.NoError(tx.Unsigned.Visit(&executor)) 435 } 436 437 // Add a validator to pending validator set of primary network 438 // Starts validating primary network 10 seconds after genesis 439 pendingDSValidatorID := ids.GenerateTestNodeID() 440 dsStartTime := genesistest.DefaultValidatorStartTime.Add(10 * time.Second) 441 dsEndTime := dsStartTime.Add(5 * defaultMinStakingDuration) 442 443 wallet := newWallet(t, env, walletConfig{}) 444 addDSTx, err := wallet.IssueAddValidatorTx( 445 &txs.Validator{ 446 NodeID: pendingDSValidatorID, 447 Start: uint64(dsStartTime.Unix()), 448 End: uint64(dsEndTime.Unix()), 449 Wght: env.config.MinValidatorStake, 450 }, 451 &secp256k1fx.OutputOwners{ 452 Threshold: 1, 453 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 454 }, 455 reward.PercentDenominator, 456 ) 457 require.NoError(err) 458 459 { 460 // Case: Proposed validator isn't in pending or current validator sets 461 wallet := newWallet(t, env, walletConfig{ 462 subnetIDs: []ids.ID{subnetID}, 463 }) 464 tx, err := wallet.IssueAddSubnetValidatorTx( 465 &txs.SubnetValidator{ 466 Validator: txs.Validator{ 467 NodeID: pendingDSValidatorID, 468 Start: uint64(dsStartTime.Unix()), // start validating subnet before primary network 469 End: uint64(dsEndTime.Unix()), 470 Wght: genesistest.DefaultValidatorWeight, 471 }, 472 Subnet: subnetID, 473 }, 474 ) 475 require.NoError(err) 476 477 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 478 require.NoError(err) 479 480 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 481 executor := StandardTxExecutor{ 482 Backend: &env.backend, 483 State: onAcceptState, 484 FeeCalculator: feeCalculator, 485 Tx: tx, 486 } 487 err = tx.Unsigned.Visit(&executor) 488 require.ErrorIs(err, ErrNotValidator) 489 } 490 491 addValTx := addDSTx.Unsigned.(*txs.AddValidatorTx) 492 staker, err := state.NewCurrentStaker( 493 addDSTx.ID(), 494 addValTx, 495 dsStartTime, 496 0, 497 ) 498 require.NoError(err) 499 500 require.NoError(env.state.PutCurrentValidator(staker)) 501 env.state.AddTx(addDSTx, status.Committed) 502 dummyHeight := uint64(1) 503 env.state.SetHeight(dummyHeight) 504 require.NoError(env.state.Commit()) 505 506 // Node with ID key.Address() now a pending validator for primary network 507 508 { 509 // Case: Proposed validator is pending validator of primary network 510 // but starts validating subnet before primary network 511 wallet := newWallet(t, env, walletConfig{ 512 subnetIDs: []ids.ID{subnetID}, 513 }) 514 tx, err := wallet.IssueAddSubnetValidatorTx( 515 &txs.SubnetValidator{ 516 Validator: txs.Validator{ 517 NodeID: pendingDSValidatorID, 518 Start: uint64(dsStartTime.Unix()) - 1, // start validating subnet before primary network 519 End: uint64(dsEndTime.Unix()), 520 Wght: genesistest.DefaultValidatorWeight, 521 }, 522 Subnet: subnetID, 523 }, 524 ) 525 require.NoError(err) 526 527 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 528 require.NoError(err) 529 530 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 531 executor := StandardTxExecutor{ 532 Backend: &env.backend, 533 State: onAcceptState, 534 FeeCalculator: feeCalculator, 535 Tx: tx, 536 } 537 err = tx.Unsigned.Visit(&executor) 538 require.ErrorIs(err, ErrPeriodMismatch) 539 } 540 541 { 542 // Case: Proposed validator is pending validator of primary network 543 // but stops validating subnet after primary network 544 wallet := newWallet(t, env, walletConfig{ 545 subnetIDs: []ids.ID{subnetID}, 546 }) 547 tx, err := wallet.IssueAddSubnetValidatorTx( 548 &txs.SubnetValidator{ 549 Validator: txs.Validator{ 550 NodeID: pendingDSValidatorID, 551 Start: uint64(dsStartTime.Unix()), 552 End: uint64(dsEndTime.Unix()) + 1, // stop validating subnet after stopping validating primary network 553 Wght: genesistest.DefaultValidatorWeight, 554 }, 555 Subnet: subnetID, 556 }, 557 ) 558 require.NoError(err) 559 560 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 561 require.NoError(err) 562 563 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 564 executor := StandardTxExecutor{ 565 Backend: &env.backend, 566 State: onAcceptState, 567 FeeCalculator: feeCalculator, 568 Tx: tx, 569 } 570 err = tx.Unsigned.Visit(&executor) 571 require.ErrorIs(err, ErrPeriodMismatch) 572 } 573 574 { 575 // Case: Proposed validator is pending validator of primary network and 576 // period validating subnet is subset of time validating primary network 577 wallet := newWallet(t, env, walletConfig{ 578 subnetIDs: []ids.ID{subnetID}, 579 }) 580 tx, err := wallet.IssueAddSubnetValidatorTx( 581 &txs.SubnetValidator{ 582 Validator: txs.Validator{ 583 NodeID: pendingDSValidatorID, 584 Start: uint64(dsStartTime.Unix()), // same start time as for primary network 585 End: uint64(dsEndTime.Unix()), // same end time as for primary network 586 Wght: genesistest.DefaultValidatorWeight, 587 }, 588 Subnet: subnetID, 589 }, 590 ) 591 require.NoError(err) 592 593 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 594 require.NoError(err) 595 596 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 597 executor := StandardTxExecutor{ 598 Backend: &env.backend, 599 State: onAcceptState, 600 FeeCalculator: feeCalculator, 601 Tx: tx, 602 } 603 require.NoError(tx.Unsigned.Visit(&executor)) 604 } 605 606 // Case: Proposed validator start validating at/before current timestamp 607 // First, advance the timestamp 608 newTimestamp := genesistest.DefaultValidatorStartTime.Add(2 * time.Second) 609 env.state.SetTimestamp(newTimestamp) 610 611 { 612 wallet := newWallet(t, env, walletConfig{ 613 subnetIDs: []ids.ID{subnetID}, 614 }) 615 tx, err := wallet.IssueAddSubnetValidatorTx( 616 &txs.SubnetValidator{ 617 Validator: txs.Validator{ 618 NodeID: nodeID, 619 Start: uint64(newTimestamp.Unix()), 620 End: uint64(newTimestamp.Add(defaultMinStakingDuration).Unix()), 621 Wght: genesistest.DefaultValidatorWeight, 622 }, 623 Subnet: subnetID, 624 }, 625 ) 626 require.NoError(err) 627 628 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 629 require.NoError(err) 630 631 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 632 executor := StandardTxExecutor{ 633 Backend: &env.backend, 634 State: onAcceptState, 635 FeeCalculator: feeCalculator, 636 Tx: tx, 637 } 638 err = tx.Unsigned.Visit(&executor) 639 require.ErrorIs(err, ErrTimestampNotBeforeStartTime) 640 } 641 642 // reset the timestamp 643 env.state.SetTimestamp(genesistest.DefaultValidatorStartTime) 644 645 // Case: Proposed validator already validating the subnet 646 // First, add validator as validator of subnet 647 wallet = newWallet(t, env, walletConfig{ 648 subnetIDs: []ids.ID{subnetID}, 649 }) 650 subnetTx, err := wallet.IssueAddSubnetValidatorTx( 651 &txs.SubnetValidator{ 652 Validator: txs.Validator{ 653 NodeID: nodeID, 654 Start: genesistest.DefaultValidatorStartTimeUnix, 655 End: genesistest.DefaultValidatorEndTimeUnix, 656 Wght: genesistest.DefaultValidatorWeight, 657 }, 658 Subnet: subnetID, 659 }, 660 ) 661 require.NoError(err) 662 663 addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) 664 staker, err = state.NewCurrentStaker( 665 subnetTx.ID(), 666 addSubnetValTx, 667 genesistest.DefaultValidatorStartTime, 668 0, 669 ) 670 require.NoError(err) 671 672 require.NoError(env.state.PutCurrentValidator(staker)) 673 env.state.AddTx(subnetTx, status.Committed) 674 env.state.SetHeight(dummyHeight) 675 require.NoError(env.state.Commit()) 676 677 { 678 // Node with ID nodeIDKey.Address() now validating subnet with ID testSubnet1.ID 679 startTime := genesistest.DefaultValidatorStartTime.Add(time.Second) 680 wallet := newWallet(t, env, walletConfig{ 681 subnetIDs: []ids.ID{subnetID}, 682 }) 683 tx, err := wallet.IssueAddSubnetValidatorTx( 684 &txs.SubnetValidator{ 685 Validator: txs.Validator{ 686 NodeID: nodeID, 687 Start: uint64(startTime.Unix()), 688 End: genesistest.DefaultValidatorEndTimeUnix, 689 Wght: genesistest.DefaultValidatorWeight, 690 }, 691 Subnet: subnetID, 692 }, 693 ) 694 require.NoError(err) 695 696 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 697 require.NoError(err) 698 699 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 700 executor := StandardTxExecutor{ 701 Backend: &env.backend, 702 State: onAcceptState, 703 FeeCalculator: feeCalculator, 704 Tx: tx, 705 } 706 err = tx.Unsigned.Visit(&executor) 707 require.ErrorIs(err, ErrDuplicateValidator) 708 } 709 710 env.state.DeleteCurrentValidator(staker) 711 env.state.SetHeight(dummyHeight) 712 require.NoError(env.state.Commit()) 713 714 { 715 // Case: Duplicate signatures 716 startTime := genesistest.DefaultValidatorStartTime.Add(time.Second) 717 wallet := newWallet(t, env, walletConfig{ 718 subnetIDs: []ids.ID{subnetID}, 719 }) 720 tx, err := wallet.IssueAddSubnetValidatorTx( 721 &txs.SubnetValidator{ 722 Validator: txs.Validator{ 723 NodeID: nodeID, 724 Start: uint64(startTime.Unix()), 725 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()) + 1, 726 Wght: genesistest.DefaultValidatorWeight, 727 }, 728 Subnet: subnetID, 729 }, 730 ) 731 require.NoError(err) 732 733 // Duplicate a signature 734 addSubnetValidatorTx := tx.Unsigned.(*txs.AddSubnetValidatorTx) 735 input := addSubnetValidatorTx.SubnetAuth.(*secp256k1fx.Input) 736 input.SigIndices = append(input.SigIndices, input.SigIndices[0]) 737 // This tx was syntactically verified when it was created...pretend it wasn't so we don't use cache 738 addSubnetValidatorTx.SyntacticallyVerified = false 739 740 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 741 require.NoError(err) 742 743 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 744 executor := StandardTxExecutor{ 745 Backend: &env.backend, 746 State: onAcceptState, 747 FeeCalculator: feeCalculator, 748 Tx: tx, 749 } 750 err = tx.Unsigned.Visit(&executor) 751 require.ErrorIs(err, secp256k1fx.ErrInputIndicesNotSortedUnique) 752 } 753 754 { 755 // Case: Too few signatures 756 startTime := genesistest.DefaultValidatorStartTime.Add(time.Second) 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: uint64(startTime.Unix()), 765 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()), 766 Wght: genesistest.DefaultValidatorWeight, 767 }, 768 Subnet: subnetID, 769 }, 770 ) 771 require.NoError(err) 772 773 // Remove a signature 774 addSubnetValidatorTx := tx.Unsigned.(*txs.AddSubnetValidatorTx) 775 input := addSubnetValidatorTx.SubnetAuth.(*secp256k1fx.Input) 776 input.SigIndices = input.SigIndices[1:] 777 // This tx was syntactically verified when it was created...pretend it wasn't so we don't use cache 778 addSubnetValidatorTx.SyntacticallyVerified = false 779 780 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 781 require.NoError(err) 782 783 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 784 executor := StandardTxExecutor{ 785 Backend: &env.backend, 786 State: onAcceptState, 787 FeeCalculator: feeCalculator, 788 Tx: tx, 789 } 790 err = tx.Unsigned.Visit(&executor) 791 require.ErrorIs(err, errUnauthorizedSubnetModification) 792 } 793 794 { 795 // Case: Control Signature from invalid key (keys[3] is not a control key) 796 startTime := genesistest.DefaultValidatorStartTime.Add(time.Second) 797 wallet := newWallet(t, env, walletConfig{ 798 subnetIDs: []ids.ID{subnetID}, 799 }) 800 tx, err := wallet.IssueAddSubnetValidatorTx( 801 &txs.SubnetValidator{ 802 Validator: txs.Validator{ 803 NodeID: nodeID, 804 Start: uint64(startTime.Unix()), 805 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()), 806 Wght: genesistest.DefaultValidatorWeight, 807 }, 808 Subnet: subnetID, 809 }, 810 ) 811 require.NoError(err) 812 813 // Replace a valid signature with one from keys[3] 814 sig, err := genesistest.DefaultFundedKeys[3].SignHash(hashing.ComputeHash256(tx.Unsigned.Bytes())) 815 require.NoError(err) 816 copy(tx.Creds[0].(*secp256k1fx.Credential).Sigs[0][:], sig) 817 818 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 819 require.NoError(err) 820 821 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 822 executor := StandardTxExecutor{ 823 Backend: &env.backend, 824 State: onAcceptState, 825 FeeCalculator: feeCalculator, 826 Tx: tx, 827 } 828 err = tx.Unsigned.Visit(&executor) 829 require.ErrorIs(err, errUnauthorizedSubnetModification) 830 } 831 832 { 833 // Case: Proposed validator in pending validator set for subnet 834 // First, add validator to pending validator set of subnet 835 startTime := genesistest.DefaultValidatorStartTime.Add(time.Second) 836 wallet := newWallet(t, env, walletConfig{ 837 subnetIDs: []ids.ID{subnetID}, 838 }) 839 tx, err := wallet.IssueAddSubnetValidatorTx( 840 &txs.SubnetValidator{ 841 Validator: txs.Validator{ 842 NodeID: nodeID, 843 Start: uint64(startTime.Unix()) + 1, 844 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()) + 1, 845 Wght: genesistest.DefaultValidatorWeight, 846 }, 847 Subnet: subnetID, 848 }, 849 ) 850 require.NoError(err) 851 852 addSubnetValTx := subnetTx.Unsigned.(*txs.AddSubnetValidatorTx) 853 staker, err = state.NewCurrentStaker( 854 subnetTx.ID(), 855 addSubnetValTx, 856 genesistest.DefaultValidatorStartTime, 857 0, 858 ) 859 require.NoError(err) 860 861 require.NoError(env.state.PutCurrentValidator(staker)) 862 env.state.AddTx(tx, status.Committed) 863 env.state.SetHeight(dummyHeight) 864 require.NoError(env.state.Commit()) 865 866 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 867 require.NoError(err) 868 869 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 870 executor := StandardTxExecutor{ 871 Backend: &env.backend, 872 State: onAcceptState, 873 FeeCalculator: feeCalculator, 874 Tx: tx, 875 } 876 err = tx.Unsigned.Visit(&executor) 877 require.ErrorIs(err, ErrDuplicateValidator) 878 } 879 } 880 881 func TestEtnaStandardTxExecutorAddSubnetValidator(t *testing.T) { 882 require := require.New(t) 883 env := newEnvironment(t, upgradetest.Etna) 884 env.ctx.Lock.Lock() 885 defer env.ctx.Lock.Unlock() 886 887 nodeID := genesistest.DefaultNodeIDs[0] 888 subnetID := testSubnet1.ID() 889 890 wallet := newWallet(t, env, walletConfig{ 891 subnetIDs: []ids.ID{subnetID}, 892 }) 893 tx, err := wallet.IssueAddSubnetValidatorTx( 894 &txs.SubnetValidator{ 895 Validator: txs.Validator{ 896 NodeID: nodeID, 897 Start: genesistest.DefaultValidatorStartTimeUnix + 1, 898 End: genesistest.DefaultValidatorEndTimeUnix, 899 Wght: genesistest.DefaultValidatorWeight, 900 }, 901 Subnet: subnetID, 902 }, 903 ) 904 require.NoError(err) 905 906 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 907 require.NoError(err) 908 909 onAcceptState.SetSubnetManager(subnetID, ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}) 910 911 executor := StandardTxExecutor{ 912 Backend: &env.backend, 913 State: onAcceptState, 914 Tx: tx, 915 } 916 err = tx.Unsigned.Visit(&executor) 917 require.ErrorIs(err, errIsImmutable) 918 } 919 920 func TestBanffStandardTxExecutorAddValidator(t *testing.T) { 921 require := require.New(t) 922 env := newEnvironment(t, upgradetest.Banff) 923 env.ctx.Lock.Lock() 924 defer env.ctx.Lock.Unlock() 925 926 nodeID := ids.GenerateTestNodeID() 927 rewardsOwner := &secp256k1fx.OutputOwners{ 928 Threshold: 1, 929 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 930 } 931 932 { 933 // Case: Validator's start time too early 934 wallet := newWallet(t, env, walletConfig{}) 935 tx, err := wallet.IssueAddValidatorTx( 936 &txs.Validator{ 937 NodeID: nodeID, 938 Start: genesistest.DefaultValidatorStartTimeUnix - 1, 939 End: genesistest.DefaultValidatorEndTimeUnix, 940 Wght: env.config.MinValidatorStake, 941 }, 942 rewardsOwner, 943 reward.PercentDenominator, 944 ) 945 require.NoError(err) 946 947 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 948 require.NoError(err) 949 950 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 951 executor := StandardTxExecutor{ 952 Backend: &env.backend, 953 State: onAcceptState, 954 FeeCalculator: feeCalculator, 955 Tx: tx, 956 } 957 err = tx.Unsigned.Visit(&executor) 958 require.ErrorIs(err, ErrTimestampNotBeforeStartTime) 959 } 960 961 { 962 // Case: Validator in current validator set of primary network 963 wallet := newWallet(t, env, walletConfig{}) 964 965 startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second) 966 tx, err := wallet.IssueAddValidatorTx( 967 &txs.Validator{ 968 NodeID: nodeID, 969 Start: uint64(startTime.Unix()), 970 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()), 971 Wght: env.config.MinValidatorStake, 972 }, 973 rewardsOwner, 974 reward.PercentDenominator, 975 ) 976 require.NoError(err) 977 978 addValTx := tx.Unsigned.(*txs.AddValidatorTx) 979 staker, err := state.NewCurrentStaker( 980 tx.ID(), 981 addValTx, 982 startTime, 983 0, 984 ) 985 require.NoError(err) 986 987 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 988 require.NoError(err) 989 990 require.NoError(onAcceptState.PutCurrentValidator(staker)) 991 onAcceptState.AddTx(tx, status.Committed) 992 993 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 994 executor := StandardTxExecutor{ 995 Backend: &env.backend, 996 State: onAcceptState, 997 FeeCalculator: feeCalculator, 998 Tx: tx, 999 } 1000 err = tx.Unsigned.Visit(&executor) 1001 require.ErrorIs(err, ErrAlreadyValidator) 1002 } 1003 1004 { 1005 // Case: Validator in pending validator set of primary network 1006 wallet := newWallet(t, env, walletConfig{}) 1007 1008 startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second) 1009 tx, err := wallet.IssueAddValidatorTx( 1010 &txs.Validator{ 1011 NodeID: nodeID, 1012 Start: uint64(startTime.Unix()), 1013 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()), 1014 Wght: env.config.MinValidatorStake, 1015 }, 1016 rewardsOwner, 1017 reward.PercentDenominator, 1018 ) 1019 require.NoError(err) 1020 1021 staker, err := state.NewPendingStaker( 1022 tx.ID(), 1023 tx.Unsigned.(*txs.AddValidatorTx), 1024 ) 1025 require.NoError(err) 1026 1027 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 1028 require.NoError(err) 1029 1030 require.NoError(onAcceptState.PutPendingValidator(staker)) 1031 onAcceptState.AddTx(tx, status.Committed) 1032 1033 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 1034 executor := StandardTxExecutor{ 1035 Backend: &env.backend, 1036 State: onAcceptState, 1037 FeeCalculator: feeCalculator, 1038 Tx: tx, 1039 } 1040 err = tx.Unsigned.Visit(&executor) 1041 require.ErrorIs(err, ErrAlreadyValidator) 1042 } 1043 1044 { 1045 // Case: Validator doesn't have enough tokens to cover stake amount 1046 wallet := newWallet(t, env, walletConfig{ 1047 keys: genesistest.DefaultFundedKeys[:1], 1048 }) 1049 1050 startTime := genesistest.DefaultValidatorStartTime.Add(1 * time.Second) 1051 tx, err := wallet.IssueAddValidatorTx( 1052 &txs.Validator{ 1053 NodeID: nodeID, 1054 Start: uint64(startTime.Unix()), 1055 End: uint64(startTime.Add(defaultMinStakingDuration).Unix()), 1056 Wght: env.config.MinValidatorStake, 1057 }, 1058 rewardsOwner, 1059 reward.PercentDenominator, 1060 ) 1061 require.NoError(err) 1062 1063 // Remove all UTXOs owned by preFundedKeys[0] 1064 utxoIDs, err := env.state.UTXOIDs(genesistest.DefaultFundedKeys[0].Address().Bytes(), ids.Empty, math.MaxInt32) 1065 require.NoError(err) 1066 1067 onAcceptState, err := state.NewDiff(lastAcceptedID, env) 1068 require.NoError(err) 1069 1070 for _, utxoID := range utxoIDs { 1071 onAcceptState.DeleteUTXO(utxoID) 1072 } 1073 1074 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 1075 executor := StandardTxExecutor{ 1076 Backend: &env.backend, 1077 FeeCalculator: feeCalculator, 1078 State: onAcceptState, 1079 Tx: tx, 1080 } 1081 err = tx.Unsigned.Visit(&executor) 1082 require.ErrorIs(err, ErrFlowCheckFailed) 1083 } 1084 } 1085 1086 // Verifies that [AddValidatorTx] and [AddDelegatorTx] are disabled post-Durango 1087 func TestDurangoDisabledTransactions(t *testing.T) { 1088 type test struct { 1089 name string 1090 buildTx func(t *testing.T, env *environment) *txs.Tx 1091 expectedErr error 1092 } 1093 1094 rewardsOwner := &secp256k1fx.OutputOwners{ 1095 Threshold: 1, 1096 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 1097 } 1098 1099 tests := []test{ 1100 { 1101 name: "AddValidatorTx", 1102 buildTx: func(t *testing.T, env *environment) *txs.Tx { 1103 var ( 1104 nodeID = ids.GenerateTestNodeID() 1105 chainTime = env.state.GetTimestamp() 1106 endTime = chainTime.Add(defaultMaxStakingDuration) 1107 ) 1108 1109 wallet := newWallet(t, env, walletConfig{}) 1110 tx, err := wallet.IssueAddValidatorTx( 1111 &txs.Validator{ 1112 NodeID: nodeID, 1113 Start: 0, 1114 End: uint64(endTime.Unix()), 1115 Wght: defaultMinValidatorStake, 1116 }, 1117 rewardsOwner, 1118 reward.PercentDenominator, 1119 ) 1120 require.NoError(t, err) 1121 1122 return tx 1123 }, 1124 expectedErr: ErrAddValidatorTxPostDurango, 1125 }, 1126 { 1127 name: "AddDelegatorTx", 1128 buildTx: func(t *testing.T, env *environment) *txs.Tx { 1129 require := require.New(t) 1130 1131 var primaryValidator *state.Staker 1132 it, err := env.state.GetCurrentStakerIterator() 1133 require.NoError(err) 1134 for it.Next() { 1135 staker := it.Value() 1136 if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority { 1137 continue 1138 } 1139 primaryValidator = staker 1140 break 1141 } 1142 it.Release() 1143 1144 wallet := newWallet(t, env, walletConfig{}) 1145 tx, err := wallet.IssueAddDelegatorTx( 1146 &txs.Validator{ 1147 NodeID: primaryValidator.NodeID, 1148 Start: 0, 1149 End: uint64(primaryValidator.EndTime.Unix()), 1150 Wght: defaultMinValidatorStake, 1151 }, 1152 rewardsOwner, 1153 ) 1154 require.NoError(err) 1155 1156 return tx 1157 }, 1158 expectedErr: ErrAddDelegatorTxPostDurango, 1159 }, 1160 } 1161 1162 for _, tt := range tests { 1163 t.Run(tt.name, func(t *testing.T) { 1164 require := require.New(t) 1165 1166 env := newEnvironment(t, upgradetest.Durango) 1167 env.ctx.Lock.Lock() 1168 defer env.ctx.Lock.Unlock() 1169 1170 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1171 require.NoError(err) 1172 1173 tx := tt.buildTx(t, env) 1174 1175 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 1176 err = tx.Unsigned.Visit(&StandardTxExecutor{ 1177 Backend: &env.backend, 1178 State: onAcceptState, 1179 FeeCalculator: feeCalculator, 1180 Tx: tx, 1181 }) 1182 require.ErrorIs(err, tt.expectedErr) 1183 }) 1184 } 1185 } 1186 1187 // Verifies that the Memo field is required to be empty post-Durango 1188 func TestDurangoMemoField(t *testing.T) { 1189 type test struct { 1190 name string 1191 setupTest func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) 1192 } 1193 1194 owners := &secp256k1fx.OutputOwners{ 1195 Threshold: 1, 1196 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 1197 } 1198 1199 tests := []test{ 1200 { 1201 name: "AddSubnetValidatorTx", 1202 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1203 require := require.New(t) 1204 1205 var primaryValidator *state.Staker 1206 it, err := env.state.GetCurrentStakerIterator() 1207 require.NoError(err) 1208 for it.Next() { 1209 staker := it.Value() 1210 if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority { 1211 continue 1212 } 1213 primaryValidator = staker 1214 break 1215 } 1216 it.Release() 1217 1218 subnetID := testSubnet1.ID() 1219 wallet := newWallet(t, env, walletConfig{ 1220 subnetIDs: []ids.ID{subnetID}, 1221 }) 1222 tx, err := wallet.IssueAddSubnetValidatorTx( 1223 &txs.SubnetValidator{ 1224 Validator: txs.Validator{ 1225 NodeID: primaryValidator.NodeID, 1226 Start: 0, 1227 End: uint64(primaryValidator.EndTime.Unix()), 1228 Wght: defaultMinValidatorStake, 1229 }, 1230 Subnet: subnetID, 1231 }, 1232 common.WithMemo(memoField), 1233 ) 1234 require.NoError(err) 1235 1236 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1237 require.NoError(err) 1238 return tx, onAcceptState 1239 }, 1240 }, 1241 { 1242 name: "CreateChainTx", 1243 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1244 require := require.New(t) 1245 1246 subnetID := testSubnet1.ID() 1247 wallet := newWallet(t, env, walletConfig{ 1248 subnetIDs: []ids.ID{subnetID}, 1249 }) 1250 1251 tx, err := wallet.IssueCreateChainTx( 1252 subnetID, 1253 []byte{}, 1254 ids.GenerateTestID(), 1255 []ids.ID{}, 1256 "aaa", 1257 common.WithMemo(memoField), 1258 ) 1259 require.NoError(err) 1260 1261 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1262 require.NoError(err) 1263 return tx, onAcceptState 1264 }, 1265 }, 1266 { 1267 name: "CreateSubnetTx", 1268 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1269 require := require.New(t) 1270 1271 wallet := newWallet(t, env, walletConfig{}) 1272 tx, err := wallet.IssueCreateSubnetTx( 1273 owners, 1274 common.WithMemo(memoField), 1275 ) 1276 require.NoError(err) 1277 1278 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1279 require.NoError(err) 1280 return tx, onAcceptState 1281 }, 1282 }, 1283 { 1284 name: "ImportTx", 1285 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1286 require := require.New(t) 1287 1288 var ( 1289 sourceChain = env.ctx.XChainID 1290 sourceKey = genesistest.DefaultFundedKeys[1] 1291 sourceAmount = 10 * units.Avax 1292 ) 1293 1294 sharedMemory := fundedSharedMemory( 1295 t, 1296 env, 1297 sourceKey, 1298 sourceChain, 1299 map[ids.ID]uint64{ 1300 env.ctx.AVAXAssetID: sourceAmount, 1301 }, 1302 rand.NewSource(0), 1303 ) 1304 env.msm.SharedMemory = sharedMemory 1305 1306 wallet := newWallet(t, env, walletConfig{ 1307 chainIDs: []ids.ID{sourceChain}, 1308 }) 1309 1310 tx, err := wallet.IssueImportTx( 1311 sourceChain, 1312 owners, 1313 common.WithMemo(memoField), 1314 ) 1315 require.NoError(err) 1316 1317 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1318 require.NoError(err) 1319 return tx, onAcceptState 1320 }, 1321 }, 1322 { 1323 name: "ExportTx", 1324 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1325 require := require.New(t) 1326 1327 wallet := newWallet(t, env, walletConfig{}) 1328 tx, err := wallet.IssueExportTx( 1329 env.ctx.XChainID, 1330 []*avax.TransferableOutput{{ 1331 Asset: avax.Asset{ID: env.ctx.AVAXAssetID}, 1332 Out: &secp256k1fx.TransferOutput{ 1333 Amt: units.Avax, 1334 OutputOwners: *owners, 1335 }, 1336 }}, 1337 common.WithMemo(memoField), 1338 ) 1339 require.NoError(err) 1340 1341 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1342 require.NoError(err) 1343 return tx, onAcceptState 1344 }, 1345 }, 1346 { 1347 name: "RemoveSubnetValidatorTx", 1348 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1349 require := require.New(t) 1350 1351 var primaryValidator *state.Staker 1352 it, err := env.state.GetCurrentStakerIterator() 1353 require.NoError(err) 1354 for it.Next() { 1355 staker := it.Value() 1356 if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority { 1357 continue 1358 } 1359 primaryValidator = staker 1360 break 1361 } 1362 it.Release() 1363 1364 endTime := primaryValidator.EndTime 1365 1366 subnetID := testSubnet1.ID() 1367 wallet := newWallet(t, env, walletConfig{ 1368 subnetIDs: []ids.ID{subnetID}, 1369 }) 1370 subnetValTx, err := wallet.IssueAddSubnetValidatorTx( 1371 &txs.SubnetValidator{ 1372 Validator: txs.Validator{ 1373 NodeID: primaryValidator.NodeID, 1374 Start: 0, 1375 End: uint64(endTime.Unix()), 1376 Wght: genesistest.DefaultValidatorWeight, 1377 }, 1378 Subnet: subnetID, 1379 }, 1380 ) 1381 require.NoError(err) 1382 1383 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1384 require.NoError(err) 1385 1386 feeCalculator := state.PickFeeCalculator(env.config, onAcceptState) 1387 require.NoError(subnetValTx.Unsigned.Visit(&StandardTxExecutor{ 1388 Backend: &env.backend, 1389 State: onAcceptState, 1390 FeeCalculator: feeCalculator, 1391 Tx: subnetValTx, 1392 })) 1393 1394 tx, err := wallet.IssueRemoveSubnetValidatorTx( 1395 primaryValidator.NodeID, 1396 subnetID, 1397 common.WithMemo(memoField), 1398 ) 1399 require.NoError(err) 1400 return tx, onAcceptState 1401 }, 1402 }, 1403 { 1404 name: "TransformSubnetTx", 1405 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1406 require := require.New(t) 1407 1408 subnetID := testSubnet1.ID() 1409 wallet := newWallet(t, env, walletConfig{ 1410 subnetIDs: []ids.ID{subnetID}, 1411 }) 1412 1413 tx, err := wallet.IssueTransformSubnetTx( 1414 subnetID, // subnetID 1415 ids.GenerateTestID(), // assetID 1416 10, // initial supply 1417 10, // max supply 1418 0, // min consumption rate 1419 reward.PercentDenominator, // max consumption rate 1420 2, // min validator stake 1421 10, // max validator stake 1422 time.Minute, // min stake duration 1423 time.Hour, // max stake duration 1424 1, // min delegation fees 1425 10, // min delegator stake 1426 1, // max validator weight factor 1427 80, // uptime requirement 1428 common.WithMemo(memoField), 1429 ) 1430 require.NoError(err) 1431 1432 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1433 require.NoError(err) 1434 return tx, onAcceptState 1435 }, 1436 }, 1437 { 1438 name: "AddPermissionlessValidatorTx", 1439 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1440 require := require.New(t) 1441 var ( 1442 nodeID = ids.GenerateTestNodeID() 1443 chainTime = env.state.GetTimestamp() 1444 endTime = chainTime.Add(defaultMaxStakingDuration) 1445 ) 1446 sk, err := bls.NewSecretKey() 1447 require.NoError(err) 1448 1449 wallet := newWallet(t, env, walletConfig{}) 1450 tx, err := wallet.IssueAddPermissionlessValidatorTx( 1451 &txs.SubnetValidator{ 1452 Validator: txs.Validator{ 1453 NodeID: nodeID, 1454 Start: 0, 1455 End: uint64(endTime.Unix()), 1456 Wght: env.config.MinValidatorStake, 1457 }, 1458 Subnet: constants.PrimaryNetworkID, 1459 }, 1460 signer.NewProofOfPossession(sk), 1461 env.ctx.AVAXAssetID, 1462 owners, 1463 owners, 1464 reward.PercentDenominator, 1465 common.WithMemo(memoField), 1466 ) 1467 require.NoError(err) 1468 1469 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1470 require.NoError(err) 1471 return tx, onAcceptState 1472 }, 1473 }, 1474 { 1475 name: "AddPermissionlessDelegatorTx", 1476 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1477 require := require.New(t) 1478 1479 var primaryValidator *state.Staker 1480 it, err := env.state.GetCurrentStakerIterator() 1481 require.NoError(err) 1482 for it.Next() { 1483 staker := it.Value() 1484 if staker.Priority != txs.PrimaryNetworkValidatorCurrentPriority { 1485 continue 1486 } 1487 primaryValidator = staker 1488 break 1489 } 1490 it.Release() 1491 1492 wallet := newWallet(t, env, walletConfig{}) 1493 tx, err := wallet.IssueAddPermissionlessDelegatorTx( 1494 &txs.SubnetValidator{ 1495 Validator: txs.Validator{ 1496 NodeID: primaryValidator.NodeID, 1497 Start: 0, 1498 End: uint64(primaryValidator.EndTime.Unix()), 1499 Wght: defaultMinValidatorStake, 1500 }, 1501 Subnet: constants.PrimaryNetworkID, 1502 }, 1503 env.ctx.AVAXAssetID, 1504 owners, 1505 common.WithMemo(memoField), 1506 ) 1507 require.NoError(err) 1508 1509 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1510 require.NoError(err) 1511 return tx, onAcceptState 1512 }, 1513 }, 1514 { 1515 name: "TransferSubnetOwnershipTx", 1516 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1517 require := require.New(t) 1518 1519 subnetID := testSubnet1.ID() 1520 wallet := newWallet(t, env, walletConfig{ 1521 subnetIDs: []ids.ID{subnetID}, 1522 }) 1523 1524 tx, err := wallet.IssueTransferSubnetOwnershipTx( 1525 subnetID, 1526 owners, 1527 common.WithMemo(memoField), 1528 ) 1529 require.NoError(err) 1530 1531 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1532 require.NoError(err) 1533 return tx, onAcceptState 1534 }, 1535 }, 1536 { 1537 name: "BaseTx", 1538 setupTest: func(t *testing.T, env *environment, memoField []byte) (*txs.Tx, state.Diff) { 1539 require := require.New(t) 1540 1541 wallet := newWallet(t, env, walletConfig{}) 1542 tx, err := wallet.IssueBaseTx( 1543 []*avax.TransferableOutput{ 1544 { 1545 Asset: avax.Asset{ID: env.ctx.AVAXAssetID}, 1546 Out: &secp256k1fx.TransferOutput{ 1547 Amt: 1, 1548 OutputOwners: secp256k1fx.OutputOwners{ 1549 Threshold: 1, 1550 Addrs: []ids.ShortID{ids.ShortEmpty}, 1551 }, 1552 }, 1553 }, 1554 }, 1555 common.WithMemo(memoField), 1556 ) 1557 require.NoError(err) 1558 1559 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1560 require.NoError(err) 1561 return tx, onAcceptState 1562 }, 1563 }, 1564 } 1565 1566 for _, tt := range tests { 1567 t.Run(tt.name, func(t *testing.T) { 1568 require := require.New(t) 1569 1570 env := newEnvironment(t, upgradetest.Durango) 1571 env.ctx.Lock.Lock() 1572 defer env.ctx.Lock.Unlock() 1573 1574 feeCalculator := state.PickFeeCalculator(env.config, env.state) 1575 1576 // Populated memo field should error 1577 tx, onAcceptState := tt.setupTest(t, env, []byte{'m', 'e', 'm', 'o'}) 1578 err := tx.Unsigned.Visit(&StandardTxExecutor{ 1579 Backend: &env.backend, 1580 State: onAcceptState, 1581 FeeCalculator: feeCalculator, 1582 Tx: tx, 1583 }) 1584 require.ErrorIs(err, avax.ErrMemoTooLarge) 1585 1586 // Empty memo field should not error 1587 tx, onAcceptState = tt.setupTest(t, env, []byte{}) 1588 require.NoError(tx.Unsigned.Visit(&StandardTxExecutor{ 1589 Backend: &env.backend, 1590 State: onAcceptState, 1591 FeeCalculator: feeCalculator, 1592 Tx: tx, 1593 })) 1594 }) 1595 } 1596 } 1597 1598 // Verifies that [TransformSubnetTx] is disabled post-Etna 1599 func TestEtnaDisabledTransactions(t *testing.T) { 1600 require := require.New(t) 1601 1602 env := newEnvironment(t, upgradetest.Etna) 1603 env.ctx.Lock.Lock() 1604 defer env.ctx.Lock.Unlock() 1605 1606 onAcceptState, err := state.NewDiff(env.state.GetLastAccepted(), env) 1607 require.NoError(err) 1608 1609 tx := &txs.Tx{ 1610 Unsigned: &txs.TransformSubnetTx{}, 1611 } 1612 1613 err = tx.Unsigned.Visit(&StandardTxExecutor{ 1614 Backend: &env.backend, 1615 State: onAcceptState, 1616 Tx: tx, 1617 }) 1618 require.ErrorIs(err, errTransformSubnetTxPostEtna) 1619 } 1620 1621 // Returns a RemoveSubnetValidatorTx that passes syntactic verification. 1622 // Memo field is empty as required post Durango activation 1623 func newRemoveSubnetValidatorTx(t *testing.T) (*txs.RemoveSubnetValidatorTx, *txs.Tx) { 1624 t.Helper() 1625 1626 creds := []verify.Verifiable{ 1627 &secp256k1fx.Credential{ 1628 Sigs: make([][65]byte, 1), 1629 }, 1630 &secp256k1fx.Credential{ 1631 Sigs: make([][65]byte, 1), 1632 }, 1633 } 1634 unsignedTx := &txs.RemoveSubnetValidatorTx{ 1635 BaseTx: txs.BaseTx{ 1636 BaseTx: avax.BaseTx{ 1637 Ins: []*avax.TransferableInput{{ 1638 UTXOID: avax.UTXOID{ 1639 TxID: ids.GenerateTestID(), 1640 }, 1641 Asset: avax.Asset{ 1642 ID: ids.GenerateTestID(), 1643 }, 1644 In: &secp256k1fx.TransferInput{ 1645 Amt: 1, 1646 Input: secp256k1fx.Input{ 1647 SigIndices: []uint32{0, 1}, 1648 }, 1649 }, 1650 }}, 1651 Outs: []*avax.TransferableOutput{ 1652 { 1653 Asset: avax.Asset{ 1654 ID: ids.GenerateTestID(), 1655 }, 1656 Out: &secp256k1fx.TransferOutput{ 1657 Amt: 1, 1658 OutputOwners: secp256k1fx.OutputOwners{ 1659 Threshold: 1, 1660 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 1661 }, 1662 }, 1663 }, 1664 }, 1665 }, 1666 }, 1667 Subnet: ids.GenerateTestID(), 1668 NodeID: ids.GenerateTestNodeID(), 1669 SubnetAuth: &secp256k1fx.Credential{ 1670 Sigs: make([][65]byte, 1), 1671 }, 1672 } 1673 tx := &txs.Tx{ 1674 Unsigned: unsignedTx, 1675 Creds: creds, 1676 } 1677 require.NoError(t, tx.Initialize(txs.Codec)) 1678 return unsignedTx, tx 1679 } 1680 1681 // mock implementations that can be used in tests 1682 // for verifying RemoveSubnetValidatorTx. 1683 type removeSubnetValidatorTxVerifyEnv struct { 1684 latestForkTime time.Time 1685 fx *fxmock.Fx 1686 flowChecker *utxomock.Verifier 1687 unsignedTx *txs.RemoveSubnetValidatorTx 1688 tx *txs.Tx 1689 state *state.MockDiff 1690 staker *state.Staker 1691 } 1692 1693 // Returns mock implementations that can be used in tests 1694 // for verifying RemoveSubnetValidatorTx. 1695 func newValidRemoveSubnetValidatorTxVerifyEnv(t *testing.T, ctrl *gomock.Controller) removeSubnetValidatorTxVerifyEnv { 1696 t.Helper() 1697 1698 now := time.Now() 1699 mockFx := fxmock.NewFx(ctrl) 1700 mockFlowChecker := utxomock.NewVerifier(ctrl) 1701 unsignedTx, tx := newRemoveSubnetValidatorTx(t) 1702 mockState := state.NewMockDiff(ctrl) 1703 return removeSubnetValidatorTxVerifyEnv{ 1704 latestForkTime: now, 1705 fx: mockFx, 1706 flowChecker: mockFlowChecker, 1707 unsignedTx: unsignedTx, 1708 tx: tx, 1709 state: mockState, 1710 staker: &state.Staker{ 1711 TxID: ids.GenerateTestID(), 1712 NodeID: ids.GenerateTestNodeID(), 1713 Priority: txs.SubnetPermissionedValidatorCurrentPriority, 1714 }, 1715 } 1716 } 1717 1718 func TestStandardExecutorRemoveSubnetValidatorTx(t *testing.T) { 1719 type test struct { 1720 name string 1721 newExecutor func(*gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) 1722 expectedErr error 1723 } 1724 1725 tests := []test{ 1726 { 1727 name: "valid tx", 1728 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1729 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1730 1731 // Set dependency expectations. 1732 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1733 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil).Times(1) 1734 subnetOwner := fxmock.NewOwner(ctrl) 1735 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) 1736 env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1) 1737 env.flowChecker.EXPECT().VerifySpend( 1738 env.unsignedTx, env.state, env.unsignedTx.Ins, env.unsignedTx.Outs, env.tx.Creds[:len(env.tx.Creds)-1], gomock.Any(), 1739 ).Return(nil).Times(1) 1740 env.state.EXPECT().DeleteCurrentValidator(env.staker) 1741 env.state.EXPECT().DeleteUTXO(gomock.Any()).Times(len(env.unsignedTx.Ins)) 1742 env.state.EXPECT().AddUTXO(gomock.Any()).Times(len(env.unsignedTx.Outs)) 1743 1744 cfg := &config.Config{ 1745 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1746 } 1747 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1748 e := &StandardTxExecutor{ 1749 Backend: &Backend{ 1750 Config: cfg, 1751 Bootstrapped: &utils.Atomic[bool]{}, 1752 Fx: env.fx, 1753 FlowChecker: env.flowChecker, 1754 Ctx: &snow.Context{}, 1755 }, 1756 FeeCalculator: feeCalculator, 1757 Tx: env.tx, 1758 State: env.state, 1759 } 1760 e.Bootstrapped.Set(true) 1761 return env.unsignedTx, e 1762 }, 1763 expectedErr: nil, 1764 }, 1765 { 1766 name: "tx fails syntactic verification", 1767 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1768 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1769 // Setting the subnet ID to the Primary Network ID makes the tx fail syntactic verification 1770 env.tx.Unsigned.(*txs.RemoveSubnetValidatorTx).Subnet = constants.PrimaryNetworkID 1771 env.state = state.NewMockDiff(ctrl) 1772 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1773 1774 cfg := &config.Config{ 1775 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1776 } 1777 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1778 e := &StandardTxExecutor{ 1779 Backend: &Backend{ 1780 Config: cfg, 1781 Bootstrapped: &utils.Atomic[bool]{}, 1782 Fx: env.fx, 1783 FlowChecker: env.flowChecker, 1784 Ctx: &snow.Context{}, 1785 }, 1786 FeeCalculator: feeCalculator, 1787 Tx: env.tx, 1788 State: env.state, 1789 } 1790 e.Bootstrapped.Set(true) 1791 return env.unsignedTx, e 1792 }, 1793 expectedErr: txs.ErrRemovePrimaryNetworkValidator, 1794 }, 1795 { 1796 name: "node isn't a validator of the subnet", 1797 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1798 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1799 env.state = state.NewMockDiff(ctrl) 1800 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1801 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(nil, database.ErrNotFound) 1802 env.state.EXPECT().GetPendingValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(nil, database.ErrNotFound) 1803 1804 cfg := &config.Config{ 1805 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1806 } 1807 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1808 e := &StandardTxExecutor{ 1809 Backend: &Backend{ 1810 Config: cfg, 1811 Bootstrapped: &utils.Atomic[bool]{}, 1812 Fx: env.fx, 1813 FlowChecker: env.flowChecker, 1814 Ctx: &snow.Context{}, 1815 }, 1816 FeeCalculator: feeCalculator, 1817 Tx: env.tx, 1818 State: env.state, 1819 } 1820 e.Bootstrapped.Set(true) 1821 return env.unsignedTx, e 1822 }, 1823 expectedErr: ErrNotValidator, 1824 }, 1825 { 1826 name: "validator is permissionless", 1827 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1828 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1829 1830 staker := *env.staker 1831 staker.Priority = txs.SubnetPermissionlessValidatorCurrentPriority 1832 1833 // Set dependency expectations. 1834 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1835 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(&staker, nil).Times(1) 1836 1837 cfg := &config.Config{ 1838 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1839 } 1840 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1841 e := &StandardTxExecutor{ 1842 Backend: &Backend{ 1843 Config: cfg, 1844 Bootstrapped: &utils.Atomic[bool]{}, 1845 Fx: env.fx, 1846 FlowChecker: env.flowChecker, 1847 Ctx: &snow.Context{}, 1848 }, 1849 FeeCalculator: feeCalculator, 1850 Tx: env.tx, 1851 State: env.state, 1852 } 1853 e.Bootstrapped.Set(true) 1854 return env.unsignedTx, e 1855 }, 1856 expectedErr: ErrRemovePermissionlessValidator, 1857 }, 1858 { 1859 name: "tx has no credentials", 1860 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1861 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1862 // Remove credentials 1863 env.tx.Creds = nil 1864 env.state = state.NewMockDiff(ctrl) 1865 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1866 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) 1867 1868 cfg := &config.Config{ 1869 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1870 } 1871 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1872 e := &StandardTxExecutor{ 1873 Backend: &Backend{ 1874 Config: cfg, 1875 Bootstrapped: &utils.Atomic[bool]{}, 1876 Fx: env.fx, 1877 FlowChecker: env.flowChecker, 1878 Ctx: &snow.Context{}, 1879 }, 1880 FeeCalculator: feeCalculator, 1881 Tx: env.tx, 1882 State: env.state, 1883 } 1884 e.Bootstrapped.Set(true) 1885 return env.unsignedTx, e 1886 }, 1887 expectedErr: errWrongNumberOfCredentials, 1888 }, 1889 { 1890 name: "can't find subnet", 1891 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1892 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1893 env.state = state.NewMockDiff(ctrl) 1894 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1895 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) 1896 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound) 1897 1898 cfg := &config.Config{ 1899 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1900 } 1901 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1902 e := &StandardTxExecutor{ 1903 Backend: &Backend{ 1904 Config: cfg, 1905 Bootstrapped: &utils.Atomic[bool]{}, 1906 Fx: env.fx, 1907 FlowChecker: env.flowChecker, 1908 Ctx: &snow.Context{}, 1909 }, 1910 FeeCalculator: feeCalculator, 1911 Tx: env.tx, 1912 State: env.state, 1913 } 1914 e.Bootstrapped.Set(true) 1915 return env.unsignedTx, e 1916 }, 1917 expectedErr: database.ErrNotFound, 1918 }, 1919 { 1920 name: "no permission to remove validator", 1921 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1922 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1923 env.state = state.NewMockDiff(ctrl) 1924 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1925 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) 1926 subnetOwner := fxmock.NewOwner(ctrl) 1927 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil) 1928 env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(errTest) 1929 1930 cfg := &config.Config{ 1931 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1932 } 1933 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1934 e := &StandardTxExecutor{ 1935 Backend: &Backend{ 1936 Config: cfg, 1937 Bootstrapped: &utils.Atomic[bool]{}, 1938 Fx: env.fx, 1939 FlowChecker: env.flowChecker, 1940 Ctx: &snow.Context{}, 1941 }, 1942 FeeCalculator: feeCalculator, 1943 Tx: env.tx, 1944 State: env.state, 1945 } 1946 e.Bootstrapped.Set(true) 1947 return env.unsignedTx, e 1948 }, 1949 expectedErr: errUnauthorizedSubnetModification, 1950 }, 1951 { 1952 name: "flow checker failed", 1953 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1954 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1955 env.state = state.NewMockDiff(ctrl) 1956 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1957 env.state.EXPECT().GetCurrentValidator(env.unsignedTx.Subnet, env.unsignedTx.NodeID).Return(env.staker, nil) 1958 subnetOwner := fxmock.NewOwner(ctrl) 1959 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil) 1960 env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil) 1961 env.flowChecker.EXPECT().VerifySpend( 1962 gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), 1963 ).Return(errTest) 1964 1965 cfg := &config.Config{ 1966 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 1967 } 1968 feeCalculator := state.PickFeeCalculator(cfg, env.state) 1969 e := &StandardTxExecutor{ 1970 Backend: &Backend{ 1971 Config: cfg, 1972 Bootstrapped: &utils.Atomic[bool]{}, 1973 Fx: env.fx, 1974 FlowChecker: env.flowChecker, 1975 Ctx: &snow.Context{}, 1976 }, 1977 FeeCalculator: feeCalculator, 1978 Tx: env.tx, 1979 State: env.state, 1980 } 1981 e.Bootstrapped.Set(true) 1982 return env.unsignedTx, e 1983 }, 1984 expectedErr: ErrFlowCheckFailed, 1985 }, 1986 { 1987 name: "attempted to remove subnet validator after subnet manager is set", 1988 newExecutor: func(ctrl *gomock.Controller) (*txs.RemoveSubnetValidatorTx, *StandardTxExecutor) { 1989 env := newValidRemoveSubnetValidatorTxVerifyEnv(t, ctrl) 1990 env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), []byte{'a', 'd', 'd', 'r', 'e', 's', 's'}, nil).AnyTimes() 1991 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 1992 1993 cfg := &config.Config{ 1994 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Etna, env.latestForkTime), 1995 } 1996 e := &StandardTxExecutor{ 1997 Backend: &Backend{ 1998 Config: cfg, 1999 Bootstrapped: &utils.Atomic[bool]{}, 2000 Fx: env.fx, 2001 FlowChecker: env.flowChecker, 2002 Ctx: &snow.Context{}, 2003 }, 2004 Tx: env.tx, 2005 State: env.state, 2006 } 2007 e.Bootstrapped.Set(true) 2008 return env.unsignedTx, e 2009 }, 2010 expectedErr: ErrRemoveValidatorManagedSubnet, 2011 }, 2012 } 2013 2014 for _, tt := range tests { 2015 t.Run(tt.name, func(t *testing.T) { 2016 require := require.New(t) 2017 ctrl := gomock.NewController(t) 2018 2019 unsignedTx, executor := tt.newExecutor(ctrl) 2020 err := executor.RemoveSubnetValidatorTx(unsignedTx) 2021 require.ErrorIs(err, tt.expectedErr) 2022 }) 2023 } 2024 } 2025 2026 // Returns a TransformSubnetTx that passes syntactic verification. 2027 // Memo field is empty as required post Durango activation 2028 func newTransformSubnetTx(t *testing.T) (*txs.TransformSubnetTx, *txs.Tx) { 2029 t.Helper() 2030 2031 creds := []verify.Verifiable{ 2032 &secp256k1fx.Credential{ 2033 Sigs: make([][65]byte, 1), 2034 }, 2035 &secp256k1fx.Credential{ 2036 Sigs: make([][65]byte, 1), 2037 }, 2038 } 2039 unsignedTx := &txs.TransformSubnetTx{ 2040 BaseTx: txs.BaseTx{ 2041 BaseTx: avax.BaseTx{ 2042 Ins: []*avax.TransferableInput{{ 2043 UTXOID: avax.UTXOID{ 2044 TxID: ids.GenerateTestID(), 2045 }, 2046 Asset: avax.Asset{ 2047 ID: ids.GenerateTestID(), 2048 }, 2049 In: &secp256k1fx.TransferInput{ 2050 Amt: 1, 2051 Input: secp256k1fx.Input{ 2052 SigIndices: []uint32{0, 1}, 2053 }, 2054 }, 2055 }}, 2056 Outs: []*avax.TransferableOutput{ 2057 { 2058 Asset: avax.Asset{ 2059 ID: ids.GenerateTestID(), 2060 }, 2061 Out: &secp256k1fx.TransferOutput{ 2062 Amt: 1, 2063 OutputOwners: secp256k1fx.OutputOwners{ 2064 Threshold: 1, 2065 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 2066 }, 2067 }, 2068 }, 2069 }, 2070 }, 2071 }, 2072 Subnet: ids.GenerateTestID(), 2073 AssetID: ids.GenerateTestID(), 2074 InitialSupply: 10, 2075 MaximumSupply: 10, 2076 MinConsumptionRate: 0, 2077 MaxConsumptionRate: reward.PercentDenominator, 2078 MinValidatorStake: 2, 2079 MaxValidatorStake: 10, 2080 MinStakeDuration: 1, 2081 MaxStakeDuration: 2, 2082 MinDelegationFee: reward.PercentDenominator, 2083 MinDelegatorStake: 1, 2084 MaxValidatorWeightFactor: 1, 2085 UptimeRequirement: reward.PercentDenominator, 2086 SubnetAuth: &secp256k1fx.Credential{ 2087 Sigs: make([][65]byte, 1), 2088 }, 2089 } 2090 tx := &txs.Tx{ 2091 Unsigned: unsignedTx, 2092 Creds: creds, 2093 } 2094 require.NoError(t, tx.Initialize(txs.Codec)) 2095 return unsignedTx, tx 2096 } 2097 2098 // mock implementations that can be used in tests 2099 // for verifying TransformSubnetTx. 2100 type transformSubnetTxVerifyEnv struct { 2101 latestForkTime time.Time 2102 fx *fxmock.Fx 2103 flowChecker *utxomock.Verifier 2104 unsignedTx *txs.TransformSubnetTx 2105 tx *txs.Tx 2106 state *state.MockDiff 2107 staker *state.Staker 2108 } 2109 2110 // Returns mock implementations that can be used in tests 2111 // for verifying TransformSubnetTx. 2112 func newValidTransformSubnetTxVerifyEnv(t *testing.T, ctrl *gomock.Controller) transformSubnetTxVerifyEnv { 2113 t.Helper() 2114 2115 now := time.Now() 2116 mockFx := fxmock.NewFx(ctrl) 2117 mockFlowChecker := utxomock.NewVerifier(ctrl) 2118 unsignedTx, tx := newTransformSubnetTx(t) 2119 mockState := state.NewMockDiff(ctrl) 2120 return transformSubnetTxVerifyEnv{ 2121 latestForkTime: now, 2122 fx: mockFx, 2123 flowChecker: mockFlowChecker, 2124 unsignedTx: unsignedTx, 2125 tx: tx, 2126 state: mockState, 2127 staker: &state.Staker{ 2128 TxID: ids.GenerateTestID(), 2129 NodeID: ids.GenerateTestNodeID(), 2130 }, 2131 } 2132 } 2133 2134 func TestStandardExecutorTransformSubnetTx(t *testing.T) { 2135 type test struct { 2136 name string 2137 newExecutor func(*gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) 2138 err error 2139 } 2140 2141 tests := []test{ 2142 { 2143 name: "tx fails syntactic verification", 2144 newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { 2145 env := newValidTransformSubnetTxVerifyEnv(t, ctrl) 2146 // Setting the tx to nil makes the tx fail syntactic verification 2147 env.tx.Unsigned = (*txs.TransformSubnetTx)(nil) 2148 env.state = state.NewMockDiff(ctrl) 2149 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 2150 2151 cfg := &config.Config{ 2152 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 2153 } 2154 feeCalculator := state.PickFeeCalculator(cfg, env.state) 2155 e := &StandardTxExecutor{ 2156 Backend: &Backend{ 2157 Config: cfg, 2158 Bootstrapped: &utils.Atomic[bool]{}, 2159 Fx: env.fx, 2160 FlowChecker: env.flowChecker, 2161 Ctx: &snow.Context{}, 2162 }, 2163 FeeCalculator: feeCalculator, 2164 Tx: env.tx, 2165 State: env.state, 2166 } 2167 e.Bootstrapped.Set(true) 2168 return env.unsignedTx, e 2169 }, 2170 err: txs.ErrNilTx, 2171 }, 2172 { 2173 name: "max stake duration too large", 2174 newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { 2175 env := newValidTransformSubnetTxVerifyEnv(t, ctrl) 2176 env.unsignedTx.MaxStakeDuration = math.MaxUint32 2177 env.state = state.NewMockDiff(ctrl) 2178 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 2179 2180 cfg := &config.Config{ 2181 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 2182 } 2183 feeCalculator := state.PickFeeCalculator(cfg, env.state) 2184 e := &StandardTxExecutor{ 2185 Backend: &Backend{ 2186 Config: cfg, 2187 Bootstrapped: &utils.Atomic[bool]{}, 2188 Fx: env.fx, 2189 FlowChecker: env.flowChecker, 2190 Ctx: &snow.Context{}, 2191 }, 2192 FeeCalculator: feeCalculator, 2193 Tx: env.tx, 2194 State: env.state, 2195 } 2196 e.Bootstrapped.Set(true) 2197 return env.unsignedTx, e 2198 }, 2199 err: errMaxStakeDurationTooLarge, 2200 }, 2201 { 2202 name: "fail subnet authorization", 2203 newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { 2204 env := newValidTransformSubnetTxVerifyEnv(t, ctrl) 2205 // Remove credentials 2206 env.tx.Creds = nil 2207 env.state = state.NewMockDiff(ctrl) 2208 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 2209 2210 cfg := &config.Config{ 2211 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 2212 MaxStakeDuration: math.MaxInt64, 2213 } 2214 2215 feeCalculator := state.PickFeeCalculator(cfg, env.state) 2216 e := &StandardTxExecutor{ 2217 Backend: &Backend{ 2218 Config: cfg, 2219 Bootstrapped: &utils.Atomic[bool]{}, 2220 Fx: env.fx, 2221 FlowChecker: env.flowChecker, 2222 Ctx: &snow.Context{}, 2223 }, 2224 FeeCalculator: feeCalculator, 2225 Tx: env.tx, 2226 State: env.state, 2227 } 2228 e.Bootstrapped.Set(true) 2229 return env.unsignedTx, e 2230 }, 2231 err: errWrongNumberOfCredentials, 2232 }, 2233 { 2234 name: "flow checker failed", 2235 newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { 2236 env := newValidTransformSubnetTxVerifyEnv(t, ctrl) 2237 env.state = state.NewMockDiff(ctrl) 2238 subnetOwner := fxmock.NewOwner(ctrl) 2239 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 2240 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil) 2241 env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.Empty, nil, database.ErrNotFound).Times(1) 2242 env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1) 2243 env.fx.EXPECT().VerifyPermission(gomock.Any(), env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil) 2244 env.flowChecker.EXPECT().VerifySpend( 2245 gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any(), 2246 ).Return(ErrFlowCheckFailed) 2247 2248 cfg := &config.Config{ 2249 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 2250 MaxStakeDuration: math.MaxInt64, 2251 } 2252 2253 feeCalculator := state.PickFeeCalculator(cfg, env.state) 2254 e := &StandardTxExecutor{ 2255 Backend: &Backend{ 2256 Config: cfg, 2257 Bootstrapped: &utils.Atomic[bool]{}, 2258 Fx: env.fx, 2259 FlowChecker: env.flowChecker, 2260 Ctx: &snow.Context{}, 2261 }, 2262 FeeCalculator: feeCalculator, 2263 Tx: env.tx, 2264 State: env.state, 2265 } 2266 e.Bootstrapped.Set(true) 2267 return env.unsignedTx, e 2268 }, 2269 err: ErrFlowCheckFailed, 2270 }, 2271 { 2272 name: "invalid if subnet manager is set", 2273 newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { 2274 env := newValidTransformSubnetTxVerifyEnv(t, ctrl) 2275 2276 // Set dependency expectations. 2277 subnetOwner := fxmock.NewOwner(ctrl) 2278 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 2279 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) 2280 env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.GenerateTestID(), make([]byte, 20), nil) 2281 env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1) 2282 env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1) 2283 2284 cfg := &config.Config{ 2285 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 2286 MaxStakeDuration: math.MaxInt64, 2287 } 2288 feeCalculator := state.PickFeeCalculator(cfg, env.state) 2289 e := &StandardTxExecutor{ 2290 Backend: &Backend{ 2291 Config: cfg, 2292 Bootstrapped: &utils.Atomic[bool]{}, 2293 Fx: env.fx, 2294 FlowChecker: env.flowChecker, 2295 Ctx: &snow.Context{}, 2296 }, 2297 FeeCalculator: feeCalculator, 2298 Tx: env.tx, 2299 State: env.state, 2300 } 2301 e.Bootstrapped.Set(true) 2302 return env.unsignedTx, e 2303 }, 2304 err: errIsImmutable, 2305 }, 2306 { 2307 name: "valid tx", 2308 newExecutor: func(ctrl *gomock.Controller) (*txs.TransformSubnetTx, *StandardTxExecutor) { 2309 env := newValidTransformSubnetTxVerifyEnv(t, ctrl) 2310 2311 // Set dependency expectations. 2312 subnetOwner := fxmock.NewOwner(ctrl) 2313 env.state.EXPECT().GetTimestamp().Return(env.latestForkTime).AnyTimes() 2314 env.state.EXPECT().GetSubnetOwner(env.unsignedTx.Subnet).Return(subnetOwner, nil).Times(1) 2315 env.state.EXPECT().GetSubnetManager(env.unsignedTx.Subnet).Return(ids.Empty, nil, database.ErrNotFound).Times(1) 2316 env.state.EXPECT().GetSubnetTransformation(env.unsignedTx.Subnet).Return(nil, database.ErrNotFound).Times(1) 2317 env.fx.EXPECT().VerifyPermission(env.unsignedTx, env.unsignedTx.SubnetAuth, env.tx.Creds[len(env.tx.Creds)-1], subnetOwner).Return(nil).Times(1) 2318 env.flowChecker.EXPECT().VerifySpend( 2319 env.unsignedTx, env.state, env.unsignedTx.Ins, env.unsignedTx.Outs, env.tx.Creds[:len(env.tx.Creds)-1], gomock.Any(), 2320 ).Return(nil).Times(1) 2321 env.state.EXPECT().AddSubnetTransformation(env.tx) 2322 env.state.EXPECT().SetCurrentSupply(env.unsignedTx.Subnet, env.unsignedTx.InitialSupply) 2323 env.state.EXPECT().DeleteUTXO(gomock.Any()).Times(len(env.unsignedTx.Ins)) 2324 env.state.EXPECT().AddUTXO(gomock.Any()).Times(len(env.unsignedTx.Outs)) 2325 2326 cfg := &config.Config{ 2327 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, env.latestForkTime), 2328 MaxStakeDuration: math.MaxInt64, 2329 } 2330 2331 feeCalculator := state.PickFeeCalculator(cfg, env.state) 2332 e := &StandardTxExecutor{ 2333 Backend: &Backend{ 2334 Config: cfg, 2335 Bootstrapped: &utils.Atomic[bool]{}, 2336 Fx: env.fx, 2337 FlowChecker: env.flowChecker, 2338 Ctx: &snow.Context{}, 2339 }, 2340 FeeCalculator: feeCalculator, 2341 Tx: env.tx, 2342 State: env.state, 2343 } 2344 e.Bootstrapped.Set(true) 2345 return env.unsignedTx, e 2346 }, 2347 err: nil, 2348 }, 2349 } 2350 2351 for _, tt := range tests { 2352 t.Run(tt.name, func(t *testing.T) { 2353 ctrl := gomock.NewController(t) 2354 2355 unsignedTx, executor := tt.newExecutor(ctrl) 2356 err := executor.TransformSubnetTx(unsignedTx) 2357 require.ErrorIs(t, err, tt.err) 2358 }) 2359 } 2360 }