Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bci 3621/try new estimation for insufficient fund error instead of retry #14234

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
090341b
update insufficient fund error to retry new estimation
huangzhen1997 Aug 26, 2024
7e81cec
add comment
huangzhen1997 Aug 26, 2024
7b293b4
add change set
huangzhen1997 Aug 26, 2024
8eb8a92
modify
huangzhen1997 Aug 26, 2024
a6168e1
fix test
huangzhen1997 Aug 27, 2024
b68fb2d
rewrite unit test to modify chain config locally in the unit test
huangzhen1997 Aug 27, 2024
f12c248
Merge branch 'develop' into BCI-3621/try-new-estimation-for-insuffici…
huangzhen1997 Aug 27, 2024
c93063d
remove a previous change
huangzhen1997 Aug 27, 2024
d7f19d1
remove unrelated comment
huangzhen1997 Aug 27, 2024
e858f60
extra
huangzhen1997 Aug 27, 2024
a581071
rephrase
huangzhen1997 Aug 27, 2024
acc9bfa
update comments
huangzhen1997 Aug 27, 2024
3cff2fc
refactor
huangzhen1997 Aug 27, 2024
5cde6d3
revert unit test
huangzhen1997 Aug 27, 2024
d71e9d2
Merge branch 'develop' into BCI-3621/try-new-estimation-for-insuffici…
huangzhen1997 Aug 27, 2024
aa7f9e2
update comments
huangzhen1997 Aug 27, 2024
6f490e3
refactor func call args
huangzhen1997 Aug 27, 2024
614da69
rename
huangzhen1997 Aug 27, 2024
27d6bdf
modify test
huangzhen1997 Aug 27, 2024
ff694a1
fix
huangzhen1997 Aug 28, 2024
c386194
revert function param
huangzhen1997 Aug 28, 2024
a8c424c
changeset
huangzhen1997 Aug 28, 2024
0ce01b4
changeset
huangzhen1997 Aug 28, 2024
84cc573
rephrase
huangzhen1997 Aug 28, 2024
765f061
address comments, refactor
huangzhen1997 Aug 28, 2024
6689d8a
refactor func name
huangzhen1997 Aug 28, 2024
0fdbc37
modify retrycount
huangzhen1997 Aug 28, 2024
15cbf29
fix unit tests
huangzhen1997 Aug 28, 2024
119f77d
rename
huangzhen1997 Aug 28, 2024
10c5cf9
rename
huangzhen1997 Aug 28, 2024
064c0ea
small refactor
huangzhen1997 Aug 28, 2024
5a6dfcc
nit
huangzhen1997 Aug 28, 2024
2113d34
update error
huangzhen1997 Aug 28, 2024
776a9db
modify test
huangzhen1997 Aug 28, 2024
940bdb9
add comments
huangzhen1997 Aug 28, 2024
26d071c
rm unused
huangzhen1997 Aug 28, 2024
2badc93
comments
huangzhen1997 Aug 29, 2024
d6e780b
fix
huangzhen1997 Aug 29, 2024
54e7c4d
adding returned retryable
huangzhen1997 Aug 29, 2024
9882b19
return true for retryable
huangzhen1997 Aug 29, 2024
ff4c290
one more
huangzhen1997 Aug 29, 2024
f019504
address comments
huangzhen1997 Aug 29, 2024
dfea008
revert changes in unit test, just update comment
huangzhen1997 Sep 2, 2024
d7c4f8d
Merge branch 'develop' into BCI-3621/try-new-estimation-for-insuffici…
huangzhen1997 Sep 2, 2024
12b2d92
update comment
huangzhen1997 Sep 3, 2024
8f60729
Merge branch 'develop' into BCI-3621/try-new-estimation-for-insuffici…
huangzhen1997 Sep 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/pretty-trees-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chainlink": minor
---

use new estimation for insufficient fund instead of retry to overcome gas spike #internal
74 changes: 41 additions & 33 deletions common/txmgr/broadcaster.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,21 +558,33 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) hand
eb.sequenceTracker.GenerateNextSequence(etx.FromAddress, *etx.Sequence)
return err, true
case client.Underpriced:
return eb.tryAgainBumpingGas(ctx, lgr, err, etx, attempt, initialBroadcastAt, retryCount+1)
amit-momin marked this conversation as resolved.
Show resolved Hide resolved
bumpedAttempt, retryable, replaceErr := eb.replaceAttemptWithBumpedGas(ctx, lgr, err, etx, attempt)
if replaceErr != nil {
return replaceErr, retryable
}

return eb.handleInProgressTx(ctx, etx, bumpedAttempt, initialBroadcastAt, retryCount+1)
case client.InsufficientFunds:
// NOTE: This bails out of the entire cycle and essentially "blocks" on
// any transaction that gets insufficient_funds. This is OK if a
// transaction with a large VALUE blocks because this always comes last
// in the processing list.
// If it blocks because of a transaction that is expensive due to large
// gas limit, we could have smaller transactions "above" it that could
// theoretically be sent, but will instead be blocked.
// NOTE: This can occur due to either insufficient funds or a gas spike
// combined with a high gas limit. Regardless of the cause, we need to obtain a new estimate,
// replace the current attempt, and retry after the backoff duration.
// The new attempt must be replaced immediately because of a database constraint.
eb.SvcErrBuffer.Append(err)
fallthrough
if _, _, replaceErr := eb.replaceAttemptWithNewEstimation(ctx, lgr, etx, attempt); replaceErr != nil {
return replaceErr, true
}
return err, true
case client.Retryable:
return err, true
case client.FeeOutOfValidRange:
return eb.tryAgainWithNewEstimation(ctx, lgr, err, etx, attempt, initialBroadcastAt)
replacementAttempt, retryable, replaceErr := eb.replaceAttemptWithNewEstimation(ctx, lgr, etx, attempt)
if replaceErr != nil {
return replaceErr, retryable
}

lgr.Warnw("L2 rejected transaction due to incorrect fee, re-estimated and will try again",
"etxID", etx.ID, "err", err, "newGasPrice", replacementAttempt.TxFee, "newGasLimit", replacementAttempt.ChainSpecificFeeLimit)
return eb.handleInProgressTx(ctx, etx, *replacementAttempt, initialBroadcastAt, 0)
case client.Unsupported:
return err, false
case client.ExceedsMaxFee:
Expand Down Expand Up @@ -680,7 +692,8 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) next
return etx, nil
}

func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryAgainBumpingGas(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time, retry int) (err error, retryable bool) {
// replaceAttemptWithBumpedGas performs the replacement of the existing tx attempt with a new bumped fee attempt.
func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) replaceAttemptWithBumpedGas(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) (replacedAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], retryable bool, err error) {
// This log error is not applicable to Hedera since the action required would not be needed for its gas estimator
if eb.chainType != hederaChainType {
logger.With(lgr,
Expand All @@ -694,37 +707,32 @@ func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryA
attempt.TxFee, txError.Error(), eb.feeConfig.FeePriceDefault())
}

replacementAttempt, bumpedFee, bumpedFeeLimit, retryable, err := eb.NewBumpTxAttempt(ctx, etx, attempt, nil, lgr)
bumpedAttempt, bumpedFee, bumpedFeeLimit, retryable, err := eb.NewBumpTxAttempt(ctx, etx, attempt, nil, lgr)
if err != nil {
return fmt.Errorf("tryAgainBumpFee failed: %w", err), retryable
return bumpedAttempt, retryable, err
}

return eb.saveTryAgainAttempt(ctx, lgr, etx, attempt, replacementAttempt, initialBroadcastAt, bumpedFee, bumpedFeeLimit, retry)
}

func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) tryAgainWithNewEstimation(ctx context.Context, lgr logger.Logger, txError error, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time) (err error, retryable bool) {
if attempt.TxType == 0x2 {
err = fmt.Errorf("re-estimation is not supported for EIP-1559 transactions. Node returned error: %v. This is a bug", txError.Error())
logger.Sugared(eb.lggr).AssumptionViolation(err.Error())
return err, false
if err = eb.txStore.SaveReplacementInProgressAttempt(ctx, attempt, &bumpedAttempt); err != nil {
return bumpedAttempt, true, err
}

replacementAttempt, fee, feeLimit, retryable, err := eb.NewTxAttemptWithType(ctx, etx, lgr, attempt.TxType, feetypes.OptForceRefetch)
lgr.Debugw("Bumped fee on initial send", "oldFee", attempt.TxFee.String(), "newFee", bumpedFee.String(), "newFeeLimit", bumpedFeeLimit)
return bumpedAttempt, true, err
}

// replaceAttemptWithNewEstimation performs the replacement of the existing tx attempt with a new estimated fee attempt.
func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) replaceAttemptWithNewEstimation(ctx context.Context, lgr logger.Logger, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) (updatedAttempt *txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], retryable bool, err error) {
newEstimatedAttempt, fee, feeLimit, retryable, err := eb.NewTxAttemptWithType(ctx, etx, lgr, attempt.TxType, feetypes.OptForceRefetch)
if err != nil {
return fmt.Errorf("tryAgainWithNewEstimation failed to build new attempt: %w", err), retryable
return &newEstimatedAttempt, retryable, err
}
lgr.Warnw("L2 rejected transaction due to incorrect fee, re-estimated and will try again",
"etxID", etx.ID, "err", err, "newGasPrice", fee, "newGasLimit", feeLimit)

return eb.saveTryAgainAttempt(ctx, lgr, etx, attempt, replacementAttempt, initialBroadcastAt, fee, feeLimit, 0)
}

func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) saveTryAgainAttempt(ctx context.Context, lgr logger.Logger, etx txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], attempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], replacementAttempt txmgrtypes.TxAttempt[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE], initialBroadcastAt time.Time, newFee FEE, newFeeLimit uint64, retry int) (err error, retyrable bool) {
if err = eb.txStore.SaveReplacementInProgressAttempt(ctx, attempt, &replacementAttempt); err != nil {
return fmt.Errorf("tryAgainWithNewFee failed: %w", err), true
if err = eb.txStore.SaveReplacementInProgressAttempt(ctx, attempt, &newEstimatedAttempt); err != nil {
return &newEstimatedAttempt, true, err
}
lgr.Debugw("Bumped fee on initial send", "oldFee", attempt.TxFee.String(), "newFee", newFee.String(), "newFeeLimit", newFeeLimit)
return eb.handleInProgressTx(ctx, etx, replacementAttempt, initialBroadcastAt, retry)

lgr.Debugw("new estimated fee on initial send", "oldFee", attempt.TxFee.String(), "newFee", fee.String(), "newFeeLimit", feeLimit)
return &newEstimatedAttempt, true, err
}

func (eb *Broadcaster[CHAIN_ID, HEAD, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) saveFatallyErroredTransaction(lgr logger.Logger, etx *txmgrtypes.Tx[CHAIN_ID, ADDR, TX_HASH, BLOCK_HASH, SEQ, FEE]) error {
Expand Down
2 changes: 1 addition & 1 deletion core/chains/evm/txmgr/broadcaster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1524,7 +1524,7 @@ func TestEthBroadcaster_ProcessUnstartedEthTxs_Errors(t *testing.T) {
pgtest.MustExec(t, db, `DELETE FROM evm.txes WHERE nonce = $1`, localNextNonce)
})

t.Run("eth tx is left in progress if eth node returns insufficient eth", func(t *testing.T) {
t.Run("tx is left in progress and its attempt gets replaced with a new re-estimated attempt if node returns insufficient eth", func(t *testing.T) {
insufficientEthError := "insufficient funds for transfer"
localNextNonce := getLocalNextNonce(t, nonceTracker, fromAddress)
etx := mustCreateUnstartedTx(t, txStore, fromAddress, toAddress, encodedPayload, gasLimit, value, testutils.FixtureChainID)
Expand Down
Loading