Skip to content

Commit

Permalink
Merge pull request #416 from multiversx/merge-rc-v1-6-0-observers-sna…
Browse files Browse the repository at this point in the history
…pshotless

Merge rc/v1.6.0 into observers snapshotless
  • Loading branch information
iulianpascalau authored Dec 12, 2023
2 parents bee9396 + eb09c87 commit 4bc6dae
Show file tree
Hide file tree
Showing 14 changed files with 524 additions and 155 deletions.
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

0 comments on commit 4bc6dae

Please sign in to comment.