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