Skip to content
This repository was archived by the owner on Oct 20, 2024. It is now read-only.

Commit 82df661

Browse files
authored
Allow relayer throttling params to be set through env vars (#112)
1 parent c46f6b9 commit 82df661

File tree

4 files changed

+65
-22
lines changed

4 files changed

+65
-22
lines changed

internal/config/values.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"math/big"
66
"strings"
7+
"time"
78

89
"github.com/ethereum/go-ethereum/common"
910
"github.com/gin-gonic/gin"
@@ -23,6 +24,10 @@ type Values struct {
2324
MaxOpsForUnstakedSender int
2425
Beneficiary string
2526

27+
// Private mode variables.
28+
RelayerBannedThreshold int
29+
RelayerBannedTimeWindow time.Duration
30+
2631
// Searcher mode variables.
2732
EthBuilderUrl string
2833
BlocksInTheFuture int
@@ -82,6 +87,8 @@ func GetValues() *Values {
8287
_ = viper.BindEnv("erc4337_bundler_beneficiary")
8388
_ = viper.BindEnv("erc4337_bundler_max_verification_gas")
8489
_ = viper.BindEnv("erc4337_bundler_max_ops_for_unstaked_sender")
90+
_ = viper.BindEnv("erc4337_bundler_relayer_banned_threshold")
91+
_ = viper.BindEnv("erc4337_bundler_relayer_banned_time_window")
8592
_ = viper.BindEnv("erc4337_bundler_eth_builder_url")
8693
_ = viper.BindEnv("erc4337_bundler_blocks_in_the_future")
8794
_ = viper.BindEnv("erc4337_bundler_debug_mode")
@@ -126,6 +133,8 @@ func GetValues() *Values {
126133
beneficiary := viper.GetString("erc4337_bundler_beneficiary")
127134
maxVerificationGas := big.NewInt(int64(viper.GetInt("erc4337_bundler_max_verification_gas")))
128135
maxOpsForUnstakedSender := viper.GetInt("erc4337_bundler_max_ops_for_unstaked_sender")
136+
relayerBannedThreshold := viper.GetInt("erc4337_bundler_relayer_banned_threshold")
137+
relayerBannedTimeWindow := viper.GetInt("erc4337_bundler_relayer_banned_time_window") * int(time.Second)
129138
ethBuilderUrl := viper.GetString("erc4337_bundler_eth_builder_url")
130139
blocksInTheFuture := viper.GetInt("erc4337_bundler_blocks_in_the_future")
131140
debugMode := viper.GetBool("erc4337_bundler_debug_mode")
@@ -139,6 +148,8 @@ func GetValues() *Values {
139148
Beneficiary: beneficiary,
140149
MaxVerificationGas: maxVerificationGas,
141150
MaxOpsForUnstakedSender: maxOpsForUnstakedSender,
151+
RelayerBannedThreshold: relayerBannedThreshold,
152+
RelayerBannedTimeWindow: time.Duration(relayerBannedTimeWindow),
142153
EthBuilderUrl: ethBuilderUrl,
143154
BlocksInTheFuture: blocksInTheFuture,
144155
DebugMode: debugMode,

internal/start/private.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,15 @@ func PrivateMode() {
6868
conf.MaxOpsForUnstakedSender,
6969
conf.BundlerCollectorTracer,
7070
)
71+
7172
relayer := relay.New(db, eoa, eth, chain, beneficiary, logr)
73+
if conf.RelayerBannedThreshold > 0 {
74+
relayer.SetBannedThreshold(conf.RelayerBannedThreshold)
75+
}
76+
if conf.RelayerBannedTimeWindow > 0 {
77+
relayer.SetBannedTimeWindow(conf.RelayerBannedTimeWindow)
78+
}
79+
7280
paymaster := paymaster.New(db)
7381

7482
// Init Client

pkg/modules/relay/relayer.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package relay
44

55
import (
6+
"fmt"
67
"math/big"
78
"net/http"
89
"time"
@@ -35,13 +36,14 @@ import (
3536
// This will only work in the case of a private mempool and will not work in the P2P case where ops are
3637
// propagated through the network and it is impossible to trust a sender's identifier.
3738
type Relayer struct {
38-
db *badger.DB
39-
eoa *signer.EOA
40-
eth *ethclient.Client
41-
chainID *big.Int
42-
beneficiary common.Address
43-
logger logr.Logger
44-
bannedThreshold int
39+
db *badger.DB
40+
eoa *signer.EOA
41+
eth *ethclient.Client
42+
chainID *big.Int
43+
beneficiary common.Address
44+
logger logr.Logger
45+
bannedThreshold int
46+
bannedTimeWindow time.Duration
4547
}
4648

4749
// New initializes a new EOA relayer for sending batches to the EntryPoint with IP throttling protection.
@@ -54,13 +56,14 @@ func New(
5456
l logr.Logger,
5557
) *Relayer {
5658
return &Relayer{
57-
db: db,
58-
eoa: eoa,
59-
eth: eth,
60-
chainID: chainID,
61-
beneficiary: beneficiary,
62-
logger: l.WithName("relayer"),
63-
bannedThreshold: DefaultBanThreshold,
59+
db: db,
60+
eoa: eoa,
61+
eth: eth,
62+
chainID: chainID,
63+
beneficiary: beneficiary,
64+
logger: l.WithName("relayer"),
65+
bannedThreshold: DefaultBanThreshold,
66+
bannedTimeWindow: DefaultBanTimeWindow,
6467
}
6568
}
6669

@@ -71,6 +74,12 @@ func (r *Relayer) SetBannedThreshold(limit int) {
7174
r.bannedThreshold = limit
7275
}
7376

77+
// SetBannedTimeWindow sets the limit for how long a banned client will be rejected for. The default value is
78+
// 24 hours.
79+
func (r *Relayer) SetBannedTimeWindow(limit time.Duration) {
80+
r.bannedTimeWindow = limit
81+
}
82+
7483
// FilterByClientID is a custom Gin middleware used to prevent requests from banned clients from adding their
7584
// userOps to the mempool. Identifiers are prioritized by the following values:
7685
// 1. X-Forwarded-By header: The first IP address in the array which is assumed to be the client
@@ -82,6 +91,7 @@ func (r *Relayer) FilterByClientID() gin.HandlerFunc {
8291
l := r.logger.WithName("filter_by_client")
8392

8493
isBanned := false
94+
var os, oi int
8595
cid := ginutils.GetClientIPFromXFF(c)
8696
err := r.db.View(func(txn *badger.Txn) error {
8797
opsSeen, opsIncluded, err := getOpsCountByClientID(txn, cid)
@@ -99,6 +109,8 @@ func (r *Relayer) FilterByClientID() gin.HandlerFunc {
99109
}
100110

101111
isBanned = true
112+
os = opsSeen
113+
oi = opsIncluded
102114
return nil
103115
})
104116
if err != nil {
@@ -110,6 +122,18 @@ func (r *Relayer) FilterByClientID() gin.HandlerFunc {
110122
if isBanned {
111123
l.Info("client banned")
112124
c.Status(http.StatusForbidden)
125+
c.JSON(
126+
http.StatusForbidden,
127+
gin.H{
128+
"error": fmt.Sprintf(
129+
"opsSeen (%d) exceeds opsIncluded (%d) by allowed threshold (%d). Wait %s to retry.",
130+
os,
131+
oi,
132+
r.bannedThreshold,
133+
r.bannedTimeWindow,
134+
),
135+
},
136+
)
113137
c.Abort()
114138
} else {
115139
l.Info("client ok")
@@ -150,7 +174,7 @@ func (r *Relayer) MapUserOpHashToClientID() gin.HandlerFunc {
150174
return err
151175
}
152176

153-
return incrementOpsSeenByClientID(txn, cid)
177+
return incrementOpsSeenByClientID(txn, cid, r.bannedTimeWindow)
154178
})
155179
if err != nil {
156180
l.Error(err, "map_userop_hash_to_client_id failed")
@@ -240,7 +264,7 @@ func (r *Relayer) SendUserOperation() modules.BatchHandlerFunc {
240264

241265
hashes := getUserOpHashesFromOps(ctx.EntryPoint, ctx.ChainID, ctx.Batch...)
242266
del = append([]string{}, hashes...)
243-
return incrementOpsIncludedByUserOpHashes(txn, hashes...)
267+
return incrementOpsIncludedByUserOpHashes(txn, r.bannedTimeWindow, hashes...)
244268
})
245269
if err != nil {
246270
return err

pkg/modules/relay/utils.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import (
1313

1414
const NoBanThreshold = 0
1515
const DefaultBanThreshold = 3
16-
const timeWindow = 7 * 24 * time.Hour
16+
const DefaultBanTimeWindow = 7 * 24 * time.Hour
1717

1818
var (
1919
keyPrefix = dbutils.JoinValues("relay")
@@ -68,18 +68,18 @@ func getOpsCountByClientID(txn *badger.Txn, clientID string) (opsSeen int, opsIn
6868
return opsSeen, opsIncluded, nil
6969
}
7070

71-
func incrementOpsSeenByClientID(txn *badger.Txn, clientID string) error {
71+
func incrementOpsSeenByClientID(txn *badger.Txn, clientID string, ttl time.Duration) error {
7272
opsSeen, opsIncluded, err := getOpsCountByClientID(txn, clientID)
7373
if err != nil {
7474
return err
7575
}
7676

7777
val := dbutils.JoinValues(strconv.Itoa(opsSeen+1), strconv.Itoa(opsIncluded))
78-
e := badger.NewEntry(getOpsCountKey(clientID), []byte(val)).WithTTL(timeWindow)
78+
e := badger.NewEntry(getOpsCountKey(clientID), []byte(val)).WithTTL(ttl)
7979
return txn.SetEntry(e)
8080
}
8181

82-
func incrementOpsIncludedByUserOpHashes(txn *badger.Txn, userOpHashes ...string) error {
82+
func incrementOpsIncludedByUserOpHashes(txn *badger.Txn, ttl time.Duration, userOpHashes ...string) error {
8383
for _, hash := range userOpHashes {
8484
item, err := txn.Get(getUserOpHashKey(hash))
8585
if err != nil && err == badger.ErrKeyNotFound {
@@ -106,13 +106,13 @@ func incrementOpsIncludedByUserOpHashes(txn *badger.Txn, userOpHashes ...string)
106106

107107
var opsCountValue string
108108
if opsSeen == 0 && opsIncluded == 0 {
109-
// Op has been in the mempool longer than timeWindow
109+
// Op has been in the mempool longer than TTL
110110
opsCountValue = dbutils.JoinValues(strconv.Itoa(opsSeen+1), strconv.Itoa(opsIncluded+1))
111111
} else {
112112
opsCountValue = dbutils.JoinValues(strconv.Itoa(opsSeen), strconv.Itoa(opsIncluded+1))
113113
}
114114

115-
e := badger.NewEntry(getOpsCountKey(cid), []byte(opsCountValue)).WithTTL(timeWindow)
115+
e := badger.NewEntry(getOpsCountKey(cid), []byte(opsCountValue)).WithTTL(ttl)
116116
if err := txn.SetEntry(e); err != nil {
117117
return err
118118
}

0 commit comments

Comments
 (0)