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