Skip to content

Commit

Permalink
Add is_cache_enabled boolean flag
Browse files Browse the repository at this point in the history
  • Loading branch information
evgeniy-scherbina committed Oct 19, 2023
1 parent 667fa36 commit 9ff12c4
Show file tree
Hide file tree
Showing 10 changed files with 74 additions and 27 deletions.
12 changes: 7 additions & 5 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -89,16 +89,18 @@ METRIC_PARTITIONING_ROUTINE_DELAY_FIRST_RUN_SECONDS=10
METRIC_PARTITIONINING_PREFILL_PERIOD_DAYS=7
# Used by `ready` script to ensure metric partitions have been created.
MINIMUM_REQUIRED_PARTITIONS=30
# RedisEndpointURL is an url of redis
# CACHE_ENABLED specifies if cache should be enabled. By default cache is disabled.
CACHE_ENABLED=true
# REDIS_ENDPOINT_URL is an url of redis
REDIS_ENDPOINT_URL=redis:6379
REDIS_PASSWORD=
# TTL for cached evm requests
# TTL should be specified in seconds
# CACHE_TTL is a TTL for cached evm requests
# CACHE_TTL should be specified in seconds
CACHE_TTL=600
# CachePrefix is used as prefix for any key in the cache, key has such structure:
# CACHE_PREFIX is used as prefix for any key in the cache, key has such structure:
# <cache_prefix>:evm-request:<method_name>:sha256:<sha256(body)>
# Possible values are testnet, mainnet, etc...
# CachePrefix must not contain colon symbol
# CACHE_PREFIX must not contain colon symbol
CACHE_PREFIX=local-chain

##### Database Config
Expand Down
3 changes: 3 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type Config struct {
MetricPartitioningRoutineInterval time.Duration
MetricPartitioningRoutineDelayFirstRun time.Duration
MetricPartitioningPrefillPeriodDays int
CacheEnabled bool
RedisEndpointURL string
RedisPassword string
CacheTTL time.Duration
Expand Down Expand Up @@ -83,6 +84,7 @@ const (
DEFAULT_DATABASE_READ_TIMEOUT_SECONDS = 60
DATABASE_WRITE_TIMEOUT_SECONDS_ENVIRONMENT_KEY = "DATABASE_WRITE_TIMEOUT_SECONDS"
DEFAULT_DATABASE_WRITE_TIMEOUT_SECONDS = 10
CACHE_ENABLED_ENVIRONMENT_KEY = "CACHE_ENABLED"
REDIS_ENDPOINT_URL_ENVIRONMENT_KEY = "REDIS_ENDPOINT_URL"
REDIS_PASSWORD_ENVIRONMENT_KEY = "REDIS_PASSWORD"
CACHE_TTL_ENVIRONMENT_KEY = "CACHE_TTL"
Expand Down Expand Up @@ -212,6 +214,7 @@ func ReadConfig() Config {
MetricPartitioningRoutineInterval: time.Duration(time.Duration(EnvOrDefaultInt(METRIC_PARTITIONING_ROUTINE_INTERVAL_SECONDS_ENVIRONMENT_KEY, DEFAULT_METRIC_PARTITIONING_ROUTINE_INTERVAL_SECONDS)) * time.Second),
MetricPartitioningRoutineDelayFirstRun: time.Duration(time.Duration(EnvOrDefaultInt(METRIC_PARTITIONING_ROUTINE_DELAY_FIRST_RUN_SECONDS_ENVIRONMENT_KEY, DEFAULT_METRIC_PARTITIONING_ROUTINE_DELAY_FIRST_RUN_SECONDS)) * time.Second),
MetricPartitioningPrefillPeriodDays: EnvOrDefaultInt(METRIC_PARTITIONING_PREFILL_PERIOD_DAYS_ENVIRONMENT_KEY, DEFAULT_METRIC_PARTITIONING_PREFILL_PERIOD_DAYS),
CacheEnabled: EnvOrDefaultBool(CACHE_ENABLED_ENVIRONMENT_KEY, false),
RedisEndpointURL: os.Getenv(REDIS_ENDPOINT_URL_ENVIRONMENT_KEY),
RedisPassword: os.Getenv(REDIS_PASSWORD_ENVIRONMENT_KEY),
CacheTTL: time.Duration(EnvOrDefaultInt(CACHE_TTL_ENVIRONMENT_KEY, 0)) * time.Second,
Expand Down
8 changes: 4 additions & 4 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ func TestE2ETestProxyTracksBlockNumberForMethodsWithBlockHashParam(t *testing.T)
}
}

func TestE2eTestCachingMdwWithBlockNumberParam(t *testing.T) {
func TestE2ETestCachingMdwWithBlockNumberParam(t *testing.T) {
// create api and database clients
client, err := ethclient.Dial(proxyServiceURL)
if err != nil {
Expand Down Expand Up @@ -513,7 +513,7 @@ func TestE2eTestCachingMdwWithBlockNumberParam(t *testing.T) {
} {
t.Run(tc.desc, func(t *testing.T) {
// test cache MISS and cache HIT scenarios for specified method
// check corresponding values in cachemdw.CacheMissHeaderValue HTTP header
// check corresponding values in cachemdw.CacheHeaderKey HTTP header
// check that cached and non-cached responses are equal

// eth_getBlockByNumber - cache MISS
Expand Down Expand Up @@ -557,7 +557,7 @@ func TestE2eTestCachingMdwWithBlockNumberParam(t *testing.T) {
cleanUpRedis(t, redisClient)
}

func TestE2eTestCachingMdwWithBlockNumberParam_EmptyResult(t *testing.T) {
func TestE2ETestCachingMdwWithBlockNumberParam_EmptyResult(t *testing.T) {
testRandomAddressHex := "0x6767114FFAA17C6439D7AEA480738B982CE63A02"
testAddress := common.HexToAddress(testRandomAddressHex)

Expand Down Expand Up @@ -590,7 +590,7 @@ func TestE2eTestCachingMdwWithBlockNumberParam_EmptyResult(t *testing.T) {
} {
t.Run(tc.desc, func(t *testing.T) {
// both calls should lead to cache MISS scenario, because empty results aren't cached
// check corresponding values in cachemdw.CacheMissHeaderValue HTTP header
// check corresponding values in cachemdw.CacheHeaderKey HTTP header
// check that responses are equal

// eth_getBlockByNumber - cache MISS
Expand Down
5 changes: 4 additions & 1 deletion service/cachemdw/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ type ServiceCache struct {
cacheTTL time.Duration
decodedRequestContextKey any
// cachePrefix is used as prefix for any key in the cache
cachePrefix string
cachePrefix string
cacheEnabled bool

*logging.ServiceLogger
}
Expand All @@ -31,6 +32,7 @@ func NewServiceCache(
cacheTTL time.Duration,
decodedRequestContextKey any,
cachePrefix string,
cacheEnabled bool,
logger *logging.ServiceLogger,
) *ServiceCache {
return &ServiceCache{
Expand All @@ -39,6 +41,7 @@ func NewServiceCache(
cacheTTL: cacheTTL,
decodedRequestContextKey: decodedRequestContextKey,
cachePrefix: cachePrefix,
cacheEnabled: cacheEnabled,
ServiceLogger: logger,
}
}
Expand Down
10 changes: 9 additions & 1 deletion service/cachemdw/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,15 @@ func TestUnitTestCacheQueryResponse(t *testing.T) {
cacheTTL := time.Hour
ctxb := context.Background()

serviceCache := cachemdw.NewServiceCache(inMemoryCache, blockGetter, cacheTTL, service.DecodedRequestContextKey, defaultCachePrefixString, &logger)
serviceCache := cachemdw.NewServiceCache(
inMemoryCache,
blockGetter,
cacheTTL,
service.DecodedRequestContextKey,
defaultCachePrefixString,
true,
&logger,
)

req := mkEVMRPCRequestEnvelope(defaultBlockNumber)
resp, err := serviceCache.GetCachedQueryResponse(ctxb, req)
Expand Down
6 changes: 6 additions & 0 deletions service/cachemdw/caching_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ func (c *ServiceCache) CachingMiddleware(
next http.Handler,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// if cache is not enabled - do nothing and forward to next middleware
if !c.cacheEnabled {
next.ServeHTTP(w, r)
return
}

// if we can't get decoded request then forward to next middleware
req := r.Context().Value(c.decodedRequestContextKey)
decodedReq, ok := (req).(*decode.EVMRPCRequestEnvelope)
Expand Down
6 changes: 6 additions & 0 deletions service/cachemdw/is_cached_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ func (c *ServiceCache) IsCachedMiddleware(
next http.Handler,
) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// if cache is not enabled - do nothing and forward to next middleware
if !c.cacheEnabled {
next.ServeHTTP(w, r)
return
}

uncachedContext := context.WithValue(r.Context(), CachedContextKey, false)
cachedContext := context.WithValue(r.Context(), CachedContextKey, true)

Expand Down
19 changes: 17 additions & 2 deletions service/cachemdw/middleware_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,23 @@ import (
"github.com/kava-labs/kava-proxy-service/service/cachemdw"
)

func TestE2ETestServiceCacheMiddleware(t *testing.T) {
func TestUnitTestServiceCacheMiddleware(t *testing.T) {
logger, err := logging.New("TRACE")
require.NoError(t, err)

inMemoryCache := cache.NewInMemoryCache()
blockGetter := NewMockEVMBlockGetter()
cacheTTL := time.Duration(0) // TTL: no expiry

serviceCache := cachemdw.NewServiceCache(inMemoryCache, blockGetter, cacheTTL, service.DecodedRequestContextKey, defaultCachePrefixString, &logger)
serviceCache := cachemdw.NewServiceCache(
inMemoryCache,
blockGetter,
cacheTTL,
service.DecodedRequestContextKey,
defaultCachePrefixString,
true,
&logger,
)

emptyHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})
cachingMdw := serviceCache.CachingMiddleware(emptyHandler)
Expand All @@ -49,6 +57,9 @@ func TestE2ETestServiceCacheMiddleware(t *testing.T) {
})
isCachedMdw := serviceCache.IsCachedMiddleware(proxyHandler)

// test cache MISS and cache HIT scenarios for specified method
// check corresponding values in cachemdw.CacheHeaderKey HTTP header

t.Run("cache miss", func(t *testing.T) {
req := createTestHttpRequest(
t,
Expand Down Expand Up @@ -81,6 +92,10 @@ func TestE2ETestServiceCacheMiddleware(t *testing.T) {
require.Equal(t, http.StatusOK, resp.Code)
require.JSONEq(t, testEVMQueries[TestRequestEthBlockByNumberSpecific].ResponseBody, resp.Body.String())
require.Equal(t, cachemdw.CacheHitHeaderValue, resp.Header().Get(cachemdw.CacheHeaderKey))

cacheItems := inMemoryCache.GetAll(context.Background())
require.Len(t, cacheItems, 1)
require.Contains(t, cacheItems, "1:evm-request:eth_getBlockByNumber:sha256:bf79de57723b25b85391513b470ea6989e7c44dd9afc0c270ee961c9f12f578d")
})
}

Expand Down
31 changes: 17 additions & 14 deletions service/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -233,9 +233,9 @@ func createProxyRequestMiddleware(next http.Handler, config config.Config, servi
response := r.Context().Value(cachemdw.ResponseContextKey)
typedResponse, ok := response.([]byte)

// if request is cached and response is present in context - serve the request from the cache
// if cache is enabled, request is cached and response is present in context - serve the request from the cache
// otherwise proxy to the actual backend
if isCached && ok {
if config.CacheEnabled && isCached && ok {
serviceLogger.Logger.Trace().
Str("method", r.Method).
Str("url", r.URL.String()).
Expand Down Expand Up @@ -271,20 +271,23 @@ func createProxyRequestMiddleware(next http.Handler, config config.Config, servi
// extract the original hostname the request was sent to
requestHostnameContext := context.WithValue(originRoundtripLatencyContext, RequestHostnameContextKey, r.Host)

var bodyCopy bytes.Buffer
tee := io.TeeReader(lrw.body, &bodyCopy)
// read all body from reader into bodyBytes, and copy into bodyCopy
bodyBytes, err := io.ReadAll(tee)
if err != nil {
serviceLogger.Error().Err(err).Msg("can't read lrw.body")
}
enrichedContext := requestHostnameContext

// replace empty body reader with fresh copy
lrw.body = &bodyCopy
// set body in context
responseContext := context.WithValue(requestHostnameContext, cachemdw.ResponseContextKey, bodyBytes)
// if cache is enabled, update enrichedContext with cachemdw.ResponseContextKey -> bodyBytes key-value pair
if config.CacheEnabled {
var bodyCopy bytes.Buffer
tee := io.TeeReader(lrw.body, &bodyCopy)
// read all body from reader into bodyBytes, and copy into bodyCopy
bodyBytes, err := io.ReadAll(tee)
if err != nil {
serviceLogger.Error().Err(err).Msg("can't read lrw.body")
}

enrichedContext := responseContext
// replace empty body reader with fresh copy
lrw.body = &bodyCopy
// set body in context
enrichedContext = context.WithValue(enrichedContext, cachemdw.ResponseContextKey, bodyBytes)
}

// parse the remote address of the request for use below
remoteAddressParts := strings.Split(r.RemoteAddr, ":")
Expand Down
1 change: 1 addition & 0 deletions service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ func createServiceCache(
config.CacheTTL,
DecodedRequestContextKey,
config.CachePrefix,
config.CacheEnabled,
logger,
)

Expand Down

0 comments on commit 9ff12c4

Please sign in to comment.