github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/client.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package platformvm 5 6 import ( 7 "context" 8 "time" 9 10 "github.com/MetalBlockchain/metalgo/api" 11 "github.com/MetalBlockchain/metalgo/ids" 12 "github.com/MetalBlockchain/metalgo/snow/validators" 13 "github.com/MetalBlockchain/metalgo/utils/constants" 14 "github.com/MetalBlockchain/metalgo/utils/crypto/secp256k1" 15 "github.com/MetalBlockchain/metalgo/utils/formatting" 16 "github.com/MetalBlockchain/metalgo/utils/formatting/address" 17 "github.com/MetalBlockchain/metalgo/utils/json" 18 "github.com/MetalBlockchain/metalgo/utils/rpc" 19 "github.com/MetalBlockchain/metalgo/vms/platformvm/status" 20 ) 21 22 var _ Client = (*client)(nil) 23 24 // Client interface for interacting with the P Chain endpoint 25 type Client interface { 26 // GetHeight returns the current block height of the P Chain 27 GetHeight(ctx context.Context, options ...rpc.Option) (uint64, error) 28 // ExportKey returns the private key corresponding to [address] from [user]'s account 29 // 30 // Deprecated: Keys should no longer be stored on the node. 31 ExportKey(ctx context.Context, user api.UserPass, address ids.ShortID, options ...rpc.Option) (*secp256k1.PrivateKey, error) 32 // GetBalance returns the balance of [addrs] on the P Chain 33 // 34 // Deprecated: GetUTXOs should be used instead. 35 GetBalance(ctx context.Context, addrs []ids.ShortID, options ...rpc.Option) (*GetBalanceResponse, error) 36 // ListAddresses returns an array of platform addresses controlled by [user] 37 // 38 // Deprecated: Keys should no longer be stored on the node. 39 ListAddresses(ctx context.Context, user api.UserPass, options ...rpc.Option) ([]ids.ShortID, error) 40 // GetUTXOs returns the byte representation of the UTXOs controlled by [addrs] 41 GetUTXOs( 42 ctx context.Context, 43 addrs []ids.ShortID, 44 limit uint32, 45 startAddress ids.ShortID, 46 startUTXOID ids.ID, 47 options ...rpc.Option, 48 ) ([][]byte, ids.ShortID, ids.ID, error) 49 // GetAtomicUTXOs returns the byte representation of the atomic UTXOs controlled by [addrs] 50 // from [sourceChain] 51 GetAtomicUTXOs( 52 ctx context.Context, 53 addrs []ids.ShortID, 54 sourceChain string, 55 limit uint32, 56 startAddress ids.ShortID, 57 startUTXOID ids.ID, 58 options ...rpc.Option, 59 ) ([][]byte, ids.ShortID, ids.ID, error) 60 // GetSubnet returns information about the specified subnet 61 GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (GetSubnetClientResponse, error) 62 // GetSubnets returns information about the specified subnets 63 // 64 // Deprecated: Subnets should be fetched from a dedicated indexer. 65 GetSubnets(ctx context.Context, subnetIDs []ids.ID, options ...rpc.Option) ([]ClientSubnet, error) 66 // GetStakingAssetID returns the assetID of the asset used for staking on 67 // subnet corresponding to [subnetID] 68 GetStakingAssetID(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (ids.ID, error) 69 // GetCurrentValidators returns the list of current validators for subnet with ID [subnetID] 70 GetCurrentValidators(ctx context.Context, subnetID ids.ID, nodeIDs []ids.NodeID, options ...rpc.Option) ([]ClientPermissionlessValidator, error) 71 // GetCurrentSupply returns an upper bound on the supply of AVAX in the system along with the P-chain height 72 GetCurrentSupply(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) 73 // SampleValidators returns the nodeIDs of a sample of [sampleSize] validators from the current validator set for subnet with ID [subnetID] 74 SampleValidators(ctx context.Context, subnetID ids.ID, sampleSize uint16, options ...rpc.Option) ([]ids.NodeID, error) 75 // GetBlockchainStatus returns the current status of blockchain with ID: [blockchainID] 76 GetBlockchainStatus(ctx context.Context, blockchainID string, options ...rpc.Option) (status.BlockchainStatus, error) 77 // ValidatedBy returns the ID of the Subnet that validates [blockchainID] 78 ValidatedBy(ctx context.Context, blockchainID ids.ID, options ...rpc.Option) (ids.ID, error) 79 // Validates returns the list of blockchains that are validated by the subnet with ID [subnetID] 80 Validates(ctx context.Context, subnetID ids.ID, options ...rpc.Option) ([]ids.ID, error) 81 // GetBlockchains returns the list of blockchains on the platform 82 // 83 // Deprecated: Blockchains should be fetched from a dedicated indexer. 84 GetBlockchains(ctx context.Context, options ...rpc.Option) ([]APIBlockchain, error) 85 // IssueTx issues the transaction and returns its txID 86 IssueTx(ctx context.Context, tx []byte, options ...rpc.Option) (ids.ID, error) 87 // GetTx returns the byte representation of the transaction corresponding to [txID] 88 GetTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) 89 // GetTxStatus returns the status of the transaction corresponding to [txID] 90 GetTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (*GetTxStatusResponse, error) 91 // GetStake returns the amount of nAVAX that [addrs] have cumulatively 92 // staked on the Primary Network. 93 // 94 // Deprecated: Stake should be calculated using GetTx and GetCurrentValidators. 95 GetStake( 96 ctx context.Context, 97 addrs []ids.ShortID, 98 validatorsOnly bool, 99 options ...rpc.Option, 100 ) (map[ids.ID]uint64, [][]byte, error) 101 // GetMinStake returns the minimum staking amount in nAVAX for validators 102 // and delegators respectively 103 GetMinStake(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) 104 // GetTotalStake returns the total amount (in nAVAX) staked on the network 105 GetTotalStake(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, error) 106 // GetRewardUTXOs returns the reward UTXOs for a transaction 107 // 108 // Deprecated: GetRewardUTXOs should be fetched from a dedicated indexer. 109 GetRewardUTXOs(context.Context, *api.GetTxArgs, ...rpc.Option) ([][]byte, error) 110 // GetTimestamp returns the current chain timestamp 111 GetTimestamp(ctx context.Context, options ...rpc.Option) (time.Time, error) 112 // GetValidatorsAt returns the weights of the validator set of a provided 113 // subnet at the specified height. 114 GetValidatorsAt( 115 ctx context.Context, 116 subnetID ids.ID, 117 height uint64, 118 options ...rpc.Option, 119 ) (map[ids.NodeID]*validators.GetValidatorOutput, error) 120 // GetBlock returns the block with the given id. 121 GetBlock(ctx context.Context, blockID ids.ID, options ...rpc.Option) ([]byte, error) 122 // GetBlockByHeight returns the block at the given [height]. 123 GetBlockByHeight(ctx context.Context, height uint64, options ...rpc.Option) ([]byte, error) 124 } 125 126 // Client implementation for interacting with the P Chain endpoint 127 type client struct { 128 requester rpc.EndpointRequester 129 } 130 131 // NewClient returns a Client for interacting with the P Chain endpoint 132 func NewClient(uri string) Client { 133 return &client{requester: rpc.NewEndpointRequester( 134 uri + "/ext/P", 135 )} 136 } 137 138 func (c *client) GetHeight(ctx context.Context, options ...rpc.Option) (uint64, error) { 139 res := &api.GetHeightResponse{} 140 err := c.requester.SendRequest(ctx, "platform.getHeight", struct{}{}, res, options...) 141 return uint64(res.Height), err 142 } 143 144 func (c *client) ExportKey(ctx context.Context, user api.UserPass, address ids.ShortID, options ...rpc.Option) (*secp256k1.PrivateKey, error) { 145 res := &ExportKeyReply{} 146 err := c.requester.SendRequest(ctx, "platform.exportKey", &ExportKeyArgs{ 147 UserPass: user, 148 Address: address.String(), 149 }, res, options...) 150 return res.PrivateKey, err 151 } 152 153 func (c *client) GetBalance(ctx context.Context, addrs []ids.ShortID, options ...rpc.Option) (*GetBalanceResponse, error) { 154 res := &GetBalanceResponse{} 155 err := c.requester.SendRequest(ctx, "platform.getBalance", &GetBalanceRequest{ 156 Addresses: ids.ShortIDsToStrings(addrs), 157 }, res, options...) 158 return res, err 159 } 160 161 func (c *client) ListAddresses(ctx context.Context, user api.UserPass, options ...rpc.Option) ([]ids.ShortID, error) { 162 res := &api.JSONAddresses{} 163 err := c.requester.SendRequest(ctx, "platform.listAddresses", &user, res, options...) 164 if err != nil { 165 return nil, err 166 } 167 return address.ParseToIDs(res.Addresses) 168 } 169 170 func (c *client) GetUTXOs( 171 ctx context.Context, 172 addrs []ids.ShortID, 173 limit uint32, 174 startAddress ids.ShortID, 175 startUTXOID ids.ID, 176 options ...rpc.Option, 177 ) ([][]byte, ids.ShortID, ids.ID, error) { 178 return c.GetAtomicUTXOs(ctx, addrs, "", limit, startAddress, startUTXOID, options...) 179 } 180 181 func (c *client) GetAtomicUTXOs( 182 ctx context.Context, 183 addrs []ids.ShortID, 184 sourceChain string, 185 limit uint32, 186 startAddress ids.ShortID, 187 startUTXOID ids.ID, 188 options ...rpc.Option, 189 ) ([][]byte, ids.ShortID, ids.ID, error) { 190 res := &api.GetUTXOsReply{} 191 err := c.requester.SendRequest(ctx, "platform.getUTXOs", &api.GetUTXOsArgs{ 192 Addresses: ids.ShortIDsToStrings(addrs), 193 SourceChain: sourceChain, 194 Limit: json.Uint32(limit), 195 StartIndex: api.Index{ 196 Address: startAddress.String(), 197 UTXO: startUTXOID.String(), 198 }, 199 Encoding: formatting.Hex, 200 }, res, options...) 201 if err != nil { 202 return nil, ids.ShortID{}, ids.Empty, err 203 } 204 205 utxos := make([][]byte, len(res.UTXOs)) 206 for i, utxo := range res.UTXOs { 207 utxoBytes, err := formatting.Decode(res.Encoding, utxo) 208 if err != nil { 209 return nil, ids.ShortID{}, ids.Empty, err 210 } 211 utxos[i] = utxoBytes 212 } 213 endAddr, err := address.ParseToID(res.EndIndex.Address) 214 if err != nil { 215 return nil, ids.ShortID{}, ids.Empty, err 216 } 217 endUTXOID, err := ids.FromString(res.EndIndex.UTXO) 218 return utxos, endAddr, endUTXOID, err 219 } 220 221 // GetSubnetClientResponse is the response from calling GetSubnet on the client 222 type GetSubnetClientResponse struct { 223 // whether it is permissioned or not 224 IsPermissioned bool 225 // subnet auth information for a permissioned subnet 226 ControlKeys []ids.ShortID 227 Threshold uint32 228 Locktime uint64 229 // subnet transformation tx ID for a permissionless subnet 230 SubnetTransformationTxID ids.ID 231 } 232 233 func (c *client) GetSubnet(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (GetSubnetClientResponse, error) { 234 res := &GetSubnetResponse{} 235 err := c.requester.SendRequest(ctx, "platform.getSubnet", &GetSubnetArgs{ 236 SubnetID: subnetID, 237 }, res, options...) 238 if err != nil { 239 return GetSubnetClientResponse{}, err 240 } 241 controlKeys, err := address.ParseToIDs(res.ControlKeys) 242 if err != nil { 243 return GetSubnetClientResponse{}, err 244 } 245 246 return GetSubnetClientResponse{ 247 IsPermissioned: res.IsPermissioned, 248 ControlKeys: controlKeys, 249 Threshold: uint32(res.Threshold), 250 Locktime: uint64(res.Locktime), 251 SubnetTransformationTxID: res.SubnetTransformationTxID, 252 }, nil 253 } 254 255 // ClientSubnet is a representation of a subnet used in client methods 256 type ClientSubnet struct { 257 // ID of the subnet 258 ID ids.ID 259 // Each element of [ControlKeys] the address of a public key. 260 // A transaction to add a validator to this subnet requires 261 // signatures from [Threshold] of these keys to be valid. 262 ControlKeys []ids.ShortID 263 Threshold uint32 264 } 265 266 func (c *client) GetSubnets(ctx context.Context, ids []ids.ID, options ...rpc.Option) ([]ClientSubnet, error) { 267 res := &GetSubnetsResponse{} 268 err := c.requester.SendRequest(ctx, "platform.getSubnets", &GetSubnetsArgs{ 269 IDs: ids, 270 }, res, options...) 271 if err != nil { 272 return nil, err 273 } 274 subnets := make([]ClientSubnet, len(res.Subnets)) 275 for i, apiSubnet := range res.Subnets { 276 controlKeys, err := address.ParseToIDs(apiSubnet.ControlKeys) 277 if err != nil { 278 return nil, err 279 } 280 281 subnets[i] = ClientSubnet{ 282 ID: apiSubnet.ID, 283 ControlKeys: controlKeys, 284 Threshold: uint32(apiSubnet.Threshold), 285 } 286 } 287 return subnets, nil 288 } 289 290 func (c *client) GetStakingAssetID(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (ids.ID, error) { 291 res := &GetStakingAssetIDResponse{} 292 err := c.requester.SendRequest(ctx, "platform.getStakingAssetID", &GetStakingAssetIDArgs{ 293 SubnetID: subnetID, 294 }, res, options...) 295 return res.AssetID, err 296 } 297 298 func (c *client) GetCurrentValidators( 299 ctx context.Context, 300 subnetID ids.ID, 301 nodeIDs []ids.NodeID, 302 options ...rpc.Option, 303 ) ([]ClientPermissionlessValidator, error) { 304 res := &GetCurrentValidatorsReply{} 305 err := c.requester.SendRequest(ctx, "platform.getCurrentValidators", &GetCurrentValidatorsArgs{ 306 SubnetID: subnetID, 307 NodeIDs: nodeIDs, 308 }, res, options...) 309 if err != nil { 310 return nil, err 311 } 312 return getClientPermissionlessValidators(res.Validators) 313 } 314 315 func (c *client) GetCurrentSupply(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) { 316 res := &GetCurrentSupplyReply{} 317 err := c.requester.SendRequest(ctx, "platform.getCurrentSupply", &GetCurrentSupplyArgs{ 318 SubnetID: subnetID, 319 }, res, options...) 320 return uint64(res.Supply), uint64(res.Height), err 321 } 322 323 func (c *client) SampleValidators(ctx context.Context, subnetID ids.ID, sampleSize uint16, options ...rpc.Option) ([]ids.NodeID, error) { 324 res := &SampleValidatorsReply{} 325 err := c.requester.SendRequest(ctx, "platform.sampleValidators", &SampleValidatorsArgs{ 326 SubnetID: subnetID, 327 Size: json.Uint16(sampleSize), 328 }, res, options...) 329 return res.Validators, err 330 } 331 332 func (c *client) GetBlockchainStatus(ctx context.Context, blockchainID string, options ...rpc.Option) (status.BlockchainStatus, error) { 333 res := &GetBlockchainStatusReply{} 334 err := c.requester.SendRequest(ctx, "platform.getBlockchainStatus", &GetBlockchainStatusArgs{ 335 BlockchainID: blockchainID, 336 }, res, options...) 337 return res.Status, err 338 } 339 340 func (c *client) ValidatedBy(ctx context.Context, blockchainID ids.ID, options ...rpc.Option) (ids.ID, error) { 341 res := &ValidatedByResponse{} 342 err := c.requester.SendRequest(ctx, "platform.validatedBy", &ValidatedByArgs{ 343 BlockchainID: blockchainID, 344 }, res, options...) 345 return res.SubnetID, err 346 } 347 348 func (c *client) Validates(ctx context.Context, subnetID ids.ID, options ...rpc.Option) ([]ids.ID, error) { 349 res := &ValidatesResponse{} 350 err := c.requester.SendRequest(ctx, "platform.validates", &ValidatesArgs{ 351 SubnetID: subnetID, 352 }, res, options...) 353 return res.BlockchainIDs, err 354 } 355 356 func (c *client) GetBlockchains(ctx context.Context, options ...rpc.Option) ([]APIBlockchain, error) { 357 res := &GetBlockchainsResponse{} 358 err := c.requester.SendRequest(ctx, "platform.getBlockchains", struct{}{}, res, options...) 359 return res.Blockchains, err 360 } 361 362 func (c *client) IssueTx(ctx context.Context, txBytes []byte, options ...rpc.Option) (ids.ID, error) { 363 txStr, err := formatting.Encode(formatting.Hex, txBytes) 364 if err != nil { 365 return ids.Empty, err 366 } 367 368 res := &api.JSONTxID{} 369 err = c.requester.SendRequest(ctx, "platform.issueTx", &api.FormattedTx{ 370 Tx: txStr, 371 Encoding: formatting.Hex, 372 }, res, options...) 373 return res.TxID, err 374 } 375 376 func (c *client) GetTx(ctx context.Context, txID ids.ID, options ...rpc.Option) ([]byte, error) { 377 res := &api.FormattedTx{} 378 err := c.requester.SendRequest(ctx, "platform.getTx", &api.GetTxArgs{ 379 TxID: txID, 380 Encoding: formatting.Hex, 381 }, res, options...) 382 if err != nil { 383 return nil, err 384 } 385 return formatting.Decode(res.Encoding, res.Tx) 386 } 387 388 func (c *client) GetTxStatus(ctx context.Context, txID ids.ID, options ...rpc.Option) (*GetTxStatusResponse, error) { 389 res := &GetTxStatusResponse{} 390 err := c.requester.SendRequest( 391 ctx, 392 "platform.getTxStatus", 393 &GetTxStatusArgs{ 394 TxID: txID, 395 }, 396 res, 397 options..., 398 ) 399 return res, err 400 } 401 402 func (c *client) GetStake( 403 ctx context.Context, 404 addrs []ids.ShortID, 405 validatorsOnly bool, 406 options ...rpc.Option, 407 ) (map[ids.ID]uint64, [][]byte, error) { 408 res := &GetStakeReply{} 409 err := c.requester.SendRequest(ctx, "platform.getStake", &GetStakeArgs{ 410 JSONAddresses: api.JSONAddresses{ 411 Addresses: ids.ShortIDsToStrings(addrs), 412 }, 413 ValidatorsOnly: validatorsOnly, 414 Encoding: formatting.Hex, 415 }, res, options...) 416 if err != nil { 417 return nil, nil, err 418 } 419 420 staked := make(map[ids.ID]uint64, len(res.Stakeds)) 421 for assetID, amount := range res.Stakeds { 422 staked[assetID] = uint64(amount) 423 } 424 425 outputs := make([][]byte, len(res.Outputs)) 426 for i, outputStr := range res.Outputs { 427 output, err := formatting.Decode(res.Encoding, outputStr) 428 if err != nil { 429 return nil, nil, err 430 } 431 outputs[i] = output 432 } 433 return staked, outputs, err 434 } 435 436 func (c *client) GetMinStake(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, uint64, error) { 437 res := &GetMinStakeReply{} 438 err := c.requester.SendRequest(ctx, "platform.getMinStake", &GetMinStakeArgs{ 439 SubnetID: subnetID, 440 }, res, options...) 441 return uint64(res.MinValidatorStake), uint64(res.MinDelegatorStake), err 442 } 443 444 func (c *client) GetTotalStake(ctx context.Context, subnetID ids.ID, options ...rpc.Option) (uint64, error) { 445 res := &GetTotalStakeReply{} 446 err := c.requester.SendRequest(ctx, "platform.getTotalStake", &GetTotalStakeArgs{ 447 SubnetID: subnetID, 448 }, res, options...) 449 var amount json.Uint64 450 if subnetID == constants.PrimaryNetworkID { 451 amount = res.Stake 452 } else { 453 amount = res.Weight 454 } 455 return uint64(amount), err 456 } 457 458 func (c *client) GetRewardUTXOs(ctx context.Context, args *api.GetTxArgs, options ...rpc.Option) ([][]byte, error) { 459 res := &GetRewardUTXOsReply{} 460 err := c.requester.SendRequest(ctx, "platform.getRewardUTXOs", args, res, options...) 461 if err != nil { 462 return nil, err 463 } 464 utxos := make([][]byte, len(res.UTXOs)) 465 for i, utxoStr := range res.UTXOs { 466 utxoBytes, err := formatting.Decode(res.Encoding, utxoStr) 467 if err != nil { 468 return nil, err 469 } 470 utxos[i] = utxoBytes 471 } 472 return utxos, err 473 } 474 475 func (c *client) GetTimestamp(ctx context.Context, options ...rpc.Option) (time.Time, error) { 476 res := &GetTimestampReply{} 477 err := c.requester.SendRequest(ctx, "platform.getTimestamp", struct{}{}, res, options...) 478 return res.Timestamp, err 479 } 480 481 func (c *client) GetValidatorsAt( 482 ctx context.Context, 483 subnetID ids.ID, 484 height uint64, 485 options ...rpc.Option, 486 ) (map[ids.NodeID]*validators.GetValidatorOutput, error) { 487 res := &GetValidatorsAtReply{} 488 err := c.requester.SendRequest(ctx, "platform.getValidatorsAt", &GetValidatorsAtArgs{ 489 SubnetID: subnetID, 490 Height: json.Uint64(height), 491 }, res, options...) 492 return res.Validators, err 493 } 494 495 func (c *client) GetBlock(ctx context.Context, blockID ids.ID, options ...rpc.Option) ([]byte, error) { 496 res := &api.FormattedBlock{} 497 if err := c.requester.SendRequest(ctx, "platform.getBlock", &api.GetBlockArgs{ 498 BlockID: blockID, 499 Encoding: formatting.Hex, 500 }, res, options...); err != nil { 501 return nil, err 502 } 503 return formatting.Decode(res.Encoding, res.Block) 504 } 505 506 func (c *client) GetBlockByHeight(ctx context.Context, height uint64, options ...rpc.Option) ([]byte, error) { 507 res := &api.FormattedBlock{} 508 err := c.requester.SendRequest(ctx, "platform.getBlockByHeight", &api.GetBlockByHeightArgs{ 509 Height: json.Uint64(height), 510 Encoding: formatting.HexNC, 511 }, res, options...) 512 if err != nil { 513 return nil, err 514 } 515 return formatting.Decode(res.Encoding, res.Block) 516 } 517 518 func AwaitTxAccepted( 519 c Client, 520 ctx context.Context, 521 txID ids.ID, 522 freq time.Duration, 523 options ...rpc.Option, 524 ) error { 525 ticker := time.NewTicker(freq) 526 defer ticker.Stop() 527 528 for { 529 res, err := c.GetTxStatus(ctx, txID, options...) 530 if err != nil { 531 return err 532 } 533 534 switch res.Status { 535 case status.Committed, status.Aborted: 536 return nil 537 } 538 539 select { 540 case <-ticker.C: 541 case <-ctx.Done(): 542 return ctx.Err() 543 } 544 } 545 }