diff --git a/proxyd/proxyd/backend.go b/proxyd/proxyd/backend.go index 9a004d0..2cee810 100644 --- a/proxyd/proxyd/backend.go +++ b/proxyd/proxyd/backend.go @@ -75,7 +75,7 @@ var ( } ErrOverRateLimit = &RPCErr{ Code: JSONRPCErrorInternal - 16, - Message: "rate limited", + Message: "over rate limit", HTTPErrorCode: 429, } diff --git a/proxyd/proxyd/integration_tests/rate_limit_test.go b/proxyd/proxyd/integration_tests/rate_limit_test.go index 5d3163a..e4cc698 100644 --- a/proxyd/proxyd/integration_tests/rate_limit_test.go +++ b/proxyd/proxyd/integration_tests/rate_limit_test.go @@ -16,7 +16,8 @@ type resWithCode struct { res []byte } -const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit"},"id":null,"jsonrpc":"2.0"}` +const frontendOverLimitResponse = `{"error":{"code":-32016,"message":"over rate limit with special message"},"id":null,"jsonrpc":"2.0"}` +const frontendOverLimitResponseWithID = `{"error":{"code":-32016,"message":"over rate limit with special message"},"id":999,"jsonrpc":"2.0"}` var ethChainID = "eth_chainId" @@ -31,7 +32,7 @@ func TestBackendMaxRPSLimit(t *testing.T) { shutdown, err := proxyd.Start(config) require.NoError(t, err) defer shutdown() - limitedRes, codes := spamReqs(t, client, ethChainID, 503) + limitedRes, codes := spamReqs(t, client, ethChainID, 503, 3) require.Equal(t, 2, codes[200]) require.Equal(t, 1, codes[503]) RequireEqualJSON(t, []byte(noBackendsResponse), limitedRes) @@ -50,7 +51,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { t.Run("non-exempt over limit", func(t *testing.T) { client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, ethChainID, 429) + limitedRes, codes := spamReqs(t, client, ethChainID, 429, 3) require.Equal(t, 1, codes[429]) require.Equal(t, 2, codes[200]) RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) @@ -60,7 +61,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h := make(http.Header) h.Set("User-Agent", "exempt_agent") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, ethChainID, 429) + _, codes := spamReqs(t, client, ethChainID, 429, 3) require.Equal(t, 3, codes[200]) }) @@ -68,7 +69,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h := make(http.Header) h.Set("Origin", "exempt_origin") client := NewProxydClientWithHeaders("http://127.0.0.1:8545", h) - _, codes := spamReqs(t, client, ethChainID, 429) + _, codes := spamReqs(t, client, ethChainID, 429, 3) require.Equal(t, 3, codes[200]) }) @@ -79,7 +80,7 @@ func TestFrontendMaxRPSLimit(t *testing.T) { h2.Set("X-Forwarded-For", "1.1.1.1") client1 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h1) client2 := NewProxydClientWithHeaders("http://127.0.0.1:8545", h2) - _, codes := spamReqs(t, client1, ethChainID, 429) + _, codes := spamReqs(t, client1, ethChainID, 429, 3) require.Equal(t, 1, codes[429]) require.Equal(t, 2, codes[200]) _, code, err := client2.SendRPC(ethChainID, nil) @@ -95,11 +96,11 @@ func TestFrontendMaxRPSLimit(t *testing.T) { t.Run("RPC override", func(t *testing.T) { client := NewProxydClient("http://127.0.0.1:8545") - limitedRes, codes := spamReqs(t, client, "eth_foobar", 429) + limitedRes, codes := spamReqs(t, client, "eth_foobar", 429, 2) // use 2 and 1 here since the limit for eth_foobar is 1 - require.Equal(t, 2, codes[429]) + require.Equal(t, 1, codes[429]) require.Equal(t, 1, codes[200]) - RequireEqualJSON(t, []byte(frontendOverLimitResponse), limitedRes) + RequireEqualJSON(t, []byte(frontendOverLimitResponseWithID), limitedRes) }) time.Sleep(time.Second) @@ -140,9 +141,9 @@ func TestFrontendMaxRPSLimit(t *testing.T) { }) } -func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int) ([]byte, map[int]int) { +func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int, n int) ([]byte, map[int]int) { resCh := make(chan *resWithCode) - for i := 0; i < 3; i++ { + for i := 0; i < n; i++ { go func() { res, code, err := client.SendRPC(method, nil) require.NoError(t, err) @@ -155,7 +156,7 @@ func spamReqs(t *testing.T, client *ProxydHTTPClient, method string, limCode int codes := make(map[int]int) var limitedRes []byte - for i := 0; i < 3; i++ { + for i := 0; i < n; i++ { res := <-resCh code := res.code if codes[code] == 0 { diff --git a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml index 8615f3f..f34840d 100644 --- a/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml +++ b/proxyd/proxyd/integration_tests/testdata/frontend_rate_limit.toml @@ -21,7 +21,7 @@ eth_foobar = "main" rate_per_second = 2 exempt_origins = ["exempt_origin"] exempt_user_agents = ["exempt_agent"] -error_message = "over rate limit" +error_message = "over rate limit with special message" [rate_limit.method_overrides.eth_foobar] limit = 1 diff --git a/proxyd/proxyd/server.go b/proxyd/proxyd/server.go index c65b86e..455989d 100644 --- a/proxyd/proxyd/server.go +++ b/proxyd/proxyd/server.go @@ -244,8 +244,11 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } if isLimited("") { - rpcErr := ErrOverRateLimit.Clone() - rpcErr.Message = s.limConfig.ErrorMessage + rpcErr := ErrOverRateLimit + if s.limConfig.ErrorMessage != "" { + rpcErr = ErrOverRateLimit.Clone() + rpcErr.Message = s.limConfig.ErrorMessage + } RecordRPCError(ctx, BackendProxyd, "unknown", rpcErr) log.Warn( "rate limited request", @@ -391,8 +394,13 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "req_id", GetReqID(ctx), "method", parsedReq.Method, ) - RecordRPCError(ctx, BackendProxyd, parsedReq.Method, ErrOverRateLimit) - responses[i] = NewRPCErrorRes(parsedReq.ID, ErrOverRateLimit) + rpcErr := ErrOverRateLimit + if s.limConfig.ErrorMessage != "" { + rpcErr = rpcErr.Clone() + rpcErr.Message = s.limConfig.ErrorMessage + } + RecordRPCError(ctx, BackendProxyd, parsedReq.Method, rpcErr) + responses[i] = NewRPCErrorRes(parsedReq.ID, rpcErr) continue }