diff --git a/cmd/proxy/config/config.toml b/cmd/proxy/config/config.toml index 03ef65fc..1a4e95e9 100644 --- a/cmd/proxy/config/config.toml +++ b/cmd/proxy/config/config.toml @@ -18,6 +18,9 @@ # before it should be updated EconomicsMetricsCacheValidityDurationSec = 600 # 10 minutes + # BlockCacheDurationSec defines how long block/hyperblock results (queried by hash or nonce) are kept in cache, in seconds. + BlockCacheDurationSec = 30 + # BalancedObservers - if this flag is set to true, then the requests will be distributed equally between observers. # Otherwise, there are chances that only one observer from a shard will process the requests BalancedObservers = true diff --git a/cmd/proxy/main.go b/cmd/proxy/main.go index 4acd6083..311bc2a9 100644 --- a/cmd/proxy/main.go +++ b/cmd/proxy/main.go @@ -30,6 +30,7 @@ import ( processFactory "github.com/multiversx/mx-chain-proxy-go/process/factory" "github.com/multiversx/mx-chain-proxy-go/testing" versionsFactory "github.com/multiversx/mx-chain-proxy-go/versions/factory" + "github.com/multiversx/mx-chain-storage-go/timecache" "github.com/urfave/cli" ) @@ -336,6 +337,7 @@ func createVersionsRegistryTestOrProduction( HeartbeatCacheValidityDurationSec: 60, ValStatsCacheValidityDurationSec: 60, EconomicsMetricsCacheValidityDurationSec: 6, + BlockCacheDurationSec: 30, FaucetValue: "10000000000", }, ApiLogging: config.ApiLoggingConfig{ @@ -512,13 +514,22 @@ func createVersionsRegistry( return nil, err } - closableComponents.Add(nodeGroupProc, valStatsProc, nodeStatusProc, bp) + cacheDuration := time.Duration(cfg.GeneralSettings.BlockCacheDurationSec) * time.Second + timedCache, err := timecache.NewTimeCacher(timecache.ArgTimeCacher{ + DefaultSpan: cacheDuration, + CacheExpiry: cacheDuration, + }) + if err != nil { + return nil, err + } + + closableComponents.Add(nodeGroupProc, valStatsProc, nodeStatusProc, bp, timedCache) nodeGroupProc.StartCacheUpdate() valStatsProc.StartCacheUpdate() nodeStatusProc.StartCacheUpdate() - blockProc, err := process.NewBlockProcessor(bp) + blockProc, err := process.NewBlockProcessor(bp, timedCache) if err != nil { return nil, err } diff --git a/config/config.go b/config/config.go index 90317616..0cfcc4de 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ type GeneralSettingsConfig struct { HeartbeatCacheValidityDurationSec int ValStatsCacheValidityDurationSec int EconomicsMetricsCacheValidityDurationSec int + BlockCacheDurationSec int FaucetValue string RateLimitWindowDurationSeconds int BalancedObservers bool diff --git a/data/block.go b/data/block.go index ac70eb0c..bf713eb9 100644 --- a/data/block.go +++ b/data/block.go @@ -12,6 +12,16 @@ type BlockApiResponse struct { Code ReturnCode `json:"code"` } +// Hash returns internal hash +func (h *BlockApiResponse) Hash() string { + return h.Data.Block.Hash +} + +// Nonce returns internal nonce +func (h *BlockApiResponse) Nonce() uint64 { + return h.Data.Block.Nonce +} + // BlockApiResponsePayload wraps a block type BlockApiResponsePayload struct { Block api.Block `json:"block"` @@ -24,6 +34,16 @@ type HyperblockApiResponse struct { Code ReturnCode `json:"code"` } +// Hash returns internal hash +func (h *HyperblockApiResponse) Hash() string { + return h.Data.Hyperblock.Hash +} + +// Nonce returns internal nonce +func (h *HyperblockApiResponse) Nonce() uint64 { + return h.Data.Hyperblock.Nonce +} + // NewHyperblockApiResponse creates a HyperblockApiResponse func NewHyperblockApiResponse(hyperblock api.Hyperblock) *HyperblockApiResponse { return &HyperblockApiResponse{ diff --git a/facade/mock/timedCacheMock.go b/facade/mock/timedCacheMock.go new file mode 100644 index 00000000..32bb8d84 --- /dev/null +++ b/facade/mock/timedCacheMock.go @@ -0,0 +1,33 @@ +package mock + +// TimedCacheMock - +type TimedCacheMock struct { + Cache map[string]interface{} +} + +// NewTimedCacheMock - +func NewTimedCacheMock() *TimedCacheMock { + return &TimedCacheMock{Cache: make(map[string]interface{})} +} + +// Put - +func (mock *TimedCacheMock) Put(key []byte, value interface{}, _ int) bool { + mock.Cache[string(key)] = value + return false +} + +// Get - +func (mock *TimedCacheMock) Get(key []byte) (value interface{}, ok bool) { + val, found := mock.Cache[string(key)] + return val, found +} + +// Close - +func (mock *TimedCacheMock) Close() error { + return nil +} + +// IsInterfaceNil - +func (mock *TimedCacheMock) IsInterfaceNil() bool { + return mock == nil +} diff --git a/facade/mock/timedCacheStub.go b/facade/mock/timedCacheStub.go new file mode 100644 index 00000000..2ade54c2 --- /dev/null +++ b/facade/mock/timedCacheStub.go @@ -0,0 +1,25 @@ +package mock + +// TimedCacheStub - +type TimedCacheStub struct { +} + +// Put - +func (stub *TimedCacheStub) Put(_ []byte, _ interface{}, _ int) bool { + return false +} + +// Get - +func (stub *TimedCacheStub) Get(_ []byte) (value interface{}, ok bool) { + return nil, false +} + +// Close - +func (stub *TimedCacheStub) Close() error { + return nil +} + +// IsInterfaceNil - +func (stub *TimedCacheStub) IsInterfaceNil() bool { + return stub == nil +} diff --git a/go.mod b/go.mod index c6882be5..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/blockProcessor.go b/process/blockProcessor.go index f70e438a..a04e3780 100644 --- a/process/blockProcessor.go +++ b/process/blockProcessor.go @@ -35,24 +35,39 @@ const ( rawPathStr = "raw" ) +const ( + blockScope = "block" + hyperBlockScope = "hyperblock" +) + // BlockProcessor handles blocks retrieving type BlockProcessor struct { - proc Processor + proc Processor + cache TimedCache } // NewBlockProcessor will create a new block processor -func NewBlockProcessor(proc Processor) (*BlockProcessor, error) { +func NewBlockProcessor(proc Processor, cache TimedCache) (*BlockProcessor, error) { if check.IfNil(proc) { return nil, ErrNilCoreProcessor } + if check.IfNil(cache) { + return nil, ErrNilTimedCache + } return &BlockProcessor{ - proc: proc, + proc: proc, + cache: cache, }, nil } // GetBlockByHash will return the block based on its hash func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options common.BlockQueryOptions) (*data.BlockApiResponse, error) { + scope := fmt.Sprintf("%s:shardID=%d", blockScope, shardID) + if cached := getObjectFromCacheWithHash[*data.BlockApiResponse](bp.cache, scope, hash, options); cached != nil { + return cached, nil + } + observers, err := bp.getObserversOrFullHistoryNodes(shardID) if err != nil { return nil, err @@ -62,7 +77,6 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co response := data.BlockApiResponse{} for _, observer := range observers { - _, err := bp.proc.CallGetRestEndPoint(observer.Address, path, &response) if err != nil { log.Error("block request", "observer", observer.Address, "error", err.Error()) @@ -70,8 +84,9 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co } log.Info("block request", "shard id", observer.ShardId, "hash", hash, "observer", observer.Address) - return &response, nil + bp.cacheObject(&response, scope, options) + return &response, nil } return nil, WrapObserversError(response.Error) @@ -79,6 +94,11 @@ func (bp *BlockProcessor) GetBlockByHash(shardID uint32, hash string, options co // GetBlockByNonce will return the block based on the nonce func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options common.BlockQueryOptions) (*data.BlockApiResponse, error) { + scope := fmt.Sprintf("%s:shardID=%d", blockScope, shardID) + if cached := getObjectFromCacheWithNonce[*data.BlockApiResponse](bp.cache, scope, nonce, options); cached != nil { + return cached, nil + } + observers, err := bp.getObserversOrFullHistoryNodes(shardID) if err != nil { return nil, err @@ -88,7 +108,6 @@ func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options response := data.BlockApiResponse{} for _, observer := range observers { - _, err := bp.proc.CallGetRestEndPoint(observer.Address, path, &response) if err != nil { log.Error("block request", "observer", observer.Address, "error", err.Error()) @@ -96,8 +115,8 @@ func (bp *BlockProcessor) GetBlockByNonce(shardID uint32, nonce uint64, options } log.Info("block request", "shard id", observer.ShardId, "nonce", nonce, "observer", observer.Address) + bp.cacheObject(&response, scope, options) return &response, nil - } return nil, WrapObserversError(response.Error) @@ -114,6 +133,10 @@ func (bp *BlockProcessor) getObserversOrFullHistoryNodes(shardID uint32) ([]*dat // GetHyperBlockByHash returns the hyperblock by hash func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.HyperblockQueryOptions) (*data.HyperblockApiResponse, error) { + if cached := getObjectFromCacheWithHash[*data.HyperblockApiResponse](bp.cache, hyperBlockScope, hash, options); cached != nil { + return cached, nil + } + builder := &hyperblockBuilder{} blockQueryOptions := common.BlockQueryOptions{ @@ -136,7 +159,10 @@ func (bp *BlockProcessor) GetHyperBlockByHash(hash string, options common.Hyperb } hyperblock := builder.build(options.NotarizedAtSource) - return data.NewHyperblockApiResponse(hyperblock), nil + hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) + bp.cacheObject(hyperBlockRsp, hyperBlockScope, options) + + return hyperBlockRsp, nil } func (bp *BlockProcessor) addShardBlocks( @@ -181,6 +207,10 @@ func (bp *BlockProcessor) getAlteredAccountsIfNeeded(options common.HyperblockQu // GetHyperBlockByNonce returns the hyperblock by nonce func (bp *BlockProcessor) GetHyperBlockByNonce(nonce uint64, options common.HyperblockQueryOptions) (*data.HyperblockApiResponse, error) { + if cached := getObjectFromCacheWithNonce[*data.HyperblockApiResponse](bp.cache, hyperBlockScope, nonce, options); cached != nil { + return cached, nil + } + builder := &hyperblockBuilder{} blockQueryOptions := common.BlockQueryOptions{ @@ -203,7 +233,10 @@ func (bp *BlockProcessor) GetHyperBlockByNonce(nonce uint64, options common.Hype } hyperblock := builder.build(options.NotarizedAtSource) - return data.NewHyperblockApiResponse(hyperblock), nil + hyperBlockRsp := data.NewHyperblockApiResponse(hyperblock) + bp.cacheObject(hyperBlockRsp, hyperBlockScope, options) + + return hyperBlockRsp, nil } // GetInternalBlockByHash will return the internal block based on its hash diff --git a/process/blockProcessorCache.go b/process/blockProcessorCache.go new file mode 100644 index 00000000..73e1ab09 --- /dev/null +++ b/process/blockProcessorCache.go @@ -0,0 +1,110 @@ +package process + +import ( + "encoding/json" + "fmt" +) + +type cacheableBlock interface { + Hash() string + Nonce() uint64 +} + +// No error checks for this cache. +// These caching errors should never happen, and if they do, they should not be blocking + +func (bp *BlockProcessor) cacheObject(obj cacheableBlock, scope string, opts interface{}) { + objKey, err := makeObjKey(scope, obj.Hash(), opts) + if err != nil { + return + } + + hashLookupKey, err := makeHashCacheKey(scope, obj.Hash(), opts) + if err != nil { + return + } + + nonceLookupKey, err := makeNonceCacheKey(scope, obj.Nonce(), opts) + if err != nil { + return + } + + // Store object + _ = bp.cache.Put(objKey, obj, 0) + + // Store nonce + hash lookup keys + _ = bp.cache.Put(hashLookupKey, objKey, 0) + _ = bp.cache.Put(nonceLookupKey, objKey, 0) +} + +func makeObjKey(scope string, hash string, opts interface{}) ([]byte, error) { + optBytes, err := json.Marshal(opts) + if err != nil { + log.Error("makeObjKey", "error", err) + return nil, err + } + return []byte(fmt.Sprintf("%s:%s|opts:%s", scope, hash, string(optBytes))), nil +} + +func makeHashCacheKey(scope string, hash string, opts interface{}) ([]byte, error) { + optBytes, err := json.Marshal(opts) + if err != nil { + log.Error("makeHashCacheKey", "error", err) + return nil, err + } + return []byte(fmt.Sprintf("%s:hash:%s|opts:%s", scope, hash, string(optBytes))), nil +} + +func makeNonceCacheKey(scope string, nonce uint64, opts interface{}) ([]byte, error) { + optBytes, err := json.Marshal(opts) + if err != nil { + log.Error("makeNonceCacheKey", "error", err) + return nil, err + } + return []byte(fmt.Sprintf("%s:nonce:%d|opts:%s", scope, nonce, string(optBytes))), nil +} + +func getObjectFromCacheWithHash[T cacheableBlock](c TimedCache, scope string, hash string, opts interface{}) T { + var nilRet T + hashKey, err := makeHashCacheKey(scope, hash, opts) + if err != nil { + return nilRet + } + + return getObjFromCache[T](c, hashKey) +} + +func getObjectFromCacheWithNonce[T cacheableBlock](c TimedCache, scope string, nonce uint64, opts interface{}) T { + var nilRet T + nonceKey, err := makeNonceCacheKey(scope, nonce, opts) + if err != nil { + return nilRet + } + + return getObjFromCache[T](c, nonceKey) +} + +func getObjFromCache[T cacheableBlock](c TimedCache, lookUpKey []byte) T { + var retObj T + + key, _ := c.Get(lookUpKey) + if key == nil { + return retObj + } + + keyBytes, ok := key.([]byte) + if !ok { + return retObj + } + + val, ok := c.Get(keyBytes) + if !ok { + return retObj + } + + result, ok := val.(T) + if !ok { + return retObj + } + return result +} diff --git a/process/blockProcessorCache_test.go b/process/blockProcessorCache_test.go new file mode 100644 index 00000000..7b9af3c8 --- /dev/null +++ b/process/blockProcessorCache_test.go @@ -0,0 +1,219 @@ +package process + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/multiversx/mx-chain-core-go/data/api" + "github.com/multiversx/mx-chain-proxy-go/common" + "github.com/multiversx/mx-chain-proxy-go/data" + facadeMock "github.com/multiversx/mx-chain-proxy-go/facade/mock" + "github.com/multiversx/mx-chain-proxy-go/process/mock" + "github.com/stretchr/testify/require" +) + +func getObjectFromCache[T cacheableBlock](c TimedCache, scope string, hash string, nonce *uint64, opts interface{}) T { + if hash != "" { + hashKey, _ := makeHashCacheKey(scope, hash, opts) + return getObjFromCache[T](c, hashKey) + } + + nonceKey, _ := makeNonceCacheKey(scope, *nonce, opts) + return getObjFromCache[T](c, nonceKey) +} + +func TestBlockProcessorCache(t *testing.T) { + t.Parallel() + + mockCache := facadeMock.NewTimedCacheMock() + bp, _ := NewBlockProcessor(&mock.ProcessorStub{}, mockCache) + + nonceBlock1 := uint64(1) + nonceBlock2 := uint64(2) + hashBlock1 := "hashBlock1" + hashBlock2 := "hashBlock2" + scope1 := "block:shard=1" + scope2 := "block:shard=2" + opts1 := common.BlockQueryOptions{WithTransactions: true} + opts2 := common.BlockQueryOptions{WithLogs: true} + + blockApi1 := &data.BlockApiResponse{ + Data: data.BlockApiResponsePayload{ + Block: api.Block{ + Nonce: nonceBlock1, + Hash: hashBlock1, + }, + }, + } + blockApi2 := &data.BlockApiResponse{ + Data: data.BlockApiResponsePayload{ + Block: api.Block{ + Nonce: nonceBlock2, + Hash: hashBlock2, + }, + }, + } + + // Some basic checks that the cache is empty + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts1, + )) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + "", + &nonceBlock1, + opts1, + )) + + // Cache scope1:blockApi1:opts1 + bp.cacheObject(blockApi1, scope1, opts1) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, // wrong scope + hashBlock1, + nil, + opts1, + )) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, + nil, + opts2, // wrong opts + )) + + require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + hashBlock1, // found cached blockApi1 object by hash + nil, + opts1, + )) + require.Equal(t, blockApi1, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope1, + "", + &nonceBlock1, // found cached blockApi1 object by nonce + opts1, + )) + + // Cache scope2:blockApi2:opts1 + bp.cacheObject(blockApi2, scope2, opts2) + require.Nil(t, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + hashBlock2, + nil, + opts1, + )) + require.Equal(t, blockApi2, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + hashBlock2, // found cached blockApi2 object by hash + nil, + opts2, + )) + require.Equal(t, blockApi2, getObjectFromCache[*data.BlockApiResponse]( + bp.cache, + scope2, + "", + &nonceBlock2, // found cached blockApi2 object by nonce + opts2, + )) + + scopeHyperBlock := "hyperBlock" + hyperBlockNonce := uint64(4) + hyperBlockHash := "hyperBlockHash" + hyperBlock := &data.HyperblockApiResponse{ + Data: data.HyperblockApiResponsePayload{ + Hyperblock: api.Hyperblock{ + Hash: hyperBlockHash, + Nonce: hyperBlockNonce, + }, + }, + } + + // Cache same hyperBlock object from two different routes: + // 1. scopeHyperBlock:hyperBlock:opts1 + // 2. scopeHyperBlock:hyperBlock:opts2 + bp.cacheObject(hyperBlock, scopeHyperBlock, opts1) + bp.cacheObject(hyperBlock, scopeHyperBlock, opts2) + + // found cached hyperBlock object by hyperBlockHash + opts2 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + hyperBlockHash, + nil, + opts2, + )) + // found cached hyperBlock object by hyperBlockNonce + opts2 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + "", + &hyperBlockNonce, + opts2, + )) + // found cached hyperBlock object by hyperBlockHash + opts1 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + hyperBlockHash, + nil, + opts1, + )) + // found cached hyperBlock object by hyperBlockNonce + opts1 + require.Equal(t, hyperBlock, getObjectFromCache[*data.HyperblockApiResponse]( + bp.cache, + scopeHyperBlock, + "", + &hyperBlockNonce, + opts1, + )) + + opts1Str, _ := json.Marshal(&opts1) + opts2Str, _ := json.Marshal(&opts2) + require.Len(t, mockCache.Cache, 12) + + expectedObjKeys := []string{ + fmt.Sprintf("%s:%s|opts:%s", scope1, hashBlock1, opts1Str), // blockApi1 + fmt.Sprintf("%s:%s|opts:%s", scope2, hashBlock2, opts2Str), // blockApi2 + fmt.Sprintf("%s:%s|opts:%s", scopeHyperBlock, hyperBlockHash, opts1Str), // hyperBlock + fmt.Sprintf("%s:%s|opts:%s", scopeHyperBlock, hyperBlockHash, opts2Str), // hyperBlock + } + + require.Equal(t, mockCache.Cache[expectedObjKeys[0]], blockApi1) + require.Equal(t, mockCache.Cache[expectedObjKeys[1]], blockApi2) + require.Equal(t, mockCache.Cache[expectedObjKeys[2]], hyperBlock) + require.Equal(t, mockCache.Cache[expectedObjKeys[3]], hyperBlock) + + expectedLookUpKeys := []string{ + fmt.Sprintf("%s:nonce:%d|opts:%s", scope1, nonceBlock1, string(opts1Str)), // blockApi1 + fmt.Sprintf("%s:hash:%s|opts:%s", scope1, hashBlock1, string(opts1Str)), // blockApi1 + + fmt.Sprintf("%s:nonce:%d|opts:%s", scope2, nonceBlock2, string(opts2Str)), // blockApi2 + fmt.Sprintf("%s:hash:%s|opts:%s", scope2, hashBlock2, string(opts2Str)), // blockApi2 + + fmt.Sprintf("%s:nonce:%d|opts:%s", scopeHyperBlock, hyperBlockNonce, string(opts1Str)), // hyperBlock + fmt.Sprintf("%s:hash:%s|opts:%s", scopeHyperBlock, hyperBlockHash, string(opts1Str)), // hyperBlock + + fmt.Sprintf("%s:nonce:%d|opts:%s", scopeHyperBlock, hyperBlockNonce, string(opts2Str)), // hyperBlock + fmt.Sprintf("%s:hash:%s|opts:%s", scopeHyperBlock, hyperBlockHash, string(opts2Str)), // hyperBlock + } + + require.Equal(t, mockCache.Cache[expectedLookUpKeys[0]], []byte(expectedObjKeys[0])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[1]], []byte(expectedObjKeys[0])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[2]], []byte(expectedObjKeys[1])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[3]], []byte(expectedObjKeys[1])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[4]], []byte(expectedObjKeys[2])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[5]], []byte(expectedObjKeys[2])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[6]], []byte(expectedObjKeys[3])) + require.Equal(t, mockCache.Cache[expectedLookUpKeys[7]], []byte(expectedObjKeys[3])) +} diff --git a/process/blockProcessor_test.go b/process/blockProcessor_test.go index e9740d83..6dbabfed 100644 --- a/process/blockProcessor_test.go +++ b/process/blockProcessor_test.go @@ -12,6 +12,7 @@ import ( "github.com/multiversx/mx-chain-core-go/data/transaction" "github.com/multiversx/mx-chain-proxy-go/common" "github.com/multiversx/mx-chain-proxy-go/data" + facadeMock "github.com/multiversx/mx-chain-proxy-go/facade/mock" "github.com/multiversx/mx-chain-proxy-go/process" "github.com/multiversx/mx-chain-proxy-go/process/mock" "github.com/stretchr/testify/assert" @@ -21,15 +22,23 @@ import ( func TestNewBlockProcessor_NilProcessorShouldErr(t *testing.T) { t.Parallel() - bp, err := process.NewBlockProcessor(nil) + bp, err := process.NewBlockProcessor(nil, &facadeMock.TimedCacheStub{}) require.Nil(t, bp) require.Equal(t, process.ErrNilCoreProcessor, err) } +func TestNewBlockProcessor_NilTimedCache(t *testing.T) { + t.Parallel() + + bp, err := process.NewBlockProcessor(&mock.ProcessorStub{}, nil) + require.Nil(t, bp) + require.Equal(t, process.ErrNilTimedCache, err) +} + func TestNewBlockProcessor_ShouldWork(t *testing.T) { t.Parallel() - bp, err := process.NewBlockProcessor(&mock.ProcessorStub{}) + bp, err := process.NewBlockProcessor(&mock.ProcessorStub{}, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) require.NoError(t, err) } @@ -51,7 +60,7 @@ func TestBlockProcessor_GetBlockByHashShouldGetFullHistoryNodes(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -77,7 +86,7 @@ func TestBlockProcessor_GetBlockByHashShouldGetObservers(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -99,7 +108,7 @@ func TestBlockProcessor_GetBlockByHashNoFullNodesOrObserversShouldErr(t *testing }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -120,7 +129,7 @@ func TestBlockProcessor_GetBlockByHashCallGetFailsShouldErr(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -143,7 +152,7 @@ func TestBlockProcessor_GetBlockByHashShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{}) @@ -171,7 +180,7 @@ func TestBlockProcessor_GetBlockByHashShouldWorkAndIncludeAlsoTxs(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByHash(0, "hash", common.BlockQueryOptions{WithTransactions: true}) @@ -200,7 +209,7 @@ func TestBlockProcessor_GetBlockByNonceShouldGetFullHistoryNodes(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByNonce(0, 0, common.BlockQueryOptions{}) @@ -226,7 +235,7 @@ func TestBlockProcessor_GetBlockByNonceShouldGetObservers(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetBlockByNonce(0, 1, common.BlockQueryOptions{}) @@ -248,7 +257,7 @@ func TestBlockProcessor_GetBlockByNonceNoFullNodesOrObserversShouldErr(t *testin }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, 1, common.BlockQueryOptions{}) @@ -269,7 +278,7 @@ func TestBlockProcessor_GetBlockByNonceCallGetFailsShouldErr(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, 0, common.BlockQueryOptions{}) @@ -292,7 +301,7 @@ func TestBlockProcessor_GetBlockByNonceShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, nonce, common.BlockQueryOptions{}) @@ -320,7 +329,7 @@ func TestBlockProcessor_GetBlockByNonceShouldWorkAndIncludeAlsoTxs(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetBlockByNonce(0, 3, common.BlockQueryOptions{WithTransactions: true}) @@ -359,7 +368,7 @@ func TestBlockProcessor_GetHyperBlock(t *testing.T) { }, } - processor, err := process.NewBlockProcessor(proc) + processor, err := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.Nil(t, err) require.NotNil(t, processor) @@ -391,7 +400,7 @@ func TestBlockProcessor_GetInternalBlockByNonceInvalidOutputFormat_ShouldFail(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalBlockByNonce(0, 0, 2) @@ -416,7 +425,7 @@ func TestBlockProcessor_GetInternalBlockByNonceShouldGetFullHistoryNodes(t *test }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByNonce(0, 0, common.Internal) @@ -442,7 +451,7 @@ func TestBlockProcessor_GetInternalBlockByNonceShouldGetObservers(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByNonce(0, 1, common.Internal) @@ -464,7 +473,7 @@ func TestBlockProcessor_GetInternalBlockByNonceNoFullNodesOrObserversShouldErr(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByNonce(0, 1, common.Internal) @@ -485,7 +494,7 @@ func TestBlockProcessor_GetInternalBlockByNonceCallGetFailsShouldErr(t *testing. }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByNonce(0, 0, common.Internal) @@ -514,7 +523,7 @@ func TestBlockProcessor_GetInternalBlockByNonceShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByNonce(0, nonce, common.Internal) @@ -539,7 +548,7 @@ func TestBlockProcessor_GetInternalBlockByHashInvalidOutputFormat_ShouldFail(t * }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalBlockByHash(0, "aaaa", 2) @@ -564,7 +573,7 @@ func TestBlockProcessor_GetInternalBlockByHashShouldGetFullHistoryNodes(t *testi }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -590,7 +599,7 @@ func TestBlockProcessor_GetInternalBlockByHashShouldGetObservers(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -612,7 +621,7 @@ func TestBlockProcessor_GetInternalBlockByHashNoFullNodesOrObserversShouldErr(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -633,7 +642,7 @@ func TestBlockProcessor_GetInternalBlockByHashCallGetFailsShouldErr(t *testing.T }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -661,7 +670,7 @@ func TestBlockProcessor_GetInternalBlockByHashShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalBlockByHash(0, "aaaa", common.Internal) @@ -686,7 +695,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashInvalidOutputFormat_ShouldFail }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, 2) @@ -711,7 +720,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashShouldGetFullHistoryNodes(t *t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -737,7 +746,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashShouldGetObservers(t *testing. }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -759,7 +768,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashNoFullNodesOrObserversShouldEr }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -780,7 +789,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashCallGetFailsShouldErr(t *testi }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -808,7 +817,7 @@ func TestBlockProcessor_GetInternalMiniBlockByHashShouldWork(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalMiniBlockByHash(0, "aaaa", 1, common.Internal) @@ -833,7 +842,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockInvalidOutputFormat_Shou }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) blk, err := bp.GetInternalStartOfEpochMetaBlock(0, 2) @@ -858,7 +867,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockShouldGetFullHistoryNode }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -884,7 +893,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockShouldGetObservers(t *te }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) _, _ = bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -906,7 +915,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockNoFullNodesOrObserversSh }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -928,7 +937,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockCallGetFailsShouldErr(t }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochMetaBlock(0, common.Internal) @@ -956,7 +965,7 @@ func TestBlockProcessor_GetInternalStartOfEpochMetaBlockShouldWork(t *testing.T) }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochMetaBlock(1, common.Internal) @@ -986,7 +995,7 @@ func TestBlockProcessor_GetAlteredAccountsByNonce(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByNonce(requestedShardID, 4, common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, expectedErr, err) require.Nil(t, res) @@ -1015,7 +1024,7 @@ func TestBlockProcessor_GetAlteredAccountsByNonce(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByNonce(requestedShardID, 4, common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, 2, callGetEndpointCt) require.True(t, errors.Is(err, process.ErrSendingRequest)) @@ -1044,7 +1053,7 @@ func TestBlockProcessor_GetAlteredAccountsByNonce(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByNonce(requestedShardID, 4, common.GetAlteredAccountsForBlockOptions{}) require.Nil(t, err) require.Equal(t, &data.AlteredAccountsApiResponse{ @@ -1073,7 +1082,7 @@ func TestBlockProcessor_GetAlteredAccountsByHash(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByHash(requestedShardID, "hash", common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, expectedErr, err) require.Nil(t, res) @@ -1102,7 +1111,7 @@ func TestBlockProcessor_GetAlteredAccountsByHash(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByHash(requestedShardID, "hash", common.GetAlteredAccountsForBlockOptions{}) require.Equal(t, 2, callGetEndpointCt) require.True(t, errors.Is(err, process.ErrSendingRequest)) @@ -1131,7 +1140,7 @@ func TestBlockProcessor_GetAlteredAccountsByHash(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetAlteredAccountsByHash(requestedShardID, "hash", common.GetAlteredAccountsForBlockOptions{}) require.Nil(t, err) require.Equal(t, &data.AlteredAccountsApiResponse{ @@ -1226,7 +1235,7 @@ func TestBlockProcessor_GetHyperBlockByNonceWithAlteredAccounts(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetHyperBlockByNonce(4, common.HyperblockQueryOptions{WithAlteredAccounts: true}) require.Nil(t, err) @@ -1343,7 +1352,7 @@ func TestBlockProcessor_GetHyperBlockByHashWithAlteredAccounts(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) res, err := bp.GetHyperBlockByHash("abcdef", common.HyperblockQueryOptions{WithAlteredAccounts: true}) require.Nil(t, err) @@ -1400,7 +1409,7 @@ func TestBlockProcessor_GetInternalStartOfEpochValidatorsInfo(t *testing.T) { }, } - bp, _ := process.NewBlockProcessor(proc) + bp, _ := process.NewBlockProcessor(proc, &facadeMock.TimedCacheStub{}) require.NotNil(t, bp) res, err := bp.GetInternalStartOfEpochValidatorsInfo(1) @@ -1408,3 +1417,132 @@ func TestBlockProcessor_GetInternalStartOfEpochValidatorsInfo(t *testing.T) { require.NotNil(t, res) require.Equal(t, expectedData, res.Data) } + +func TestBlockProcessor_GetCachedHyperBlocks(t *testing.T) { + t.Parallel() + + numGetBlockCalled := 0 + hash := "hash" + nonce := uint64(42) + shardBlockResponse := data.BlockApiResponsePayload{Block: api.Block{Nonce: 41}} + metaBlockResponse := data.BlockApiResponsePayload{ + Block: api.Block{ + Hash: "hash", + Nonce: 42, + NotarizedBlocks: []*api.NotarizedBlock{ + {Shard: 0, Nonce: 41}, + {Shard: 1, Nonce: 41}, + {Shard: 2, Nonce: 41}, + }}, + } + proc := &mock.ProcessorStub{ + GetFullHistoryNodesCalled: func(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { + return []*data.NodeData{{ShardId: shardId, Address: fmt.Sprintf("observer-%d", shardId)}}, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + numGetBlockCalled++ + + response := value.(*data.BlockApiResponse) + if strings.Contains(address, "4294967295") { + response.Data = metaBlockResponse + } else { + response.Data = shardBlockResponse + } + + return 200, nil + }, + } + + processor, err := process.NewBlockProcessor(proc, facadeMock.NewTimedCacheMock()) + require.Nil(t, err) + require.NotNil(t, processor) + + expectedHyperBlock := &data.HyperblockApiResponse{ + Code: data.ReturnCodeSuccess, + Data: data.HyperblockApiResponsePayload{ + Hyperblock: api.Hyperblock{ + Nonce: 42, + Hash: hash, + ShardBlocks: []*api.NotarizedBlock{ + {Nonce: 41, AlteredAccounts: make([]*alteredAccount.AlteredAccount, 0), MiniBlockHashes: make([]string, 0)}, + {Nonce: 41, AlteredAccounts: make([]*alteredAccount.AlteredAccount, 0), MiniBlockHashes: make([]string, 0)}, + {Nonce: 41, AlteredAccounts: make([]*alteredAccount.AlteredAccount, 0), MiniBlockHashes: make([]string, 0)}, + }, + Transactions: make([]*transaction.ApiTransactionResult, 0), + }, + }, + } + + numGetBlockCalled = 0 + response, err := processor.GetHyperBlockByHash(hash, common.HyperblockQueryOptions{}) + require.Nil(t, err) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 4, numGetBlockCalled, "get block should be called for metablock and for all notarized shard blocks") + + response, err = processor.GetHyperBlockByHash(hash, common.HyperblockQueryOptions{}) + require.Nil(t, err) + require.NotNil(t, response) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 4, numGetBlockCalled) + + response, err = processor.GetHyperBlockByNonce(nonce, common.HyperblockQueryOptions{}) + require.Nil(t, err) + require.NotNil(t, response) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 4, numGetBlockCalled) + + // Different query options, should not take block from cache + response, err = processor.GetHyperBlockByNonce(nonce, common.HyperblockQueryOptions{WithLogs: true}) + require.Nil(t, err) + require.NotNil(t, response) + require.Equal(t, expectedHyperBlock, response) + require.Equal(t, 8, numGetBlockCalled) +} + +func TestBlockProcessor_GetCachedBlocks(t *testing.T) { + t.Parallel() + + nonce := uint64(42) + hash := "hash" + blockApiResponse := data.BlockApiResponsePayload{Block: api.Block{Nonce: nonce, Hash: hash}} + + numGetBlockCalled := 0 + proc := &mock.ProcessorStub{ + GetFullHistoryNodesCalled: func(shardId uint32, dataAvailability data.ObserverDataAvailabilityType) ([]*data.NodeData, error) { + return []*data.NodeData{{ShardId: shardId, Address: "addr"}}, nil + }, + CallGetRestEndPointCalled: func(address string, path string, value interface{}) (int, error) { + numGetBlockCalled++ + valResp := value.(*data.BlockApiResponse) + valResp.Data = blockApiResponse + return 200, nil + }, + } + + bp, _ := process.NewBlockProcessor(proc, facadeMock.NewTimedCacheMock()) + require.NotNil(t, bp) + + expectedBlock := &data.BlockApiResponse{ + Data: blockApiResponse, + } + res, err := bp.GetBlockByHash(0, hash, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 1, numGetBlockCalled) + + res, err = bp.GetBlockByHash(0, hash, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 1, numGetBlockCalled) + + res, err = bp.GetBlockByNonce(0, nonce, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 1, numGetBlockCalled) + + // Different shard id + res, err = bp.GetBlockByNonce(1, nonce, common.BlockQueryOptions{}) + require.Equal(t, expectedBlock, res) + require.Nil(t, err) + require.Equal(t, 2, numGetBlockCalled) +} diff --git a/process/cache/errors.go b/process/cache/errors.go index a06d6aca..7d9bca16 100644 --- a/process/cache/errors.go +++ b/process/cache/errors.go @@ -16,6 +16,3 @@ var ErrNilValidatorStatsToStoreInCache = errors.New("nil validator statistics to // ErrNilGenericApiResponseInCache signals that the generic api response stored in cache is nil var ErrNilGenericApiResponseInCache = errors.New("nil generic api response in cache") - -// ErrNilGenericApiResponseToStoreInCache signals that the provided generic api response is nil -var ErrNilGenericApiResponseToStoreInCache = errors.New("nil generic api response to store in cache") diff --git a/process/errors.go b/process/errors.go index 8060b7b4..3ef4a328 100644 --- a/process/errors.go +++ b/process/errors.go @@ -112,3 +112,6 @@ var ErrEmptyPubKey = errors.New("public key is empty") // ErrNilHttpClient signals that a nil http client has been provided var ErrNilHttpClient = errors.New("nil http client") + +// ErrNilTimedCache signals that a nil timed cache has been provided +var ErrNilTimedCache = errors.New("nil timed cache") diff --git a/process/interface.go b/process/interface.go index f424bdfc..3a27454a 100644 --- a/process/interface.go +++ b/process/interface.go @@ -85,3 +85,12 @@ type StatusMetricsProvider interface { type HttpClient interface { Do(req *http.Request) (*http.Response, error) } + +// TimedCache provides time-based eviction: each entry has a TTL and the cache +// periodically sweeps expired items automatically. +type TimedCache interface { + Put(key []byte, value interface{}, _ int) (evicted bool) + Get(key []byte) (value interface{}, ok bool) + Close() error + IsInterfaceNil() bool +}