Skip to content

Commit

Permalink
txmgr: Add min basefee and tip cap parameters to enforce fee minima
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastianst committed Jan 3, 2024
1 parent 900ec63 commit 86e731e
Show file tree
Hide file tree
Showing 3 changed files with 146 additions and 11 deletions.
70 changes: 63 additions & 7 deletions op-service/txmgr/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
opservice "github.com/ethereum-optimism/optimism/op-service"
opcrypto "github.com/ethereum-optimism/optimism/op-service/crypto"
opsigner "github.com/ethereum-optimism/optimism/op-service/signer"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/log"
Expand All @@ -30,6 +31,8 @@ const (
SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count"
FeeLimitMultiplierFlagName = "fee-limit-multiplier"
FeeLimitThresholdFlagName = "txmgr.fee-limit-threshold"
MinBasefeeFlagName = "txmgr.min-basefee"
MinTipCapFlagName = "txmgr.min-tip-cap"
ResubmissionTimeoutFlagName = "resubmission-timeout"
NetworkTimeoutFlagName = "network-timeout"
TxSendTimeoutFlagName = "txmgr.send-timeout"
Expand Down Expand Up @@ -137,6 +140,16 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl
Value: defaults.FeeLimitThresholdGwei,
EnvVars: prefixEnvVars("TXMGR_FEE_LIMIT_THRESHOLD"),
},
&cli.Float64Flag{
Name: MinBasefeeFlagName,
Usage: "Enforces a minimum basefee (in GWei) to assume when determining tx fees. Off by default.",
EnvVars: prefixEnvVars("TXMGR_MIN_BASEFEE"),
},
&cli.Float64Flag{
Name: MinTipCapFlagName,
Usage: "Enforces a minimum tip cap (in GWei) to use when determining tx fees. Off by default.",
EnvVars: prefixEnvVars("TXMGR_MIN_TIP_CAP"),
},
&cli.DurationFlag{
Name: ResubmissionTimeoutFlagName,
Usage: "Duration we will wait before resubmitting a transaction to L1",
Expand Down Expand Up @@ -182,6 +195,8 @@ type CLIConfig struct {
SafeAbortNonceTooLowCount uint64
FeeLimitMultiplier uint64
FeeLimitThresholdGwei float64
MinBasefeeGwei float64
MinTipCapGwei float64
ResubmissionTimeout time.Duration
ReceiptQueryInterval time.Duration
NetworkTimeout time.Duration
Expand Down Expand Up @@ -218,6 +233,10 @@ func (m CLIConfig) Check() error {
if m.FeeLimitMultiplier == 0 {
return errors.New("must provide FeeLimitMultiplier")
}
if m.MinBasefeeGwei < m.MinTipCapGwei {
return fmt.Errorf("minBasefee smaller than minTipCap, have %f < %f",
m.MinBasefeeGwei, m.MinTipCapGwei)
}
if m.ResubmissionTimeout == 0 {
return errors.New("must provide ResubmissionTimeout")
}
Expand Down Expand Up @@ -249,6 +268,8 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig {
SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName),
FeeLimitThresholdGwei: ctx.Float64(FeeLimitThresholdFlagName),
MinBasefeeGwei: ctx.Float64(MinBasefeeFlagName),
MinTipCapGwei: ctx.Float64(MinTipCapFlagName),
ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName),
NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName),
Expand Down Expand Up @@ -289,21 +310,28 @@ func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) {
return Config{}, fmt.Errorf("could not init signer: %w", err)
}

if thr := cfg.FeeLimitThresholdGwei; math.IsNaN(thr) || math.IsInf(thr, 0) {
return Config{}, fmt.Errorf("invalid fee limit threshold: %v", thr)
feeLimitThreshold, err := gweiToWei(cfg.FeeLimitThresholdGwei)
if err != nil {
return Config{}, fmt.Errorf("invalid fee limit threshold: %w", err)
}

// convert float GWei value into integer Wei value
feeLimitThreshold, _ := new(big.Float).Mul(
big.NewFloat(cfg.FeeLimitThresholdGwei),
big.NewFloat(params.GWei)).
Int(nil)
minBasefee, err := gweiToWei(cfg.MinBasefeeGwei)
if err != nil {
return Config{}, fmt.Errorf("invalid min basefee: %w", err)
}

minTipCap, err := gweiToWei(cfg.MinTipCapGwei)
if err != nil {
return Config{}, fmt.Errorf("invalid min tip cap: %w", err)
}

return Config{
Backend: l1,
ResubmissionTimeout: cfg.ResubmissionTimeout,
FeeLimitMultiplier: cfg.FeeLimitMultiplier,
FeeLimitThreshold: feeLimitThreshold,
MinBasefee: minBasefee,
MinTipCap: minTipCap,
ChainID: chainID,
TxSendTimeout: cfg.TxSendTimeout,
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
Expand Down Expand Up @@ -333,6 +361,12 @@ type Config struct {
// below this threshold.
FeeLimitThreshold *big.Int

// Minimum basefee (in Wei) to assume when determining tx fees.
MinBasefee *big.Int

// Minimum tip cap (in Wei) to enforce when determining tx fees.
MinTipCap *big.Int

// ChainID is the chain ID of the L1 chain.
ChainID *big.Int

Expand Down Expand Up @@ -380,6 +414,10 @@ func (m Config) Check() error {
if m.FeeLimitMultiplier == 0 {
return errors.New("must provide FeeLimitMultiplier")
}
if m.MinBasefee != nil && m.MinTipCap != nil && m.MinBasefee.Cmp(m.MinTipCap) == -1 {
return fmt.Errorf("minBasefee smaller than minTipCap, have %v < %v",
m.MinBasefee, m.MinTipCap)
}
if m.ResubmissionTimeout == 0 {
return errors.New("must provide ResubmissionTimeout")
}
Expand All @@ -400,3 +438,21 @@ func (m Config) Check() error {
}
return nil
}

func gweiToWei(gwei float64) (*big.Int, error) {
if math.IsNaN(gwei) || math.IsInf(gwei, 0) {
return nil, fmt.Errorf("invalid gwei value: %v", gwei)
}

// convert float GWei value into integer Wei value
wei, _ := new(big.Float).Mul(
big.NewFloat(gwei),
big.NewFloat(params.GWei)).
Int(nil)

if wei.Cmp(abi.MaxUint256) == 1 {
return nil, errors.New("gwei value larger than max uint256")
}

return wei, nil
}
16 changes: 14 additions & 2 deletions op-service/txmgr/txmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -594,9 +594,21 @@ func (m *SimpleTxManager) suggestGasPriceCaps(ctx context.Context) (*big.Int, *b
} else if head.BaseFee == nil {
return nil, nil, errors.New("txmgr does not support pre-london blocks that do not have a basefee")
}
m.metr.RecordBasefee(head.BaseFee)
basefee := head.BaseFee
m.metr.RecordBasefee(basefee)
m.metr.RecordTipCap(tip)
return tip, head.BaseFee, nil

// Enforce minimum basefee and tip cap
if minTipCap := m.cfg.MinTipCap; minTipCap != nil && tip.Cmp(minTipCap) == -1 {
m.l.Debug("Enforcing min tip cap", "minTipCap", m.cfg.MinTipCap, "origTipCap", tip)
tip = new(big.Int).Set(m.cfg.MinTipCap)
}
if minBasefee := m.cfg.MinBasefee; minBasefee != nil && basefee.Cmp(minBasefee) == -1 {
m.l.Debug("Enforcing min basefee", "minBasefee", m.cfg.MinBasefee, "origBasefee", basefee)
basefee = new(big.Int).Set(m.cfg.MinBasefee)
}

return tip, basefee, nil
}

func (m *SimpleTxManager) checkLimits(tip, basefee, bumpedTip, bumpedFee *big.Int) error {
Expand Down
71 changes: 69 additions & 2 deletions op-service/txmgr/txmgr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -871,15 +871,15 @@ func TestIncreaseGasPrice(t *testing.T) {
},
},
{
name: "enforces min bump on only tip incrase",
name: "enforces min bump on only tip increase",
run: func(t *testing.T) {
tx, newTx := doGasPriceIncrease(t, 100, 1000, 101, 440)
require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger")
require.True(t, newTx.GasTipCap().Cmp(tx.GasTipCap()) > 0, "new tx tip must be larger")
},
},
{
name: "enforces min bump on only basefee incrase",
name: "enforces min bump on only basefee increase",
run: func(t *testing.T) {
tx, newTx := doGasPriceIncrease(t, 100, 1000, 99, 460)
require.True(t, newTx.GasFeeCap().Cmp(tx.GasFeeCap()) > 0, "new tx fee cap must be larger")
Expand Down Expand Up @@ -1053,3 +1053,70 @@ func TestNonceReset(t *testing.T) {
// internal nonce tracking should be reset every 3rd tx
require.Equal(t, []uint64{0, 0, 1, 2, 0, 1, 2, 0}, nonces)
}

func TestMinFees(t *testing.T) {
for _, tt := range []struct {
desc string
minBasefee *big.Int
minTipCap *big.Int
expectMinBasefee bool
expectMinTipCap bool
}{
{
desc: "no-mins",
},
{
desc: "high-min-basefee",
minBasefee: big.NewInt(10_000_000),
expectMinBasefee: true,
},
{
desc: "high-min-tipcap",
minTipCap: big.NewInt(1_000_000),
expectMinTipCap: true,
},
{
desc: "high-mins",
minBasefee: big.NewInt(10_000_000),
minTipCap: big.NewInt(1_000_000),
expectMinBasefee: true,
expectMinTipCap: true,
},
{
desc: "low-min-basefee",
minBasefee: big.NewInt(1),
},
{
desc: "low-min-tipcap",
minTipCap: big.NewInt(1),
},
{
desc: "low-mins",
minBasefee: big.NewInt(1),
minTipCap: big.NewInt(1),
},
} {
t.Run(tt.desc, func(t *testing.T) {
require := require.New(t)
conf := configWithNumConfs(1)
conf.MinBasefee = tt.minBasefee
conf.MinTipCap = tt.minTipCap
h := newTestHarnessWithConfig(t, conf)

tip, basefee, err := h.mgr.suggestGasPriceCaps(context.TODO())
require.NoError(err)

if tt.expectMinBasefee {
require.Equal(tt.minBasefee, basefee, "expect suggested basefee to equal MinBasefee")
} else {
require.Equal(h.gasPricer.baseBaseFee, basefee, "expect suggested basefee to equal mock basefee")
}

if tt.expectMinTipCap {
require.Equal(tt.minTipCap, tip, "expect suggested tip to equal MinTipCap")
} else {
require.Equal(h.gasPricer.baseGasTipFee, tip, "expect suggested tip to equal mock tip")
}
})
}
}

0 comments on commit 86e731e

Please sign in to comment.