infra/proxyd/proxyd/rewriter.go

238 lines
5.9 KiB
Go
Raw Normal View History

package proxyd
import (
"encoding/json"
"errors"
2023-05-05 01:36:37 +03:00
"github.com/ethereum/go-ethereum/common/hexutil"
2023-05-05 01:33:08 +03:00
"github.com/ethereum/go-ethereum/rpc"
)
type RewriteContext struct {
latest hexutil.Uint64
safe hexutil.Uint64
2023-05-27 00:22:50 +03:00
finalized hexutil.Uint64
}
type RewriteResult uint8
const (
// RewriteNone means request should be forwarded as-is
RewriteNone RewriteResult = iota
// RewriteOverrideError means there was an error attempting to rewrite
RewriteOverrideError
// RewriteOverrideRequest means the modified request should be forwarded to the backend
RewriteOverrideRequest
// RewriteOverrideResponse means to skip calling the backend and serve the overridden response
RewriteOverrideResponse
)
var (
ErrRewriteBlockOutOfRange = errors.New("block is out of range")
)
// RewriteTags modifies the request and the response based on block tags
func RewriteTags(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
rw, err := RewriteResponse(rctx, req, res)
if rw == RewriteOverrideResponse {
return rw, err
}
return RewriteRequest(rctx, req, res)
}
// RewriteResponse modifies the response object to comply with the rewrite context
// after the method has been called at the backend
// RewriteResult informs the decision of the rewrite
func RewriteResponse(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
switch req.Method {
case "eth_blockNumber":
res.Result = rctx.latest
return RewriteOverrideResponse, nil
}
return RewriteNone, nil
}
// RewriteRequest modifies the request object to comply with the rewrite context
// before the method has been called at the backend
// it returns false if nothing was changed
func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
switch req.Method {
case "eth_getLogs",
"eth_newFilter":
return rewriteRange(rctx, req, res, 0)
case "consensus_getReceipts":
return rewriteGetReceiptsParams(rctx, req, res)
case "debug_getRawReceipts":
return rewriteParam(rctx, req, res, 0, true)
case "eth_getBalance",
"eth_getCode",
"eth_getTransactionCount",
"eth_call":
return rewriteParam(rctx, req, res, 1, false)
case "eth_getStorageAt":
return rewriteParam(rctx, req, res, 2, false)
case "eth_getBlockTransactionCountByNumber",
"eth_getUncleCountByBlockNumber",
"eth_getBlockByNumber",
"eth_getTransactionByBlockNumberAndIndex",
"eth_getUncleByBlockNumberAndIndex":
return rewriteParam(rctx, req, res, 0, false)
}
return RewriteNone, nil
}
func rewriteGetReceiptsParams(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResult, error) {
var p []interface{}
err := json.Unmarshal(req.Params, &p)
if err != nil {
return RewriteOverrideError, err
}
if len(p) != 1 {
return RewriteNone, nil
}
if m, ok := p[0].(map[string]interface{}); !ok || m["blockOrHash"] == nil {
return RewriteNone, nil
}
val, rw, err := rewriteTag(rctx, p[0].(map[string]interface{})["blockOrHash"].(string))
if err != nil {
return RewriteOverrideError, err
}
if rw {
p[0].(map[string]interface{})["blockOrHash"] = val
paramRaw, err := json.Marshal(p)
if err != nil {
return RewriteOverrideError, err
}
req.Params = paramRaw
return RewriteOverrideRequest, nil
}
return RewriteNone, nil
}
func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) {
var p []interface{}
err := json.Unmarshal(req.Params, &p)
if err != nil {
return RewriteOverrideError, err
}
// we assume latest if the param is missing,
// and we don't rewrite if there is not enough params
if len(p) == pos && !required {
p = append(p, "latest")
} else if len(p) <= pos {
return RewriteNone, nil
}
val, rw, err := rewriteTag(rctx, p[pos].(string))
if err != nil {
return RewriteOverrideError, err
}
if rw {
p[pos] = val
paramRaw, err := json.Marshal(p)
if err != nil {
return RewriteOverrideError, err
}
req.Params = paramRaw
return RewriteOverrideRequest, nil
}
return RewriteNone, nil
}
func rewriteRange(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int) (RewriteResult, error) {
var p []map[string]interface{}
err := json.Unmarshal(req.Params, &p)
if err != nil {
return RewriteOverrideError, err
}
modifiedFrom, err := rewriteTagMap(rctx, p[pos], "fromBlock")
if err != nil {
return RewriteOverrideError, err
}
modifiedTo, err := rewriteTagMap(rctx, p[pos], "toBlock")
if err != nil {
return RewriteOverrideError, err
}
// if any of the fields the request have been changed, re-marshal the params
if modifiedFrom || modifiedTo {
paramsRaw, err := json.Marshal(p)
req.Params = paramsRaw
if err != nil {
return RewriteOverrideError, err
}
return RewriteOverrideRequest, nil
}
return RewriteNone, nil
}
func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (bool, error) {
if m[key] == nil || m[key] == "" {
return false, nil
}
current, ok := m[key].(string)
if !ok {
return false, errors.New("expected string")
}
val, rw, err := rewriteTag(rctx, current)
if err != nil {
return false, err
}
if rw {
m[key] = val
return true, nil
}
return false, nil
}
func rewriteTag(rctx RewriteContext, current string) (string, bool, error) {
2023-05-05 01:33:08 +03:00
jv, err := json.Marshal(current)
if err != nil {
return "", false, err
}
var bnh rpc.BlockNumberOrHash
err = bnh.UnmarshalJSON(jv)
if err != nil {
return "", false, err
}
// this is a hash, not a block
if bnh.BlockNumber == nil {
return current, false, nil
}
switch *bnh.BlockNumber {
case rpc.PendingBlockNumber,
rpc.EarliestBlockNumber:
return current, false, nil
case rpc.FinalizedBlockNumber:
return rctx.finalized.String(), true, nil
case rpc.SafeBlockNumber:
return rctx.safe.String(), true, nil
case rpc.LatestBlockNumber:
return rctx.latest.String(), true, nil
default:
if bnh.BlockNumber.Int64() > int64(rctx.latest) {
return "", false, ErrRewriteBlockOutOfRange
}
}
return current, false, nil
}