Skip to content

Commit

Permalink
Temporary Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
evgeniy-scherbina committed Nov 1, 2023
1 parent db25858 commit 49eddd3
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 15 deletions.
24 changes: 18 additions & 6 deletions main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -480,9 +480,9 @@ func TestE2ETestCachingMdwWithBlockNumberParam(t *testing.T) {
// check that cached and non-cached responses are equal

// eth_getBlockByNumber - cache MISS
resp1 := mkJsonRpcRequest(t, proxyServiceURL, 1, tc.method, tc.params)
require.Equal(t, cachemdw.CacheMissHeaderValue, resp1.Header[cachemdw.CacheHeaderKey][0])
body1, err := io.ReadAll(resp1.Body)
cacheMissResp := mkJsonRpcRequest(t, proxyServiceURL, 1, tc.method, tc.params)
require.Equal(t, cachemdw.CacheMissHeaderValue, cacheMissResp.Header[cachemdw.CacheHeaderKey][0])
body1, err := io.ReadAll(cacheMissResp.Body)
require.NoError(t, err)
err = checkJsonRpcErr(body1)
require.NoError(t, err)
Expand All @@ -491,16 +491,18 @@ func TestE2ETestCachingMdwWithBlockNumberParam(t *testing.T) {
containsKey(t, redisClient, expectedKey)

// eth_getBlockByNumber - cache HIT
resp2 := mkJsonRpcRequest(t, proxyServiceURL, 1, tc.method, tc.params)
require.Equal(t, cachemdw.CacheHitHeaderValue, resp2.Header[cachemdw.CacheHeaderKey][0])
body2, err := io.ReadAll(resp2.Body)
cacheHitResp := mkJsonRpcRequest(t, proxyServiceURL, 1, tc.method, tc.params)
require.Equal(t, cachemdw.CacheHitHeaderValue, cacheHitResp.Header[cachemdw.CacheHeaderKey][0])
body2, err := io.ReadAll(cacheHitResp.Body)
require.NoError(t, err)
err = checkJsonRpcErr(body2)
require.NoError(t, err)
expectKeysNum(t, redisClient, tc.keysNum)
containsKey(t, redisClient, expectedKey)

require.JSONEq(t, string(body1), string(body2), "blocks should be the same")

equalHeaders(t, cacheMissResp.Header, cacheHitResp.Header)
})
}

Expand All @@ -526,6 +528,16 @@ func TestE2ETestCachingMdwWithBlockNumberParam(t *testing.T) {
cleanUpRedis(t, redisClient)
}

func equalHeaders(t *testing.T, cacheMissHeaders, cacheHitHeaders http.Header) {
for headerName, headerValues := range cacheMissHeaders {
if headerName == cachemdw.CacheHeaderKey || headerName == "Content-Length" {
continue
}

require.Equal(t, headerValues, cacheHitHeaders[headerName])
}
}

func TestE2ETestCachingMdwWithBlockNumberParam_Metrics(t *testing.T) {
client, err := ethclient.Dial(proxyServiceURL)
require.NoError(t, err)
Expand Down
32 changes: 27 additions & 5 deletions service/cachemdw/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ func IsCacheable(
func (c *ServiceCache) GetCachedQueryResponse(
ctx context.Context,
req *decode.EVMRPCRequestEnvelope,
) ([]byte, error) {
) (*QueryResponse, error) {
// if request isn't cacheable - there is no point to try to get it from cache so exit early with an error
cacheable := IsCacheable(c.ServiceLogger, req)
if !cacheable {
Expand All @@ -102,24 +102,36 @@ func (c *ServiceCache) GetCachedQueryResponse(
}

// get JSON-RPC response's result from the cache
result, err := c.cacheClient.Get(ctx, key)
queryResponseInJSON, err := c.cacheClient.Get(ctx, key)
if err != nil {
return nil, err
}

var queryResponse QueryResponse
if err := json.Unmarshal(queryResponseInJSON, &queryResponse); err != nil {
return nil, err
}

// JSON-RPC response's ID and Version should match JSON-RPC request
id := strconv.Itoa(int(req.ID))
response := JsonRpcResponse{
Version: req.JSONRPCVersion,
ID: []byte(id),
Result: result,
Result: queryResponse.ResponseInBytes,
}
responseInJSON, err := json.Marshal(response)
if err != nil {
return nil, err
}

return responseInJSON, nil
queryResponse.ResponseInBytes = responseInJSON

return &queryResponse, nil
}

type QueryResponse struct {
ResponseInBytes []byte `json:"response_in_bytes"`
HeaderMap map[string][]string `json:"header_map"`
}

// CacheQueryResponse calculates cache key for request and then saves response to the cache.
Expand All @@ -130,6 +142,7 @@ func (c *ServiceCache) CacheQueryResponse(
ctx context.Context,
req *decode.EVMRPCRequestEnvelope,
responseInBytes []byte,
headerMap map[string][]string,
) error {
// don't cache uncacheable requests
if !IsCacheable(c.ServiceLogger, req) {
Expand All @@ -150,7 +163,16 @@ func (c *ServiceCache) CacheQueryResponse(
return err
}

return c.cacheClient.Set(ctx, key, response.Result, c.cacheTTL, c.cacheIndefinitely)
queryResponse := &QueryResponse{
ResponseInBytes: response.Result,
HeaderMap: headerMap,
}
queryResponseInJSON, err := json.Marshal(queryResponse)
if err != nil {
return err
}

return c.cacheClient.Set(ctx, key, queryResponseInJSON, c.cacheTTL, c.cacheIndefinitely)
}

func (c *ServiceCache) Healthcheck(ctx context.Context) error {
Expand Down
4 changes: 2 additions & 2 deletions service/cachemdw/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,12 +90,12 @@ func TestUnitTestCacheQueryResponse(t *testing.T) {
require.Equal(t, cache.ErrNotFound, err)
require.Empty(t, resp)

err = serviceCache.CacheQueryResponse(ctxb, req, defaultQueryResp)
err = serviceCache.CacheQueryResponse(ctxb, req, defaultQueryResp, map[string][]string{})
require.NoError(t, err)

resp, err = serviceCache.GetCachedQueryResponse(ctxb, req)
require.NoError(t, err)
require.JSONEq(t, string(defaultQueryResp), string(resp))
require.JSONEq(t, string(defaultQueryResp), string(resp.ResponseInBytes))
}

func mkEVMRPCRequestEnvelope(blockNumber string) *decode.EVMRPCRequestEnvelope {
Expand Down
10 changes: 10 additions & 0 deletions service/cachemdw/caching_middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,22 @@ func (c *ServiceCache) CachingMiddleware(
response := r.Context().Value(ResponseContextKey)
typedResponse, ok := response.([]byte)

whitelistedHeaders := []string{"Vary"}

// if request isn't already cached, request is cacheable and response is present in context - cache the response
if !isCached && cacheable && ok {
headerMap := make(map[string][]string, 0)

for _, headerName := range whitelistedHeaders {
headerValues := w.Header().Values(headerName)
headerMap[headerName] = headerValues
}

if err := c.CacheQueryResponse(
r.Context(),
decodedReq,
typedResponse,
headerMap,
); err != nil {
c.Logger.Error().Msgf("can't validate and cache response: %v", err)
}
Expand Down
7 changes: 5 additions & 2 deletions service/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ func createProxyRequestMiddleware(next http.Handler, config config.Config, servi

isCached := cachemdw.IsRequestCached(r.Context())
cachedResponse := r.Context().Value(cachemdw.ResponseContextKey)
typedCachedResponse, ok := cachedResponse.([]byte)
typedCachedResponse, ok := cachedResponse.(*cachemdw.QueryResponse)

// 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
Expand All @@ -250,7 +250,10 @@ func createProxyRequestMiddleware(next http.Handler, config config.Config, servi

w.Header().Add(cachemdw.CacheHeaderKey, cachemdw.CacheHitHeaderValue)
w.Header().Add("Content-Type", "application/json")
_, err := w.Write(typedCachedResponse)
for headerName, headerValues := range typedCachedResponse.HeaderMap {
w.Header().Add(headerName, headerValues[0])
}
_, err := w.Write(typedCachedResponse.ResponseInBytes)
if err != nil {
serviceLogger.Logger.Error().Msg(fmt.Sprintf("can't write cached response: %v", err))
}
Expand Down

0 comments on commit 49eddd3

Please sign in to comment.