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

Commit 9777e7f

Browse files
authored
Add TTL value for UserOperations (#252)
1 parent 5e077e5 commit 9777e7f

File tree

5 files changed

+99
-0
lines changed

5 files changed

+99
-0
lines changed

internal/config/values.go

Lines changed: 6 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"
@@ -20,6 +21,7 @@ type Values struct {
2021
SupportedEntryPoints []common.Address
2122
MaxVerificationGas *big.Int
2223
MaxBatchGasLimit *big.Int
24+
MaxOpTTL time.Duration
2325
MaxOpsForUnstakedSender int
2426
Beneficiary string
2527

@@ -73,6 +75,7 @@ func GetValues() *Values {
7375
viper.SetDefault("erc4337_bundler_supported_entry_points", "0x5FF137D4b0FDCD49DcA30c7CF57E578a026d2789")
7476
viper.SetDefault("erc4337_bundler_max_verification_gas", 3000000)
7577
viper.SetDefault("erc4337_bundler_max_batch_gas_limit", 25000000)
78+
viper.SetDefault("erc4337_bundler_max_op_ttl_seconds", 180)
7679
viper.SetDefault("erc4337_bundler_max_ops_for_unstaked_sender", 4)
7780
viper.SetDefault("erc4337_bundler_blocks_in_the_future", 25)
7881
viper.SetDefault("erc4337_bundler_otel_insecure_mode", false)
@@ -101,6 +104,7 @@ func GetValues() *Values {
101104
_ = viper.BindEnv("erc4337_bundler_beneficiary")
102105
_ = viper.BindEnv("erc4337_bundler_max_verification_gas")
103106
_ = viper.BindEnv("erc4337_bundler_max_batch_gas_limit")
107+
_ = viper.BindEnv("erc4337_bundler_max_op_ttl_seconds")
104108
_ = viper.BindEnv("erc4337_bundler_max_ops_for_unstaked_sender")
105109
_ = viper.BindEnv("erc4337_bundler_eth_builder_url")
106110
_ = viper.BindEnv("erc4337_bundler_blocks_in_the_future")
@@ -150,6 +154,7 @@ func GetValues() *Values {
150154
beneficiary := viper.GetString("erc4337_bundler_beneficiary")
151155
maxVerificationGas := big.NewInt(int64(viper.GetInt("erc4337_bundler_max_verification_gas")))
152156
maxBatchGasLimit := big.NewInt(int64(viper.GetInt("erc4337_bundler_max_batch_gas_limit")))
157+
maxOpTTL := time.Second * viper.GetDuration("erc4337_bundler_max_op_ttl_seconds")
153158
maxOpsForUnstakedSender := viper.GetInt("erc4337_bundler_max_ops_for_unstaked_sender")
154159
ethBuilderUrl := viper.GetString("erc4337_bundler_eth_builder_url")
155160
blocksInTheFuture := viper.GetInt("erc4337_bundler_blocks_in_the_future")
@@ -168,6 +173,7 @@ func GetValues() *Values {
168173
Beneficiary: beneficiary,
169174
MaxVerificationGas: maxVerificationGas,
170175
MaxBatchGasLimit: maxBatchGasLimit,
176+
MaxOpTTL: maxOpTTL,
171177
MaxOpsForUnstakedSender: maxOpsForUnstakedSender,
172178
EthBuilderUrl: ethBuilderUrl,
173179
BlocksInTheFuture: blocksInTheFuture,

internal/start/private.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/stackup-wallet/stackup-bundler/pkg/mempool"
2323
"github.com/stackup-wallet/stackup-bundler/pkg/modules/batch"
2424
"github.com/stackup-wallet/stackup-bundler/pkg/modules/checks"
25+
"github.com/stackup-wallet/stackup-bundler/pkg/modules/expire"
2526
"github.com/stackup-wallet/stackup-bundler/pkg/modules/gasprice"
2627
"github.com/stackup-wallet/stackup-bundler/pkg/modules/paymaster"
2728
"github.com/stackup-wallet/stackup-bundler/pkg/modules/relay"
@@ -107,6 +108,8 @@ func PrivateMode() {
107108
conf.MaxOpsForUnstakedSender,
108109
)
109110

111+
exp := expire.New(conf.MaxOpTTL)
112+
110113
relayer := relay.New(eoa, eth, chain, beneficiary, logr)
111114

112115
paymaster := paymaster.New(db)
@@ -134,6 +137,7 @@ func PrivateMode() {
134137
log.Fatal(err)
135138
}
136139
b.UseModules(
140+
exp.DropExpired(),
137141
gasprice.SortByGasPrice(),
138142
gasprice.FilterUnderpriced(),
139143
batch.SortByNonce(),

internal/start/searcher.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"github.com/stackup-wallet/stackup-bundler/pkg/modules/batch"
2525
"github.com/stackup-wallet/stackup-bundler/pkg/modules/builder"
2626
"github.com/stackup-wallet/stackup-bundler/pkg/modules/checks"
27+
"github.com/stackup-wallet/stackup-bundler/pkg/modules/expire"
2728
"github.com/stackup-wallet/stackup-bundler/pkg/modules/gasprice"
2829
"github.com/stackup-wallet/stackup-bundler/pkg/modules/paymaster"
2930
"github.com/stackup-wallet/stackup-bundler/pkg/signer"
@@ -104,6 +105,9 @@ func SearcherMode() {
104105
conf.MaxBatchGasLimit,
105106
conf.MaxOpsForUnstakedSender,
106107
)
108+
109+
exp := expire.New(conf.MaxOpTTL)
110+
107111
// TODO: Create separate go-routine for tracking transactions sent to the block builder.
108112
builder := builder.New(eoa, eth, fb, beneficiary, conf.BlocksInTheFuture)
109113
paymaster := paymaster.New(db)
@@ -132,6 +136,7 @@ func SearcherMode() {
132136
log.Fatal(err)
133137
}
134138
b.UseModules(
139+
exp.DropExpired(),
135140
gasprice.SortByGasPrice(),
136141
gasprice.FilterUnderpriced(),
137142
batch.SortByNonce(),

pkg/modules/expire/expire.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package expire
2+
3+
import (
4+
"time"
5+
6+
"github.com/ethereum/go-ethereum/common"
7+
"github.com/stackup-wallet/stackup-bundler/pkg/modules"
8+
)
9+
10+
type ExpireHandler struct {
11+
seenAt map[common.Hash]time.Time
12+
ttl time.Duration
13+
}
14+
15+
// New returns an ExpireHandler which contains a BatchHandlerFunc to track and drop UserOperations that have
16+
// been in the mempool for longer than the TTL duration.
17+
func New(ttl time.Duration) *ExpireHandler {
18+
return &ExpireHandler{
19+
seenAt: make(map[common.Hash]time.Time),
20+
ttl: ttl,
21+
}
22+
}
23+
24+
// DropExpired returns a BatchHandlerFunc that will drop UserOperations from the mempool if it has been around
25+
// for longer than the TTL duration.
26+
func (e *ExpireHandler) DropExpired() modules.BatchHandlerFunc {
27+
return func(ctx *modules.BatchHandlerCtx) error {
28+
end := len(ctx.Batch) - 1
29+
for i := end; i >= 0; i-- {
30+
hash := ctx.Batch[i].GetUserOpHash(ctx.EntryPoint, ctx.ChainID)
31+
if seenAt, ok := e.seenAt[hash]; !ok {
32+
e.seenAt[hash] = time.Now()
33+
} else if seenAt.Add(e.ttl).Before(time.Now()) {
34+
ctx.MarkOpIndexForRemoval(i)
35+
}
36+
}
37+
return nil
38+
}
39+
}

pkg/modules/expire/expire_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package expire
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/ethereum/go-ethereum/common"
8+
"github.com/stackup-wallet/stackup-bundler/internal/testutils"
9+
"github.com/stackup-wallet/stackup-bundler/pkg/modules"
10+
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
11+
)
12+
13+
// TestDropExpired calls (*ExpireHandler).DropExpired and verifies that it marks old UserOperations for
14+
// pending removal.
15+
func TestDropExpired(t *testing.T) {
16+
exp := New(time.Second * 30)
17+
op1 := testutils.MockValidInitUserOp()
18+
op2 := testutils.MockValidInitUserOp()
19+
op2.CallData = common.Hex2Bytes("0xdead")
20+
exp.seenAt = map[common.Hash]time.Time{
21+
op1.GetUserOpHash(testutils.ValidAddress1, common.Big1): time.Now().Add(time.Second * -45),
22+
op2.GetUserOpHash(testutils.ValidAddress1, common.Big1): time.Now().Add(time.Second * -15),
23+
}
24+
25+
ctx := modules.NewBatchHandlerContext(
26+
[]*userop.UserOperation{op1, op2},
27+
testutils.ValidAddress1,
28+
testutils.ChainID,
29+
nil,
30+
nil,
31+
nil,
32+
)
33+
if err := exp.DropExpired()(ctx); err != nil {
34+
t.Fatalf("got %v, want nil", err)
35+
} else if len(ctx.Batch) != 1 {
36+
t.Fatalf("got batch length %d, want 1", len(ctx.Batch))
37+
} else if len(ctx.PendingRemoval) != 1 {
38+
t.Fatalf("got pending removal length %d, want 1", len(ctx.Batch))
39+
} else if !testutils.IsOpsEqual(ctx.Batch[0], op2) {
40+
t.Fatal("incorrect batch: Dropped legit op")
41+
} else if !testutils.IsOpsEqual(ctx.PendingRemoval[0], op1) {
42+
t.Fatal("incorrect pending removal: Didn't drop bad op")
43+
}
44+
45+
}

0 commit comments

Comments
 (0)