diff --git a/proxyd/proxyd/rewriter.go b/proxyd/proxyd/rewriter.go index 98b59ec..605787e 100644 --- a/proxyd/proxyd/rewriter.go +++ b/proxyd/proxyd/rewriter.go @@ -66,25 +66,26 @@ func RewriteRequest(rctx RewriteContext, req *RPCReq, res *RPCRes) (RewriteResul "eth_newFilter": return rewriteRange(rctx, req, res, 0) case "debug_getRawReceipts", "consensus_getReceipts": - return rewriteParam(rctx, req, res, 0, true) + return rewriteParam(rctx, req, res, 0, true, false) 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) + return rewriteParam(rctx, req, res, 1, false, true) + case "eth_getStorageAt", + "eth_getProof": + return rewriteParam(rctx, req, res, 2, false, true) case "eth_getBlockTransactionCountByNumber", "eth_getUncleCountByBlockNumber", "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex", "eth_getUncleByBlockNumberAndIndex": - return rewriteParam(rctx, req, res, 0, false) + return rewriteParam(rctx, req, res, 0, false, false) } return RewriteNone, nil } -func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool) (RewriteResult, error) { +func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, required bool, blockNrOrHash bool) (RewriteResult, error) { var p []interface{} err := json.Unmarshal(req.Params, &p) if err != nil { @@ -99,13 +100,38 @@ func rewriteParam(rctx RewriteContext, req *RPCReq, res *RPCRes, pos int, requir return RewriteNone, nil } - s, ok := p[pos].(string) - if !ok { - return RewriteOverrideError, errors.New("expected string") - } - val, rw, err := rewriteTag(rctx, s) - if err != nil { - return RewriteOverrideError, err + // support for https://eips.ethereum.org/EIPS/eip-1898 + var val interface{} + var rw bool + if blockNrOrHash { + bnh, err := remarshalBlockNumberOrHash(p[pos]) + if err != nil { + // fallback to string + s, ok := p[pos].(string) + if ok { + val, rw, err = rewriteTag(rctx, s) + if err != nil { + return RewriteOverrideError, err + } + } else { + return RewriteOverrideError, errors.New("expected BlockNumberOrHash or string") + } + } else { + val, rw, err = rewriteTagBlockNumberOrHash(rctx, bnh) + if err != nil { + return RewriteOverrideError, err + } + } + } else { + s, ok := p[pos].(string) + if !ok { + return RewriteOverrideError, errors.New("expected string") + } + + val, rw, err = rewriteTag(rctx, s) + if err != nil { + return RewriteOverrideError, err + } } if rw { @@ -210,14 +236,23 @@ func rewriteTagMap(rctx RewriteContext, m map[string]interface{}, key string) (b return false, nil } -func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { +func remarshalBlockNumberOrHash(current interface{}) (*rpc.BlockNumberOrHash, error) { jv, err := json.Marshal(current) if err != nil { - return "", false, err + return nil, err } var bnh rpc.BlockNumberOrHash err = bnh.UnmarshalJSON(jv) + if err != nil { + return nil, err + } + + return &bnh, nil +} + +func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { + bnh, err := remarshalBlockNumberOrHash(current) if err != nil { return "", false, err } @@ -245,3 +280,31 @@ func rewriteTag(rctx RewriteContext, current string) (string, bool, error) { return current, false, nil } + +func rewriteTagBlockNumberOrHash(rctx RewriteContext, current *rpc.BlockNumberOrHash) (*rpc.BlockNumberOrHash, bool, error) { + // this is a hash, not a block number + if current.BlockNumber == nil { + return current, false, nil + } + + switch *current.BlockNumber { + case rpc.PendingBlockNumber, + rpc.EarliestBlockNumber: + return current, false, nil + case rpc.FinalizedBlockNumber: + bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.finalized)) + return &bn, true, nil + case rpc.SafeBlockNumber: + bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.safe)) + return &bn, true, nil + case rpc.LatestBlockNumber: + bn := rpc.BlockNumberOrHashWithNumber(rpc.BlockNumber(rctx.latest)) + return &bn, true, nil + default: + if current.BlockNumber.Int64() > int64(rctx.latest) { + return nil, false, ErrRewriteBlockOutOfRange + } + } + + return current, false, nil +} diff --git a/proxyd/proxyd/rewriter_test.go b/proxyd/proxyd/rewriter_test.go index 94bc5c9..1f0d80b 100644 --- a/proxyd/proxyd/rewriter_test.go +++ b/proxyd/proxyd/rewriter_test.go @@ -5,7 +5,9 @@ import ( "strings" "testing" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" "github.com/stretchr/testify/require" ) @@ -282,12 +284,14 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 2, len(p)) require.Equal(t, "0x123", p[0]) - require.Equal(t, hexutil.Uint64(100).String(), p[1]) + bnh, err := remarshalBlockNumberOrHash(p[1]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -314,12 +318,14 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 2, len(p)) require.Equal(t, "0x123", p[0]) - require.Equal(t, hexutil.Uint64(100).String(), p[1]) + bnh, err := remarshalBlockNumberOrHash(p[1]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -359,13 +365,15 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 3, len(p)) require.Equal(t, "0x123", p[0]) require.Equal(t, "5", p[1]) - require.Equal(t, hexutil.Uint64(100).String(), p[2]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -377,13 +385,15 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteOverrideRequest, check: func(t *testing.T, args args) { - var p []string + var p []interface{} err := json.Unmarshal(args.req.Params, &p) require.Nil(t, err) require.Equal(t, 3, len(p)) require.Equal(t, "0x123", p[0]) require.Equal(t, "5", p[1]) - require.Equal(t, hexutil.Uint64(100).String(), p[2]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) }, }, { @@ -517,6 +527,88 @@ func TestRewriteRequest(t *testing.T) { }, expected: RewriteNone, }, + // eip1898 + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash at genesis (blockNumber)", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockNumber": "0x0", + }})}, + res: nil, + }, + expected: RewriteNone, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash at genesis (hash)", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockHash": "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3", + "requireCanonical": true, + }})}, + res: nil, + }, + expected: RewriteNone, + check: func(t *testing.T, args args) { + var p []interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0xae851f927ee40de99aabb7461c00f9622ab91d60", p[0]) + require.Equal(t, "10", p[1]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithHash(common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"), true), *bnh) + require.True(t, bnh.RequireCanonical) + }, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash at latest (blockNumber)", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockNumber": "latest", + }})}, + res: nil, + }, + expected: RewriteOverrideRequest, + check: func(t *testing.T, args args) { + var p []interface{} + err := json.Unmarshal(args.req.Params, &p) + require.Nil(t, err) + require.Equal(t, 3, len(p)) + require.Equal(t, "0xae851f927ee40de99aabb7461c00f9622ab91d60", p[0]) + require.Equal(t, "10", p[1]) + bnh, err := remarshalBlockNumberOrHash(p[2]) + require.Nil(t, err) + require.Equal(t, rpc.BlockNumberOrHashWithNumber(100), *bnh) + }, + }, + { + name: "eth_getStorageAt using rpc.BlockNumberOrHash out of range", + args: args{ + rctx: RewriteContext{latest: hexutil.Uint64(100)}, + req: &RPCReq{Method: "eth_getStorageAt", Params: mustMarshalJSON([]interface{}{ + "0xae851f927ee40de99aabb7461c00f9622ab91d60", + "10", + map[string]interface{}{ + "blockNumber": "0x111", + }})}, + res: nil, + }, + expected: RewriteOverrideError, + expectedErr: ErrRewriteBlockOutOfRange, + }, } // generalize tests for other methods with same interface and behavior @@ -528,6 +620,7 @@ func TestRewriteRequest(t *testing.T) { tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleCountByBlockNumber") tests = generalize(tests, "eth_getBlockByNumber", "eth_getTransactionByBlockNumberAndIndex") tests = generalize(tests, "eth_getBlockByNumber", "eth_getUncleByBlockNumberAndIndex") + tests = generalize(tests, "eth_getStorageSlotAt", "eth_getProof") for _, tt := range tests { t.Run(tt.name, func(t *testing.T) {