aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/scripts/controllers/transactions.js97
-rw-r--r--app/scripts/lib/nonce-tracker.js59
-rw-r--r--app/scripts/metamask-controller.js2
3 files changed, 116 insertions, 42 deletions
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 43735a691..45c6fb25a 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -1,12 +1,12 @@
const EventEmitter = require('events')
const async = require('async')
const extend = require('xtend')
-const Semaphore = require('semaphore')
const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util')
+const denodeify = require('denodeify')
const TxProviderUtil = require('../lib/tx-utils')
const createId = require('../lib/random-id')
-const denodeify = require('denodeify')
+const NonceTracker = require('../lib/nonce-tracker')
module.exports = class TransactionController extends EventEmitter {
constructor (opts) {
@@ -20,6 +20,17 @@ module.exports = class TransactionController extends EventEmitter {
this.txHistoryLimit = opts.txHistoryLimit
this.provider = opts.provider
this.blockTracker = opts.blockTracker
+ this.nonceTracker = new NonceTracker({
+ provider: this.provider,
+ blockTracker: this.provider._blockTracker,
+ getPendingTransactions: (address) => {
+ return this.getFilteredTxList({
+ from: address,
+ status: 'submitted',
+ err: undefined,
+ })
+ },
+ })
this.query = opts.ethQuery
this.txProviderUtils = new TxProviderUtil(this.query)
this.blockTracker.on('rawBlock', this.checkForTxInBlock.bind(this))
@@ -29,7 +40,6 @@ module.exports = class TransactionController extends EventEmitter {
this.blockTracker.once('latest', () => this.blockTracker.on('latest', this.resubmitPendingTxs.bind(this)))
this.blockTracker.on('sync', this.queryPendingTxs.bind(this))
this.signEthTx = opts.signTransaction
- this.nonceLock = Semaphore(1)
this.ethStore = opts.ethStore
// memstore is computed from a few different stores
this._updateMemstore()
@@ -173,29 +183,33 @@ module.exports = class TransactionController extends EventEmitter {
}, {})
}
- approveTransaction (txId, cb = warn) {
- const self = this
- // approve
- self.setTxStatusApproved(txId)
- // only allow one tx at a time for atomic nonce usage
- self.nonceLock.take(() => {
- // begin signature process
- async.waterfall([
- (cb) => self.fillInTxParams(txId, cb),
- (cb) => self.signTransaction(txId, cb),
- (rawTx, cb) => self.publishTransaction(txId, rawTx, cb),
- ], (err) => {
- self.nonceLock.leave()
- if (err) {
- this.setTxStatusFailed(txId, {
- errCode: err.errCode || err,
- message: err.message || 'Transaction failed during approval',
- })
- return cb(err)
- }
- cb()
+ async approveTransaction (txId, cb = warn) {
+ let nonceLock
+ try {
+ // approve
+ this.setTxStatusApproved(txId)
+ // get next nonce
+ const txMeta = this.getTx(txId)
+ const fromAddress = txMeta.txParams.from
+ nonceLock = await this.nonceTracker.getNonceLock(fromAddress)
+ txMeta.txParams.nonce = nonceLock.nextNonce
+ this.updateTx(txMeta)
+ // sign transaction
+ const rawTx = await denodeify(this.signTransaction.bind(this))(txId)
+ await this.publishTransaction(txId, rawTx)
+ // must set transaction to submitted/failed before releasing lock
+ nonceLock.releaseLock()
+ cb()
+ } catch (err) {
+ this.setTxStatusFailed(txId, {
+ errCode: err.errCode || err,
+ message: err.message || 'Transaction failed during approval',
})
- })
+ // must set transaction to submitted/failed before releasing lock
+ if (nonceLock) nonceLock.releaseLock()
+ // continue with error chain
+ cb(err)
+ }
}
cancelTransaction (txId, cb = warn) {
@@ -203,15 +217,6 @@ module.exports = class TransactionController extends EventEmitter {
cb()
}
- fillInTxParams (txId, cb) {
- const txMeta = this.getTx(txId)
- this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => {
- if (err) return cb(err)
- this.updateTx(txMeta)
- cb()
- })
- }
-
getChainId () {
const networkState = this.networkStore.getState()
const getChainId = parseInt(networkState)
@@ -237,16 +242,17 @@ module.exports = class TransactionController extends EventEmitter {
})
}
- publishTransaction (txId, rawTx, cb = warn) {
+ publishTransaction (txId, rawTx) {
const txMeta = this.getTx(txId)
txMeta.rawTx = rawTx
this.updateTx(txMeta)
-
- this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
- if (err) return cb(err)
- this.setTxHash(txId, txHash)
- this.setTxStatusSubmitted(txId)
- cb()
+ return new Promise((resolve, reject) => {
+ this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => {
+ if (err) reject(err)
+ this.setTxHash(txId, txHash)
+ this.setTxStatusSubmitted(txId)
+ resolve()
+ })
})
}
@@ -264,10 +270,19 @@ module.exports = class TransactionController extends EventEmitter {
to: '0x0..',
from: '0x0..',
status: 'signed',
+ err: undefined,
}
and returns a list of tx with all
options matching
+ ****************HINT****************
+ | `err: undefined` is like looking |
+ | for a tx with no err |
+ | so you can also search txs that |
+ | dont have something as well by |
+ | setting the value as undefined |
+ ************************************
+
this is for things like filtering a the tx list
for only tx's from 1 account
or for filltering for all txs from one account
diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js
new file mode 100644
index 000000000..ab2893b10
--- /dev/null
+++ b/app/scripts/lib/nonce-tracker.js
@@ -0,0 +1,59 @@
+const EthQuery = require('eth-query')
+
+class NonceTracker {
+
+ constructor ({ blockTracker, provider, getPendingTransactions }) {
+ this.blockTracker = blockTracker
+ this.ethQuery = new EthQuery(provider)
+ this.getPendingTransactions = getPendingTransactions
+ this.lockMap = {}
+ }
+
+ // releaseLock must be called
+ // releaseLock must be called after adding signed tx to pending transactions (or discarding)
+ async getNonceLock (address) {
+ // await lock free
+ await this.lockMap[address]
+ // take lock
+ const releaseLock = this._takeLock(address)
+ // 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 pendingTransactions = this.getPendingTransactions(address)
+ const baseCount = await this._getTxCount(address, currentBlock)
+ const nextNonce = parseInt(baseCount) + pendingTransactions.length
+ // return next nonce and release cb
+ return { nextNonce: nextNonce.toString(16), releaseLock }
+ }
+
+ async _getCurrentBlock () {
+ const currentBlock = this.blockTracker.getCurrentBlock()
+ if (currentBlock) return currentBlock
+ return await Promise((reject, resolve) => {
+ this.blockTracker.once('latest', resolve)
+ })
+ }
+
+ _takeLock (lockId) {
+ let releaseLock = null
+ // create and store lock
+ const lock = new Promise((resolve, reject) => { releaseLock = resolve })
+ this.lockMap[lockId] = lock
+ // setup lock teardown
+ lock.then(() => delete this.lockMap[lockId])
+ return releaseLock
+ }
+
+ async _getTxCount (address, currentBlock) {
+ const blockNumber = currentBlock.number
+ return new Promise((resolve, reject) => {
+ this.ethQuery.getTransactionCount(address, blockNumber, (err, result) => {
+ err ? reject(err) : resolve(result)
+ })
+ })
+ }
+
+}
+
+module.exports = NonceTracker
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index 73093dfad..573594b39 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -312,7 +312,7 @@ module.exports = class MetamaskController extends EventEmitter {
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
// txController
- approveTransaction: txController.approveTransaction.bind(txController),
+ approveTransaction: nodeify(txController.approveTransaction).bind(txController),
cancelTransaction: txController.cancelTransaction.bind(txController),
updateAndApproveTransaction: this.updateAndApproveTx.bind(this),