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

Commit e1ad241

Browse files
authored
Add tip to context and fix MaxFee calculation (#197)
1 parent 404ebbd commit e1ad241

File tree

12 files changed

+103
-70
lines changed

12 files changed

+103
-70
lines changed

internal/start/private.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ func PrivateMode() {
111111
// Init Bundler
112112
b := bundler.New(mem, chain, conf.SupportedEntryPoints)
113113
b.SetGetBaseFeeFunc(gasprice.GetBaseFeeWithEthClient(eth))
114+
b.SetGetGasTipFunc(gasprice.GetGasTipWithEthClient(eth))
114115
b.SetGetLegacyGasPriceFunc(gasprice.GetLegacyGasPriceWithEthClient(eth))
115116
b.UseLogger(logr)
116117
b.UseModules(

internal/start/searcher.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func SearcherMode() {
104104
// Init Bundler
105105
b := bundler.New(mem, chain, conf.SupportedEntryPoints)
106106
b.SetGetBaseFeeFunc(gasprice.GetBaseFeeWithEthClient(eth))
107+
b.SetGetGasTipFunc(gasprice.GetGasTipWithEthClient(eth))
107108
b.SetGetLegacyGasPriceFunc(gasprice.GetLegacyGasPriceWithEthClient(eth))
108109
b.UseLogger(logr)
109110
b.UseModules(

pkg/bundler/bundler.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type Bundler struct {
2828
stop func()
2929
maxBatch int
3030
gbf gasprice.GetBaseFeeFunc
31+
ggt gasprice.GetGasTipFunc
3132
ggp gasprice.GetLegacyGasPriceFunc
3233
}
3334

@@ -45,6 +46,7 @@ func New(mempool *mempool.Mempool, chainID *big.Int, supportedEntryPoints []comm
4546
stop: func() {},
4647
maxBatch: 0,
4748
gbf: gasprice.NoopGetBaseFeeFunc(),
49+
ggt: gasprice.NoopGetGasTipFunc(),
4850
ggp: gasprice.NoopGetLegacyGasPriceFunc(),
4951
}
5052
}
@@ -59,6 +61,11 @@ func (i *Bundler) SetGetBaseFeeFunc(gbf gasprice.GetBaseFeeFunc) {
5961
i.gbf = gbf
6062
}
6163

64+
// SetGetGasTipFunc defines the function used to retrieve an estimate for gas tip during each bundler run.
65+
func (i *Bundler) SetGetGasTipFunc(ggt gasprice.GetGasTipFunc) {
66+
i.ggt = ggt
67+
}
68+
6269
// SetGetLegacyGasPriceFunc defines the function used to retrieve an estimate for gas price during each
6370
// bundler run.
6471
func (i *Bundler) SetGetLegacyGasPriceFunc(ggp gasprice.GetLegacyGasPriceFunc) {
@@ -91,6 +98,16 @@ func (i *Bundler) Process(ep common.Address) (*modules.BatchHandlerCtx, error) {
9198
return nil, err
9299
}
93100

101+
// Get suggested gas tip
102+
var gt *big.Int
103+
if bf != nil {
104+
gt, err = i.ggt()
105+
if err != nil {
106+
l.Error(err, "bundler run error")
107+
return nil, err
108+
}
109+
}
110+
94111
// Get suggested gas price (for networks that don't support EIP-1559)
95112
gp, err := i.ggp()
96113
if err != nil {
@@ -111,7 +128,7 @@ func (i *Bundler) Process(ep common.Address) (*modules.BatchHandlerCtx, error) {
111128
batch = adjustBatchSize(i.maxBatch, batch)
112129

113130
// Create context and execute modules.
114-
ctx := modules.NewBatchHandlerContext(batch, ep, i.chainID, bf, gp)
131+
ctx := modules.NewBatchHandlerContext(batch, ep, i.chainID, bf, gt, gp)
115132
if err := i.batchHandler(ctx); err != nil {
116133
l.Error(err, "bundler run error")
117134
return nil, err

pkg/entrypoint/transaction/gas.go

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,33 @@
11
package transaction
22

33
import (
4-
"context"
54
"math/big"
65

76
"github.com/ethereum/go-ethereum/common"
8-
"github.com/ethereum/go-ethereum/ethclient"
97
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
108
)
119

1210
// SuggestMeanGasTipCap suggests a Max Priority Fee for an EIP-1559 transaction to submit a batch of
13-
// UserOperations to the EntryPoint. It returns the larger value between the gas tip suggested by the
14-
// underlying node (i.e. eth_maxPriorityFeePerGas) or the average maxPriorityFeePerGas of the entire batch.
15-
func SuggestMeanGasTipCap(eth *ethclient.Client, batch []*userop.UserOperation) (*big.Int, error) {
16-
tip, err := eth.SuggestGasTipCap(context.Background())
17-
if err != nil {
18-
return nil, err
19-
}
20-
11+
// UserOperations to the EntryPoint. It returns the larger value between the suggested gas tip or the average
12+
// maxPriorityFeePerGas of the entire batch.
13+
func SuggestMeanGasTipCap(tip *big.Int, batch []*userop.UserOperation) *big.Int {
2114
sum := big.NewInt(0)
2215
for _, op := range batch {
2316
sum = big.NewInt(0).Add(sum, op.MaxPriorityFeePerGas)
2417
}
2518
avg := big.NewInt(0).Div(sum, big.NewInt(int64(len(batch))))
2619

2720
if avg.Cmp(tip) == 1 {
28-
return avg, nil
21+
return avg
2922
}
30-
return tip, nil
23+
return tip
3124
}
3225

3326
// SuggestMeanGasFeeCap suggests a Max Fee for an EIP-1559 transaction to submit a batch of UserOperations to
3427
// the EntryPoint. It returns the larger value between the recommended max fee or the average maxFeePerGas of
3528
// the entire batch.
36-
func SuggestMeanGasFeeCap(basefee *big.Int, batch []*userop.UserOperation) *big.Int {
37-
mf := big.NewInt(0).Mul(basefee, common.Big2)
38-
29+
func SuggestMeanGasFeeCap(basefee *big.Int, tip *big.Int, batch []*userop.UserOperation) *big.Int {
30+
mf := big.NewInt(0).Add(tip, big.NewInt(0).Mul(basefee, common.Big2))
3931
sum := big.NewInt(0)
4032
for _, op := range batch {
4133
sum = big.NewInt(0).Add(sum, op.MaxFeePerGas)

pkg/entrypoint/transaction/gas_test.go

Lines changed: 14 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,38 @@ import (
55
"testing"
66

77
"github.com/ethereum/go-ethereum/common"
8-
"github.com/ethereum/go-ethereum/common/hexutil"
9-
"github.com/ethereum/go-ethereum/ethclient"
108
"github.com/stackup-wallet/stackup-bundler/internal/testutils"
119
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1210
)
1311

1412
// TestSuggestMeanGasTipCapForNormalLoad simulates a scenario of normal network load. In this case the average
15-
// tip from userOps is assumed to not be above the tip suggested by the underlying node. In which case the
16-
// node's suggested tip is the optimal choice.
13+
// tip from userOps is assumed to not be above the suggested tip. In which case the suggested tip is the
14+
// optimal choice.
1715
func TestSuggestMeanGasTipCapForNormalLoad(t *testing.T) {
18-
expected := big.NewInt(2)
19-
be := testutils.EthMock(testutils.MethodMocks{
20-
"eth_maxPriorityFeePerGas": hexutil.EncodeBig(expected),
21-
})
22-
eth, err := ethclient.Dial(be.URL)
23-
if err != nil {
24-
panic(err)
25-
}
26-
2716
op1 := testutils.MockValidInitUserOp()
2817
op1.MaxPriorityFeePerGas = big.NewInt(1)
2918
op2 := testutils.MockValidInitUserOp()
3019
op2.MaxPriorityFeePerGas = big.NewInt(1)
3120
batch := []*userop.UserOperation{op1, op2}
32-
if tip, err := SuggestMeanGasTipCap(eth, batch); err != nil {
33-
t.Fatalf("got %v, want %d", err, expected.Int64())
34-
} else if tip.Cmp(expected) != 0 {
21+
22+
expected := big.NewInt(2)
23+
if tip := SuggestMeanGasTipCap(expected, batch); tip.Cmp(expected) != 0 {
3524
t.Fatalf("got %d, want %d", tip.Int64(), expected.Int64())
3625
}
3726
}
3827

3928
// TestSuggestMeanGasTipCapForHighLoad simulates a scenario of high network load. In this case the average tip
40-
// from userOps is assumed to be above the tip suggested by the underlying node (i.e. userOps want to be
41-
// included quickly). In which case the average tip from userOps is the optimal choice.
29+
// from userOps is assumed to be above the suggested tip (i.e. userOps want to be included quickly). In which
30+
// case the average tip from userOps is the optimal choice.
4231
func TestSuggestMeanGasTipCapForHighLoad(t *testing.T) {
43-
be := testutils.EthMock(testutils.MethodMocks{
44-
"eth_maxPriorityFeePerGas": hexutil.EncodeBig(big.NewInt(2)),
45-
})
46-
eth, err := ethclient.Dial(be.URL)
47-
if err != nil {
48-
panic(err)
49-
}
50-
5132
op1 := testutils.MockValidInitUserOp()
5233
op1.MaxPriorityFeePerGas = big.NewInt(5)
5334
op2 := testutils.MockValidInitUserOp()
5435
op2.MaxPriorityFeePerGas = big.NewInt(10)
55-
expected := big.NewInt(7)
5636
batch := []*userop.UserOperation{op1, op2}
57-
if tip, err := SuggestMeanGasTipCap(eth, batch); err != nil {
58-
t.Fatalf("got %v, want %d", err, expected.Int64())
59-
} else if tip.Cmp(expected) != 0 {
37+
38+
expected := big.NewInt(7)
39+
if tip := SuggestMeanGasTipCap(big.NewInt(2), batch); tip.Cmp(expected) != 0 {
6040
t.Fatalf("got %d, want %d", tip.Int64(), expected.Int64())
6141
}
6242
}
@@ -72,8 +52,9 @@ func TestSuggestMeanGasFeeCapNormalLoad(t *testing.T) {
7252
batch := []*userop.UserOperation{op1, op2}
7353

7454
bf := big.NewInt(1)
55+
tip := big.NewInt(0)
7556
expected := big.NewInt(0).Mul(bf, common.Big2)
76-
mf := SuggestMeanGasFeeCap(bf, batch)
57+
mf := SuggestMeanGasFeeCap(bf, tip, batch)
7758
if mf.Cmp(expected) != 0 {
7859
t.Fatalf("got %d, want %d", mf.Int64(), expected.Int64())
7960
}
@@ -90,8 +71,9 @@ func TestSuggestMeanGasFeeCapHighLoad(t *testing.T) {
9071
batch := []*userop.UserOperation{op1, op2}
9172

9273
bf := big.NewInt(1)
74+
tip := big.NewInt(0)
9375
expected := big.NewInt(7)
94-
mf := SuggestMeanGasFeeCap(bf, batch)
76+
mf := SuggestMeanGasFeeCap(bf, tip, batch)
9577
if mf.Cmp(expected) != 0 {
9678
t.Fatalf("got %d, want %d", mf.Int64(), expected.Int64())
9779
}

pkg/entrypoint/transaction/handleops.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ type Opts struct {
3535

3636
// Options for the EOA transaction
3737
BaseFee *big.Int
38+
Tip *big.Int
3839
GasPrice *big.Int
3940
GasLimit uint64
4041
WaitTimeout time.Duration
@@ -110,21 +111,13 @@ func HandleOps(opts *Opts) (txn *types.Transaction, err error) {
110111
}
111112
auth.Nonce = big.NewInt(int64(nonce))
112113

113-
if opts.BaseFee != nil {
114-
if tip, err := SuggestMeanGasTipCap(opts.Eth, opts.Batch); err != nil {
115-
return nil, err
116-
} else {
117-
// Note: If gas price was to spike, we can run into an edge case where the suggested gas tip from
118-
// the underlying node is greater than either of the suggested gas fee caps. This is fine since
119-
// the eth client will throw an error and the userOp will not be submitted until either the gas
120-
// tip comes back down or the userOp gets replaced with 10% higher fees.
121-
auth.GasFeeCap = SuggestMeanGasFeeCap(opts.BaseFee, opts.Batch)
122-
auth.GasTipCap = tip
123-
}
114+
if opts.BaseFee != nil && opts.Tip != nil {
115+
auth.GasTipCap = SuggestMeanGasTipCap(opts.Tip, opts.Batch)
116+
auth.GasFeeCap = SuggestMeanGasFeeCap(opts.BaseFee, opts.Tip, opts.Batch)
124117
} else if opts.GasPrice != nil {
125118
auth.GasPrice = SuggestMeanGasPrice(opts.GasPrice, opts.Batch)
126119
} else {
127-
return nil, errors.New("transaction: opts.BaseFee and opts.GasPrice cannot both be nil")
120+
return nil, errors.New("transaction: either the dynamic or legacy gas fees must be set")
128121
}
129122

130123
txn, err = ep.HandleOps(auth, toAbiType(opts.Batch), opts.Beneficiary)

pkg/modules/context.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ type BatchHandlerCtx struct {
1818
EntryPoint common.Address
1919
ChainID *big.Int
2020
BaseFee *big.Int
21+
Tip *big.Int
2122
GasPrice *big.Int
2223
Data map[string]any
2324
}
@@ -28,6 +29,7 @@ func NewBatchHandlerContext(
2829
entryPoint common.Address,
2930
chainID *big.Int,
3031
baseFee *big.Int,
32+
tip *big.Int,
3133
gasPrice *big.Int,
3234
) *BatchHandlerCtx {
3335
var copy []*userop.UserOperation
@@ -39,6 +41,7 @@ func NewBatchHandlerContext(
3941
EntryPoint: entryPoint,
4042
ChainID: chainID,
4143
BaseFee: baseFee,
44+
Tip: tip,
4245
GasPrice: gasPrice,
4346
Data: make(map[string]any),
4447
}

pkg/modules/gasprice/filter.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
package gasprice
22

33
import (
4+
"math/big"
5+
46
"github.com/stackup-wallet/stackup-bundler/pkg/modules"
57
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
68
)
79

810
// FilterUnderpriced returns a BatchHandlerFunc that will filter out all the userOps that are below either the
9-
// BaseFee or GasPrice set in the context.
11+
// dynamic or legacy GasPrice set in the context.
1012
func FilterUnderpriced() modules.BatchHandlerFunc {
1113
return func(ctx *modules.BatchHandlerCtx) error {
1214
b := []*userop.UserOperation{}
1315
for _, op := range ctx.Batch {
14-
if ctx.BaseFee != nil {
15-
if op.GetDynamicGasPrice(ctx.BaseFee).Cmp(ctx.BaseFee) >= 0 {
16+
if ctx.BaseFee != nil && ctx.Tip != nil {
17+
gp := big.NewInt(0).Add(ctx.BaseFee, ctx.Tip)
18+
if op.GetDynamicGasPrice(ctx.BaseFee).Cmp(gp) >= 0 {
1619
b = append(b, op)
1720
}
1821
} else if ctx.GasPrice != nil {

pkg/modules/gasprice/fliter_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import (
1010
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1111
)
1212

13-
// TestFilterUnderpricedBaseFee verifies that FilterUnderpriced will remove all UserOperations from a batch
14-
// where the effective gas price is less than the context BaseFee.
15-
func TestFilterUnderpricedBaseFee(t *testing.T) {
13+
// TestFilterUnderpricedDynamic verifies that FilterUnderpriced will remove all UserOperations from a batch
14+
// where the effective gas price is less than the expected bundler transaction's gas price.
15+
func TestFilterUnderpricedDynamic(t *testing.T) {
16+
bf := big.NewInt(4)
17+
tip := big.NewInt(1)
18+
1619
op1 := testutils.MockValidInitUserOp()
1720
op1.MaxFeePerGas = big.NewInt(4)
1821
op1.MaxPriorityFeePerGas = big.NewInt(3)
@@ -31,7 +34,8 @@ func TestFilterUnderpricedBaseFee(t *testing.T) {
3134
[]*userop.UserOperation{op1, op2, op3},
3235
testutils.ValidAddress1,
3336
testutils.ChainID,
34-
big.NewInt(5),
37+
bf,
38+
tip,
3539
big.NewInt(10),
3640
)
3741
if err := gasprice.FilterUnderpriced()(ctx); err != nil {
@@ -67,6 +71,7 @@ func TestFilterUnderpricedGasPrice(t *testing.T) {
6771
testutils.ValidAddress1,
6872
testutils.ChainID,
6973
nil,
74+
nil,
7075
big.NewInt(5),
7176
)
7277
if err := gasprice.FilterUnderpriced()(ctx); err != nil {

pkg/modules/gasprice/sort_test.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ import (
1010
"github.com/stackup-wallet/stackup-bundler/pkg/userop"
1111
)
1212

13-
// TestSortByGasPriceBaseFee verifies that SortByGasPrice sorts the UserOperations in a batch by highest
13+
// TestSortByGasPriceBaseDynamic verifies that SortByGasPrice sorts the UserOperations in a batch by highest
1414
// effective Gas Price first.
15-
func TestSortByGasPriceBaseFee(t *testing.T) {
15+
func TestSortByGasPriceBaseDynamic(t *testing.T) {
16+
bf := big.NewInt(3)
17+
tip := big.NewInt(0)
18+
1619
op1 := testutils.MockValidInitUserOp()
1720
op1.MaxFeePerGas = big.NewInt(4)
1821
op1.MaxPriorityFeePerGas = big.NewInt(3)
@@ -31,7 +34,8 @@ func TestSortByGasPriceBaseFee(t *testing.T) {
3134
[]*userop.UserOperation{op1, op2, op3},
3235
testutils.ValidAddress1,
3336
testutils.ChainID,
34-
big.NewInt(3),
37+
bf,
38+
tip,
3539
big.NewInt(6),
3640
)
3741
if err := gasprice.SortByGasPrice()(ctx); err != nil {
@@ -69,6 +73,7 @@ func TestSortByGasPriceLegacy(t *testing.T) {
6973
testutils.ValidAddress1,
7074
testutils.ChainID,
7175
nil,
76+
nil,
7277
big.NewInt(4),
7378
)
7479
if err := gasprice.SortByGasPrice()(ctx); err != nil {

0 commit comments

Comments
 (0)