Merge pull request #2306 from MetaMask/i2294-MarkTxAsFailedAfterSameNonceIsMined

Mark tx as failed after tx with same nonce is mined
feature/default_network_editable
kumavis 7 years ago committed by GitHub
commit 9adf77e3fe
  1. 1
      app/scripts/controllers/transactions.js
  2. 20
      app/scripts/lib/pending-tx-tracker.js
  3. 6
      app/scripts/lib/tx-state-manager.js
  4. 52
      test/unit/pending-tx-test.js

@ -62,6 +62,7 @@ module.exports = class TransactionController extends EventEmitter {
retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day. retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day.
publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx),
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
}) })
this.txStateManager.store.subscribe(() => this.emit('update:badge')) this.txStateManager.store.subscribe(() => this.emit('update:badge'))

@ -25,6 +25,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
// default is one day // default is one day
this.retryTimePeriod = config.retryTimePeriod || 86400000 this.retryTimePeriod = config.retryTimePeriod || 86400000
this.getPendingTransactions = config.getPendingTransactions this.getPendingTransactions = config.getPendingTransactions
this.getCompletedTransactions = config.getCompletedTransactions
this.publishTransaction = config.publishTransaction this.publishTransaction = config.publishTransaction
} }
@ -120,6 +121,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
async _checkPendingTx (txMeta) { async _checkPendingTx (txMeta) {
const txHash = txMeta.hash const txHash = txMeta.hash
const txId = txMeta.id const txId = txMeta.id
// extra check in case there was an uncaught error during the // extra check in case there was an uncaught error during the
// signature and submission process // signature and submission process
if (!txHash) { if (!txHash) {
@ -128,6 +130,15 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.emit('tx:failed', txId, noTxHashErr) this.emit('tx:failed', txId, noTxHashErr)
return return
} }
// If another tx with the same nonce is mined, set as failed.
const taken = await this._checkIfNonceIsTaken(txMeta)
if (taken) {
const nonceTakenErr = new Error('Another transaction with this nonce has been mined.')
nonceTakenErr.name = 'NonceTakenErr'
return this.emit('tx:failed', txId, nonceTakenErr)
}
// get latest transaction status // get latest transaction status
let txParams let txParams
try { try {
@ -159,4 +170,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
} }
nonceGlobalLock.releaseLock() nonceGlobalLock.releaseLock()
} }
async _checkIfNonceIsTaken (txMeta) {
const completed = this.getCompletedTransactions()
const sameNonce = completed.filter((otherMeta) => {
return otherMeta.txParams.nonce === txMeta.txParams.nonce
})
return sameNonce.length > 0
}
} }

@ -46,6 +46,12 @@ module.exports = class TransactionStateManger extends EventEmitter {
return this.getFilteredTxList(opts) return this.getFilteredTxList(opts)
} }
getConfirmedTransactions (address) {
const opts = { status: 'confirmed' }
if (address) opts.from = address
return this.getFilteredTxList(opts)
}
addTx (txMeta) { addTx (txMeta) {
this.once(`${txMeta.id}:signed`, function (txId) { this.once(`${txMeta.id}:signed`, function (txId) {
this.removeAllListeners(`${txMeta.id}:rejected`) this.removeAllListeners(`${txMeta.id}:rejected`)

@ -5,6 +5,8 @@ const ObservableStore = require('obs-store')
const clone = require('clone') const clone = require('clone')
const { createStubedProvider } = require('../stub/provider') const { createStubedProvider } = require('../stub/provider')
const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker') const PendingTransactionTracker = require('../../app/scripts/lib/pending-tx-tracker')
const MockTxGen = require('../lib/mock-tx-gen')
const sinon = require('sinon')
const noop = () => true const noop = () => true
const currentNetworkId = 42 const currentNetworkId = 42
const otherNetworkId = 36 const otherNetworkId = 36
@ -46,10 +48,60 @@ describe('PendingTransactionTracker', function () {
} }
}, },
getPendingTransactions: () => {return []}, getPendingTransactions: () => {return []},
getCompletedTransactions: () => {return []},
publishTransaction: () => {}, publishTransaction: () => {},
}) })
}) })
describe('_checkPendingTx state management', function () {
let stub
afterEach(function () {
if (stub) {
stub.restore()
}
})
it('should become failed if another tx with the same nonce succeeds', async function () {
// SETUP
const txGen = new MockTxGen()
txGen.generate({
id: '456',
value: '0x01',
hash: '0xbad',
status: 'confirmed',
nonce: '0x01',
}, { count: 1 })
const pending = txGen.generate({
id: '123',
value: '0x02',
hash: '0xfad',
status: 'submitted',
nonce: '0x01',
}, { count: 1 })[0]
stub = sinon.stub(pendingTxTracker, 'getCompletedTransactions')
.returns(txGen.txs)
// THE EXPECTATION
const spy = sinon.spy()
pendingTxTracker.on('tx:failed', (txId, err) => {
assert.equal(txId, pending.id, 'should fail the pending tx')
assert.equal(err.name, 'NonceTakenErr', 'should emit a nonce taken error.')
spy(txId, err)
})
// THE METHOD
await pendingTxTracker._checkPendingTx(pending)
// THE ASSERTION
assert.ok(spy.calledWith(pending.id), 'tx failed should be emitted')
})
})
describe('#checkForTxInBlock', function () { describe('#checkForTxInBlock', function () {
it('should return if no pending transactions', function () { it('should return if no pending transactions', function () {
// throw a type error if it trys to do anything on the block // throw a type error if it trys to do anything on the block

Loading…
Cancel
Save