Skip to content

Commit c3d4502

Browse files
committed
Add more tests
1 parent 7ef98a2 commit c3d4502

File tree

3 files changed

+101
-4
lines changed

3 files changed

+101
-4
lines changed

pkg/txm/attempt_builder.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package txm
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
67
"math/big"
78

@@ -13,6 +14,7 @@ import (
1314
"github.com/smartcontractkit/chainlink-evm/pkg/gas"
1415
"github.com/smartcontractkit/chainlink-evm/pkg/keys"
1516
"github.com/smartcontractkit/chainlink-evm/pkg/txm/types"
17+
"github.com/smartcontractkit/chainlink-framework/chains/fees"
1618
)
1719

1820
// maxBumpThreshold controls the maximum number of bumps for an attempt.
@@ -70,11 +72,14 @@ func (a *attemptBuilder) NewAgnosticBumpAttempt(ctx context.Context, lggr logger
7072

7173
// bump purge attempts
7274
if tx.IsPurgeable {
75+
// TODO: add better handling
7376
for {
74-
// TODO: add better handling
7577
bumpedAttempt, err := a.NewBumpAttempt(ctx, lggr, tx, *attempt)
7678
if err != nil {
77-
return attempt, nil
79+
if errors.Is(err, fees.ErrConnectivity) {
80+
return attempt, nil
81+
}
82+
return nil, fmt.Errorf("error bumping attempt for txID: %v, err: %w", tx.ID, err)
7883
}
7984
attempt = bumpedAttempt
8085
}

pkg/txm/txm.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,8 @@ func (t *Txm) getNonce(address common.Address) uint64 {
203203

204204
func (t *Txm) setNonce(address common.Address, nonce uint64) {
205205
t.nonceMapMu.Lock()
206-
t.nonceMap[address] = nonce
207206
defer t.nonceMapMu.Unlock()
207+
t.nonceMap[address] = nonce
208208
}
209209

210210
func newBackoff(minDuration time.Duration) backoff.Backoff {
@@ -371,7 +371,7 @@ func (t *Txm) backfillTransactions(ctx context.Context, address common.Address)
371371
}
372372
if unconfirmedCount == 0 {
373373
t.lggr.Debugf("All transactions confirmed for address: %v", address)
374-
return err
374+
return nil
375375
}
376376

377377
if tx == nil || *tx.Nonce != latestNonce {

pkg/txm/txm_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"github.com/smartcontractkit/chainlink-common/pkg/utils/tests"
1818
"github.com/smartcontractkit/chainlink-evm/pkg/assets"
1919
"github.com/smartcontractkit/chainlink-evm/pkg/gas"
20+
"github.com/smartcontractkit/chainlink-evm/pkg/gas/mocks"
2021
"github.com/smartcontractkit/chainlink-evm/pkg/keys/keystest"
2122
"github.com/smartcontractkit/chainlink-evm/pkg/testutils"
2223
"github.com/smartcontractkit/chainlink-evm/pkg/txm/storage"
@@ -425,3 +426,94 @@ func TestBackfillTransactions(t *testing.T) {
425426
tests.AssertLogEventually(t, observedLogs, fmt.Sprintf("Reached max attempts threshold for txID: %d", 0))
426427
})
427428
}
429+
430+
func TestFlow_ResendTransaction(t *testing.T) {
431+
t.Parallel()
432+
433+
client := newMockClient(t)
434+
txStoreManager := storage.NewInMemoryStoreManager(logger.Test(t), testutils.FixtureChainID)
435+
address := testutils.NewAddress()
436+
require.NoError(t, txStoreManager.Add(address))
437+
config := Config{EIP1559: true, EmptyTxLimitDefault: 22000, RetryBlockThreshold: 1, BlockTime: 2 * time.Second}
438+
mockEstimator := mocks.NewEvmFeeEstimator(t)
439+
defaultGasLimit := uint64(100000)
440+
keystore := &keystest.FakeChainStore{}
441+
attemptBuilder := NewAttemptBuilder(func(address common.Address) *assets.Wei { return assets.NewWeiI(1) }, mockEstimator, keystore, 22000)
442+
stuckTxDetector := NewStuckTxDetector(logger.Test(t), "", StuckTxDetectorConfig{BlockTime: config.BlockTime, StuckTxBlockThreshold: uint32(config.RetryBlockThreshold + 1)})
443+
txm := NewTxm(logger.Test(t), testutils.FixtureChainID, client, attemptBuilder, txStoreManager, stuckTxDetector, config, keystore, nil)
444+
metrics, err := NewTxmMetrics(testutils.FixtureChainID)
445+
require.NoError(t, err)
446+
txm.metrics = metrics
447+
initialNonce := uint64(0)
448+
txm.setNonce(address, initialNonce)
449+
IDK := "IDK"
450+
451+
// Create transaction
452+
_, err = txm.CreateTransaction(t.Context(), &types.TxRequest{
453+
IdempotencyKey: &IDK,
454+
ChainID: testutils.FixtureChainID,
455+
FromAddress: address,
456+
ToAddress: testutils.NewAddress(),
457+
})
458+
require.NoError(t, err)
459+
460+
// Broadcast transaction
461+
mockEstimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
462+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(5), GasFeeCap: assets.NewWeiI(10)}}, defaultGasLimit, nil).Once()
463+
client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
464+
_, err = txm.broadcastTransaction(t.Context(), address)
465+
require.NoError(t, err)
466+
467+
// Backfill transaction
468+
client.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(initialNonce, nil).Maybe() // Transaction was not confirmed
469+
require.NoError(t, txm.backfillTransactions(t.Context(), address))
470+
471+
// Set LastBroadcastAt to a time in the past to trigger retry condition
472+
txStore := txStoreManager.InMemoryStoreMap[address]
473+
require.NotNil(t, txStore)
474+
tx := txStore.UnconfirmedTransactions[initialNonce]
475+
require.NotNil(t, tx)
476+
pastTime := time.Now().Add(-(config.BlockTime*time.Duration(config.RetryBlockThreshold) + 1*time.Second))
477+
tx.LastBroadcastAt = &pastTime
478+
479+
// Retry with bumped fee
480+
client.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(initialNonce, nil).Maybe() // Transaction was not confirmed again
481+
mockEstimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
482+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(5), GasFeeCap: assets.NewWeiI(10)}}, defaultGasLimit, nil).Once()
483+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
484+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(6), GasFeeCap: assets.NewWeiI(12)}}, defaultGasLimit, nil).Once()
485+
client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
486+
require.NoError(t, txm.backfillTransactions(t.Context(), address)) // retry
487+
488+
// Set LastBroadcastAt to a time in the past to trigger purge condition
489+
pastTime = time.Now().Add(-(config.BlockTime*time.Duration(config.RetryBlockThreshold) + 2*time.Second))
490+
tx.LastBroadcastAt = &pastTime
491+
492+
// Purge transaction
493+
client.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(initialNonce, nil).Maybe() // Transaction was not confirmed again
494+
mockEstimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
495+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(5), GasFeeCap: assets.NewWeiI(10)}}, defaultGasLimit, nil).Once()
496+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
497+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(6), GasFeeCap: assets.NewWeiI(12)}}, defaultGasLimit, nil).Once()
498+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
499+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(7), GasFeeCap: assets.NewWeiI(14)}}, defaultGasLimit, nil).Once()
500+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
501+
Return(gas.EvmFee{}, uint64(0), errors.New("transaction propagation issue: transactions are not being mined")).Once() // Purgeable transactions bump up the connectivity percentile, where error is returned
502+
client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
503+
require.NoError(t, txm.backfillTransactions(t.Context(), address)) // retry
504+
505+
// Instant retransmission of purgeable transaction
506+
client.On("NonceAt", mock.Anything, mock.Anything, mock.Anything).Return(initialNonce, nil).Maybe() // Transaction was not confirmed again
507+
mockEstimator.On("GetFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
508+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(5), GasFeeCap: assets.NewWeiI(10)}}, defaultGasLimit, nil).Once()
509+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
510+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(6), GasFeeCap: assets.NewWeiI(12)}}, defaultGasLimit, nil).Once()
511+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
512+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(7), GasFeeCap: assets.NewWeiI(14)}}, defaultGasLimit, nil).Once()
513+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
514+
Return(gas.EvmFee{DynamicFee: gas.DynamicFee{GasTipCap: assets.NewWeiI(8), GasFeeCap: assets.NewWeiI(16)}}, defaultGasLimit, nil).Once()
515+
mockEstimator.On("BumpFee", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).
516+
Return(gas.EvmFee{}, uint64(0), errors.New("transaction propagation issue: transactions are not being mined")).Once() // Purgeable transactions bump up the connectivity percentile, where error is returned
517+
client.On("SendTransaction", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
518+
require.NoError(t, txm.backfillTransactions(t.Context(), address)) // retry
519+
}

0 commit comments

Comments
 (0)