github.com/ava-labs/avalanchego@v1.11.11/vms/platformvm/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 platformvm
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"maps"
    12  	"math"
    13  	"net/http"
    14  	"time"
    15  
    16  	"go.uber.org/zap"
    17  
    18  	"github.com/ava-labs/avalanchego/api"
    19  	"github.com/ava-labs/avalanchego/cache"
    20  	"github.com/ava-labs/avalanchego/database"
    21  	"github.com/ava-labs/avalanchego/ids"
    22  	"github.com/ava-labs/avalanchego/snow/validators"
    23  	"github.com/ava-labs/avalanchego/utils"
    24  	"github.com/ava-labs/avalanchego/utils/constants"
    25  	"github.com/ava-labs/avalanchego/utils/crypto/bls"
    26  	"github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
    27  	"github.com/ava-labs/avalanchego/utils/formatting"
    28  	"github.com/ava-labs/avalanchego/utils/logging"
    29  	"github.com/ava-labs/avalanchego/utils/set"
    30  	"github.com/ava-labs/avalanchego/vms/components/avax"
    31  	"github.com/ava-labs/avalanchego/vms/components/gas"
    32  	"github.com/ava-labs/avalanchego/vms/components/keystore"
    33  	"github.com/ava-labs/avalanchego/vms/platformvm/fx"
    34  	"github.com/ava-labs/avalanchego/vms/platformvm/reward"
    35  	"github.com/ava-labs/avalanchego/vms/platformvm/signer"
    36  	"github.com/ava-labs/avalanchego/vms/platformvm/stakeable"
    37  	"github.com/ava-labs/avalanchego/vms/platformvm/state"
    38  	"github.com/ava-labs/avalanchego/vms/platformvm/status"
    39  	"github.com/ava-labs/avalanchego/vms/platformvm/txs"
    40  	"github.com/ava-labs/avalanchego/vms/secp256k1fx"
    41  
    42  	avajson "github.com/ava-labs/avalanchego/utils/json"
    43  	safemath "github.com/ava-labs/avalanchego/utils/math"
    44  	platformapi "github.com/ava-labs/avalanchego/vms/platformvm/api"
    45  )
    46  
    47  const (
    48  	// Max number of addresses that can be passed in as argument to GetUTXOs
    49  	maxGetUTXOsAddrs = 1024
    50  
    51  	// Max number of addresses that can be passed in as argument to GetStake
    52  	maxGetStakeAddrs = 256
    53  
    54  	// Max number of items allowed in a page
    55  	maxPageSize = 1024
    56  
    57  	// Note: Staker attributes cache should be large enough so that no evictions
    58  	// happen when the API loops through all stakers.
    59  	stakerAttributesCacheSize = 100_000
    60  )
    61  
    62  var (
    63  	errMissingDecisionBlock       = errors.New("should have a decision block within the past two blocks")
    64  	errPrimaryNetworkIsNotASubnet = errors.New("the primary network isn't a subnet")
    65  	errNoAddresses                = errors.New("no addresses provided")
    66  	errMissingBlockchainID        = errors.New("argument 'blockchainID' not given")
    67  )
    68  
    69  // Service defines the API calls that can be made to the platform chain
    70  type Service struct {
    71  	vm                    *VM
    72  	addrManager           avax.AddressManager
    73  	stakerAttributesCache *cache.LRU[ids.ID, *stakerAttributes]
    74  }
    75  
    76  // All attributes are optional and may not be filled for each stakerTx.
    77  type stakerAttributes struct {
    78  	shares                 uint32
    79  	rewardsOwner           fx.Owner
    80  	validationRewardsOwner fx.Owner
    81  	delegationRewardsOwner fx.Owner
    82  	proofOfPossession      *signer.ProofOfPossession
    83  }
    84  
    85  // GetHeight returns the height of the last accepted block
    86  func (s *Service) GetHeight(r *http.Request, _ *struct{}, response *api.GetHeightResponse) error {
    87  	s.vm.ctx.Log.Debug("API called",
    88  		zap.String("service", "platform"),
    89  		zap.String("method", "getHeight"),
    90  	)
    91  
    92  	s.vm.ctx.Lock.Lock()
    93  	defer s.vm.ctx.Lock.Unlock()
    94  
    95  	ctx := r.Context()
    96  	height, err := s.vm.GetCurrentHeight(ctx)
    97  	response.Height = avajson.Uint64(height)
    98  	return err
    99  }
   100  
   101  // ExportKeyArgs are arguments for ExportKey
   102  type ExportKeyArgs struct {
   103  	api.UserPass
   104  	Address string `json:"address"`
   105  }
   106  
   107  // ExportKeyReply is the response for ExportKey
   108  type ExportKeyReply struct {
   109  	// The decrypted PrivateKey for the Address provided in the arguments
   110  	PrivateKey *secp256k1.PrivateKey `json:"privateKey"`
   111  }
   112  
   113  // ExportKey returns a private key from the provided user
   114  func (s *Service) ExportKey(_ *http.Request, args *ExportKeyArgs, reply *ExportKeyReply) error {
   115  	s.vm.ctx.Log.Warn("deprecated API called",
   116  		zap.String("service", "platform"),
   117  		zap.String("method", "exportKey"),
   118  		logging.UserString("username", args.Username),
   119  	)
   120  
   121  	address, err := avax.ParseServiceAddress(s.addrManager, args.Address)
   122  	if err != nil {
   123  		return fmt.Errorf("couldn't parse %s to address: %w", args.Address, err)
   124  	}
   125  
   126  	s.vm.ctx.Lock.Lock()
   127  	defer s.vm.ctx.Lock.Unlock()
   128  
   129  	user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	reply.PrivateKey, err = user.GetKey(address)
   135  	if err != nil {
   136  		// Drop any potential error closing the user to report the original
   137  		// error
   138  		_ = user.Close()
   139  		return fmt.Errorf("problem retrieving private key: %w", err)
   140  	}
   141  	return user.Close()
   142  }
   143  
   144  type GetBalanceRequest struct {
   145  	Addresses []string `json:"addresses"`
   146  }
   147  
   148  // Note: We explicitly duplicate AVAX out of the maps to ensure backwards
   149  // compatibility.
   150  type GetBalanceResponse struct {
   151  	// Balance, in nAVAX, of the address
   152  	Balance             avajson.Uint64            `json:"balance"`
   153  	Unlocked            avajson.Uint64            `json:"unlocked"`
   154  	LockedStakeable     avajson.Uint64            `json:"lockedStakeable"`
   155  	LockedNotStakeable  avajson.Uint64            `json:"lockedNotStakeable"`
   156  	Balances            map[ids.ID]avajson.Uint64 `json:"balances"`
   157  	Unlockeds           map[ids.ID]avajson.Uint64 `json:"unlockeds"`
   158  	LockedStakeables    map[ids.ID]avajson.Uint64 `json:"lockedStakeables"`
   159  	LockedNotStakeables map[ids.ID]avajson.Uint64 `json:"lockedNotStakeables"`
   160  	UTXOIDs             []*avax.UTXOID            `json:"utxoIDs"`
   161  }
   162  
   163  // GetBalance gets the balance of an address
   164  func (s *Service) GetBalance(_ *http.Request, args *GetBalanceRequest, response *GetBalanceResponse) error {
   165  	s.vm.ctx.Log.Debug("deprecated API called",
   166  		zap.String("service", "platform"),
   167  		zap.String("method", "getBalance"),
   168  		logging.UserStrings("addresses", args.Addresses),
   169  	)
   170  
   171  	addrs, err := avax.ParseServiceAddresses(s.addrManager, args.Addresses)
   172  	if err != nil {
   173  		return err
   174  	}
   175  
   176  	s.vm.ctx.Lock.Lock()
   177  	defer s.vm.ctx.Lock.Unlock()
   178  
   179  	utxos, err := avax.GetAllUTXOs(s.vm.state, addrs)
   180  	if err != nil {
   181  		return fmt.Errorf("couldn't get UTXO set of %v: %w", args.Addresses, err)
   182  	}
   183  
   184  	currentTime := s.vm.clock.Unix()
   185  
   186  	unlockeds := map[ids.ID]uint64{}
   187  	lockedStakeables := map[ids.ID]uint64{}
   188  	lockedNotStakeables := map[ids.ID]uint64{}
   189  
   190  utxoFor:
   191  	for _, utxo := range utxos {
   192  		assetID := utxo.AssetID()
   193  		switch out := utxo.Out.(type) {
   194  		case *secp256k1fx.TransferOutput:
   195  			if out.Locktime <= currentTime {
   196  				newBalance, err := safemath.Add(unlockeds[assetID], out.Amount())
   197  				if err != nil {
   198  					unlockeds[assetID] = math.MaxUint64
   199  				} else {
   200  					unlockeds[assetID] = newBalance
   201  				}
   202  			} else {
   203  				newBalance, err := safemath.Add(lockedNotStakeables[assetID], out.Amount())
   204  				if err != nil {
   205  					lockedNotStakeables[assetID] = math.MaxUint64
   206  				} else {
   207  					lockedNotStakeables[assetID] = newBalance
   208  				}
   209  			}
   210  		case *stakeable.LockOut:
   211  			innerOut, ok := out.TransferableOut.(*secp256k1fx.TransferOutput)
   212  			switch {
   213  			case !ok:
   214  				s.vm.ctx.Log.Warn("unexpected output type in UTXO",
   215  					zap.String("type", fmt.Sprintf("%T", out.TransferableOut)),
   216  				)
   217  				continue utxoFor
   218  			case innerOut.Locktime > currentTime:
   219  				newBalance, err := safemath.Add(lockedNotStakeables[assetID], out.Amount())
   220  				if err != nil {
   221  					lockedNotStakeables[assetID] = math.MaxUint64
   222  				} else {
   223  					lockedNotStakeables[assetID] = newBalance
   224  				}
   225  			case out.Locktime <= currentTime:
   226  				newBalance, err := safemath.Add(unlockeds[assetID], out.Amount())
   227  				if err != nil {
   228  					unlockeds[assetID] = math.MaxUint64
   229  				} else {
   230  					unlockeds[assetID] = newBalance
   231  				}
   232  			default:
   233  				newBalance, err := safemath.Add(lockedStakeables[assetID], out.Amount())
   234  				if err != nil {
   235  					lockedStakeables[assetID] = math.MaxUint64
   236  				} else {
   237  					lockedStakeables[assetID] = newBalance
   238  				}
   239  			}
   240  		default:
   241  			continue utxoFor
   242  		}
   243  
   244  		response.UTXOIDs = append(response.UTXOIDs, &utxo.UTXOID)
   245  	}
   246  
   247  	balances := maps.Clone(lockedStakeables)
   248  	for assetID, amount := range lockedNotStakeables {
   249  		newBalance, err := safemath.Add(balances[assetID], amount)
   250  		if err != nil {
   251  			balances[assetID] = math.MaxUint64
   252  		} else {
   253  			balances[assetID] = newBalance
   254  		}
   255  	}
   256  	for assetID, amount := range unlockeds {
   257  		newBalance, err := safemath.Add(balances[assetID], amount)
   258  		if err != nil {
   259  			balances[assetID] = math.MaxUint64
   260  		} else {
   261  			balances[assetID] = newBalance
   262  		}
   263  	}
   264  
   265  	response.Balances = newJSONBalanceMap(balances)
   266  	response.Unlockeds = newJSONBalanceMap(unlockeds)
   267  	response.LockedStakeables = newJSONBalanceMap(lockedStakeables)
   268  	response.LockedNotStakeables = newJSONBalanceMap(lockedNotStakeables)
   269  	response.Balance = response.Balances[s.vm.ctx.AVAXAssetID]
   270  	response.Unlocked = response.Unlockeds[s.vm.ctx.AVAXAssetID]
   271  	response.LockedStakeable = response.LockedStakeables[s.vm.ctx.AVAXAssetID]
   272  	response.LockedNotStakeable = response.LockedNotStakeables[s.vm.ctx.AVAXAssetID]
   273  	return nil
   274  }
   275  
   276  func newJSONBalanceMap(balanceMap map[ids.ID]uint64) map[ids.ID]avajson.Uint64 {
   277  	jsonBalanceMap := make(map[ids.ID]avajson.Uint64, len(balanceMap))
   278  	for assetID, amount := range balanceMap {
   279  		jsonBalanceMap[assetID] = avajson.Uint64(amount)
   280  	}
   281  	return jsonBalanceMap
   282  }
   283  
   284  // ListAddresses returns the addresses controlled by [args.Username]
   285  func (s *Service) ListAddresses(_ *http.Request, args *api.UserPass, response *api.JSONAddresses) error {
   286  	s.vm.ctx.Log.Warn("deprecated API called",
   287  		zap.String("service", "platform"),
   288  		zap.String("method", "listAddresses"),
   289  		logging.UserString("username", args.Username),
   290  	)
   291  
   292  	s.vm.ctx.Lock.Lock()
   293  	defer s.vm.ctx.Lock.Unlock()
   294  
   295  	user, err := keystore.NewUserFromKeystore(s.vm.ctx.Keystore, args.Username, args.Password)
   296  	if err != nil {
   297  		return err
   298  	}
   299  	defer user.Close()
   300  
   301  	addresses, err := user.GetAddresses()
   302  	if err != nil {
   303  		return fmt.Errorf("couldn't get addresses: %w", err)
   304  	}
   305  	response.Addresses = make([]string, len(addresses))
   306  	for i, addr := range addresses {
   307  		response.Addresses[i], err = s.addrManager.FormatLocalAddress(addr)
   308  		if err != nil {
   309  			return fmt.Errorf("problem formatting address: %w", err)
   310  		}
   311  	}
   312  	return user.Close()
   313  }
   314  
   315  // Index is an address and an associated UTXO.
   316  // Marks a starting or stopping point when fetching UTXOs. Used for pagination.
   317  type Index struct {
   318  	Address string `json:"address"` // The address as a string
   319  	UTXO    string `json:"utxo"`    // The UTXO ID as a string
   320  }
   321  
   322  // GetUTXOs returns the UTXOs controlled by the given addresses
   323  func (s *Service) GetUTXOs(_ *http.Request, args *api.GetUTXOsArgs, response *api.GetUTXOsReply) error {
   324  	s.vm.ctx.Log.Debug("API called",
   325  		zap.String("service", "platform"),
   326  		zap.String("method", "getUTXOs"),
   327  	)
   328  
   329  	if len(args.Addresses) == 0 {
   330  		return errNoAddresses
   331  	}
   332  	if len(args.Addresses) > maxGetUTXOsAddrs {
   333  		return fmt.Errorf("number of addresses given, %d, exceeds maximum, %d", len(args.Addresses), maxGetUTXOsAddrs)
   334  	}
   335  
   336  	var sourceChain ids.ID
   337  	if args.SourceChain == "" {
   338  		sourceChain = s.vm.ctx.ChainID
   339  	} else {
   340  		chainID, err := s.vm.ctx.BCLookup.Lookup(args.SourceChain)
   341  		if err != nil {
   342  			return fmt.Errorf("problem parsing source chainID %q: %w", args.SourceChain, err)
   343  		}
   344  		sourceChain = chainID
   345  	}
   346  
   347  	addrSet, err := avax.ParseServiceAddresses(s.addrManager, args.Addresses)
   348  	if err != nil {
   349  		return err
   350  	}
   351  
   352  	startAddr := ids.ShortEmpty
   353  	startUTXO := ids.Empty
   354  	if args.StartIndex.Address != "" || args.StartIndex.UTXO != "" {
   355  		startAddr, err = avax.ParseServiceAddress(s.addrManager, args.StartIndex.Address)
   356  		if err != nil {
   357  			return fmt.Errorf("couldn't parse start index address %q: %w", args.StartIndex.Address, err)
   358  		}
   359  		startUTXO, err = ids.FromString(args.StartIndex.UTXO)
   360  		if err != nil {
   361  			return fmt.Errorf("couldn't parse start index utxo: %w", err)
   362  		}
   363  	}
   364  
   365  	var (
   366  		utxos     []*avax.UTXO
   367  		endAddr   ids.ShortID
   368  		endUTXOID ids.ID
   369  	)
   370  	limit := int(args.Limit)
   371  	if limit <= 0 || maxPageSize < limit {
   372  		limit = maxPageSize
   373  	}
   374  
   375  	s.vm.ctx.Lock.Lock()
   376  	defer s.vm.ctx.Lock.Unlock()
   377  
   378  	if sourceChain == s.vm.ctx.ChainID {
   379  		utxos, endAddr, endUTXOID, err = avax.GetPaginatedUTXOs(
   380  			s.vm.state,
   381  			addrSet,
   382  			startAddr,
   383  			startUTXO,
   384  			limit,
   385  		)
   386  	} else {
   387  		utxos, endAddr, endUTXOID, err = avax.GetAtomicUTXOs(
   388  			s.vm.ctx.SharedMemory,
   389  			txs.Codec,
   390  			sourceChain,
   391  			addrSet,
   392  			startAddr,
   393  			startUTXO,
   394  			limit,
   395  		)
   396  	}
   397  	if err != nil {
   398  		return fmt.Errorf("problem retrieving UTXOs: %w", err)
   399  	}
   400  
   401  	response.UTXOs = make([]string, len(utxos))
   402  	for i, utxo := range utxos {
   403  		bytes, err := txs.Codec.Marshal(txs.CodecVersion, utxo)
   404  		if err != nil {
   405  			return fmt.Errorf("couldn't serialize UTXO %q: %w", utxo.InputID(), err)
   406  		}
   407  		response.UTXOs[i], err = formatting.Encode(args.Encoding, bytes)
   408  		if err != nil {
   409  			return fmt.Errorf("couldn't encode UTXO %s as %s: %w", utxo.InputID(), args.Encoding, err)
   410  		}
   411  	}
   412  
   413  	endAddress, err := s.addrManager.FormatLocalAddress(endAddr)
   414  	if err != nil {
   415  		return fmt.Errorf("problem formatting address: %w", err)
   416  	}
   417  
   418  	response.EndIndex.Address = endAddress
   419  	response.EndIndex.UTXO = endUTXOID.String()
   420  	response.NumFetched = avajson.Uint64(len(utxos))
   421  	response.Encoding = args.Encoding
   422  	return nil
   423  }
   424  
   425  // GetSubnetArgs are the arguments to GetSubnet
   426  type GetSubnetArgs struct {
   427  	// ID of the subnet to retrieve information about
   428  	SubnetID ids.ID `json:"subnetID"`
   429  }
   430  
   431  // GetSubnetResponse is the response from calling GetSubnet
   432  type GetSubnetResponse struct {
   433  	// whether it is permissioned or not
   434  	IsPermissioned bool `json:"isPermissioned"`
   435  	// subnet auth information for a permissioned subnet
   436  	ControlKeys []string       `json:"controlKeys"`
   437  	Threshold   avajson.Uint32 `json:"threshold"`
   438  	Locktime    avajson.Uint64 `json:"locktime"`
   439  	// subnet transformation tx ID for a permissionless subnet
   440  	SubnetTransformationTxID ids.ID `json:"subnetTransformationTxID"`
   441  }
   442  
   443  func (s *Service) GetSubnet(_ *http.Request, args *GetSubnetArgs, response *GetSubnetResponse) error {
   444  	s.vm.ctx.Log.Debug("API called",
   445  		zap.String("service", "platform"),
   446  		zap.String("method", "getSubnet"),
   447  		zap.Stringer("subnetID", args.SubnetID),
   448  	)
   449  
   450  	if args.SubnetID == constants.PrimaryNetworkID {
   451  		return errPrimaryNetworkIsNotASubnet
   452  	}
   453  
   454  	s.vm.ctx.Lock.Lock()
   455  	defer s.vm.ctx.Lock.Unlock()
   456  
   457  	subnetOwner, err := s.vm.state.GetSubnetOwner(args.SubnetID)
   458  	if err != nil {
   459  		return err
   460  	}
   461  	owner, ok := subnetOwner.(*secp256k1fx.OutputOwners)
   462  	if !ok {
   463  		return fmt.Errorf("expected *secp256k1fx.OutputOwners but got %T", subnetOwner)
   464  	}
   465  	controlAddrs := make([]string, len(owner.Addrs))
   466  	for i, controlKeyID := range owner.Addrs {
   467  		addr, err := s.addrManager.FormatLocalAddress(controlKeyID)
   468  		if err != nil {
   469  			return fmt.Errorf("problem formatting address: %w", err)
   470  		}
   471  		controlAddrs[i] = addr
   472  	}
   473  
   474  	response.ControlKeys = controlAddrs
   475  	response.Threshold = avajson.Uint32(owner.Threshold)
   476  	response.Locktime = avajson.Uint64(owner.Locktime)
   477  
   478  	switch subnetTransformationTx, err := s.vm.state.GetSubnetTransformation(args.SubnetID); err {
   479  	case nil:
   480  		response.IsPermissioned = false
   481  		response.SubnetTransformationTxID = subnetTransformationTx.ID()
   482  	case database.ErrNotFound:
   483  		response.IsPermissioned = true
   484  		response.SubnetTransformationTxID = ids.Empty
   485  	default:
   486  		return err
   487  	}
   488  
   489  	return nil
   490  }
   491  
   492  // APISubnet is a representation of a subnet used in API calls
   493  type APISubnet struct {
   494  	// ID of the subnet
   495  	ID ids.ID `json:"id"`
   496  
   497  	// Each element of [ControlKeys] the address of a public key.
   498  	// A transaction to add a validator to this subnet requires
   499  	// signatures from [Threshold] of these keys to be valid.
   500  	ControlKeys []string       `json:"controlKeys"`
   501  	Threshold   avajson.Uint32 `json:"threshold"`
   502  }
   503  
   504  // GetSubnetsArgs are the arguments to GetSubnets
   505  type GetSubnetsArgs struct {
   506  	// IDs of the subnets to retrieve information about
   507  	// If omitted, gets all subnets
   508  	IDs []ids.ID `json:"ids"`
   509  }
   510  
   511  // GetSubnetsResponse is the response from calling GetSubnets
   512  type GetSubnetsResponse struct {
   513  	// Each element is a subnet that exists
   514  	// Null if there are no subnets other than the primary network
   515  	Subnets []APISubnet `json:"subnets"`
   516  }
   517  
   518  // GetSubnets returns the subnets whose ID are in [args.IDs]
   519  // The response will include the primary network
   520  func (s *Service) GetSubnets(_ *http.Request, args *GetSubnetsArgs, response *GetSubnetsResponse) error {
   521  	s.vm.ctx.Log.Debug("deprecated API called",
   522  		zap.String("service", "platform"),
   523  		zap.String("method", "getSubnets"),
   524  	)
   525  
   526  	s.vm.ctx.Lock.Lock()
   527  	defer s.vm.ctx.Lock.Unlock()
   528  
   529  	getAll := len(args.IDs) == 0
   530  	if getAll {
   531  		subnetIDs, err := s.vm.state.GetSubnetIDs() // all subnets
   532  		if err != nil {
   533  			return fmt.Errorf("error getting subnets from database: %w", err)
   534  		}
   535  
   536  		response.Subnets = make([]APISubnet, len(subnetIDs)+1)
   537  		for i, subnetID := range subnetIDs {
   538  			if _, err := s.vm.state.GetSubnetTransformation(subnetID); err == nil {
   539  				response.Subnets[i] = APISubnet{
   540  					ID:          subnetID,
   541  					ControlKeys: []string{},
   542  					Threshold:   avajson.Uint32(0),
   543  				}
   544  				continue
   545  			}
   546  
   547  			subnetOwner, err := s.vm.state.GetSubnetOwner(subnetID)
   548  			if err != nil {
   549  				return err
   550  			}
   551  
   552  			owner, ok := subnetOwner.(*secp256k1fx.OutputOwners)
   553  			if !ok {
   554  				return fmt.Errorf("expected *secp256k1fx.OutputOwners but got %T", subnetOwner)
   555  			}
   556  
   557  			controlAddrs := make([]string, len(owner.Addrs))
   558  			for i, controlKeyID := range owner.Addrs {
   559  				addr, err := s.addrManager.FormatLocalAddress(controlKeyID)
   560  				if err != nil {
   561  					return fmt.Errorf("problem formatting address: %w", err)
   562  				}
   563  				controlAddrs[i] = addr
   564  			}
   565  			response.Subnets[i] = APISubnet{
   566  				ID:          subnetID,
   567  				ControlKeys: controlAddrs,
   568  				Threshold:   avajson.Uint32(owner.Threshold),
   569  			}
   570  		}
   571  		// Include primary network
   572  		response.Subnets[len(subnetIDs)] = APISubnet{
   573  			ID:          constants.PrimaryNetworkID,
   574  			ControlKeys: []string{},
   575  			Threshold:   avajson.Uint32(0),
   576  		}
   577  		return nil
   578  	}
   579  
   580  	subnetSet := set.NewSet[ids.ID](len(args.IDs))
   581  	for _, subnetID := range args.IDs {
   582  		if subnetSet.Contains(subnetID) {
   583  			continue
   584  		}
   585  		subnetSet.Add(subnetID)
   586  
   587  		if subnetID == constants.PrimaryNetworkID {
   588  			response.Subnets = append(response.Subnets,
   589  				APISubnet{
   590  					ID:          constants.PrimaryNetworkID,
   591  					ControlKeys: []string{},
   592  					Threshold:   avajson.Uint32(0),
   593  				},
   594  			)
   595  			continue
   596  		}
   597  
   598  		if _, err := s.vm.state.GetSubnetTransformation(subnetID); err == nil {
   599  			response.Subnets = append(response.Subnets, APISubnet{
   600  				ID:          subnetID,
   601  				ControlKeys: []string{},
   602  				Threshold:   avajson.Uint32(0),
   603  			})
   604  			continue
   605  		}
   606  
   607  		subnetOwner, err := s.vm.state.GetSubnetOwner(subnetID)
   608  		if err == database.ErrNotFound {
   609  			continue
   610  		}
   611  		if err != nil {
   612  			return err
   613  		}
   614  
   615  		owner, ok := subnetOwner.(*secp256k1fx.OutputOwners)
   616  		if !ok {
   617  			return fmt.Errorf("expected *secp256k1fx.OutputOwners but got %T", subnetOwner)
   618  		}
   619  
   620  		controlAddrs := make([]string, len(owner.Addrs))
   621  		for i, controlKeyID := range owner.Addrs {
   622  			addr, err := s.addrManager.FormatLocalAddress(controlKeyID)
   623  			if err != nil {
   624  				return fmt.Errorf("problem formatting address: %w", err)
   625  			}
   626  			controlAddrs[i] = addr
   627  		}
   628  
   629  		response.Subnets = append(response.Subnets, APISubnet{
   630  			ID:          subnetID,
   631  			ControlKeys: controlAddrs,
   632  			Threshold:   avajson.Uint32(owner.Threshold),
   633  		})
   634  	}
   635  	return nil
   636  }
   637  
   638  // GetStakingAssetIDArgs are the arguments to GetStakingAssetID
   639  type GetStakingAssetIDArgs struct {
   640  	SubnetID ids.ID `json:"subnetID"`
   641  }
   642  
   643  // GetStakingAssetIDResponse is the response from calling GetStakingAssetID
   644  type GetStakingAssetIDResponse struct {
   645  	AssetID ids.ID `json:"assetID"`
   646  }
   647  
   648  // GetStakingAssetID returns the assetID of the token used to stake on the
   649  // provided subnet
   650  func (s *Service) GetStakingAssetID(_ *http.Request, args *GetStakingAssetIDArgs, response *GetStakingAssetIDResponse) error {
   651  	s.vm.ctx.Log.Debug("API called",
   652  		zap.String("service", "platform"),
   653  		zap.String("method", "getStakingAssetID"),
   654  	)
   655  
   656  	if args.SubnetID == constants.PrimaryNetworkID {
   657  		response.AssetID = s.vm.ctx.AVAXAssetID
   658  		return nil
   659  	}
   660  
   661  	s.vm.ctx.Lock.Lock()
   662  	defer s.vm.ctx.Lock.Unlock()
   663  
   664  	transformSubnetIntf, err := s.vm.state.GetSubnetTransformation(args.SubnetID)
   665  	if err != nil {
   666  		return fmt.Errorf(
   667  			"failed fetching subnet transformation for %s: %w",
   668  			args.SubnetID,
   669  			err,
   670  		)
   671  	}
   672  	transformSubnet, ok := transformSubnetIntf.Unsigned.(*txs.TransformSubnetTx)
   673  	if !ok {
   674  		return fmt.Errorf(
   675  			"unexpected subnet transformation tx type fetched %T",
   676  			transformSubnetIntf.Unsigned,
   677  		)
   678  	}
   679  
   680  	response.AssetID = transformSubnet.AssetID
   681  	return nil
   682  }
   683  
   684  // GetCurrentValidatorsArgs are the arguments for calling GetCurrentValidators
   685  type GetCurrentValidatorsArgs struct {
   686  	// Subnet we're listing the validators of
   687  	// If omitted, defaults to primary network
   688  	SubnetID ids.ID `json:"subnetID"`
   689  	// NodeIDs of validators to request. If [NodeIDs]
   690  	// is empty, it fetches all current validators. If
   691  	// some nodeIDs are not currently validators, they
   692  	// will be omitted from the response.
   693  	NodeIDs []ids.NodeID `json:"nodeIDs"`
   694  }
   695  
   696  // GetCurrentValidatorsReply are the results from calling GetCurrentValidators.
   697  // Each validator contains a list of delegators to itself.
   698  type GetCurrentValidatorsReply struct {
   699  	Validators []interface{} `json:"validators"`
   700  }
   701  
   702  func (s *Service) loadStakerTxAttributes(txID ids.ID) (*stakerAttributes, error) {
   703  	// Lookup tx from the cache first.
   704  	attr, found := s.stakerAttributesCache.Get(txID)
   705  	if found {
   706  		return attr, nil
   707  	}
   708  
   709  	// Tx not available in cache; pull it from disk and populate the cache.
   710  	tx, _, err := s.vm.state.GetTx(txID)
   711  	if err != nil {
   712  		return nil, err
   713  	}
   714  
   715  	switch stakerTx := tx.Unsigned.(type) {
   716  	case txs.ValidatorTx:
   717  		var pop *signer.ProofOfPossession
   718  		if staker, ok := stakerTx.(*txs.AddPermissionlessValidatorTx); ok {
   719  			if s, ok := staker.Signer.(*signer.ProofOfPossession); ok {
   720  				pop = s
   721  			}
   722  		}
   723  
   724  		attr = &stakerAttributes{
   725  			shares:                 stakerTx.Shares(),
   726  			validationRewardsOwner: stakerTx.ValidationRewardsOwner(),
   727  			delegationRewardsOwner: stakerTx.DelegationRewardsOwner(),
   728  			proofOfPossession:      pop,
   729  		}
   730  
   731  	case txs.DelegatorTx:
   732  		attr = &stakerAttributes{
   733  			rewardsOwner: stakerTx.RewardsOwner(),
   734  		}
   735  
   736  	default:
   737  		return nil, fmt.Errorf("unexpected staker tx type %T", tx.Unsigned)
   738  	}
   739  
   740  	s.stakerAttributesCache.Put(txID, attr)
   741  	return attr, nil
   742  }
   743  
   744  // GetCurrentValidators returns the current validators. If a single nodeID
   745  // is provided, full delegators information is also returned. Otherwise only
   746  // delegators' number and total weight is returned.
   747  func (s *Service) GetCurrentValidators(_ *http.Request, args *GetCurrentValidatorsArgs, reply *GetCurrentValidatorsReply) error {
   748  	s.vm.ctx.Log.Debug("API called",
   749  		zap.String("service", "platform"),
   750  		zap.String("method", "getCurrentValidators"),
   751  	)
   752  
   753  	reply.Validators = []interface{}{}
   754  
   755  	// Validator's node ID as string --> Delegators to them
   756  	vdrToDelegators := map[ids.NodeID][]platformapi.PrimaryDelegator{}
   757  
   758  	// Create set of nodeIDs
   759  	nodeIDs := set.Of(args.NodeIDs...)
   760  
   761  	s.vm.ctx.Lock.Lock()
   762  	defer s.vm.ctx.Lock.Unlock()
   763  
   764  	numNodeIDs := nodeIDs.Len()
   765  	targetStakers := make([]*state.Staker, 0, numNodeIDs)
   766  	if numNodeIDs == 0 { // Include all nodes
   767  		currentStakerIterator, err := s.vm.state.GetCurrentStakerIterator()
   768  		if err != nil {
   769  			return err
   770  		}
   771  		// TODO: avoid iterating over delegators here.
   772  		for currentStakerIterator.Next() {
   773  			staker := currentStakerIterator.Value()
   774  			if args.SubnetID != staker.SubnetID {
   775  				continue
   776  			}
   777  			targetStakers = append(targetStakers, staker)
   778  		}
   779  		currentStakerIterator.Release()
   780  	} else {
   781  		for nodeID := range nodeIDs {
   782  			staker, err := s.vm.state.GetCurrentValidator(args.SubnetID, nodeID)
   783  			switch err {
   784  			case nil:
   785  			case database.ErrNotFound:
   786  				// nothing to do, continue
   787  				continue
   788  			default:
   789  				return err
   790  			}
   791  			targetStakers = append(targetStakers, staker)
   792  
   793  			// TODO: avoid iterating over delegators when numNodeIDs > 1.
   794  			delegatorsIt, err := s.vm.state.GetCurrentDelegatorIterator(args.SubnetID, nodeID)
   795  			if err != nil {
   796  				return err
   797  			}
   798  			for delegatorsIt.Next() {
   799  				staker := delegatorsIt.Value()
   800  				targetStakers = append(targetStakers, staker)
   801  			}
   802  			delegatorsIt.Release()
   803  		}
   804  	}
   805  
   806  	for _, currentStaker := range targetStakers {
   807  		nodeID := currentStaker.NodeID
   808  		weight := avajson.Uint64(currentStaker.Weight)
   809  		apiStaker := platformapi.Staker{
   810  			TxID:        currentStaker.TxID,
   811  			StartTime:   avajson.Uint64(currentStaker.StartTime.Unix()),
   812  			EndTime:     avajson.Uint64(currentStaker.EndTime.Unix()),
   813  			Weight:      weight,
   814  			StakeAmount: &weight,
   815  			NodeID:      nodeID,
   816  		}
   817  		potentialReward := avajson.Uint64(currentStaker.PotentialReward)
   818  
   819  		delegateeReward, err := s.vm.state.GetDelegateeReward(currentStaker.SubnetID, currentStaker.NodeID)
   820  		if err != nil {
   821  			return err
   822  		}
   823  		jsonDelegateeReward := avajson.Uint64(delegateeReward)
   824  
   825  		switch currentStaker.Priority {
   826  		case txs.PrimaryNetworkValidatorCurrentPriority, txs.SubnetPermissionlessValidatorCurrentPriority:
   827  			attr, err := s.loadStakerTxAttributes(currentStaker.TxID)
   828  			if err != nil {
   829  				return err
   830  			}
   831  
   832  			shares := attr.shares
   833  			delegationFee := avajson.Float32(100 * float32(shares) / float32(reward.PercentDenominator))
   834  
   835  			uptime, err := s.getAPIUptime(currentStaker)
   836  			if err != nil {
   837  				return err
   838  			}
   839  
   840  			connected := s.vm.uptimeManager.IsConnected(nodeID, args.SubnetID)
   841  			var (
   842  				validationRewardOwner *platformapi.Owner
   843  				delegationRewardOwner *platformapi.Owner
   844  			)
   845  			validationOwner, ok := attr.validationRewardsOwner.(*secp256k1fx.OutputOwners)
   846  			if ok {
   847  				validationRewardOwner, err = s.getAPIOwner(validationOwner)
   848  				if err != nil {
   849  					return err
   850  				}
   851  			}
   852  			delegationOwner, ok := attr.delegationRewardsOwner.(*secp256k1fx.OutputOwners)
   853  			if ok {
   854  				delegationRewardOwner, err = s.getAPIOwner(delegationOwner)
   855  				if err != nil {
   856  					return err
   857  				}
   858  			}
   859  
   860  			vdr := platformapi.PermissionlessValidator{
   861  				Staker:                 apiStaker,
   862  				Uptime:                 uptime,
   863  				Connected:              connected,
   864  				PotentialReward:        &potentialReward,
   865  				AccruedDelegateeReward: &jsonDelegateeReward,
   866  				RewardOwner:            validationRewardOwner,
   867  				ValidationRewardOwner:  validationRewardOwner,
   868  				DelegationRewardOwner:  delegationRewardOwner,
   869  				DelegationFee:          delegationFee,
   870  				Signer:                 attr.proofOfPossession,
   871  			}
   872  			reply.Validators = append(reply.Validators, vdr)
   873  
   874  		case txs.PrimaryNetworkDelegatorCurrentPriority, txs.SubnetPermissionlessDelegatorCurrentPriority:
   875  			var rewardOwner *platformapi.Owner
   876  			// If we are handling multiple nodeIDs, we don't return the
   877  			// delegator information.
   878  			if numNodeIDs == 1 {
   879  				attr, err := s.loadStakerTxAttributes(currentStaker.TxID)
   880  				if err != nil {
   881  					return err
   882  				}
   883  				owner, ok := attr.rewardsOwner.(*secp256k1fx.OutputOwners)
   884  				if ok {
   885  					rewardOwner, err = s.getAPIOwner(owner)
   886  					if err != nil {
   887  						return err
   888  					}
   889  				}
   890  			}
   891  
   892  			delegator := platformapi.PrimaryDelegator{
   893  				Staker:          apiStaker,
   894  				RewardOwner:     rewardOwner,
   895  				PotentialReward: &potentialReward,
   896  			}
   897  			vdrToDelegators[delegator.NodeID] = append(vdrToDelegators[delegator.NodeID], delegator)
   898  
   899  		case txs.SubnetPermissionedValidatorCurrentPriority:
   900  			uptime, err := s.getAPIUptime(currentStaker)
   901  			if err != nil {
   902  				return err
   903  			}
   904  			connected := s.vm.uptimeManager.IsConnected(nodeID, args.SubnetID)
   905  			reply.Validators = append(reply.Validators, platformapi.PermissionedValidator{
   906  				Staker:    apiStaker,
   907  				Connected: connected,
   908  				Uptime:    uptime,
   909  			})
   910  
   911  		default:
   912  			return fmt.Errorf("unexpected staker priority %d", currentStaker.Priority)
   913  		}
   914  	}
   915  
   916  	// handle delegators' information
   917  	for i, vdrIntf := range reply.Validators {
   918  		vdr, ok := vdrIntf.(platformapi.PermissionlessValidator)
   919  		if !ok {
   920  			continue
   921  		}
   922  		delegators, ok := vdrToDelegators[vdr.NodeID]
   923  		if !ok {
   924  			// If we are expected to populate the delegators field, we should
   925  			// always return a non-nil value.
   926  			delegators = []platformapi.PrimaryDelegator{}
   927  		}
   928  		delegatorCount := avajson.Uint64(len(delegators))
   929  		delegatorWeight := avajson.Uint64(0)
   930  		for _, d := range delegators {
   931  			delegatorWeight += d.Weight
   932  		}
   933  
   934  		vdr.DelegatorCount = &delegatorCount
   935  		vdr.DelegatorWeight = &delegatorWeight
   936  
   937  		if numNodeIDs == 1 {
   938  			// queried a specific validator, load all of its delegators
   939  			vdr.Delegators = &delegators
   940  		}
   941  		reply.Validators[i] = vdr
   942  	}
   943  
   944  	return nil
   945  }
   946  
   947  // GetCurrentSupplyArgs are the arguments for calling GetCurrentSupply
   948  type GetCurrentSupplyArgs struct {
   949  	SubnetID ids.ID `json:"subnetID"`
   950  }
   951  
   952  // GetCurrentSupplyReply are the results from calling GetCurrentSupply
   953  type GetCurrentSupplyReply struct {
   954  	Supply avajson.Uint64 `json:"supply"`
   955  	Height avajson.Uint64 `json:"height"`
   956  }
   957  
   958  // GetCurrentSupply returns an upper bound on the supply of AVAX in the system
   959  func (s *Service) GetCurrentSupply(r *http.Request, args *GetCurrentSupplyArgs, reply *GetCurrentSupplyReply) error {
   960  	s.vm.ctx.Log.Debug("API called",
   961  		zap.String("service", "platform"),
   962  		zap.String("method", "getCurrentSupply"),
   963  	)
   964  
   965  	s.vm.ctx.Lock.Lock()
   966  	defer s.vm.ctx.Lock.Unlock()
   967  
   968  	supply, err := s.vm.state.GetCurrentSupply(args.SubnetID)
   969  	if err != nil {
   970  		return fmt.Errorf("fetching current supply failed: %w", err)
   971  	}
   972  	reply.Supply = avajson.Uint64(supply)
   973  
   974  	ctx := r.Context()
   975  	height, err := s.vm.GetCurrentHeight(ctx)
   976  	if err != nil {
   977  		return fmt.Errorf("fetching current height failed: %w", err)
   978  	}
   979  	reply.Height = avajson.Uint64(height)
   980  
   981  	return nil
   982  }
   983  
   984  // SampleValidatorsArgs are the arguments for calling SampleValidators
   985  type SampleValidatorsArgs struct {
   986  	// Number of validators in the sample
   987  	Size avajson.Uint16 `json:"size"`
   988  
   989  	// ID of subnet to sample validators from
   990  	// If omitted, defaults to the primary network
   991  	SubnetID ids.ID `json:"subnetID"`
   992  }
   993  
   994  // SampleValidatorsReply are the results from calling Sample
   995  type SampleValidatorsReply struct {
   996  	Validators []ids.NodeID `json:"validators"`
   997  }
   998  
   999  // SampleValidators returns a sampling of the list of current validators
  1000  func (s *Service) SampleValidators(_ *http.Request, args *SampleValidatorsArgs, reply *SampleValidatorsReply) error {
  1001  	s.vm.ctx.Log.Debug("API called",
  1002  		zap.String("service", "platform"),
  1003  		zap.String("method", "sampleValidators"),
  1004  		zap.Uint16("size", uint16(args.Size)),
  1005  	)
  1006  
  1007  	sample, err := s.vm.Validators.Sample(args.SubnetID, int(args.Size))
  1008  	if err != nil {
  1009  		return fmt.Errorf("sampling %s errored with %w", args.SubnetID, err)
  1010  	}
  1011  
  1012  	if sample == nil {
  1013  		reply.Validators = []ids.NodeID{}
  1014  	} else {
  1015  		utils.Sort(sample)
  1016  		reply.Validators = sample
  1017  	}
  1018  	return nil
  1019  }
  1020  
  1021  // GetBlockchainStatusArgs is the arguments for calling GetBlockchainStatus
  1022  // [BlockchainID] is the ID of or an alias of the blockchain to get the status of.
  1023  type GetBlockchainStatusArgs struct {
  1024  	BlockchainID string `json:"blockchainID"`
  1025  }
  1026  
  1027  // GetBlockchainStatusReply is the reply from calling GetBlockchainStatus
  1028  // [Status] is the blockchain's status.
  1029  type GetBlockchainStatusReply struct {
  1030  	Status status.BlockchainStatus `json:"status"`
  1031  }
  1032  
  1033  // GetBlockchainStatus gets the status of a blockchain with the ID [args.BlockchainID].
  1034  func (s *Service) GetBlockchainStatus(r *http.Request, args *GetBlockchainStatusArgs, reply *GetBlockchainStatusReply) error {
  1035  	s.vm.ctx.Log.Debug("API called",
  1036  		zap.String("service", "platform"),
  1037  		zap.String("method", "getBlockchainStatus"),
  1038  	)
  1039  
  1040  	if args.BlockchainID == "" {
  1041  		return errMissingBlockchainID
  1042  	}
  1043  
  1044  	s.vm.ctx.Lock.Lock()
  1045  	defer s.vm.ctx.Lock.Unlock()
  1046  
  1047  	// if its aliased then vm created this chain.
  1048  	if aliasedID, err := s.vm.Chains.Lookup(args.BlockchainID); err == nil {
  1049  		if s.nodeValidates(aliasedID) {
  1050  			reply.Status = status.Validating
  1051  			return nil
  1052  		}
  1053  
  1054  		reply.Status = status.Syncing
  1055  		return nil
  1056  	}
  1057  
  1058  	blockchainID, err := ids.FromString(args.BlockchainID)
  1059  	if err != nil {
  1060  		return fmt.Errorf("problem parsing blockchainID %q: %w", args.BlockchainID, err)
  1061  	}
  1062  
  1063  	ctx := r.Context()
  1064  	lastAcceptedID, err := s.vm.LastAccepted(ctx)
  1065  	if err != nil {
  1066  		return fmt.Errorf("problem loading last accepted ID: %w", err)
  1067  	}
  1068  
  1069  	exists, err := s.chainExists(ctx, lastAcceptedID, blockchainID)
  1070  	if err != nil {
  1071  		return fmt.Errorf("problem looking up blockchain: %w", err)
  1072  	}
  1073  	if exists {
  1074  		reply.Status = status.Created
  1075  		return nil
  1076  	}
  1077  
  1078  	preferredBlkID := s.vm.manager.Preferred()
  1079  	preferred, err := s.chainExists(ctx, preferredBlkID, blockchainID)
  1080  	if err != nil {
  1081  		return fmt.Errorf("problem looking up blockchain: %w", err)
  1082  	}
  1083  	if preferred {
  1084  		reply.Status = status.Preferred
  1085  	} else {
  1086  		reply.Status = status.UnknownChain
  1087  	}
  1088  	return nil
  1089  }
  1090  
  1091  func (s *Service) nodeValidates(blockchainID ids.ID) bool {
  1092  	chainTx, _, err := s.vm.state.GetTx(blockchainID)
  1093  	if err != nil {
  1094  		return false
  1095  	}
  1096  
  1097  	chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
  1098  	if !ok {
  1099  		return false
  1100  	}
  1101  
  1102  	_, isValidator := s.vm.Validators.GetValidator(chain.SubnetID, s.vm.ctx.NodeID)
  1103  	return isValidator
  1104  }
  1105  
  1106  func (s *Service) chainExists(ctx context.Context, blockID ids.ID, chainID ids.ID) (bool, error) {
  1107  	state, ok := s.vm.manager.GetState(blockID)
  1108  	if !ok {
  1109  		block, err := s.vm.GetBlock(ctx, blockID)
  1110  		if err != nil {
  1111  			return false, err
  1112  		}
  1113  		state, ok = s.vm.manager.GetState(block.Parent())
  1114  		if !ok {
  1115  			return false, errMissingDecisionBlock
  1116  		}
  1117  	}
  1118  
  1119  	tx, _, err := state.GetTx(chainID)
  1120  	if err == database.ErrNotFound {
  1121  		return false, nil
  1122  	}
  1123  	if err != nil {
  1124  		return false, err
  1125  	}
  1126  	_, ok = tx.Unsigned.(*txs.CreateChainTx)
  1127  	return ok, nil
  1128  }
  1129  
  1130  // ValidatedByArgs is the arguments for calling ValidatedBy
  1131  type ValidatedByArgs struct {
  1132  	// ValidatedBy returns the ID of the Subnet validating the blockchain with this ID
  1133  	BlockchainID ids.ID `json:"blockchainID"`
  1134  }
  1135  
  1136  // ValidatedByResponse is the reply from calling ValidatedBy
  1137  type ValidatedByResponse struct {
  1138  	// ID of the Subnet validating the specified blockchain
  1139  	SubnetID ids.ID `json:"subnetID"`
  1140  }
  1141  
  1142  // ValidatedBy returns the ID of the Subnet that validates [args.BlockchainID]
  1143  func (s *Service) ValidatedBy(r *http.Request, args *ValidatedByArgs, response *ValidatedByResponse) error {
  1144  	s.vm.ctx.Log.Debug("API called",
  1145  		zap.String("service", "platform"),
  1146  		zap.String("method", "validatedBy"),
  1147  	)
  1148  
  1149  	s.vm.ctx.Lock.Lock()
  1150  	defer s.vm.ctx.Lock.Unlock()
  1151  
  1152  	var err error
  1153  	ctx := r.Context()
  1154  	response.SubnetID, err = s.vm.GetSubnetID(ctx, args.BlockchainID)
  1155  	return err
  1156  }
  1157  
  1158  // ValidatesArgs are the arguments to Validates
  1159  type ValidatesArgs struct {
  1160  	SubnetID ids.ID `json:"subnetID"`
  1161  }
  1162  
  1163  // ValidatesResponse is the response from calling Validates
  1164  type ValidatesResponse struct {
  1165  	BlockchainIDs []ids.ID `json:"blockchainIDs"`
  1166  }
  1167  
  1168  // Validates returns the IDs of the blockchains validated by [args.SubnetID]
  1169  func (s *Service) Validates(_ *http.Request, args *ValidatesArgs, response *ValidatesResponse) error {
  1170  	s.vm.ctx.Log.Debug("API called",
  1171  		zap.String("service", "platform"),
  1172  		zap.String("method", "validates"),
  1173  	)
  1174  
  1175  	s.vm.ctx.Lock.Lock()
  1176  	defer s.vm.ctx.Lock.Unlock()
  1177  
  1178  	if args.SubnetID != constants.PrimaryNetworkID {
  1179  		subnetTx, _, err := s.vm.state.GetTx(args.SubnetID)
  1180  		if err != nil {
  1181  			return fmt.Errorf(
  1182  				"problem retrieving subnet %q: %w",
  1183  				args.SubnetID,
  1184  				err,
  1185  			)
  1186  		}
  1187  		_, ok := subnetTx.Unsigned.(*txs.CreateSubnetTx)
  1188  		if !ok {
  1189  			return fmt.Errorf("%q is not a subnet", args.SubnetID)
  1190  		}
  1191  	}
  1192  
  1193  	// Get the chains that exist
  1194  	chains, err := s.vm.state.GetChains(args.SubnetID)
  1195  	if err != nil {
  1196  		return fmt.Errorf("problem retrieving chains for subnet %q: %w", args.SubnetID, err)
  1197  	}
  1198  
  1199  	response.BlockchainIDs = make([]ids.ID, len(chains))
  1200  	for i, chain := range chains {
  1201  		response.BlockchainIDs[i] = chain.ID()
  1202  	}
  1203  	return nil
  1204  }
  1205  
  1206  // APIBlockchain is the representation of a blockchain used in API calls
  1207  type APIBlockchain struct {
  1208  	// Blockchain's ID
  1209  	ID ids.ID `json:"id"`
  1210  
  1211  	// Blockchain's (non-unique) human-readable name
  1212  	Name string `json:"name"`
  1213  
  1214  	// Subnet that validates the blockchain
  1215  	SubnetID ids.ID `json:"subnetID"`
  1216  
  1217  	// Virtual Machine the blockchain runs
  1218  	VMID ids.ID `json:"vmID"`
  1219  }
  1220  
  1221  // GetBlockchainsResponse is the response from a call to GetBlockchains
  1222  type GetBlockchainsResponse struct {
  1223  	// blockchains that exist
  1224  	Blockchains []APIBlockchain `json:"blockchains"`
  1225  }
  1226  
  1227  // GetBlockchains returns all of the blockchains that exist
  1228  func (s *Service) GetBlockchains(_ *http.Request, _ *struct{}, response *GetBlockchainsResponse) error {
  1229  	s.vm.ctx.Log.Debug("deprecated API called",
  1230  		zap.String("service", "platform"),
  1231  		zap.String("method", "getBlockchains"),
  1232  	)
  1233  
  1234  	s.vm.ctx.Lock.Lock()
  1235  	defer s.vm.ctx.Lock.Unlock()
  1236  
  1237  	subnetIDs, err := s.vm.state.GetSubnetIDs()
  1238  	if err != nil {
  1239  		return fmt.Errorf("couldn't retrieve subnets: %w", err)
  1240  	}
  1241  
  1242  	response.Blockchains = []APIBlockchain{}
  1243  	for _, subnetID := range subnetIDs {
  1244  		chains, err := s.vm.state.GetChains(subnetID)
  1245  		if err != nil {
  1246  			return fmt.Errorf(
  1247  				"couldn't retrieve chains for subnet %q: %w",
  1248  				subnetID,
  1249  				err,
  1250  			)
  1251  		}
  1252  
  1253  		for _, chainTx := range chains {
  1254  			chainID := chainTx.ID()
  1255  			chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
  1256  			if !ok {
  1257  				return fmt.Errorf("expected tx type *txs.CreateChainTx but got %T", chainTx.Unsigned)
  1258  			}
  1259  			response.Blockchains = append(response.Blockchains, APIBlockchain{
  1260  				ID:       chainID,
  1261  				Name:     chain.ChainName,
  1262  				SubnetID: subnetID,
  1263  				VMID:     chain.VMID,
  1264  			})
  1265  		}
  1266  	}
  1267  
  1268  	chains, err := s.vm.state.GetChains(constants.PrimaryNetworkID)
  1269  	if err != nil {
  1270  		return fmt.Errorf("couldn't retrieve subnets: %w", err)
  1271  	}
  1272  	for _, chainTx := range chains {
  1273  		chainID := chainTx.ID()
  1274  		chain, ok := chainTx.Unsigned.(*txs.CreateChainTx)
  1275  		if !ok {
  1276  			return fmt.Errorf("expected tx type *txs.CreateChainTx but got %T", chainTx.Unsigned)
  1277  		}
  1278  		response.Blockchains = append(response.Blockchains, APIBlockchain{
  1279  			ID:       chainID,
  1280  			Name:     chain.ChainName,
  1281  			SubnetID: constants.PrimaryNetworkID,
  1282  			VMID:     chain.VMID,
  1283  		})
  1284  	}
  1285  
  1286  	return nil
  1287  }
  1288  
  1289  func (s *Service) IssueTx(_ *http.Request, args *api.FormattedTx, response *api.JSONTxID) error {
  1290  	s.vm.ctx.Log.Debug("API called",
  1291  		zap.String("service", "platform"),
  1292  		zap.String("method", "issueTx"),
  1293  	)
  1294  
  1295  	txBytes, err := formatting.Decode(args.Encoding, args.Tx)
  1296  	if err != nil {
  1297  		return fmt.Errorf("problem decoding transaction: %w", err)
  1298  	}
  1299  	tx, err := txs.Parse(txs.Codec, txBytes)
  1300  	if err != nil {
  1301  		return fmt.Errorf("couldn't parse tx: %w", err)
  1302  	}
  1303  
  1304  	if err := s.vm.issueTxFromRPC(tx); err != nil {
  1305  		return fmt.Errorf("couldn't issue tx: %w", err)
  1306  	}
  1307  
  1308  	response.TxID = tx.ID()
  1309  	return nil
  1310  }
  1311  
  1312  func (s *Service) GetTx(_ *http.Request, args *api.GetTxArgs, response *api.GetTxReply) error {
  1313  	s.vm.ctx.Log.Debug("API called",
  1314  		zap.String("service", "platform"),
  1315  		zap.String("method", "getTx"),
  1316  	)
  1317  
  1318  	s.vm.ctx.Lock.Lock()
  1319  	defer s.vm.ctx.Lock.Unlock()
  1320  
  1321  	tx, _, err := s.vm.state.GetTx(args.TxID)
  1322  	if err != nil {
  1323  		return fmt.Errorf("couldn't get tx: %w", err)
  1324  	}
  1325  	response.Encoding = args.Encoding
  1326  
  1327  	var result any
  1328  	if args.Encoding == formatting.JSON {
  1329  		tx.Unsigned.InitCtx(s.vm.ctx)
  1330  		result = tx
  1331  	} else {
  1332  		result, err = formatting.Encode(args.Encoding, tx.Bytes())
  1333  		if err != nil {
  1334  			return fmt.Errorf("couldn't encode tx as %s: %w", args.Encoding, err)
  1335  		}
  1336  	}
  1337  
  1338  	response.Tx, err = json.Marshal(result)
  1339  	return err
  1340  }
  1341  
  1342  type GetTxStatusArgs struct {
  1343  	TxID ids.ID `json:"txID"`
  1344  }
  1345  
  1346  type GetTxStatusResponse struct {
  1347  	Status status.Status `json:"status"`
  1348  	// Reason this tx was dropped.
  1349  	// Only non-empty if Status is dropped
  1350  	Reason string `json:"reason,omitempty"`
  1351  }
  1352  
  1353  // GetTxStatus gets a tx's status
  1354  func (s *Service) GetTxStatus(_ *http.Request, args *GetTxStatusArgs, response *GetTxStatusResponse) error {
  1355  	s.vm.ctx.Log.Debug("API called",
  1356  		zap.String("service", "platform"),
  1357  		zap.String("method", "getTxStatus"),
  1358  	)
  1359  
  1360  	s.vm.ctx.Lock.Lock()
  1361  	defer s.vm.ctx.Lock.Unlock()
  1362  
  1363  	_, txStatus, err := s.vm.state.GetTx(args.TxID)
  1364  	if err == nil { // Found the status. Report it.
  1365  		response.Status = txStatus
  1366  		return nil
  1367  	}
  1368  	if err != database.ErrNotFound {
  1369  		return err
  1370  	}
  1371  
  1372  	// The status of this transaction is not in the database - check if the tx
  1373  	// is in the preferred block's db. If so, return that it's processing.
  1374  	preferredID := s.vm.manager.Preferred()
  1375  	onAccept, ok := s.vm.manager.GetState(preferredID)
  1376  	if !ok {
  1377  		return fmt.Errorf("could not retrieve state for block %s", preferredID)
  1378  	}
  1379  
  1380  	_, _, err = onAccept.GetTx(args.TxID)
  1381  	if err == nil {
  1382  		// Found the status in the preferred block's db. Report tx is processing.
  1383  		response.Status = status.Processing
  1384  		return nil
  1385  	}
  1386  	if err != database.ErrNotFound {
  1387  		return err
  1388  	}
  1389  
  1390  	if _, ok := s.vm.Builder.Get(args.TxID); ok {
  1391  		// Found the tx in the mempool. Report tx is processing.
  1392  		response.Status = status.Processing
  1393  		return nil
  1394  	}
  1395  
  1396  	// Note: we check if tx is dropped only after having looked for it
  1397  	// in the database and the mempool, because dropped txs may be re-issued.
  1398  	reason := s.vm.Builder.GetDropReason(args.TxID)
  1399  	if reason == nil {
  1400  		// The tx isn't being tracked by the node.
  1401  		response.Status = status.Unknown
  1402  		return nil
  1403  	}
  1404  
  1405  	// The tx was recently dropped because it was invalid.
  1406  	response.Status = status.Dropped
  1407  	response.Reason = reason.Error()
  1408  	return nil
  1409  }
  1410  
  1411  type GetStakeArgs struct {
  1412  	api.JSONAddresses
  1413  	ValidatorsOnly bool                `json:"validatorsOnly"`
  1414  	Encoding       formatting.Encoding `json:"encoding"`
  1415  }
  1416  
  1417  // GetStakeReply is the response from calling GetStake.
  1418  type GetStakeReply struct {
  1419  	Staked  avajson.Uint64            `json:"staked"`
  1420  	Stakeds map[ids.ID]avajson.Uint64 `json:"stakeds"`
  1421  	// String representation of staked outputs
  1422  	// Each is of type avax.TransferableOutput
  1423  	Outputs []string `json:"stakedOutputs"`
  1424  	// Encoding of [Outputs]
  1425  	Encoding formatting.Encoding `json:"encoding"`
  1426  }
  1427  
  1428  // GetStake returns the amount of nAVAX that [args.Addresses] have cumulatively
  1429  // staked on the Primary Network.
  1430  //
  1431  // This method assumes that each stake output has only owner
  1432  // This method assumes only AVAX can be staked
  1433  // This method only concerns itself with the Primary Network, not subnets
  1434  // TODO: Improve the performance of this method by maintaining this data
  1435  // in a data structure rather than re-calculating it by iterating over stakers
  1436  func (s *Service) GetStake(_ *http.Request, args *GetStakeArgs, response *GetStakeReply) error {
  1437  	s.vm.ctx.Log.Debug("deprecated API called",
  1438  		zap.String("service", "platform"),
  1439  		zap.String("method", "getStake"),
  1440  	)
  1441  
  1442  	if len(args.Addresses) > maxGetStakeAddrs {
  1443  		return fmt.Errorf("%d addresses provided but this method can take at most %d", len(args.Addresses), maxGetStakeAddrs)
  1444  	}
  1445  
  1446  	addrs, err := avax.ParseServiceAddresses(s.addrManager, args.Addresses)
  1447  	if err != nil {
  1448  		return err
  1449  	}
  1450  
  1451  	s.vm.ctx.Lock.Lock()
  1452  	defer s.vm.ctx.Lock.Unlock()
  1453  
  1454  	currentStakerIterator, err := s.vm.state.GetCurrentStakerIterator()
  1455  	if err != nil {
  1456  		return err
  1457  	}
  1458  	defer currentStakerIterator.Release()
  1459  
  1460  	var (
  1461  		totalAmountStaked = make(map[ids.ID]uint64)
  1462  		stakedOuts        []avax.TransferableOutput
  1463  	)
  1464  	for currentStakerIterator.Next() { // Iterates over current stakers
  1465  		staker := currentStakerIterator.Value()
  1466  
  1467  		if args.ValidatorsOnly && !staker.Priority.IsValidator() {
  1468  			continue
  1469  		}
  1470  
  1471  		tx, _, err := s.vm.state.GetTx(staker.TxID)
  1472  		if err != nil {
  1473  			return err
  1474  		}
  1475  
  1476  		stakedOuts = append(stakedOuts, getStakeHelper(tx, addrs, totalAmountStaked)...)
  1477  	}
  1478  
  1479  	pendingStakerIterator, err := s.vm.state.GetPendingStakerIterator()
  1480  	if err != nil {
  1481  		return err
  1482  	}
  1483  	defer pendingStakerIterator.Release()
  1484  
  1485  	for pendingStakerIterator.Next() { // Iterates over pending stakers
  1486  		staker := pendingStakerIterator.Value()
  1487  
  1488  		if args.ValidatorsOnly && !staker.Priority.IsValidator() {
  1489  			continue
  1490  		}
  1491  
  1492  		tx, _, err := s.vm.state.GetTx(staker.TxID)
  1493  		if err != nil {
  1494  			return err
  1495  		}
  1496  
  1497  		stakedOuts = append(stakedOuts, getStakeHelper(tx, addrs, totalAmountStaked)...)
  1498  	}
  1499  
  1500  	response.Stakeds = newJSONBalanceMap(totalAmountStaked)
  1501  	response.Staked = response.Stakeds[s.vm.ctx.AVAXAssetID]
  1502  	response.Outputs = make([]string, len(stakedOuts))
  1503  	for i, output := range stakedOuts {
  1504  		bytes, err := txs.Codec.Marshal(txs.CodecVersion, output)
  1505  		if err != nil {
  1506  			return fmt.Errorf("couldn't serialize output %s: %w", output.ID, err)
  1507  		}
  1508  		response.Outputs[i], err = formatting.Encode(args.Encoding, bytes)
  1509  		if err != nil {
  1510  			return fmt.Errorf("couldn't encode output %s as %s: %w", output.ID, args.Encoding, err)
  1511  		}
  1512  	}
  1513  	response.Encoding = args.Encoding
  1514  
  1515  	return nil
  1516  }
  1517  
  1518  // GetMinStakeArgs are the arguments for calling GetMinStake.
  1519  type GetMinStakeArgs struct {
  1520  	SubnetID ids.ID `json:"subnetID"`
  1521  }
  1522  
  1523  // GetMinStakeReply is the response from calling GetMinStake.
  1524  type GetMinStakeReply struct {
  1525  	//  The minimum amount of tokens one must bond to be a validator
  1526  	MinValidatorStake avajson.Uint64 `json:"minValidatorStake"`
  1527  	// Minimum stake, in nAVAX, that can be delegated on the primary network
  1528  	MinDelegatorStake avajson.Uint64 `json:"minDelegatorStake"`
  1529  }
  1530  
  1531  // GetMinStake returns the minimum staking amount in nAVAX.
  1532  func (s *Service) GetMinStake(_ *http.Request, args *GetMinStakeArgs, reply *GetMinStakeReply) error {
  1533  	s.vm.ctx.Log.Debug("API called",
  1534  		zap.String("service", "platform"),
  1535  		zap.String("method", "getMinStake"),
  1536  	)
  1537  
  1538  	if args.SubnetID == constants.PrimaryNetworkID {
  1539  		reply.MinValidatorStake = avajson.Uint64(s.vm.MinValidatorStake)
  1540  		reply.MinDelegatorStake = avajson.Uint64(s.vm.MinDelegatorStake)
  1541  		return nil
  1542  	}
  1543  
  1544  	s.vm.ctx.Lock.Lock()
  1545  	defer s.vm.ctx.Lock.Unlock()
  1546  
  1547  	transformSubnetIntf, err := s.vm.state.GetSubnetTransformation(args.SubnetID)
  1548  	if err != nil {
  1549  		return fmt.Errorf(
  1550  			"failed fetching subnet transformation for %s: %w",
  1551  			args.SubnetID,
  1552  			err,
  1553  		)
  1554  	}
  1555  	transformSubnet, ok := transformSubnetIntf.Unsigned.(*txs.TransformSubnetTx)
  1556  	if !ok {
  1557  		return fmt.Errorf(
  1558  			"unexpected subnet transformation tx type fetched %T",
  1559  			transformSubnetIntf.Unsigned,
  1560  		)
  1561  	}
  1562  
  1563  	reply.MinValidatorStake = avajson.Uint64(transformSubnet.MinValidatorStake)
  1564  	reply.MinDelegatorStake = avajson.Uint64(transformSubnet.MinDelegatorStake)
  1565  
  1566  	return nil
  1567  }
  1568  
  1569  // GetTotalStakeArgs are the arguments for calling GetTotalStake
  1570  type GetTotalStakeArgs struct {
  1571  	// Subnet we're getting the total stake
  1572  	// If omitted returns Primary network weight
  1573  	SubnetID ids.ID `json:"subnetID"`
  1574  }
  1575  
  1576  // GetTotalStakeReply is the response from calling GetTotalStake.
  1577  type GetTotalStakeReply struct {
  1578  	// Deprecated: Use Weight instead.
  1579  	Stake avajson.Uint64 `json:"stake"`
  1580  
  1581  	Weight avajson.Uint64 `json:"weight"`
  1582  }
  1583  
  1584  // GetTotalStake returns the total amount staked on the Primary Network
  1585  func (s *Service) GetTotalStake(_ *http.Request, args *GetTotalStakeArgs, reply *GetTotalStakeReply) error {
  1586  	s.vm.ctx.Log.Debug("API called",
  1587  		zap.String("service", "platform"),
  1588  		zap.String("method", "getTotalStake"),
  1589  	)
  1590  
  1591  	totalWeight, err := s.vm.Validators.TotalWeight(args.SubnetID)
  1592  	if err != nil {
  1593  		return fmt.Errorf("couldn't get total weight: %w", err)
  1594  	}
  1595  	weight := avajson.Uint64(totalWeight)
  1596  	reply.Weight = weight
  1597  	reply.Stake = weight
  1598  	return nil
  1599  }
  1600  
  1601  // GetRewardUTXOsReply defines the GetRewardUTXOs replies returned from the API
  1602  type GetRewardUTXOsReply struct {
  1603  	// Number of UTXOs returned
  1604  	NumFetched avajson.Uint64 `json:"numFetched"`
  1605  	// The UTXOs
  1606  	UTXOs []string `json:"utxos"`
  1607  	// Encoding specifies the encoding format the UTXOs are returned in
  1608  	Encoding formatting.Encoding `json:"encoding"`
  1609  }
  1610  
  1611  // GetRewardUTXOs returns the UTXOs that were rewarded after the provided
  1612  // transaction's staking period ended.
  1613  func (s *Service) GetRewardUTXOs(_ *http.Request, args *api.GetTxArgs, reply *GetRewardUTXOsReply) error {
  1614  	s.vm.ctx.Log.Debug("deprecated API called",
  1615  		zap.String("service", "platform"),
  1616  		zap.String("method", "getRewardUTXOs"),
  1617  	)
  1618  
  1619  	s.vm.ctx.Lock.Lock()
  1620  	defer s.vm.ctx.Lock.Unlock()
  1621  
  1622  	utxos, err := s.vm.state.GetRewardUTXOs(args.TxID)
  1623  	if err != nil {
  1624  		return fmt.Errorf("couldn't get reward UTXOs: %w", err)
  1625  	}
  1626  
  1627  	reply.NumFetched = avajson.Uint64(len(utxos))
  1628  	reply.UTXOs = make([]string, len(utxos))
  1629  	for i, utxo := range utxos {
  1630  		utxoBytes, err := txs.GenesisCodec.Marshal(txs.CodecVersion, utxo)
  1631  		if err != nil {
  1632  			return fmt.Errorf("couldn't encode UTXO to bytes: %w", err)
  1633  		}
  1634  
  1635  		utxoStr, err := formatting.Encode(args.Encoding, utxoBytes)
  1636  		if err != nil {
  1637  			return fmt.Errorf("couldn't encode utxo as %s: %w", args.Encoding, err)
  1638  		}
  1639  		reply.UTXOs[i] = utxoStr
  1640  	}
  1641  	reply.Encoding = args.Encoding
  1642  	return nil
  1643  }
  1644  
  1645  // GetTimestampReply is the response from GetTimestamp
  1646  type GetTimestampReply struct {
  1647  	// Current timestamp
  1648  	Timestamp time.Time `json:"timestamp"`
  1649  }
  1650  
  1651  // GetTimestamp returns the current timestamp on chain.
  1652  func (s *Service) GetTimestamp(_ *http.Request, _ *struct{}, reply *GetTimestampReply) error {
  1653  	s.vm.ctx.Log.Debug("API called",
  1654  		zap.String("service", "platform"),
  1655  		zap.String("method", "getTimestamp"),
  1656  	)
  1657  
  1658  	s.vm.ctx.Lock.Lock()
  1659  	defer s.vm.ctx.Lock.Unlock()
  1660  
  1661  	reply.Timestamp = s.vm.state.GetTimestamp()
  1662  	return nil
  1663  }
  1664  
  1665  // GetValidatorsAtArgs is the response from GetValidatorsAt
  1666  type GetValidatorsAtArgs struct {
  1667  	Height   avajson.Uint64 `json:"height"`
  1668  	SubnetID ids.ID         `json:"subnetID"`
  1669  }
  1670  
  1671  type jsonGetValidatorOutput struct {
  1672  	PublicKey *string        `json:"publicKey"`
  1673  	Weight    avajson.Uint64 `json:"weight"`
  1674  }
  1675  
  1676  func (v *GetValidatorsAtReply) MarshalJSON() ([]byte, error) {
  1677  	m := make(map[ids.NodeID]*jsonGetValidatorOutput, len(v.Validators))
  1678  	for _, vdr := range v.Validators {
  1679  		vdrJSON := &jsonGetValidatorOutput{
  1680  			Weight: avajson.Uint64(vdr.Weight),
  1681  		}
  1682  
  1683  		if vdr.PublicKey != nil {
  1684  			pk, err := formatting.Encode(formatting.HexNC, bls.PublicKeyToCompressedBytes(vdr.PublicKey))
  1685  			if err != nil {
  1686  				return nil, err
  1687  			}
  1688  			vdrJSON.PublicKey = &pk
  1689  		}
  1690  
  1691  		m[vdr.NodeID] = vdrJSON
  1692  	}
  1693  	return json.Marshal(m)
  1694  }
  1695  
  1696  func (v *GetValidatorsAtReply) UnmarshalJSON(b []byte) error {
  1697  	var m map[ids.NodeID]*jsonGetValidatorOutput
  1698  	if err := json.Unmarshal(b, &m); err != nil {
  1699  		return err
  1700  	}
  1701  
  1702  	if m == nil {
  1703  		v.Validators = nil
  1704  		return nil
  1705  	}
  1706  
  1707  	v.Validators = make(map[ids.NodeID]*validators.GetValidatorOutput, len(m))
  1708  	for nodeID, vdrJSON := range m {
  1709  		vdr := &validators.GetValidatorOutput{
  1710  			NodeID: nodeID,
  1711  			Weight: uint64(vdrJSON.Weight),
  1712  		}
  1713  
  1714  		if vdrJSON.PublicKey != nil {
  1715  			pkBytes, err := formatting.Decode(formatting.HexNC, *vdrJSON.PublicKey)
  1716  			if err != nil {
  1717  				return err
  1718  			}
  1719  			vdr.PublicKey, err = bls.PublicKeyFromCompressedBytes(pkBytes)
  1720  			if err != nil {
  1721  				return err
  1722  			}
  1723  		}
  1724  
  1725  		v.Validators[nodeID] = vdr
  1726  	}
  1727  	return nil
  1728  }
  1729  
  1730  // GetValidatorsAtReply is the response from GetValidatorsAt
  1731  type GetValidatorsAtReply struct {
  1732  	Validators map[ids.NodeID]*validators.GetValidatorOutput
  1733  }
  1734  
  1735  // GetValidatorsAt returns the weights of the validator set of a provided subnet
  1736  // at the specified height.
  1737  func (s *Service) GetValidatorsAt(r *http.Request, args *GetValidatorsAtArgs, reply *GetValidatorsAtReply) error {
  1738  	height := uint64(args.Height)
  1739  	s.vm.ctx.Log.Debug("API called",
  1740  		zap.String("service", "platform"),
  1741  		zap.String("method", "getValidatorsAt"),
  1742  		zap.Uint64("height", height),
  1743  		zap.Stringer("subnetID", args.SubnetID),
  1744  	)
  1745  
  1746  	s.vm.ctx.Lock.Lock()
  1747  	defer s.vm.ctx.Lock.Unlock()
  1748  
  1749  	ctx := r.Context()
  1750  	var err error
  1751  	reply.Validators, err = s.vm.GetValidatorSet(ctx, height, args.SubnetID)
  1752  	if err != nil {
  1753  		return fmt.Errorf("failed to get validator set: %w", err)
  1754  	}
  1755  	return nil
  1756  }
  1757  
  1758  func (s *Service) GetBlock(_ *http.Request, args *api.GetBlockArgs, response *api.GetBlockResponse) error {
  1759  	s.vm.ctx.Log.Debug("API called",
  1760  		zap.String("service", "platform"),
  1761  		zap.String("method", "getBlock"),
  1762  		zap.Stringer("blkID", args.BlockID),
  1763  		zap.Stringer("encoding", args.Encoding),
  1764  	)
  1765  
  1766  	s.vm.ctx.Lock.Lock()
  1767  	defer s.vm.ctx.Lock.Unlock()
  1768  
  1769  	block, err := s.vm.manager.GetStatelessBlock(args.BlockID)
  1770  	if err != nil {
  1771  		return fmt.Errorf("couldn't get block with id %s: %w", args.BlockID, err)
  1772  	}
  1773  	response.Encoding = args.Encoding
  1774  
  1775  	var result any
  1776  	if args.Encoding == formatting.JSON {
  1777  		block.InitCtx(s.vm.ctx)
  1778  		result = block
  1779  	} else {
  1780  		result, err = formatting.Encode(args.Encoding, block.Bytes())
  1781  		if err != nil {
  1782  			return fmt.Errorf("couldn't encode block %s as %s: %w", args.BlockID, args.Encoding, err)
  1783  		}
  1784  	}
  1785  
  1786  	response.Block, err = json.Marshal(result)
  1787  	return err
  1788  }
  1789  
  1790  // GetBlockByHeight returns the block at the given height.
  1791  func (s *Service) GetBlockByHeight(_ *http.Request, args *api.GetBlockByHeightArgs, response *api.GetBlockResponse) error {
  1792  	s.vm.ctx.Log.Debug("API called",
  1793  		zap.String("service", "platform"),
  1794  		zap.String("method", "getBlockByHeight"),
  1795  		zap.Uint64("height", uint64(args.Height)),
  1796  		zap.Stringer("encoding", args.Encoding),
  1797  	)
  1798  
  1799  	s.vm.ctx.Lock.Lock()
  1800  	defer s.vm.ctx.Lock.Unlock()
  1801  
  1802  	blockID, err := s.vm.state.GetBlockIDAtHeight(uint64(args.Height))
  1803  	if err != nil {
  1804  		return fmt.Errorf("couldn't get block at height %d: %w", args.Height, err)
  1805  	}
  1806  
  1807  	block, err := s.vm.manager.GetStatelessBlock(blockID)
  1808  	if err != nil {
  1809  		s.vm.ctx.Log.Error("couldn't get accepted block",
  1810  			zap.Stringer("blkID", blockID),
  1811  			zap.Error(err),
  1812  		)
  1813  		return fmt.Errorf("couldn't get block with id %s: %w", blockID, err)
  1814  	}
  1815  	response.Encoding = args.Encoding
  1816  
  1817  	var result any
  1818  	if args.Encoding == formatting.JSON {
  1819  		block.InitCtx(s.vm.ctx)
  1820  		result = block
  1821  	} else {
  1822  		result, err = formatting.Encode(args.Encoding, block.Bytes())
  1823  		if err != nil {
  1824  			return fmt.Errorf("couldn't encode block %s as %s: %w", blockID, args.Encoding, err)
  1825  		}
  1826  	}
  1827  
  1828  	response.Block, err = json.Marshal(result)
  1829  	return err
  1830  }
  1831  
  1832  // GetFeeConfig returns the dynamic fee config of the chain.
  1833  func (s *Service) GetFeeConfig(_ *http.Request, _ *struct{}, reply *gas.Config) error {
  1834  	s.vm.ctx.Log.Debug("API called",
  1835  		zap.String("service", "platform"),
  1836  		zap.String("method", "getFeeConfig"),
  1837  	)
  1838  
  1839  	// TODO: Remove after Etna is activated.
  1840  	now := time.Now()
  1841  	if !s.vm.Config.UpgradeConfig.IsEtnaActivated(now) {
  1842  		return nil
  1843  	}
  1844  
  1845  	*reply = s.vm.DynamicFeeConfig
  1846  	return nil
  1847  }
  1848  
  1849  type GetFeeStateReply struct {
  1850  	gas.State
  1851  	Price gas.Price `json:"price"`
  1852  	Time  time.Time `json:"timestamp"`
  1853  }
  1854  
  1855  // GetFeeState returns the current fee state of the chain.
  1856  func (s *Service) GetFeeState(_ *http.Request, _ *struct{}, reply *GetFeeStateReply) error {
  1857  	s.vm.ctx.Log.Debug("API called",
  1858  		zap.String("service", "platform"),
  1859  		zap.String("method", "getFeeState"),
  1860  	)
  1861  
  1862  	s.vm.ctx.Lock.Lock()
  1863  	defer s.vm.ctx.Lock.Unlock()
  1864  
  1865  	reply.State = s.vm.state.GetFeeState()
  1866  	reply.Price = gas.CalculatePrice(
  1867  		s.vm.DynamicFeeConfig.MinPrice,
  1868  		reply.State.Excess,
  1869  		s.vm.DynamicFeeConfig.ExcessConversionConstant,
  1870  	)
  1871  	reply.Time = s.vm.state.GetTimestamp()
  1872  	return nil
  1873  }
  1874  
  1875  func (s *Service) getAPIUptime(staker *state.Staker) (*avajson.Float32, error) {
  1876  	// Only report uptimes that we have been actively tracking.
  1877  	if constants.PrimaryNetworkID != staker.SubnetID && !s.vm.TrackedSubnets.Contains(staker.SubnetID) {
  1878  		return nil, nil
  1879  	}
  1880  
  1881  	rawUptime, err := s.vm.uptimeManager.CalculateUptimePercentFrom(staker.NodeID, staker.SubnetID, staker.StartTime)
  1882  	if err != nil {
  1883  		return nil, err
  1884  	}
  1885  	// Transform this to a percentage (0-100) to make it consistent
  1886  	// with observedUptime in info.peers API
  1887  	uptime := avajson.Float32(rawUptime * 100)
  1888  	return &uptime, nil
  1889  }
  1890  
  1891  func (s *Service) getAPIOwner(owner *secp256k1fx.OutputOwners) (*platformapi.Owner, error) {
  1892  	apiOwner := &platformapi.Owner{
  1893  		Locktime:  avajson.Uint64(owner.Locktime),
  1894  		Threshold: avajson.Uint32(owner.Threshold),
  1895  		Addresses: make([]string, 0, len(owner.Addrs)),
  1896  	}
  1897  	for _, addr := range owner.Addrs {
  1898  		addrStr, err := s.addrManager.FormatLocalAddress(addr)
  1899  		if err != nil {
  1900  			return nil, err
  1901  		}
  1902  		apiOwner.Addresses = append(apiOwner.Addresses, addrStr)
  1903  	}
  1904  	return apiOwner, nil
  1905  }
  1906  
  1907  // Takes in a staker and a set of addresses
  1908  // Returns:
  1909  // 1) The total amount staked by addresses in [addrs]
  1910  // 2) The staked outputs
  1911  func getStakeHelper(tx *txs.Tx, addrs set.Set[ids.ShortID], totalAmountStaked map[ids.ID]uint64) []avax.TransferableOutput {
  1912  	staker, ok := tx.Unsigned.(txs.PermissionlessStaker)
  1913  	if !ok {
  1914  		return nil
  1915  	}
  1916  
  1917  	stake := staker.Stake()
  1918  	stakedOuts := make([]avax.TransferableOutput, 0, len(stake))
  1919  	// Go through all of the staked outputs
  1920  	for _, output := range stake {
  1921  		out := output.Out
  1922  		if lockedOut, ok := out.(*stakeable.LockOut); ok {
  1923  			// This output can only be used for staking until [stakeOnlyUntil]
  1924  			out = lockedOut.TransferableOut
  1925  		}
  1926  		secpOut, ok := out.(*secp256k1fx.TransferOutput)
  1927  		if !ok {
  1928  			continue
  1929  		}
  1930  
  1931  		// Check whether this output is owned by one of the given addresses
  1932  		contains := false
  1933  		for _, addr := range secpOut.Addrs {
  1934  			if addrs.Contains(addr) {
  1935  				contains = true
  1936  				break
  1937  			}
  1938  		}
  1939  		if !contains {
  1940  			// This output isn't owned by one of the given addresses. Ignore.
  1941  			continue
  1942  		}
  1943  
  1944  		assetID := output.AssetID()
  1945  		newAmount, err := safemath.Add(totalAmountStaked[assetID], secpOut.Amt)
  1946  		if err != nil {
  1947  			newAmount = math.MaxUint64
  1948  		}
  1949  		totalAmountStaked[assetID] = newAmount
  1950  
  1951  		stakedOuts = append(
  1952  			stakedOuts,
  1953  			*output,
  1954  		)
  1955  	}
  1956  	return stakedOuts
  1957  }