aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/controllers/transactions/index.js
diff options
context:
space:
mode:
authorKevin Serrano <kevgagser@gmail.com>2018-10-15 08:32:29 +0800
committerGitHub <noreply@github.com>2018-10-15 08:32:29 +0800
commit85884b21afe6e5e85b58123b24e72776d1437cc6 (patch)
tree6dbec947089691c0dffd5febbe6e92c1f712e40f /app/scripts/controllers/transactions/index.js
parentdb981b827b07c946e17d3a8aeaefd2143c236ef7 (diff)
parent7f6b488c04e6ea12b2ef24ac936b6a236ad904c2 (diff)
downloadtangerine-wallet-browser-85884b21afe6e5e85b58123b24e72776d1437cc6.tar.gz
tangerine-wallet-browser-85884b21afe6e5e85b58123b24e72776d1437cc6.tar.zst
tangerine-wallet-browser-85884b21afe6e5e85b58123b24e72776d1437cc6.zip
Merge pull request #5512 from MetaMask/v4.14.0
Version 4.14.0
Diffstat (limited to 'app/scripts/controllers/transactions/index.js')
-rw-r--r--app/scripts/controllers/transactions/index.js122
1 files changed, 109 insertions, 13 deletions
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 8e2288aed..a57c85f50 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -11,6 +11,14 @@ const txUtils = require('./lib/util')
const cleanErrorStack = require('../../lib/cleanErrorStack')
const log = require('loglevel')
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
+const {
+ TRANSACTION_TYPE_CANCEL,
+ TRANSACTION_TYPE_RETRY,
+ TRANSACTION_TYPE_STANDARD,
+ TRANSACTION_STATUS_APPROVED,
+} = require('./enums')
+
+const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util')
/**
Transaction Controller is an aggregate of sub-controllers and trackers
@@ -65,6 +73,7 @@ class TransactionController extends EventEmitter {
this.store = this.txStateManager.store
this.nonceTracker = new NonceTracker({
provider: this.provider,
+ blockTracker: this.blockTracker,
getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager),
getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager),
})
@@ -78,13 +87,17 @@ class TransactionController extends EventEmitter {
})
this.txStateManager.store.subscribe(() => this.emit('update:badge'))
- this._setupListners()
+ this._setupListeners()
// memstore is computed from a few different stores
this._updateMemstore()
this.txStateManager.store.subscribe(() => this._updateMemstore())
this.networkStore.subscribe(() => this._updateMemstore())
this.preferencesStore.subscribe(() => this._updateMemstore())
+
+ // request state update to finalize initialization
+ this._updatePendingTxsAfterFirstBlock()
}
+
/** @returns {number} the chainId*/
getChainId () {
const networkState = this.networkStore.getState()
@@ -153,9 +166,16 @@ class TransactionController extends EventEmitter {
async addUnapprovedTransaction (txParams) {
// validate
const normalizedTxParams = txUtils.normalizeTxParams(txParams)
+ // Assert the from address is the selected address
+ if (normalizedTxParams.from !== this.getSelectedAddress()) {
+ throw new Error(`Transaction from address isn't valid for this account`)
+ }
txUtils.validateTxParams(normalizedTxParams)
// construct txMeta
- let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
+ let txMeta = this.txStateManager.generateTxMeta({
+ txParams: normalizedTxParams,
+ type: TRANSACTION_TYPE_STANDARD,
+ })
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -209,6 +229,7 @@ class TransactionController extends EventEmitter {
txParams: originalTxMeta.txParams,
lastGasPrice,
loadingDefaults: false,
+ type: TRANSACTION_TYPE_RETRY,
})
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
@@ -216,6 +237,40 @@ class TransactionController extends EventEmitter {
}
/**
+ * Creates a new approved transaction to attempt to cancel a previously submitted transaction. The
+ * new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to
+ * the sender's address, and has a higher gasPrice than that of the previous transaction.
+ * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel
+ * @param {string=} customGasPrice - the hex value to use for the cancel transaction
+ * @returns {txMeta}
+ */
+ async createCancelTransaction (originalTxId, customGasPrice) {
+ const originalTxMeta = this.txStateManager.getTx(originalTxId)
+ const { txParams } = originalTxMeta
+ const { gasPrice: lastGasPrice, from, nonce } = txParams
+
+ const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10))
+ const newTxMeta = this.txStateManager.generateTxMeta({
+ txParams: {
+ from,
+ to: from,
+ nonce,
+ gas: '0x5208',
+ value: '0x0',
+ gasPrice: newGasPrice,
+ },
+ lastGasPrice,
+ loadingDefaults: false,
+ status: TRANSACTION_STATUS_APPROVED,
+ type: TRANSACTION_TYPE_CANCEL,
+ })
+
+ this.addTx(newTxMeta)
+ await this.approveTransaction(newTxMeta.id)
+ return newTxMeta
+ }
+
+ /**
updates the txMeta in the txStateManager
@param txMeta {Object} - the updated txMeta
*/
@@ -311,6 +366,11 @@ class TransactionController extends EventEmitter {
this.txStateManager.setTxStatusSubmitted(txId)
}
+ confirmTransaction (txId) {
+ this.txStateManager.setTxStatusConfirmed(txId)
+ this._markNonceDuplicatesDropped(txId)
+ }
+
/**
Convenience method for the ui thats sets the transaction to rejected
@param txId {number} - the tx's Id
@@ -354,6 +414,14 @@ class TransactionController extends EventEmitter {
this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts)
}
+ // called once on startup
+ async _updatePendingTxsAfterFirstBlock () {
+ // wait for first block so we know we're ready
+ await this.blockTracker.getLatestBlock()
+ // get status update for all pending transactions (for the current network)
+ await this.pendingTxTracker.updatePendingTxs()
+ }
+
/**
If transaction controller was rebooted with transactions that are uncompleted
in steps of the transaction signing or user confirmation process it will either
@@ -375,7 +443,7 @@ class TransactionController extends EventEmitter {
})
this.txStateManager.getFilteredTxList({
- status: 'approved',
+ status: TRANSACTION_STATUS_APPROVED,
}).forEach((txMeta) => {
const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing')
this.txStateManager.setTxStatusFailed(txMeta.id, txSignError)
@@ -386,14 +454,14 @@ class TransactionController extends EventEmitter {
is called in constructor applies the listeners for pendingTxTracker txStateManager
and blockTracker
*/
- _setupListners () {
+ _setupListeners () {
this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update'))
+ this._setupBlockTrackerListener()
this.pendingTxTracker.on('tx:warning', (txMeta) => {
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning')
})
- this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId))
- 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', (txId) => this.confirmTransaction(txId))
this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => {
if (!txMeta.firstRetryBlockNumber) {
txMeta.firstRetryBlockNumber = latestBlockNumber
@@ -405,13 +473,6 @@ class TransactionController extends EventEmitter {
txMeta.retryCount++
this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry')
})
-
- this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker))
- // this is a little messy but until ethstore has been either
- // removed or redone this is to guard against the race condition
- this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker))
- this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker))
-
}
/**
@@ -435,10 +496,45 @@ class TransactionController extends EventEmitter {
})
}
+ _setupBlockTrackerListener () {
+ let listenersAreActive = false
+ const latestBlockHandler = this._onLatestBlock.bind(this)
+ const blockTracker = this.blockTracker
+ const txStateManager = this.txStateManager
+
+ txStateManager.on('tx:status-update', updateSubscription)
+ updateSubscription()
+
+ function updateSubscription () {
+ const pendingTxs = txStateManager.getPendingTransactions()
+ if (!listenersAreActive && pendingTxs.length > 0) {
+ blockTracker.on('latest', latestBlockHandler)
+ listenersAreActive = true
+ } else if (listenersAreActive && !pendingTxs.length) {
+ blockTracker.removeListener('latest', latestBlockHandler)
+ listenersAreActive = false
+ }
+ }
+ }
+
+ async _onLatestBlock (blockNumber) {
+ try {
+ await this.pendingTxTracker.updatePendingTxs()
+ } catch (err) {
+ log.error(err)
+ }
+ try {
+ await this.pendingTxTracker.resubmitPendingTxs(blockNumber)
+ } catch (err) {
+ log.error(err)
+ }
+ }
+
/**
Updates the memStore in transaction controller
*/
_updateMemstore () {
+ this.pendingTxTracker.updatePendingTxs()
const unapprovedTxs = this.txStateManager.getUnapprovedTxList()
const selectedAddressTxList = this.txStateManager.getFilteredTxList({
from: this.getSelectedAddress(),