github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/txs/executor/staker_tx_verification.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 "fmt" 9 "math" 10 "time" 11 12 "github.com/ava-labs/avalanchego/database" 13 "github.com/ava-labs/avalanchego/ids" 14 "github.com/ava-labs/avalanchego/utils/constants" 15 "github.com/ava-labs/avalanchego/vms/components/avax" 16 "github.com/ava-labs/avalanchego/vms/platformvm/state" 17 "github.com/ava-labs/avalanchego/vms/platformvm/txs" 18 "github.com/ava-labs/avalanchego/vms/platformvm/txs/fee" 19 20 safemath "github.com/ava-labs/avalanchego/utils/math" 21 ) 22 23 var ( 24 ErrWeightTooSmall = errors.New("weight of this validator is too low") 25 ErrWeightTooLarge = errors.New("weight of this validator is too large") 26 ErrInsufficientDelegationFee = errors.New("staker charges an insufficient delegation fee") 27 ErrStakeTooShort = errors.New("staking period is too short") 28 ErrStakeTooLong = errors.New("staking period is too long") 29 ErrFlowCheckFailed = errors.New("flow check failed") 30 ErrNotValidator = errors.New("isn't a current or pending validator") 31 ErrRemovePermissionlessValidator = errors.New("attempting to remove permissionless validator") 32 ErrStakeOverflow = errors.New("validator stake exceeds limit") 33 ErrPeriodMismatch = errors.New("proposed staking period is not inside dependant staking period") 34 ErrOverDelegated = errors.New("validator would be over delegated") 35 ErrIsNotTransformSubnetTx = errors.New("is not a transform subnet tx") 36 ErrTimestampNotBeforeStartTime = errors.New("chain timestamp not before start time") 37 ErrAlreadyValidator = errors.New("already a validator") 38 ErrDuplicateValidator = errors.New("duplicate validator") 39 ErrDelegateToPermissionedValidator = errors.New("delegation to permissioned validator") 40 ErrWrongStakedAssetID = errors.New("incorrect staked assetID") 41 ErrDurangoUpgradeNotActive = errors.New("attempting to use a Durango-upgrade feature prior to activation") 42 ErrAddValidatorTxPostDurango = errors.New("AddValidatorTx is not permitted post-Durango") 43 ErrAddDelegatorTxPostDurango = errors.New("AddDelegatorTx is not permitted post-Durango") 44 ErrRemoveValidatorManagedSubnet = errors.New("RemoveSubnetValidatorTx cannot be used to remove a validator from a Subnet with a manager") 45 ) 46 47 // verifySubnetValidatorPrimaryNetworkRequirements verifies the primary 48 // network requirements for [subnetValidator]. An error is returned if they 49 // are not fulfilled. 50 func verifySubnetValidatorPrimaryNetworkRequirements( 51 isDurangoActive bool, 52 chainState state.Chain, 53 subnetValidator txs.Validator, 54 ) error { 55 primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, subnetValidator.NodeID) 56 if err == database.ErrNotFound { 57 return fmt.Errorf( 58 "%s %w of the primary network", 59 subnetValidator.NodeID, 60 ErrNotValidator, 61 ) 62 } 63 if err != nil { 64 return fmt.Errorf( 65 "failed to fetch the primary network validator for %s: %w", 66 subnetValidator.NodeID, 67 err, 68 ) 69 } 70 71 // Ensure that the period this validator validates the specified subnet 72 // is a subset of the time they validate the primary network. 73 startTime := chainState.GetTimestamp() 74 if !isDurangoActive { 75 startTime = subnetValidator.StartTime() 76 } 77 if !txs.BoundedBy( 78 startTime, 79 subnetValidator.EndTime(), 80 primaryNetworkValidator.StartTime, 81 primaryNetworkValidator.EndTime, 82 ) { 83 return ErrPeriodMismatch 84 } 85 86 return nil 87 } 88 89 // verifyAddValidatorTx carries out the validation for an AddValidatorTx. 90 // It returns the tx outputs that should be returned if this validator is not 91 // added to the staking set. 92 func verifyAddValidatorTx( 93 backend *Backend, 94 feeCalculator fee.Calculator, 95 chainState state.Chain, 96 sTx *txs.Tx, 97 tx *txs.AddValidatorTx, 98 ) ( 99 []*avax.TransferableOutput, 100 error, 101 ) { 102 currentTimestamp := chainState.GetTimestamp() 103 if backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) { 104 return nil, ErrAddValidatorTxPostDurango 105 } 106 107 // Verify the tx is well-formed 108 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 109 return nil, err 110 } 111 112 if err := avax.VerifyMemoFieldLength(tx.Memo, false /*=isDurangoActive*/); err != nil { 113 return nil, err 114 } 115 116 startTime := tx.StartTime() 117 duration := tx.EndTime().Sub(startTime) 118 switch { 119 case tx.Validator.Wght < backend.Config.MinValidatorStake: 120 // Ensure validator is staking at least the minimum amount 121 return nil, ErrWeightTooSmall 122 123 case tx.Validator.Wght > backend.Config.MaxValidatorStake: 124 // Ensure validator isn't staking too much 125 return nil, ErrWeightTooLarge 126 127 case tx.DelegationShares < backend.Config.MinDelegationFee: 128 // Ensure the validator fee is at least the minimum amount 129 return nil, ErrInsufficientDelegationFee 130 131 case duration < backend.Config.MinStakeDuration: 132 // Ensure staking length is not too short 133 return nil, ErrStakeTooShort 134 135 case duration > backend.Config.MaxStakeDuration: 136 // Ensure staking length is not too long 137 return nil, ErrStakeTooLong 138 } 139 140 outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) 141 copy(outs, tx.Outs) 142 copy(outs[len(tx.Outs):], tx.StakeOuts) 143 144 if !backend.Bootstrapped.Get() { 145 return outs, nil 146 } 147 148 if err := verifyStakerStartTime(false /*=isDurangoActive*/, currentTimestamp, startTime); err != nil { 149 return nil, err 150 } 151 152 _, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID) 153 if err == nil { 154 return nil, fmt.Errorf( 155 "%s is %w of the primary network", 156 tx.Validator.NodeID, 157 ErrAlreadyValidator, 158 ) 159 } 160 if err != database.ErrNotFound { 161 return nil, fmt.Errorf( 162 "failed to find whether %s is a primary network validator: %w", 163 tx.Validator.NodeID, 164 err, 165 ) 166 } 167 168 // Verify the flowcheck 169 fee, err := feeCalculator.CalculateFee(tx) 170 if err != nil { 171 return nil, err 172 } 173 if err := backend.FlowChecker.VerifySpend( 174 tx, 175 chainState, 176 tx.Ins, 177 outs, 178 sTx.Creds, 179 map[ids.ID]uint64{ 180 backend.Ctx.AVAXAssetID: fee, 181 }, 182 ); err != nil { 183 return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 184 } 185 186 return outs, nil 187 } 188 189 // verifyAddSubnetValidatorTx carries out the validation for an 190 // AddSubnetValidatorTx. 191 func verifyAddSubnetValidatorTx( 192 backend *Backend, 193 feeCalculator fee.Calculator, 194 chainState state.Chain, 195 sTx *txs.Tx, 196 tx *txs.AddSubnetValidatorTx, 197 ) error { 198 // Verify the tx is well-formed 199 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 200 return err 201 } 202 203 var ( 204 currentTimestamp = chainState.GetTimestamp() 205 isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) 206 ) 207 if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { 208 return err 209 } 210 211 startTime := currentTimestamp 212 if !isDurangoActive { 213 startTime = tx.StartTime() 214 } 215 duration := tx.EndTime().Sub(startTime) 216 217 switch { 218 case duration < backend.Config.MinStakeDuration: 219 // Ensure staking length is not too short 220 return ErrStakeTooShort 221 222 case duration > backend.Config.MaxStakeDuration: 223 // Ensure staking length is not too long 224 return ErrStakeTooLong 225 } 226 227 if !backend.Bootstrapped.Get() { 228 return nil 229 } 230 231 if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { 232 return err 233 } 234 235 _, err := GetValidator(chainState, tx.SubnetValidator.Subnet, tx.Validator.NodeID) 236 if err == nil { 237 return fmt.Errorf( 238 "attempted to issue %w for %s on subnet %s", 239 ErrDuplicateValidator, 240 tx.Validator.NodeID, 241 tx.SubnetValidator.Subnet, 242 ) 243 } 244 if err != database.ErrNotFound { 245 return fmt.Errorf( 246 "failed to find whether %s is a subnet validator: %w", 247 tx.Validator.NodeID, 248 err, 249 ) 250 } 251 252 if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil { 253 return err 254 } 255 256 baseTxCreds, err := verifyPoASubnetAuthorization(backend, chainState, sTx, tx.SubnetValidator.Subnet, tx.SubnetAuth) 257 if err != nil { 258 return err 259 } 260 261 // Verify the flowcheck 262 fee, err := feeCalculator.CalculateFee(tx) 263 if err != nil { 264 return err 265 } 266 if err := backend.FlowChecker.VerifySpend( 267 tx, 268 chainState, 269 tx.Ins, 270 tx.Outs, 271 baseTxCreds, 272 map[ids.ID]uint64{ 273 backend.Ctx.AVAXAssetID: fee, 274 }, 275 ); err != nil { 276 return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 277 } 278 279 return nil 280 } 281 282 // Returns the representation of [tx.NodeID] validating [tx.Subnet]. 283 // Returns true if [tx.NodeID] is a current validator of [tx.Subnet]. 284 // Returns an error if the given tx is invalid. 285 // The transaction is valid if: 286 // * [tx.NodeID] is a current/pending PoA validator of [tx.Subnet]. 287 // * [sTx]'s creds authorize it to spend the stated inputs. 288 // * [sTx]'s creds authorize it to remove a validator from [tx.Subnet]. 289 // * The flow checker passes. 290 func verifyRemoveSubnetValidatorTx( 291 backend *Backend, 292 feeCalculator fee.Calculator, 293 chainState state.Chain, 294 sTx *txs.Tx, 295 tx *txs.RemoveSubnetValidatorTx, 296 ) (*state.Staker, bool, error) { 297 // Verify the tx is well-formed 298 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 299 return nil, false, err 300 } 301 302 var ( 303 currentTimestamp = chainState.GetTimestamp() 304 isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) 305 ) 306 if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { 307 return nil, false, err 308 } 309 310 if backend.Config.UpgradeConfig.IsEtnaActivated(currentTimestamp) { 311 _, _, err := chainState.GetSubnetManager(tx.Subnet) 312 if err == nil { 313 return nil, false, fmt.Errorf("%w: %q", ErrRemoveValidatorManagedSubnet, tx.Subnet) 314 } 315 if err != database.ErrNotFound { 316 return nil, false, err 317 } 318 } 319 320 isCurrentValidator := true 321 vdr, err := chainState.GetCurrentValidator(tx.Subnet, tx.NodeID) 322 if err == database.ErrNotFound { 323 vdr, err = chainState.GetPendingValidator(tx.Subnet, tx.NodeID) 324 isCurrentValidator = false 325 } 326 if err != nil { 327 // It isn't a current or pending validator. 328 return nil, false, fmt.Errorf( 329 "%s %w of %s: %w", 330 tx.NodeID, 331 ErrNotValidator, 332 tx.Subnet, 333 err, 334 ) 335 } 336 337 if !vdr.Priority.IsPermissionedValidator() { 338 return nil, false, ErrRemovePermissionlessValidator 339 } 340 341 if !backend.Bootstrapped.Get() { 342 // Not bootstrapped yet -- don't need to do full verification. 343 return vdr, isCurrentValidator, nil 344 } 345 346 baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth) 347 if err != nil { 348 return nil, false, err 349 } 350 351 // Verify the flowcheck 352 fee, err := feeCalculator.CalculateFee(tx) 353 if err != nil { 354 return nil, false, err 355 } 356 if err := backend.FlowChecker.VerifySpend( 357 tx, 358 chainState, 359 tx.Ins, 360 tx.Outs, 361 baseTxCreds, 362 map[ids.ID]uint64{ 363 backend.Ctx.AVAXAssetID: fee, 364 }, 365 ); err != nil { 366 return nil, false, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 367 } 368 369 return vdr, isCurrentValidator, nil 370 } 371 372 // verifyAddDelegatorTx carries out the validation for an AddDelegatorTx. 373 // It returns the tx outputs that should be returned if this delegator is not 374 // added to the staking set. 375 func verifyAddDelegatorTx( 376 backend *Backend, 377 feeCalculator fee.Calculator, 378 chainState state.Chain, 379 sTx *txs.Tx, 380 tx *txs.AddDelegatorTx, 381 ) ( 382 []*avax.TransferableOutput, 383 error, 384 ) { 385 currentTimestamp := chainState.GetTimestamp() 386 if backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) { 387 return nil, ErrAddDelegatorTxPostDurango 388 } 389 390 // Verify the tx is well-formed 391 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 392 return nil, err 393 } 394 395 if err := avax.VerifyMemoFieldLength(tx.Memo, false /*=isDurangoActive*/); err != nil { 396 return nil, err 397 } 398 399 var ( 400 endTime = tx.EndTime() 401 startTime = tx.StartTime() 402 duration = endTime.Sub(startTime) 403 ) 404 switch { 405 case duration < backend.Config.MinStakeDuration: 406 // Ensure staking length is not too short 407 return nil, ErrStakeTooShort 408 409 case duration > backend.Config.MaxStakeDuration: 410 // Ensure staking length is not too long 411 return nil, ErrStakeTooLong 412 413 case tx.Validator.Wght < backend.Config.MinDelegatorStake: 414 // Ensure validator is staking at least the minimum amount 415 return nil, ErrWeightTooSmall 416 } 417 418 outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) 419 copy(outs, tx.Outs) 420 copy(outs[len(tx.Outs):], tx.StakeOuts) 421 422 if !backend.Bootstrapped.Get() { 423 return outs, nil 424 } 425 426 if err := verifyStakerStartTime(false /*=isDurangoActive*/, currentTimestamp, startTime); err != nil { 427 return nil, err 428 } 429 430 primaryNetworkValidator, err := GetValidator(chainState, constants.PrimaryNetworkID, tx.Validator.NodeID) 431 if err != nil { 432 return nil, fmt.Errorf( 433 "failed to fetch the primary network validator for %s: %w", 434 tx.Validator.NodeID, 435 err, 436 ) 437 } 438 439 maximumWeight, err := safemath.Mul(MaxValidatorWeightFactor, primaryNetworkValidator.Weight) 440 if err != nil { 441 return nil, ErrStakeOverflow 442 } 443 444 if backend.Config.UpgradeConfig.IsApricotPhase3Activated(currentTimestamp) { 445 maximumWeight = min(maximumWeight, backend.Config.MaxValidatorStake) 446 } 447 448 if !txs.BoundedBy( 449 startTime, 450 endTime, 451 primaryNetworkValidator.StartTime, 452 primaryNetworkValidator.EndTime, 453 ) { 454 return nil, ErrPeriodMismatch 455 } 456 overDelegated, err := overDelegated( 457 chainState, 458 primaryNetworkValidator, 459 maximumWeight, 460 tx.Validator.Wght, 461 startTime, 462 endTime, 463 ) 464 if err != nil { 465 return nil, err 466 } 467 if overDelegated { 468 return nil, ErrOverDelegated 469 } 470 471 // Verify the flowcheck 472 fee, err := feeCalculator.CalculateFee(tx) 473 if err != nil { 474 return nil, err 475 } 476 if err := backend.FlowChecker.VerifySpend( 477 tx, 478 chainState, 479 tx.Ins, 480 outs, 481 sTx.Creds, 482 map[ids.ID]uint64{ 483 backend.Ctx.AVAXAssetID: fee, 484 }, 485 ); err != nil { 486 return nil, fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 487 } 488 489 return outs, nil 490 } 491 492 // verifyAddPermissionlessValidatorTx carries out the validation for an 493 // AddPermissionlessValidatorTx. 494 func verifyAddPermissionlessValidatorTx( 495 backend *Backend, 496 feeCalculator fee.Calculator, 497 chainState state.Chain, 498 sTx *txs.Tx, 499 tx *txs.AddPermissionlessValidatorTx, 500 ) error { 501 // Verify the tx is well-formed 502 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 503 return err 504 } 505 506 var ( 507 currentTimestamp = chainState.GetTimestamp() 508 isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) 509 ) 510 if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { 511 return err 512 } 513 514 if !backend.Bootstrapped.Get() { 515 return nil 516 } 517 518 startTime := currentTimestamp 519 if !isDurangoActive { 520 startTime = tx.StartTime() 521 } 522 duration := tx.EndTime().Sub(startTime) 523 524 if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { 525 return err 526 } 527 528 validatorRules, err := getValidatorRules(backend, chainState, tx.Subnet) 529 if err != nil { 530 return err 531 } 532 533 stakedAssetID := tx.StakeOuts[0].AssetID() 534 switch { 535 case tx.Validator.Wght < validatorRules.minValidatorStake: 536 // Ensure validator is staking at least the minimum amount 537 return ErrWeightTooSmall 538 539 case tx.Validator.Wght > validatorRules.maxValidatorStake: 540 // Ensure validator isn't staking too much 541 return ErrWeightTooLarge 542 543 case tx.DelegationShares < validatorRules.minDelegationFee: 544 // Ensure the validator fee is at least the minimum amount 545 return ErrInsufficientDelegationFee 546 547 case duration < validatorRules.minStakeDuration: 548 // Ensure staking length is not too short 549 return ErrStakeTooShort 550 551 case duration > validatorRules.maxStakeDuration: 552 // Ensure staking length is not too long 553 return ErrStakeTooLong 554 555 case stakedAssetID != validatorRules.assetID: 556 // Wrong assetID used 557 return fmt.Errorf( 558 "%w: %s != %s", 559 ErrWrongStakedAssetID, 560 validatorRules.assetID, 561 stakedAssetID, 562 ) 563 } 564 565 _, err = GetValidator(chainState, tx.Subnet, tx.Validator.NodeID) 566 if err == nil { 567 return fmt.Errorf( 568 "%w: %s on %s", 569 ErrDuplicateValidator, 570 tx.Validator.NodeID, 571 tx.Subnet, 572 ) 573 } 574 if err != database.ErrNotFound { 575 return fmt.Errorf( 576 "failed to find whether %s is a validator on %s: %w", 577 tx.Validator.NodeID, 578 tx.Subnet, 579 err, 580 ) 581 } 582 583 if tx.Subnet != constants.PrimaryNetworkID { 584 if err := verifySubnetValidatorPrimaryNetworkRequirements(isDurangoActive, chainState, tx.Validator); err != nil { 585 return err 586 } 587 } 588 589 outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) 590 copy(outs, tx.Outs) 591 copy(outs[len(tx.Outs):], tx.StakeOuts) 592 593 // Verify the flowcheck 594 fee, err := feeCalculator.CalculateFee(tx) 595 if err != nil { 596 return err 597 } 598 if err := backend.FlowChecker.VerifySpend( 599 tx, 600 chainState, 601 tx.Ins, 602 outs, 603 sTx.Creds, 604 map[ids.ID]uint64{ 605 backend.Ctx.AVAXAssetID: fee, 606 }, 607 ); err != nil { 608 return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 609 } 610 611 return nil 612 } 613 614 // verifyAddPermissionlessDelegatorTx carries out the validation for an 615 // AddPermissionlessDelegatorTx. 616 func verifyAddPermissionlessDelegatorTx( 617 backend *Backend, 618 feeCalculator fee.Calculator, 619 chainState state.Chain, 620 sTx *txs.Tx, 621 tx *txs.AddPermissionlessDelegatorTx, 622 ) error { 623 // Verify the tx is well-formed 624 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 625 return err 626 } 627 628 var ( 629 currentTimestamp = chainState.GetTimestamp() 630 isDurangoActive = backend.Config.UpgradeConfig.IsDurangoActivated(currentTimestamp) 631 ) 632 if err := avax.VerifyMemoFieldLength(tx.Memo, isDurangoActive); err != nil { 633 return err 634 } 635 636 if !backend.Bootstrapped.Get() { 637 return nil 638 } 639 640 var ( 641 endTime = tx.EndTime() 642 startTime = currentTimestamp 643 ) 644 if !isDurangoActive { 645 startTime = tx.StartTime() 646 } 647 duration := endTime.Sub(startTime) 648 649 if err := verifyStakerStartTime(isDurangoActive, currentTimestamp, startTime); err != nil { 650 return err 651 } 652 653 delegatorRules, err := getDelegatorRules(backend, chainState, tx.Subnet) 654 if err != nil { 655 return err 656 } 657 658 stakedAssetID := tx.StakeOuts[0].AssetID() 659 switch { 660 case tx.Validator.Wght < delegatorRules.minDelegatorStake: 661 // Ensure delegator is staking at least the minimum amount 662 return ErrWeightTooSmall 663 664 case duration < delegatorRules.minStakeDuration: 665 // Ensure staking length is not too short 666 return ErrStakeTooShort 667 668 case duration > delegatorRules.maxStakeDuration: 669 // Ensure staking length is not too long 670 return ErrStakeTooLong 671 672 case stakedAssetID != delegatorRules.assetID: 673 // Wrong assetID used 674 return fmt.Errorf( 675 "%w: %s != %s", 676 ErrWrongStakedAssetID, 677 delegatorRules.assetID, 678 stakedAssetID, 679 ) 680 } 681 682 validator, err := GetValidator(chainState, tx.Subnet, tx.Validator.NodeID) 683 if err != nil { 684 return fmt.Errorf( 685 "failed to fetch the validator for %s on %s: %w", 686 tx.Validator.NodeID, 687 tx.Subnet, 688 err, 689 ) 690 } 691 692 maximumWeight, err := safemath.Mul( 693 uint64(delegatorRules.maxValidatorWeightFactor), 694 validator.Weight, 695 ) 696 if err != nil { 697 maximumWeight = math.MaxUint64 698 } 699 maximumWeight = min(maximumWeight, delegatorRules.maxValidatorStake) 700 701 if !txs.BoundedBy( 702 startTime, 703 endTime, 704 validator.StartTime, 705 validator.EndTime, 706 ) { 707 return ErrPeriodMismatch 708 } 709 overDelegated, err := overDelegated( 710 chainState, 711 validator, 712 maximumWeight, 713 tx.Validator.Wght, 714 startTime, 715 endTime, 716 ) 717 if err != nil { 718 return err 719 } 720 if overDelegated { 721 return ErrOverDelegated 722 } 723 724 outs := make([]*avax.TransferableOutput, len(tx.Outs)+len(tx.StakeOuts)) 725 copy(outs, tx.Outs) 726 copy(outs[len(tx.Outs):], tx.StakeOuts) 727 728 if tx.Subnet != constants.PrimaryNetworkID { 729 // Invariant: Delegators must only be able to reference validator 730 // transactions that implement [txs.ValidatorTx]. All 731 // validator transactions implement this interface except the 732 // AddSubnetValidatorTx. AddSubnetValidatorTx is the only 733 // permissioned validator, so we verify this delegator is 734 // pointing to a permissionless validator. 735 if validator.Priority.IsPermissionedValidator() { 736 return ErrDelegateToPermissionedValidator 737 } 738 } 739 740 // Verify the flowcheck 741 fee, err := feeCalculator.CalculateFee(tx) 742 if err != nil { 743 return err 744 } 745 if err := backend.FlowChecker.VerifySpend( 746 tx, 747 chainState, 748 tx.Ins, 749 outs, 750 sTx.Creds, 751 map[ids.ID]uint64{ 752 backend.Ctx.AVAXAssetID: fee, 753 }, 754 ); err != nil { 755 return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 756 } 757 758 return nil 759 } 760 761 // Returns an error if the given tx is invalid. 762 // The transaction is valid if: 763 // * [sTx]'s creds authorize it to spend the stated inputs. 764 // * [sTx]'s creds authorize it to transfer ownership of [tx.Subnet]. 765 // * The flow checker passes. 766 func verifyTransferSubnetOwnershipTx( 767 backend *Backend, 768 feeCalculator fee.Calculator, 769 chainState state.Chain, 770 sTx *txs.Tx, 771 tx *txs.TransferSubnetOwnershipTx, 772 ) error { 773 var ( 774 currentTimestamp = chainState.GetTimestamp() 775 upgrades = backend.Config.UpgradeConfig 776 ) 777 if !upgrades.IsDurangoActivated(currentTimestamp) { 778 return ErrDurangoUpgradeNotActive 779 } 780 781 // Verify the tx is well-formed 782 if err := sTx.SyntacticVerify(backend.Ctx); err != nil { 783 return err 784 } 785 786 if err := avax.VerifyMemoFieldLength(tx.Memo, true /*=isDurangoActive*/); err != nil { 787 return err 788 } 789 790 if !backend.Bootstrapped.Get() { 791 // Not bootstrapped yet -- don't need to do full verification. 792 return nil 793 } 794 795 baseTxCreds, err := verifySubnetAuthorization(backend, chainState, sTx, tx.Subnet, tx.SubnetAuth) 796 if err != nil { 797 return err 798 } 799 800 // Verify the flowcheck 801 fee, err := feeCalculator.CalculateFee(tx) 802 if err != nil { 803 return err 804 } 805 if err := backend.FlowChecker.VerifySpend( 806 tx, 807 chainState, 808 tx.Ins, 809 tx.Outs, 810 baseTxCreds, 811 map[ids.ID]uint64{ 812 backend.Ctx.AVAXAssetID: fee, 813 }, 814 ); err != nil { 815 return fmt.Errorf("%w: %w", ErrFlowCheckFailed, err) 816 } 817 818 return nil 819 } 820 821 // Ensure the proposed validator starts after the current time 822 func verifyStakerStartTime(isDurangoActive bool, chainTime, stakerTime time.Time) error { 823 // Pre Durango activation, start time must be after current chain time. 824 // Post Durango activation, start time is not validated 825 if isDurangoActive { 826 return nil 827 } 828 829 if !chainTime.Before(stakerTime) { 830 return fmt.Errorf( 831 "%w: %s >= %s", 832 ErrTimestampNotBeforeStartTime, 833 chainTime, 834 stakerTime, 835 ) 836 } 837 return nil 838 }