github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/staker_tx_verification_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 "testing" 8 "time" 9 10 "github.com/stretchr/testify/require" 11 "go.uber.org/mock/gomock" 12 13 "github.com/ava-labs/avalanchego/database" 14 "github.com/ava-labs/avalanchego/ids" 15 "github.com/ava-labs/avalanchego/snow" 16 "github.com/ava-labs/avalanchego/snow/snowtest" 17 "github.com/ava-labs/avalanchego/upgrade/upgradetest" 18 "github.com/ava-labs/avalanchego/utils" 19 "github.com/ava-labs/avalanchego/utils/constants" 20 "github.com/ava-labs/avalanchego/utils/timer/mockable" 21 "github.com/ava-labs/avalanchego/vms/components/avax" 22 "github.com/ava-labs/avalanchego/vms/components/verify" 23 "github.com/ava-labs/avalanchego/vms/platformvm/config" 24 "github.com/ava-labs/avalanchego/vms/platformvm/state" 25 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 26 "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" 27 "github.com/ava-labs/avalanchego/vms/platformvm/utxo/utxomock" 28 "github.com/ava-labs/avalanchego/vms/secp256k1fx" 29 ) 30 31 func TestVerifyAddPermissionlessValidatorTx(t *testing.T) { 32 ctx := snowtest.Context(t, snowtest.PChainID) 33 34 type test struct { 35 name string 36 backendF func(*gomock.Controller) *Backend 37 stateF func(*gomock.Controller) state.Chain 38 sTxF func() *txs.Tx 39 txF func() *txs.AddPermissionlessValidatorTx 40 expectedErr error 41 } 42 43 var ( 44 // in the following tests we set the fork time for forks we want active 45 // to activeForkTime, which is ensured to be before any other time related 46 // quantity (based on now) 47 activeForkTime = time.Unix(0, 0) 48 now = time.Now().Truncate(time.Second) // after activeForkTime 49 50 subnetID = ids.GenerateTestID() 51 customAssetID = ids.GenerateTestID() 52 unsignedTransformTx = &txs.TransformSubnetTx{ 53 AssetID: customAssetID, 54 MinValidatorStake: 1, 55 MaxValidatorStake: 2, 56 MinStakeDuration: 3, 57 MaxStakeDuration: 4, 58 MinDelegationFee: 5, 59 } 60 transformTx = txs.Tx{ 61 Unsigned: unsignedTransformTx, 62 Creds: []verify.Verifiable{}, 63 } 64 // This tx already passed syntactic verification. 65 startTime = now.Add(time.Second) 66 endTime = startTime.Add(time.Second * time.Duration(unsignedTransformTx.MinStakeDuration)) 67 verifiedTx = txs.AddPermissionlessValidatorTx{ 68 BaseTx: txs.BaseTx{ 69 SyntacticallyVerified: true, 70 BaseTx: avax.BaseTx{ 71 NetworkID: ctx.NetworkID, 72 BlockchainID: ctx.ChainID, 73 Outs: []*avax.TransferableOutput{}, 74 Ins: []*avax.TransferableInput{}, 75 }, 76 }, 77 Validator: txs.Validator{ 78 NodeID: ids.GenerateTestNodeID(), 79 // Note: [Start] is not set here as it will be ignored 80 // Post-Durango in favor of the current chain time 81 End: uint64(endTime.Unix()), 82 Wght: unsignedTransformTx.MinValidatorStake, 83 }, 84 Subnet: subnetID, 85 StakeOuts: []*avax.TransferableOutput{ 86 { 87 Asset: avax.Asset{ 88 ID: customAssetID, 89 }, 90 }, 91 }, 92 ValidatorRewardsOwner: &secp256k1fx.OutputOwners{ 93 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 94 Threshold: 1, 95 }, 96 DelegatorRewardsOwner: &secp256k1fx.OutputOwners{ 97 Addrs: []ids.ShortID{ids.GenerateTestShortID()}, 98 Threshold: 1, 99 }, 100 DelegationShares: 20_000, 101 } 102 verifiedSignedTx = txs.Tx{ 103 Unsigned: &verifiedTx, 104 Creds: []verify.Verifiable{}, 105 } 106 ) 107 verifiedSignedTx.SetBytes([]byte{1}, []byte{2}) 108 109 tests := []test{ 110 { 111 name: "fail syntactic verification", 112 backendF: func(*gomock.Controller) *Backend { 113 return &Backend{ 114 Ctx: ctx, 115 Config: &config.Config{ 116 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 117 }, 118 } 119 }, 120 121 stateF: func(ctrl *gomock.Controller) state.Chain { 122 mockState := state.NewMockChain(ctrl) 123 mockState.EXPECT().GetTimestamp().Return(now) 124 return mockState 125 }, 126 sTxF: func() *txs.Tx { 127 return nil 128 }, 129 txF: func() *txs.AddPermissionlessValidatorTx { 130 return nil 131 }, 132 expectedErr: txs.ErrNilSignedTx, 133 }, 134 { 135 name: "not bootstrapped", 136 backendF: func(*gomock.Controller) *Backend { 137 return &Backend{ 138 Ctx: ctx, 139 Config: &config.Config{ 140 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 141 }, 142 Bootstrapped: &utils.Atomic[bool]{}, 143 } 144 }, 145 stateF: func(ctrl *gomock.Controller) state.Chain { 146 mockState := state.NewMockChain(ctrl) 147 mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after Durango fork activation since now.After(activeForkTime) 148 return mockState 149 }, 150 sTxF: func() *txs.Tx { 151 return &verifiedSignedTx 152 }, 153 txF: func() *txs.AddPermissionlessValidatorTx { 154 return &txs.AddPermissionlessValidatorTx{} 155 }, 156 expectedErr: nil, 157 }, 158 { 159 name: "start time too early", 160 backendF: func(*gomock.Controller) *Backend { 161 bootstrapped := &utils.Atomic[bool]{} 162 bootstrapped.Set(true) 163 return &Backend{ 164 Ctx: ctx, 165 Config: &config.Config{ 166 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Cortina, activeForkTime), 167 }, 168 Bootstrapped: bootstrapped, 169 } 170 }, 171 stateF: func(ctrl *gomock.Controller) state.Chain { 172 state := state.NewMockChain(ctrl) 173 state.EXPECT().GetTimestamp().Return(verifiedTx.StartTime()).Times(2) 174 return state 175 }, 176 sTxF: func() *txs.Tx { 177 return &verifiedSignedTx 178 }, 179 txF: func() *txs.AddPermissionlessValidatorTx { 180 return &verifiedTx 181 }, 182 expectedErr: ErrTimestampNotBeforeStartTime, 183 }, 184 { 185 name: "weight too low", 186 backendF: func(*gomock.Controller) *Backend { 187 bootstrapped := &utils.Atomic[bool]{} 188 bootstrapped.Set(true) 189 return &Backend{ 190 Ctx: ctx, 191 Config: &config.Config{ 192 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 193 }, 194 Bootstrapped: bootstrapped, 195 } 196 }, 197 stateF: func(ctrl *gomock.Controller) state.Chain { 198 state := state.NewMockChain(ctrl) 199 state.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 200 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 201 return state 202 }, 203 sTxF: func() *txs.Tx { 204 return &verifiedSignedTx 205 }, 206 txF: func() *txs.AddPermissionlessValidatorTx { 207 tx := verifiedTx // Note that this copies [verifiedTx] 208 tx.Validator.Wght = unsignedTransformTx.MinValidatorStake - 1 209 return &tx 210 }, 211 expectedErr: ErrWeightTooSmall, 212 }, 213 { 214 name: "weight too high", 215 backendF: func(*gomock.Controller) *Backend { 216 bootstrapped := &utils.Atomic[bool]{} 217 bootstrapped.Set(true) 218 return &Backend{ 219 Ctx: ctx, 220 Config: &config.Config{ 221 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 222 }, 223 Bootstrapped: bootstrapped, 224 } 225 }, 226 stateF: func(ctrl *gomock.Controller) state.Chain { 227 state := state.NewMockChain(ctrl) 228 state.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 229 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 230 return state 231 }, 232 sTxF: func() *txs.Tx { 233 return &verifiedSignedTx 234 }, 235 txF: func() *txs.AddPermissionlessValidatorTx { 236 tx := verifiedTx // Note that this copies [verifiedTx] 237 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake + 1 238 return &tx 239 }, 240 expectedErr: ErrWeightTooLarge, 241 }, 242 { 243 name: "insufficient delegation fee", 244 backendF: func(*gomock.Controller) *Backend { 245 bootstrapped := &utils.Atomic[bool]{} 246 bootstrapped.Set(true) 247 return &Backend{ 248 Ctx: ctx, 249 Config: &config.Config{ 250 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 251 }, 252 Bootstrapped: bootstrapped, 253 } 254 }, 255 stateF: func(ctrl *gomock.Controller) state.Chain { 256 state := state.NewMockChain(ctrl) 257 state.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 258 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 259 return state 260 }, 261 sTxF: func() *txs.Tx { 262 return &verifiedSignedTx 263 }, 264 txF: func() *txs.AddPermissionlessValidatorTx { 265 tx := verifiedTx // Note that this copies [verifiedTx] 266 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake 267 tx.DelegationShares = unsignedTransformTx.MinDelegationFee - 1 268 return &tx 269 }, 270 expectedErr: ErrInsufficientDelegationFee, 271 }, 272 { 273 name: "duration too short", 274 backendF: func(*gomock.Controller) *Backend { 275 bootstrapped := &utils.Atomic[bool]{} 276 bootstrapped.Set(true) 277 return &Backend{ 278 Ctx: ctx, 279 Config: &config.Config{ 280 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 281 }, 282 Bootstrapped: bootstrapped, 283 } 284 }, 285 stateF: func(ctrl *gomock.Controller) state.Chain { 286 state := state.NewMockChain(ctrl) 287 state.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 288 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 289 return state 290 }, 291 sTxF: func() *txs.Tx { 292 return &verifiedSignedTx 293 }, 294 txF: func() *txs.AddPermissionlessValidatorTx { 295 tx := verifiedTx // Note that this copies [verifiedTx] 296 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake 297 tx.DelegationShares = unsignedTransformTx.MinDelegationFee 298 299 // Note the duration is 1 less than the minimum 300 tx.Validator.End = tx.Validator.Start + uint64(unsignedTransformTx.MinStakeDuration) - 1 301 return &tx 302 }, 303 expectedErr: ErrStakeTooShort, 304 }, 305 { 306 name: "duration too long", 307 backendF: func(*gomock.Controller) *Backend { 308 bootstrapped := &utils.Atomic[bool]{} 309 bootstrapped.Set(true) 310 return &Backend{ 311 Ctx: ctx, 312 Config: &config.Config{ 313 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 314 }, 315 Bootstrapped: bootstrapped, 316 } 317 }, 318 stateF: func(ctrl *gomock.Controller) state.Chain { 319 state := state.NewMockChain(ctrl) 320 state.EXPECT().GetTimestamp().Return(time.Unix(1, 0)).Times(2) // chain time is after fork activation since time.Unix(1, 0).After(activeForkTime) 321 state.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 322 return state 323 }, 324 sTxF: func() *txs.Tx { 325 return &verifiedSignedTx 326 }, 327 txF: func() *txs.AddPermissionlessValidatorTx { 328 tx := verifiedTx // Note that this copies [verifiedTx] 329 tx.Validator.Wght = unsignedTransformTx.MaxValidatorStake 330 tx.DelegationShares = unsignedTransformTx.MinDelegationFee 331 332 // Note the duration is more than the maximum 333 tx.Validator.End = uint64(unsignedTransformTx.MaxStakeDuration) + 2 334 return &tx 335 }, 336 expectedErr: ErrStakeTooLong, 337 }, 338 { 339 name: "wrong assetID", 340 backendF: func(*gomock.Controller) *Backend { 341 bootstrapped := &utils.Atomic[bool]{} 342 bootstrapped.Set(true) 343 return &Backend{ 344 Ctx: ctx, 345 Config: &config.Config{ 346 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 347 }, 348 Bootstrapped: bootstrapped, 349 } 350 }, 351 stateF: func(ctrl *gomock.Controller) state.Chain { 352 mockState := state.NewMockChain(ctrl) 353 mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 354 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 355 return mockState 356 }, 357 sTxF: func() *txs.Tx { 358 return &verifiedSignedTx 359 }, 360 txF: func() *txs.AddPermissionlessValidatorTx { 361 tx := verifiedTx // Note that this copies [verifiedTx] 362 tx.StakeOuts = []*avax.TransferableOutput{ 363 { 364 Asset: avax.Asset{ 365 ID: ids.GenerateTestID(), 366 }, 367 }, 368 } 369 return &tx 370 }, 371 expectedErr: ErrWrongStakedAssetID, 372 }, 373 { 374 name: "duplicate validator", 375 backendF: func(*gomock.Controller) *Backend { 376 bootstrapped := &utils.Atomic[bool]{} 377 bootstrapped.Set(true) 378 return &Backend{ 379 Ctx: ctx, 380 Config: &config.Config{ 381 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 382 }, 383 Bootstrapped: bootstrapped, 384 } 385 }, 386 stateF: func(ctrl *gomock.Controller) state.Chain { 387 mockState := state.NewMockChain(ctrl) 388 mockState.EXPECT().GetTimestamp().Return(now).Times(2) // chain time is after latest fork activation since now.After(activeForkTime) 389 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 390 // State says validator exists 391 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, nil) 392 return mockState 393 }, 394 sTxF: func() *txs.Tx { 395 return &verifiedSignedTx 396 }, 397 txF: func() *txs.AddPermissionlessValidatorTx { 398 return &verifiedTx 399 }, 400 expectedErr: ErrDuplicateValidator, 401 }, 402 { 403 name: "validator not subset of primary network validator", 404 backendF: func(*gomock.Controller) *Backend { 405 bootstrapped := &utils.Atomic[bool]{} 406 bootstrapped.Set(true) 407 return &Backend{ 408 Ctx: ctx, 409 Config: &config.Config{ 410 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 411 }, 412 Bootstrapped: bootstrapped, 413 } 414 }, 415 stateF: func(ctrl *gomock.Controller) state.Chain { 416 mockState := state.NewMockChain(ctrl) 417 mockState.EXPECT().GetTimestamp().Return(now).Times(3) // chain time is after latest fork activation since now.After(activeForkTime) 418 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 419 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 420 mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 421 // Validator time isn't subset of primary network validator time 422 primaryNetworkVdr := &state.Staker{ 423 EndTime: verifiedTx.EndTime().Add(-1 * time.Second), 424 } 425 mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) 426 return mockState 427 }, 428 sTxF: func() *txs.Tx { 429 return &verifiedSignedTx 430 }, 431 txF: func() *txs.AddPermissionlessValidatorTx { 432 return &verifiedTx 433 }, 434 expectedErr: ErrPeriodMismatch, 435 }, 436 { 437 name: "flow check fails", 438 backendF: func(ctrl *gomock.Controller) *Backend { 439 bootstrapped := &utils.Atomic[bool]{} 440 bootstrapped.Set(true) 441 442 flowChecker := utxomock.NewVerifier(ctrl) 443 flowChecker.EXPECT().VerifySpend( 444 gomock.Any(), 445 gomock.Any(), 446 gomock.Any(), 447 gomock.Any(), 448 gomock.Any(), 449 gomock.Any(), 450 ).Return(ErrFlowCheckFailed) 451 452 return &Backend{ 453 FlowChecker: flowChecker, 454 Config: &config.Config{ 455 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 456 StaticFeeConfig: fee.StaticConfig{ 457 AddSubnetValidatorFee: 1, 458 }, 459 }, 460 Ctx: ctx, 461 Bootstrapped: bootstrapped, 462 } 463 }, 464 stateF: func(ctrl *gomock.Controller) state.Chain { 465 mockState := state.NewMockChain(ctrl) 466 mockState.EXPECT().GetTimestamp().Return(now).Times(3) // chain time is after latest fork activation since now.After(activeForkTime) 467 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 468 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 469 mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 470 primaryNetworkVdr := &state.Staker{ 471 EndTime: mockable.MaxTime, 472 } 473 mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) 474 return mockState 475 }, 476 sTxF: func() *txs.Tx { 477 return &verifiedSignedTx 478 }, 479 txF: func() *txs.AddPermissionlessValidatorTx { 480 return &verifiedTx 481 }, 482 expectedErr: ErrFlowCheckFailed, 483 }, 484 { 485 name: "success", 486 backendF: func(ctrl *gomock.Controller) *Backend { 487 bootstrapped := &utils.Atomic[bool]{} 488 bootstrapped.Set(true) 489 490 flowChecker := utxomock.NewVerifier(ctrl) 491 flowChecker.EXPECT().VerifySpend( 492 gomock.Any(), 493 gomock.Any(), 494 gomock.Any(), 495 gomock.Any(), 496 gomock.Any(), 497 gomock.Any(), 498 ).Return(nil) 499 500 return &Backend{ 501 FlowChecker: flowChecker, 502 Config: &config.Config{ 503 UpgradeConfig: upgradetest.GetConfigWithUpgradeTime(upgradetest.Durango, activeForkTime), 504 StaticFeeConfig: fee.StaticConfig{ 505 AddSubnetValidatorFee: 1, 506 }, 507 }, 508 Ctx: ctx, 509 Bootstrapped: bootstrapped, 510 } 511 }, 512 stateF: func(ctrl *gomock.Controller) state.Chain { 513 mockState := state.NewMockChain(ctrl) 514 mockState.EXPECT().GetTimestamp().Return(now).Times(3) // chain time is after Durango fork activation since now.After(activeForkTime) 515 mockState.EXPECT().GetSubnetTransformation(subnetID).Return(&transformTx, nil) 516 mockState.EXPECT().GetCurrentValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 517 mockState.EXPECT().GetPendingValidator(subnetID, verifiedTx.NodeID()).Return(nil, database.ErrNotFound) 518 primaryNetworkVdr := &state.Staker{ 519 EndTime: mockable.MaxTime, 520 } 521 mockState.EXPECT().GetCurrentValidator(constants.PrimaryNetworkID, verifiedTx.NodeID()).Return(primaryNetworkVdr, nil) 522 return mockState 523 }, 524 sTxF: func() *txs.Tx { 525 return &verifiedSignedTx 526 }, 527 txF: func() *txs.AddPermissionlessValidatorTx { 528 return &verifiedTx 529 }, 530 expectedErr: nil, 531 }, 532 } 533 534 for _, tt := range tests { 535 t.Run(tt.name, func(t *testing.T) { 536 ctrl := gomock.NewController(t) 537 538 var ( 539 backend = tt.backendF(ctrl) 540 chain = tt.stateF(ctrl) 541 sTx = tt.sTxF() 542 tx = tt.txF() 543 ) 544 545 feeCalculator := state.PickFeeCalculator(backend.Config, chain) 546 err := verifyAddPermissionlessValidatorTx(backend, feeCalculator, chain, sTx, tx) 547 require.ErrorIs(t, err, tt.expectedErr) 548 }) 549 } 550 } 551 552 func TestGetValidatorRules(t *testing.T) { 553 type test struct { 554 name string 555 subnetID ids.ID 556 backend *Backend 557 chainStateF func(*gomock.Controller) state.Chain 558 expectedRules *addValidatorRules 559 expectedErr error 560 } 561 562 var ( 563 config = &config.Config{ 564 MinValidatorStake: 1, 565 MaxValidatorStake: 2, 566 MinStakeDuration: time.Second, 567 MaxStakeDuration: 2 * time.Second, 568 MinDelegationFee: 1337, 569 } 570 avaxAssetID = ids.GenerateTestID() 571 customAssetID = ids.GenerateTestID() 572 subnetID = ids.GenerateTestID() 573 ) 574 575 tests := []test{ 576 { 577 name: "primary network", 578 subnetID: constants.PrimaryNetworkID, 579 backend: &Backend{ 580 Config: config, 581 Ctx: &snow.Context{ 582 AVAXAssetID: avaxAssetID, 583 }, 584 }, 585 chainStateF: func(*gomock.Controller) state.Chain { 586 return nil 587 }, 588 expectedRules: &addValidatorRules{ 589 assetID: avaxAssetID, 590 minValidatorStake: config.MinValidatorStake, 591 maxValidatorStake: config.MaxValidatorStake, 592 minStakeDuration: config.MinStakeDuration, 593 maxStakeDuration: config.MaxStakeDuration, 594 minDelegationFee: config.MinDelegationFee, 595 }, 596 }, 597 { 598 name: "can't get subnet transformation", 599 subnetID: subnetID, 600 backend: nil, 601 chainStateF: func(ctrl *gomock.Controller) state.Chain { 602 state := state.NewMockChain(ctrl) 603 state.EXPECT().GetSubnetTransformation(subnetID).Return(nil, errTest) 604 return state 605 }, 606 expectedRules: &addValidatorRules{}, 607 expectedErr: errTest, 608 }, 609 { 610 name: "invalid transformation tx", 611 subnetID: subnetID, 612 backend: nil, 613 chainStateF: func(ctrl *gomock.Controller) state.Chain { 614 state := state.NewMockChain(ctrl) 615 tx := &txs.Tx{ 616 Unsigned: &txs.AddDelegatorTx{}, 617 } 618 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 619 return state 620 }, 621 expectedRules: &addValidatorRules{}, 622 expectedErr: ErrIsNotTransformSubnetTx, 623 }, 624 { 625 name: "subnet", 626 subnetID: subnetID, 627 backend: nil, 628 chainStateF: func(ctrl *gomock.Controller) state.Chain { 629 state := state.NewMockChain(ctrl) 630 tx := &txs.Tx{ 631 Unsigned: &txs.TransformSubnetTx{ 632 AssetID: customAssetID, 633 MinValidatorStake: config.MinValidatorStake, 634 MaxValidatorStake: config.MaxValidatorStake, 635 MinStakeDuration: 1337, 636 MaxStakeDuration: 42, 637 MinDelegationFee: config.MinDelegationFee, 638 }, 639 } 640 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 641 return state 642 }, 643 expectedRules: &addValidatorRules{ 644 assetID: customAssetID, 645 minValidatorStake: config.MinValidatorStake, 646 maxValidatorStake: config.MaxValidatorStake, 647 minStakeDuration: 1337 * time.Second, 648 maxStakeDuration: 42 * time.Second, 649 minDelegationFee: config.MinDelegationFee, 650 }, 651 expectedErr: nil, 652 }, 653 } 654 655 for _, tt := range tests { 656 t.Run(tt.name, func(t *testing.T) { 657 require := require.New(t) 658 ctrl := gomock.NewController(t) 659 660 chainState := tt.chainStateF(ctrl) 661 rules, err := getValidatorRules(tt.backend, chainState, tt.subnetID) 662 if tt.expectedErr != nil { 663 require.ErrorIs(err, tt.expectedErr) 664 return 665 } 666 require.NoError(err) 667 require.Equal(tt.expectedRules, rules) 668 }) 669 } 670 } 671 672 func TestGetDelegatorRules(t *testing.T) { 673 type test struct { 674 name string 675 subnetID ids.ID 676 backend *Backend 677 chainStateF func(*gomock.Controller) state.Chain 678 expectedRules *addDelegatorRules 679 expectedErr error 680 } 681 var ( 682 config = &config.Config{ 683 MinDelegatorStake: 1, 684 MaxValidatorStake: 2, 685 MinStakeDuration: time.Second, 686 MaxStakeDuration: 2 * time.Second, 687 } 688 avaxAssetID = ids.GenerateTestID() 689 customAssetID = ids.GenerateTestID() 690 subnetID = ids.GenerateTestID() 691 ) 692 tests := []test{ 693 { 694 name: "primary network", 695 subnetID: constants.PrimaryNetworkID, 696 backend: &Backend{ 697 Config: config, 698 Ctx: &snow.Context{ 699 AVAXAssetID: avaxAssetID, 700 }, 701 }, 702 chainStateF: func(*gomock.Controller) state.Chain { 703 return nil 704 }, 705 expectedRules: &addDelegatorRules{ 706 assetID: avaxAssetID, 707 minDelegatorStake: config.MinDelegatorStake, 708 maxValidatorStake: config.MaxValidatorStake, 709 minStakeDuration: config.MinStakeDuration, 710 maxStakeDuration: config.MaxStakeDuration, 711 maxValidatorWeightFactor: MaxValidatorWeightFactor, 712 }, 713 }, 714 { 715 name: "can't get subnet transformation", 716 subnetID: subnetID, 717 backend: nil, 718 chainStateF: func(ctrl *gomock.Controller) state.Chain { 719 state := state.NewMockChain(ctrl) 720 state.EXPECT().GetSubnetTransformation(subnetID).Return(nil, errTest) 721 return state 722 }, 723 expectedRules: &addDelegatorRules{}, 724 expectedErr: errTest, 725 }, 726 { 727 name: "invalid transformation tx", 728 subnetID: subnetID, 729 backend: nil, 730 chainStateF: func(ctrl *gomock.Controller) state.Chain { 731 state := state.NewMockChain(ctrl) 732 tx := &txs.Tx{ 733 Unsigned: &txs.AddDelegatorTx{}, 734 } 735 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 736 return state 737 }, 738 expectedRules: &addDelegatorRules{}, 739 expectedErr: ErrIsNotTransformSubnetTx, 740 }, 741 { 742 name: "subnet", 743 subnetID: subnetID, 744 backend: nil, 745 chainStateF: func(ctrl *gomock.Controller) state.Chain { 746 state := state.NewMockChain(ctrl) 747 tx := &txs.Tx{ 748 Unsigned: &txs.TransformSubnetTx{ 749 AssetID: customAssetID, 750 MinDelegatorStake: config.MinDelegatorStake, 751 MinValidatorStake: config.MinValidatorStake, 752 MaxValidatorStake: config.MaxValidatorStake, 753 MinStakeDuration: 1337, 754 MaxStakeDuration: 42, 755 MinDelegationFee: config.MinDelegationFee, 756 MaxValidatorWeightFactor: 21, 757 }, 758 } 759 state.EXPECT().GetSubnetTransformation(subnetID).Return(tx, nil) 760 return state 761 }, 762 expectedRules: &addDelegatorRules{ 763 assetID: customAssetID, 764 minDelegatorStake: config.MinDelegatorStake, 765 maxValidatorStake: config.MaxValidatorStake, 766 minStakeDuration: 1337 * time.Second, 767 maxStakeDuration: 42 * time.Second, 768 maxValidatorWeightFactor: 21, 769 }, 770 expectedErr: nil, 771 }, 772 } 773 for _, tt := range tests { 774 t.Run(tt.name, func(t *testing.T) { 775 require := require.New(t) 776 ctrl := gomock.NewController(t) 777 778 chainState := tt.chainStateF(ctrl) 779 rules, err := getDelegatorRules(tt.backend, chainState, tt.subnetID) 780 if tt.expectedErr != nil { 781 require.ErrorIs(err, tt.expectedErr) 782 return 783 } 784 require.NoError(err) 785 require.Equal(tt.expectedRules, rules) 786 }) 787 } 788 }