From b094897b9e25592406fa08e5f5f5323411783f74 Mon Sep 17 00:00:00 2001 From: Sorin Stanculeanu Date: Thu, 16 Nov 2023 12:07:52 +0200 Subject: [PATCH 1/4] added support for new endpoint /node/waiting-epochs-left/:key --- api/groups/baseNodeGroup.go | 12 ++++ api/groups/baseNodeGroup_test.go | 50 ++++++++++++++ api/groups/interface.go | 1 + api/mock/facadeStub.go | 9 +++ cmd/proxy/config/apiConfig/v1_0.toml | 3 +- cmd/proxy/config/apiConfig/v_next.toml | 3 +- cmd/proxy/config/swagger/openapi.json | 34 ++++++++++ data/nodeStatus.go | 12 ++++ facade/baseFacade.go | 5 ++ facade/baseFacade_test.go | 35 ++++++++++ facade/interface.go | 1 + facade/mock/nodeGroupProcessorStub.go | 13 +++- process/nodeGroupProcessor.go | 94 +++++++++++++++++--------- process/nodeGroupProcessor_test.go | 72 ++++++++++++++++++++ 14 files changed, 307 insertions(+), 37 deletions(-) diff --git a/api/groups/baseNodeGroup.go b/api/groups/baseNodeGroup.go index c822fa96..aecd4c85 100644 --- a/api/groups/baseNodeGroup.go +++ b/api/groups/baseNodeGroup.go @@ -29,6 +29,7 @@ func NewNodeGroup(facadeHandler data.FacadeHandler) (*nodeGroup, error) { baseRoutesHandlers := []*data.EndpointHandlerData{ {Path: "/heartbeatstatus", Handler: ng.getHeartbeatData, Method: http.MethodGet}, {Path: "/old-storage-token/:token/nonce/:nonce", Handler: ng.isOldStorageForToken, Method: http.MethodGet}, + {Path: "/waiting-epochs-left/:key", Handler: ng.waitingEpochsLeft, Method: http.MethodGet}, } ng.baseGroup.endpoints = baseRoutesHandlers @@ -69,3 +70,14 @@ func (group *nodeGroup) isOldStorageForToken(c *gin.Context) { shared.RespondWith(c, http.StatusOK, gin.H{"isOldStorage": isOldStorage}, "", data.ReturnCodeSuccess) } + +func (group *nodeGroup) waitingEpochsLeft(c *gin.Context) { + publicKey := c.Param("key") + response, err := group.facade.GetWaitingEpochsLeftForPublicKey(publicKey) + if err != nil { + shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError) + return + } + + shared.RespondWith(c, http.StatusOK, response.Data, "", data.ReturnCodeSuccess) +} diff --git a/api/groups/baseNodeGroup_test.go b/api/groups/baseNodeGroup_test.go index 25083b58..402516d0 100644 --- a/api/groups/baseNodeGroup_test.go +++ b/api/groups/baseNodeGroup_test.go @@ -152,3 +152,53 @@ func TestNodeGroup_IsOldStorageToken(t *testing.T) { assert.Equal(t, "map[isOldStorage:true]", fmt.Sprintf("%v", result.Data)) }) } + +func TestBaseNodeGroup_GetWaitingEpochsLeftForPublicKey(t *testing.T) { + t.Parallel() + + t.Run("facade returns bad request", func(t *testing.T) { + t.Parallel() + + facade := &mock.FacadeStub{ + GetWaitingEpochsLeftForPublicKeyCalled: func(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + return nil, errors.New("bad request") + }, + } + nodeGroup, err := groups.NewNodeGroup(facade) + require.NoError(t, err) + ws := startProxyServer(nodeGroup, nodePath) + + req, _ := http.NewRequest("GET", "/node/waiting-epochs-left/key", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusInternalServerError, resp.Code) + }) + t.Run("facade returns bad request", func(t *testing.T) { + t.Parallel() + + providedData := data.WaitingEpochsLeftResponse{ + EpochsLeft: 10, + } + facade := &mock.FacadeStub{ + GetWaitingEpochsLeftForPublicKeyCalled: func(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + return &data.WaitingEpochsLeftApiResponse{ + Data: providedData, + }, nil + }, + } + nodeGroup, err := groups.NewNodeGroup(facade) + require.NoError(t, err) + ws := startProxyServer(nodeGroup, nodePath) + + req, _ := http.NewRequest("GET", "/node/waiting-epochs-left/key", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusOK, resp.Code) + + var result data.WaitingEpochsLeftApiResponse + loadResponse(resp.Body, &result) + assert.Equal(t, providedData, result.Data) + }) +} diff --git a/api/groups/interface.go b/api/groups/interface.go index 253a3327..0557468b 100644 --- a/api/groups/interface.go +++ b/api/groups/interface.go @@ -81,6 +81,7 @@ type NetworkFacadeHandler interface { type NodeFacadeHandler interface { GetHeartbeatData() (*data.HeartbeatResponse, error) IsOldStorageForToken(tokenID string, nonce uint64) (bool, error) + GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) } // StatusFacadeHandler interface defines methods that can be used from the facade diff --git a/api/mock/facadeStub.go b/api/mock/facadeStub.go index abf744a8..89143c40 100644 --- a/api/mock/facadeStub.go +++ b/api/mock/facadeStub.go @@ -81,6 +81,7 @@ type FacadeStub struct { GetCodeHashCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) GetGuardianDataCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) IsDataTrieMigratedCalled func(address string, options common.AccountQueryOptions) (*data.GenericAPIResponse, error) + GetWaitingEpochsLeftForPublicKeyCalled func(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) } // GetProof - @@ -554,6 +555,14 @@ func (f *FacadeStub) IsDataTrieMigrated(address string, options common.AccountQu return &data.GenericAPIResponse{}, nil } +// GetWaitingEpochsLeftForPublicKey - +func (f *FacadeStub) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + if f.GetWaitingEpochsLeftForPublicKeyCalled != nil { + return f.GetWaitingEpochsLeftForPublicKeyCalled(publicKey) + } + return &data.WaitingEpochsLeftApiResponse{}, nil +} + // WrongFacade is a struct that can be used as a wrong implementation of the node router handler type WrongFacade struct { } diff --git a/cmd/proxy/config/apiConfig/v1_0.toml b/cmd/proxy/config/apiConfig/v1_0.toml index b8cea0ed..71a13e46 100644 --- a/cmd/proxy/config/apiConfig/v1_0.toml +++ b/cmd/proxy/config/apiConfig/v1_0.toml @@ -24,7 +24,8 @@ Routes = [ [APIPackages.node] Routes = [ { Name = "/heartbeatstatus", Open = true, Secured = false, RateLimit = 0 }, - { Name = "/old-storage-token/:token/nonce/:nonce", Open = true, Secured = false, RateLimit = 0} + { Name = "/old-storage-token/:token/nonce/:nonce", Open = true, Secured = false, RateLimit = 0}, + { Name = "/waiting-epochs-left/:key", Open = true, Secured = false, RateLimit = 0} ] [APIPackages.address] diff --git a/cmd/proxy/config/apiConfig/v_next.toml b/cmd/proxy/config/apiConfig/v_next.toml index c1e6a0e6..292772b1 100644 --- a/cmd/proxy/config/apiConfig/v_next.toml +++ b/cmd/proxy/config/apiConfig/v_next.toml @@ -24,7 +24,8 @@ Routes = [ [APIPackages.node] Routes = [ { Name = "/heartbeatstatus", Open = true, Secured = false, RateLimit = 0 }, - { Name = "/old-storage-token/:token/nonce/:nonce", Open = true, Secured = false, RateLimit = 0} + { Name = "/old-storage-token/:token/nonce/:nonce", Open = true, Secured = false, RateLimit = 0}, + { Name = "/waiting-epochs-left/:key", Open = true, Secured = false, RateLimit = 0} ] [APIPackages.address] diff --git a/cmd/proxy/config/swagger/openapi.json b/cmd/proxy/config/swagger/openapi.json index 41539fe3..2abd81ee 100644 --- a/cmd/proxy/config/swagger/openapi.json +++ b/cmd/proxy/config/swagger/openapi.json @@ -1406,6 +1406,40 @@ } } }, + "/node/waiting-epochs-left/{key}": { + "get": { + "tags": [ + "node" + ], + "summary": "will return the number of epochs left for the public key until it becomes eligible", + "parameters": [ + { + "name": "key", + "in": "path", + "description": "the public key to look after", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericResponse" + } + } + } + }, + "400": { + "description": "validation error" + } + } + } + }, "/proof/root-hash/{roothash}/address/{address}": { "get": { "tags": [ diff --git a/data/nodeStatus.go b/data/nodeStatus.go index 11e44884..ed8e0cd5 100644 --- a/data/nodeStatus.go +++ b/data/nodeStatus.go @@ -69,3 +69,15 @@ type TrieStatisticsAPIResponse struct { Error string `json:"error"` Code string `json:"code"` } + +// WaitingEpochsLeftResponse matches the output structure the data field for a waiting epochs left response +type WaitingEpochsLeftResponse struct { + EpochsLeft uint32 `json:"epochsLeft"` +} + +// WaitingEpochsLeftApiResponse matches the output of an observer's waiting epochs left endpoint +type WaitingEpochsLeftApiResponse struct { + Data WaitingEpochsLeftResponse `json:"data"` + Error string `json:"error"` + Code string `json:"code"` +} diff --git a/facade/baseFacade.go b/facade/baseFacade.go index 3a72611a..2cacf673 100644 --- a/facade/baseFacade.go +++ b/facade/baseFacade.go @@ -531,6 +531,11 @@ func (epf *ProxyFacade) GetInternalStartOfEpochValidatorsInfo(epoch uint32) (*da return epf.blockProc.GetInternalStartOfEpochValidatorsInfo(epoch) } +// GetWaitingEpochsLeftForPublicKey returns the number of epochs left for the public key until it becomes eligible +func (epf *ProxyFacade) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + return epf.nodeGroupProc.GetWaitingEpochsLeftForPublicKey(publicKey) +} + // 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) diff --git a/facade/baseFacade_test.go b/facade/baseFacade_test.go index 4e136fa2..77430a75 100644 --- a/facade/baseFacade_test.go +++ b/facade/baseFacade_test.go @@ -1061,6 +1061,41 @@ func TestProxyFacade_GetGasConfigs(t *testing.T) { assert.Equal(t, expectedResult, actualResult) } +func TestProxyFacade_GetWaitingEpochsLeftForPublicKey(t *testing.T) { + t.Parallel() + + expectedResults := &data.WaitingEpochsLeftApiResponse{ + Data: data.WaitingEpochsLeftResponse{ + EpochsLeft: 10, + }, + } + epf, _ := facade.NewProxyFacade( + &mock.ActionsProcessorStub{}, + &mock.AccountProcessorStub{}, + &mock.TransactionProcessorStub{}, + &mock.SCQueryServiceStub{}, + &mock.NodeGroupProcessorStub{ + GetWaitingEpochsLeftForPublicKeyCalled: func(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + return expectedResults, nil + }, + }, + &mock.ValidatorStatisticsProcessorStub{}, + &mock.FaucetProcessorStub{}, + &mock.NodeStatusProcessorStub{}, + &mock.BlockProcessorStub{}, + &mock.BlocksProcessorStub{}, + &mock.ProofProcessorStub{}, + publicKeyConverter, + &mock.ESDTSuppliesProcessorStub{}, + &mock.StatusProcessorStub{}, + &mock.AboutInfoProcessorStub{}, + ) + + actualResult, _ := epf.GetWaitingEpochsLeftForPublicKey("key") + + assert.Equal(t, expectedResults, actualResult) +} + func getPrivKey() crypto.PrivateKey { keyGen := signing.NewKeyGenerator(ed25519.NewEd25519()) sk, _ := keyGen.GeneratePair() diff --git a/facade/interface.go b/facade/interface.go index ceb04241..d06a049c 100644 --- a/facade/interface.go +++ b/facade/interface.go @@ -69,6 +69,7 @@ type SCQueryService interface { type NodeGroupProcessor interface { GetHeartbeatData() (*data.HeartbeatResponse, error) IsOldStorageForToken(tokenID string, nonce uint64) (bool, error) + GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) } // ValidatorStatisticsProcessor defines what a validator statistics processor should do diff --git a/facade/mock/nodeGroupProcessorStub.go b/facade/mock/nodeGroupProcessorStub.go index a65f3b01..eb0aa106 100644 --- a/facade/mock/nodeGroupProcessorStub.go +++ b/facade/mock/nodeGroupProcessorStub.go @@ -4,8 +4,9 @@ import "github.com/multiversx/mx-chain-proxy-go/data" // NodeGroupProcessorStub represents a stub implementation of a NodeGroupProcessor type NodeGroupProcessorStub struct { - GetHeartbeatDataCalled func() (*data.HeartbeatResponse, error) - IsOldStorageForTokenCalled func(tokenID string, nonce uint64) (bool, error) + GetHeartbeatDataCalled func() (*data.HeartbeatResponse, error) + IsOldStorageForTokenCalled func(tokenID string, nonce uint64) (bool, error) + GetWaitingEpochsLeftForPublicKeyCalled func(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) } // IsOldStorageForToken - @@ -17,3 +18,11 @@ func (hbps *NodeGroupProcessorStub) IsOldStorageForToken(tokenID string, nonce u func (hbps *NodeGroupProcessorStub) GetHeartbeatData() (*data.HeartbeatResponse, error) { return hbps.GetHeartbeatDataCalled() } + +// GetWaitingEpochsLeftForPublicKey - +func (hbps *NodeGroupProcessorStub) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + if hbps.GetWaitingEpochsLeftForPublicKeyCalled != nil { + return hbps.GetWaitingEpochsLeftForPublicKeyCalled(publicKey) + } + return &data.WaitingEpochsLeftApiResponse{}, nil +} diff --git a/process/nodeGroupProcessor.go b/process/nodeGroupProcessor.go index 6f4088c6..3225ff64 100644 --- a/process/nodeGroupProcessor.go +++ b/process/nodeGroupProcessor.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "errors" + "fmt" "math/big" "net/http" "sort" @@ -14,10 +15,13 @@ import ( "github.com/multiversx/mx-chain-proxy-go/data" ) -// HeartBeatPath represents the path where an observer exposes his heartbeat status -const HeartBeatPath = "/node/heartbeatstatus" - -const systemAccountAddress = "erd1lllllllllllllllllllllllllllllllllllllllllllllllllllsckry7t" +const ( + // heartbeatPath represents the path where an observer exposes his heartbeat status + heartbeatPath = "/node/heartbeatstatus" + // waitingEpochsLeftPath represents the path where an observer the number of epochs left in waiting state for a key + waitingEpochsLeftPath = "node/waiting-epochs-left/%s" + systemAccountAddress = "erd1lllllllllllllllllllllllllllllllllllllllllllllllllllsckry7t" +) // NodeGroupProcessor is able to process transaction requests type NodeGroupProcessor struct { @@ -42,18 +46,18 @@ func NewNodeGroupProcessor( if cacheValidityDuration <= 0 { return nil, ErrInvalidCacheValidityDuration } - hbp := &NodeGroupProcessor{ + ngp := &NodeGroupProcessor{ proc: proc, cacher: cacher, cacheValidityDuration: cacheValidityDuration, } - return hbp, nil + return ngp, nil } // IsOldStorageForToken returns true if the token is stored in the old fashion -func (hbp *NodeGroupProcessor) IsOldStorageForToken(tokenID string, nonce uint64) (bool, error) { - observers, err := hbp.proc.GetAllObservers() +func (ngp *NodeGroupProcessor) IsOldStorageForToken(tokenID string, nonce uint64) (bool, error) { + observers, err := ngp.proc.GetAllObservers() if err != nil { return false, err } @@ -67,7 +71,7 @@ func (hbp *NodeGroupProcessor) IsOldStorageForToken(tokenID string, nonce uint64 apiResponse := data.AccountKeyValueResponse{} apiPath := addressPath + systemAccountAddress + "/key/" + tokenStorageKey - respCode, err := hbp.proc.CallGetRestEndPoint(observer.Address, apiPath, &apiResponse) + respCode, err := ngp.proc.CallGetRestEndPoint(observer.Address, apiPath, &apiResponse) if err == nil || respCode == http.StatusBadRequest || respCode == http.StatusInternalServerError { log.Info("account value for key request", "address", systemAccountAddress, @@ -104,23 +108,23 @@ func computeTokenStorageKey(tokenID string, nonce uint64) string { } // GetHeartbeatData will simply forward the heartbeat status from an observer -func (hbp *NodeGroupProcessor) GetHeartbeatData() (*data.HeartbeatResponse, error) { - heartbeatsToReturn, err := hbp.cacher.LoadHeartbeats() +func (ngp *NodeGroupProcessor) GetHeartbeatData() (*data.HeartbeatResponse, error) { + heartbeatsToReturn, err := ngp.cacher.LoadHeartbeats() if err == nil { return heartbeatsToReturn, nil } log.Info("heartbeat: cannot get from cache. Will fetch from API", "error", err.Error()) - return hbp.getHeartbeatsFromApi() + return ngp.getHeartbeatsFromApi() } -func (hbp *NodeGroupProcessor) getHeartbeatsFromApi() (*data.HeartbeatResponse, error) { - shardIDs := hbp.proc.GetShardIDs() +func (ngp *NodeGroupProcessor) getHeartbeatsFromApi() (*data.HeartbeatResponse, error) { + shardIDs := ngp.proc.GetShardIDs() responseMap := make(map[string]data.PubKeyHeartbeat) for _, shard := range shardIDs { - observers, err := hbp.proc.GetObservers(shard) + observers, err := ngp.proc.GetObservers(shard) if err != nil { log.Error("could not get observers", "shard", shard, "error", err.Error()) continue @@ -129,10 +133,10 @@ func (hbp *NodeGroupProcessor) getHeartbeatsFromApi() (*data.HeartbeatResponse, errorsCount := 0 var response data.HeartbeatApiResponse for _, observer := range observers { - _, err = hbp.proc.CallGetRestEndPoint(observer.Address, HeartBeatPath, &response) + _, err = ngp.proc.CallGetRestEndPoint(observer.Address, heartbeatPath, &response) heartbeats := response.Data.Heartbeats if err == nil && len(heartbeats) > 0 { - hbp.addMessagesToMap(responseMap, heartbeats, shard) + ngp.addMessagesToMap(responseMap, heartbeats, shard) break } @@ -155,10 +159,10 @@ func (hbp *NodeGroupProcessor) getHeartbeatsFromApi() (*data.HeartbeatResponse, return nil, ErrHeartbeatNotAvailable } - return hbp.mapToResponse(responseMap), nil + return ngp.mapToResponse(responseMap), nil } -func (hbp *NodeGroupProcessor) addMessagesToMap(responseMap map[string]data.PubKeyHeartbeat, heartbeats []data.PubKeyHeartbeat, observerShard uint32) { +func (ngp *NodeGroupProcessor) addMessagesToMap(responseMap map[string]data.PubKeyHeartbeat, heartbeats []data.PubKeyHeartbeat, observerShard uint32) { for _, heartbeatMessage := range heartbeats { isMessageFromCurrentShard := heartbeatMessage.ComputedShardID == observerShard isMessageFromShardAfterShuffleOut := heartbeatMessage.ReceivedShardID == observerShard @@ -179,7 +183,7 @@ func (hbp *NodeGroupProcessor) addMessagesToMap(responseMap map[string]data.PubK } } -func (hbp *NodeGroupProcessor) mapToResponse(responseMap map[string]data.PubKeyHeartbeat) *data.HeartbeatResponse { +func (ngp *NodeGroupProcessor) mapToResponse(responseMap map[string]data.PubKeyHeartbeat) *data.HeartbeatResponse { heartbeats := make([]data.PubKeyHeartbeat, 0) for _, heartbeatMessage := range responseMap { heartbeats = append(heartbeats, heartbeatMessage) @@ -195,27 +199,27 @@ func (hbp *NodeGroupProcessor) mapToResponse(responseMap map[string]data.PubKeyH } // StartCacheUpdate will start the updating of the cache from the API at a given period -func (hbp *NodeGroupProcessor) StartCacheUpdate() { - if hbp.cancelFunc != nil { +func (ngp *NodeGroupProcessor) StartCacheUpdate() { + if ngp.cancelFunc != nil { log.Error("NodeGroupProcessor - cache update already started") return } var ctx context.Context - ctx, hbp.cancelFunc = context.WithCancel(context.Background()) + ctx, ngp.cancelFunc = context.WithCancel(context.Background()) go func(ctx context.Context) { - timer := time.NewTimer(hbp.cacheValidityDuration) + timer := time.NewTimer(ngp.cacheValidityDuration) defer timer.Stop() - hbp.handleHeartbeatCacheUpdate() + ngp.handleHeartbeatCacheUpdate() for { - timer.Reset(hbp.cacheValidityDuration) + timer.Reset(ngp.cacheValidityDuration) select { case <-timer.C: - hbp.handleHeartbeatCacheUpdate() + ngp.handleHeartbeatCacheUpdate() case <-ctx.Done(): log.Debug("finishing NodeGroupProcessor cache update...") return @@ -224,24 +228,48 @@ func (hbp *NodeGroupProcessor) StartCacheUpdate() { }(ctx) } -func (hbp *NodeGroupProcessor) handleHeartbeatCacheUpdate() { - hbts, err := hbp.getHeartbeatsFromApi() +func (ngp *NodeGroupProcessor) handleHeartbeatCacheUpdate() { + hbts, err := ngp.getHeartbeatsFromApi() if err != nil { log.Warn("heartbeat: get from API", "error", err.Error()) } if hbts != nil { - err = hbp.cacher.StoreHeartbeats(hbts) + err = ngp.cacher.StoreHeartbeats(hbts) if err != nil { log.Warn("heartbeat: store in cache", "error", err.Error()) } } } +// GetWaitingEpochsLeftForPublicKey returns the number of epochs left for the public key until it becomes eligible +func (ngp *NodeGroupProcessor) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + observers, err := ngp.proc.GetAllObservers() + if err != nil { + return nil, err + } + + path := fmt.Sprintf(waitingEpochsLeftPath, publicKey) + for _, observer := range observers { + var responseWaitingEpochsLeft data.WaitingEpochsLeftApiResponse + _, err = ngp.proc.CallGetRestEndPoint(observer.Address, path, &responseWaitingEpochsLeft) + if err != nil { + log.Error("waiting epochs left request", "observer", observer.Address, "public key", publicKey, "error", err.Error()) + continue + } + + log.Info("waiting epochs left request", "shard ID", observer.ShardId, "observer", observer.Address, "public key", publicKey) + return &responseWaitingEpochsLeft, nil + + } + + return nil, ErrSendingRequest +} + // Close will handle the closing of the cache update go routine -func (hbp *NodeGroupProcessor) Close() error { - if hbp.cancelFunc != nil { - hbp.cancelFunc() +func (ngp *NodeGroupProcessor) Close() error { + if ngp.cancelFunc != nil { + ngp.cancelFunc() } return nil diff --git a/process/nodeGroupProcessor_test.go b/process/nodeGroupProcessor_test.go index 918a8564..2f15bc89 100644 --- a/process/nodeGroupProcessor_test.go +++ b/process/nodeGroupProcessor_test.go @@ -632,3 +632,75 @@ func TestComputeTokenStorageKey(t *testing.T) { expectedKey := append(append([]byte(core.ProtectedKeyPrefix+"esdt"), []byte(testTokenID)...), big.NewInt(int64(testNonce)).Bytes()...) require.Equal(t, hex.EncodeToString(expectedKey), process.ComputeTokenStorageKey(testTokenID, testNonce)) } + +func TestNodeGroupProcessor_GetWaitingEpochsLeftForPublicKey(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("expected error") + t.Run("GetAllObservers returns error should error", func(t *testing.T) { + t.Parallel() + + proc, _ := process.NewNodeGroupProcessor( + &mock.ProcessorStub{ + GetAllObserversCalled: func() ([]*data.NodeData, error) { + return nil, expectedErr + }, + }, + &mock.HeartbeatCacherMock{}, + 10, + ) + + response, err := proc.GetWaitingEpochsLeftForPublicKey("") + require.Nil(t, response) + require.Equal(t, expectedErr, err) + }) + t.Run("all observers return error should error", func(t *testing.T) { + t.Parallel() + + proc, _ := process.NewNodeGroupProcessor( + &mock.ProcessorStub{ + GetAllObserversCalled: func() ([]*data.NodeData, error) { + return []*data.NodeData{ + {Address: "addr0", ShardId: 0}, + {Address: "addr1", ShardId: 1}, + }, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + return 0, expectedErr + }, + }, + &mock.HeartbeatCacherMock{}, + 10, + ) + + response, err := proc.GetWaitingEpochsLeftForPublicKey("") + require.Nil(t, response) + require.Equal(t, process.ErrSendingRequest, err) + }) + t.Run("should work", func(t *testing.T) { + t.Parallel() + + providedEpochsLeft := uint32(10) + proc, _ := process.NewNodeGroupProcessor( + &mock.ProcessorStub{ + GetAllObserversCalled: func() ([]*data.NodeData, error) { + return []*data.NodeData{ + {Address: "addr0", ShardId: 0}, + {Address: "addr1", ShardId: 1}, + }, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + valResponse := value.(*data.WaitingEpochsLeftApiResponse) + valResponse.Data.EpochsLeft = providedEpochsLeft + return 0, nil + }, + }, + &mock.HeartbeatCacherMock{}, + 10, + ) + + response, err := proc.GetWaitingEpochsLeftForPublicKey("key") + require.NoError(t, err) + require.Equal(t, providedEpochsLeft, response.Data.EpochsLeft) + }) +} From 4a34b5faf00ea67b3b38322b8edb9f96199086cf Mon Sep 17 00:00:00 2001 From: Sorin Stanculeanu Date: Thu, 16 Nov 2023 13:37:23 +0200 Subject: [PATCH 2/4] fix path --- process/nodeGroupProcessor.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/process/nodeGroupProcessor.go b/process/nodeGroupProcessor.go index 3225ff64..9df24010 100644 --- a/process/nodeGroupProcessor.go +++ b/process/nodeGroupProcessor.go @@ -19,7 +19,7 @@ const ( // heartbeatPath represents the path where an observer exposes his heartbeat status heartbeatPath = "/node/heartbeatstatus" // waitingEpochsLeftPath represents the path where an observer the number of epochs left in waiting state for a key - waitingEpochsLeftPath = "node/waiting-epochs-left/%s" + waitingEpochsLeftPath = "/node/waiting-epochs-left/%s" systemAccountAddress = "erd1lllllllllllllllllllllllllllllllllllllllllllllllllllsckry7t" ) From 8aef7db1e95d3afb42d4bf44b9fcc2129d8a68d0 Mon Sep 17 00:00:00 2001 From: Sorin Stanculeanu Date: Mon, 20 Nov 2023 10:12:54 +0200 Subject: [PATCH 3/4] fix after review, added extra check --- process/errors.go | 3 +++ process/nodeGroupProcessor.go | 4 ++++ process/nodeGroupProcessor_test.go | 17 +++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/process/errors.go b/process/errors.go index e5f1ddbc..65b03271 100644 --- a/process/errors.go +++ b/process/errors.go @@ -109,3 +109,6 @@ var ErrEmptyAppVersionString = errors.New("empty app version string") // ErrEmptyCommitString signals than an empty commit id string has been provided var ErrEmptyCommitString = errors.New("empty commit id string") + +// ErrEmptyPubKey signals that an empty public key has been provided +var ErrEmptyPubKey = errors.New("public key is empty") diff --git a/process/nodeGroupProcessor.go b/process/nodeGroupProcessor.go index 9df24010..25042d35 100644 --- a/process/nodeGroupProcessor.go +++ b/process/nodeGroupProcessor.go @@ -244,6 +244,10 @@ func (ngp *NodeGroupProcessor) handleHeartbeatCacheUpdate() { // GetWaitingEpochsLeftForPublicKey returns the number of epochs left for the public key until it becomes eligible func (ngp *NodeGroupProcessor) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + if len(publicKey) == 0 { + return nil, ErrEmptyPubKey + } + observers, err := ngp.proc.GetAllObservers() if err != nil { return nil, err diff --git a/process/nodeGroupProcessor_test.go b/process/nodeGroupProcessor_test.go index 2f15bc89..da380c85 100644 --- a/process/nodeGroupProcessor_test.go +++ b/process/nodeGroupProcessor_test.go @@ -637,6 +637,19 @@ func TestNodeGroupProcessor_GetWaitingEpochsLeftForPublicKey(t *testing.T) { t.Parallel() expectedErr := errors.New("expected error") + t.Run("empty pub key should error", func(t *testing.T) { + t.Parallel() + + proc, _ := process.NewNodeGroupProcessor( + &mock.ProcessorStub{}, + &mock.HeartbeatCacherMock{}, + 10, + ) + + response, err := proc.GetWaitingEpochsLeftForPublicKey("") + require.Nil(t, response) + require.Equal(t, process.ErrEmptyPubKey, err) + }) t.Run("GetAllObservers returns error should error", func(t *testing.T) { t.Parallel() @@ -650,7 +663,7 @@ func TestNodeGroupProcessor_GetWaitingEpochsLeftForPublicKey(t *testing.T) { 10, ) - response, err := proc.GetWaitingEpochsLeftForPublicKey("") + response, err := proc.GetWaitingEpochsLeftForPublicKey("key") require.Nil(t, response) require.Equal(t, expectedErr, err) }) @@ -673,7 +686,7 @@ func TestNodeGroupProcessor_GetWaitingEpochsLeftForPublicKey(t *testing.T) { 10, ) - response, err := proc.GetWaitingEpochsLeftForPublicKey("") + response, err := proc.GetWaitingEpochsLeftForPublicKey("key") require.Nil(t, response) require.Equal(t, process.ErrSendingRequest, err) }) From f6dea6f9af18aaa0f291a39ebb51a6ce7099bcd0 Mon Sep 17 00:00:00 2001 From: Sorin Stanculeanu Date: Tue, 21 Nov 2023 14:16:36 +0200 Subject: [PATCH 4/4] improved error returned on the new endpoint --- process/nodeGroupProcessor.go | 11 ++++++----- process/nodeGroupProcessor_test.go | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/process/nodeGroupProcessor.go b/process/nodeGroupProcessor.go index 25042d35..caf4bf92 100644 --- a/process/nodeGroupProcessor.go +++ b/process/nodeGroupProcessor.go @@ -253,12 +253,13 @@ func (ngp *NodeGroupProcessor) GetWaitingEpochsLeftForPublicKey(publicKey string return nil, err } + var lastErr error + var responseWaitingEpochsLeft data.WaitingEpochsLeftApiResponse path := fmt.Sprintf(waitingEpochsLeftPath, publicKey) for _, observer := range observers { - var responseWaitingEpochsLeft data.WaitingEpochsLeftApiResponse - _, err = ngp.proc.CallGetRestEndPoint(observer.Address, path, &responseWaitingEpochsLeft) - if err != nil { - log.Error("waiting epochs left request", "observer", observer.Address, "public key", publicKey, "error", err.Error()) + _, lastErr = ngp.proc.CallGetRestEndPoint(observer.Address, path, &responseWaitingEpochsLeft) + if lastErr != nil { + log.Error("waiting epochs left request", "observer", observer.Address, "public key", publicKey, "error", lastErr.Error()) continue } @@ -267,7 +268,7 @@ func (ngp *NodeGroupProcessor) GetWaitingEpochsLeftForPublicKey(publicKey string } - return nil, ErrSendingRequest + return nil, fmt.Errorf("%w, %s", ErrSendingRequest, responseWaitingEpochsLeft.Error) } // Close will handle the closing of the cache update go routine diff --git a/process/nodeGroupProcessor_test.go b/process/nodeGroupProcessor_test.go index da380c85..0c960bf5 100644 --- a/process/nodeGroupProcessor_test.go +++ b/process/nodeGroupProcessor_test.go @@ -688,7 +688,7 @@ func TestNodeGroupProcessor_GetWaitingEpochsLeftForPublicKey(t *testing.T) { response, err := proc.GetWaitingEpochsLeftForPublicKey("key") require.Nil(t, response) - require.Equal(t, process.ErrSendingRequest, err) + require.True(t, errors.Is(err, process.ErrSendingRequest)) }) t.Run("should work", func(t *testing.T) { t.Parallel()