diff --git a/README.md b/README.md index 9262d6c1..b50a0979 100644 --- a/README.md +++ b/README.md @@ -56,6 +56,7 @@ For more details, go [here](https://docs.multiversx.com/sdk-and-tools/proxy/). - `/v1.0/network/direct-staked-info` (GET) --> returns the list of direct staked values - `/v1.0/network/delegated-info` (GET) --> returns the list of delegated values - `/v1.0/network/enable-epochs` (GET) --> returns the activation epochs metric +- `/v1.0/network/enable-epochs-v2` (GET) --> returns the newer version of activation epochs ### node - `/v1.0/node/heartbeatstatus` (GET) --> returns the heartbeat data from an observer from any shard. Has a cache to avoid many requests diff --git a/api/groups/baseNetworkGroup.go b/api/groups/baseNetworkGroup.go index 8c4956f5..d165c581 100644 --- a/api/groups/baseNetworkGroup.go +++ b/api/groups/baseNetworkGroup.go @@ -37,6 +37,7 @@ func NewNetworkGroup(facadeHandler data.FacadeHandler) (*networkGroup, error) { {Path: "/esdt/non-fungible-tokens", Handler: ng.getEsdtHandlerFunc(data.NonFungibleTokens), Method: http.MethodGet}, {Path: "/esdt/supply/:token", Handler: ng.getESDTSupply, Method: http.MethodGet}, {Path: "/enable-epochs", Handler: ng.getEnableEpochs, Method: http.MethodGet}, + {Path: "/enable-epochs-v2", Handler: ng.getEnableEpochsV2, Method: http.MethodGet}, {Path: "/direct-staked-info", Handler: ng.getDirectStakedInfo, Method: http.MethodGet}, {Path: "/delegated-info", Handler: ng.getDelegatedInfo, Method: http.MethodGet}, {Path: "/ratings", Handler: ng.getRatingsConfig, Method: http.MethodGet}, @@ -144,6 +145,16 @@ func (group *networkGroup) getEnableEpochs(c *gin.Context) { c.JSON(http.StatusOK, enableEpochsMetrics) } +func (group *networkGroup) getEnableEpochsV2(c *gin.Context) { + enableEpochsMetrics, err := group.facade.GetEnableEpochsMetricsV2() + if err != nil { + shared.RespondWith(c, http.StatusInternalServerError, nil, err.Error(), data.ReturnCodeInternalError) + return + } + + c.JSON(http.StatusOK, enableEpochsMetrics) +} + func (group *networkGroup) getESDTSupply(c *gin.Context) { tokenIdentifier := c.Param("token") if tokenIdentifier == "" { diff --git a/api/groups/baseNetworkGroup_test.go b/api/groups/baseNetworkGroup_test.go index 9086274d..04b20580 100644 --- a/api/groups/baseNetworkGroup_test.go +++ b/api/groups/baseNetworkGroup_test.go @@ -216,7 +216,7 @@ func TestGetEconomicsData_ShouldWork(t *testing.T) { assert.Equal(t, http.StatusOK, resp.Code) assert.Equal(t, expectedResp, ecDataResp) - assert.Equal(t, expectedResp.Data, ecDataResp.Data) //extra safe + assert.Equal(t, expectedResp.Data, ecDataResp.Data) // extra safe } func TestGetAllIssuedESDTs_ShouldErr(t *testing.T) { @@ -325,7 +325,7 @@ func TestGetDelegatedInfo_ShouldWork(t *testing.T) { assert.Equal(t, http.StatusOK, resp.Code) assert.Equal(t, expectedResp, delegatedInfoResp) - assert.Equal(t, expectedResp.Data, delegatedInfoResp.Data) //extra safe + assert.Equal(t, expectedResp.Data, delegatedInfoResp.Data) // extra safe } func TestGetDirectStaked_ShouldErr(t *testing.T) { @@ -374,7 +374,7 @@ func TestGetDirectStaked_ShouldWork(t *testing.T) { assert.Equal(t, http.StatusOK, resp.Code) assert.Equal(t, expectedResp, directStakedResp) - assert.Equal(t, expectedResp.Data, directStakedResp.Data) //extra safe + assert.Equal(t, expectedResp.Data, directStakedResp.Data) // extra safe } func TestGetEnableEpochsMetrics_FacadeErrShouldErr(t *testing.T) { @@ -452,6 +452,81 @@ func TestGetEnableEpochsMetrics_OkRequestShouldWork(t *testing.T) { assert.Equal(t, value, res) } +func TestGetEnableEpochsMetricsV2_FacadeErrShouldErr(t *testing.T) { + t.Parallel() + + expectedErr := errors.New("expected err") + facade := &mock.FacadeStub{ + GetEnableEpochsMetricsV2Handler: func() (*data.GenericAPIResponse, error) { + return nil, expectedErr + }, + } + networkGroup, err := groups.NewNetworkGroup(facade) + require.NoError(t, err) + ws := startProxyServer(networkGroup, networkPath) + + req, _ := http.NewRequest("GET", "/network/enable-epochs-v2", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + assert.Equal(t, http.StatusInternalServerError, resp.Code) + + var result metricsResponse + loadResponse(resp.Body, &result) + + assert.Equal(t, expectedErr.Error(), result.Error) +} + +func TestGetEnableEpochsMetricsV2_BadRequestShouldErr(t *testing.T) { + t.Parallel() + + facade := &mock.FacadeStub{ + GetEnableEpochsMetricsV2Handler: func() (*data.GenericAPIResponse, error) { + return nil, errors.New("bad request") + }, + } + networkGroup, err := groups.NewNetworkGroup(facade) + require.NoError(t, err) + ws := startProxyServer(networkGroup, networkPath) + + req, _ := http.NewRequest("GET", "/network/enable-epochs-v2", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + + assert.Equal(t, http.StatusInternalServerError, resp.Code) +} + +func TestGetEnableEpochsMetricsV2_OkRequestShouldWork(t *testing.T) { + t.Parallel() + + key := "SupernovaEnableEpoch" + value := float64(4) + facade := &mock.FacadeStub{ + GetEnableEpochsMetricsV2Handler: func() (*data.GenericAPIResponse, error) { + return &data.GenericAPIResponse{ + Data: map[string]interface{}{ + key: value, + }, + Error: "", + }, nil + }, + } + networkGroup, err := groups.NewNetworkGroup(facade) + require.NoError(t, err) + ws := startProxyServer(networkGroup, networkPath) + + req, _ := http.NewRequest("GET", "/network/enable-epochs-v2", nil) + resp := httptest.NewRecorder() + ws.ServeHTTP(resp, req) + assert.Equal(t, http.StatusOK, resp.Code) + + var result metricsResponse + loadResponse(resp.Body, &result) + + res, ok := result.Data[key] + assert.True(t, ok) + assert.Equal(t, value, res) +} + func TestGetRatingsConfig_ShouldFail(t *testing.T) { t.Parallel() diff --git a/api/groups/interface.go b/api/groups/interface.go index 26569ff1..f7a2ae11 100644 --- a/api/groups/interface.go +++ b/api/groups/interface.go @@ -65,6 +65,7 @@ type NetworkFacadeHandler interface { GetDirectStakedInfo() (*data.GenericAPIResponse, error) GetDelegatedInfo() (*data.GenericAPIResponse, error) GetEnableEpochsMetrics() (*data.GenericAPIResponse, error) + GetEnableEpochsMetricsV2() (*data.GenericAPIResponse, error) GetESDTSupply(token string) (*data.ESDTSupplyResponse, error) GetRatingsConfig() (*data.GenericAPIResponse, error) GetGenesisNodesPubKeys() (*data.GenericAPIResponse, error) diff --git a/api/mock/facadeStub.go b/api/mock/facadeStub.go index 3bb3c181..b8219c04 100644 --- a/api/mock/facadeStub.go +++ b/api/mock/facadeStub.go @@ -45,6 +45,7 @@ type FacadeStub struct { GetNetworkMetricsHandler func(shardID uint32) (*data.GenericAPIResponse, error) GetAllIssuedESDTsHandler func(tokenType string) (*data.GenericAPIResponse, error) GetEnableEpochsMetricsHandler func() (*data.GenericAPIResponse, error) + GetEnableEpochsMetricsV2Handler func() (*data.GenericAPIResponse, error) GetEconomicsDataMetricsHandler func() (*data.GenericAPIResponse, error) GetDirectStakedInfoCalled func() (*data.GenericAPIResponse, error) GetDelegatedInfoCalled func() (*data.GenericAPIResponse, error) @@ -235,6 +236,11 @@ func (f *FacadeStub) GetEnableEpochsMetrics() (*data.GenericAPIResponse, error) return f.GetEnableEpochsMetricsHandler() } +// GetEnableEpochsMetricsV2 - +func (f *FacadeStub) GetEnableEpochsMetricsV2() (*data.GenericAPIResponse, error) { + return f.GetEnableEpochsMetricsV2Handler() +} + // GetRatingsConfig - func (f *FacadeStub) GetRatingsConfig() (*data.GenericAPIResponse, error) { return f.GetRatingsConfigCalled() diff --git a/cmd/proxy/config/apiConfig/v1_0.toml b/cmd/proxy/config/apiConfig/v1_0.toml index 0accd45e..1e7e83b4 100644 --- a/cmd/proxy/config/apiConfig/v1_0.toml +++ b/cmd/proxy/config/apiConfig/v1_0.toml @@ -69,6 +69,7 @@ Routes = [ { Name = "/direct-staked-info", Open = true, Secured = true, RateLimit = 0 }, { Name = "/delegated-info", Open = true, Secured = true, RateLimit = 0 }, { Name = "/enable-epochs", Open = true, Secured = false, RateLimit = 0 }, + { Name = "/enable-epochs-v2", Open = true, Secured = false, RateLimit = 0 }, { Name = "/ratings", Open = true, Secured = false, RateLimit = 0 }, { Name = "/genesis-nodes", Open = true, Secured = false, RateLimit = 0 }, { Name = "/gas-configs", Open = true, Secured = false, RateLimit = 0 }, diff --git a/cmd/proxy/config/apiConfig/v_next.toml b/cmd/proxy/config/apiConfig/v_next.toml index 0ada0023..d38355ab 100644 --- a/cmd/proxy/config/apiConfig/v_next.toml +++ b/cmd/proxy/config/apiConfig/v_next.toml @@ -69,6 +69,7 @@ Routes = [ { Name = "/direct-staked-info", Open = true, Secured = true, RateLimit = 0 }, { Name = "/delegated-info", Open = true, Secured = true, RateLimit = 0 }, { Name = "/enable-epochs", Open = true, Secured = false, RateLimit = 0 }, + { Name = "/enable-epochs-v2", Open = true, Secured = false, RateLimit = 0 }, { Name = "/ratings", Open = true, Secured = false, RateLimit = 0 }, { Name = "/genesis-nodes", Open = true, Secured = false, RateLimit = 0 }, { Name = "/gas-configs", Open = true, Secured = false, RateLimit = 0 }, diff --git a/cmd/proxy/config/config.toml b/cmd/proxy/config/config.toml index 03ef65fc..1a4e95e9 100644 --- a/cmd/proxy/config/config.toml +++ b/cmd/proxy/config/config.toml @@ -18,6 +18,9 @@ # before it should be updated EconomicsMetricsCacheValidityDurationSec = 600 # 10 minutes + # BlockCacheDurationSec defines how long block/hyperblock results (queried by hash or nonce) are kept in cache, in seconds. + BlockCacheDurationSec = 30 + # BalancedObservers - if this flag is set to true, then the requests will be distributed equally between observers. # Otherwise, there are chances that only one observer from a shard will process the requests BalancedObservers = true diff --git a/cmd/proxy/config/swagger/openapi.json b/cmd/proxy/config/swagger/openapi.json index 8c77f753..f38c35bf 100644 --- a/cmd/proxy/config/swagger/openapi.json +++ b/cmd/proxy/config/swagger/openapi.json @@ -316,7 +316,7 @@ "tags": [ "address" ], - "summary": "returns all ESDT tokens that the provided address interacted with", + "summary": "returns all the ESDT Tokens owned by the specified address.", "parameters": [ { "name": "address", @@ -422,7 +422,7 @@ "tags": [ "address" ], - "summary": "returns all ESDT tokens that the provided address interacted with", + "summary": "returns the details of a specific ESDT token for a given address", "parameters": [ { "name": "address", @@ -1071,6 +1071,26 @@ } } }, + "/network/enable-epochs-v2": { + "get": { + "tags": [ + "network" + ], + "summary": "returns the activation epochs metric", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenericResponse" + } + } + } + } + } + } + }, "/network/ratings": { "get": { "tags": [ diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 4acd6083..311bc2a9 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -30,6 +30,7 @@ import ( processFactory "github.com/multiversx/mx-chain-proxy-go/process/factory" "github.com/multiversx/mx-chain-proxy-go/testing" versionsFactory "github.com/multiversx/mx-chain-proxy-go/versions/factory" + "github.com/multiversx/mx-chain-storage-go/timecache" "github.com/urfave/cli" ) @@ -336,6 +337,7 @@ func createVersionsRegistryTestOrProduction( HeartbeatCacheValidityDurationSec: 60, ValStatsCacheValidityDurationSec: 60, EconomicsMetricsCacheValidityDurationSec: 6, + BlockCacheDurationSec: 30, FaucetValue: "10000000000", }, ApiLogging: config.ApiLoggingConfig{ @@ -512,13 +514,22 @@ func createVersionsRegistry( return nil, err } - closableComponents.Add(nodeGroupProc, valStatsProc, nodeStatusProc, bp) + cacheDuration := time.Duration(cfg.GeneralSettings.BlockCacheDurationSec) * time.Second + timedCache, err := timecache.NewTimeCacher(timecache.ArgTimeCacher{ + DefaultSpan: cacheDuration, + CacheExpiry: cacheDuration, + }) + if err != nil { + return nil, err + } + + closableComponents.Add(nodeGroupProc, valStatsProc, nodeStatusProc, bp, timedCache) nodeGroupProc.StartCacheUpdate() valStatsProc.StartCacheUpdate() nodeStatusProc.StartCacheUpdate() - blockProc, err := process.NewBlockProcessor(bp) + blockProc, err := process.NewBlockProcessor(bp, timedCache) if err != nil { return nil, err } diff --git a/config/config.go b/config/config.go index 90317616..0cfcc4de 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ type GeneralSettingsConfig struct { HeartbeatCacheValidityDurationSec int ValStatsCacheValidityDurationSec int EconomicsMetricsCacheValidityDurationSec int + BlockCacheDurationSec int FaucetValue string RateLimitWindowDurationSeconds int BalancedObservers bool diff --git a/data/block.go b/data/block.go index ac70eb0c..bf713eb9 100644 --- a/data/block.go +++ b/data/block.go @@ -12,6 +12,16 @@ type BlockApiResponse struct { Code ReturnCode `json:"code"` } +// Hash returns internal hash +func (h *BlockApiResponse) Hash() string { + return h.Data.Block.Hash +} + +// Nonce returns internal nonce +func (h *BlockApiResponse) Nonce() uint64 { + return h.Data.Block.Nonce +} + // BlockApiResponsePayload wraps a block type BlockApiResponsePayload struct { Block api.Block `json:"block"` @@ -24,6 +34,16 @@ type HyperblockApiResponse struct { Code ReturnCode `json:"code"` } +// Hash returns internal hash +func (h *HyperblockApiResponse) Hash() string { + return h.Data.Hyperblock.Hash +} + +// Nonce returns internal nonce +func (h *HyperblockApiResponse) Nonce() uint64 { + return h.Data.Hyperblock.Nonce +} + // NewHyperblockApiResponse creates a HyperblockApiResponse func NewHyperblockApiResponse(hyperblock api.Hyperblock) *HyperblockApiResponse { return &HyperblockApiResponse{ diff --git a/facade/baseFacade.go b/facade/baseFacade.go index 541e8eeb..b3fb63c8 100644 --- a/facade/baseFacade.go +++ b/facade/baseFacade.go @@ -345,6 +345,11 @@ func (pf *ProxyFacade) GetEnableEpochsMetrics() (*data.GenericAPIResponse, error return pf.nodeStatusProc.GetEnableEpochsMetrics() } +// GetEnableEpochsMetricsV2 retrieves the activation epochs +func (pf *ProxyFacade) GetEnableEpochsMetricsV2() (*data.GenericAPIResponse, error) { + return pf.nodeStatusProc.GetEnableEpochsMetricsV2() +} + // GetRatingsConfig retrieves the node's configuration's metrics func (pf *ProxyFacade) GetRatingsConfig() (*data.GenericAPIResponse, error) { return pf.nodeStatusProc.GetRatingsConfig() @@ -406,8 +411,8 @@ func (pf *ProxyFacade) ValidatorStatistics() (map[string]*data.ValidatorApiRespo } // AuctionList will return the auction list -func (epf *ProxyFacade) AuctionList() ([]*data.AuctionListValidatorAPIResponse, error) { - auctionList, err := epf.valStatsProc.GetAuctionList() +func (pf *ProxyFacade) AuctionList() ([]*data.AuctionListValidatorAPIResponse, error) { + auctionList, err := pf.valStatsProc.GetAuctionList() if err != nil { return nil, err } @@ -536,8 +541,8 @@ func (pf *ProxyFacade) GetInternalStartOfEpochValidatorsInfo(epoch uint32) (*dat } // 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) +func (pf *ProxyFacade) GetWaitingEpochsLeftForPublicKey(publicKey string) (*data.WaitingEpochsLeftApiResponse, error) { + return pf.nodeGroupProc.GetWaitingEpochsLeftForPublicKey(publicKey) } // IsDataTrieMigrated returns true if the data trie for the given address is migrated diff --git a/facade/interface.go b/facade/interface.go index 1048fa17..aaa712fc 100644 --- a/facade/interface.go +++ b/facade/interface.go @@ -92,6 +92,7 @@ type NodeStatusProcessor interface { GetLatestFullySynchronizedHyperblockNonce() (uint64, error) GetAllIssuedESDTs(tokenType string) (*data.GenericAPIResponse, error) GetEnableEpochsMetrics() (*data.GenericAPIResponse, error) + GetEnableEpochsMetricsV2() (*data.GenericAPIResponse, error) GetDirectStakedInfo() (*data.GenericAPIResponse, error) GetDelegatedInfo() (*data.GenericAPIResponse, error) GetRatingsConfig() (*data.GenericAPIResponse, error) diff --git a/facade/mock/nodeStatusProcessorStub.go b/facade/mock/nodeStatusProcessorStub.go index efe42914..991524e4 100644 --- a/facade/mock/nodeStatusProcessorStub.go +++ b/facade/mock/nodeStatusProcessorStub.go @@ -12,6 +12,7 @@ type NodeStatusProcessorStub struct { GetDirectStakedInfoCalled func() (*data.GenericAPIResponse, error) GetDelegatedInfoCalled func() (*data.GenericAPIResponse, error) GetEnableEpochsMetricsCalled func() (*data.GenericAPIResponse, error) + GetEnableEpochsMetricsV2Called func() (*data.GenericAPIResponse, error) GetRatingsConfigCalled func() (*data.GenericAPIResponse, error) GetGenesisNodesPubKeysCalled func() (*data.GenericAPIResponse, error) GetGasConfigsCalled func() (*data.GenericAPIResponse, error) @@ -91,6 +92,15 @@ func (stub *NodeStatusProcessorStub) GetEnableEpochsMetrics() (*data.GenericAPIR return &data.GenericAPIResponse{}, nil } +// GetEnableEpochsMetricsV2 - +func (stub *NodeStatusProcessorStub) GetEnableEpochsMetricsV2() (*data.GenericAPIResponse, error) { + if stub.GetEnableEpochsMetricsV2Called != nil { + return stub.GetEnableEpochsMetricsV2Called() + } + + return &data.GenericAPIResponse{}, nil +} + // GetRatingsConfig - func (stub *NodeStatusProcessorStub) GetRatingsConfig() (*data.GenericAPIResponse, error) { if stub.GetRatingsConfigCalled != nil { diff --git a/facade/mock/timedCacheMock.go b/facade/mock/timedCacheMock.go new file mode 100644 index 00000000..32bb8d84 --- /dev/null +++ b/facade/mock/timedCacheMock.go @@ -0,0 +1,33 @@ +package mock + +// TimedCacheMock - +type TimedCacheMock struct { + Cache map[string]interface{} +} + +// NewTimedCacheMock - +func NewTimedCacheMock() *TimedCacheMock { + return &TimedCacheMock{Cache: make(map[string]interface{})} +} + +// Put - +func (mock *TimedCacheMock) Put(key []byte, value interface{}, _ int) bool { + mock.Cache[string(key)] = value + return false +} + +// Get - +func (mock *TimedCacheMock) Get(key []byte) (value interface{}, ok bool) { + val, found := mock.Cache[string(key)] + return val, found +} + +// Close - +func (mock *TimedCacheMock) Close() error { + return nil +} + +// IsInterfaceNil - +func (mock *TimedCacheMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/facade/mock/timedCacheStub.go b/facade/mock/timedCacheStub.go new file mode 100644 index 00000000..2ade54c2 --- /dev/null +++ b/facade/mock/timedCacheStub.go @@ -0,0 +1,25 @@ +package mock + +// TimedCacheStub - +type TimedCacheStub struct { +} + +// Put - +func (stub *TimedCacheStub) Put(_ []byte, _ interface{}, _ int) bool { + return false +} + +// Get - +func (stub *TimedCacheStub) Get(_ []byte) (value interface{}, ok bool) { + return nil, false +} + +// Close - +func (stub *TimedCacheStub) Close() error { + return nil +} + +// IsInterfaceNil - +func (stub *TimedCacheStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/go.mod b/go.mod index c6882be5..f82fa260 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,11 @@ require ( github.com/gin-contrib/pprof v1.4.0 github.com/gin-contrib/static v0.0.1 github.com/gin-gonic/gin v1.10.0 - github.com/multiversx/mx-chain-core-go v1.4.0 + github.com/multiversx/mx-chain-core-go v1.4.2-0.20260112082618-e36a88370557 github.com/multiversx/mx-chain-crypto-go v1.3.0 - github.com/multiversx/mx-chain-es-indexer-go v1.8.0 + github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6 github.com/multiversx/mx-chain-logger-go v1.1.0 + github.com/multiversx/mx-chain-storage-go v1.1.0 github.com/pkg/errors v0.9.1 github.com/stretchr/testify v1.10.0 github.com/urfave/cli v1.22.16 @@ -48,11 +49,11 @@ require ( github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect golang.org/x/arch v0.8.0 // indirect - golang.org/x/crypto v0.31.0 // indirect - golang.org/x/net v0.33.0 // indirect - golang.org/x/sys v0.28.0 // indirect + golang.org/x/crypto v0.32.0 // indirect + golang.org/x/net v0.34.0 // indirect + golang.org/x/sys v0.30.0 // indirect golang.org/x/text v0.21.0 // indirect - google.golang.org/protobuf v1.36.3 // indirect + google.golang.org/protobuf v1.36.4 // indirect gopkg.in/go-playground/assert.v1 v1.2.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index c837895e..456bd0bd 100644 --- a/go.sum +++ b/go.sum @@ -132,14 +132,16 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= -github.com/multiversx/mx-chain-core-go v1.4.0 h1:p6FbfCzvMXF54kpS0B5mrjNWYpq4SEQqo0UvrMF7YVY= -github.com/multiversx/mx-chain-core-go v1.4.0/go.mod h1:IO+vspNan+gT0WOHnJ95uvWygiziHZvfXpff6KnxV7g= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20260112082618-e36a88370557 h1:OKl4jmdUPjR9qlK8e5g8BMA9vM0rtr8i87WnfSn7gVA= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20260112082618-e36a88370557/go.mod h1:IO+vspNan+gT0WOHnJ95uvWygiziHZvfXpff6KnxV7g= github.com/multiversx/mx-chain-crypto-go v1.3.0 h1:0eK2bkDOMi8VbSPrB1/vGJSYT81IBtfL4zw+C4sWe/k= github.com/multiversx/mx-chain-crypto-go v1.3.0/go.mod h1:nPIkxxzyTP8IquWKds+22Q2OJ9W7LtusC7cAosz7ojM= -github.com/multiversx/mx-chain-es-indexer-go v1.8.0 h1:VLN9V3yNxchyGub25yOIUoa3KOuxqxGfRuXIH5f24s8= -github.com/multiversx/mx-chain-es-indexer-go v1.8.0/go.mod h1:OLsBLRme3wL59qPRSJ2UG3xRU8QIR/JnbXHPEncR4dg= +github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6 h1:ywYAthnCkytgSGfMBTBvojlBJh9o5zUmottZwaVYTd8= +github.com/multiversx/mx-chain-es-indexer-go v1.9.3-0.20260112102658-97d6a0ceb5f6/go.mod h1:F/BpaYVPuHN7POJN6gwvJfZ22diYtvz2576a+PWiPvw= github.com/multiversx/mx-chain-logger-go v1.1.0 h1:97x84A6L4RfCa6YOx1HpAFxZp1cf/WI0Qh112whgZNM= github.com/multiversx/mx-chain-logger-go v1.1.0/go.mod h1:K9XgiohLwOsNACETMNL0LItJMREuEvTH6NsoXWXWg7g= +github.com/multiversx/mx-chain-storage-go v1.1.0 h1:M1Y9DqMrJ62s7Zw31+cyuqsnPIvlG4jLBJl5WzeZLe8= +github.com/multiversx/mx-chain-storage-go v1.1.0/go.mod h1:o6Jm7cjfPmcc6XpyihYWrd6sx3sgqwurrunw3ZrfyxI= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -200,8 +202,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= +golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -213,8 +215,8 @@ golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= +golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -235,8 +237,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -262,8 +264,8 @@ google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= -google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/protobuf v1.36.4 h1:6A3ZDJHn/eNqc1i+IdefRzy/9PokBTPvcqMySR7NNIM= +google.golang.org/protobuf v1.36.4/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= diff --git a/process/blockProcessor.go b/process/blockProcessor.go index f70e438a..a04e3780 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -35,24 +35,39 @@ const ( rawPathStr = "raw" ) +const ( + blockScope = "block" + hyperBlockScope = "hyperblock" +) + // BlockProcessor handles blocks retrieving type BlockProcessor struct { - proc Processor + proc Processor + cache TimedCache } // NewBlockProcessor will create a new block processor -func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { +func NewBlockProcessor(proc Processor, cache TimedCache) (*BlockProcessor, error) { if check.IfNil(proc) { return nil, ErrNilCoreProcessor } + if check.IfNil(cache) { + return nil, ErrNilTimedCache + } return &BlockProcessor{ - proc: proc, + proc: proc, + cache: cache, }, nil } // GetBlockByHash will return the block based on its hash func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options common.BlockQueryOptions) (*data.BlockApiResponse, error) { + scope := fmt.Sprintf("%s:shardID=%d", blockScope, shardID) + if cached := getObjectFromCacheWithHash[*data.BlockApiResponse](bp.cache, scope, hash, options); cached != nil { + return cached, nil + } + observers, err := bp.getObserversOrFullHistoryNodes(shardID) if err != nil { return nil, err @@ -62,7 +77,6 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co response := data.BlockApiResponse{} for _, observer := range observers { - _, err := bp.proc.CallGetRestEndPoint(observer.Address, path, &response) if err != nil { log.Error("block request", "observer", observer.Address, "error", err.Error()) @@ -70,8 +84,9 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co } log.Info("block request", "shard id", observer.ShardId, "hash", hash, "observer", observer.Address) - return &response, nil + bp.cacheObject(&response, scope, options) + return &response, nil } return nil, WrapObserversError(response.Error) @@ -79,6 +94,11 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co // GetBlockByNonce will return the block based on the nonce func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options common.BlockQueryOptions) (*data.BlockApiResponse, error) { + scope := fmt.Sprintf("%s:shardID=%d", blockScope, shardID) + if cached := getObjectFromCacheWithNonce[*data.BlockApiResponse](bp.cache, scope, nonce, options); cached != nil { + return cached, nil + } + observers, err := bp.getObserversOrFullHistoryNodes(shardID) if err != nil { return nil, err @@ -88,7 +108,6 @@ func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options response := data.BlockApiResponse{} for _, observer := range observers { - _, err := bp.proc.CallGetRestEndPoint(observer.Address, path, &response) if err != nil { log.Error("block request", "observer", observer.Address, "error", err.Error()) @@ -96,8 +115,8 @@ func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options } log.Info("block request", "shard id", observer.ShardId, "nonce", nonce, "observer", observer.Address) + bp.cacheObject(&response, scope, options) return &response, nil - } return nil, WrapObserversError(response.Error) @@ -114,6 +133,10 @@ func (bp *BlockProcessor) getObserversOrFullHistoryNodes(shardID uint32) ([]*dat // GetHyperBlockByHash returns the hyperblock by hash func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.HyperblockQueryOptions) (*data.HyperblockApiResponse, error) { + if cached := getObjectFromCacheWithHash[*data.HyperblockApiResponse](bp.cache, hyperBlockScope, hash, options); cached != nil { + return cached, nil + } + builder := &hyperblockBuilder{} blockQueryOptions := common.BlockQueryOptions{ @@ -136,7 +159,10 @@ func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.Hyperb } hyperblock := builder.build(options.NotarizedAtSource) - return data.NewHyperblockApiResponse(hyperblock), nil + hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) + bp.cacheObject(hyperBlockRsp, hyperBlockScope, options) + + return hyperBlockRsp, nil } func (bp *BlockProcessor) addShardBlocks( @@ -181,6 +207,10 @@ func (bp *BlockProcessor) getAlteredAccountsIfNeeded(options common.HyperblockQu // GetHyperBlockByNonce returns the hyperblock by nonce func (bp *BlockProcessor) GetHyperBlockByNonce(nonce uint64, options common.HyperblockQueryOptions) (*data.HyperblockApiResponse, error) { + if cached := getObjectFromCacheWithNonce[*data.HyperblockApiResponse](bp.cache, hyperBlockScope, nonce, options); cached != nil { + return cached, nil + } + builder := &hyperblockBuilder{} blockQueryOptions := common.BlockQueryOptions{ @@ -203,7 +233,10 @@ func (bp *BlockProcessor) GetHyperBlockByNonce(nonce uint64, options common.Hype } hyperblock := builder.build(options.NotarizedAtSource) - return data.NewHyperblockApiResponse(hyperblock), nil + hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) + bp.cacheObject(hyperBlockRsp, hyperBlockScope, options) + + return hyperBlockRsp, nil } // GetInternalBlockByHash will return the internal block based on its hash diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go new file mode 100644 index 00000000..73e1ab09 --- /dev/null +++ b/process/blockProcessorCache.go @@ -0,0 +1,110 @@ +package process + +import ( + "encoding/json" + "fmt" +) + +type cacheableBlock interface { + Hash() string + Nonce() uint64 +} + +// No error checks for this cache. +// These caching errors should never happen, and if they do, they should not be blocking + +func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { + objKey, err := makeObjKey(scope, obj.Hash(), opts) + if err != nil { + return + } + + hashLookupKey, err := makeHashCacheKey(scope, obj.Hash(), opts) + if err != nil { + return + } + + nonceLookupKey, err := makeNonceCacheKey(scope, obj.Nonce(), opts) + if err != nil { + return + } + + // Store object + _ = bp.cache.Put(objKey, obj, 0) + + // Store nonce + hash lookup keys + _ = bp.cache.Put(hashLookupKey, objKey, 0) + _ = bp.cache.Put(nonceLookupKey, objKey, 0) +} + +func makeObjKey(scope string, hash string, opts interface{}) ([]byte, error) { + optBytes, err := json.Marshal(opts) + if err != nil { + log.Error("makeObjKey", "error", err) + return nil, err + } + return []byte(fmt.Sprintf("%s:%s|opts:%s", scope, hash, string(optBytes))), nil +} + +func makeHashCacheKey(scope string, hash string, opts interface{}) ([]byte, error) { + optBytes, err := json.Marshal(opts) + if err != nil { + log.Error("makeHashCacheKey", "error", err) + return nil, err + } + return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))), nil +} + +func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) ([]byte, error) { + optBytes, err := json.Marshal(opts) + if err != nil { + log.Error("makeNonceCacheKey", "error", err) + return nil, err + } + return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))), nil +} + +func getObjectFromCacheWithHash[T cacheableBlock](c TimedCache, scope string, hash string, opts interface{}) T { + var nilRet T + hashKey, err := makeHashCacheKey(scope, hash, opts) + if err != nil { + return nilRet + } + + return getObjFromCache[T](c, hashKey) +} + +func getObjectFromCacheWithNonce[T cacheableBlock](c TimedCache, scope string, nonce uint64, opts interface{}) T { + var nilRet T + nonceKey, err := makeNonceCacheKey(scope, nonce, opts) + if err != nil { + return nilRet + } + + return getObjFromCache[T](c, nonceKey) +} + +func getObjFromCache[T cacheableBlock](c TimedCache, lookUpKey []byte) T { + var retObj T + + key, _ := c.Get(lookUpKey) + if key == nil { + return retObj + } + + keyBytes, ok := key.([]byte) + if !ok { + return retObj + } + + val, ok := c.Get(keyBytes) + if !ok { + return retObj + } + + result, ok := val.(T) + if !ok { + return retObj + } + return result +} diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go new file mode 100644 index 00000000..7b9af3c8 --- /dev/null +++ b/process/blockProcessorCache_test.go @@ -0,0 +1,219 @@ +package process + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-proxy-go/common" + "github.com/multiversx/mx-chain-proxy-go/data" + facadeMock "github.com/multiversx/mx-chain-proxy-go/facade/mock" + "github.com/multiversx/mx-chain-proxy-go/process/mock" + "github.com/stretchr/testify/require" +) + +func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) T { + if hash != "" { + hashKey, _ := makeHashCacheKey(scope, hash, opts) + return getObjFromCache[T](c, hashKey) + } + + nonceKey, _ := makeNonceCacheKey(scope, *nonce, opts) + return getObjFromCache[T](c, nonceKey) +} + +func TestBlockProcessorCache(t *testing.T) { + t.Parallel() + + mockCache := facadeMock.NewTimedCacheMock() + bp, _ := NewBlockProcessor(&mock.ProcessorStub{}, mockCache) + + nonceBlock1 := uint64(1) + nonceBlock2 := uint64(2) + hashBlock1 := "hashBlock1" + hashBlock2 := "hashBlock2" + scope1 := "block:shard=1" + scope2 := "block:shard=2" + opts1 := common.BlockQueryOptions{WithTransactions: true} + opts2 := common.BlockQueryOptions{WithLogs: true} + + blockApi1 := &data.BlockApiResponse{ + Data: data.BlockApiResponsePayload{ + Block: api.Block{ + Nonce: nonceBlock1, + Hash: hashBlock1, + }, + }, + } + blockApi2 := &data.BlockApiResponse{ + Data: data.BlockApiResponsePayload{ + Block: api.Block{ + Nonce: nonceBlock2, + Hash: hashBlock2, + }, + }, + } + + // Some basic checks that the cache is empty + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts1, + )) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + "", + &nonceBlock1, + opts1, + )) + + // Cache scope1:blockApi1:opts1 + bp.cacheObject(blockApi1, scope1, opts1) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, // wrong scope + hashBlock1, + nil, + opts1, + )) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts2, // wrong opts + )) + + require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, // found cached blockApi1 object by hash + nil, + opts1, + )) + require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + "", + &nonceBlock1, // found cached blockApi1 object by nonce + opts1, + )) + + // Cache scope2:blockApi2:opts1 + bp.cacheObject(blockApi2, scope2, opts2) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + hashBlock2, + nil, + opts1, + )) + require.Equal(t, blockApi2, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + hashBlock2, // found cached blockApi2 object by hash + nil, + opts2, + )) + require.Equal(t, blockApi2, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + "", + &nonceBlock2, // found cached blockApi2 object by nonce + opts2, + )) + + scopeHyperBlock := "hyperBlock" + hyperBlockNonce := uint64(4) + hyperBlockHash := "hyperBlockHash" + hyperBlock := &data.HyperblockApiResponse{ + Data: data.HyperblockApiResponsePayload{ + Hyperblock: api.Hyperblock{ + Hash: hyperBlockHash, + Nonce: hyperBlockNonce, + }, + }, + } + + // Cache same hyperBlock object from two different routes: + // 1. scopeHyperBlock:hyperBlock:opts1 + // 2. scopeHyperBlock:hyperBlock:opts2 + bp.cacheObject(hyperBlock, scopeHyperBlock, opts1) + bp.cacheObject(hyperBlock, scopeHyperBlock, opts2) + + // found cached hyperBlock object by hyperBlockHash + opts2 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + hyperBlockHash, + nil, + opts2, + )) + // found cached hyperBlock object by hyperBlockNonce + opts2 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + "", + &hyperBlockNonce, + opts2, + )) + // found cached hyperBlock object by hyperBlockHash + opts1 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + hyperBlockHash, + nil, + opts1, + )) + // found cached hyperBlock object by hyperBlockNonce + opts1 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + "", + &hyperBlockNonce, + opts1, + )) + + opts1Str, _ := json.Marshal(&opts1) + opts2Str, _ := json.Marshal(&opts2) + require.Len(t, mockCache.Cache, 12) + + expectedObjKeys := []string{ + fmt.Sprintf("%s:%s|opts:%s", scope1, hashBlock1, opts1Str), // blockApi1 + fmt.Sprintf("%s:%s|opts:%s", scope2, hashBlock2, opts2Str), // blockApi2 + fmt.Sprintf("%s:%s|opts:%s", scopeHyperBlock, hyperBlockHash, opts1Str), // hyperBlock + fmt.Sprintf("%s:%s|opts:%s", scopeHyperBlock, hyperBlockHash, opts2Str), // hyperBlock + } + + require.Equal(t, mockCache.Cache[expectedObjKeys[0]], blockApi1) + require.Equal(t, mockCache.Cache[expectedObjKeys[1]], blockApi2) + require.Equal(t, mockCache.Cache[expectedObjKeys[2]], hyperBlock) + require.Equal(t, mockCache.Cache[expectedObjKeys[3]], hyperBlock) + + expectedLookUpKeys := []string{ + fmt.Sprintf("%s:nonce:%d|opts:%s", scope1, nonceBlock1, string(opts1Str)), // blockApi1 + fmt.Sprintf("%s:hash:%s|opts:%s", scope1, hashBlock1, string(opts1Str)), // blockApi1 + + fmt.Sprintf("%s:nonce:%d|opts:%s", scope2, nonceBlock2, string(opts2Str)), // blockApi2 + fmt.Sprintf("%s:hash:%s|opts:%s", scope2, hashBlock2, string(opts2Str)), // blockApi2 + + fmt.Sprintf("%s:nonce:%d|opts:%s", scopeHyperBlock, hyperBlockNonce, string(opts1Str)), // hyperBlock + fmt.Sprintf("%s:hash:%s|opts:%s", scopeHyperBlock, hyperBlockHash, string(opts1Str)), // hyperBlock + + fmt.Sprintf("%s:nonce:%d|opts:%s", scopeHyperBlock, hyperBlockNonce, string(opts2Str)), // hyperBlock + fmt.Sprintf("%s:hash:%s|opts:%s", scopeHyperBlock, hyperBlockHash, string(opts2Str)), // hyperBlock + } + + require.Equal(t, mockCache.Cache[expectedLookUpKeys[0]], []byte(expectedObjKeys[0])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[1]], []byte(expectedObjKeys[0])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[2]], []byte(expectedObjKeys[1])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[3]], []byte(expectedObjKeys[1])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[4]], []byte(expectedObjKeys[2])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[5]], []byte(expectedObjKeys[2])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[6]], []byte(expectedObjKeys[3])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[7]], []byte(expectedObjKeys[3])) +} diff --git a/process/blockProcessor_test.go b/process/blockProcessor_test.go index e9740d83..6dbabfed 100644 --- a/process/blockProcessor_test.go +++ b/process/blockProcessor_test.go @@ -12,6 +12,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-proxy-go/common" "github.com/multiversx/mx-chain-proxy-go/data" + facadeMock "github.com/multiversx/mx-chain-proxy-go/facade/mock" "github.com/multiversx/mx-chain-proxy-go/process" "github.com/multiversx/mx-chain-proxy-go/process/mock" "github.com/stretchr/testify/assert" @@ -21,15 +22,23 @@ import ( func TestNewBlockProcessor_NilProcessorShouldErr(t *testing.T) { t.Parallel() - bp, err := process.NewBlockProcessor(nil) + bp, err := process.NewBlockProcessor(nil, &facadeMock.TimedCacheStub{}) require.Nil(t, bp) require.Equal(t, process.ErrNilCoreProcessor, err) } +func TestNewBlockProcessor_NilTimedCache(t *testing.T) { + t.Parallel() + + bp, err := process.NewBlockProcessor(&mock.ProcessorStub{}, nil) + require.Nil(t, bp) + require.Equal(t, process.ErrNilTimedCache, err) +} + func TestNewBlockProcessor_ShouldWork(t *testing.T) { t.Parallel() - bp, err := process.NewBlockProcessor(&mock.ProcessorStub{}) + bp, err := process.NewBlockProcessor(&mock.ProcessorStub{}, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) require.NoError(t, err) } @@ -51,7 +60,7 @@ func TestBlockProcessor_GetBlockByHashShouldGetFullHistoryNodes(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -77,7 +86,7 @@ func TestBlockProcessor_GetBlockByHashShouldGetObservers(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -99,7 +108,7 @@ func TestBlockProcessor_GetBlockByHashNoFullNodesOrObserversShouldErr(t *testing }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -120,7 +129,7 @@ func TestBlockProcessor_GetBlockByHashCallGetFailsShouldErr(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -143,7 +152,7 @@ func TestBlockProcessor_GetBlockByHashShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -171,7 +180,7 @@ func TestBlockProcessor_GetBlockByHashShouldWorkAndIncludeAlsoTxs(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{WithTransactions: true}) @@ -200,7 +209,7 @@ func TestBlockProcessor_GetBlockByNonceShouldGetFullHistoryNodes(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByNonce(0, 0, common.BlockQueryOptions{}) @@ -226,7 +235,7 @@ func TestBlockProcessor_GetBlockByNonceShouldGetObservers(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByNonce(0, 1, common.BlockQueryOptions{}) @@ -248,7 +257,7 @@ func TestBlockProcessor_GetBlockByNonceNoFullNodesOrObserversShouldErr(t *testin }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, 1, common.BlockQueryOptions{}) @@ -269,7 +278,7 @@ func TestBlockProcessor_GetBlockByNonceCallGetFailsShouldErr(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, 0, common.BlockQueryOptions{}) @@ -292,7 +301,7 @@ func TestBlockProcessor_GetBlockByNonceShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, nonce, common.BlockQueryOptions{}) @@ -320,7 +329,7 @@ func TestBlockProcessor_GetBlockByNonceShouldWorkAndIncludeAlsoTxs(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, 3, common.BlockQueryOptions{WithTransactions: true}) @@ -359,7 +368,7 @@ func TestBlockProcessor_GetHyperBlock(t *testing.T) { }, } - processor, err := process.NewBlockProcessor(proc) + processor, err := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.Nil(t, err) require.NotNil(t, processor) @@ -391,7 +400,7 @@ func TestBlockProcessor_GetInternalBlockByNonceInvalidOutputFormat_ShouldFail(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalBlockByNonce(0, 0, 2) @@ -416,7 +425,7 @@ func TestBlockProcessor_GetInternalBlockByNonceShouldGetFullHistoryNodes(t *test }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByNonce(0, 0, common.Internal) @@ -442,7 +451,7 @@ func TestBlockProcessor_GetInternalBlockByNonceShouldGetObservers(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByNonce(0, 1, common.Internal) @@ -464,7 +473,7 @@ func TestBlockProcessor_GetInternalBlockByNonceNoFullNodesOrObserversShouldErr(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByNonce(0, 1, common.Internal) @@ -485,7 +494,7 @@ func TestBlockProcessor_GetInternalBlockByNonceCallGetFailsShouldErr(t *testing. }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByNonce(0, 0, common.Internal) @@ -514,7 +523,7 @@ func TestBlockProcessor_GetInternalBlockByNonceShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByNonce(0, nonce, common.Internal) @@ -539,7 +548,7 @@ func TestBlockProcessor_GetInternalBlockByHashInvalidOutputFormat_ShouldFail(t * }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalBlockByHash(0, "aaaa", 2) @@ -564,7 +573,7 @@ func TestBlockProcessor_GetInternalBlockByHashShouldGetFullHistoryNodes(t *testi }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -590,7 +599,7 @@ func TestBlockProcessor_GetInternalBlockByHashShouldGetObservers(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -612,7 +621,7 @@ func TestBlockProcessor_GetInternalBlockByHashNoFullNodesOrObserversShouldErr(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -633,7 +642,7 @@ func TestBlockProcessor_GetInternalBlockByHashCallGetFailsShouldErr(t *testing.T }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -661,7 +670,7 @@ func TestBlockProcessor_GetInternalBlockByHashShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -686,7 +695,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashInvalidOutputFormat_ShouldFail }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, 2) @@ -711,7 +720,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashShouldGetFullHistoryNodes(t *t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -737,7 +746,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashShouldGetObservers(t *testing. }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -759,7 +768,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashNoFullNodesOrObserversShouldEr }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -780,7 +789,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashCallGetFailsShouldErr(t *testi }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -808,7 +817,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -833,7 +842,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockInvalidOutputFormat_Shou }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalStartOfEpochMetaBlock(0, 2) @@ -858,7 +867,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockShouldGetFullHistoryNode }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -884,7 +893,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockShouldGetObservers(t *te }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -906,7 +915,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockNoFullNodesOrObserversSh }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -928,7 +937,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockCallGetFailsShouldErr(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -956,7 +965,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockShouldWork(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochMetaBlock(1, common.Internal) @@ -986,7 +995,7 @@ func TestBlockProcessor_GetAlteredAccountsByNonce(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByNonce(requestedShardID, 4, common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, expectedErr, err) require.Nil(t, res) @@ -1015,7 +1024,7 @@ func TestBlockProcessor_GetAlteredAccountsByNonce(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByNonce(requestedShardID, 4, common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, 2, callGetEndpointCt) require.True(t, errors.Is(err, process.ErrSendingRequest)) @@ -1044,7 +1053,7 @@ func TestBlockProcessor_GetAlteredAccountsByNonce(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByNonce(requestedShardID, 4, common.GetAlteredAccountsForBlockOptions{}) require.Nil(t, err) require.Equal(t, &data.AlteredAccountsApiResponse{ @@ -1073,7 +1082,7 @@ func TestBlockProcessor_GetAlteredAccountsByHash(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByHash(requestedShardID, "hash", common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, expectedErr, err) require.Nil(t, res) @@ -1102,7 +1111,7 @@ func TestBlockProcessor_GetAlteredAccountsByHash(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByHash(requestedShardID, "hash", common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, 2, callGetEndpointCt) require.True(t, errors.Is(err, process.ErrSendingRequest)) @@ -1131,7 +1140,7 @@ func TestBlockProcessor_GetAlteredAccountsByHash(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByHash(requestedShardID, "hash", common.GetAlteredAccountsForBlockOptions{}) require.Nil(t, err) require.Equal(t, &data.AlteredAccountsApiResponse{ @@ -1226,7 +1235,7 @@ func TestBlockProcessor_GetHyperBlockByNonceWithAlteredAccounts(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetHyperBlockByNonce(4, common.HyperblockQueryOptions{WithAlteredAccounts: true}) require.Nil(t, err) @@ -1343,7 +1352,7 @@ func TestBlockProcessor_GetHyperBlockByHashWithAlteredAccounts(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetHyperBlockByHash("abcdef", common.HyperblockQueryOptions{WithAlteredAccounts: true}) require.Nil(t, err) @@ -1400,7 +1409,7 @@ func TestBlockProcessor_GetInternalStartOfEpochValidatorsInfo(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochValidatorsInfo(1) @@ -1408,3 +1417,132 @@ func TestBlockProcessor_GetInternalStartOfEpochValidatorsInfo(t *testing.T) { require.NotNil(t, res) require.Equal(t, expectedData, res.Data) } + +func TestBlockProcessor_GetCachedHyperBlocks(t *testing.T) { + t.Parallel() + + numGetBlockCalled := 0 + hash := "hash" + nonce := uint64(42) + shardBlockResponse := data.BlockApiResponsePayload{Block: api.Block{Nonce: 41}} + metaBlockResponse := data.BlockApiResponsePayload{ + Block: api.Block{ + Hash: "hash", + Nonce: 42, + NotarizedBlocks: []*api.NotarizedBlock{ + {Shard: 0, Nonce: 41}, + {Shard: 1, Nonce: 41}, + {Shard: 2, Nonce: 41}, + }}, + } + proc := &mock.ProcessorStub{ + GetFullHistoryNodesCalled: func(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { + return []*data.NodeData{{ShardId: shardId, Address: fmt.Sprintf("observer-%d", shardId)}}, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + numGetBlockCalled++ + + response := value.(*data.BlockApiResponse) + if strings.Contains(address, "4294967295") { + response.Data = metaBlockResponse + } else { + response.Data = shardBlockResponse + } + + return 200, nil + }, + } + + processor, err := process.NewBlockProcessor(proc, facadeMock.NewTimedCacheMock()) + require.Nil(t, err) + require.NotNil(t, processor) + + expectedHyperBlock := &data.HyperblockApiResponse{ + Code: data.ReturnCodeSuccess, + Data: data.HyperblockApiResponsePayload{ + Hyperblock: api.Hyperblock{ + Nonce: 42, + Hash: hash, + ShardBlocks: []*api.NotarizedBlock{ + {Nonce: 41, AlteredAccounts: make([]*alteredAccount.AlteredAccount, 0), MiniBlockHashes: make([]string, 0)}, + {Nonce: 41, AlteredAccounts: make([]*alteredAccount.AlteredAccount, 0), MiniBlockHashes: make([]string, 0)}, + {Nonce: 41, AlteredAccounts: make([]*alteredAccount.AlteredAccount, 0), MiniBlockHashes: make([]string, 0)}, + }, + Transactions: make([]*transaction.ApiTransactionResult, 0), + }, + }, + } + + numGetBlockCalled = 0 + response, err := processor.GetHyperBlockByHash(hash, common.HyperblockQueryOptions{}) + require.Nil(t, err) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 4, numGetBlockCalled, "get block should be called for metablock and for all notarized shard blocks") + + response, err = processor.GetHyperBlockByHash(hash, common.HyperblockQueryOptions{}) + require.Nil(t, err) + require.NotNil(t, response) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 4, numGetBlockCalled) + + response, err = processor.GetHyperBlockByNonce(nonce, common.HyperblockQueryOptions{}) + require.Nil(t, err) + require.NotNil(t, response) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 4, numGetBlockCalled) + + // Different query options, should not take block from cache + response, err = processor.GetHyperBlockByNonce(nonce, common.HyperblockQueryOptions{WithLogs: true}) + require.Nil(t, err) + require.NotNil(t, response) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 8, numGetBlockCalled) +} + +func TestBlockProcessor_GetCachedBlocks(t *testing.T) { + t.Parallel() + + nonce := uint64(42) + hash := "hash" + blockApiResponse := data.BlockApiResponsePayload{Block: api.Block{Nonce: nonce, Hash: hash}} + + numGetBlockCalled := 0 + proc := &mock.ProcessorStub{ + GetFullHistoryNodesCalled: func(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { + return []*data.NodeData{{ShardId: shardId, Address: "addr"}}, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + numGetBlockCalled++ + valResp := value.(*data.BlockApiResponse) + valResp.Data = blockApiResponse + return 200, nil + }, + } + + bp, _ := process.NewBlockProcessor(proc, facadeMock.NewTimedCacheMock()) + require.NotNil(t, bp) + + expectedBlock := &data.BlockApiResponse{ + Data: blockApiResponse, + } + res, err := bp.GetBlockByHash(0, hash, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 1, numGetBlockCalled) + + res, err = bp.GetBlockByHash(0, hash, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 1, numGetBlockCalled) + + res, err = bp.GetBlockByNonce(0, nonce, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 1, numGetBlockCalled) + + // Different shard id + res, err = bp.GetBlockByNonce(1, nonce, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 2, numGetBlockCalled) +} diff --git a/process/cache/errors.go b/process/cache/errors.go index a06d6aca..7d9bca16 100644 --- a/process/cache/errors.go +++ b/process/cache/errors.go @@ -16,6 +16,3 @@ var ErrNilValidatorStatsToStoreInCache = errors.New("nil validator statistics to // ErrNilGenericApiResponseInCache signals that the generic api response stored in cache is nil var ErrNilGenericApiResponseInCache = errors.New("nil generic api response in cache") - -// ErrNilGenericApiResponseToStoreInCache signals that the provided generic api response is nil -var ErrNilGenericApiResponseToStoreInCache = errors.New("nil generic api response to store in cache") diff --git a/process/errors.go b/process/errors.go index 8060b7b4..3ef4a328 100644 --- a/process/errors.go +++ b/process/errors.go @@ -112,3 +112,6 @@ var ErrEmptyPubKey = errors.New("public key is empty") // ErrNilHttpClient signals that a nil http client has been provided var ErrNilHttpClient = errors.New("nil http client") + +// ErrNilTimedCache signals that a nil timed cache has been provided +var ErrNilTimedCache = errors.New("nil timed cache") diff --git a/process/interface.go b/process/interface.go index f424bdfc..3a27454a 100644 --- a/process/interface.go +++ b/process/interface.go @@ -85,3 +85,12 @@ type StatusMetricsProvider interface { type HttpClient interface { Do(req *http.Request) (*http.Response, error) } + +// TimedCache provides time-based eviction: each entry has a TTL and the cache +// periodically sweeps expired items automatically. +type TimedCache interface { + Put(key []byte, value interface{}, _ int) (evicted bool) + Get(key []byte) (value interface{}, ok bool) + Close() error + IsInterfaceNil() bool +} diff --git a/process/nodeStatusProcessor.go b/process/nodeStatusProcessor.go index 3ec70b33..b96d7128 100644 --- a/process/nodeStatusProcessor.go +++ b/process/nodeStatusProcessor.go @@ -47,6 +47,9 @@ const ( // EnableEpochsPath represents the path where an observer exposes all the activation epochs EnableEpochsPath = "/network/enable-epochs" + // EnableEpochsV2Path represents the path where an observer exposes all the activation epochs + EnableEpochsV2Path = "/network/enable-epochs-v2" + // MetricCrossCheckBlockHeight is the metric that stores cross block height MetricCrossCheckBlockHeight = "erd_cross_check_block_height" @@ -138,6 +141,15 @@ func (nsp *NodeStatusProcessor) GetNetworkConfigMetrics() (*data.GenericAPIRespo // GetEnableEpochsMetrics will simply forward the activation epochs config metrics from an observer func (nsp *NodeStatusProcessor) GetEnableEpochsMetrics() (*data.GenericAPIResponse, error) { + return nsp.getEnableEpochsMetrics(EnableEpochsPath) +} + +// GetEnableEpochsMetricsV2 will simply forward the activation epochs config metrics from an observer +func (nsp *NodeStatusProcessor) GetEnableEpochsMetricsV2() (*data.GenericAPIResponse, error) { + return nsp.getEnableEpochsMetrics(EnableEpochsV2Path) +} + +func (nsp *NodeStatusProcessor) getEnableEpochsMetrics(path string) (*data.GenericAPIResponse, error) { observers, err := nsp.proc.GetAllObservers(data.AvailabilityRecent) if err != nil { return nil, err @@ -146,7 +158,7 @@ func (nsp *NodeStatusProcessor) GetEnableEpochsMetrics() (*data.GenericAPIRespon responseEnableEpochsMetrics := data.GenericAPIResponse{} for _, observer := range observers { - _, err := nsp.proc.CallGetRestEndPoint(observer.Address, EnableEpochsPath, &responseEnableEpochsMetrics) + _, err := nsp.proc.CallGetRestEndPoint(observer.Address, path, &responseEnableEpochsMetrics) if err != nil { log.Error("enable epochs metrics request", "observer", observer.Address, "error", err.Error()) continue diff --git a/process/nodeStatusProcessor_test.go b/process/nodeStatusProcessor_test.go index c074c154..d4b67c8b 100644 --- a/process/nodeStatusProcessor_test.go +++ b/process/nodeStatusProcessor_test.go @@ -523,6 +523,43 @@ func TestNodeStatusProcessor_GetEnableEpochsMetricsShouldWork(t *testing.T) { require.Equal(t, expectedValue, actualValue) } +func TestNodeStatusProcessor_GetEnableEpochsMetricsV2ShouldWork(t *testing.T) { + t.Parallel() + + key := "SupernovaEnableEpoch" + expectedValue := float64(4) + nodesStatusProc, _ := NewNodeStatusProcessor(&mock.ProcessorStub{ + GetAllObserversCalled: func(dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { + return []*data.NodeData{ + {Address: "addr1", ShardId: 0}, + }, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + metricMap := map[string]interface{}{ + key: expectedValue, + } + genericResp := &data.GenericAPIResponse{Data: metricMap} + genericRespBytes, _ := json.Marshal(genericResp) + + return 0, json.Unmarshal(genericRespBytes, value) + }, + }, + &mock.GenericApiResponseCacherMock{}, + time.Nanosecond, + ) + + genericResponse, err := nodesStatusProc.GetEnableEpochsMetricsV2() + require.Nil(t, err) + require.NotNil(t, genericResponse) + + metricsMap, ok := genericResponse.Data.(map[string]interface{}) + require.True(t, ok) + + actualValue, ok := metricsMap[key] + require.True(t, ok) + require.Equal(t, expectedValue, actualValue) +} + func TestNodeStatusProcessor_GetEnableEpochsMetricsGetObserversShouldErr(t *testing.T) { t.Parallel() diff --git a/process/testdata/tx-with-log-events.json b/process/testdata/tx-with-log-events.json new file mode 100644 index 00000000..a6688715 --- /dev/null +++ b/process/testdata/tx-with-log-events.json @@ -0,0 +1,57 @@ +{ + "transaction": { + "type": "normal", + "processingTypeOnSource": "MoveBalance", + "processingTypeOnDestination": "MoveBalance", + "hash": "a61c1062f927863a929e2fedce589a074672a211cfae512a7d6cbf4c9e3e30f8", + "nonce": 4, + "round": 63, + "epoch": 4, + "value": "0", + "receiver": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "sender": "erd1u56kw3acded08m5k6trfwvqt2a6pkamy54a59v5z98yqwsundpcqvsrjtl", + "gasPrice": 1000000000, + "gasLimit": 12000000, + "gasUsed": 117500, + "data": "bW9kaWZ5VG90YWxEZWxlZ2F0aW9uQ2FwQDAtMzYzNWM5YWRjNWRlYTAwMDAw", + "signature": "c3695887c5691538a46342c58af5130d6cf0375cac323e44bf76943c7daff8e343754e8ad03c6e8901b3914a7553b51141c91ca4339801d20fd97331442f9406", + "sourceShard": 0, + "destinationShard": 4294967295, + "blockNonce": 63, + "blockHash": "976b9ace84236612982c56fd88307d3032ad6112dee02c411b0332ac9327e505", + "notarizedAtSourceInMetaNonce": 63, + "NotarizedAtSourceInMetaHash": "976b9ace84236612982c56fd88307d3032ad6112dee02c411b0332ac9327e505", + "notarizedAtDestinationInMetaNonce": 63, + "notarizedAtDestinationInMetaHash": "976b9ace84236612982c56fd88307d3032ad6112dee02c411b0332ac9327e505", + "miniblockType": "TxBlock", + "miniblockHash": "7f7870c19f4ea4f93af2fb9cbfeb58879146a65f6a80163c57ef82b187bcda89", + "hyperblockNonce": 63, + "hyperblockHash": "976b9ace84236612982c56fd88307d3032ad6112dee02c411b0332ac9327e505", + "timestamp": 1766062438000, + "timestampMs": 1766062438000, + "logs": { + "address": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "events": [ + { + "address": "erd1qqqqqqqqqqqqqqqpqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqhllllsajxzat", + "identifier": "signalError", + "topics": [ + "5TVnR7huWvPultLGlzALV3Qbd2Sle0KyginIB0OTaHA=", + "aW52YWxpZCB0cmFuc2FjdGlvbg==" + ], + "data": "QDY5NmU3NjYxNmM2OTY0MjA3NDcyNjE2ZTczNjE2Mzc0Njk2ZjZl", + "additionalData": [ + "QDY5NmU3NjYxNmM2OTY0MjA3NDcyNjE2ZTczNjE2Mzc0Njk2ZjZl" + ] + } + ] + }, + "status": "success", + "operation": "transfer", + "initiallyPaidFee": "236325000000000", + "fee": "117500000000000", + "chainID": "chain", + "version": 2, + "options": 0 + } +} diff --git a/process/transactionProcessor.go b/process/transactionProcessor.go index b79a3dba..6b25429c 100644 --- a/process/transactionProcessor.go +++ b/process/transactionProcessor.go @@ -593,7 +593,9 @@ func checkIfMoveBalanceNotarized(tx *transaction.ApiTransactionResult) bool { if !isNotarized { return false } - isMoveBalance := tx.ProcessingTypeOnSource == moveBalanceDescriptor && tx.ProcessingTypeOnDestination == moveBalanceDescriptor + hasLogEvents := tx.Logs != nil && len(tx.Logs.Events) > 0 + isMoveBalance := tx.ProcessingTypeOnSource == moveBalanceDescriptor && + tx.ProcessingTypeOnDestination == moveBalanceDescriptor && !hasLogEvents return isMoveBalance } diff --git a/process/transactionProcessor_test.go b/process/transactionProcessor_test.go index 78ec6081..ba8d6687 100644 --- a/process/transactionProcessor_test.go +++ b/process/transactionProcessor_test.go @@ -1851,6 +1851,13 @@ func TestTransactionProcessor_computeTransactionStatus(t *testing.T) { status := tp.ComputeTransactionStatus(testData.Transaction, withResults) require.Equal(t, string(transaction.TxStatusFail), status.Status) }) + + t.Run("tx failed with log events", func(t *testing.T) { + testData := loadJsonIntoTxAndScrs(t, "./testdata/tx-with-log-events.json") + tp := createTestProcessorFromScenarioData(testData) + status := tp.ComputeTransactionStatus(testData.Transaction, withResults) + require.Equal(t, string(transaction.TxStatusFail), status.Status) + }) }) t.Run("SC deploy", func(t *testing.T) { t.Run("ok simple SC deploy", func(t *testing.T) {