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  }