diff --git a/pkg/transaction/export_test.go b/pkg/transaction/export_test.go index 5ead1d78865..bfd13648dac 100644 --- a/pkg/transaction/export_test.go +++ b/pkg/transaction/export_test.go @@ -6,4 +6,5 @@ package transaction var ( StoredTransactionKey = storedTransactionKey + MaxBlocksToWait = maxBlocksToWait ) diff --git a/pkg/transaction/monitor.go b/pkg/transaction/monitor.go index bbd4a50ec58..ee88749ed0a 100644 --- a/pkg/transaction/monitor.go +++ b/pkg/transaction/monitor.go @@ -21,6 +21,9 @@ import ( var ErrTransactionCancelled = errors.New("transaction cancelled") var ErrMonitorClosed = errors.New("monitor closed") +// maxBlocksToWait is the maximum number of blocks to wait before considering a missing transaction as cancelled. +const maxBlocksToWait = 5 + // Monitor is a nonce-based watcher for transaction confirmations. // Instead of watching transactions individually, the senders nonce is monitored and transactions are checked based on this. // The idea is that if the nonce is still lower than that of a pending transaction, there is no point in actually checking the transaction for a receipt. @@ -199,10 +202,11 @@ func (tm *transactionMonitor) checkPending(block uint64) error { receipt, err := tm.backend.TransactionReceipt(tm.ctx, txHash) if err != nil { // wait for a few blocks to be mined before considering a transaction not existing - transactionWatchNotFoundTimeout := 5 * tm.pollingInterval - if errors.Is(err, ethereum.NotFound) && watchStart(watches).Before(time.Now().Add(transactionWatchNotFoundTimeout)) { - // if both err and receipt are nil, there is no receipt - // the reason why we consider this only potentially cancelled is to catch cases where after a reorg the original transaction wins + transactionWatchNotFoundTimeout := maxBlocksToWait * tm.pollingInterval + if errors.Is(err, ethereum.NotFound) { + if watchStart(watches).Add(transactionWatchNotFoundTimeout).Before(time.Now()) { + cancelledNonces = append(cancelledNonces, nonceGroup) + } continue } return err diff --git a/pkg/transaction/monitor_test.go b/pkg/transaction/monitor_test.go index 7b88e84629f..0217f162284 100644 --- a/pkg/transaction/monitor_test.go +++ b/pkg/transaction/monitor_test.go @@ -388,4 +388,41 @@ func TestMonitorWatchTransaction(t *testing.T) { } }) + t.Run("missing transaction", func(t *testing.T) { + t.Parallel() + var blocks []backendsimulation.Block + for i := range transaction.MaxBlocksToWait + 1 { + blocks = append(blocks, backendsimulation.Block{Number: uint64(i)}) + } + monitor := transaction.NewMonitor( + logger, + backendsimulation.New( + backendsimulation.WithBlocks(blocks...), + ), + sender, + pollingInterval, + cancellationDepth, + ) + + receiptC, errC, err := monitor.WatchTransaction(txHash, nonce) + if err != nil { + t.Fatal(err) + } + + select { + case <-receiptC: + t.Fatal("got receipt") + case err := <-errC: + if !errors.Is(err, transaction.ErrTransactionCancelled) { + t.Fatalf("got wrong error. wanted %v, got %v", transaction.ErrTransactionCancelled, err) + } + case <-time.After(testTimeout): + t.Fatal("timed out") + } + + err = monitor.Close() + if err != nil { + t.Fatal(err) + } + }) }