Merge pull request #6358 from ethereum-optimism/felipe/rate-limit-dos

feat(proxyd): sender rate limiter should check chain id
This commit is contained in:
OptimismBot 2023-07-26 18:28:12 -07:00 committed by GitHub
commit 73ce23c025
5 changed files with 39 additions and 13 deletions

@ -2,6 +2,7 @@ package proxyd
import (
"fmt"
"math/big"
"os"
"strings"
"time"
@ -121,9 +122,10 @@ type BatchConfig struct {
// SenderRateLimitConfig configures the sender-based rate limiter
// for eth_sendRawTransaction requests.
type SenderRateLimitConfig struct {
Enabled bool
Interval TOMLDuration
Limit int
Enabled bool
Interval TOMLDuration
Limit int
AllowedChainIds []*big.Int `toml:"allowed_chain_ids"`
}
type Config struct {

@ -20,12 +20,10 @@ const txHex1 = "0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d
"1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" +
"08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"
const txHex2 = "0xf8aa82afd2830f4240830493e094464959ad46e64046b891f562cff202a465d5" +
"22f380b844d5bade070000000000000000000000004200000000000000000000" +
"0000000000000000060000000000000000000000000000000000000000000000" +
"0000000025ef43fc0038a05d8ea9837ea81469bda4dadbe852fdd37fcfbcd666" +
"5641a35e4726fbc04364e7a0107e20bb34aea53c695a551204a11d42fe465055" +
"510ee240e8f884fb70289be6"
const txHex2 = "0x02f8758201a48217fd84773594008504a817c80082520894be53e587975603" +
"a13d0923d0aa6d37c5233dd750865af3107a400080c080a04aefbd5819c35729" +
"138fe26b6ae1783ebf08d249b356c2f920345db97877f3f7a008d5ae92560a3c" +
"65f723439887205713af7ce7d7f6b24fba198f2afa03435867"
const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}`
@ -81,10 +79,10 @@ func TestSenderRateLimitLimiting(t *testing.T) {
// should be rate limited.
res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1))
require.NoError(t, err)
res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1))
require.NoError(t, err)
RequireEqualJSON(t, []byte(dummyRes), res1)
require.Equal(t, 200, code1)
res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1))
require.NoError(t, err)
RequireEqualJSON(t, []byte(limRes), res2)
require.Equal(t, 429, code2)

@ -20,4 +20,5 @@ eth_sendRawTransaction = "main"
[sender_rate_limit]
enabled = true
interval = "1s"
limit = 1
limit = 1
allowed_chain_ids = [420]

@ -8,4 +8,6 @@ invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","par
invalid transaction data|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x1234"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32602,"message":"transaction type not supported"},"id":1}
valid transaction data - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"}
valid transaction data - contract call|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d6cd17379ed88e261249b5280b84447e7ef2400000000000000000000000089c8b1b2774201bac50f627403eac1b732459cf70000000000000000000000000000000000000000000000056bc75e2d63100000c080a0473c95566026c312c9664cd61145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"}
batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}]
valid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1}|{"id": 123, "jsonrpc": "2.0", "result": "dummy"}
invalid chain id - simple send|{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1}|{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1}
batch with mixed results|[{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f87683ab41308217af84773594008504a817c80082520894be53e587975603a13d0923d0aa6d37c5233dd750865af3107a400080c001a04ae265f17e882b922d39f0f0cb058a6378df1dc89da8b8165ab6bc53851b426aa0682079486be2aa23bc7514477473362cc7d63afa12c99f7d8fb15e68d69d9a48"],"id":1},{"jsonrpc":"2.0","method":"eth_sendRawTransaction","params":["0x02f8748201a415843b9aca31843b9aca3182520894f80267194936da1e98db10bce06f3147d580a62e880de0b6b3a764000080c001a0b50ee053102360ff5fedf0933b912b7e140c90fe57fa07a0cebe70dbd72339dda072974cb7bfe5c3dc54dde110e2b049408ccab8a879949c3b4d42a3a7555a618b"],"id":1},{"bad":"json"},{"jsonrpc":"2.0","method":"eth_fooTheBar","params":[],"id":123}]|[{"jsonrpc":"2.0","error":{"code":-32000,"message":"invalid sender"},"id":1},{"id": 123, "jsonrpc": "2.0", "result": "dummy"},{"jsonrpc":"2.0","error":{"code":-32600,"message":"invalid JSON-RPC version"},"id":null},{"jsonrpc":"2.0","error":{"code":-32001,"message":"rpc method is not whitelisted"},"id":123}]

@ -9,6 +9,7 @@ import (
"fmt"
"io"
"math"
"math/big"
"net/http"
"regexp"
"strconv"
@ -18,6 +19,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/go-redis/redis/v8"
@ -56,6 +58,7 @@ type Server struct {
mainLim FrontendRateLimiter
overrideLims map[string]FrontendRateLimiter
senderLim FrontendRateLimiter
allowedChainIds []*big.Int
limExemptOrigins []*regexp.Regexp
limExemptUserAgents []*regexp.Regexp
globallyLimitedMethods map[string]bool
@ -173,6 +176,7 @@ func NewServer(
overrideLims: overrideLims,
globallyLimitedMethods: globalMethodLims,
senderLim: senderLim,
allowedChainIds: senderRateLimitConfig.AllowedChainIds,
limExemptOrigins: limExemptOrigins,
limExemptUserAgents: limExemptUserAgents,
}, nil
@ -654,6 +658,13 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error {
return ErrInvalidParams(err.Error())
}
// Check if the transaction is for the expected chain,
// otherwise reject before rate limiting to avoid replay attacks.
if !s.isAllowedChainId(tx.ChainId()) {
log.Debug("chain id is not allowed", "req_id", GetReqID(ctx))
return txpool.ErrInvalidSender
}
// Convert the transaction into a Message object so that we can get the
// sender. This method performs an ecrecover, which can be expensive.
msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil)
@ -674,6 +685,18 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error {
return nil
}
func (s *Server) isAllowedChainId(chainId *big.Int) bool {
if s.allowedChainIds == nil || len(s.allowedChainIds) == 0 {
return true
}
for _, id := range s.allowedChainIds {
if chainId.Cmp(id) == 0 {
return true
}
}
return false
}
func setCacheHeader(w http.ResponseWriter, cached bool) {
if cached {
w.Header().Set(cacheStatusHdr, "HIT")