cache.go 4.63 KB
Newer Older
1 2 3 4
package proxyd

import (
	"context"
5
	"encoding/json"
6
	"strings"
7
	"time"
8

Felipe Andrade's avatar
Felipe Andrade committed
9
	"github.com/ethereum/go-ethereum/rpc"
10
	"github.com/redis/go-redis/v9"
Felipe Andrade's avatar
Felipe Andrade committed
11

12
	"github.com/golang/snappy"
13 14 15 16 17
	lru "github.com/hashicorp/golang-lru"
)

type Cache interface {
	Get(ctx context.Context, key string) (string, error)
18
	Put(ctx context.Context, key string, value string) error
19 20
}

21
const (
inphi's avatar
inphi committed
22 23
	// assuming an average RPCRes size of 3 KB
	memoryCacheLimit = 4096
24 25
	// Set a large ttl to avoid expirations. However, a ttl must be set for volatile-lru to take effect.
	redisTTL = 30 * 7 * 24 * time.Hour
26
)
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43

type cache struct {
	lru *lru.Cache
}

func newMemoryCache() *cache {
	rep, _ := lru.New(memoryCacheLimit)
	return &cache{rep}
}

func (c *cache) Get(ctx context.Context, key string) (string, error) {
	if val, ok := c.lru.Get(key); ok {
		return val.(string), nil
	}
	return "", nil
}

44
func (c *cache) Put(ctx context.Context, key string, value string) error {
45 46 47 48
	c.lru.Add(key, value)
	return nil
}

inphi's avatar
inphi committed
49
type redisCache struct {
50 51
	rdb    *redis.Client
	prefix string
inphi's avatar
inphi committed
52 53
}

54 55 56 57 58 59 60 61 62
func newRedisCache(rdb *redis.Client, prefix string) *redisCache {
	return &redisCache{rdb, prefix}
}

func (c *redisCache) namespaced(key string) string {
	if c.prefix == "" {
		return key
	}
	return strings.Join([]string{c.prefix, key}, ":")
inphi's avatar
inphi committed
63 64 65
}

func (c *redisCache) Get(ctx context.Context, key string) (string, error) {
66
	start := time.Now()
67
	val, err := c.rdb.Get(ctx, c.namespaced(key)).Result()
68 69
	redisCacheDurationSumm.WithLabelValues("GET").Observe(float64(time.Since(start).Milliseconds()))

70 71 72
	if err == redis.Nil {
		return "", nil
	} else if err != nil {
73
		RecordRedisError("CacheGet")
inphi's avatar
inphi committed
74 75 76 77 78
		return "", err
	}
	return val, nil
}

79
func (c *redisCache) Put(ctx context.Context, key string, value string) error {
80
	start := time.Now()
81
	err := c.rdb.SetEx(ctx, c.namespaced(key), value, redisTTL).Err()
82 83
	redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds()))

84 85 86
	if err != nil {
		RecordRedisError("CacheSet")
	}
inphi's avatar
inphi committed
87 88 89
	return err
}

90 91 92 93 94 95 96 97 98 99
type cacheWithCompression struct {
	cache Cache
}

func newCacheWithCompression(cache Cache) *cacheWithCompression {
	return &cacheWithCompression{cache}
}

func (c *cacheWithCompression) Get(ctx context.Context, key string) (string, error) {
	encodedVal, err := c.cache.Get(ctx, key)
inphi's avatar
inphi committed
100
	if err != nil {
101
		return "", err
inphi's avatar
inphi committed
102
	}
103 104 105 106 107 108 109 110 111 112 113 114 115
	if encodedVal == "" {
		return "", nil
	}
	val, err := snappy.Decode(nil, []byte(encodedVal))
	if err != nil {
		return "", err
	}
	return string(val), nil
}

func (c *cacheWithCompression) Put(ctx context.Context, key string, value string) error {
	encodedVal := snappy.Encode(nil, []byte(value))
	return c.cache.Put(ctx, key, string(encodedVal))
inphi's avatar
inphi committed
116 117
}

inphi's avatar
inphi committed
118 119
type RPCCache interface {
	GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error)
120
	PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error
121 122
}

inphi's avatar
inphi committed
123 124 125
type rpcCache struct {
	cache    Cache
	handlers map[string]RPCMethodHandler
126 127
}

128
func newRPCCache(cache Cache) RPCCache {
Felipe Andrade's avatar
Felipe Andrade committed
129
	staticHandler := &StaticMethodHandler{cache: cache}
130
	debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache,
131
		filterGet: func(req *RPCReq) bool {
132 133 134 135 136 137 138 139 140 141 142 143
			// cache only if the request is for a block hash

			var p []rpc.BlockNumberOrHash
			err := json.Unmarshal(req.Params, &p)
			if err != nil {
				return false
			}
			if len(p) != 1 {
				return false
			}
			return p[0].BlockHash != nil
		},
144 145 146 147 148 149 150 151
		filterPut: func(req *RPCReq, res *RPCRes) bool {
			// don't cache if response contains 0 receipts
			rawReceipts, ok := res.Result.([]interface{})
			if !ok {
				return false
			}
			return len(rawReceipts) > 0
		},
152
	}
inphi's avatar
inphi committed
153
	handlers := map[string]RPCMethodHandler{
Felipe Andrade's avatar
Felipe Andrade committed
154 155 156 157 158 159 160
		"eth_chainId":                           staticHandler,
		"net_version":                           staticHandler,
		"eth_getBlockTransactionCountByHash":    staticHandler,
		"eth_getUncleCountByBlockHash":          staticHandler,
		"eth_getBlockByHash":                    staticHandler,
		"eth_getTransactionByBlockHashAndIndex": staticHandler,
		"eth_getUncleByBlockHashAndIndex":       staticHandler,
161
		"debug_getRawReceipts":                  debugGetRawReceiptsHandler,
inphi's avatar
inphi committed
162 163 164 165
	}
	return &rpcCache{
		cache:    cache,
		handlers: handlers,
inphi's avatar
inphi committed
166
	}
167 168
}

inphi's avatar
inphi committed
169 170 171 172 173
func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) {
	handler := c.handlers[req.Method]
	if handler == nil {
		return nil, nil
	}
inphi's avatar
inphi committed
174
	res, err := handler.GetRPCMethod(ctx, req)
175 176 177 178 179 180 181 182
	if err != nil {
		RecordCacheError(req.Method)
		return nil, err
	}
	if res == nil {
		RecordCacheMiss(req.Method)
	} else {
		RecordCacheHit(req.Method)
inphi's avatar
inphi committed
183
	}
Felipe Andrade's avatar
Felipe Andrade committed
184
	return res, nil
185 186
}

187
func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error {
inphi's avatar
inphi committed
188 189 190 191
	handler := c.handlers[req.Method]
	if handler == nil {
		return nil
	}
192
	return handler.PutRPCMethod(ctx, req, res)
193
}