From bfe72ecf1c23471ee14aa6aa4554930d3b73a45e Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 10 Apr 2020 14:48:38 -0400 Subject: [PATCH 1/6] add ListAssets API method --- vms/avm/service.go | 77 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index a96200d..0d72e30 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -7,6 +7,7 @@ import ( "bytes" "errors" "fmt" + "math" "net/http" "github.com/ava-labs/gecko/ids" @@ -15,7 +16,7 @@ import ( "github.com/ava-labs/gecko/utils/formatting" "github.com/ava-labs/gecko/utils/hashing" "github.com/ava-labs/gecko/utils/json" - "github.com/ava-labs/gecko/utils/math" + safemath "github.com/ava-labs/gecko/utils/math" "github.com/ava-labs/gecko/vms/components/ava" "github.com/ava-labs/gecko/vms/components/verify" "github.com/ava-labs/gecko/vms/secp256k1fx" @@ -221,7 +222,7 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply if !ok { continue } - amt, err := math.Add64(transferable.Amount(), uint64(reply.Balance)) + amt, err := safemath.Add64(transferable.Amount(), uint64(reply.Balance)) if err != nil { return err } @@ -231,6 +232,72 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply return nil } +// ListAssetsArgs are arguments for calling into ListAssets +type ListAssetsArgs struct { + Address string `json:"address"` +} + +type listAssetsReplyElement struct { + AssetID string `json:"assetID"` + Balance json.Uint64 `json:"balance"` +} + +// ListAssetsReply is the response from a call to ListAssets +type ListAssetsReply struct { + Assets []listAssetsReplyElement `json:"assets"` +} + +// ListAssets returns a list of maps where each map is: +// Key: ID of an asset such that [args.Address] has a non-zero balance of the asset +// Value: The balance of the asset held by the address +// Returns null if the address holds no assets +// Note that balances include assets that the address only _partially_ owns +// (ie is one of several addresses specified in a multi-sig) +func (service *Service) ListAssets(r *http.Request, args *ListAssetsArgs, reply *ListAssetsReply) error { + service.vm.ctx.Log.Verbo("ListAssets called with address: %s", args.Address) + + address, err := service.vm.Parse(args.Address) + if err != nil { + return fmt.Errorf("couldn't parse given address: %s", err) + } + addrAsSet := ids.Set{} + addrAsSet.Add(ids.NewID(hashing.ComputeHash256Array(address))) + + utxos, err := service.vm.GetUTXOs(addrAsSet) + if err != nil { + return fmt.Errorf("couldn't get address's UTXOs: %s", err) + } + + assetIDs := ids.Set{} // IDs of assets the address has a non-zero balance of + balances := make(map[[32]byte]uint64, 0) // key: ID (as bytes). value: balance of that asset + for _, utxo := range utxos { + transferable, ok := utxo.Out.(ava.Transferable) + if !ok { + continue + } + assetID := utxo.AssetID() + assetIDs.Add(assetID) + if balance, ok := balances[assetID.Key()]; ok { + balance, err := safemath.Add64(transferable.Amount(), balance) + if err != nil { + balances[assetID.Key()] = math.MaxUint64 + } else { + balances[assetID.Key()] = balance + } + } else { + balances[assetID.Key()] = transferable.Amount() + } + } + + sortedAssetIDs := assetIDs.List() // sort so response is always in same order + ids.SortIDs(sortedAssetIDs) + for _, assetID := range sortedAssetIDs { + reply.Assets = append(reply.Assets, listAssetsReplyElement{assetID.String(), json.Uint64(balances[assetID.Key()])}) + } + + return nil +} + // CreateFixedCapAssetArgs are arguments for passing into CreateFixedCapAsset requests type CreateFixedCapAssetArgs struct { Username string `json:"username"` @@ -613,7 +680,7 @@ func (service *Service) Send(r *http.Request, args *SendArgs, reply *SendReply) if !ok { continue } - spent, err := math.Add64(amountSpent, input.Amount()) + spent, err := safemath.Add64(amountSpent, input.Amount()) if err != nil { return errSpendOverflow } @@ -1020,7 +1087,7 @@ func (service *Service) ImportAVA(_ *http.Request, args *ImportAVAArgs, reply *I if !ok { continue } - spent, err := math.Add64(amount, input.Amount()) + spent, err := safemath.Add64(amount, input.Amount()) if err != nil { return errSpendOverflow } @@ -1164,7 +1231,7 @@ func (service *Service) ExportAVA(_ *http.Request, args *ExportAVAArgs, reply *E if !ok { continue } - spent, err := math.Add64(amountSpent, input.Amount()) + spent, err := safemath.Add64(amountSpent, input.Amount()) if err != nil { return errSpendOverflow } From 53b29745b6cb79280d2de76ef503bfb3c99ddec0 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 10 Apr 2020 16:05:11 -0400 Subject: [PATCH 2/6] rename method to GetAllBalances; change return type to map --- vms/avm/service.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index 0d72e30..b16d5e2 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -232,29 +232,23 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply return nil } -// ListAssetsArgs are arguments for calling into ListAssets -type ListAssetsArgs struct { +// GetAllBalancesArgs are arguments for calling into GetAllBalances +type GetAllBalancesArgs struct { Address string `json:"address"` } -type listAssetsReplyElement struct { - AssetID string `json:"assetID"` - Balance json.Uint64 `json:"balance"` +// GetAllBalancesReply is the response from a call to GetAllBalances +type GetAllBalancesReply struct { + Balances map[string]json.Uint64 `json:"balances"` } -// ListAssetsReply is the response from a call to ListAssets -type ListAssetsReply struct { - Assets []listAssetsReplyElement `json:"assets"` -} - -// ListAssets returns a list of maps where each map is: +// GetAllBalances returns a map where: // Key: ID of an asset such that [args.Address] has a non-zero balance of the asset // Value: The balance of the asset held by the address -// Returns null if the address holds no assets // Note that balances include assets that the address only _partially_ owns // (ie is one of several addresses specified in a multi-sig) -func (service *Service) ListAssets(r *http.Request, args *ListAssetsArgs, reply *ListAssetsReply) error { - service.vm.ctx.Log.Verbo("ListAssets called with address: %s", args.Address) +func (service *Service) GetAllBalances(r *http.Request, args *GetAllBalancesArgs, reply *GetAllBalancesReply) error { + service.vm.ctx.Log.Verbo("GetAllBalances called with address: %s", args.Address) address, err := service.vm.Parse(args.Address) if err != nil { @@ -291,8 +285,9 @@ func (service *Service) ListAssets(r *http.Request, args *ListAssetsArgs, reply sortedAssetIDs := assetIDs.List() // sort so response is always in same order ids.SortIDs(sortedAssetIDs) + reply.Balances = make(map[string]json.Uint64, len(sortedAssetIDs)) for _, assetID := range sortedAssetIDs { - reply.Assets = append(reply.Assets, listAssetsReplyElement{assetID.String(), json.Uint64(balances[assetID.Key()])}) + reply.Balances[assetID.String()] = json.Uint64(balances[assetID.Key()]) } return nil From 10aa724d30f2d499d0424558550b24341b1d0a9e Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Fri, 1 May 2020 13:28:38 -0400 Subject: [PATCH 3/6] don't try to sort balances since maps aren't sorted by key. simplify balance calculation since zero value of uint64 is 0. --- vms/avm/service.go | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index b16d5e2..8b90278 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -271,22 +271,17 @@ func (service *Service) GetAllBalances(r *http.Request, args *GetAllBalancesArgs } assetID := utxo.AssetID() assetIDs.Add(assetID) - if balance, ok := balances[assetID.Key()]; ok { - balance, err := safemath.Add64(transferable.Amount(), balance) - if err != nil { - balances[assetID.Key()] = math.MaxUint64 - } else { - balances[assetID.Key()] = balance - } + balance := balances[assetID.Key()] // 0 if key doesn't exist + balance, err := safemath.Add64(transferable.Amount(), balance) + if err != nil { + balances[assetID.Key()] = math.MaxUint64 } else { - balances[assetID.Key()] = transferable.Amount() + balances[assetID.Key()] = balance } } - sortedAssetIDs := assetIDs.List() // sort so response is always in same order - ids.SortIDs(sortedAssetIDs) - reply.Balances = make(map[string]json.Uint64, len(sortedAssetIDs)) - for _, assetID := range sortedAssetIDs { + reply.Balances = make(map[string]json.Uint64, assetIDs.Len()) + for _, assetID := range assetIDs.List() { reply.Balances[assetID.String()] = json.Uint64(balances[assetID.Key()]) } From cbb20b2faaae224791a71d6bfb526fc38b0c8567 Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 4 May 2020 13:59:10 -0400 Subject: [PATCH 4/6] return 'AVA' rather than its asset id --- vms/avm/service.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index 8b90278..20b4578 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -280,9 +280,18 @@ func (service *Service) GetAllBalances(r *http.Request, args *GetAllBalancesArgs } } + avaAssetID, err := service.vm.Lookup("AVA") + if err != nil { + return errors.New("couldn't get asset ID of AVA") + } + reply.Balances = make(map[string]json.Uint64, assetIDs.Len()) for _, assetID := range assetIDs.List() { - reply.Balances[assetID.String()] = json.Uint64(balances[assetID.Key()]) + if assetID.Equals(avaAssetID) { + reply.Balances["AVA"] = json.Uint64(balances[assetID.Key()]) + } else { + reply.Balances[assetID.String()] = json.Uint64(balances[assetID.Key()]) + } } return nil From d727166f4fb4835384cd522d0d6dcc1a0741fecb Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 4 May 2020 14:44:35 -0400 Subject: [PATCH 5/6] change response format for getAllBalances --- vms/avm/service.go | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index 20b4578..b9f3aaa 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -232,6 +232,12 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply return nil } +// Balance ... +type Balance struct { + AssetID string `json:"assetID"` + Balance json.Uint64 `json:"balance"` +} + // GetAllBalancesArgs are arguments for calling into GetAllBalances type GetAllBalancesArgs struct { Address string `json:"address"` @@ -239,7 +245,7 @@ type GetAllBalancesArgs struct { // GetAllBalancesReply is the response from a call to GetAllBalances type GetAllBalancesReply struct { - Balances map[string]json.Uint64 `json:"balances"` + Balances []Balance `json:"balances"` } // GetAllBalances returns a map where: @@ -285,13 +291,15 @@ func (service *Service) GetAllBalances(r *http.Request, args *GetAllBalancesArgs return errors.New("couldn't get asset ID of AVA") } - reply.Balances = make(map[string]json.Uint64, assetIDs.Len()) - for _, assetID := range assetIDs.List() { + reply.Balances = make([]Balance, assetIDs.Len()) + for i, assetID := range assetIDs.List() { + var b Balance if assetID.Equals(avaAssetID) { - reply.Balances["AVA"] = json.Uint64(balances[assetID.Key()]) + b = Balance{AssetID: "AVA", Balance: json.Uint64(balances[assetID.Key()])} } else { - reply.Balances[assetID.String()] = json.Uint64(balances[assetID.Key()]) + b = Balance{AssetID: assetID.String(), Balance: json.Uint64(balances[assetID.Key()])} } + reply.Balances[i] = b } return nil From a721c188a5c868f98a572135e624c5110f52bbae Mon Sep 17 00:00:00 2001 From: Dan Laine Date: Mon, 4 May 2020 17:06:07 -0400 Subject: [PATCH 6/6] use asset alias in response --- vms/avm/service.go | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/vms/avm/service.go b/vms/avm/service.go index b9f3aaa..10b9b3b 100644 --- a/vms/avm/service.go +++ b/vms/avm/service.go @@ -234,7 +234,7 @@ func (service *Service) GetBalance(r *http.Request, args *GetBalanceArgs, reply // Balance ... type Balance struct { - AssetID string `json:"assetID"` + AssetID string `json:"asset"` Balance json.Uint64 `json:"balance"` } @@ -286,20 +286,19 @@ func (service *Service) GetAllBalances(r *http.Request, args *GetAllBalancesArgs } } - avaAssetID, err := service.vm.Lookup("AVA") - if err != nil { - return errors.New("couldn't get asset ID of AVA") - } - reply.Balances = make([]Balance, assetIDs.Len()) for i, assetID := range assetIDs.List() { - var b Balance - if assetID.Equals(avaAssetID) { - b = Balance{AssetID: "AVA", Balance: json.Uint64(balances[assetID.Key()])} + if alias, err := service.vm.PrimaryAlias(assetID); err == nil { + reply.Balances[i] = Balance{ + AssetID: alias, + Balance: json.Uint64(balances[assetID.Key()]), + } } else { - b = Balance{AssetID: assetID.String(), Balance: json.Uint64(balances[assetID.Key()])} + reply.Balances[i] = Balance{ + AssetID: assetID.String(), + Balance: json.Uint64(balances[assetID.Key()]), + } } - reply.Balances[i] = b } return nil