194 lines
4.7 KiB
Go
194 lines
4.7 KiB
Go
package proxyd
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/rpc"
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
"github.com/golang/snappy"
|
|
lru "github.com/hashicorp/golang-lru"
|
|
)
|
|
|
|
type Cache interface {
|
|
Get(ctx context.Context, key string) (string, error)
|
|
Put(ctx context.Context, key string, value string) error
|
|
}
|
|
|
|
const (
|
|
// assuming an average RPCRes size of 3 KB
|
|
memoryCacheLimit = 4096
|
|
)
|
|
|
|
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
|
|
}
|
|
|
|
func (c *cache) Put(ctx context.Context, key string, value string) error {
|
|
c.lru.Add(key, value)
|
|
return nil
|
|
}
|
|
|
|
type redisCache struct {
|
|
redisClient *redis.Client
|
|
redisReadClient *redis.Client
|
|
prefix string
|
|
ttl time.Duration
|
|
}
|
|
|
|
func newRedisCache(redisClient *redis.Client, redisReadClient *redis.Client, prefix string, ttl time.Duration) *redisCache {
|
|
return &redisCache{redisClient, redisReadClient, prefix, ttl}
|
|
}
|
|
|
|
func (c *redisCache) namespaced(key string) string {
|
|
if c.prefix == "" {
|
|
return key
|
|
}
|
|
return strings.Join([]string{c.prefix, key}, ":")
|
|
}
|
|
|
|
func (c *redisCache) Get(ctx context.Context, key string) (string, error) {
|
|
start := time.Now()
|
|
val, err := c.redisReadClient.Get(ctx, c.namespaced(key)).Result()
|
|
redisCacheDurationSumm.WithLabelValues("GET").Observe(float64(time.Since(start).Milliseconds()))
|
|
|
|
if err == redis.Nil {
|
|
return "", nil
|
|
} else if err != nil {
|
|
RecordRedisError("CacheGet")
|
|
return "", err
|
|
}
|
|
return val, nil
|
|
}
|
|
|
|
func (c *redisCache) Put(ctx context.Context, key string, value string) error {
|
|
start := time.Now()
|
|
err := c.redisClient.SetEx(ctx, c.namespaced(key), value, c.ttl).Err()
|
|
redisCacheDurationSumm.WithLabelValues("SETEX").Observe(float64(time.Since(start).Milliseconds()))
|
|
|
|
if err != nil {
|
|
RecordRedisError("CacheSet")
|
|
}
|
|
return err
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
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))
|
|
}
|
|
|
|
type RPCCache interface {
|
|
GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error)
|
|
PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error
|
|
}
|
|
|
|
type rpcCache struct {
|
|
cache Cache
|
|
handlers map[string]RPCMethodHandler
|
|
}
|
|
|
|
func newRPCCache(cache Cache) RPCCache {
|
|
staticHandler := &StaticMethodHandler{cache: cache}
|
|
debugGetRawReceiptsHandler := &StaticMethodHandler{cache: cache,
|
|
filterGet: func(req *RPCReq) bool {
|
|
// 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
|
|
},
|
|
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
|
|
},
|
|
}
|
|
handlers := map[string]RPCMethodHandler{
|
|
"eth_chainId": staticHandler,
|
|
"net_version": staticHandler,
|
|
"eth_getBlockTransactionCountByHash": staticHandler,
|
|
"eth_getUncleCountByBlockHash": staticHandler,
|
|
"eth_getBlockByHash": staticHandler,
|
|
"eth_getTransactionByBlockHashAndIndex": staticHandler,
|
|
"eth_getUncleByBlockHashAndIndex": staticHandler,
|
|
"debug_getRawReceipts": debugGetRawReceiptsHandler,
|
|
}
|
|
return &rpcCache{
|
|
cache: cache,
|
|
handlers: handlers,
|
|
}
|
|
}
|
|
|
|
func (c *rpcCache) GetRPC(ctx context.Context, req *RPCReq) (*RPCRes, error) {
|
|
handler := c.handlers[req.Method]
|
|
if handler == nil {
|
|
return nil, nil
|
|
}
|
|
res, err := handler.GetRPCMethod(ctx, req)
|
|
if err != nil {
|
|
RecordCacheError(req.Method)
|
|
return nil, err
|
|
}
|
|
if res == nil {
|
|
RecordCacheMiss(req.Method)
|
|
} else {
|
|
RecordCacheHit(req.Method)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
func (c *rpcCache) PutRPC(ctx context.Context, req *RPCReq, res *RPCRes) error {
|
|
handler := c.handlers[req.Method]
|
|
if handler == nil {
|
|
return nil
|
|
}
|
|
return handler.PutRPCMethod(ctx, req, res)
|
|
}
|