Commit 753c36ca authored by Michael de Hoog's avatar Michael de Hoog

txmgr: don't wait the resubmission timeout for gas increases for underpriced errors

parent cf7b329a
...@@ -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("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,17 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -297,19 +294,17 @@ 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) { sendTx := func(tx *types.Transaction, bumpFees bool) *types.Transaction {
defer wg.Done() return m.publishAndWaitForTx(ctx, tx, sendState, receiptChan, bumpFees, wg.Done)
m.publishAndWaitForTx(ctx, tx, sendState, receiptChan)
} }
// Immediately publish a transaction before starting the resumbission loop // Immediately publish a transaction before starting the resumbission loop
wg.Add(1) wg.Add(1)
go sendTxAsync(tx) tx = sendTx(tx, false)
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,25 +317,14 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -322,25 +317,14 @@ 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
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) wg.Add(1)
bumpCounter += 1 tx = sendTx(tx, true)
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
} }
...@@ -350,57 +334,101 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t ...@@ -350,57 +334,101 @@ func (m *SimpleTxManager) sendTx(ctx context.Context, tx *types.Transaction) (*t
// publishAndWaitForTx publishes the transaction to the transaction pool and then waits for it with [waitMined]. // publishAndWaitForTx publishes the transaction to the transaction pool and then waits for it with [waitMined].
// 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 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
// for the transaction. // for the transaction.
func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt) { func (m *SimpleTxManager) publishAndWaitForTx(ctx context.Context, tx *types.Transaction, sendState *SendState, receiptChan chan *types.Receipt, bumpFees bool, done func()) *types.Transaction {
log := m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap()) waiting := false
log.Info("Publishing transaction") defer func() {
if !waiting {
done()
}
}()
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout) updateLogFields := func(tx *types.Transaction) log.Logger {
defer cancel() return m.l.New("hash", tx.Hash(), "nonce", tx.Nonce(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
}
l := updateLogFields(tx)
l.Info("Publishing transaction")
t := time.Now() t := time.Now()
err := m.backend.SendTransaction(cCtx, tx)
sendState.ProcessSendError(err)
// Properly log & exit if there is an error for {
if err != nil { if bumpFees {
newTx, err := m.increaseGasPrice(ctx, tx)
if err != nil {
l.Error("unable to increase gas", "err", err)
m.metr.TxPublished("bump_failed")
return tx
}
tx = newTx
sendState.bumpCount++
l = updateLogFields(tx)
}
bumpFees = 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
}
cCtx, cancel := context.WithTimeout(ctx, m.cfg.NetworkTimeout)
err := m.backend.SendTransaction(cCtx, tx)
cancel()
sendState.ProcessSendError(err)
if err == nil {
break
}
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
} }
m.metr.TxPublished("")
m.metr.TxPublished("")
log.Info("Transaction successfully published") log.Info("Transaction successfully published")
// Poll for the transaction to be ready & then send the result to receiptChan
receipt, err := m.waitMined(ctx, tx, sendState) waiting = true
if err != nil { go func() {
// this will happen if the tx was successfully replaced by a tx with bumped fees defer done()
log.Info("Transaction receipt not found", "err", err) // Poll for the transaction to be ready & then send the result to receiptChan
return receipt, err := m.waitMined(ctx, tx, sendState)
} if err != nil {
select { // this will happen if the tx was successfully replaced by a tx with bumped fees
case receiptChan <- receipt: log.Info("Transaction receipt not found", "err", err)
m.metr.RecordTxConfirmationLatency(time.Since(t).Milliseconds()) return
default: }
} select {
case receiptChan <- receipt:
m.metr.RecordTxConfirmationLatency(time.Since(t).Milliseconds())
default:
}
}()
return tx
} }
// waitMined waits for the transaction to be mined or for the context to be cancelled. // waitMined waits for the transaction to be mined or for the context to be cancelled.
...@@ -485,15 +513,13 @@ func (m *SimpleTxManager) increaseGasPrice(ctx context.Context, tx *types.Transa ...@@ -485,15 +513,13 @@ 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 5x 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