diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 6a70f31d0..cbe4b1dcb 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -52,45 +52,41 @@ export default class PendingTransactionTracker extends EventEmitter { * Resubmits each pending transaction * @param {string} blockNumber - the latest block number in hex * @emits tx:warning + * @returns {Promise} */ - resubmitPendingTxs (blockNumber) { + async resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() - // only try resubmitting if their are transactions to resubmit if (!pending.length) { return } - pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { - /* - Dont marked as failed if the error is a "known" transaction warning - "there is already a transaction with the same sender-nonce - but higher/same gas price" - - Also don't mark as failed if it has ever been broadcast successfully. - A successful broadcast means it may still be mined. - */ - const errorMessage = err.message.toLowerCase() - const isKnownTx = ( - // geth - errorMessage.includes('replacement transaction underpriced') || - errorMessage.includes('known transaction') || - // parity - errorMessage.includes('gas price too low to replace') || - errorMessage.includes('transaction with the same hash was already imported') || - // other - errorMessage.includes('gateway timeout') || - errorMessage.includes('nonce too low') - ) - // ignore resubmit warnings, return early - if (isKnownTx) { - return + for (const txMeta of pending) { + try { + await this._resubmitTx(txMeta, blockNumber) + } catch (err) { + const errorMessage = err.message.toLowerCase() + const isKnownTx = ( + // geth + errorMessage.includes('replacement transaction underpriced') || + errorMessage.includes('known transaction') || + // parity + errorMessage.includes('gas price too low to replace') || + errorMessage.includes('transaction with the same hash was already imported') || + // other + errorMessage.includes('gateway timeout') || + errorMessage.includes('nonce too low') + ) + // ignore resubmit warnings, return early + if (isKnownTx) { + return + } + // encountered real error - transition to error state + txMeta.warning = { + error: errorMessage, + message: 'There was an error when resubmitting this transaction.', + } + this.emit('tx:warning', txMeta, err) } - // encountered real error - transition to error state - txMeta.warning = { - error: errorMessage, - message: 'There was an error when resubmitting this transaction.', - } - this.emit('tx:warning', txMeta, err) - })) + } } /** diff --git a/test/unit/app/controllers/transactions/pending-tx-tracker-test.js b/test/unit/app/controllers/transactions/pending-tx-tracker-test.js index 2c9c500f0..602597849 100644 --- a/test/unit/app/controllers/transactions/pending-tx-tracker-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-tracker-test.js @@ -25,7 +25,7 @@ describe('PendingTransactionTracker', function () { const warningListener = sinon.spy() pendingTxTracker.on('tx:warning', warningListener) - pendingTxTracker.resubmitPendingTxs('0x1') + await pendingTxTracker.resubmitPendingTxs('0x1') assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') assert.ok(resubmitTx.notCalled, 'should NOT call _resubmitTx') @@ -57,7 +57,7 @@ describe('PendingTransactionTracker', function () { const warningListener = sinon.spy() pendingTxTracker.on('tx:warning', warningListener) - pendingTxTracker.resubmitPendingTxs('0x1') + await pendingTxTracker.resubmitPendingTxs('0x1') assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') assert.ok(resubmitTx.calledTwice, 'should call _resubmitTx') @@ -87,12 +87,42 @@ describe('PendingTransactionTracker', function () { const warningListener = sinon.spy() pendingTxTracker.on('tx:warning', warningListener) - pendingTxTracker.resubmitPendingTxs('0x1') + await pendingTxTracker.resubmitPendingTxs('0x1') assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') assert.ok(resubmitTx.calledOnce, 'should call _resubmitTx') assert.ok(warningListener.notCalled, "should NOT emit 'tx:warning'") }) + + it("should emit 'tx:warning' for unknown failed resubmission", async function () { + const getPendingTransactions = sinon.stub().returns([{ + id: 1, + }]) + const pendingTxTracker = new PendingTransactionTracker({ + query: { + getTransactionReceipt: sinon.stub(), + }, + nonceTracker: { + getGlobalLock: sinon.stub().resolves({ + releaseLock: sinon.spy(), + }), + }, + getPendingTransactions, + getCompletedTransactions: sinon.stub().returns([]), + approveTransaction: sinon.spy(), + publishTransaction: sinon.spy(), + confirmTransaction: sinon.spy(), + }) + const resubmitTx = sinon.stub(pendingTxTracker, '_resubmitTx').rejects({ message: 'who dis' }) + const warningListener = sinon.spy() + + pendingTxTracker.on('tx:warning', warningListener) + await pendingTxTracker.resubmitPendingTxs('0x1') + + assert.ok(getPendingTransactions.calledOnceWithExactly(), 'should call getPendingTransaction') + assert.ok(resubmitTx.calledOnce, 'should call _resubmitTx') + assert.ok(warningListener.calledOnce, "should emit 'tx:warning'") + }) }) describe('#updatePendingTxs', function () {