Skip to content

Commit 9e3b5d0

Browse files
jelias2sebastianst
andauthored
feat: proxyd update consensus mode out of range requests to be api-spec compliant (#552)
* fix(proxyd): don't return error for out-of-range block requests * feat: update proxyd consensus mode rewriter to provide json rpc compliant responses on out of range requests --------- Co-authored-by: Sebastian Stammler <seb@oplabs.co>
1 parent 48e8517 commit 9e3b5d0

File tree

2 files changed

+43
-14
lines changed

2 files changed

+43
-14
lines changed

proxyd/backend.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -97,12 +97,6 @@ var (
9797
Message: "backend is currently not healthy to serve traffic",
9898
HTTPErrorCode: 503,
9999
}
100-
ErrBlockOutOfRange = &RPCErr{
101-
Code: JSONRPCErrorInternal - 19,
102-
Message: "block is out of range",
103-
HTTPErrorCode: 400,
104-
}
105-
106100
ErrRequestBodyTooLarge = &RPCErr{
107101
Code: JSONRPCErrorInternal - 21,
108102
Message: "request body too large",
@@ -1795,7 +1789,37 @@ func (bg *BackendGroup) OverwriteConsensusResponses(rpcReqs []*RPCReq, overridde
17951789
res: &res,
17961790
})
17971791
if errors.Is(err, ErrRewriteBlockOutOfRange) {
1798-
res.Error = ErrBlockOutOfRange
1792+
// Return spec-compliant response for out-of-range blocks
1793+
// instead of forwarding to backend (fail-fast)
1794+
log.Debug(
1795+
"returning spec-compliant response for out-of-range block request",
1796+
"method", req.Method,
1797+
"req_id", req.ID,
1798+
)
1799+
switch req.Method {
1800+
case "eth_getBlockByNumber",
1801+
"eth_getBlockByHash",
1802+
"eth_getBlockReceipts",
1803+
"eth_getBlockTransactionCountByNumber",
1804+
"eth_getUncleCountByBlockNumber",
1805+
"eth_getTransactionByBlockNumberAndIndex",
1806+
"eth_getUncleByBlockNumberAndIndex",
1807+
"debug_getRawReceipts",
1808+
"consensus_getReceipts":
1809+
res.Result = json.RawMessage("null")
1810+
case "eth_getLogs":
1811+
res.Result = json.RawMessage("[]")
1812+
case "eth_getCode":
1813+
res.Result = json.RawMessage("\"0x\"")
1814+
case "eth_getStorageAt":
1815+
res.Result = json.RawMessage("\"0x0000000000000000000000000000000000000000000000000000000000000000\"")
1816+
case "eth_getBalance",
1817+
"eth_getTransactionCount":
1818+
res.Result = json.RawMessage("\"0x0\"")
1819+
default:
1820+
// For unknown methods, return null
1821+
res.Result = json.RawMessage("null")
1822+
}
17991823
} else if errors.Is(err, ErrRewriteRangeTooLarge) {
18001824
res.Error = ErrInvalidParams(
18011825
fmt.Sprintf("block range greater than %d max", rctx.maxBlockRange),

proxyd/integration_tests/consensus_test.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -752,19 +752,24 @@ func TestConsensus(t *testing.T) {
752752
require.Equal(t, "0xe1", jsonMap["params"].([]interface{})[0])
753753
})
754754

755-
t.Run("rewrite request of eth_getBlockByNumber - out of range", func(t *testing.T) {
755+
t.Run("rewrite request of eth_getBlockByNumber - out of range returns null without hitting backend", func(t *testing.T) {
756756
reset()
757757
useOnlyNode1()
758758

759+
// the request for a future block should return null directly from proxyd
760+
// per the Ethereum JSON-RPC spec, without hitting the backend (fail-fast)
759761
resRaw, statusCode, err := client.SendRPC("eth_getBlockByNumber", []interface{}{"0x300"})
760762
require.NoError(t, err)
761-
require.Equal(t, 400, statusCode)
763+
require.Equal(t, 200, statusCode)
762764

763765
var jsonMap map[string]interface{}
764766
err = json.Unmarshal(resRaw, &jsonMap)
765767
require.NoError(t, err)
766-
require.Equal(t, -32019, int(jsonMap["error"].(map[string]interface{})["code"].(float64)))
767-
require.Equal(t, "block is out of range", jsonMap["error"].(map[string]interface{})["message"])
768+
require.Nil(t, jsonMap["result"])
769+
require.Nil(t, jsonMap["error"])
770+
771+
// verify backend was NOT called (fail-fast)
772+
require.Equal(t, 0, len(nodes["node1"].mockBackend.Requests()))
768773
})
769774

770775
t.Run("batched rewrite", func(t *testing.T) {
@@ -786,9 +791,9 @@ func TestConsensus(t *testing.T) {
786791
// rewrite latest to 0x101
787792
require.Equal(t, "0x101", jsonMap[0]["result"].(map[string]interface{})["number"])
788793

789-
// out of bounds for block 0x102
790-
require.Equal(t, -32019, int(jsonMap[1]["error"].(map[string]interface{})["code"].(float64)))
791-
require.Equal(t, "block is out of range", jsonMap[1]["error"].(map[string]interface{})["message"])
794+
// block 0x102 is beyond latest (0x101) so returns null (fail-fast, spec-compliant)
795+
require.Nil(t, jsonMap[1]["result"])
796+
require.Nil(t, jsonMap[1]["error"])
792797

793798
// dont rewrite for 0xe1
794799
require.Equal(t, "0xe1", jsonMap[2]["result"].(map[string]interface{})["number"])

0 commit comments

Comments
 (0)