diff options
Merge branch 'master' of github.com:MetaMask/metamask-extension into i18n-translator-redux
Diffstat (limited to 'app/scripts')
-rw-r--r-- | app/scripts/controllers/transactions.js | 53 | ||||
-rw-r--r-- | app/scripts/lib/tx-state-manager.js | 42 |
2 files changed, 64 insertions, 31 deletions
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 9c2ca0dc8..3e3909361 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -6,7 +6,6 @@ const EthQuery = require('ethjs-query') const TransactionStateManager = require('../lib/tx-state-manager') const TxGasUtil = require('../lib/tx-gas-utils') const PendingTransactionTracker = require('../lib/pending-tx-tracker') -const createId = require('../lib/random-id') const NonceTracker = require('../lib/nonce-tracker') /* @@ -92,8 +91,8 @@ module.exports = class TransactionController extends EventEmitter { this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) + this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) - this.pendingTxTracker.on('tx:confirmed', this.txStateManager.setTxStatusConfirmed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber @@ -186,14 +185,7 @@ module.exports = class TransactionController extends EventEmitter { // validate await this.txGasUtil.validateTxParams(txParams) // construct txMeta - const txMeta = { - id: createId(), - time: (new Date()).getTime(), - status: 'unapproved', - metamaskNetworkId: this.getNetwork(), - txParams: txParams, - loadingDefaults: true, - } + const txMeta = this.txStateManager.generateTxMeta({txParams}) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) // add default tx params @@ -215,7 +207,6 @@ module.exports = class TransactionController extends EventEmitter { const txParams = txMeta.txParams // ensure value txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) - txMeta.nonceSpecified = Boolean(txParams.nonce) let gasPrice = txParams.gasPrice if (!gasPrice) { gasPrice = this.getGasPrice ? this.getGasPrice() : await this.query.gasPrice() @@ -226,11 +217,17 @@ module.exports = class TransactionController extends EventEmitter { return await this.txGasUtil.analyzeGasUsage(txMeta) } - async retryTransaction (txId) { - this.txStateManager.setTxStatusUnapproved(txId) - const txMeta = this.txStateManager.getTx(txId) - txMeta.lastGasPrice = txMeta.txParams.gasPrice - this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry') + async retryTransaction (originalTxId) { + const originalTxMeta = this.txStateManager.getTx(originalTxId) + const lastGasPrice = originalTxMeta.txParams.gasPrice + const txMeta = this.txStateManager.generateTxMeta({ + txParams: originalTxMeta.txParams, + lastGasPrice, + loadingDefaults: false, + }) + this.addTx(txMeta) + this.emit('newUnapprovedTx', txMeta) + return txMeta } async updateTransaction (txMeta) { @@ -253,11 +250,9 @@ module.exports = class TransactionController extends EventEmitter { // wait for a nonce nonceLock = await this.nonceTracker.getNonceLock(fromAddress) // add nonce to txParams - const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce - if (nonce > nonceLock.nextNonce) { - const message = `Specified nonce may not be larger than account's next valid nonce.` - throw new Error(message) - } + // if txMeta has lastGasPrice then it is a retry at same nonce with higher + // gas price transaction and their for the nonce should not be calculated + const nonce = txMeta.lastGasPrice ? txMeta.txParams.nonce : nonceLock.nextNonce txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16)) // add nonce debugging information to txMeta txMeta.nonceDetails = nonceLock.nonceDetails @@ -314,6 +309,22 @@ module.exports = class TransactionController extends EventEmitter { // PRIVATE METHODS // + _markNonceDuplicatesDropped (txId) { + this.txStateManager.setTxStatusConfirmed(txId) + // get the confirmed transactions nonce and from address + const txMeta = this.txStateManager.getTx(txId) + const { nonce, from } = txMeta.txParams + const sameNonceTxs = this.txStateManager.getFilteredTxList({nonce, from}) + if (!sameNonceTxs.length) return + // mark all same nonce transactions as dropped and give i a replacedBy hash + sameNonceTxs.forEach((otherTxMeta) => { + if (otherTxMeta.id === txId) return + otherTxMeta.replacedBy = txMeta.hash + this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:confirmed reference to confirmed txHash with same nonce') + this.txStateManager.setTxStatusDropped(otherTxMeta.id) + }) + } + _updateMemstore () { const unapprovedTxs = this.txStateManager.getUnapprovedTxList() const selectedAddressTxList = this.txStateManager.getFilteredTxList({ diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 2eb006380..ad07c813f 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -1,9 +1,21 @@ const extend = require('xtend') const EventEmitter = require('events') const ObservableStore = require('obs-store') +const createId = require('./random-id') const ethUtil = require('ethereumjs-util') const txStateHistoryHelper = require('./tx-state-history-helper') +// STATUS METHODS + // statuses: + // - `'unapproved'` the user has not responded + // - `'rejected'` the user has responded no! + // - `'approved'` the user has approved the tx + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + // - `'failed'` the tx failed for some reason, included on tx data. + // - `'dropped'` the tx nonce was already used + module.exports = class TransactionStateManager extends EventEmitter { constructor ({ initState, txHistoryLimit, getNetwork }) { super() @@ -16,6 +28,16 @@ module.exports = class TransactionStateManager extends EventEmitter { this.getNetwork = getNetwork } + generateTxMeta (opts) { + return extend({ + id: createId(), + time: (new Date()).getTime(), + status: 'unapproved', + metamaskNetworkId: this.getNetwork(), + loadingDefaults: true, + }, opts) + } + // Returns the number of txs for the current network. getTxCount () { return this.getTxList().length @@ -164,16 +186,6 @@ module.exports = class TransactionStateManager extends EventEmitter { }) } - // STATUS METHODS - // statuses: - // - `'unapproved'` the user has not responded - // - `'rejected'` the user has responded no! - // - `'approved'` the user has approved the tx - // - `'signed'` the tx is signed - // - `'submitted'` the tx is sent to a server - // - `'confirmed'` the tx has been included in a block. - // - `'failed'` the tx failed for some reason, included on tx data. - // get::set status // should return the status of the tx. @@ -202,7 +214,11 @@ module.exports = class TransactionStateManager extends EventEmitter { } // should update the status of the tx to 'submitted'. + // and add a time stamp for when it was called setTxStatusSubmitted (txId) { + const txMeta = this.getTx(txId) + txMeta.submittedTime = (new Date()).getTime() + this.updateTx(txMeta, 'txStateManager - add submitted time stamp') this._setTxStatus(txId, 'submitted') } @@ -211,6 +227,12 @@ module.exports = class TransactionStateManager extends EventEmitter { this._setTxStatus(txId, 'confirmed') } + // should update the status dropped + setTxStatusDropped (txId) { + this._setTxStatus(txId, 'dropped') + } + + setTxStatusFailed (txId, err) { const txMeta = this.getTx(txId) txMeta.err = { |