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

Commit 421b66a

Browse files
authored
Allow configurable max ops for unstaked sender (#80)
1 parent 2858a13 commit 421b66a

File tree

10 files changed

+147
-76
lines changed

10 files changed

+147
-76
lines changed

internal/config/values.go

Lines changed: 30 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,14 @@ import (
1414

1515
type Values struct {
1616
// Documented variables.
17-
PrivateKey string
18-
EthClientUrl string
19-
Port int
20-
DataDirectory string
21-
SupportedEntryPoints []common.Address
22-
MaxVerificationGas *big.Int
23-
Beneficiary string
17+
PrivateKey string
18+
EthClientUrl string
19+
Port int
20+
DataDirectory string
21+
SupportedEntryPoints []common.Address
22+
MaxVerificationGas *big.Int
23+
MaxOpsForUnstakedSender int
24+
Beneficiary string
2425

2526
// Undocumented variables.
2627
DebugMode bool
@@ -46,6 +47,7 @@ func GetValues() *Values {
4647
viper.SetDefault("erc4337_bundler_data_directory", "/tmp/stackup_bundler")
4748
viper.SetDefault("erc4337_bundler_supported_entry_points", "0x0F46c65C17AA6b4102046935F33301f0510B163A")
4849
viper.SetDefault("erc4337_bundler_max_verification_gas", 1500000)
50+
viper.SetDefault("erc4337_bundler_max_ops_for_unstaked_sender", 4)
4951
viper.SetDefault("erc4337_bundler_debug_mode", false)
5052
viper.SetDefault("erc4337_bundler_gin_mode", gin.ReleaseMode)
5153

@@ -63,30 +65,15 @@ func GetValues() *Values {
6365
}
6466

6567
// Read in from environment variables
66-
if err := viper.BindEnv("erc4337_bundler_eth_client_url"); err != nil {
67-
panic(err)
68-
}
69-
if err := viper.BindEnv("erc4337_bundler_private_key"); err != nil {
70-
panic(err)
71-
}
72-
if err := viper.BindEnv("erc4337_bundler_port"); err != nil {
73-
panic(err)
74-
}
75-
if err := viper.BindEnv("erc4337_bundler_data_directory"); err != nil {
76-
panic(err)
77-
}
78-
if err := viper.BindEnv("erc4337_bundler_supported_entry_points"); err != nil {
79-
panic(err)
80-
}
81-
if err := viper.BindEnv("erc4337_bundler_beneficiary"); err != nil {
82-
panic(err)
83-
}
84-
if err := viper.BindEnv("erc4337_bundler_max_verification_gas"); err != nil {
85-
panic(err)
86-
}
87-
if err := viper.BindEnv("erc4337_bundler_gin_mode"); err != nil {
88-
panic(err)
89-
}
68+
_ = viper.BindEnv("erc4337_bundler_eth_client_url")
69+
_ = viper.BindEnv("erc4337_bundler_private_key")
70+
_ = viper.BindEnv("erc4337_bundler_port")
71+
_ = viper.BindEnv("erc4337_bundler_data_directory")
72+
_ = viper.BindEnv("erc4337_bundler_supported_entry_points")
73+
_ = viper.BindEnv("erc4337_bundler_beneficiary")
74+
_ = viper.BindEnv("erc4337_bundler_max_verification_gas")
75+
_ = viper.BindEnv("erc4337_bundler_max_ops_for_unstaked_sender")
76+
_ = viper.BindEnv("erc4337_bundler_gin_mode")
9077

9178
// Validate required variables
9279
if !viper.IsSet("erc4337_bundler_eth_client_url") ||
@@ -120,18 +107,20 @@ func GetValues() *Values {
120107
supportedEntryPoints := envArrayToAddressSlice(viper.GetString("erc4337_bundler_supported_entry_points"))
121108
beneficiary := viper.GetString("erc4337_bundler_beneficiary")
122109
maxVerificationGas := big.NewInt(int64(viper.GetInt("erc4337_bundler_max_verification_gas")))
110+
maxOpsForUnstakedSender := viper.GetInt("erc4337_bundler_max_ops_for_unstaked_sender")
123111
debugMode := viper.GetBool("erc4337_bundler_debug_mode")
124112
ginMode := viper.GetString("erc4337_bundler_gin_mode")
125113
return &Values{
126-
PrivateKey: privateKey,
127-
EthClientUrl: ethClientUrl,
128-
Port: port,
129-
DataDirectory: dataDirectory,
130-
SupportedEntryPoints: supportedEntryPoints,
131-
Beneficiary: beneficiary,
132-
MaxVerificationGas: maxVerificationGas,
133-
DebugMode: debugMode,
134-
GinMode: ginMode,
135-
BundlerCollectorTracer: bct,
114+
PrivateKey: privateKey,
115+
EthClientUrl: ethClientUrl,
116+
Port: port,
117+
DataDirectory: dataDirectory,
118+
SupportedEntryPoints: supportedEntryPoints,
119+
Beneficiary: beneficiary,
120+
MaxVerificationGas: maxVerificationGas,
121+
MaxOpsForUnstakedSender: maxOpsForUnstakedSender,
122+
DebugMode: debugMode,
123+
GinMode: ginMode,
124+
BundlerCollectorTracer: bct,
136125
}
137126
}

internal/start/private.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,12 @@ func PrivateMode() {
7777
log.Fatal(err)
7878
}
7979

80-
check := checks.New(rpc, conf.MaxVerificationGas, conf.BundlerCollectorTracer)
80+
check := checks.New(
81+
rpc,
82+
conf.MaxVerificationGas,
83+
conf.MaxOpsForUnstakedSender,
84+
conf.BundlerCollectorTracer,
85+
)
8186
relayer := relay.New(db, eoa, eth, chain, beneficiary, logr)
8287
paymaster := paymaster.New(db)
8388

internal/testutils/constants.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ import (
99
)
1010

1111
var (
12-
OneETH = big.NewInt(1000000000000000000)
13-
DefaultUnstakeDelaySec = uint32(86400)
14-
ValidAddress = common.HexToAddress("0x7357b8a705328FC283dF72D7Ac546895B596DC12")
15-
ChainID = big.NewInt(1)
16-
StakedDepositInfo = &entrypoint.IStakeManagerDepositInfo{
12+
OneETH = big.NewInt(1000000000000000000)
13+
DefaultUnstakeDelaySec = uint32(86400)
14+
ValidAddress1 = common.HexToAddress("0x7357b8a705328FC283dF72D7Ac546895B596DC12")
15+
ValidAddress2 = common.HexToAddress("0x7357c9504B8686c008CCcD6ea47f1c21B7475dE3")
16+
ChainID = big.NewInt(1)
17+
MaxOpsForUnstakedSender = 1
18+
StakedDepositInfo = &entrypoint.IStakeManagerDepositInfo{
1719
Deposit: big.NewInt(OneETH.Int64()),
1820
Staked: true,
1921
Stake: big.NewInt(OneETH.Int64()),

pkg/client/debug.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ func (d *Debug) SendBundleNow() (string, error) {
7777
if err != nil {
7878
return "", err
7979
}
80+
if len(batch) > 1 {
81+
batch = batch[:1]
82+
}
8083

8184
est, revert, err := entrypoint.EstimateHandleOpsGas(
8285
d.eoa,
@@ -107,6 +110,10 @@ func (d *Debug) SendBundleNow() (string, error) {
107110
return "", errors.New("debug: bad batch during call")
108111
}
109112

113+
if err := d.mempool.RemoveOps(d.entrypoint, batch...); err != nil {
114+
return "", err
115+
}
116+
110117
return txn.Hash().String(), nil
111118
}
112119

pkg/mempool/instance_test.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ func TestAddOpToMempool(t *testing.T) {
1414
db := testutils.DBMock()
1515
defer db.Close()
1616
mem, _ := New(db)
17-
ep := testutils.ValidAddress
17+
ep := testutils.ValidAddress1
1818
op := testutils.MockValidInitUserOp()
1919

2020
if err := mem.AddOp(ep, op); err != nil {
@@ -40,7 +40,7 @@ func TestReplaceOpInMempool(t *testing.T) {
4040
db := testutils.DBMock()
4141
defer db.Close()
4242
mem, _ := New(db)
43-
ep := testutils.ValidAddress
43+
ep := testutils.ValidAddress1
4444
op1 := testutils.MockValidInitUserOp()
4545
op2 := testutils.MockValidInitUserOp()
4646
op2.MaxPriorityFeePerGas = big.NewInt(0).Add(op1.MaxPriorityFeePerGas, common.Big1)
@@ -70,7 +70,7 @@ func TestRemoveOpsFromMempool(t *testing.T) {
7070
db := testutils.DBMock()
7171
defer db.Close()
7272
mem, _ := New(db)
73-
ep := testutils.ValidAddress
73+
ep := testutils.ValidAddress1
7474
op := testutils.MockValidInitUserOp()
7575

7676
if err := mem.AddOp(ep, op); err != nil {
@@ -96,9 +96,10 @@ func TestBundleOpsFromMempool(t *testing.T) {
9696
db := testutils.DBMock()
9797
defer db.Close()
9898
mem, _ := New(db)
99-
ep := testutils.ValidAddress
99+
ep := testutils.ValidAddress1
100100
op1 := testutils.MockValidInitUserOp()
101101
op2 := testutils.MockValidInitUserOp()
102+
op2.Sender = testutils.ValidAddress2
102103
op2.Nonce = big.NewInt(0).Add(op1.Nonce, common.Big1)
103104
op2.MaxPriorityFeePerGas = big.NewInt(0).Add(op1.MaxPriorityFeePerGas, common.Big1)
104105

@@ -128,7 +129,7 @@ func TestNewMempoolLoadsFromDisk(t *testing.T) {
128129
db := testutils.DBMock()
129130
defer db.Close()
130131
mem1, _ := New(db)
131-
ep := testutils.ValidAddress
132+
ep := testutils.ValidAddress1
132133
op1 := testutils.MockValidInitUserOp()
133134
op2 := testutils.MockValidInitUserOp()
134135
op2.Nonce = big.NewInt(0).Add(op1.Nonce, common.Big1)

pkg/mempool/queue.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ func (q *userOpQueues) Next(entryPoint common.Address) []*userop.UserOperation {
6969
batch = append(batch, n.Value.(*userop.UserOperation))
7070
}
7171

72+
// Ensure that ops with same sender is ordered by ascending nonce regardless of MaxPriorityFeePerGas
73+
for i := 0; i < len(batch); i++ {
74+
for j := i + 1; j < len(batch); j++ {
75+
if batch[i].Sender == batch[j].Sender && batch[i].Nonce.Cmp(batch[j].Nonce) > 0 {
76+
batch[i], batch[j] = batch[j], batch[i]
77+
}
78+
}
79+
}
80+
7281
return batch
7382
}
7483

pkg/modules/checks/pendingops.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package checks
22

33
import (
44
"errors"
5+
"fmt"
56
"math/big"
67

78
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
@@ -11,8 +12,13 @@ import (
1112
//
1213
// 1. Sender doesn't have another UserOperation already present in the pool.
1314
// 2. It replaces an existing UserOperation with same nonce and higher fee.
14-
// 3. Sender is staked and is allowed multiple UserOperations in the pool.
15-
func ValidatePendingOps(op *userop.UserOperation, penOps []*userop.UserOperation, gs GetStakeFunc) error {
15+
// 3. Sender is staked and is allowed uncapped UserOperations in the pool.
16+
func ValidatePendingOps(
17+
op *userop.UserOperation,
18+
penOps []*userop.UserOperation,
19+
maxOpsForUnstakedSender int,
20+
gs GetStakeFunc,
21+
) error {
1622
dep, err := gs(op.Sender)
1723
if err != nil {
1824
return err
@@ -38,9 +44,10 @@ func ValidatePendingOps(op *userop.UserOperation, penOps []*userop.UserOperation
3844
if op.MaxFeePerGas.Cmp(mf) != 0 {
3945
return errors.New("pending ops: replaced op must have an equally higher max fee")
4046
}
41-
} else if !dep.Staked {
42-
return errors.New(
43-
"pending ops: sender must be staked to have multiple ops in the mempool",
47+
} else if !dep.Staked && len(penOps) >= maxOpsForUnstakedSender {
48+
return fmt.Errorf(
49+
"pending ops: sender must be staked to have more than %d ops in the mempool",
50+
maxOpsForUnstakedSender,
4451
)
4552
}
4653
}

pkg/modules/checks/pendingops_test.go

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ import (
1313
func TestNoPendingOps(t *testing.T) {
1414
penOps := []*userop.UserOperation{}
1515
op := testutils.MockValidInitUserOp()
16-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
16+
err := ValidatePendingOps(
17+
op,
18+
penOps,
19+
testutils.MaxOpsForUnstakedSender,
20+
testutils.MockGetNotStakeZeroDeposit,
21+
)
1722

1823
if err != nil {
1924
t.Fatalf("got err %v, want nil", err)
@@ -27,7 +32,12 @@ func TestPendingOpsNotStaked(t *testing.T) {
2732
penOps := []*userop.UserOperation{penOp}
2833
op := testutils.MockValidInitUserOp()
2934
op.Nonce = big.NewInt(0).Add(penOp.Nonce, common.Big1)
30-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
35+
err := ValidatePendingOps(
36+
op,
37+
penOps,
38+
testutils.MaxOpsForUnstakedSender,
39+
testutils.MockGetNotStakeZeroDeposit,
40+
)
3141

3242
if err == nil {
3343
t.Fatal("got nil, want err")
@@ -41,7 +51,12 @@ func TestPendingOpsStaked(t *testing.T) {
4151
penOps := []*userop.UserOperation{penOp}
4252
op := testutils.MockValidInitUserOp()
4353
op.Nonce = big.NewInt(0).Add(penOp.Nonce, common.Big1)
44-
err := ValidatePendingOps(op, penOps, testutils.MockGetStakeZeroDeposit)
54+
err := ValidatePendingOps(
55+
op,
56+
penOps,
57+
testutils.MaxOpsForUnstakedSender,
58+
testutils.MockGetStakeZeroDeposit,
59+
)
4560

4661
if err != nil {
4762
t.Fatalf("got err %v, want nil", err)
@@ -56,7 +71,12 @@ func TestReplaceOp(t *testing.T) {
5671
op := testutils.MockValidInitUserOp()
5772
op.MaxPriorityFeePerGas = big.NewInt(0).Add(penOp.MaxPriorityFeePerGas, common.Big1)
5873
op.MaxFeePerGas = big.NewInt(0).Add(penOp.MaxFeePerGas, common.Big1)
59-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
74+
err := ValidatePendingOps(
75+
op,
76+
penOps,
77+
testutils.MaxOpsForUnstakedSender,
78+
testutils.MockGetNotStakeZeroDeposit,
79+
)
6080

6181
if err != nil {
6282
t.Fatalf("got err %v, want nil", err)
@@ -70,7 +90,12 @@ func TestReplaceOpLowerMPF(t *testing.T) {
7090
penOps := []*userop.UserOperation{penOp}
7191
op := testutils.MockValidInitUserOp()
7292
op.MaxPriorityFeePerGas = big.NewInt(0).Sub(penOp.MaxPriorityFeePerGas, common.Big1)
73-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
93+
err := ValidatePendingOps(
94+
op,
95+
penOps,
96+
testutils.MaxOpsForUnstakedSender,
97+
testutils.MockGetNotStakeZeroDeposit,
98+
)
7499

75100
if err == nil {
76101
t.Fatal("got nil, want err")
@@ -84,7 +109,12 @@ func TestReplaceOpEqualMPF(t *testing.T) {
84109
penOps := []*userop.UserOperation{penOp}
85110
op := testutils.MockValidInitUserOp()
86111
op.MaxPriorityFeePerGas = big.NewInt(0).Add(penOp.MaxPriorityFeePerGas, common.Big0)
87-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
112+
err := ValidatePendingOps(
113+
op,
114+
penOps,
115+
testutils.MaxOpsForUnstakedSender,
116+
testutils.MockGetNotStakeZeroDeposit,
117+
)
88118

89119
if err == nil {
90120
t.Fatal("got nil, want err")
@@ -99,7 +129,12 @@ func TestReplaceOpNotEqualIncMF(t *testing.T) {
99129
op := testutils.MockValidInitUserOp()
100130
op.MaxPriorityFeePerGas = big.NewInt(0).Add(penOp.MaxPriorityFeePerGas, common.Big2)
101131
op.MaxFeePerGas = big.NewInt(0).Add(penOp.MaxFeePerGas, common.Big1)
102-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
132+
err := ValidatePendingOps(
133+
op,
134+
penOps,
135+
testutils.MaxOpsForUnstakedSender,
136+
testutils.MockGetNotStakeZeroDeposit,
137+
)
103138

104139
if err == nil {
105140
t.Fatal("got nil, want err")
@@ -114,7 +149,12 @@ func TestReplaceOpSameMF(t *testing.T) {
114149
op := testutils.MockValidInitUserOp()
115150
op.MaxPriorityFeePerGas = big.NewInt(0).Add(penOp.MaxPriorityFeePerGas, common.Big1)
116151
op.MaxFeePerGas = big.NewInt(0).Add(penOp.MaxFeePerGas, common.Big0)
117-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
152+
err := ValidatePendingOps(
153+
op,
154+
penOps,
155+
testutils.MaxOpsForUnstakedSender,
156+
testutils.MockGetNotStakeZeroDeposit,
157+
)
118158

119159
if err == nil {
120160
t.Fatal("got nil, want err")
@@ -129,7 +169,12 @@ func TestReplaceOpDecMF(t *testing.T) {
129169
op := testutils.MockValidInitUserOp()
130170
op.MaxPriorityFeePerGas = big.NewInt(0).Add(penOp.MaxPriorityFeePerGas, common.Big1)
131171
op.MaxFeePerGas = big.NewInt(0).Sub(penOp.MaxFeePerGas, common.Big1)
132-
err := ValidatePendingOps(op, penOps, testutils.MockGetNotStakeZeroDeposit)
172+
err := ValidatePendingOps(
173+
op,
174+
penOps,
175+
testutils.MaxOpsForUnstakedSender,
176+
testutils.MockGetNotStakeZeroDeposit,
177+
)
133178

134179
if err == nil {
135180
t.Fatal("got nil, want err")

0 commit comments

Comments
 (0)