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 }