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

Commit b9733f0

Browse files
authored
Fix failing bundler spec tests (#77)
1 parent 4902e01 commit b9733f0

File tree

20 files changed

+509
-149
lines changed

20 files changed

+509
-149
lines changed

internal/start/private.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ func PrivateMode() {
8282
paymaster := paymaster.New(db)
8383

8484
// Init Client
85-
c := client.New(mem, chain, conf.SupportedEntryPoints)
85+
c := client.New(mem, chain, conf.SupportedEntryPoints, conf.MaxVerificationGas)
86+
c.SetGetUserOpReceiptFunc(client.GetUserOpReceiptWithEthClient(eth))
8687
c.UseLogger(logr)
8788
c.UseModules(
8889
check.ValidateOpValues(),
@@ -91,12 +92,6 @@ func PrivateMode() {
9192
paymaster.IncOpsSeen(),
9293
)
9394

94-
// init Debug
95-
var d *client.Debug
96-
if conf.DebugMode {
97-
d = client.NewDebug(eoa, eth, mem, chain, conf.SupportedEntryPoints[0], beneficiary)
98-
}
99-
10095
// Init Bundler
10196
b := bundler.New(mem, chain, conf.SupportedEntryPoints)
10297
b.UseLogger(logr)
@@ -109,6 +104,13 @@ func PrivateMode() {
109104
log.Fatal(err)
110105
}
111106

107+
// init Debug
108+
var d *client.Debug
109+
if conf.DebugMode {
110+
d = client.NewDebug(eoa, eth, mem, b, chain, conf.SupportedEntryPoints[0], beneficiary)
111+
relayer.SetBannedThreshold(relay.NoBanThreshold)
112+
}
113+
112114
// Init HTTP server
113115
gin.SetMode(conf.GinMode)
114116
r := gin.New()

internal/testutils/checksmock.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func MockGetNotStakeZeroDeposit(addr common.Address) (*entrypoint.IStakeManagerD
3131
return NonStakedZeroDepositInfo, nil
3232
}
3333

34-
func GetMockGasTipFunc(val *big.Int) func() (*big.Int, error) {
34+
func GetMockBaseFeeFunc(val *big.Int) func() (*big.Int, error) {
3535
return func() (*big.Int, error) {
3636
return val, nil
3737
}

pkg/bundler/bundler.go

Lines changed: 63 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ type Bundler struct {
2222
supportedEntryPoints []common.Address
2323
batchHandler modules.BatchHandlerFunc
2424
logger logr.Logger
25+
isRunning bool
26+
stop chan bool
2527
}
2628

2729
// New initializes a new EIP-4337 bundler which can be extended with modules for validating batches and
@@ -33,6 +35,8 @@ func New(mempool *mempool.Mempool, chainID *big.Int, supportedEntryPoints []comm
3335
supportedEntryPoints: supportedEntryPoints,
3436
batchHandler: noop.BatchHandler,
3537
logger: logger.NewZeroLogr().WithName("bundler"),
38+
isRunning: false,
39+
stop: make(chan bool),
3640
}
3741
}
3842

@@ -48,58 +52,78 @@ func (i *Bundler) UseModules(handlers ...modules.BatchHandlerFunc) {
4852

4953
// Run starts a goroutine that will continuously process batches from the mempool.
5054
func (i *Bundler) Run() error {
55+
if i.isRunning {
56+
return nil
57+
}
58+
5159
go func(i *Bundler) {
5260
logger := i.logger.WithName("run")
5361

5462
for {
55-
for _, ep := range i.supportedEntryPoints {
56-
start := time.Now()
57-
l := logger.
58-
WithValues("entrypoint", ep.String()).
59-
WithValues("chain_id", i.chainID.String())
60-
61-
batch, err := i.mempool.BundleOps(ep)
62-
if err != nil {
63-
l.Error(err, "bundler run error")
64-
continue
65-
}
66-
if len(batch) == 0 {
67-
continue
68-
}
63+
select {
64+
case <-i.stop:
65+
return
66+
default:
67+
for _, ep := range i.supportedEntryPoints {
68+
start := time.Now()
69+
l := logger.
70+
WithValues("entrypoint", ep.String()).
71+
WithValues("chain_id", i.chainID.String())
6972

70-
ctx := modules.NewBatchHandlerContext(batch, ep, i.chainID)
71-
if err := i.batchHandler(ctx); err != nil {
72-
l.Error(err, "bundler run error")
73-
continue
74-
}
73+
batch, err := i.mempool.BundleOps(ep)
74+
if err != nil {
75+
l.Error(err, "bundler run error")
76+
continue
77+
}
78+
if len(batch) == 0 {
79+
continue
80+
}
7581

76-
rmOps := append([]*userop.UserOperation{}, ctx.Batch...)
77-
rmOps = append(rmOps, ctx.PendingRemoval...)
78-
if err := i.mempool.RemoveOps(ep, rmOps...); err != nil {
79-
l.Error(err, "bundler run error")
80-
continue
81-
}
82+
ctx := modules.NewBatchHandlerContext(batch, ep, i.chainID)
83+
if err := i.batchHandler(ctx); err != nil {
84+
l.Error(err, "bundler run error")
85+
continue
86+
}
8287

83-
bat := []string{}
84-
for _, op := range ctx.Batch {
85-
bat = append(bat, op.GetUserOpHash(ep, i.chainID).String())
86-
}
87-
l = l.WithValues("batch_userop_hashes", bat)
88+
rmOps := append([]*userop.UserOperation{}, ctx.Batch...)
89+
rmOps = append(rmOps, ctx.PendingRemoval...)
90+
if err := i.mempool.RemoveOps(ep, rmOps...); err != nil {
91+
l.Error(err, "bundler run error")
92+
continue
93+
}
8894

89-
drp := []string{}
90-
for _, op := range ctx.PendingRemoval {
91-
drp = append(drp, op.GetUserOpHash(ep, i.chainID).String())
92-
}
93-
l = l.WithValues("dropped_userop_hashes", drp)
95+
bat := []string{}
96+
for _, op := range ctx.Batch {
97+
bat = append(bat, op.GetUserOpHash(ep, i.chainID).String())
98+
}
99+
l = l.WithValues("batch_userop_hashes", bat)
100+
101+
drp := []string{}
102+
for _, op := range ctx.PendingRemoval {
103+
drp = append(drp, op.GetUserOpHash(ep, i.chainID).String())
104+
}
105+
l = l.WithValues("dropped_userop_hashes", drp)
94106

95-
for k, v := range ctx.Data {
96-
l = l.WithValues(k, v)
107+
for k, v := range ctx.Data {
108+
l = l.WithValues(k, v)
109+
}
110+
l = l.WithValues("duration", time.Since(start))
111+
l.Info("bundler run ok")
97112
}
98-
l = l.WithValues("duration", time.Since(start))
99-
l.Info("bundler run ok")
100113
}
101114
}
102115
}(i)
103116

117+
i.isRunning = true
104118
return nil
105119
}
120+
121+
// Stop signals the bundler to stop continuously processing batches from the mempool.
122+
func (i *Bundler) Stop() {
123+
if !i.isRunning {
124+
return
125+
}
126+
127+
i.isRunning = false
128+
i.stop <- true
129+
}

pkg/client/client.go

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"github.com/ethereum/go-ethereum/common/hexutil"
1010
"github.com/go-logr/logr"
1111
"github.com/stackup-wallet/stackup-bundler/internal/logger"
12+
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
13+
"github.com/stackup-wallet/stackup-bundler/pkg/gas"
1214
"github.com/stackup-wallet/stackup-bundler/pkg/mempool"
1315
"github.com/stackup-wallet/stackup-bundler/pkg/modules"
1416
"github.com/stackup-wallet/stackup-bundler/pkg/modules/noop"
@@ -21,19 +23,28 @@ type Client struct {
2123
mempool *mempool.Mempool
2224
chainID *big.Int
2325
supportedEntryPoints []common.Address
26+
maxVerificationGas *big.Int
2427
userOpHandler modules.UserOpHandlerFunc
2528
logger logr.Logger
29+
getUserOpReceipt GetUserOpReceiptFunc
2630
}
2731

2832
// New initializes a new ERC-4337 client which can be extended with modules for validating UserOperations
2933
// that are allowed to be added to the mempool.
30-
func New(mempool *mempool.Mempool, chainID *big.Int, supportedEntryPoints []common.Address) *Client {
34+
func New(
35+
mempool *mempool.Mempool,
36+
chainID *big.Int,
37+
supportedEntryPoints []common.Address,
38+
maxVerificationGas *big.Int,
39+
) *Client {
3140
return &Client{
3241
mempool: mempool,
3342
chainID: chainID,
3443
supportedEntryPoints: supportedEntryPoints,
44+
maxVerificationGas: maxVerificationGas,
3545
userOpHandler: noop.UserOpHandler,
3646
logger: logger.NewZeroLogr().WithName("client"),
47+
getUserOpReceipt: getUserOpReceiptNoop(),
3748
}
3849
}
3950

@@ -57,6 +68,12 @@ func (i *Client) UseModules(handlers ...modules.UserOpHandlerFunc) {
5768
i.userOpHandler = modules.ComposeUserOpHandlerFunc(handlers...)
5869
}
5970

71+
// SetGetUserOpReceiptFunc defines a general function for fetching a UserOpReceipt given a userOpHash and
72+
// EntryPoint address. This function is called in *Client.GetUserOperationReceipt.
73+
func (i *Client) SetGetUserOpReceiptFunc(fn GetUserOpReceiptFunc) {
74+
i.getUserOpReceipt = fn
75+
}
76+
6077
// SendUserOperation implements the method call for eth_sendUserOperation.
6178
// It returns true if userOp was accepted otherwise returns an error.
6279
func (i *Client) SendUserOperation(op map[string]any, ep string) (string, error) {
@@ -105,6 +122,59 @@ func (i *Client) SendUserOperation(op map[string]any, ep string) (string, error)
105122
return hash.String(), nil
106123
}
107124

125+
// EstimateUserOperationGas returns estimates for PreVerificationGas, VerificationGas, and CallGasLimit given
126+
// a UserOperation and EntryPoint address. The signature field and current gas values will not be validated
127+
// although there should be dummy values in place for the most reliable results (e.g. a signature with the
128+
// correct length).
129+
func (i *Client) EstimateUserOperationGas(op map[string]any, ep string) (*gas.GasEstimates, error) {
130+
// Init logger
131+
l := i.logger.WithName("eth_estimateUserOperationGas")
132+
133+
// Check EntryPoint and userOp is valid.
134+
epAddr, err := i.parseEntryPointAddress(ep)
135+
if err != nil {
136+
l.Error(err, "eth_estimateUserOperationGas error")
137+
return nil, err
138+
}
139+
l = l.
140+
WithValues("entrypoint", epAddr.String()).
141+
WithValues("chain_id", i.chainID.String())
142+
143+
userOp, err := userop.New(op)
144+
if err != nil {
145+
l.Error(err, "eth_estimateUserOperationGas error")
146+
return nil, err
147+
}
148+
hash := userOp.GetUserOpHash(epAddr, i.chainID)
149+
l = l.WithValues("userop_hash", hash)
150+
151+
l.Info("eth_estimateUserOperationGas ok")
152+
// TODO: Return more reliable values
153+
return &gas.GasEstimates{
154+
PreVerificationGas: gas.NewDefaultOverhead().CalcPreVerificationGas(userOp),
155+
VerificationGas: i.maxVerificationGas,
156+
CallGasLimit: gas.NewDefaultOverhead().NonZeroValueCall(),
157+
}, nil
158+
}
159+
160+
// GetUserOperationReceipt fetches a UserOperation receipt based on a userOpHash returned by
161+
// *Client.SendUserOperation.
162+
func (i *Client) GetUserOperationReceipt(
163+
hash string,
164+
) (*entrypoint.UserOperationReceipt, error) {
165+
// Init logger
166+
l := i.logger.WithName("eth_getUserOperationReceipt").WithValues("userop_hash", hash)
167+
168+
ev, err := i.getUserOpReceipt(hash, i.supportedEntryPoints[0])
169+
if err != nil {
170+
l.Error(err, "eth_getUserOperationReceipt error")
171+
return nil, err
172+
}
173+
174+
l.Info("eth_getUserOperationReceipt ok")
175+
return ev, nil
176+
}
177+
108178
// SupportedEntryPoints implements the method call for eth_supportedEntryPoints. It returns the array of
109179
// EntryPoint addresses that is supported by the client. The first address in the array is the preferred
110180
// EntryPoint.

pkg/client/debug.go

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
package client
22

33
import (
4+
"encoding/json"
45
"errors"
6+
"fmt"
57
"math/big"
68

79
"github.com/ethereum/go-ethereum/common"
810
"github.com/ethereum/go-ethereum/ethclient"
11+
"github.com/stackup-wallet/stackup-bundler/pkg/bundler"
912
"github.com/stackup-wallet/stackup-bundler/pkg/entrypoint"
1013
"github.com/stackup-wallet/stackup-bundler/pkg/mempool"
1114
"github.com/stackup-wallet/stackup-bundler/pkg/signer"
@@ -16,6 +19,7 @@ type Debug struct {
1619
eoa *signer.EOA
1720
eth *ethclient.Client
1821
mempool *mempool.Mempool
22+
bundler *bundler.Bundler
1923
chainID *big.Int
2024
entrypoint common.Address
2125
beneficiary common.Address
@@ -25,11 +29,46 @@ func NewDebug(
2529
eoa *signer.EOA,
2630
eth *ethclient.Client,
2731
mempool *mempool.Mempool,
32+
bundler *bundler.Bundler,
2833
chainID *big.Int,
2934
entrypoint common.Address,
3035
beneficiary common.Address,
3136
) *Debug {
32-
return &Debug{eoa, eth, mempool, chainID, entrypoint, beneficiary}
37+
return &Debug{eoa, eth, mempool, bundler, chainID, entrypoint, beneficiary}
38+
}
39+
40+
// ClearState clears the bundler mempool and reputation data of paymasters/accounts/factories/aggregators.
41+
func (d *Debug) ClearState() (string, error) {
42+
if err := d.mempool.Clear(); err != nil {
43+
return "", err
44+
}
45+
46+
return "ok", nil
47+
}
48+
49+
// DumpMempool dumps the current UserOperations mempool in order of arrival.
50+
func (d *Debug) DumpMempool(ep string) ([]map[string]any, error) {
51+
ops, err := d.mempool.Dump(common.HexToAddress(ep))
52+
if err != nil {
53+
return []map[string]any{}, err
54+
}
55+
56+
res := []map[string]any{}
57+
for _, op := range ops {
58+
data, err := op.MarshalJSON()
59+
if err != nil {
60+
return []map[string]any{}, err
61+
}
62+
63+
item := make(map[string]any)
64+
if err := json.Unmarshal(data, &item); err != nil {
65+
return []map[string]any{}, err
66+
}
67+
68+
res = append(res, item)
69+
}
70+
71+
return res, nil
3372
}
3473

3574
// SendBundleNow forces the bundler to build and execute a bundle from the mempool as handleOps() transaction.
@@ -61,8 +100,6 @@ func (d *Debug) SendBundleNow() (string, error) {
61100
batch,
62101
d.beneficiary,
63102
est,
64-
big.NewInt((int64(est))),
65-
big.NewInt((int64(est))),
66103
)
67104
if err != nil {
68105
return "", err
@@ -72,3 +109,20 @@ func (d *Debug) SendBundleNow() (string, error) {
72109

73110
return txn.Hash().String(), nil
74111
}
112+
113+
// SetBundlingMode allows the bundler to be stopped so that an explicit call to debug_bundler_sendBundleNow is
114+
// required to send a bundle.
115+
func (d *Debug) SetBundlingMode(mode string) (string, error) {
116+
switch mode {
117+
case "manual":
118+
d.bundler.Stop()
119+
case "auto":
120+
if err := d.bundler.Run(); err != nil {
121+
return "", err
122+
}
123+
default:
124+
return "", fmt.Errorf("debug: unrecognized mode %s", mode)
125+
}
126+
127+
return "ok", nil
128+
}

0 commit comments

Comments
 (0)