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