Commit 58769ba1 authored by Joshua Gutow's avatar Joshua Gutow Committed by GitHub

Merge pull request #7472 from mdehoog/michael/retry-underpriced-immediately

txmgr: don't wait the resubmission timeout for gas increases for underpriced errors
parents 1d9d830a ce0f43d4
...@@ -71,6 +71,7 @@ func newTxMgrConfig(l1Addr string, privKey *ecdsa.PrivateKey) txmgr.CLIConfig { ...@@ -71,6 +71,7 @@ func newTxMgrConfig(l1Addr string, privKey *ecdsa.PrivateKey) txmgr.CLIConfig {
PrivateKey: hexPriv(privKey), PrivateKey: hexPriv(privKey),
NumConfirmations: 1, NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
FeeLimitMultiplier: 5,
ResubmissionTimeout: 3 * time.Second, ResubmissionTimeout: 3 * time.Second,
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NetworkTimeout: 2 * time.Second, NetworkTimeout: 2 * time.Second,
......
...@@ -26,6 +26,7 @@ const ( ...@@ -26,6 +26,7 @@ const (
// TxMgr Flags (new + legacy + some shared flags) // TxMgr Flags (new + legacy + some shared flags)
NumConfirmationsFlagName = "num-confirmations" NumConfirmationsFlagName = "num-confirmations"
SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count" SafeAbortNonceTooLowCountFlagName = "safe-abort-nonce-too-low-count"
FeeLimitMultiplierFlagName = "fee-limit-multiplier"
ResubmissionTimeoutFlagName = "resubmission-timeout" ResubmissionTimeoutFlagName = "resubmission-timeout"
NetworkTimeoutFlagName = "network-timeout" NetworkTimeoutFlagName = "network-timeout"
TxSendTimeoutFlagName = "txmgr.send-timeout" TxSendTimeoutFlagName = "txmgr.send-timeout"
...@@ -51,6 +52,7 @@ var ( ...@@ -51,6 +52,7 @@ var (
type DefaultFlagValues struct { type DefaultFlagValues struct {
NumConfirmations uint64 NumConfirmations uint64
SafeAbortNonceTooLowCount uint64 SafeAbortNonceTooLowCount uint64
FeeLimitMultiplier uint64
ResubmissionTimeout time.Duration ResubmissionTimeout time.Duration
NetworkTimeout time.Duration NetworkTimeout time.Duration
TxSendTimeout time.Duration TxSendTimeout time.Duration
...@@ -62,6 +64,7 @@ var ( ...@@ -62,6 +64,7 @@ var (
DefaultBatcherFlagValues = DefaultFlagValues{ DefaultBatcherFlagValues = DefaultFlagValues{
NumConfirmations: uint64(10), NumConfirmations: uint64(10),
SafeAbortNonceTooLowCount: uint64(3), SafeAbortNonceTooLowCount: uint64(3),
FeeLimitMultiplier: uint64(5),
ResubmissionTimeout: 48 * time.Second, ResubmissionTimeout: 48 * time.Second,
NetworkTimeout: 10 * time.Second, NetworkTimeout: 10 * time.Second,
TxSendTimeout: 0 * time.Second, TxSendTimeout: 0 * time.Second,
...@@ -71,6 +74,7 @@ var ( ...@@ -71,6 +74,7 @@ var (
DefaultChallengerFlagValues = DefaultFlagValues{ DefaultChallengerFlagValues = DefaultFlagValues{
NumConfirmations: uint64(3), NumConfirmations: uint64(3),
SafeAbortNonceTooLowCount: uint64(3), SafeAbortNonceTooLowCount: uint64(3),
FeeLimitMultiplier: uint64(5),
ResubmissionTimeout: 24 * time.Second, ResubmissionTimeout: 24 * time.Second,
NetworkTimeout: 10 * time.Second, NetworkTimeout: 10 * time.Second,
TxSendTimeout: 2 * time.Minute, TxSendTimeout: 2 * time.Minute,
...@@ -115,6 +119,12 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl ...@@ -115,6 +119,12 @@ func CLIFlagsWithDefaults(envPrefix string, defaults DefaultFlagValues) []cli.Fl
Value: defaults.SafeAbortNonceTooLowCount, Value: defaults.SafeAbortNonceTooLowCount,
EnvVars: prefixEnvVars("SAFE_ABORT_NONCE_TOO_LOW_COUNT"), EnvVars: prefixEnvVars("SAFE_ABORT_NONCE_TOO_LOW_COUNT"),
}, },
&cli.Uint64Flag{
Name: FeeLimitMultiplierFlagName,
Usage: "The multiplier applied to fee suggestions to put a hard limit on fee increases",
Value: defaults.FeeLimitMultiplier,
EnvVars: prefixEnvVars("TXMGR_FEE_LIMIT_MULTIPLIER"),
},
&cli.DurationFlag{ &cli.DurationFlag{
Name: ResubmissionTimeoutFlagName, Name: ResubmissionTimeoutFlagName,
Usage: "Duration we will wait before resubmitting a transaction to L1", Usage: "Duration we will wait before resubmitting a transaction to L1",
...@@ -158,6 +168,7 @@ type CLIConfig struct { ...@@ -158,6 +168,7 @@ type CLIConfig struct {
SignerCLIConfig opsigner.CLIConfig SignerCLIConfig opsigner.CLIConfig
NumConfirmations uint64 NumConfirmations uint64
SafeAbortNonceTooLowCount uint64 SafeAbortNonceTooLowCount uint64
FeeLimitMultiplier uint64
ResubmissionTimeout time.Duration ResubmissionTimeout time.Duration
ReceiptQueryInterval time.Duration ReceiptQueryInterval time.Duration
NetworkTimeout time.Duration NetworkTimeout time.Duration
...@@ -170,6 +181,7 @@ func NewCLIConfig(l1RPCURL string, defaults DefaultFlagValues) CLIConfig { ...@@ -170,6 +181,7 @@ func NewCLIConfig(l1RPCURL string, defaults DefaultFlagValues) CLIConfig {
L1RPCURL: l1RPCURL, L1RPCURL: l1RPCURL,
NumConfirmations: defaults.NumConfirmations, NumConfirmations: defaults.NumConfirmations,
SafeAbortNonceTooLowCount: defaults.SafeAbortNonceTooLowCount, SafeAbortNonceTooLowCount: defaults.SafeAbortNonceTooLowCount,
FeeLimitMultiplier: defaults.FeeLimitMultiplier,
ResubmissionTimeout: defaults.ResubmissionTimeout, ResubmissionTimeout: defaults.ResubmissionTimeout,
NetworkTimeout: defaults.NetworkTimeout, NetworkTimeout: defaults.NetworkTimeout,
TxSendTimeout: defaults.TxSendTimeout, TxSendTimeout: defaults.TxSendTimeout,
...@@ -189,6 +201,9 @@ func (m CLIConfig) Check() error { ...@@ -189,6 +201,9 @@ func (m CLIConfig) Check() error {
if m.NetworkTimeout == 0 { if m.NetworkTimeout == 0 {
return errors.New("must provide NetworkTimeout") return errors.New("must provide NetworkTimeout")
} }
if m.FeeLimitMultiplier == 0 {
return errors.New("must provide FeeLimitMultiplier")
}
if m.ResubmissionTimeout == 0 { if m.ResubmissionTimeout == 0 {
return errors.New("must provide ResubmissionTimeout") return errors.New("must provide ResubmissionTimeout")
} }
...@@ -218,6 +233,7 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig { ...@@ -218,6 +233,7 @@ func ReadCLIConfig(ctx *cli.Context) CLIConfig {
SignerCLIConfig: opsigner.ReadCLIConfig(ctx), SignerCLIConfig: opsigner.ReadCLIConfig(ctx),
NumConfirmations: ctx.Uint64(NumConfirmationsFlagName), NumConfirmations: ctx.Uint64(NumConfirmationsFlagName),
SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName), SafeAbortNonceTooLowCount: ctx.Uint64(SafeAbortNonceTooLowCountFlagName),
FeeLimitMultiplier: ctx.Uint64(FeeLimitMultiplierFlagName),
ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName), ResubmissionTimeout: ctx.Duration(ResubmissionTimeoutFlagName),
ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName), ReceiptQueryInterval: ctx.Duration(ReceiptQueryIntervalFlagName),
NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName), NetworkTimeout: ctx.Duration(NetworkTimeoutFlagName),
...@@ -261,6 +277,7 @@ func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) { ...@@ -261,6 +277,7 @@ func NewConfig(cfg CLIConfig, l log.Logger) (Config, error) {
return Config{ return Config{
Backend: l1, Backend: l1,
ResubmissionTimeout: cfg.ResubmissionTimeout, ResubmissionTimeout: cfg.ResubmissionTimeout,
FeeLimitMultiplier: cfg.FeeLimitMultiplier,
ChainID: chainID, ChainID: chainID,
TxSendTimeout: cfg.TxSendTimeout, TxSendTimeout: cfg.TxSendTimeout,
TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout, TxNotInMempoolTimeout: cfg.TxNotInMempoolTimeout,
...@@ -282,6 +299,9 @@ type Config struct { ...@@ -282,6 +299,9 @@ type Config struct {
// attempted. // attempted.
ResubmissionTimeout time.Duration ResubmissionTimeout time.Duration
// The multiplier applied to fee suggestions to put a hard limit on fee increases.
FeeLimitMultiplier uint64
// ChainID is the chain ID of the L1 chain. // ChainID is the chain ID of the L1 chain.
ChainID *big.Int ChainID *big.Int
...@@ -326,6 +346,9 @@ func (m Config) Check() error { ...@@ -326,6 +346,9 @@ func (m Config) Check() error {
if m.NetworkTimeout == 0 { if m.NetworkTimeout == 0 {
return errors.New("must provide NetworkTimeout") return errors.New("must provide NetworkTimeout")
} }
if m.FeeLimitMultiplier == 0 {
return errors.New("must provide FeeLimitMultiplier")
}
if m.ResubmissionTimeout == 0 { if m.ResubmissionTimeout == 0 {
return errors.New("must provide ResubmissionTimeout") return errors.New("must provide ResubmissionTimeout")
} }
......
...@@ -26,6 +26,9 @@ type SendState struct { ...@@ -26,6 +26,9 @@ type SendState struct {
// Counts of the different types of errors // Counts of the different types of errors
successFullPublishCount uint64 // nil error => tx made it to the mempool successFullPublishCount uint64 // nil error => tx made it to the mempool
safeAbortNonceTooLowCount uint64 // nonce too low error safeAbortNonceTooLowCount uint64 // nonce too low error
// Miscellaneous tracking
bumpCount int // number of times we have bumped the gas price
} }
// NewSendStateWithNow creates a new send state with the provided clock. // NewSendStateWithNow creates a new send state with the provided clock.
......
...@@ -24,9 +24,6 @@ import ( ...@@ -24,9 +24,6 @@ import (
const ( const (
// Geth requires a minimum fee bump of 10% for tx resubmission // Geth requires a minimum fee bump of 10% for tx resubmission
priceBump int64 = 10 priceBump int64 = 10
// The multiplier applied to fee suggestions to put a hard limit on fee increases
feeLimitMultiplier = 5
) )
// new = old * (100 + priceBump) / 100 // new = old * (100 + priceBump) / 100
...@@ -297,19 +294,26 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -297,19 +294,26 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
sendState := NewSendState(m.cfg.SafeAbortNonceTooLowCount, m.cfg.TxNotInMempoolTimeout) sendState := NewSendState(m.cfg.SafeAbortNonceTooLowCount, m.cfg.TxNotInMempoolTimeout)
receiptChan := make(chan *types.Receipt, 1) receiptChan := make(chan *types.Receipt, 1)
sendTxAsync := func(tx *types.Transaction) { publishAndWait := func(tx *types.Transaction, bumpFees bool) *types.Transaction {
defer wg.Done() wg.Add(1)
m.publishAndWaitForTx(ctx, tx, sendState, receiptChan) tx, published := m.publishTx(ctx, tx, sendState, bumpFees)
if published {
go func() {
defer wg.Done()
m.waitForTx(ctx, tx, sendState, receiptChan)
}()
} else {
wg.Done()
}
return tx
} }
// Immediately publish a transaction before starting the resumbission loop // Immediately publish a transaction before starting the resumbission loop
wg.Add(1) tx = publishAndWait(tx, false)
go sendTxAsync(tx)
ticker := time.NewTicker(m.cfg.ResubmissionTimeout) ticker := time.NewTicker(m.cfg.ResubmissionTimeout)
defer ticker.Stop() defer ticker.Stop()
bumpCounter := 0
for { for {
select { select {
case <-ticker.C: case <-ticker.C:
...@@ -322,73 +326,95 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -322,73 +326,95 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
m.l.Warn("Aborting transaction submission") m.l.Warn("Aborting transaction submission")
return nil, errors.New("aborted transaction sending") return nil, errors.New("aborted transaction sending")
} }
// Increase the gas price & submit the new transaction tx = publishAndWait(tx, true)
newTx, err := m.increaseGasPrice(ctx, tx)
if err != nil || sendState.IsWaitingForConfirmation() {
// there is a chance the previous tx goes into "waiting for confirmation" state
// during the increaseGasPrice call. In some (but not all) cases increaseGasPrice
// will error out during gas estimation. In either case we should continue waiting
// rather than resubmit the tx.
continue
}
tx = newTx
wg.Add(1)
bumpCounter += 1
go sendTxAsync(tx)
case <-ctx.Done(): case <-ctx.Done():
return nil, ctx.Err() return nil, ctx.Err()
case receipt := <-receiptChan: case receipt := <-receiptChan:
m.metr.RecordGasBumpCount(bumpCounter) m.metr.RecordGasBumpCount(sendState.bumpCount)
m.metr.TxConfirmed(receipt) m.metr.TxConfirmed(receipt)
return receipt, nil return receipt, nil
} }
} }
} }
// publishAndWaitForTx publishes the transaction to the transaction pool and then waits for it with [waitMined]. // publishTx publishes the transaction to the transaction pool. If it receives any underpriced errors
// It should be called in a new go-routine. It will send the receipt to receiptChan in a non-blocking way if a receipt is found // it will bump the fees and retry.
// for the transaction. // Returns the latest fee bumped tx, and a boolean indicating whether the tx was sent or not
func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) { func (m *SimpleTxManager) publishTx(ctx context.Context, tx *types.Transaction, sendState *SendState, bumpFeesImmediately bool) (*types.Transaction, bool) {
log := m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) updateLogFields := func(tx *types.Transaction) log.Logger {
log.Info("Publishing transaction") return m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
}
l := updateLogFields(tx)
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) l.Info("Publishing transaction")
defer cancel()
t := time.Now() for {
err := m.backend.SendTransaction(cCtx, tx) if bumpFeesImmediately {
sendState.ProcessSendError(err) newTx, err := m.increaseGasPrice(ctx, tx)
if err != nil {
l.Error("unable to increase gas", "err", err)
m.metr.TxPublished("bump_failed")
return tx, false
}
tx = newTx
sendState.bumpCount++
l = updateLogFields(tx)
}
bumpFeesImmediately = true // bump fees next loop
if sendState.IsWaitingForConfirmation() {
// there is a chance the previous tx goes into "waiting for confirmation" state
// during the increaseGasPrice call; continue waiting rather than resubmit the tx
return tx, false
}
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
err := m.backend.SendTransaction(cCtx, tx)
cancel()
sendState.ProcessSendError(err)
if err == nil {
m.metr.TxPublished("")
log.Info("Transaction successfully published")
return tx, true
}
// Properly log & exit if there is an error
if err != nil {
switch { switch {
case errStringMatch(err, core.ErrNonceTooLow): case errStringMatch(err, core.ErrNonceTooLow):
log.Warn("nonce too low", "err", err) l.Warn("nonce too low", "err", err)
m.metr.TxPublished("nonce_to_low") m.metr.TxPublished("nonce_to_low")
case errStringMatch(err, context.Canceled): case errStringMatch(err, context.Canceled):
m.metr.RPCError() m.metr.RPCError()
log.Warn("transaction send cancelled", "err", err) l.Warn("transaction send cancelled", "err", err)
m.metr.TxPublished("context_cancelled") m.metr.TxPublished("context_cancelled")
case errStringMatch(err, txpool.ErrAlreadyKnown): case errStringMatch(err, txpool.ErrAlreadyKnown):
log.Warn("resubmitted already known transaction", "err", err) l.Warn("resubmitted already known transaction", "err", err)
m.metr.TxPublished("tx_already_known") m.metr.TxPublished("tx_already_known")
case errStringMatch(err, txpool.ErrReplaceUnderpriced): case errStringMatch(err, txpool.ErrReplaceUnderpriced):
log.Warn("transaction replacement is underpriced", "err", err) l.Warn("transaction replacement is underpriced", "err", err)
m.metr.TxPublished("tx_replacement_underpriced") m.metr.TxPublished("tx_replacement_underpriced")
continue // retry with fee bump
case errStringMatch(err, txpool.ErrUnderpriced): case errStringMatch(err, txpool.ErrUnderpriced):
log.Warn("transaction is underpriced", "err", err) l.Warn("transaction is underpriced", "err", err)
m.metr.TxPublished("tx_underpriced") m.metr.TxPublished("tx_underpriced")
continue // retry with fee bump
default: default:
m.metr.RPCError() m.metr.RPCError()
log.Error("unable to publish transaction", "err", err) l.Error("unable to publish transaction", "err", err)
m.metr.TxPublished("unknown_error") m.metr.TxPublished("unknown_error")
} }
return
// on non-underpriced error return immediately; will retry on next resubmission timeout
return tx, false
} }
m.metr.TxPublished("") }
log.Info("Transaction successfully published") // waitForTx calls waitMined, and then sends the receipt to receiptChan in a non-blocking way if a receipt is found
// for the transaction. It should be called in a separate goroutine.
func (m *SimpleTxManager) waitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) {
t := time.Now()
// Poll for the transaction to be ready & then send the result to receiptChan // Poll for the transaction to be ready & then send the result to receiptChan
receipt, err := m.waitMined(ctx, tx, sendState) receipt, err := m.waitMined(ctx, tx, sendState)
if err != nil { if err != nil {
...@@ -484,16 +510,14 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa ...@@ -484,16 +510,14 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa
} }
bumpedTip, bumpedFee := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, m.l) bumpedTip, bumpedFee := updateFees(tx.GasTipCap(), tx.GasFeeCap(), tip, basefee, m.l)
// Make sure increase is at most 5x the suggested values // Make sure increase is at most [FeeLimitMultiplier] the suggested values
maxTip := new(big.Int).Mul(tip, big.NewInt(feeLimitMultiplier)) maxTip := new(big.Int).Mul(tip, big.NewInt(int64(m.cfg.FeeLimitMultiplier)))
if bumpedTip.Cmp(maxTip) > 0 { if bumpedTip.Cmp(maxTip) > 0 {
m.l.Warn(fmt.Sprintf("bumped tip getting capped at %dx multiple of the suggested value", feeLimitMultiplier), "bumped", bumpedTip, "suggestion", tip) return nil, fmt.Errorf("bumped tip 0x%s is over %dx multiple of the suggested value", bumpedTip.Text(16), m.cfg.FeeLimitMultiplier)
bumpedTip.Set(maxTip)
} }
maxFee := calcGasFeeCap(new(big.Int).Mul(basefee, big.NewInt(feeLimitMultiplier)), maxTip) maxFee := calcGasFeeCap(new(big.Int).Mul(basefee, big.NewInt(int64(m.cfg.FeeLimitMultiplier))), maxTip)
if bumpedFee.Cmp(maxFee) > 0 { if bumpedFee.Cmp(maxFee) > 0 {
m.l.Warn("bumped fee getting capped at multiple of the implied suggested value", "bumped", bumpedFee, "suggestion", maxFee) return nil, fmt.Errorf("bumped fee 0x%s is over %dx multiple of the suggested value", bumpedFee.Text(16), m.cfg.FeeLimitMultiplier)
bumpedFee.Set(maxFee)
} }
rawTx := &types.DynamicFeeTx{ rawTx := &types.DynamicFeeTx{
ChainID: tx.ChainId(), ChainID: tx.ChainId(),
......
...@@ -80,6 +80,7 @@ func configWithNumConfs(numConfirmations uint64) Config { ...@@ -80,6 +80,7 @@ func configWithNumConfs(numConfirmations uint64) Config {
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: numConfirmations, NumConfirmations: numConfirmations,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
FeeLimitMultiplier: 5,
TxNotInMempoolTimeout: 1 * time.Hour, TxNotInMempoolTimeout: 1 * time.Hour,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return tx, nil return tx, nil
...@@ -766,6 +767,7 @@ func doGasPriceIncrease(t *testing.T, txTipCap, txFeeCap, newTip, newBaseFee int ...@@ -766,6 +767,7 @@ func doGasPriceIncrease(t *testing.T, txTipCap, txFeeCap, newTip, newBaseFee int
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
FeeLimitMultiplier: 5,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return tx, nil return tx, nil
}, },
...@@ -867,6 +869,7 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) { ...@@ -867,6 +869,7 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) {
ReceiptQueryInterval: 50 * time.Millisecond, ReceiptQueryInterval: 50 * time.Millisecond,
NumConfirmations: 1, NumConfirmations: 1,
SafeAbortNonceTooLowCount: 3, SafeAbortNonceTooLowCount: 3,
FeeLimitMultiplier: 5,
Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) { Signer: func(ctx context.Context, from common.Address, tx *types.Transaction) (*types.Transaction, error) {
return tx, nil return tx, nil
}, },
...@@ -883,23 +886,20 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) { ...@@ -883,23 +886,20 @@ func TestIncreaseGasPriceNotExponential(t *testing.T) {
}) })
// Run IncreaseGasPrice a bunch of times in a row to simulate a very fast resubmit loop. // Run IncreaseGasPrice a bunch of times in a row to simulate a very fast resubmit loop.
var err error ctx := context.Background()
for i := 0; i < 30; i++ { for {
ctx := context.Background() newTx, err := mgr.increaseGasPrice(ctx, tx)
tx, err = mgr.increaseGasPrice(ctx, tx) if err != nil {
require.NoError(t, err) break
}
tx = newTx
} }
lastTip, lastFee := tx.GasTipCap(), tx.GasFeeCap() lastTip, lastFee := tx.GasTipCap(), tx.GasFeeCap()
require.Equal(t, lastTip.Int64(), feeLimitMultiplier*borkedTip) require.Equal(t, lastTip.Int64(), int64(36))
require.Equal(t, lastFee.Int64(), feeLimitMultiplier*(borkedTip+2*borkedFee)) require.Equal(t, lastFee.Int64(), int64(493))
// Confirm that fees stop rising // Confirm that fees stop rising
for i := 0; i < 5; i++ { _, err := mgr.increaseGasPrice(ctx, tx)
ctx := context.Background() require.Error(t, err)
tx, err := mgr.increaseGasPrice(ctx, tx)
require.NoError(t, err)
require.True(t, tx.GasTipCap().Cmp(lastTip) == 0, "suggested tx tip must stop increasing")
require.True(t, tx.GasFeeCap().Cmp(lastFee) == 0, "suggested tx fee must stop increasing")
}
} }
func TestErrStringMatch(t *testing.T) { func TestErrStringMatch(t *testing.T) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment