transaction controller - use nonce-tracker

feature/default_network_editable
kumavis 8 years ago
parent dab2fccc78
commit b3492d9c17
  1. 90
      app/scripts/controllers/transactions.js
  2. 2
      app/scripts/metamask-controller.js
  3. 7
      test/unit/tx-controller-test.js

@ -4,9 +4,10 @@ const extend = require('xtend')
const Semaphore = require('semaphore') const Semaphore = require('semaphore')
const ObservableStore = require('obs-store') const ObservableStore = require('obs-store')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const denodeify = require('denodeify')
const TxProviderUtil = require('../lib/tx-utils') const TxProviderUtil = require('../lib/tx-utils')
const createId = require('../lib/random-id') const createId = require('../lib/random-id')
const denodeify = require('denodeify') const NonceTracker = require('../lib/nonce-tracker')
const RETRY_LIMIT = 200 const RETRY_LIMIT = 200
@ -22,6 +23,11 @@ module.exports = class TransactionController extends EventEmitter {
this.txHistoryLimit = opts.txHistoryLimit this.txHistoryLimit = opts.txHistoryLimit
this.provider = opts.provider this.provider = opts.provider
this.blockTracker = opts.blockTracker this.blockTracker = opts.blockTracker
this.nonceTracker = new NonceTracker({
provider: this.provider,
blockTracker: this.blockTracker,
getPendingTransactions: (address) => this.getFilteredTxList({ from: address, status: 'submitted' }),
})
this.query = opts.ethQuery this.query = opts.ethQuery
this.txProviderUtils = new TxProviderUtil(this.query) this.txProviderUtils = new TxProviderUtil(this.query)
this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) this.blockTracker.on('block', this.checkForTxInBlock.bind(this))
@ -169,29 +175,58 @@ module.exports = class TransactionController extends EventEmitter {
}, {}) }, {})
} }
approveTransaction (txId, cb = warn) { // approveTransaction (txId, cb = warn) {
const self = this // promiseToCallback((async () => {
// approve // // approve
self.setTxStatusApproved(txId) // self.setTxStatusApproved(txId)
// only allow one tx at a time for atomic nonce usage // // get next nonce
self.nonceLock.take(() => { // const txMeta = this.getTx(txId)
// begin signature process // const fromAddress = txMeta.txParams.from
async.waterfall([ // const { nextNonce, releaseLock } = await this.nonceTracker.getNonceLock(fromAddress)
(cb) => self.fillInTxParams(txId, cb), // txMeta.txParams.nonce = nonce
(cb) => self.signTransaction(txId, cb), // this.updateTx(txMeta)
(rawTx, cb) => self.publishTransaction(txId, rawTx, cb), // // sign transaction
], (err) => { // const rawTx = await denodeify(self.signTransaction.bind(self))(txId)
self.nonceLock.leave() // await denodeify(self.publishTransaction.bind(self))(txId, rawTx)
if (err) { // })())((err) => {
this.setTxStatusFailed(txId, { // if (err) {
errCode: err.errCode || err, // this.setTxStatusFailed(txId, {
message: err.message || 'Transaction failed during approval', // errCode: err.errCode || err,
}) // message: err.message || 'Transaction failed during approval',
return cb(err) // })
} // }
cb() // // must set transaction to submitted/failed before releasing lock
// releaseLock()
// cb(err)
// })
// }
async approveTransaction (txId) {
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 denodeify(this.publishTransaction.bind(this))(txId, rawTx)
// must set transaction to submitted/failed before releasing lock
nonceLock.releaseLock()
} 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
throw err
}
} }
cancelTransaction (txId, cb = warn) { cancelTransaction (txId, cb = warn) {
@ -199,15 +234,6 @@ module.exports = class TransactionController extends EventEmitter {
cb() 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 () { getChainId () {
const networkState = this.networkStore.getState() const networkState = this.networkStore.getState()
const getChainId = parseInt(networkState) const getChainId = parseInt(networkState)

@ -290,7 +290,7 @@ module.exports = class MetamaskController extends EventEmitter {
exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), exportAccount: nodeify(keyringController.exportAccount).bind(keyringController),
// txController // txController
approveTransaction: txController.approveTransaction.bind(txController), approveTransaction: nodeify(txController.approveTransaction).bind(txController),
cancelTransaction: txController.cancelTransaction.bind(txController), cancelTransaction: txController.cancelTransaction.bind(txController),
updateAndApproveTransaction: this.updateAndApproveTx.bind(this), updateAndApproveTransaction: this.updateAndApproveTx.bind(this),

@ -1,5 +1,4 @@
const assert = require('assert') const assert = require('assert')
const EventEmitter = require('events')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
const EthTx = require('ethereumjs-tx') const EthTx = require('ethereumjs-tx')
const EthQuery = require('eth-query') const EthQuery = require('eth-query')
@ -19,13 +18,15 @@ describe('Transaction Controller', function () {
txController = new TransactionController({ txController = new TransactionController({
networkStore: new ObservableStore(currentNetworkId), networkStore: new ObservableStore(currentNetworkId),
txHistoryLimit: 10, txHistoryLimit: 10,
blockTracker: new EventEmitter(), blockTracker: { getCurrentBlock: noop, on: noop },
ethQuery: new EthQuery(new EventEmitter()), provider: { sendAsync: noop },
ethQuery: new EthQuery({ sendAsync: noop }),
signTransaction: (ethTx) => new Promise((resolve) => { signTransaction: (ethTx) => new Promise((resolve) => {
ethTx.sign(privKey) ethTx.sign(privKey)
resolve() resolve()
}), }),
}) })
txController.nonceTracker.getNonceLock = () => Promise.resolve({ nextNonce: 0, releaseLock: noop })
}) })
describe('#validateTxParams', function () { describe('#validateTxParams', function () {

Loading…
Cancel
Save