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:
commit
73ce23c025
@ -2,6 +2,7 @@ package proxyd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@ -121,9 +122,10 @@ type BatchConfig struct {
|
|||||||
// SenderRateLimitConfig configures the sender-based rate limiter
|
// SenderRateLimitConfig configures the sender-based rate limiter
|
||||||
// for eth_sendRawTransaction requests.
|
// for eth_sendRawTransaction requests.
|
||||||
type SenderRateLimitConfig struct {
|
type SenderRateLimitConfig struct {
|
||||||
Enabled bool
|
Enabled bool
|
||||||
Interval TOMLDuration
|
Interval TOMLDuration
|
||||||
Limit int
|
Limit int
|
||||||
|
AllowedChainIds []*big.Int `toml:"allowed_chain_ids"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
@ -20,12 +20,10 @@ const txHex1 = "0x02f8b28201a406849502f931849502f931830147f9948f3ddd0fbf3e78ca1d
|
|||||||
"1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" +
|
"1145d2f3e759d49209fe96011ac012884ec5b017a0763b58f6fa6096e6ba28ee" +
|
||||||
"08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"
|
"08bfac58f58fb3b8bcef5af98578bdeaddf40bde42"
|
||||||
|
|
||||||
const txHex2 = "0xf8aa82afd2830f4240830493e094464959ad46e64046b891f562cff202a465d5" +
|
const txHex2 = "0x02f8758201a48217fd84773594008504a817c80082520894be53e587975603" +
|
||||||
"22f380b844d5bade070000000000000000000000004200000000000000000000" +
|
"a13d0923d0aa6d37c5233dd750865af3107a400080c080a04aefbd5819c35729" +
|
||||||
"0000000000000000060000000000000000000000000000000000000000000000" +
|
"138fe26b6ae1783ebf08d249b356c2f920345db97877f3f7a008d5ae92560a3c" +
|
||||||
"0000000025ef43fc0038a05d8ea9837ea81469bda4dadbe852fdd37fcfbcd666" +
|
"65f723439887205713af7ce7d7f6b24fba198f2afa03435867"
|
||||||
"5641a35e4726fbc04364e7a0107e20bb34aea53c695a551204a11d42fe465055" +
|
|
||||||
"510ee240e8f884fb70289be6"
|
|
||||||
|
|
||||||
const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}`
|
const dummyRes = `{"id": 123, "jsonrpc": "2.0", "result": "dummy"}`
|
||||||
|
|
||||||
@ -81,10 +79,10 @@ func TestSenderRateLimitLimiting(t *testing.T) {
|
|||||||
// should be rate limited.
|
// should be rate limited.
|
||||||
res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1))
|
res1, code1, err := client.SendRequest(makeSendRawTransaction(txHex1))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1))
|
|
||||||
require.NoError(t, err)
|
|
||||||
RequireEqualJSON(t, []byte(dummyRes), res1)
|
RequireEqualJSON(t, []byte(dummyRes), res1)
|
||||||
require.Equal(t, 200, code1)
|
require.Equal(t, 200, code1)
|
||||||
|
res2, code2, err := client.SendRequest(makeSendRawTransaction(txHex1))
|
||||||
|
require.NoError(t, err)
|
||||||
RequireEqualJSON(t, []byte(limRes), res2)
|
RequireEqualJSON(t, []byte(limRes), res2)
|
||||||
require.Equal(t, 429, code2)
|
require.Equal(t, 429, code2)
|
||||||
|
|
||||||
|
@ -21,3 +21,4 @@ eth_sendRawTransaction = "main"
|
|||||||
enabled = true
|
enabled = true
|
||||||
interval = "1s"
|
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}
|
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 - 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"}
|
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"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
"math"
|
||||||
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
@ -18,6 +19,7 @@ import (
|
|||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/core"
|
"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/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/go-redis/redis/v8"
|
"github.com/go-redis/redis/v8"
|
||||||
@ -56,6 +58,7 @@ type Server struct {
|
|||||||
mainLim FrontendRateLimiter
|
mainLim FrontendRateLimiter
|
||||||
overrideLims map[string]FrontendRateLimiter
|
overrideLims map[string]FrontendRateLimiter
|
||||||
senderLim FrontendRateLimiter
|
senderLim FrontendRateLimiter
|
||||||
|
allowedChainIds []*big.Int
|
||||||
limExemptOrigins []*regexp.Regexp
|
limExemptOrigins []*regexp.Regexp
|
||||||
limExemptUserAgents []*regexp.Regexp
|
limExemptUserAgents []*regexp.Regexp
|
||||||
globallyLimitedMethods map[string]bool
|
globallyLimitedMethods map[string]bool
|
||||||
@ -173,6 +176,7 @@ func NewServer(
|
|||||||
overrideLims: overrideLims,
|
overrideLims: overrideLims,
|
||||||
globallyLimitedMethods: globalMethodLims,
|
globallyLimitedMethods: globalMethodLims,
|
||||||
senderLim: senderLim,
|
senderLim: senderLim,
|
||||||
|
allowedChainIds: senderRateLimitConfig.AllowedChainIds,
|
||||||
limExemptOrigins: limExemptOrigins,
|
limExemptOrigins: limExemptOrigins,
|
||||||
limExemptUserAgents: limExemptUserAgents,
|
limExemptUserAgents: limExemptUserAgents,
|
||||||
}, nil
|
}, nil
|
||||||
@ -654,6 +658,13 @@ func (s *Server) rateLimitSender(ctx context.Context, req *RPCReq) error {
|
|||||||
return ErrInvalidParams(err.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
|
// Convert the transaction into a Message object so that we can get the
|
||||||
// sender. This method performs an ecrecover, which can be expensive.
|
// sender. This method performs an ecrecover, which can be expensive.
|
||||||
msg, err := core.TransactionToMessage(tx, types.LatestSignerForChainID(tx.ChainId()), nil)
|
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
|
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) {
|
func setCacheHeader(w http.ResponseWriter, cached bool) {
|
||||||
if cached {
|
if cached {
|
||||||
w.Header().Set(cacheStatusHdr, "HIT")
|
w.Header().Set(cacheStatusHdr, "HIT")
|
||||||
|
Loading…
Reference in New Issue
Block a user