Skip to content

Commit 23c3850

Browse files
committed
feat: poll bundler to fetch userops
1 parent ef7b99a commit 23c3850

File tree

4 files changed

+196
-25
lines changed

4 files changed

+196
-25
lines changed

cmd/betsy/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,10 +224,11 @@ func main() {
224224
}
225225

226226
// create a start mempool polling
227+
bundlerUrl := "http://localhost:" + strconv.Itoa(cCtx.Int("bundler.port")) + "/rpc"
227228
mempool := mempool.NewUserOpMempool(
228229
betsyWallet.GetBundlerWalletDetails().EntryPointAddress,
229230
betsyWallet.GetEthClient(),
230-
"http://localhost:"+strconv.Itoa(cCtx.Int("bundler.port")),
231+
bundlerUrl,
231232
)
232233
go func() {
233234
if err := mempool.Run(); err != nil {
@@ -259,7 +260,7 @@ func main() {
259260
prefix := "http://localhost:"
260261
err = printBetsyInfo(NodeInfo{
261262
EthNodeUrl: prefix + strconv.Itoa(cCtx.Int("eth.port")),
262-
BundlerNodeUrl: prefix + strconv.Itoa(cCtx.Int("bundler.port")),
263+
BundlerNodeUrl: bundlerUrl,
263264
DashboardServerUrl: prefix + strconv.Itoa(cCtx.Int("http.port")),
264265
DevAccounts: accounts,
265266
})

internal/client/bundler.go

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package client
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"sync"
11+
"time"
12+
13+
"github.com/rs/zerolog/log"
14+
"github.com/transeptorlabs/betsy/internal/data"
15+
)
16+
17+
type BundlerClient struct {
18+
bundlerUrl string
19+
jsonRpcRequestID int
20+
mutex sync.Mutex
21+
}
22+
23+
type jsonrpcBase struct {
24+
jsonrpc string
25+
id int
26+
}
27+
28+
type debugBundlerDumpMempoolRes struct {
29+
jsonrpcBase
30+
result []data.UserOpV7Hexify
31+
}
32+
33+
type debug_bundler_addUserOpsRes struct {
34+
jsonrpcBase
35+
result string
36+
}
37+
38+
func NewBundlerClient(bundlerUrl string) *BundlerClient {
39+
return &BundlerClient{
40+
bundlerUrl: bundlerUrl,
41+
jsonRpcRequestID: 1,
42+
}
43+
}
44+
45+
func (b *BundlerClient) getRequest(rpcMethod string, params []interface{}) (*http.Request, error) {
46+
// Make json rpc request
47+
jsonBody, _ := json.Marshal(map[string]interface{}{
48+
"jsonrpc": "2.0",
49+
"id": b.jsonRpcRequestID,
50+
"method": rpcMethod,
51+
"params": params,
52+
})
53+
bodyReader := bytes.NewReader(jsonBody)
54+
55+
req, err := http.NewRequest(http.MethodPost, b.bundlerUrl, bodyReader)
56+
if err != nil {
57+
return nil, err
58+
}
59+
req.Header.Set("Content-Type", "application/json")
60+
61+
return req, nil
62+
}
63+
64+
func (b *BundlerClient) Debug_bundler_dumpMempool() ([]data.UserOpV7Hexify, error) {
65+
log.Info().Msgf("Making call to bundler node debug_bundler_dumpMempool at %s", b.bundlerUrl)
66+
b.mutex.Lock()
67+
defer b.mutex.Unlock()
68+
69+
req, err := b.getRequest("debug_bundler_dumpMempool", []interface{}{})
70+
if err != nil {
71+
return nil, err
72+
}
73+
74+
client := http.Client{
75+
Timeout: 30 * time.Second,
76+
}
77+
78+
res, err := client.Do(req)
79+
if err != nil {
80+
return nil, err
81+
}
82+
83+
// handle json rpc response
84+
b.jsonRpcRequestID = b.jsonRpcRequestID + 1
85+
if res.StatusCode != 200 {
86+
return nil, errors.New(fmt.Sprintf("Request to bundler debug_bundler_dumpMempool rpc method failed with status code: %d", res.StatusCode))
87+
}
88+
89+
resJsonBody, err := io.ReadAll(res.Body)
90+
if err != nil {
91+
return nil, err
92+
}
93+
94+
// parse result
95+
var data *debugBundlerDumpMempoolRes
96+
err = json.Unmarshal(resJsonBody, &data)
97+
if err != nil {
98+
return nil, err
99+
}
100+
101+
return data.result, nil
102+
}
103+
104+
func (b *BundlerClient) Debug_bundler_addUserOps(ops []data.UserOpV7Hexify) error {
105+
log.Info().Msgf("Making call to bundler node debug_bundler_addUsers at %s", b.bundlerUrl)
106+
b.mutex.Lock()
107+
defer b.mutex.Unlock()
108+
109+
if ops == nil || len(ops) == 0 {
110+
return errors.New("Can not add empty userOps")
111+
}
112+
113+
req, err := b.getRequest("debug_bundler_addUserOps", []interface{}{
114+
ops,
115+
})
116+
if err != nil {
117+
return err
118+
}
119+
120+
client := http.Client{
121+
Timeout: 30 * time.Second,
122+
}
123+
124+
res, err := client.Do(req)
125+
if err != nil {
126+
return err
127+
}
128+
129+
// handle json rpc response
130+
b.jsonRpcRequestID = b.jsonRpcRequestID + 1
131+
if res.StatusCode != 200 {
132+
return errors.New(fmt.Sprintf("Request to bundler debug_bundler_addUserOps rpc method failed with status code: %d", res.StatusCode))
133+
}
134+
135+
resJsonBody, err := io.ReadAll(res.Body)
136+
if err != nil {
137+
return err
138+
}
139+
140+
// parse result
141+
var data *debug_bundler_addUserOpsRes
142+
err = json.Unmarshal(resJsonBody, &data)
143+
if err != nil {
144+
return err
145+
}
146+
147+
return nil
148+
}

internal/mempool/mempool.go

Lines changed: 44 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -7,31 +7,41 @@ import (
77
"github.com/ethereum/go-ethereum/common"
88
"github.com/ethereum/go-ethereum/ethclient"
99
"github.com/rs/zerolog/log"
10+
"github.com/transeptorlabs/betsy/internal/client"
1011
"github.com/transeptorlabs/betsy/internal/data"
1112
)
1213

1314
type UserOpMempool struct {
14-
userOps map[common.Hash]*data.UserOpV7Hexify
15-
mutex sync.Mutex
16-
ethClient *ethclient.Client
17-
epAddress common.Address
18-
bundlerUrl string
19-
ticker *time.Ticker
20-
isRunning bool
21-
done chan bool
15+
userOps map[common.Hash]*data.UserOpV7Hexify
16+
mutex sync.Mutex
17+
ethClient *ethclient.Client
18+
epAddress common.Address
19+
bundlerClient *client.BundlerClient
20+
ticker *time.Ticker
21+
isRunning bool
22+
done chan bool
23+
mempoolRefreshErrorCount int
2224
}
2325

2426
func NewUserOpMempool(epAddress common.Address, ethClient *ethclient.Client, bundlerUrl string) *UserOpMempool {
2527
return &UserOpMempool{
26-
userOps: make(map[common.Hash]*data.UserOpV7Hexify),
27-
epAddress: epAddress,
28-
ethClient: ethClient,
29-
bundlerUrl: bundlerUrl,
30-
isRunning: false,
31-
done: make(chan bool),
28+
userOps: make(map[common.Hash]*data.UserOpV7Hexify),
29+
epAddress: epAddress,
30+
ethClient: ethClient,
31+
bundlerClient: client.NewBundlerClient(bundlerUrl),
32+
isRunning: false,
33+
done: make(chan bool),
34+
mempoolRefreshErrorCount: 0,
3235
}
3336
}
3437

38+
func (m *UserOpMempool) GetUserOps() map[common.Hash]*data.UserOpV7Hexify {
39+
m.mutex.Lock()
40+
defer m.mutex.Unlock()
41+
42+
return m.userOps
43+
}
44+
3545
func (m *UserOpMempool) addUserOp(op *data.UserOpV7Hexify) error {
3646
m.mutex.Lock()
3747
defer m.mutex.Unlock()
@@ -50,15 +60,25 @@ func (m *UserOpMempool) addUserOp(op *data.UserOpV7Hexify) error {
5060
return nil
5161
}
5262

53-
func (m *UserOpMempool) GetUserOps() map[common.Hash]*data.UserOpV7Hexify {
54-
m.mutex.Lock()
55-
defer m.mutex.Unlock()
63+
func (m *UserOpMempool) refreshMempool() error {
64+
log.Info().Msg("Refreshing mempool...")
5665

57-
return m.userOps
58-
}
66+
userOps, err := m.bundlerClient.Debug_bundler_dumpMempool()
67+
if err != nil {
68+
return err
69+
}
70+
71+
log.Info().Msgf("userOps fetched from bundler(data): %#v\n", userOps)
72+
log.Info().Msgf("total userOps fetched from bundler(count): %#v\n", len(userOps))
73+
if len(userOps) > 0 {
74+
for _, op := range userOps {
75+
err = m.addUserOp(&op)
76+
if err != nil {
77+
return err
78+
}
79+
}
80+
}
5981

60-
func (m *UserOpMempool) fetchUserOps() error {
61-
log.Debug().Msg("Fecthing userOps from bundler node...")
6282
return nil
6383
}
6484

@@ -75,8 +95,10 @@ func (m *UserOpMempool) Run() error {
7595
case <-m.done:
7696
return
7797
case <-m.ticker.C:
78-
err := m.fetchUserOps()
98+
err := m.refreshMempool()
7999
if err != nil {
100+
log.Err(err).Msg("Could not refresh mempool")
101+
m.mempoolRefreshErrorCount = m.mempoolRefreshErrorCount + 1
80102
continue
81103
}
82104
}

ui/templates/banner.tmpl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@ Betsy comes with:
2323
- An ERC 4337 userOp mempool dashboard
2424
- An ERC 4337 Bundler bundle dashboard
2525

26-
Type besty --help to explore the list of commands.
26+
Type betsy --help to explore the list of commands.
2727

2828
****************************************************

0 commit comments

Comments
 (0)