github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/txs/add_permissionless_validator_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 "errors" 8 "fmt" 9 10 "github.com/MetalBlockchain/metalgo/ids" 11 "github.com/MetalBlockchain/metalgo/snow" 12 "github.com/MetalBlockchain/metalgo/utils/constants" 13 "github.com/MetalBlockchain/metalgo/utils/crypto/bls" 14 "github.com/MetalBlockchain/metalgo/utils/math" 15 "github.com/MetalBlockchain/metalgo/vms/components/avax" 16 "github.com/MetalBlockchain/metalgo/vms/components/verify" 17 "github.com/MetalBlockchain/metalgo/vms/platformvm/fx" 18 "github.com/MetalBlockchain/metalgo/vms/platformvm/reward" 19 "github.com/MetalBlockchain/metalgo/vms/platformvm/signer" 20 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 21 ) 22 23 var ( 24 _ ValidatorTx = (*AddPermissionlessValidatorTx)(nil) 25 _ ScheduledStaker = (*AddPermissionlessDelegatorTx)(nil) 26 27 errEmptyNodeID = errors.New("validator nodeID cannot be empty") 28 errNoStake = errors.New("no stake") 29 errInvalidSigner = errors.New("invalid signer") 30 errMultipleStakedAssets = errors.New("multiple staked assets") 31 errValidatorWeightMismatch = errors.New("validator weight mismatch") 32 ) 33 34 // AddPermissionlessValidatorTx is an unsigned addPermissionlessValidatorTx 35 type AddPermissionlessValidatorTx struct { 36 // Metadata, inputs and outputs 37 BaseTx `serialize:"true"` 38 // Describes the validator 39 Validator `serialize:"true" json:"validator"` 40 // ID of the subnet this validator is validating 41 Subnet ids.ID `serialize:"true" json:"subnetID"` 42 // If the [Subnet] is the primary network, [Signer] is the BLS key for this 43 // validator. If the [Subnet] is not the primary network, this value is the 44 // empty signer 45 // Note: We do not enforce that the BLS key is unique across all validators. 46 // This means that validators can share a key if they so choose. 47 // However, a NodeID does uniquely map to a BLS key 48 Signer signer.Signer `serialize:"true" json:"signer"` 49 // Where to send staked tokens when done validating 50 StakeOuts []*avax.TransferableOutput `serialize:"true" json:"stake"` 51 // Where to send validation rewards when done validating 52 ValidatorRewardsOwner fx.Owner `serialize:"true" json:"validationRewardsOwner"` 53 // Where to send delegation rewards when done validating 54 DelegatorRewardsOwner fx.Owner `serialize:"true" json:"delegationRewardsOwner"` 55 // Fee this validator charges delegators as a percentage, times 10,000 56 // For example, if this validator has DelegationShares=300,000 then they 57 // take 30% of rewards from delegators 58 DelegationShares uint32 `serialize:"true" json:"shares"` 59 } 60 61 // InitCtx sets the FxID fields in the inputs and outputs of this 62 // [AddPermissionlessValidatorTx]. Also sets the [ctx] to the given [vm.ctx] so 63 // that the addresses can be json marshalled into human readable format 64 func (tx *AddPermissionlessValidatorTx) InitCtx(ctx *snow.Context) { 65 tx.BaseTx.InitCtx(ctx) 66 for _, out := range tx.StakeOuts { 67 out.FxID = secp256k1fx.ID 68 out.InitCtx(ctx) 69 } 70 tx.ValidatorRewardsOwner.InitCtx(ctx) 71 tx.DelegatorRewardsOwner.InitCtx(ctx) 72 } 73 74 func (tx *AddPermissionlessValidatorTx) SubnetID() ids.ID { 75 return tx.Subnet 76 } 77 78 func (tx *AddPermissionlessValidatorTx) NodeID() ids.NodeID { 79 return tx.Validator.NodeID 80 } 81 82 func (tx *AddPermissionlessValidatorTx) PublicKey() (*bls.PublicKey, bool, error) { 83 if err := tx.Signer.Verify(); err != nil { 84 return nil, false, err 85 } 86 key := tx.Signer.Key() 87 return key, key != nil, nil 88 } 89 90 func (tx *AddPermissionlessValidatorTx) PendingPriority() Priority { 91 if tx.Subnet == constants.PrimaryNetworkID { 92 return PrimaryNetworkValidatorPendingPriority 93 } 94 return SubnetPermissionlessValidatorPendingPriority 95 } 96 97 func (tx *AddPermissionlessValidatorTx) CurrentPriority() Priority { 98 if tx.Subnet == constants.PrimaryNetworkID { 99 return PrimaryNetworkValidatorCurrentPriority 100 } 101 return SubnetPermissionlessValidatorCurrentPriority 102 } 103 104 func (tx *AddPermissionlessValidatorTx) Stake() []*avax.TransferableOutput { 105 return tx.StakeOuts 106 } 107 108 func (tx *AddPermissionlessValidatorTx) ValidationRewardsOwner() fx.Owner { 109 return tx.ValidatorRewardsOwner 110 } 111 112 func (tx *AddPermissionlessValidatorTx) DelegationRewardsOwner() fx.Owner { 113 return tx.DelegatorRewardsOwner 114 } 115 116 func (tx *AddPermissionlessValidatorTx) Shares() uint32 { 117 return tx.DelegationShares 118 } 119 120 // SyntacticVerify returns nil iff [tx] is valid 121 func (tx *AddPermissionlessValidatorTx) SyntacticVerify(ctx *snow.Context) error { 122 switch { 123 case tx == nil: 124 return ErrNilTx 125 case tx.SyntacticallyVerified: // already passed syntactic verification 126 return nil 127 case tx.Validator.NodeID == ids.EmptyNodeID: 128 return errEmptyNodeID 129 case len(tx.StakeOuts) == 0: // Ensure there is provided stake 130 return errNoStake 131 case tx.DelegationShares > reward.PercentDenominator: 132 return errTooManyShares 133 } 134 135 if err := tx.BaseTx.SyntacticVerify(ctx); err != nil { 136 return fmt.Errorf("failed to verify BaseTx: %w", err) 137 } 138 if err := verify.All(&tx.Validator, tx.Signer, tx.ValidatorRewardsOwner, tx.DelegatorRewardsOwner); err != nil { 139 return fmt.Errorf("failed to verify validator, signer, or rewards owners: %w", err) 140 } 141 142 hasKey := tx.Signer.Key() != nil 143 isPrimaryNetwork := tx.Subnet == constants.PrimaryNetworkID 144 if hasKey != isPrimaryNetwork { 145 return fmt.Errorf( 146 "%w: hasKey=%v != isPrimaryNetwork=%v", 147 errInvalidSigner, 148 hasKey, 149 isPrimaryNetwork, 150 ) 151 } 152 153 for _, out := range tx.StakeOuts { 154 if err := out.Verify(); err != nil { 155 return fmt.Errorf("failed to verify output: %w", err) 156 } 157 } 158 159 firstStakeOutput := tx.StakeOuts[0] 160 stakedAssetID := firstStakeOutput.AssetID() 161 totalStakeWeight := firstStakeOutput.Output().Amount() 162 for _, out := range tx.StakeOuts[1:] { 163 newWeight, err := math.Add64(totalStakeWeight, out.Output().Amount()) 164 if err != nil { 165 return err 166 } 167 totalStakeWeight = newWeight 168 169 assetID := out.AssetID() 170 if assetID != stakedAssetID { 171 return fmt.Errorf("%w: %q and %q", errMultipleStakedAssets, stakedAssetID, assetID) 172 } 173 } 174 175 switch { 176 case !avax.IsSortedTransferableOutputs(tx.StakeOuts, Codec): 177 return errOutputsNotSorted 178 case totalStakeWeight != tx.Wght: 179 return fmt.Errorf("%w: weight %d != stake %d", errValidatorWeightMismatch, tx.Wght, totalStakeWeight) 180 } 181 182 // cache that this is valid 183 tx.SyntacticallyVerified = true 184 return nil 185 } 186 187 func (tx *AddPermissionlessValidatorTx) Visit(visitor Visitor) error { 188 return visitor.AddPermissionlessValidatorTx(tx) 189 }