diff --git a/.changeset/forty-walls-fry.md b/.changeset/forty-walls-fry.md new file mode 100644 index 0000000000000..8eb4ac6e037ca --- /dev/null +++ b/.changeset/forty-walls-fry.md @@ -0,0 +1,5 @@ +--- +'@eth-optimism/proxyd': patch +--- + +Add customizable whitelist error diff --git a/proxyd/config.go b/proxyd/config.go index ba60d67defd92..466deb9231b62 100644 --- a/proxyd/config.go +++ b/proxyd/config.go @@ -96,18 +96,19 @@ type BackendGroupsConfig map[string]*BackendGroupConfig type MethodMappingsConfig map[string]string type Config struct { - WSBackendGroup string `toml:"ws_backend_group"` - Server ServerConfig `toml:"server"` - Cache CacheConfig `toml:"cache"` - Redis RedisConfig `toml:"redis"` - Metrics MetricsConfig `toml:"metrics"` - RateLimit RateLimitConfig `toml:"rate_limit"` - BackendOptions BackendOptions `toml:"backend"` - Backends BackendsConfig `toml:"backends"` - Authentication map[string]string `toml:"authentication"` - BackendGroups BackendGroupsConfig `toml:"backend_groups"` - RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` - WSMethodWhitelist []string `toml:"ws_method_whitelist"` + WSBackendGroup string `toml:"ws_backend_group"` + Server ServerConfig `toml:"server"` + Cache CacheConfig `toml:"cache"` + Redis RedisConfig `toml:"redis"` + Metrics MetricsConfig `toml:"metrics"` + RateLimit RateLimitConfig `toml:"rate_limit"` + BackendOptions BackendOptions `toml:"backend"` + Backends BackendsConfig `toml:"backends"` + Authentication map[string]string `toml:"authentication"` + BackendGroups BackendGroupsConfig `toml:"backend_groups"` + RPCMethodMappings map[string]string `toml:"rpc_method_mappings"` + WSMethodWhitelist []string `toml:"ws_method_whitelist"` + WhitelistErrorMessage string `toml:"whitelist_error_message"` } func ReadFromEnvOrConfig(value string) (string, error) { diff --git a/proxyd/integration_tests/testdata/whitelist.toml b/proxyd/integration_tests/testdata/whitelist.toml index 55b118c38f62a..4a65248d71628 100644 --- a/proxyd/integration_tests/testdata/whitelist.toml +++ b/proxyd/integration_tests/testdata/whitelist.toml @@ -1,3 +1,5 @@ +whitelist_error_message = "rpc method is not whitelisted custom message" + [server] rpc_port = 8545 diff --git a/proxyd/integration_tests/testdata/ws.toml b/proxyd/integration_tests/testdata/ws.toml index f86b22f270546..4642e6bc0fc90 100644 --- a/proxyd/integration_tests/testdata/ws.toml +++ b/proxyd/integration_tests/testdata/ws.toml @@ -1,3 +1,5 @@ +whitelist_error_message = "rpc method is not whitelisted" + ws_backend_group = "main" ws_method_whitelist = [ diff --git a/proxyd/integration_tests/validation_test.go b/proxyd/integration_tests/validation_test.go index be964c14d0d71..6f0653df2c0e8 100644 --- a/proxyd/integration_tests/validation_test.go +++ b/proxyd/integration_tests/validation_test.go @@ -10,7 +10,7 @@ import ( ) const ( - notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":999}` + notWhitelistedResponse = `{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted custom message"},"id":999}` parseErrResponse = `{"jsonrpc":"2.0","error":{"code":-32700,"message":"parse error"},"id":null}` invalidJSONRPCVersionResponse = `{"error":{"code":-32601,"message":"invalid JSON-RPC version"},"id":null,"jsonrpc":"2.0"}` invalidIDResponse = `{"error":{"code":-32601,"message":"invalid ID"},"id":null,"jsonrpc":"2.0"}` diff --git a/proxyd/integration_tests/ws_test.go b/proxyd/integration_tests/ws_test.go index d93d3a7562485..9ae12abdcbf83 100644 --- a/proxyd/integration_tests/ws_test.go +++ b/proxyd/integration_tests/ws_test.go @@ -7,6 +7,8 @@ import ( "testing" "time" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum-optimism/optimism/proxyd" "github.com/gorilla/websocket" "github.com/stretchr/testify/require" @@ -42,6 +44,13 @@ func TestConcurrentWSPanic(t *testing.T) { require.NoError(t, err) defer shutdown() + // suppress tons of log messages + oldHandler := log.Root().GetHandler() + log.Root().SetHandler(log.DiscardHandler()) + defer func() { + log.Root().SetHandler(oldHandler) + }() + <-readyCh var wg sync.WaitGroup diff --git a/proxyd/proxyd.go b/proxyd/proxyd.go index e9bbe42fc75f4..af9f44eb73197 100644 --- a/proxyd/proxyd.go +++ b/proxyd/proxyd.go @@ -55,6 +55,17 @@ func Start(config *Config) (func(), error) { } } + // While modifying shared globals is a bad practice, the alternative + // is to clone these errors on every invocation. This is inefficient. + // We'd also have to make sure that errors.Is and errors.As continue + // to function properly on the cloned errors. + if config.RateLimit.ErrorMessage != "" { + ErrOverRateLimit.Message = config.RateLimit.ErrorMessage + } + if config.WhitelistErrorMessage != "" { + ErrMethodNotWhitelisted.Message = config.WhitelistErrorMessage + } + maxConcurrentRPCs := config.Server.MaxConcurrentRPCs if maxConcurrentRPCs == 0 { maxConcurrentRPCs = math.MaxInt64 diff --git a/proxyd/server.go b/proxyd/server.go index 455989dbd12b2..509dc6a338c5b 100644 --- a/proxyd/server.go +++ b/proxyd/server.go @@ -244,12 +244,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { } if isLimited("") { - rpcErr := ErrOverRateLimit - if s.limConfig.ErrorMessage != "" { - rpcErr = ErrOverRateLimit.Clone() - rpcErr.Message = s.limConfig.ErrorMessage - } - RecordRPCError(ctx, BackendProxyd, "unknown", rpcErr) + RecordRPCError(ctx, BackendProxyd, "unknown", ErrOverRateLimit) log.Warn( "rate limited request", "req_id", GetReqID(ctx), @@ -258,7 +253,7 @@ func (s *Server) HandleRPC(w http.ResponseWriter, r *http.Request) { "origin", origin, "remote_ip", xff, ) - writeRPCError(ctx, w, nil, rpcErr) + writeRPCError(ctx, w, nil, ErrOverRateLimit) return } @@ -394,13 +389,8 @@ func (s *Server) handleBatchRPC(ctx context.Context, reqs []json.RawMessage, isL "req_id", GetReqID(ctx), "method", parsedReq.Method, ) - 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) + RecordRPCError(ctx, BackendProxyd, parsedReq.Method, ErrOverRateLimit) + responses[i] = NewRPCErrorRes(parsedReq.ID, ErrOverRateLimit) continue }