From 7c8d0e17698f597cc24151992a309869885def07 Mon Sep 17 00:00:00 2001 From: Marius C Date: Fri, 21 Nov 2025 15:29:51 +0200 Subject: [PATCH 01/17] FEAT: Start caching hyperblock with lookup keys --- facade/mock/TimedCacheStub.go | 23 ++++++++++++++ process/blockProcessor.go | 18 ++++++++--- process/blockProcessorCache.go | 55 ++++++++++++++++++++++++++++++++++ process/interface.go | 7 +++++ 4 files changed, 99 insertions(+), 4 deletions(-) create mode 100644 facade/mock/TimedCacheStub.go create mode 100644 process/blockProcessorCache.go diff --git a/facade/mock/TimedCacheStub.go b/facade/mock/TimedCacheStub.go new file mode 100644 index 00000000..e97cfcd3 --- /dev/null +++ b/facade/mock/TimedCacheStub.go @@ -0,0 +1,23 @@ +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{}, sizeInBytes int) (evicted 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 +} diff --git a/process/blockProcessor.go b/process/blockProcessor.go index f70e438a..26f967bd 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -9,6 +9,7 @@ import ( "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" + "github.com/multiversx/mx-chain-proxy-go/facade/mock" ) const ( @@ -37,7 +38,8 @@ const ( // BlockProcessor handles blocks retrieving type BlockProcessor struct { - proc Processor + proc Processor + cache TimedCache } // NewBlockProcessor will create a new block processor @@ -45,9 +47,9 @@ func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { if check.IfNil(proc) { return nil, ErrNilCoreProcessor } - return &BlockProcessor{ - proc: proc, + proc: proc, + cache: mock.NewTimedCacheMock(), }, nil } @@ -114,6 +116,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 hyperBlock := bp.getHyperblockFromCache(hash, nil, options); hyperBlock != nil { + return hyperBlock, nil + } + builder := &hyperblockBuilder{} blockQueryOptions := common.BlockQueryOptions{ @@ -136,7 +142,11 @@ func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.Hyperb } hyperblock := builder.build(options.NotarizedAtSource) - return data.NewHyperblockApiResponse(hyperblock), nil + hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) + + bp.cacheHyperblock(hyperBlockRsp, options) + + return hyperBlockRsp, nil } func (bp *BlockProcessor) addShardBlocks( diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go new file mode 100644 index 00000000..7a2d2e92 --- /dev/null +++ b/process/blockProcessorCache.go @@ -0,0 +1,55 @@ +package process + +import ( + "encoding/json" + "fmt" + + "github.com/multiversx/mx-chain-proxy-go/common" + "github.com/multiversx/mx-chain-proxy-go/data" +) + +type hyperBlockCacheHandler interface { + cacheHyperblock(resp *data.HyperblockApiResponse, opts common.HyperblockQueryOptions) + getHyperblockFromCache(hash string, nonce *uint64, opts common.HyperblockQueryOptions) *data.HyperblockApiResponse +} + +func (bp *BlockProcessor) cacheHyperblock(resp *data.HyperblockApiResponse, opts common.HyperblockQueryOptions) { + hashKey := resp.Data.Hyperblock.Hash + optsStr, _ := json.Marshal(opts) + objKey := []byte(hashKey + string(optsStr)) + + // Store object + bp.cache.Put(objKey, resp, 0) + + // Store nonce + hash lookup keys + bp.cache.Put(getHashCacheKey(resp.Data.Hyperblock.Hash, opts), objKey, 0) + bp.cache.Put(getNonceCacheKey(resp.Data.Hyperblock.Nonce, opts), objKey, 0) +} + +func (bp *BlockProcessor) getHyperblockFromCache(hash string, nonce *uint64, opts common.HyperblockQueryOptions) *data.HyperblockApiResponse { + var key interface{} + if hash != "" { + key, _ = bp.cache.Get(getHashCacheKey(hash, opts)) + } else if nonce != nil { + key, _ = bp.cache.Get(getNonceCacheKey(*nonce, opts)) + } + + if key != nil { + val, ok := bp.cache.Get(key.([]byte)) + if ok { + return val.(*data.HyperblockApiResponse) + } + } + + return nil +} + +func getHashCacheKey(hash string, opts common.HyperblockQueryOptions) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(fmt.Sprintf("hash:%s|opts:%s", hash, string(optBytes))) +} + +func getNonceCacheKey(nonce uint64, opts common.HyperblockQueryOptions) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(fmt.Sprintf("nonce:%d|opts:%s", nonce, string(optBytes))) +} diff --git a/process/interface.go b/process/interface.go index f424bdfc..5634c3b1 100644 --- a/process/interface.go +++ b/process/interface.go @@ -85,3 +85,10 @@ type StatusMetricsProvider interface { type HttpClient interface { Do(req *http.Request) (*http.Response, error) } + +type TimedCache interface { + // Put adds a value to the cache. Returns true if an eviction occurred. + Put(key []byte, value interface{}, sizeInBytes int) (evicted bool) + // Get looks up a key's value from the cache. + Get(key []byte) (value interface{}, ok bool) +} From 30728a49ef26610b552f4ccd5e3cf8a204cdd11e Mon Sep 17 00:00:00 2001 From: Marius C Date: Fri, 21 Nov 2025 16:47:46 +0200 Subject: [PATCH 02/17] FEAT: Use templates to store cachable blocks --- data/interface.go | 25 +++++++++++++++ process/blockProcessor.go | 33 +++++++++++++------ process/blockProcessorCache.go | 58 +++++++++++++++++----------------- 3 files changed, 78 insertions(+), 38 deletions(-) create mode 100644 data/interface.go diff --git a/data/interface.go b/data/interface.go new file mode 100644 index 00000000..110fd256 --- /dev/null +++ b/data/interface.go @@ -0,0 +1,25 @@ +package data + +func (h *HyperblockApiResponse) ID() string { + return h.Data.Hyperblock.Hash +} + +func (h *HyperblockApiResponse) Hash() string { + return h.Data.Hyperblock.Hash +} + +func (h *HyperblockApiResponse) Nonce() uint64 { + return h.Data.Hyperblock.Nonce +} + +func (h *BlockApiResponse) ID() string { + return h.Data.Block.Hash +} + +func (h *BlockApiResponse) Hash() string { + return h.Data.Block.Hash +} + +func (h *BlockApiResponse) Nonce() uint64 { + return h.Data.Block.Nonce +} diff --git a/process/blockProcessor.go b/process/blockProcessor.go index 26f967bd..58703c2c 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -55,6 +55,11 @@ func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { // 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("block:shardID=%d", shardID) + if cached := getObjectFromCache[*data.BlockApiResponse](bp.cache, scope, hash, nil, options); cached != nil { + return *cached, nil + } + observers, err := bp.getObserversOrFullHistoryNodes(shardID) if err != nil { return nil, err @@ -64,7 +69,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()) @@ -72,8 +76,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) @@ -81,6 +86,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("block:shardID=%d", shardID) + if cached := getObjectFromCache[*data.BlockApiResponse](bp.cache, scope, "", &nonce, options); cached != nil { + return *cached, nil + } + observers, err := bp.getObserversOrFullHistoryNodes(shardID) if err != nil { return nil, err @@ -90,7 +100,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()) @@ -98,8 +107,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) @@ -116,8 +125,8 @@ 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 hyperBlock := bp.getHyperblockFromCache(hash, nil, options); hyperBlock != nil { - return hyperBlock, nil + if cached := getObjectFromCache[*data.HyperblockApiResponse](bp.cache, "hyperblock", hash, nil, options); cached != nil { + return *cached, nil } builder := &hyperblockBuilder{} @@ -143,8 +152,7 @@ func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.Hyperb hyperblock := builder.build(options.NotarizedAtSource) hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) - - bp.cacheHyperblock(hyperBlockRsp, options) + bp.cacheObject(hyperBlockRsp, "hyperblock", options) return hyperBlockRsp, nil } @@ -191,6 +199,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 := getObjectFromCache[*data.HyperblockApiResponse](bp.cache, "hyperblock", "", &nonce, options); cached != nil { + return *cached, nil + } + builder := &hyperblockBuilder{} blockQueryOptions := common.BlockQueryOptions{ @@ -213,7 +225,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, "hyperblock", options) + + return hyperBlockRsp, nil } // GetInternalBlockByHash will return the internal block based on its hash diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 7a2d2e92..3fd2c2f3 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -3,53 +3,53 @@ package process import ( "encoding/json" "fmt" - - "github.com/multiversx/mx-chain-proxy-go/common" - "github.com/multiversx/mx-chain-proxy-go/data" ) -type hyperBlockCacheHandler interface { - cacheHyperblock(resp *data.HyperblockApiResponse, opts common.HyperblockQueryOptions) - getHyperblockFromCache(hash string, nonce *uint64, opts common.HyperblockQueryOptions) *data.HyperblockApiResponse +type cacheableBlock interface { + ID() string + Hash() string + Nonce() uint64 } -func (bp *BlockProcessor) cacheHyperblock(resp *data.HyperblockApiResponse, opts common.HyperblockQueryOptions) { - hashKey := resp.Data.Hyperblock.Hash - optsStr, _ := json.Marshal(opts) - objKey := []byte(hashKey + string(optsStr)) +func makeHashCacheKey(scope string, hash string, opts interface{}) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))) +} + +func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))) +} + +func makeObjKey(id string, opts interface{}) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(id + string(optBytes)) +} + +func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { + objKey := makeObjKey(obj.ID(), opts) // Store object - bp.cache.Put(objKey, resp, 0) + bp.cache.Put(objKey, obj, 0) // Store nonce + hash lookup keys - bp.cache.Put(getHashCacheKey(resp.Data.Hyperblock.Hash, opts), objKey, 0) - bp.cache.Put(getNonceCacheKey(resp.Data.Hyperblock.Nonce, opts), objKey, 0) + bp.cache.Put(makeHashCacheKey(scope, obj.Hash(), opts), objKey, 0) + bp.cache.Put(makeNonceCacheKey(scope, obj.Nonce(), opts), objKey, 0) } -func (bp *BlockProcessor) getHyperblockFromCache(hash string, nonce *uint64, opts common.HyperblockQueryOptions) *data.HyperblockApiResponse { +func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) *T { var key interface{} if hash != "" { - key, _ = bp.cache.Get(getHashCacheKey(hash, opts)) + key, _ = c.Get(makeHashCacheKey(scope, hash, opts)) } else if nonce != nil { - key, _ = bp.cache.Get(getNonceCacheKey(*nonce, opts)) + key, _ = c.Get(makeNonceCacheKey(scope, *nonce, opts)) } if key != nil { - val, ok := bp.cache.Get(key.([]byte)) + val, ok := c.Get(key.([]byte)) if ok { - return val.(*data.HyperblockApiResponse) + return val.(*T) } } - return nil } - -func getHashCacheKey(hash string, opts common.HyperblockQueryOptions) []byte { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("hash:%s|opts:%s", hash, string(optBytes))) -} - -func getNonceCacheKey(nonce uint64, opts common.HyperblockQueryOptions) []byte { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("nonce:%d|opts:%s", nonce, string(optBytes))) -} From 6d65d6b7e90a912d1ced0ce150f4d16cc1a375dc Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 11:40:56 +0200 Subject: [PATCH 03/17] FEAT: Add part of time cacher --- process/cache/errors.go | 5 +- process/cache/timeCacher.go | 121 ++++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 2 deletions(-) create mode 100644 process/cache/timeCacher.go diff --git a/process/cache/errors.go b/process/cache/errors.go index a06d6aca..9e8fb448 100644 --- a/process/cache/errors.go +++ b/process/cache/errors.go @@ -17,5 +17,6 @@ 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") +var errInvalidCacheExpiry = errors.New("invalid cache expiry time") + +var errEmptyKey = errors.New("empty key") diff --git a/process/cache/timeCacher.go b/process/cache/timeCacher.go new file mode 100644 index 00000000..d5a2c565 --- /dev/null +++ b/process/cache/timeCacher.go @@ -0,0 +1,121 @@ +package cache + +import ( + "context" + "sync" + "time" + + logger "github.com/multiversx/mx-chain-logger-go" +) + +var log = logger.GetOrCreate("cache") + +const minDuration = time.Second + +type entry struct { + timestamp time.Time + span time.Duration + value interface{} +} +type timeCacher struct { + *sync.RWMutex + data map[string]*entry + duration time.Duration + cancelFunc func() +} + +// NewTimeCacher creates a new timeCacher +func NewTimeCacher(cacheExpiry time.Duration) (*timeCacher, error) { + if cacheExpiry < minDuration { + return nil, errInvalidCacheExpiry + } + + tc := &timeCacher{ + RWMutex: &sync.RWMutex{}, + data: make(map[string]*entry), + duration: cacheExpiry, + } + + var ctx context.Context + ctx, tc.cancelFunc = context.WithCancel(context.Background()) + go tc.startSweeping(ctx) + + return tc, nil +} + +// startSweeping handles sweeping the time cache +func (tc *timeCacher) startSweeping(ctx context.Context) { + timer := time.NewTimer(tc.duration) + defer timer.Stop() + + for { + timer.Reset(tc.duration) + + select { + case <-timer.C: + tc.sweep() + case <-ctx.Done(): + log.Info("closing mapTimeCacher's sweep go routine...") + return + } + } +} + +// Put will add the key, value and provided duration, overriding values if the data already existed +// It also operates on the locker so the call is concurrent safe +func (tc *timeCacher) Put(key string, value interface{}) error { + if len(key) == 0 { + return errEmptyKey + } + + tc.Lock() + tc.data[key] = &entry{ + timestamp: time.Now(), + span: tc.duration, + value: value, + } + tc.Unlock() + + return nil +} + +// Get returns a key's value from the cache +func (tc *timeCacher) Get(key []byte) (interface{}, bool) { + tc.RLock() + defer tc.RUnlock() + + v, ok := tc.data[string(key)] + if !ok { + return nil, ok + } + + return v.value, ok +} + +// sweep iterates over all contained elements checking if the element is still valid to be kept +// It also operates on the locker so the call is concurrent safe +func (tc *timeCacher) sweep() { + tc.Lock() + defer tc.Unlock() + + for key, element := range tc.data { + isOldElement := time.Since(element.timestamp) > element.span + if isOldElement { + delete(tc.data, key) + } + } +} + +// Close will close the internal sweep go routine +func (tc *timeCacher) Close() error { + if tc.cancelFunc != nil { + tc.cancelFunc() + } + + return nil +} + +// IsInterfaceNil returns true if there is no value under the interface +func (tc *timeCacher) IsInterfaceNil() bool { + return tc == nil +} From 93f69bf27c33fae06fe2863d220d3a2d73ee5514 Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 12:03:04 +0200 Subject: [PATCH 04/17] FEAT: Test block processor cache --- process/blockProcessor.go | 8 +- process/blockProcessorCache.go | 9 +- process/blockProcessorCache_test.go | 155 ++++++++++++++++++++++++++++ 3 files changed, 165 insertions(+), 7 deletions(-) create mode 100644 process/blockProcessorCache_test.go diff --git a/process/blockProcessor.go b/process/blockProcessor.go index 58703c2c..279819bf 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -57,7 +57,7 @@ func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options common.BlockQueryOptions) (*data.BlockApiResponse, error) { scope := fmt.Sprintf("block:shardID=%d", shardID) if cached := getObjectFromCache[*data.BlockApiResponse](bp.cache, scope, hash, nil, options); cached != nil { - return *cached, nil + return cached, nil } observers, err := bp.getObserversOrFullHistoryNodes(shardID) @@ -88,7 +88,7 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options common.BlockQueryOptions) (*data.BlockApiResponse, error) { scope := fmt.Sprintf("block:shardID=%d", shardID) if cached := getObjectFromCache[*data.BlockApiResponse](bp.cache, scope, "", &nonce, options); cached != nil { - return *cached, nil + return cached, nil } observers, err := bp.getObserversOrFullHistoryNodes(shardID) @@ -126,7 +126,7 @@ 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 := getObjectFromCache[*data.HyperblockApiResponse](bp.cache, "hyperblock", hash, nil, options); cached != nil { - return *cached, nil + return cached, nil } builder := &hyperblockBuilder{} @@ -200,7 +200,7 @@ 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 := getObjectFromCache[*data.HyperblockApiResponse](bp.cache, "hyperblock", "", &nonce, options); cached != nil { - return *cached, nil + return cached, nil } builder := &hyperblockBuilder{} diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 3fd2c2f3..443dd036 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -37,7 +37,9 @@ func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts int bp.cache.Put(makeNonceCacheKey(scope, obj.Nonce(), opts), objKey, 0) } -func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) *T { +func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) T { + var retObj T + var key interface{} if hash != "" { key, _ = c.Get(makeHashCacheKey(scope, hash, opts)) @@ -48,8 +50,9 @@ func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash strin if key != nil { val, ok := c.Get(key.([]byte)) if ok { - return val.(*T) + retObj = val.(T) } } - return nil + + return retObj } diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go new file mode 100644 index 00000000..d825ae64 --- /dev/null +++ b/process/blockProcessorCache_test.go @@ -0,0 +1,155 @@ +package process + +import ( + "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" + "github.com/multiversx/mx-chain-proxy-go/process/mock" + "github.com/stretchr/testify/require" +) + +func TestBlockProcessorCache(t *testing.T) { + t.Parallel() + + bp, _ := NewBlockProcessor(&mock.ProcessorStub{}) + + 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, + }, + }, + } + + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts1, + )) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + "", + &nonceBlock1, + opts1, + )) + + bp.cacheObject(blockApi1, scope1, opts1) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + hashBlock1, + nil, + opts1, + )) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts2, + )) + + require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts1, + )) + require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + "", + &nonceBlock1, + 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, + nil, + opts2, + )) + require.Equal(t, blockApi2, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + "", + &nonceBlock2, + opts2, + )) + + scopeHyperBlock := "hyperBlock" + hyperBlockNonce := uint64(4) + hyperBlockHash := "hyperBlockHash" + hyperBlock := &data.HyperblockApiResponse{ + Data: data.HyperblockApiResponsePayload{ + Hyperblock: api.Hyperblock{ + Hash: hyperBlockHash, + Nonce: hyperBlockNonce, + }, + }, + } + + bp.cacheObject(hyperBlock, scopeHyperBlock, opts1) + bp.cacheObject(hyperBlock, scopeHyperBlock, opts2) + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + hyperBlockHash, + nil, + opts2, + )) + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + "", + &hyperBlockNonce, + opts2, + )) + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + hyperBlockHash, + nil, + opts1, + )) + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + "", + &hyperBlockNonce, + opts1, + )) +} From 498274bddb3d46a3675a678db5d7f0360c8ec45a Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 13:34:37 +0200 Subject: [PATCH 05/17] CLN: Refactor solution --- facade/mock/TimedCacheStub.go | 4 +-- process/blockProcessor.go | 8 +++--- process/blockProcessorCache.go | 40 ++++++++++++++++------------- process/blockProcessorCache_test.go | 8 ++++++ process/cache/timeCacher.go | 4 +-- process/interface.go | 4 +-- 6 files changed, 39 insertions(+), 29 deletions(-) diff --git a/facade/mock/TimedCacheStub.go b/facade/mock/TimedCacheStub.go index e97cfcd3..59ca0677 100644 --- a/facade/mock/TimedCacheStub.go +++ b/facade/mock/TimedCacheStub.go @@ -11,9 +11,9 @@ func NewTimedCacheMock() *TimedCacheMock { } // Put - -func (mock *TimedCacheMock) Put(key []byte, value interface{}, sizeInBytes int) (evicted bool) { +func (mock *TimedCacheMock) Put(key []byte, value interface{}) error { mock.cache[string(key)] = value - return false + return nil } // Get - diff --git a/process/blockProcessor.go b/process/blockProcessor.go index 279819bf..79abbd51 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -56,7 +56,7 @@ func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { // 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("block:shardID=%d", shardID) - if cached := getObjectFromCache[*data.BlockApiResponse](bp.cache, scope, hash, nil, options); cached != nil { + if cached := getObjectFromCacheWithHash[*data.BlockApiResponse](bp.cache, scope, hash, options); cached != nil { return cached, nil } @@ -87,7 +87,7 @@ 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("block:shardID=%d", shardID) - if cached := getObjectFromCache[*data.BlockApiResponse](bp.cache, scope, "", &nonce, options); cached != nil { + if cached := getObjectFromCacheWithNonce[*data.BlockApiResponse](bp.cache, scope, nonce, options); cached != nil { return cached, nil } @@ -125,7 +125,7 @@ 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 := getObjectFromCache[*data.HyperblockApiResponse](bp.cache, "hyperblock", hash, nil, options); cached != nil { + if cached := getObjectFromCacheWithHash[*data.HyperblockApiResponse](bp.cache, "hyperblock", hash, options); cached != nil { return cached, nil } @@ -199,7 +199,7 @@ 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 := getObjectFromCache[*data.HyperblockApiResponse](bp.cache, "hyperblock", "", &nonce, options); cached != nil { + if cached := getObjectFromCacheWithNonce[*data.HyperblockApiResponse](bp.cache, "hyperblock", nonce, options); cached != nil { return cached, nil } diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 443dd036..7e8b0238 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -21,38 +21,42 @@ func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) []byte { return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))) } -func makeObjKey(id string, opts interface{}) []byte { +func makeObjKey(scope string, hash string, opts interface{}) []byte { optBytes, _ := json.Marshal(opts) - return []byte(id + string(optBytes)) + return []byte(scope + ":" + hash + ":" + string(optBytes)) } func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { - objKey := makeObjKey(obj.ID(), opts) + objKey := makeObjKey(scope, obj.Hash(), opts) // Store object - bp.cache.Put(objKey, obj, 0) + _ = bp.cache.Put(objKey, obj) // Store nonce + hash lookup keys - bp.cache.Put(makeHashCacheKey(scope, obj.Hash(), opts), objKey, 0) - bp.cache.Put(makeNonceCacheKey(scope, obj.Nonce(), opts), objKey, 0) + _ = bp.cache.Put(makeHashCacheKey(scope, obj.Hash(), opts), objKey) + _ = bp.cache.Put(makeNonceCacheKey(scope, obj.Nonce(), opts), objKey) } -func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) T { +func getObjectFromCacheWithHash[T cacheableBlock](c TimedCache, scope string, hash string, opts interface{}) T { + return getObjFromCache[T](c, makeHashCacheKey(scope, hash, opts)) +} + +func getObjectFromCacheWithNonce[T cacheableBlock](c TimedCache, scope string, nonce uint64, opts interface{}) T { + return getObjFromCache[T](c, makeNonceCacheKey(scope, nonce, opts)) +} + +func getObjFromCache[T cacheableBlock](c TimedCache, lookUpKey []byte) T { var retObj T - var key interface{} - if hash != "" { - key, _ = c.Get(makeHashCacheKey(scope, hash, opts)) - } else if nonce != nil { - key, _ = c.Get(makeNonceCacheKey(scope, *nonce, opts)) + key, _ := c.Get(lookUpKey) + if key == nil { + return retObj } - if key != nil { - val, ok := c.Get(key.([]byte)) - if ok { - retObj = val.(T) - } + val, ok := c.Get(key.([]byte)) + if !ok { + return retObj } - return retObj + return val.(T) } diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go index d825ae64..660c9db5 100644 --- a/process/blockProcessorCache_test.go +++ b/process/blockProcessorCache_test.go @@ -10,6 +10,14 @@ import ( "github.com/stretchr/testify/require" ) +func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) T { + if hash != "" { + return getObjFromCache[T](c, makeHashCacheKey(scope, hash, opts)) + } + + return getObjFromCache[T](c, makeNonceCacheKey(scope, *nonce, opts)) +} + func TestBlockProcessorCache(t *testing.T) { t.Parallel() diff --git a/process/cache/timeCacher.go b/process/cache/timeCacher.go index d5a2c565..8751732e 100644 --- a/process/cache/timeCacher.go +++ b/process/cache/timeCacher.go @@ -63,13 +63,13 @@ func (tc *timeCacher) startSweeping(ctx context.Context) { // Put will add the key, value and provided duration, overriding values if the data already existed // It also operates on the locker so the call is concurrent safe -func (tc *timeCacher) Put(key string, value interface{}) error { +func (tc *timeCacher) Put(key []byte, value interface{}) error { if len(key) == 0 { return errEmptyKey } tc.Lock() - tc.data[key] = &entry{ + tc.data[string(key)] = &entry{ timestamp: time.Now(), span: tc.duration, value: value, diff --git a/process/interface.go b/process/interface.go index 5634c3b1..afd9e616 100644 --- a/process/interface.go +++ b/process/interface.go @@ -87,8 +87,6 @@ type HttpClient interface { } type TimedCache interface { - // Put adds a value to the cache. Returns true if an eviction occurred. - Put(key []byte, value interface{}, sizeInBytes int) (evicted bool) - // Get looks up a key's value from the cache. + Put(key []byte, value interface{}) error Get(key []byte) (value interface{}, ok bool) } From c201a9780714e7d316e1880dfbf0e940163e12be Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 14:31:31 +0200 Subject: [PATCH 06/17] FEAT: Extend test to look for keys + lookup keys --- facade/mock/TimedCacheStub.go | 8 ++-- process/blockProcessor.go | 17 +++++--- process/blockProcessorCache.go | 5 ++- process/blockProcessorCache_test.go | 67 ++++++++++++++++++++++++++--- 4 files changed, 80 insertions(+), 17 deletions(-) diff --git a/facade/mock/TimedCacheStub.go b/facade/mock/TimedCacheStub.go index 59ca0677..3849fd57 100644 --- a/facade/mock/TimedCacheStub.go +++ b/facade/mock/TimedCacheStub.go @@ -2,22 +2,22 @@ package mock // TimedCacheMock - type TimedCacheMock struct { - cache map[string]interface{} + Cache map[string]interface{} } // NewTimedCacheMock - func NewTimedCacheMock() *TimedCacheMock { - return &TimedCacheMock{cache: make(map[string]interface{})} + return &TimedCacheMock{Cache: make(map[string]interface{})} } // Put - func (mock *TimedCacheMock) Put(key []byte, value interface{}) error { - mock.cache[string(key)] = value + mock.Cache[string(key)] = value return nil } // Get - func (mock *TimedCacheMock) Get(key []byte) (value interface{}, ok bool) { - val, found := mock.cache[string(key)] + val, found := mock.Cache[string(key)] return val, found } diff --git a/process/blockProcessor.go b/process/blockProcessor.go index 79abbd51..5634fa59 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -36,6 +36,11 @@ const ( rawPathStr = "raw" ) +const ( + blockScope = "block" + hyperBlockScope = "hyperblock" +) + // BlockProcessor handles blocks retrieving type BlockProcessor struct { proc Processor @@ -55,7 +60,7 @@ func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { // 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("block:shardID=%d", shardID) + scope := fmt.Sprintf("%s:shardID=%d", blockScope, shardID) if cached := getObjectFromCacheWithHash[*data.BlockApiResponse](bp.cache, scope, hash, options); cached != nil { return cached, nil } @@ -86,7 +91,7 @@ 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("block:shardID=%d", shardID) + scope := fmt.Sprintf("%s:shardID=%d", blockScope, shardID) if cached := getObjectFromCacheWithNonce[*data.BlockApiResponse](bp.cache, scope, nonce, options); cached != nil { return cached, nil } @@ -125,7 +130,7 @@ 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, "hyperblock", hash, options); cached != nil { + if cached := getObjectFromCacheWithHash[*data.HyperblockApiResponse](bp.cache, hyperBlockScope, hash, options); cached != nil { return cached, nil } @@ -152,7 +157,7 @@ func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.Hyperb hyperblock := builder.build(options.NotarizedAtSource) hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) - bp.cacheObject(hyperBlockRsp, "hyperblock", options) + bp.cacheObject(hyperBlockRsp, hyperBlockScope, options) return hyperBlockRsp, nil } @@ -199,7 +204,7 @@ 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, "hyperblock", nonce, options); cached != nil { + if cached := getObjectFromCacheWithNonce[*data.HyperblockApiResponse](bp.cache, hyperBlockScope, nonce, options); cached != nil { return cached, nil } @@ -226,7 +231,7 @@ func (bp *BlockProcessor) GetHyperBlockByNonce(nonce uint64, options common.Hype hyperblock := builder.build(options.NotarizedAtSource) hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) - bp.cacheObject(hyperBlockRsp, "hyperblock", options) + bp.cacheObject(hyperBlockRsp, hyperBlockScope, options) return hyperBlockRsp, nil } diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 7e8b0238..4782338b 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -11,6 +11,9 @@ type cacheableBlock interface { Nonce() uint64 } +// No error checks or returns in for this cache. +// These caching errors should never happen, and if they do, they should not be blocking + func makeHashCacheKey(scope string, hash string, opts interface{}) []byte { optBytes, _ := json.Marshal(opts) return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))) @@ -23,7 +26,7 @@ func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) []byte { func makeObjKey(scope string, hash string, opts interface{}) []byte { optBytes, _ := json.Marshal(opts) - return []byte(scope + ":" + hash + ":" + string(optBytes)) + return []byte(scope + ":" + hash + "|" + string(optBytes)) } func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go index 660c9db5..c2739036 100644 --- a/process/blockProcessorCache_test.go +++ b/process/blockProcessorCache_test.go @@ -1,11 +1,14 @@ 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" ) @@ -49,6 +52,7 @@ func TestBlockProcessorCache(t *testing.T) { }, } + // Some basic checks that te cache is empty require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( bp.cache, scope1, @@ -64,10 +68,11 @@ func TestBlockProcessorCache(t *testing.T) { opts1, )) + // Cache scope1:blockApi1:opts1 bp.cacheObject(blockApi1, scope1, opts1) require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( bp.cache, - scope2, + scope2, // wrong scope hashBlock1, nil, opts1, @@ -77,13 +82,13 @@ func TestBlockProcessorCache(t *testing.T) { scope1, hashBlock1, nil, - opts2, + opts2, // wrong opts )) require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( bp.cache, scope1, - hashBlock1, + hashBlock1, // found cached blockApi1 object by hash nil, opts1, )) @@ -91,10 +96,11 @@ func TestBlockProcessorCache(t *testing.T) { bp.cache, scope1, "", - &nonceBlock1, + &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, @@ -106,7 +112,7 @@ func TestBlockProcessorCache(t *testing.T) { require.Equal(t, blockApi2, getObjectFromCache[*data.BlockApiResponse]( bp.cache, scope2, - hashBlock2, + hashBlock2, // found cached blockApi2 object by hash nil, opts2, )) @@ -114,7 +120,7 @@ func TestBlockProcessorCache(t *testing.T) { bp.cache, scope2, "", - &nonceBlock2, + &nonceBlock2, // found cached blockApi2 object by nonce opts2, )) @@ -130,8 +136,13 @@ func TestBlockProcessorCache(t *testing.T) { }, } + // 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, @@ -139,6 +150,7 @@ func TestBlockProcessorCache(t *testing.T) { nil, opts2, )) + // found cached hyperBlock object by hyperBlockNonce + opts2 require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( bp.cache, scopeHyperBlock, @@ -146,6 +158,7 @@ func TestBlockProcessorCache(t *testing.T) { &hyperBlockNonce, opts2, )) + // found cached hyperBlock object by hyperBlockHash + opts1 require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( bp.cache, scopeHyperBlock, @@ -153,6 +166,7 @@ func TestBlockProcessorCache(t *testing.T) { nil, opts1, )) + // found cached hyperBlock object by hyperBlockNonce + opts1 require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( bp.cache, scopeHyperBlock, @@ -160,4 +174,45 @@ func TestBlockProcessorCache(t *testing.T) { &hyperBlockNonce, opts1, )) + + opts1Str, _ := json.Marshal(&opts1) + opts2Str, _ := json.Marshal(&opts2) + + mockCache := bp.cache.(*facadeMock.TimedCacheMock) + require.Len(t, mockCache.Cache, 12) + + expectedObjKeys := []string{ + fmt.Sprintf("%s:%s|%s", scope1, hashBlock1, opts1Str), // blockApi1 + fmt.Sprintf("%s:%s|%s", scope2, hashBlock2, opts2Str), // blockApi2 + fmt.Sprintf("%s:%s|%s", scopeHyperBlock, hyperBlockHash, opts1Str), // hyperBlock + fmt.Sprintf("%s:%s|%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])) } From 77c182a46dd3edaf1042a3815df8f1a39a18be99 Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 15:46:38 +0200 Subject: [PATCH 07/17] FEAT: Inject timed cache in block processor --- cmd/proxy/main.go | 9 +- data/interface.go | 8 -- .../{TimedCacheStub.go => timedCacheMock.go} | 10 ++ facade/mock/timedCacheStub.go | 25 +++++ process/blockProcessor.go | 9 +- process/blockProcessorCache.go | 33 +++--- process/blockProcessorCache_test.go | 5 +- process/blockProcessor_test.go | 105 ++++++++++-------- process/errors.go | 3 + process/interface.go | 2 + 10 files changed, 128 insertions(+), 81 deletions(-) rename facade/mock/{TimedCacheStub.go => timedCacheMock.go} (75%) create mode 100644 facade/mock/timedCacheStub.go diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 4acd6083..93a0611a 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -512,13 +512,18 @@ func createVersionsRegistry( return nil, err } - closableComponents.Add(nodeGroupProc, valStatsProc, nodeStatusProc, bp) + timedCache, err := cache.NewTimeCacher(time.Second * 5) + 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/data/interface.go b/data/interface.go index 110fd256..7deafb1c 100644 --- a/data/interface.go +++ b/data/interface.go @@ -1,9 +1,5 @@ package data -func (h *HyperblockApiResponse) ID() string { - return h.Data.Hyperblock.Hash -} - func (h *HyperblockApiResponse) Hash() string { return h.Data.Hyperblock.Hash } @@ -12,10 +8,6 @@ func (h *HyperblockApiResponse) Nonce() uint64 { return h.Data.Hyperblock.Nonce } -func (h *BlockApiResponse) ID() string { - return h.Data.Block.Hash -} - func (h *BlockApiResponse) Hash() string { return h.Data.Block.Hash } diff --git a/facade/mock/TimedCacheStub.go b/facade/mock/timedCacheMock.go similarity index 75% rename from facade/mock/TimedCacheStub.go rename to facade/mock/timedCacheMock.go index 3849fd57..a159d134 100644 --- a/facade/mock/TimedCacheStub.go +++ b/facade/mock/timedCacheMock.go @@ -21,3 +21,13 @@ 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..8356006a --- /dev/null +++ b/facade/mock/timedCacheStub.go @@ -0,0 +1,25 @@ +package mock + +// TimedCacheStub - +type TimedCacheStub struct { +} + +// Put - +func (stub *TimedCacheStub) Put(_ []byte, _ interface{}) error { + return nil +} + +// 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/process/blockProcessor.go b/process/blockProcessor.go index 5634fa59..a04e3780 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -9,7 +9,6 @@ import ( "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" - "github.com/multiversx/mx-chain-proxy-go/facade/mock" ) const ( @@ -48,13 +47,17 @@ type BlockProcessor struct { } // 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, - cache: mock.NewTimedCacheMock(), + cache: cache, }, nil } diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 4782338b..20ca36d9 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -6,29 +6,13 @@ import ( ) type cacheableBlock interface { - ID() string Hash() string Nonce() uint64 } -// No error checks or returns in for this cache. +// No error checks for this cache. // These caching errors should never happen, and if they do, they should not be blocking -func makeHashCacheKey(scope string, hash string, opts interface{}) []byte { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))) -} - -func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) []byte { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))) -} - -func makeObjKey(scope string, hash string, opts interface{}) []byte { - optBytes, _ := json.Marshal(opts) - return []byte(scope + ":" + hash + "|" + string(optBytes)) -} - func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { objKey := makeObjKey(scope, obj.Hash(), opts) @@ -40,6 +24,21 @@ func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts int _ = bp.cache.Put(makeNonceCacheKey(scope, obj.Nonce(), opts), objKey) } +func makeObjKey(scope string, hash string, opts interface{}) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(scope + ":" + hash + "|" + string(optBytes)) +} + +func makeHashCacheKey(scope string, hash string, opts interface{}) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))) +} + +func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) []byte { + optBytes, _ := json.Marshal(opts) + return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))) +} + func getObjectFromCacheWithHash[T cacheableBlock](c TimedCache, scope string, hash string, opts interface{}) T { return getObjFromCache[T](c, makeHashCacheKey(scope, hash, opts)) } diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go index c2739036..d0d2af5a 100644 --- a/process/blockProcessorCache_test.go +++ b/process/blockProcessorCache_test.go @@ -24,7 +24,8 @@ func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash strin func TestBlockProcessorCache(t *testing.T) { t.Parallel() - bp, _ := NewBlockProcessor(&mock.ProcessorStub{}) + mockCache := facadeMock.NewTimedCacheMock() + bp, _ := NewBlockProcessor(&mock.ProcessorStub{}, mockCache) nonceBlock1 := uint64(1) nonceBlock2 := uint64(2) @@ -177,8 +178,6 @@ func TestBlockProcessorCache(t *testing.T) { opts1Str, _ := json.Marshal(&opts1) opts2Str, _ := json.Marshal(&opts2) - - mockCache := bp.cache.(*facadeMock.TimedCacheMock) require.Len(t, mockCache.Cache, 12) expectedObjKeys := []string{ diff --git a/process/blockProcessor_test.go b/process/blockProcessor_test.go index e9740d83..b7a8e9bd 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) 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 afd9e616..a70d2026 100644 --- a/process/interface.go +++ b/process/interface.go @@ -89,4 +89,6 @@ type HttpClient interface { type TimedCache interface { Put(key []byte, value interface{}) error Get(key []byte) (value interface{}, ok bool) + Close() error + IsInterfaceNil() bool } From 9ec89c4dfdd0cd40b003c4421027fa0a3e73eb88 Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 16:37:47 +0200 Subject: [PATCH 08/17] FEAT: Config cache duration + test get cached hyper block --- cmd/proxy/config/config.toml | 3 ++ cmd/proxy/main.go | 3 +- config/config.go | 1 + process/blockProcessor_test.go | 74 ++++++++++++++++++++++++++++++++++ process/cache/timeCacher.go | 4 +- 5 files changed, 81 insertions(+), 4 deletions(-) diff --git a/cmd/proxy/config/config.toml b/cmd/proxy/config/config.toml index 03ef65fc..ee4b3bc6 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/main.go b/cmd/proxy/main.go index 93a0611a..1185aa73 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -336,6 +336,7 @@ func createVersionsRegistryTestOrProduction( HeartbeatCacheValidityDurationSec: 60, ValStatsCacheValidityDurationSec: 60, EconomicsMetricsCacheValidityDurationSec: 6, + BlockCacheDurationSec: 1, FaucetValue: "10000000000", }, ApiLogging: config.ApiLoggingConfig{ @@ -512,7 +513,7 @@ func createVersionsRegistry( return nil, err } - timedCache, err := cache.NewTimeCacher(time.Second * 5) + timedCache, err := cache.NewTimeCacher(time.Duration(cfg.GeneralSettings.BlockCacheDurationSec) * time.Second) 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/process/blockProcessor_test.go b/process/blockProcessor_test.go index b7a8e9bd..f40103f5 100644 --- a/process/blockProcessor_test.go +++ b/process/blockProcessor_test.go @@ -1417,3 +1417,77 @@ 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) +} diff --git a/process/cache/timeCacher.go b/process/cache/timeCacher.go index 8751732e..9f6e8257 100644 --- a/process/cache/timeCacher.go +++ b/process/cache/timeCacher.go @@ -14,7 +14,6 @@ const minDuration = time.Second type entry struct { timestamp time.Time - span time.Duration value interface{} } type timeCacher struct { @@ -71,7 +70,6 @@ func (tc *timeCacher) Put(key []byte, value interface{}) error { tc.Lock() tc.data[string(key)] = &entry{ timestamp: time.Now(), - span: tc.duration, value: value, } tc.Unlock() @@ -99,7 +97,7 @@ func (tc *timeCacher) sweep() { defer tc.Unlock() for key, element := range tc.data { - isOldElement := time.Since(element.timestamp) > element.span + isOldElement := time.Since(element.timestamp) > tc.duration if isOldElement { delete(tc.data, key) } From e82423be41735e59ff308a537b058e4b070c96c8 Mon Sep 17 00:00:00 2001 From: Marius C Date: Mon, 24 Nov 2025 16:57:26 +0200 Subject: [PATCH 09/17] FEAT: Test cached block by hash/nonce --- process/blockProcessor_test.go | 55 ++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/process/blockProcessor_test.go b/process/blockProcessor_test.go index f40103f5..6dbabfed 100644 --- a/process/blockProcessor_test.go +++ b/process/blockProcessor_test.go @@ -1490,4 +1490,59 @@ func TestBlockProcessor_GetCachedHyperBlocks(t *testing.T) { 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) } From cf1a7a844c2bc129a1687870eb406dc2c79aef82 Mon Sep 17 00:00:00 2001 From: Marius C Date: Tue, 25 Nov 2025 16:03:18 +0200 Subject: [PATCH 10/17] CLN: After self review --- cmd/proxy/main.go | 2 +- data/block.go | 20 ++++++++++++++++++++ data/interface.go | 17 ----------------- process/blockProcessorCache_test.go | 2 +- process/interface.go | 2 ++ 5 files changed, 24 insertions(+), 19 deletions(-) delete mode 100644 data/interface.go diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 1185aa73..39fc8e79 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -336,7 +336,7 @@ func createVersionsRegistryTestOrProduction( HeartbeatCacheValidityDurationSec: 60, ValStatsCacheValidityDurationSec: 60, EconomicsMetricsCacheValidityDurationSec: 6, - BlockCacheDurationSec: 1, + BlockCacheDurationSec: 30, FaucetValue: "10000000000", }, ApiLogging: config.ApiLoggingConfig{ 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/data/interface.go b/data/interface.go deleted file mode 100644 index 7deafb1c..00000000 --- a/data/interface.go +++ /dev/null @@ -1,17 +0,0 @@ -package data - -func (h *HyperblockApiResponse) Hash() string { - return h.Data.Hyperblock.Hash -} - -func (h *HyperblockApiResponse) Nonce() uint64 { - return h.Data.Hyperblock.Nonce -} - -func (h *BlockApiResponse) Hash() string { - return h.Data.Block.Hash -} - -func (h *BlockApiResponse) Nonce() uint64 { - return h.Data.Block.Nonce -} diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go index d0d2af5a..81bc8776 100644 --- a/process/blockProcessorCache_test.go +++ b/process/blockProcessorCache_test.go @@ -53,7 +53,7 @@ func TestBlockProcessorCache(t *testing.T) { }, } - // Some basic checks that te cache is empty + // Some basic checks that the cache is empty require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( bp.cache, scope1, diff --git a/process/interface.go b/process/interface.go index a70d2026..aea93650 100644 --- a/process/interface.go +++ b/process/interface.go @@ -86,6 +86,8 @@ 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{}) error Get(key []byte) (value interface{}, ok bool) From 9fc037947c96629e5049a3eb0d774c41a7a0e6df Mon Sep 17 00:00:00 2001 From: Marius C Date: Tue, 2 Dec 2025 17:31:02 +0200 Subject: [PATCH 11/17] FIX: After review pt 1 --- cmd/proxy/main.go | 7 +- facade/mock/timedCacheMock.go | 4 +- facade/mock/timedCacheStub.go | 4 +- go.mod | 1 + go.sum | 2 + process/blockProcessorCache.go | 23 ++++-- process/blockProcessorCache_test.go | 8 +- process/cache/errors.go | 4 - process/cache/timeCacher.go | 119 ---------------------------- process/interface.go | 2 +- 10 files changed, 35 insertions(+), 139 deletions(-) delete mode 100644 process/cache/timeCacher.go diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 39fc8e79..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" ) @@ -513,7 +514,11 @@ func createVersionsRegistry( return nil, err } - timedCache, err := cache.NewTimeCacher(time.Duration(cfg.GeneralSettings.BlockCacheDurationSec) * time.Second) + cacheDuration := time.Duration(cfg.GeneralSettings.BlockCacheDurationSec) * time.Second + timedCache, err := timecache.NewTimeCacher(timecache.ArgTimeCacher{ + DefaultSpan: cacheDuration, + CacheExpiry: cacheDuration, + }) if err != nil { return nil, err } diff --git a/facade/mock/timedCacheMock.go b/facade/mock/timedCacheMock.go index a159d134..32bb8d84 100644 --- a/facade/mock/timedCacheMock.go +++ b/facade/mock/timedCacheMock.go @@ -11,9 +11,9 @@ func NewTimedCacheMock() *TimedCacheMock { } // Put - -func (mock *TimedCacheMock) Put(key []byte, value interface{}) error { +func (mock *TimedCacheMock) Put(key []byte, value interface{}, _ int) bool { mock.Cache[string(key)] = value - return nil + return false } // Get - diff --git a/facade/mock/timedCacheStub.go b/facade/mock/timedCacheStub.go index 8356006a..2ade54c2 100644 --- a/facade/mock/timedCacheStub.go +++ b/facade/mock/timedCacheStub.go @@ -5,8 +5,8 @@ type TimedCacheStub struct { } // Put - -func (stub *TimedCacheStub) Put(_ []byte, _ interface{}) error { - return nil +func (stub *TimedCacheStub) Put(_ []byte, _ interface{}, _ int) bool { + return false } // Get - diff --git a/go.mod b/go.mod index c6882be5..75048051 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( 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-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 diff --git a/go.sum b/go.sum index c837895e..d0429167 100644 --- a/go.sum +++ b/go.sum @@ -140,6 +140,8 @@ github.com/multiversx/mx-chain-es-indexer-go v1.8.0 h1:VLN9V3yNxchyGub25yOIUoa3K github.com/multiversx/mx-chain-es-indexer-go v1.8.0/go.mod h1:OLsBLRme3wL59qPRSJ2UG3xRU8QIR/JnbXHPEncR4dg= 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= diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 20ca36d9..56e64991 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -15,18 +15,20 @@ type cacheableBlock interface { func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { objKey := makeObjKey(scope, obj.Hash(), opts) + hashLookupKey := makeHashCacheKey(scope, obj.Hash(), opts) + nonceLookupKey := makeNonceCacheKey(scope, obj.Nonce(), opts) // Store object - _ = bp.cache.Put(objKey, obj) + _ = bp.cache.Put(objKey, obj, 0) // Store nonce + hash lookup keys - _ = bp.cache.Put(makeHashCacheKey(scope, obj.Hash(), opts), objKey) - _ = bp.cache.Put(makeNonceCacheKey(scope, obj.Nonce(), opts), objKey) + _ = bp.cache.Put(hashLookupKey, objKey, 0) + _ = bp.cache.Put(nonceLookupKey, objKey, 0) } func makeObjKey(scope string, hash string, opts interface{}) []byte { optBytes, _ := json.Marshal(opts) - return []byte(scope + ":" + hash + "|" + string(optBytes)) + return []byte(fmt.Sprintf("%s:%s|opts:%s", scope, hash, string(optBytes))) } func makeHashCacheKey(scope string, hash string, opts interface{}) []byte { @@ -55,10 +57,19 @@ func getObjFromCache[T cacheableBlock](c TimedCache, lookUpKey []byte) T { return retObj } - val, ok := c.Get(key.([]byte)) + keyBytes, ok := key.([]byte) if !ok { return retObj } - return val.(T) + 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 index 81bc8776..ba8c17dc 100644 --- a/process/blockProcessorCache_test.go +++ b/process/blockProcessorCache_test.go @@ -181,10 +181,10 @@ func TestBlockProcessorCache(t *testing.T) { require.Len(t, mockCache.Cache, 12) expectedObjKeys := []string{ - fmt.Sprintf("%s:%s|%s", scope1, hashBlock1, opts1Str), // blockApi1 - fmt.Sprintf("%s:%s|%s", scope2, hashBlock2, opts2Str), // blockApi2 - fmt.Sprintf("%s:%s|%s", scopeHyperBlock, hyperBlockHash, opts1Str), // hyperBlock - fmt.Sprintf("%s:%s|%s", scopeHyperBlock, hyperBlockHash, opts2Str), // hyperBlock + 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) diff --git a/process/cache/errors.go b/process/cache/errors.go index 9e8fb448..7d9bca16 100644 --- a/process/cache/errors.go +++ b/process/cache/errors.go @@ -16,7 +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") - -var errInvalidCacheExpiry = errors.New("invalid cache expiry time") - -var errEmptyKey = errors.New("empty key") diff --git a/process/cache/timeCacher.go b/process/cache/timeCacher.go deleted file mode 100644 index 9f6e8257..00000000 --- a/process/cache/timeCacher.go +++ /dev/null @@ -1,119 +0,0 @@ -package cache - -import ( - "context" - "sync" - "time" - - logger "github.com/multiversx/mx-chain-logger-go" -) - -var log = logger.GetOrCreate("cache") - -const minDuration = time.Second - -type entry struct { - timestamp time.Time - value interface{} -} -type timeCacher struct { - *sync.RWMutex - data map[string]*entry - duration time.Duration - cancelFunc func() -} - -// NewTimeCacher creates a new timeCacher -func NewTimeCacher(cacheExpiry time.Duration) (*timeCacher, error) { - if cacheExpiry < minDuration { - return nil, errInvalidCacheExpiry - } - - tc := &timeCacher{ - RWMutex: &sync.RWMutex{}, - data: make(map[string]*entry), - duration: cacheExpiry, - } - - var ctx context.Context - ctx, tc.cancelFunc = context.WithCancel(context.Background()) - go tc.startSweeping(ctx) - - return tc, nil -} - -// startSweeping handles sweeping the time cache -func (tc *timeCacher) startSweeping(ctx context.Context) { - timer := time.NewTimer(tc.duration) - defer timer.Stop() - - for { - timer.Reset(tc.duration) - - select { - case <-timer.C: - tc.sweep() - case <-ctx.Done(): - log.Info("closing mapTimeCacher's sweep go routine...") - return - } - } -} - -// Put will add the key, value and provided duration, overriding values if the data already existed -// It also operates on the locker so the call is concurrent safe -func (tc *timeCacher) Put(key []byte, value interface{}) error { - if len(key) == 0 { - return errEmptyKey - } - - tc.Lock() - tc.data[string(key)] = &entry{ - timestamp: time.Now(), - value: value, - } - tc.Unlock() - - return nil -} - -// Get returns a key's value from the cache -func (tc *timeCacher) Get(key []byte) (interface{}, bool) { - tc.RLock() - defer tc.RUnlock() - - v, ok := tc.data[string(key)] - if !ok { - return nil, ok - } - - return v.value, ok -} - -// sweep iterates over all contained elements checking if the element is still valid to be kept -// It also operates on the locker so the call is concurrent safe -func (tc *timeCacher) sweep() { - tc.Lock() - defer tc.Unlock() - - for key, element := range tc.data { - isOldElement := time.Since(element.timestamp) > tc.duration - if isOldElement { - delete(tc.data, key) - } - } -} - -// Close will close the internal sweep go routine -func (tc *timeCacher) Close() error { - if tc.cancelFunc != nil { - tc.cancelFunc() - } - - return nil -} - -// IsInterfaceNil returns true if there is no value under the interface -func (tc *timeCacher) IsInterfaceNil() bool { - return tc == nil -} diff --git a/process/interface.go b/process/interface.go index aea93650..3a27454a 100644 --- a/process/interface.go +++ b/process/interface.go @@ -89,7 +89,7 @@ type HttpClient interface { // 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{}) error + Put(key []byte, value interface{}, _ int) (evicted bool) Get(key []byte) (value interface{}, ok bool) Close() error IsInterfaceNil() bool From acb543e89f1f130f937febde1dac86bd911bbb0c Mon Sep 17 00:00:00 2001 From: Marius C Date: Wed, 3 Dec 2025 13:47:20 +0200 Subject: [PATCH 12/17] FIX: After review pt 2 --- cmd/proxy/config/config.toml | 2 +- process/blockProcessorCache.go | 63 ++++++++++++++++++++++------- process/blockProcessorCache_test.go | 6 ++- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/cmd/proxy/config/config.toml b/cmd/proxy/config/config.toml index ee4b3bc6..1a4e95e9 100644 --- a/cmd/proxy/config/config.toml +++ b/cmd/proxy/config/config.toml @@ -18,7 +18,7 @@ # 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 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. diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go index 56e64991..73e1ab09 100644 --- a/process/blockProcessorCache.go +++ b/process/blockProcessorCache.go @@ -14,9 +14,20 @@ type cacheableBlock interface { // 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 := makeObjKey(scope, obj.Hash(), opts) - hashLookupKey := makeHashCacheKey(scope, obj.Hash(), opts) - nonceLookupKey := makeNonceCacheKey(scope, obj.Nonce(), opts) + 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) @@ -26,27 +37,51 @@ func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts int _ = bp.cache.Put(nonceLookupKey, objKey, 0) } -func makeObjKey(scope string, hash string, opts interface{}) []byte { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("%s:%s|opts:%s", scope, hash, string(optBytes))) +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 { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))) +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 { - optBytes, _ := json.Marshal(opts) - return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))) +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 { - return getObjFromCache[T](c, makeHashCacheKey(scope, hash, opts)) + 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 { - return getObjFromCache[T](c, makeNonceCacheKey(scope, nonce, opts)) + 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 { diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go index ba8c17dc..7b9af3c8 100644 --- a/process/blockProcessorCache_test.go +++ b/process/blockProcessorCache_test.go @@ -15,10 +15,12 @@ import ( func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) T { if hash != "" { - return getObjFromCache[T](c, makeHashCacheKey(scope, hash, opts)) + hashKey, _ := makeHashCacheKey(scope, hash, opts) + return getObjFromCache[T](c, hashKey) } - return getObjFromCache[T](c, makeNonceCacheKey(scope, *nonce, opts)) + nonceKey, _ := makeNonceCacheKey(scope, *nonce, opts) + return getObjFromCache[T](c, nonceKey) } func TestBlockProcessorCache(t *testing.T) { From 2c3e73359fdd71bdb61271a49c3740e9d68e41f7 Mon Sep 17 00:00:00 2001 From: Marius C Date: Fri, 12 Dec 2025 15:39:05 +0200 Subject: [PATCH 13/17] FEAT: Go core with get proxy block with exec res --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 75048051..d0c3a9e2 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ 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.20251211081223-2b266c66a68a 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-logger-go v1.1.0 diff --git a/go.sum b/go.sum index d0429167..55252fc7 100644 --- a/go.sum +++ b/go.sum @@ -132,8 +132,8 @@ 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.20251211081223-2b266c66a68a h1:+MWgbbcL8RoBOweAP+EBMgFYKk6wCYi0Ob+3Flgwypw= +github.com/multiversx/mx-chain-core-go v1.4.2-0.20251211081223-2b266c66a68a/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= From 21763951cae22eabfe94fd4a99e9be7cf07380dc Mon Sep 17 00:00:00 2001 From: miiu Date: Mon, 22 Dec 2025 11:58:19 +0200 Subject: [PATCH 14/17] process status fixes --- process/testdata/tx-with-log-events.json | 57 ++++++++++++++++++++++++ process/transactionProcessor.go | 4 +- process/transactionProcessor_test.go | 7 +++ 3 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 process/testdata/tx-with-log-events.json 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) { From 2b5a701943ed88a045dbffc98a5251c203abd715 Mon Sep 17 00:00:00 2001 From: Sorin Stanculeanu Date: Wed, 21 Jan 2026 12:09:43 +0200 Subject: [PATCH 15/17] enable epochs v2 --- README.md | 1 + api/groups/baseNetworkGroup.go | 11 ++++ api/groups/baseNetworkGroup_test.go | 81 +++++++++++++++++++++++++- api/groups/interface.go | 1 + api/mock/facadeStub.go | 6 ++ cmd/proxy/config/apiConfig/v1_0.toml | 1 + cmd/proxy/config/apiConfig/v_next.toml | 1 + cmd/proxy/config/swagger/openapi.json | 20 +++++++ facade/baseFacade.go | 13 +++-- facade/interface.go | 1 + facade/mock/nodeStatusProcessorStub.go | 10 ++++ go.mod | 12 ++-- go.sum | 24 ++++---- process/nodeStatusProcessor.go | 14 ++++- process/nodeStatusProcessor_test.go | 37 ++++++++++++ 15 files changed, 207 insertions(+), 26 deletions(-) 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/swagger/openapi.json b/cmd/proxy/config/swagger/openapi.json index 8c77f753..5b1e55d2 100644 --- a/cmd/proxy/config/swagger/openapi.json +++ b/cmd/proxy/config/swagger/openapi.json @@ -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/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/go.mod b/go.mod index d0c3a9e2..f82fa260 100644 --- a/go.mod +++ b/go.mod @@ -7,9 +7,9 @@ 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.2-0.20251211081223-2b266c66a68a + 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 @@ -49,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 55252fc7..456bd0bd 100644 --- a/go.sum +++ b/go.sum @@ -132,12 +132,12 @@ 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.2-0.20251211081223-2b266c66a68a h1:+MWgbbcL8RoBOweAP+EBMgFYKk6wCYi0Ob+3Flgwypw= -github.com/multiversx/mx-chain-core-go v1.4.2-0.20251211081223-2b266c66a68a/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= @@ -202,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= @@ -215,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= @@ -237,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= @@ -264,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/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() From 94f582649405206ee86b07653d619a7b5c738414 Mon Sep 17 00:00:00 2001 From: miiu Date: Fri, 13 Feb 2026 10:54:36 +0200 Subject: [PATCH 16/17] fix description --- cmd/proxy/config/swagger/openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/proxy/config/swagger/openapi.json b/cmd/proxy/config/swagger/openapi.json index 5b1e55d2..e4179dae 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", From 8338035c51ff31aa3dcbff329850a53381c0ac38 Mon Sep 17 00:00:00 2001 From: miiu Date: Fri, 13 Feb 2026 11:02:22 +0200 Subject: [PATCH 17/17] fix --- cmd/proxy/config/swagger/openapi.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/proxy/config/swagger/openapi.json b/cmd/proxy/config/swagger/openapi.json index e4179dae..f38c35bf 100644 --- a/cmd/proxy/config/swagger/openapi.json +++ b/cmd/proxy/config/swagger/openapi.json @@ -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",