github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/add_permissionless_delegator_tx.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package txs 5 6 import ( 7 "fmt" 8 9 "github.com/MetalBlockchain/metalgo/ids" 10 "github.com/MetalBlockchain/metalgo/snow" 11 "github.com/MetalBlockchain/metalgo/utils/constants" 12 "github.com/MetalBlockchain/metalgo/utils/crypto/bls" 13 "github.com/MetalBlockchain/metalgo/utils/math" 14 "github.com/MetalBlockchain/metalgo/vms/components/avax" 15 "github.com/MetalBlockchain/metalgo/vms/components/verify" 16 "github.com/MetalBlockchain/metalgo/vms/platformvm/fx" 17 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 18 ) 19 20 var ( 21 _ DelegatorTx = (*AddPermissionlessDelegatorTx)(nil) 22 _ ScheduledStaker = (*AddPermissionlessDelegatorTx)(nil) 23 ) 24 25 // AddPermissionlessDelegatorTx is an unsigned addPermissionlessDelegatorTx 26 type AddPermissionlessDelegatorTx struct { 27 // Metadata, inputs and outputs 28 BaseTx `serialize:"true"` 29 // Describes the validator 30 Validator `serialize:"true" json:"validator"` 31 // ID of the subnet this validator is validating 32 Subnet ids.ID `serialize:"true" json:"subnetID"` 33 // Where to send staked tokens when done validating 34 StakeOuts []*avax.TransferableOutput `serialize:"true" json:"stake"` 35 // Where to send staking rewards when done validating 36 DelegationRewardsOwner fx.Owner `serialize:"true" json:"rewardsOwner"` 37 } 38 39 // InitCtx sets the FxID fields in the inputs and outputs of this 40 // [AddPermissionlessDelegatorTx]. Also sets the [ctx] to the given [vm.ctx] so 41 // that the addresses can be json marshalled into human readable format 42 func (tx *AddPermissionlessDelegatorTx) InitCtx(ctx *snow.Context) { 43 tx.BaseTx.InitCtx(ctx) 44 for _, out := range tx.StakeOuts { 45 out.FxID = secp256k1fx.ID 46 out.InitCtx(ctx) 47 } 48 tx.DelegationRewardsOwner.InitCtx(ctx) 49 } 50 51 func (tx *AddPermissionlessDelegatorTx) SubnetID() ids.ID { 52 return tx.Subnet 53 } 54 55 func (tx *AddPermissionlessDelegatorTx) NodeID() ids.NodeID { 56 return tx.Validator.NodeID 57 } 58 59 func (*AddPermissionlessDelegatorTx) PublicKey() (*bls.PublicKey, bool, error) { 60 return nil, false, nil 61 } 62 63 func (tx *AddPermissionlessDelegatorTx) PendingPriority() Priority { 64 if tx.Subnet == constants.PrimaryNetworkID { 65 return PrimaryNetworkDelegatorBanffPendingPriority 66 } 67 return SubnetPermissionlessDelegatorPendingPriority 68 } 69 70 func (tx *AddPermissionlessDelegatorTx) CurrentPriority() Priority { 71 if tx.Subnet == constants.PrimaryNetworkID { 72 return PrimaryNetworkDelegatorCurrentPriority 73 } 74 return SubnetPermissionlessDelegatorCurrentPriority 75 } 76 77 func (tx *AddPermissionlessDelegatorTx) Stake() []*avax.TransferableOutput { 78 return tx.StakeOuts 79 } 80 81 func (tx *AddPermissionlessDelegatorTx) RewardsOwner() fx.Owner { 82 return tx.DelegationRewardsOwner 83 } 84 85 // SyntacticVerify returns nil iff [tx] is valid 86 func (tx *AddPermissionlessDelegatorTx) SyntacticVerify(ctx *snow.Context) error { 87 switch { 88 case tx == nil: 89 return ErrNilTx 90 case tx.SyntacticallyVerified: // already passed syntactic verification 91 return nil 92 case len(tx.StakeOuts) == 0: // Ensure there is provided stake 93 return errNoStake 94 } 95 96 if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { 97 return fmt.Errorf("failed to verify BaseTx: %w", err) 98 } 99 if err := verify.All(&tx.Validator, tx.DelegationRewardsOwner); err != nil { 100 return fmt.Errorf("failed to verify validator or rewards owner: %w", err) 101 } 102 103 for _, out := range tx.StakeOuts { 104 if err := out.Verify(); err != nil { 105 return fmt.Errorf("failed to verify output: %w", err) 106 } 107 } 108 109 firstStakeOutput := tx.StakeOuts[0] 110 stakedAssetID := firstStakeOutput.AssetID() 111 totalStakeWeight := firstStakeOutput.Output().Amount() 112 for _, out := range tx.StakeOuts[1:] { 113 newWeight, err := math.Add64(totalStakeWeight, out.Output().Amount()) 114 if err != nil { 115 return err 116 } 117 totalStakeWeight = newWeight 118 119 assetID := out.AssetID() 120 if assetID != stakedAssetID { 121 return fmt.Errorf("%w: %q and %q", errMultipleStakedAssets, stakedAssetID, assetID) 122 } 123 } 124 125 switch { 126 case !avax.IsSortedTransferableOutputs(tx.StakeOuts, Codec): 127 return errOutputsNotSorted 128 case totalStakeWeight != tx.Wght: 129 return fmt.Errorf("%w, delegator weight %d total stake weight %d", 130 errDelegatorWeightMismatch, 131 tx.Wght, 132 totalStakeWeight, 133 ) 134 } 135 136 // cache that this is valid 137 tx.SyntacticallyVerified = true 138 return nil 139 } 140 141 func (tx *AddPermissionlessDelegatorTx) Visit(visitor Visitor) error { 142 return visitor.AddPermissionlessDelegatorTx(tx) 143 }