Skip to content

Commit

Permalink
Merge pull request #379 from multiversx/add-data-trie-migration-statu…
Browse files Browse the repository at this point in the history
…s-endpoint

add is-data-trie-migrated endpoint for address
  • Loading branch information
BeniaminDrasovean authored May 30, 2023
2 parents c8d16de + 27560fe commit 5c93c27
Show file tree
Hide file tree
Showing 12 changed files with 227 additions and 2 deletions.
3 changes: 3 additions & 0 deletions api/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ var ErrInvalidFields = errors.New("invalid fields")
// ErrOperationNotAllowed signals that the operation is not allowed
var ErrOperationNotAllowed = errors.New("operation not allowed")

// ErrIsDataTrieMigrated signals that an error occurred while trying to verify the migration status of the data trie
var ErrIsDataTrieMigrated = errors.New("could not verify the migration status of the data trie")

// ErrInvalidTxFields signals that one or more field of a transaction are invalid
type ErrInvalidTxFields struct {
Message string
Expand Down
23 changes: 23 additions & 0 deletions api/groups/baseAccountsGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ func NewAccountsGroup(facadeHandler data.FacadeHandler) (*accountsGroup, error)
{Path: "/:address/esdts/roles", Handler: ag.getESDTsRoles, Method: http.MethodGet},
{Path: "/:address/registered-nfts", Handler: ag.getRegisteredNFTs, Method: http.MethodGet},
{Path: "/:address/nft/:tokenIdentifier/nonce/:nonce", Handler: ag.getESDTNftTokenData, Method: http.MethodGet},
{Path: "/:address/is-data-trie-migrated", Handler: ag.isDataTrieMigrated, Method: http.MethodGet},
}
ag.baseGroup.endpoints = baseRoutesHandlers

Expand Down Expand Up @@ -370,3 +371,25 @@ func (group *accountsGroup) getESDTTokens(c *gin.Context) {

c.JSON(http.StatusOK, tokens)
}

func (group *accountsGroup) isDataTrieMigrated(c *gin.Context) {
addr := c.Param("address")
if addr == "" {
shared.RespondWithValidationError(c, errors.ErrIsDataTrieMigrated, errors.ErrEmptyAddress)
return
}

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

isMigrated, err := group.facade.IsDataTrieMigrated(addr, options)
if err != nil {
shared.RespondWithInternalError(c, errors.ErrIsDataTrieMigrated, err)
return
}

c.JSON(http.StatusOK, isMigrated)
}
61 changes: 61 additions & 0 deletions api/groups/baseAccountsGroup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -841,3 +841,64 @@ func TestGetCodeHash_ReturnsSuccessfully(t *testing.T) {
assert.Equal(t, expectedResponse, actualResponse)
assert.Empty(t, actualResponse.Error)
}

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

t.Run("should return error when facade returns error", func(t *testing.T) {
t.Parallel()

expectedErr := errors.New("internal err")
facade := &mock.FacadeStub{
IsDataTrieMigratedCalled: func(_ string, _ common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
return nil, expectedErr
},
}
addressGroup, err := groups.NewAccountsGroup(facade)
require.NoError(t, err)
ws := startProxyServer(addressGroup, addressPath)

reqAddress := "test"
req, _ := http.NewRequest("GET", fmt.Sprintf("/address/%s/is-data-trie-migrated", reqAddress), nil)
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

response := &data.GenericAPIResponse{}
loadResponse(resp.Body, &response)

assert.Equal(t, http.StatusInternalServerError, resp.Code)
assert.True(t, strings.Contains(response.Error, expectedErr.Error()))
})

t.Run("should return successfully", func(t *testing.T) {
t.Parallel()

expectedResponse := &data.GenericAPIResponse{
Data: map[string]interface{}{
"isMigrated": "true",
},
Error: "",
Code: data.ReturnCodeSuccess,
}
facade := &mock.FacadeStub{
IsDataTrieMigratedCalled: func(_ string, _ common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
return expectedResponse, nil
},
}
addressGroup, err := groups.NewAccountsGroup(facade)
require.NoError(t, err)
ws := startProxyServer(addressGroup, addressPath)

reqAddress := "test"
req, _ := http.NewRequest("GET", fmt.Sprintf("/address/%s/is-data-trie-migrated", reqAddress), nil)
resp := httptest.NewRecorder()
ws.ServeHTTP(resp, req)

actualResponse := &data.GenericAPIResponse{}
loadResponse(resp.Body, &actualResponse)

assert.Equal(t, http.StatusOK, resp.Code)
assert.Equal(t, expectedResponse, actualResponse)
assert.Empty(t, actualResponse.Error)
})
}
1 change: 1 addition & 0 deletions api/groups/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type AccountsFacadeHandler interface {
GetESDTsRoles(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetESDTNftTokenData(address string, key string, nonce uint64, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetNFTTokenIDsRegisteredByAddress(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
IsDataTrieMigrated(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
}

// BlockFacadeHandler interface defines methods that can be used from the facade
Expand Down
10 changes: 10 additions & 0 deletions api/mock/facadeStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ type FacadeStub struct {
GetTriesStatisticsCalled func(shardID uint32) (*data.TrieStatisticsAPIResponse, error)
GetEpochStartDataCalled func(epoch uint32, shardID uint32) (*data.GenericAPIResponse, error)
GetCodeHashCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
IsDataTrieMigratedCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
}

// GetProof -
Expand Down Expand Up @@ -526,6 +527,15 @@ func (f *FacadeStub) GetCodeHash(address string, options common.AccountQueryOpti
return f.GetCodeHashCalled(address, options)
}

// IsDataTrieMigrated -
func (f *FacadeStub) IsDataTrieMigrated(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
if f.IsDataTrieMigratedCalled != nil {
return f.IsDataTrieMigratedCalled(address, options)
}

return &data.GenericAPIResponse{}, nil
}

// WrongFacade is a struct that can be used as a wrong implementation of the node router handler
type WrongFacade struct {
}
3 changes: 2 additions & 1 deletion cmd/proxy/config/apiConfig/v1_0.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Routes = [
{ Name = "/:address/registered-nfts", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/nft/:tokenIdentifier/nonce/:nonce", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/shard", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/transactions", Open = true, Secured = false, RateLimit = 0 }
{ Name = "/:address/transactions", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/is-data-trie-migrated", Open = true, Secured = false, RateLimit = 0 }
]

[APIPackages.hyperblock]
Expand Down
3 changes: 2 additions & 1 deletion cmd/proxy/config/apiConfig/v_next.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ Routes = [
{ Name = "/:address/registered-nfts", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/nft/:tokenIdentifier/nonce/:nonce", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/shard", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/transactions", Open = true, Secured = false, RateLimit = 0 }
{ Name = "/:address/transactions", Open = true, Secured = false, RateLimit = 0 },
{ Name = "/:address/is-data-trie-migrated", Open = true, Secured = false, RateLimit = 0 }
]

[APIPackages.hyperblock]
Expand Down
5 changes: 5 additions & 0 deletions facade/baseFacade.go
Original file line number Diff line number Diff line change
Expand Up @@ -515,3 +515,8 @@ func (epf *ProxyFacade) GetEpochStartData(epoch uint32, shardID uint32) (*data.G
func (epf *ProxyFacade) GetInternalStartOfEpochValidatorsInfo(epoch uint32) (*data.ValidatorsInfoApiResponse, error) {
return epf.blockProc.GetInternalStartOfEpochValidatorsInfo(epoch)
}

// IsDataTrieMigrated returns true if the data trie for the given address is migrated
func (epf *ProxyFacade) IsDataTrieMigrated(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
return epf.accountProc.IsDataTrieMigrated(address, options)
}
1 change: 1 addition & 0 deletions facade/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type AccountProcessor interface {
GetESDTNftTokenData(address string, key string, nonce uint64, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetNFTTokenIDsRegisteredByAddress(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetCodeHash(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
IsDataTrieMigrated(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
}

// TransactionProcessor defines what a transaction request processor should do
Expand Down
10 changes: 10 additions & 0 deletions facade/mock/accountProccessorStub.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ type AccountProcessorStub struct {
GetKeyValuePairsCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetESDTsRolesCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
GetCodeHashCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
IsDataTrieMigratedCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error)
}

// GetKeyValuePairs -
Expand Down Expand Up @@ -90,3 +91,12 @@ func (aps *AccountProcessorStub) GetCodeHash(address string, options common.Acco
func (aps *AccountProcessorStub) ValidatorStatistics() (map[string]*data.ValidatorApiResponse, error) {
return aps.ValidatorStatisticsCalled()
}

// IsDataTrieMigrated --
func (aps *AccountProcessorStub) IsDataTrieMigrated(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
if aps.IsDataTrieMigratedCalled != nil {
return aps.IsDataTrieMigratedCalled(address, options)
}

return &data.GenericAPIResponse{}, nil
}
31 changes: 31 additions & 0 deletions process/accountProcessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,34 @@ func (ap *AccountProcessor) getObserversForAddress(address string) ([]*data.Node
func (ap *AccountProcessor) GetBaseProcessor() Processor {
return ap.proc
}

// IsDataTrieMigrated returns true if the data trie for the given address is migrated
func (ap *AccountProcessor) IsDataTrieMigrated(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) {
observers, err := ap.getObserversForAddress(address)
if err != nil {
return nil, err
}

for _, observer := range observers {
apiResponse := data.GenericAPIResponse{}
apiPath := AddressPath + address + "/is-data-trie-migrated"
apiPath = common.BuildUrlWithAccountQueryOptions(apiPath, options)
respCode, err := ap.proc.CallGetRestEndPoint(observer.Address, apiPath, &apiResponse)
if err == nil || respCode == http.StatusBadRequest || respCode == http.StatusInternalServerError {
log.Info("is data trie migrated",
"address", address,
"shard ID", observer.ShardId,
"observer", observer.Address,
"http code", respCode)
if apiResponse.Error != "" {
return nil, errors.New(apiResponse.Error)
}

return &apiResponse, nil
}

log.Error("account is data trie migrated", "observer", observer.Address, "address", address, "error", err.Error())
}

return nil, ErrSendingRequest
}
78 changes: 78 additions & 0 deletions process/accountProcessor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,3 +486,81 @@ func TestAccountProcessor_GetCodeHash(t *testing.T) {
require.NoError(t, err)
require.Equal(t, "code-hash", response.Data.([]string)[0])
}

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

t.Run("should return error when cannot get observers", func(t *testing.T) {
ap, _ := process.NewAccountProcessor(
&mock.ProcessorStub{
GetObserversCalled: func(_ uint32) ([]*data.NodeData, error) {
return nil, errors.New("cannot get observers")
},
},
&mock.PubKeyConverterMock{},
&mock.ElasticSearchConnectorMock{},
)

result, err := ap.IsDataTrieMigrated("address", common.AccountQueryOptions{})
require.Error(t, err)
require.Nil(t, result)
})

t.Run("should return error when cannot get data trie migrated", func(t *testing.T) {
ap, _ := process.NewAccountProcessor(
&mock.ProcessorStub{
GetObserversCalled: func(_ uint32) ([]*data.NodeData, error) {
return []*data.NodeData{
{
Address: "observer0",
ShardId: 0,
},
}, nil
},

CallGetRestEndPointCalled: func(_ string, _ string, _ interface{}) (int, error) {
return 0, errors.New("cannot get data trie migrated")
},
ComputeShardIdCalled: func(_ []byte) (uint32, error) {
return 0, nil
},
},
&mock.PubKeyConverterMock{},
&mock.ElasticSearchConnectorMock{},
)

result, err := ap.IsDataTrieMigrated("DEADBEEF", common.AccountQueryOptions{})
require.Error(t, err)
require.Nil(t, result)
})

t.Run("should work", func(t *testing.T) {
ap, _ := process.NewAccountProcessor(
&mock.ProcessorStub{
GetObserversCalled: func(_ uint32) ([]*data.NodeData, error) {
return []*data.NodeData{
{
Address: "observer0",
ShardId: 0,
},
}, nil
},

CallGetRestEndPointCalled: func(_ string, _ string, value interface{}) (int, error) {
dataTrieMigratedResponse := value.(*data.GenericAPIResponse)
dataTrieMigratedResponse.Data = true
return 0, nil
},
ComputeShardIdCalled: func(_ []byte) (uint32, error) {
return 0, nil
},
},
&mock.PubKeyConverterMock{},
&mock.ElasticSearchConnectorMock{},
)

result, err := ap.IsDataTrieMigrated("DEADBEEF", common.AccountQueryOptions{})
require.NoError(t, err)
require.True(t, result.Data.(bool))
})
}

0 comments on commit 5c93c27

Please sign in to comment.