@@ -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