Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge rc/v1.6.0 into observers snapshotless #416

6 changes: 6 additions & 0 deletions api/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ var ErrGetValueForKey = errors.New("get value for key error")
// ErrGetKeyValuePairs signals an error in getting the key-value pairs for a given address
var ErrGetKeyValuePairs = errors.New("get key value pairs error")

// ErrInvalidAddressesArray signals that an invalid input has been provided
var ErrInvalidAddressesArray = errors.New("invalid addresses array")

// ErrCannotGetAddresses signals an error when trying to fetch a bulk of accounts
var ErrCannotGetAddresses = errors.New("error while fetching a bulk of accounts")

// ErrComputeShardForAddress signals an error in computing the shard ID for a given address
var ErrComputeShardForAddress = errors.New("compute shard ID for address error")

Expand Down
25 changes: 25 additions & 0 deletions api/groups/baseAccountsGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func NewAccountsGroup(facadeHandler data.FacadeHandler) (*accountsGroup, error)
{Path: "/:address/nft/:tokenIdentifier/nonce/:nonce", Handler: ag.getESDTNftTokenData, Method: http.MethodGet},
{Path: "/:address/guardian-data", Handler: ag.getGuardianData, Method: http.MethodGet},
{Path: "/:address/is-data-trie-migrated", Handler: ag.isDataTrieMigrated, Method: http.MethodGet},
{Path: "/bulk", Handler: ag.getAccounts, Method: http.MethodPost},
}
ag.baseGroup.endpoints = baseRoutesHandlers

Expand Down Expand Up @@ -127,6 +128,30 @@ func (group *accountsGroup) getCodeHash(c *gin.Context) {
c.JSON(http.StatusOK, codeHashResponse)
}

// getAccounts will handle the request for a bulk of addresses data
func (group *accountsGroup) getAccounts(c *gin.Context) {
var addresses []string
err := c.ShouldBindJSON(&addresses)
if err != nil {
shared.RespondWithBadRequest(c, errors.ErrInvalidAddressesArray.Error())
return
}

options, err := parseAccountQueryOptions(c)
if err != nil {
shared.RespondWithValidationError(c, errors.ErrInvalidFields, err)
return
}

response, err := group.facade.GetAccounts(addresses, options)
if err != nil {
shared.RespondWithInternalError(c, errors.ErrCannotGetAddresses, err)
return
}

shared.RespondWith(c, http.StatusOK, response, "", data.ReturnCodeSuccess)
}

// getTransactions returns the transactions for the address parameter
func (group *accountsGroup) getTransactions(c *gin.Context) {
transactions, status, err := group.getTransactionsFromFacade(c)
Expand Down
99 changes: 99 additions & 0 deletions api/groups/baseAccountsGroup_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package groups_test

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -45,6 +47,15 @@ type balanceResponse struct {
Data balanceResponseData
}

type accountsResponseData struct {
Accounts map[string]*data.Account `json:"accounts"`
}

type accountsResponse struct {
GeneralResponse
Data accountsResponseData `json:"data"`
}

type usernameResponseData struct {
Username string `json:"username"`
}
Expand Down Expand Up @@ -240,6 +251,94 @@ func TestGetAccount_ReturnsSuccessfully(t *testing.T) {
assert.Empty(t, accountResponse.Error)
}

//------- GetAccounts

func TestGetAccount_FailsWhenInvalidRequest(t *testing.T) {
t.Parallel()

facade := &mock.FacadeStub{}
addressGroup, err := groups.NewAccountsGroup(facade)
require.NoError(t, err)
ws := startProxyServer(addressGroup, addressPath)

req, _ := http.NewRequest("POST", "/address/bulk", bytes.NewBuffer([]byte(`invalid request`)))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

accountsResponse := accountsResponse{}
loadResponse(resp.Body, &accountsResponse)

assert.Equal(t, http.StatusBadRequest, resp.Code)
assert.Empty(t, accountsResponse.Data)
assert.Equal(t, accountsResponse.Error, apiErrors.ErrInvalidAddressesArray.Error())
}

func TestGetAccount_FailWhenFacadeGetAccountsFails(t *testing.T) {
t.Parallel()

returnedError := "i am an error"
facade := &mock.FacadeStub{
GetAccountsHandler: func(addresses []string, _ common.AccountQueryOptions) (*data.AccountsModel, error) {
return nil, errors.New(returnedError)
},
}
addressGroup, err := groups.NewAccountsGroup(facade)
require.NoError(t, err)
ws := startProxyServer(addressGroup, addressPath)

req, _ := http.NewRequest("POST", "/address/bulk", bytes.NewBuffer([]byte(`["test", "test"]`)))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

accountsResponse := accountsResponse{}
loadResponse(resp.Body, &accountsResponse)

assert.Equal(t, http.StatusInternalServerError, resp.Code)
assert.Empty(t, accountsResponse.Data)
assert.Contains(t, accountsResponse.Error, returnedError)
}

func TestGetAccounts_ReturnsSuccessfully(t *testing.T) {
t.Parallel()

accounts := map[string]*data.Account{
"erd1alice": {
Address: "erd1alice",
Nonce: 1,
Balance: "100",
},
"erd1bob": {
Address: "erd1bob",
Nonce: 1,
Balance: "101",
},
}
facade := &mock.FacadeStub{
GetAccountsHandler: func(addresses []string, _ common.AccountQueryOptions) (*data.AccountsModel, error) {
return &data.AccountsModel{
Accounts: accounts,
}, nil
},
}
addressGroup, err := groups.NewAccountsGroup(facade)
require.NoError(t, err)
ws := startProxyServer(addressGroup, addressPath)

reqAddresses := []string{"erd1alice", "erd1bob"}
addressBytes, _ := json.Marshal(reqAddresses)
fmt.Println(string(addressBytes))
req, _ := http.NewRequest("POST", "/address/bulk", bytes.NewBuffer(addressBytes))
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

accountsResponse := accountsResponse{}
loadResponse(resp.Body, &accountsResponse)

assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, accountsResponse.Data.Accounts, accounts)
assert.Empty(t, accountsResponse.Error)
}

//------- GetBalance

func TestGetBalance_ReturnsSuccessfully(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions api/groups/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type AccountsFacadeHandler interface {
GetValueForKey(address string, key string, options common.AccountQueryOptions) (string, error)
GetAllESDTTokens(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetKeyValuePairs(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetAccounts(addresses []string, options common.AccountQueryOptions) (*data.AccountsModel, error)
GetESDTTokenData(address string, key string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetESDTsWithRole(address string, role string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetESDTsRoles(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
Expand Down
6 changes: 6 additions & 0 deletions api/mock/facadeStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
type FacadeStub struct {
IsFaucetEnabledHandler func() bool
GetAccountHandler func(address string, options common.AccountQueryOptions) (*data.AccountModel, error)
GetAccountsHandler func(addresses []string, options common.AccountQueryOptions) (*data.AccountsModel, error)
GetShardIDForAddressHandler func(address string) (uint32, error)
GetValueForKeyHandler func(address string, key string, options common.AccountQueryOptions) (string, error)
GetKeyValuePairsHandler func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
Expand Down Expand Up @@ -256,6 +257,11 @@ func (f *FacadeStub) GetAccount(address string, options common.AccountQueryOptio
return f.GetAccountHandler(address, options)
}

// GetAccounts -
func (f *FacadeStub) GetAccounts(addresses []string, options common.AccountQueryOptions) (*data.AccountsModel, error) {
return f.GetAccountsHandler(addresses, options)
}

// GetKeyValuePairs -
func (f *FacadeStub) GetKeyValuePairs(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
return f.GetKeyValuePairsHandler(address, options)
Expand Down
1 change: 1 addition & 0 deletions cmd/proxy/config/apiConfig/v1_0.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Routes = [
[APIPackages.address]
Routes = [
{ Name = "/:address", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/bulk", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/balance", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/nonce", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/username", Open = true, Secured = false, RateLimit = 0 },
Expand Down
1 change: 1 addition & 0 deletions cmd/proxy/config/apiConfig/v_next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Routes = [
[APIPackages.address]
Routes = [
{ Name = "/:address", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/bulk", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/balance", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/nonce", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/username", Open = true, Secured = false, RateLimit = 0 },
Expand Down
22 changes: 17 additions & 5 deletions cmd/proxy/config/swagger/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -1243,12 +1243,24 @@
}
}
},
"/network/trie-statistics": {
"/network/trie-statistics/{shard}": {
"get": {
"tags": [
"network"
],
"summary": "returns metrics for the trie statistics",
"summary": "returns metrics for the trie statistics in the given shard",
"parameters": [
{
"name": "shard",
"in": "path",
"description": "the shard ID to fetch the status from",
"required": true,
"schema": {
"type": "integer",
"default": 0
}
}
],
"responses": {
"200": {
"description": "successful operation",
Expand Down Expand Up @@ -1650,7 +1662,7 @@
"tags": [
"internal"
],
"summary": "returns the block at the given nonce following the protocol structure, but JSON encoded",
"summary": "returns the block at the given nonce following the protocol structure, JSON encoded",
"parameters": [
{
"name": "shard",
Expand Down Expand Up @@ -1693,7 +1705,7 @@
"tags": [
"internal"
],
"summary": "returns the block with the given hash following the protocol structure, nut JSON encoded",
"summary": "returns the block with the given hash following the protocol structure, JSON encoded",
"parameters": [
{
"name": "shard",
Expand Down Expand Up @@ -3269,4 +3281,4 @@
}
}
}
}
}
12 changes: 12 additions & 0 deletions data/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ type AccountModel struct {
BlockInfo BlockInfo `json:"blockInfo"`
}

// AccountsModel defines the model of the accounts response
type AccountsModel struct {
Accounts map[string]*Account `json:"accounts"`
}

// Account defines the data structure for an account
type Account struct {
Address string `json:"address"`
Expand Down Expand Up @@ -58,6 +63,13 @@ type AccountApiResponse struct {
Code string `json:"code"`
}

// AccountsApiResponse defines the response that will be returned by the node when requesting multiple accounts
type AccountsApiResponse struct {
Data AccountsModel `json:"data"`
Error string `json:"error"`
Code string `json:"code"`
}

// AccountKeyValueResponseData follows the format of the data field on an account key-value response
type AccountKeyValueResponseData struct {
Value string `json:"value"`
Expand Down
Loading
Loading