diff --git a/clients/cache/inmemory.go b/clients/cache/inmemory.go new file mode 100644 index 0000000..437f935 --- /dev/null +++ b/clients/cache/inmemory.go @@ -0,0 +1,97 @@ +package cache + +import ( + "context" + "sync" + "time" +) + +// InMemoryCache is an in-memory implementation of the Cache interface. +type InMemoryCache struct { + data map[string]cacheItem + mutex sync.RWMutex +} + +// Ensure InMemoryCache implements the Cache interface. +var _ Cache = (*InMemoryCache)(nil) + +// cacheItem represents an item stored in the cache. +type cacheItem struct { + data []byte + expiration time.Time +} + +// NewInMemoryCache creates a new instance of InMemoryCache. +func NewInMemoryCache() *InMemoryCache { + return &InMemoryCache{ + data: make(map[string]cacheItem), + } +} + +// Set sets the value of a key in the cache. +func (c *InMemoryCache) Set( + ctx context.Context, + key string, + data []byte, + expiration time.Duration, +) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + expiry := time.Now().Add(expiration) + + if expiration == 0 { + // 100 years in the future to prevent expiry + expiry = time.Now().AddDate(100, 0, 0) + } + + c.data[key] = cacheItem{ + data: data, + expiration: expiry, + } + + return nil +} + +// Get retrieves the value of a key from the cache. +func (c *InMemoryCache) Get(ctx context.Context, key string) ([]byte, bool) { + c.mutex.RLock() + defer c.mutex.RUnlock() + + item, ok := c.data[key] + if !ok || time.Now().After(item.expiration) { + // Not a real ttl but just replicates it for fetching + delete(c.data, key) + + return nil, false + } + + return item.data, true +} + +// GetAll returns all the non-expired data in the cache. +func (c *InMemoryCache) GetAll(ctx context.Context) map[string][]byte { + c.mutex.RLock() + defer c.mutex.RUnlock() + + result := make(map[string][]byte) + + for key, item := range c.data { + if time.Now().After(item.expiration) { + delete(c.data, key) + } else { + result[key] = item.data + } + } + + return result +} + +// Delete removes a key from the cache. +func (c *InMemoryCache) Delete(ctx context.Context, key string) error { + c.mutex.Lock() + defer c.mutex.Unlock() + + delete(c.data, key) + return nil +} diff --git a/service/cachemdw/cache.go b/service/cachemdw/cache.go index fe7e3db..664ff42 100644 --- a/service/cachemdw/cache.go +++ b/service/cachemdw/cache.go @@ -59,7 +59,8 @@ func (c *ServiceCache) GetCachedQueryResponse( requestHost string, req *decode.EVMRPCRequestEnvelope, ) ([]byte, bool) { - chainID, found := c.GetCachedChainID(ctx, requestHost) + chainID, err := c.GetAndCacheChainID(ctx, requestHost) + found := err == nil // TODO(yevhenii): refactor if !found { return nil, false } diff --git a/service/cachemdw/cache_test.go b/service/cachemdw/cache_test.go index 378c7ee..3463efa 100644 --- a/service/cachemdw/cache_test.go +++ b/service/cachemdw/cache_test.go @@ -2,12 +2,15 @@ package cachemdw import ( "context" - "math/big" - "testing" - + "fmt" "github.com/ethereum/go-ethereum/common" ethctypes "github.com/ethereum/go-ethereum/core/types" + "github.com/kava-labs/kava-proxy-service/clients/cache" + "github.com/kava-labs/kava-proxy-service/service" "github.com/stretchr/testify/require" + "math/big" + "testing" + "time" "github.com/kava-labs/kava-proxy-service/decode" ) @@ -25,7 +28,7 @@ func (c *MockEVMClient) BlockByHash(ctx context.Context, hash common.Hash) (*eth } func (c *MockEVMClient) ChainID(ctx context.Context) (*big.Int, error) { - panic("not implemented") + return big.NewInt(1), nil } func TestUnitTestIsCacheable(t *testing.T) { @@ -63,3 +66,21 @@ func TestUnitTestIsCacheable(t *testing.T) { }) } } + +func TestUnitTestCacheQueryResponse(t *testing.T) { + inMemoryCache := cache.NewInMemoryCache() + evmClient := NewMockEVMClient() + cacheTTL := time.Hour + + serviceCache := NewServiceCache(inMemoryCache, evmClient, cacheTTL, service.DecodedRequestContextKey) + + req := decode.EVMRPCRequestEnvelope{ + JSONRPCVersion: "2.0", + ID: 1, + Method: "eth_getBalance", + Params: []interface{}{"0x1234", "42"}, + } + resp, err := serviceCache.GetCachedQueryResponse(context.Background(), "api.kava.io", &req) + + fmt.Printf("resp: %v, err: %v\n", resp, err) +}