github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/api/static_service.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package api 5 6 import ( 7 "cmp" 8 "errors" 9 "fmt" 10 "net/http" 11 12 "github.com/MetalBlockchain/metalgo/ids" 13 "github.com/MetalBlockchain/metalgo/utils" 14 "github.com/MetalBlockchain/metalgo/utils/formatting" 15 "github.com/MetalBlockchain/metalgo/utils/formatting/address" 16 "github.com/MetalBlockchain/metalgo/utils/json" 17 "github.com/MetalBlockchain/metalgo/utils/math" 18 "github.com/MetalBlockchain/metalgo/vms/components/avax" 19 "github.com/MetalBlockchain/metalgo/vms/platformvm/genesis" 20 "github.com/MetalBlockchain/metalgo/vms/platformvm/signer" 21 "github.com/MetalBlockchain/metalgo/vms/platformvm/stakeable" 22 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs" 23 "github.com/MetalBlockchain/metalgo/vms/platformvm/txs/txheap" 24 "github.com/MetalBlockchain/metalgo/vms/secp256k1fx" 25 ) 26 27 // Note that since an Avalanche network has exactly one Platform Chain, 28 // and the Platform Chain defines the genesis state of the network 29 // (who is staking, which chains exist, etc.), defining the genesis 30 // state of the Platform Chain is the same as defining the genesis 31 // state of the network. 32 33 var ( 34 errUTXOHasNoValue = errors.New("genesis UTXO has no value") 35 errValidatorHasNoWeight = errors.New("validator has not weight") 36 errValidatorAlreadyExited = errors.New("validator would have already unstaked") 37 errStakeOverflow = errors.New("validator stake exceeds limit") 38 39 _ utils.Sortable[UTXO] = UTXO{} 40 ) 41 42 // StaticService defines the static API methods exposed by the platform VM 43 type StaticService struct{} 44 45 // UTXO is a UTXO on the Platform Chain that exists at the chain's genesis. 46 type UTXO struct { 47 Locktime json.Uint64 `json:"locktime"` 48 Amount json.Uint64 `json:"amount"` 49 Address string `json:"address"` 50 Message string `json:"message"` 51 } 52 53 // TODO can we define this on *UTXO? 54 func (utxo UTXO) Compare(other UTXO) int { 55 if locktimeCmp := cmp.Compare(utxo.Locktime, other.Locktime); locktimeCmp != 0 { 56 return locktimeCmp 57 } 58 if amountCmp := cmp.Compare(utxo.Amount, other.Amount); amountCmp != 0 { 59 return amountCmp 60 } 61 62 utxoAddr, err := bech32ToID(utxo.Address) 63 if err != nil { 64 return 0 65 } 66 67 otherAddr, err := bech32ToID(other.Address) 68 if err != nil { 69 return 0 70 } 71 72 return utxoAddr.Compare(otherAddr) 73 } 74 75 // TODO: Refactor APIStaker, APIValidators and merge them together for 76 // PermissionedValidators + PermissionlessValidators. 77 78 // APIStaker is the representation of a staker sent via APIs. 79 // [TxID] is the txID of the transaction that added this staker. 80 // [Amount] is the amount of tokens being staked. 81 // [StartTime] is the Unix time when they start staking 82 // [Endtime] is the Unix time repr. of when they are done staking 83 // [NodeID] is the node ID of the staker 84 // [Uptime] is the observed uptime of this staker 85 type Staker struct { 86 TxID ids.ID `json:"txID"` 87 StartTime json.Uint64 `json:"startTime"` 88 EndTime json.Uint64 `json:"endTime"` 89 Weight json.Uint64 `json:"weight"` 90 NodeID ids.NodeID `json:"nodeID"` 91 92 // Deprecated: Use Weight instead 93 // TODO: remove [StakeAmount] after enough time for dependencies to update 94 StakeAmount *json.Uint64 `json:"stakeAmount,omitempty"` 95 } 96 97 // GenesisValidator should to be used for genesis validators only. 98 type GenesisValidator Staker 99 100 // Owner is the repr. of a reward owner sent over APIs. 101 type Owner struct { 102 Locktime json.Uint64 `json:"locktime"` 103 Threshold json.Uint32 `json:"threshold"` 104 Addresses []string `json:"addresses"` 105 } 106 107 // PermissionlessValidator is the repr. of a permissionless validator sent over 108 // APIs. 109 type PermissionlessValidator struct { 110 Staker 111 // Deprecated: RewardOwner has been replaced by ValidationRewardOwner and 112 // DelegationRewardOwner. 113 RewardOwner *Owner `json:"rewardOwner,omitempty"` 114 // The owner of the rewards from the validation period, if applicable. 115 ValidationRewardOwner *Owner `json:"validationRewardOwner,omitempty"` 116 // The owner of the rewards from delegations during the validation period, 117 // if applicable. 118 DelegationRewardOwner *Owner `json:"delegationRewardOwner,omitempty"` 119 PotentialReward *json.Uint64 `json:"potentialReward,omitempty"` 120 AccruedDelegateeReward *json.Uint64 `json:"accruedDelegateeReward,omitempty"` 121 DelegationFee json.Float32 `json:"delegationFee"` 122 ExactDelegationFee *json.Uint32 `json:"exactDelegationFee,omitempty"` 123 Uptime *json.Float32 `json:"uptime,omitempty"` 124 Connected bool `json:"connected"` 125 Staked []UTXO `json:"staked,omitempty"` 126 Signer *signer.ProofOfPossession `json:"signer,omitempty"` 127 128 // The delegators delegating to this validator 129 DelegatorCount *json.Uint64 `json:"delegatorCount,omitempty"` 130 DelegatorWeight *json.Uint64 `json:"delegatorWeight,omitempty"` 131 Delegators *[]PrimaryDelegator `json:"delegators,omitempty"` 132 } 133 134 // GenesisPermissionlessValidator should to be used for genesis validators only. 135 type GenesisPermissionlessValidator struct { 136 GenesisValidator 137 RewardOwner *Owner `json:"rewardOwner,omitempty"` 138 DelegationFee json.Float32 `json:"delegationFee"` 139 ExactDelegationFee *json.Uint32 `json:"exactDelegationFee,omitempty"` 140 Staked []UTXO `json:"staked,omitempty"` 141 Signer *signer.ProofOfPossession `json:"signer,omitempty"` 142 } 143 144 // PermissionedValidator is the repr. of a permissioned validator sent over APIs. 145 type PermissionedValidator struct { 146 Staker 147 // The owner the staking reward, if applicable, will go to 148 Connected bool `json:"connected"` 149 Uptime *json.Float32 `json:"uptime,omitempty"` 150 } 151 152 // PrimaryDelegator is the repr. of a primary network delegator sent over APIs. 153 type PrimaryDelegator struct { 154 Staker 155 RewardOwner *Owner `json:"rewardOwner,omitempty"` 156 PotentialReward *json.Uint64 `json:"potentialReward,omitempty"` 157 } 158 159 // Chain defines a chain that exists 160 // at the network's genesis. 161 // [GenesisData] is the initial state of the chain. 162 // [VMID] is the ID of the VM this chain runs. 163 // [FxIDs] are the IDs of the Fxs the chain supports. 164 // [Name] is a human-readable, non-unique name for the chain. 165 // [SubnetID] is the ID of the subnet that validates the chain 166 type Chain struct { 167 GenesisData string `json:"genesisData"` 168 VMID ids.ID `json:"vmID"` 169 FxIDs []ids.ID `json:"fxIDs"` 170 Name string `json:"name"` 171 SubnetID ids.ID `json:"subnetID"` 172 } 173 174 // BuildGenesisArgs are the arguments used to create 175 // the genesis data of the Platform Chain. 176 // [NetworkID] is the ID of the network 177 // [UTXOs] are the UTXOs on the Platform Chain that exist at genesis. 178 // [Validators] are the validators of the primary network at genesis. 179 // [Chains] are the chains that exist at genesis. 180 // [Time] is the Platform Chain's time at network genesis. 181 type BuildGenesisArgs struct { 182 AvaxAssetID ids.ID `json:"avaxAssetID"` 183 NetworkID json.Uint32 `json:"networkID"` 184 UTXOs []UTXO `json:"utxos"` 185 Validators []GenesisPermissionlessValidator `json:"validators"` 186 Chains []Chain `json:"chains"` 187 Time json.Uint64 `json:"time"` 188 InitialSupply json.Uint64 `json:"initialSupply"` 189 Message string `json:"message"` 190 Encoding formatting.Encoding `json:"encoding"` 191 } 192 193 // BuildGenesisReply is the reply from BuildGenesis 194 type BuildGenesisReply struct { 195 Bytes string `json:"bytes"` 196 Encoding formatting.Encoding `json:"encoding"` 197 } 198 199 // bech32ToID takes bech32 address and produces a shortID 200 func bech32ToID(addrStr string) (ids.ShortID, error) { 201 _, addrBytes, err := address.ParseBech32(addrStr) 202 if err != nil { 203 return ids.ShortID{}, err 204 } 205 return ids.ToShortID(addrBytes) 206 } 207 208 // BuildGenesis build the genesis state of the Platform Chain (and thereby the Avalanche network.) 209 func (*StaticService) BuildGenesis(_ *http.Request, args *BuildGenesisArgs, reply *BuildGenesisReply) error { 210 // Specify the UTXOs on the Platform chain that exist at genesis. 211 utxos := make([]*genesis.UTXO, 0, len(args.UTXOs)) 212 for i, apiUTXO := range args.UTXOs { 213 if apiUTXO.Amount == 0 { 214 return errUTXOHasNoValue 215 } 216 addrID, err := bech32ToID(apiUTXO.Address) 217 if err != nil { 218 return err 219 } 220 221 utxo := avax.UTXO{ 222 UTXOID: avax.UTXOID{ 223 TxID: ids.Empty, 224 OutputIndex: uint32(i), 225 }, 226 Asset: avax.Asset{ID: args.AvaxAssetID}, 227 Out: &secp256k1fx.TransferOutput{ 228 Amt: uint64(apiUTXO.Amount), 229 OutputOwners: secp256k1fx.OutputOwners{ 230 Locktime: 0, 231 Threshold: 1, 232 Addrs: []ids.ShortID{addrID}, 233 }, 234 }, 235 } 236 if apiUTXO.Locktime > args.Time { 237 utxo.Out = &stakeable.LockOut{ 238 Locktime: uint64(apiUTXO.Locktime), 239 TransferableOut: utxo.Out.(avax.TransferableOut), 240 } 241 } 242 messageBytes, err := formatting.Decode(args.Encoding, apiUTXO.Message) 243 if err != nil { 244 return fmt.Errorf("problem decoding UTXO message bytes: %w", err) 245 } 246 utxos = append(utxos, &genesis.UTXO{ 247 UTXO: utxo, 248 Message: messageBytes, 249 }) 250 } 251 252 // Specify the validators that are validating the primary network at genesis. 253 vdrs := txheap.NewByEndTime() 254 for _, vdr := range args.Validators { 255 weight := uint64(0) 256 stake := make([]*avax.TransferableOutput, len(vdr.Staked)) 257 utils.Sort(vdr.Staked) 258 for i, apiUTXO := range vdr.Staked { 259 addrID, err := bech32ToID(apiUTXO.Address) 260 if err != nil { 261 return err 262 } 263 264 utxo := &avax.TransferableOutput{ 265 Asset: avax.Asset{ID: args.AvaxAssetID}, 266 Out: &secp256k1fx.TransferOutput{ 267 Amt: uint64(apiUTXO.Amount), 268 OutputOwners: secp256k1fx.OutputOwners{ 269 Locktime: 0, 270 Threshold: 1, 271 Addrs: []ids.ShortID{addrID}, 272 }, 273 }, 274 } 275 if apiUTXO.Locktime > args.Time { 276 utxo.Out = &stakeable.LockOut{ 277 Locktime: uint64(apiUTXO.Locktime), 278 TransferableOut: utxo.Out, 279 } 280 } 281 stake[i] = utxo 282 283 newWeight, err := math.Add64(weight, uint64(apiUTXO.Amount)) 284 if err != nil { 285 return errStakeOverflow 286 } 287 weight = newWeight 288 } 289 290 if weight == 0 { 291 return errValidatorHasNoWeight 292 } 293 if uint64(vdr.EndTime) <= uint64(args.Time) { 294 return errValidatorAlreadyExited 295 } 296 297 owner := &secp256k1fx.OutputOwners{ 298 Locktime: uint64(vdr.RewardOwner.Locktime), 299 Threshold: uint32(vdr.RewardOwner.Threshold), 300 } 301 for _, addrStr := range vdr.RewardOwner.Addresses { 302 addrID, err := bech32ToID(addrStr) 303 if err != nil { 304 return err 305 } 306 owner.Addrs = append(owner.Addrs, addrID) 307 } 308 utils.Sort(owner.Addrs) 309 310 delegationFee := uint32(0) 311 if vdr.ExactDelegationFee != nil { 312 delegationFee = uint32(*vdr.ExactDelegationFee) 313 } 314 315 var ( 316 baseTx = txs.BaseTx{BaseTx: avax.BaseTx{ 317 NetworkID: uint32(args.NetworkID), 318 BlockchainID: ids.Empty, 319 }} 320 validator = txs.Validator{ 321 NodeID: vdr.NodeID, 322 Start: uint64(args.Time), 323 End: uint64(vdr.EndTime), 324 Wght: weight, 325 } 326 tx *txs.Tx 327 ) 328 if vdr.Signer == nil { 329 tx = &txs.Tx{Unsigned: &txs.AddValidatorTx{ 330 BaseTx: baseTx, 331 Validator: validator, 332 StakeOuts: stake, 333 RewardsOwner: owner, 334 DelegationShares: delegationFee, 335 }} 336 } else { 337 tx = &txs.Tx{Unsigned: &txs.AddPermissionlessValidatorTx{ 338 BaseTx: baseTx, 339 Validator: validator, 340 Signer: vdr.Signer, 341 StakeOuts: stake, 342 ValidatorRewardsOwner: owner, 343 DelegatorRewardsOwner: owner, 344 DelegationShares: delegationFee, 345 }} 346 } 347 348 if err := tx.Initialize(txs.GenesisCodec); err != nil { 349 return err 350 } 351 352 vdrs.Add(tx) 353 } 354 355 // Specify the chains that exist at genesis. 356 chains := []*txs.Tx{} 357 for _, chain := range args.Chains { 358 genesisBytes, err := formatting.Decode(args.Encoding, chain.GenesisData) 359 if err != nil { 360 return fmt.Errorf("problem decoding chain genesis data: %w", err) 361 } 362 tx := &txs.Tx{Unsigned: &txs.CreateChainTx{ 363 BaseTx: txs.BaseTx{BaseTx: avax.BaseTx{ 364 NetworkID: uint32(args.NetworkID), 365 BlockchainID: ids.Empty, 366 }}, 367 SubnetID: chain.SubnetID, 368 ChainName: chain.Name, 369 VMID: chain.VMID, 370 FxIDs: chain.FxIDs, 371 GenesisData: genesisBytes, 372 SubnetAuth: &secp256k1fx.Input{}, 373 }} 374 if err := tx.Initialize(txs.GenesisCodec); err != nil { 375 return err 376 } 377 378 chains = append(chains, tx) 379 } 380 381 validatorTxs := vdrs.List() 382 383 // genesis holds the genesis state 384 g := genesis.Genesis{ 385 UTXOs: utxos, 386 Validators: validatorTxs, 387 Chains: chains, 388 Timestamp: uint64(args.Time), 389 InitialSupply: uint64(args.InitialSupply), 390 Message: args.Message, 391 } 392 393 // Marshal genesis to bytes 394 bytes, err := genesis.Codec.Marshal(genesis.CodecVersion, g) 395 if err != nil { 396 return fmt.Errorf("couldn't marshal genesis: %w", err) 397 } 398 reply.Bytes, err = formatting.Encode(args.Encoding, bytes) 399 if err != nil { 400 return fmt.Errorf("couldn't encode genesis as string: %w", err) 401 } 402 reply.Encoding = args.Encoding 403 return nil 404 }