aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/controllers/transactions
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts/controllers/transactions')
-rw-r--r--app/scripts/controllers/transactions/index.js67
-rw-r--r--app/scripts/controllers/transactions/nonce-tracker.js28
-rw-r--r--app/scripts/controllers/transactions/pending-tx-tracker.js80
-rw-r--r--app/scripts/controllers/transactions/tx-gas-utils.js2
4 files changed, 81 insertions, 96 deletions
diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js
index 8e2288aed..5d7d6d6da 100644
--- a/app/scripts/controllers/transactions/index.js
+++ b/app/scripts/controllers/transactions/index.js
@@ -65,6 +65,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 +79,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()
@@ -311,6 +316,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 +364,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
@@ -386,14 +404,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 +423,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,6 +446,40 @@ 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
*/
diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js
index 06f336eaa..421036368 100644
--- a/app/scripts/controllers/transactions/nonce-tracker.js
+++ b/app/scripts/controllers/transactions/nonce-tracker.js
@@ -12,8 +12,9 @@ const Mutex = require('await-semaphore').Mutex
*/
class NonceTracker {
- constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
+ constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
this.provider = provider
+ this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.getPendingTransactions = getPendingTransactions
this.getConfirmedTransactions = getConfirmedTransactions
@@ -34,7 +35,7 @@ class NonceTracker {
* @typedef NonceDetails
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
* @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
- * @property {number} highetSuggested - The maximum between the other two, the number returned.
+ * @property {number} highestSuggested - The maximum between the other two, the number returned.
*/
/**
@@ -80,15 +81,6 @@ class NonceTracker {
}
}
- async _getCurrentBlock () {
- const blockTracker = this._getBlockTracker()
- const currentBlock = blockTracker.getCurrentBlock()
- if (currentBlock) return currentBlock
- return await new Promise((reject, resolve) => {
- blockTracker.once('latest', resolve)
- })
- }
-
async _globalMutexFree () {
const globalMutex = this._lookupMutex('global')
const releaseLock = await globalMutex.acquire()
@@ -114,9 +106,8 @@ class NonceTracker {
// calculate next nonce
// we need to make sure our base count
// and pending count are from the same block
- const currentBlock = await this._getCurrentBlock()
- const blockNumber = currentBlock.blockNumber
- const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber || 'latest')
+ const blockNumber = await this.blockTracker.getLatestBlock()
+ const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber)
const baseCount = baseCountBN.toNumber()
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
const nonceDetails = { blockNumber, baseCount }
@@ -165,15 +156,6 @@ class NonceTracker {
return { name: 'local', nonce: highest, details: { startPoint, highest } }
}
- // this is a hotfix for the fact that the blockTracker will
- // change when the network changes
-
- /**
- @returns {Object} the current blockTracker
- */
- _getBlockTracker () {
- return this.provider._blockTracker
- }
}
module.exports = NonceTracker
diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js
index 4e41cdaf8..70cac096b 100644
--- a/app/scripts/controllers/transactions/pending-tx-tracker.js
+++ b/app/scripts/controllers/transactions/pending-tx-tracker.js
@@ -1,6 +1,7 @@
const EventEmitter = require('events')
const log = require('loglevel')
const EthQuery = require('ethjs-query')
+
/**
Event emitter utility class for tracking the transactions as they<br>
@@ -23,55 +24,26 @@ class PendingTransactionTracker extends EventEmitter {
super()
this.query = new EthQuery(config.provider)
this.nonceTracker = config.nonceTracker
- // default is one day
this.getPendingTransactions = config.getPendingTransactions
this.getCompletedTransactions = config.getCompletedTransactions
this.publishTransaction = config.publishTransaction
- this._checkPendingTxs()
+ this.confirmTransaction = config.confirmTransaction
}
/**
- checks if a signed tx is in a block and
- if it is included emits tx status as 'confirmed'
- @param block {object}, a full block
- @emits tx:confirmed
- @emits tx:failed
- */
- checkForTxInBlock (block) {
- const signedTxList = this.getPendingTransactions()
- if (!signedTxList.length) return
- signedTxList.forEach((txMeta) => {
- const txHash = txMeta.hash
- const txId = txMeta.id
-
- if (!txHash) {
- const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.')
- noTxHashErr.name = 'NoTxHashError'
- this.emit('tx:failed', txId, noTxHashErr)
- return
- }
-
-
- block.transactions.forEach((tx) => {
- if (tx.hash === txHash) this.emit('tx:confirmed', txId)
- })
- })
- }
-
- /**
- asks the network for the transaction to see if a block number is included on it
- if we have skipped/missed blocks
- @param object - oldBlock newBlock
+ checks the network for signed txs and releases the nonce global lock if it is
*/
- queryPendingTxs ({ oldBlock, newBlock }) {
- // check pending transactions on start
- if (!oldBlock) {
- this._checkPendingTxs()
- return
+ async updatePendingTxs () {
+ // in order to keep the nonceTracker accurate we block it while updating pending transactions
+ const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
+ try {
+ const pendingTxs = this.getPendingTransactions()
+ await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta)))
+ } catch (err) {
+ log.error('PendingTransactionTracker - Error updating pending transactions')
+ log.error(err)
}
- // if we synced by more than one block, check for missed pending transactions
- const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16)
- if (diff > 1) this._checkPendingTxs()
+ nonceGlobalLock.releaseLock()
}
/**
@@ -79,11 +51,11 @@ class PendingTransactionTracker extends EventEmitter {
@param block {object} - a block object
@emits tx:warning
*/
- resubmitPendingTxs (block) {
+ 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, block.number).catch((err) => {
+ 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
@@ -145,6 +117,7 @@ class PendingTransactionTracker extends EventEmitter {
this.emit('tx:retry', txMeta)
return txHash
}
+
/**
Ask the network for the transaction to see if it has been include in a block
@param txMeta {Object} - the txMeta object
@@ -174,9 +147,8 @@ class PendingTransactionTracker extends EventEmitter {
}
// get latest transaction status
- let txParams
try {
- txParams = await this.query.getTransactionByHash(txHash)
+ const txParams = await this.query.getTransactionByHash(txHash)
if (!txParams) return
if (txParams.blockNumber) {
this.emit('tx:confirmed', txId)
@@ -191,26 +163,12 @@ class PendingTransactionTracker extends EventEmitter {
}
/**
- checks the network for signed txs and releases the nonce global lock if it is
- */
- async _checkPendingTxs () {
- const signedTxList = this.getPendingTransactions()
- // in order to keep the nonceTracker accurate we block it while updating pending transactions
- const { releaseLock } = await this.nonceTracker.getGlobalLock()
- try {
- await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
- } catch (err) {
- log.error('PendingTransactionWatcher - Error updating pending transactions')
- log.error(err)
- }
- releaseLock()
- }
-
- /**
checks to see if a confirmed txMeta has the same nonce
@param txMeta {Object} - txMeta object
@returns {boolean}
*/
+
+
async _checkIfNonceIsTaken (txMeta) {
const address = txMeta.txParams.from
const completed = this.getCompletedTransactions(address)
diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js
index 5cd0f5407..3dd45507f 100644
--- a/app/scripts/controllers/transactions/tx-gas-utils.js
+++ b/app/scripts/controllers/transactions/tx-gas-utils.js
@@ -25,7 +25,7 @@ class TxGasUtil {
@returns {object} the txMeta object with the gas written to the txParams
*/
async analyzeGasUsage (txMeta) {
- const block = await this.query.getBlockByNumber('latest', true)
+ const block = await this.query.getBlockByNumber('latest', false)
let estimatedGasHex
try {
estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit)