From 6e78494846c9032fbf1264a0225c0df4df0867cb Mon Sep 17 00:00:00 2001 From: Frances Pangilinan Date: Fri, 16 Dec 2016 10:33:36 -0800 Subject: First pass at revision requests --- app/scripts/transaction-manager.js | 247 +++++++++++++++++++++++-------------- 1 file changed, 151 insertions(+), 96 deletions(-) (limited to 'app/scripts/transaction-manager.js') diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index d0226a600..6b3d1806f 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -1,15 +1,31 @@ const EventEmitter = require('events') const extend = require('xtend') -const TxProviderUtil = require('./lib/provider-utils') +const ethUtil = require('ethereumjs-util') +const TxProviderUtil = require('./lib/tx-utils') +const createId = require('./lib/random-id') module.exports = class TransactionManager extends EventEmitter { constructor (opts) { super() - this.txList = opts.TxListFromStore || [] - this._persistTxList = opts.setTxList + this.txList = opts.txList || [] + this._setTxList = opts.setTxList this._unconfTxCbs = {} - this.txLimit = opts.txLimit + this.txHistoryLimit = opts.txHistoryLimit + // txManager :: tx approvals and rejection cb's + this.provider = opts.provider + this.blockTracker = opts.blockTracker + this.txProviderUtils = new TxProviderUtil(this.provider) + this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + this.getGasMultiplier = opts.getGasMultiplier + this.getNetwork = opts.getNetwork + } + + getState () { + return { + transactions: this.getTxList(), + unconfTxs: this.getUnapprovedTxList(), + } } // Returns the tx list @@ -17,49 +33,49 @@ module.exports = class TransactionManager extends EventEmitter { return this.txList } - // Saves the new/updated txList. - // Function is intended only for internal use - _saveTxList (txList) { - this.txList = txList - this._persistTxList(txList) - } - // Adds a tx to the txlist - addTx (txData, onTxDoneCb) { + addTx (txMeta, onTxDoneCb = noop) { var txList = this.getTxList() - var txLimit = this.txLimit - if (txList.length > txLimit - 1) { - txList.shift() + var txHistoryLimit = this.txHistoryLimit + if (txList.length > txHistoryLimit - 1) { + var index = txList.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') + index ? txList.splice(index, index) : txList.shift() } - txList.push(txData) + txList.push(txMeta) this._saveTxList(txList) - this.addOnTxDoneCb(txData.id, onTxDoneCb) - this.emit('unapproved', txData) + // keep the onTxDoneCb around in a listener + // for after approval/denial (requires user interaction) + // This onTxDoneCb fires completion to the Dapp's write operation. + this.once(`${txMeta.id}:signed`, function (txId) { + this.removeAllListeners(`${txMeta.id}:rejected`) + onTxDoneCb(null, true) + }) + this.once(`${txMeta.id}:rejected`, function (txId) { + this.removeAllListeners(`${txMeta.id}:signed`) + onTxDoneCb(null, false) + }) + this.emit('update') + this.emit(`${txMeta.id}:unapproved`, txMeta) } // gets tx by Id and returns it getTx (txId, cb) { var txList = this.getTxList() - var tx = txList.find((tx) => tx.id === txId) - return cb ? cb(tx) : tx + var txMeta = txList.find((txData) => txData.id === txId) + return cb ? cb(txMeta) : txMeta } // - updateTx (txData) { - var txId = txData.id + updateTx (txMeta) { + var txId = txMeta.id var txList = this.getTxList() - - var updatedTxList = txList.map((tx) => { - if (tx.id === txId) { - tx = txData - } - return tx - }) - this._saveTxList(updatedTxList) + var index = txList.findIndex((txData) => txData.id === txId) + txList[index] = txMeta + this._saveTxList(txList) } - get unConftxCount () { + get unconfTxCount () { return Object.keys(this.getUnapprovedTxList()).length } @@ -67,16 +83,66 @@ module.exports = class TransactionManager extends EventEmitter { return this.getTxsByMetaData('status', 'signed').length } + addUnapprovedTransaction (txParams, onTxDoneCb, cb) { + // create txData obj with parameters and meta data + var time = (new Date()).getTime() + var txId = createId() + txParams.metamaskId = txId + txParams.metamaskNetworkId = this.getNetwork() + var txData = { + id: txId, + txParams: txParams, + time: time, + status: 'unapproved', + gasMultiplier: this.getGasMultiplier() || 1, + metamaskNetworkId: this.getNetwork(), + } + this.txProviderUtils.analyzeGasUsage(txData, this.txDidComplete.bind(this, txData, onTxDoneCb, cb)) + // calculate metadata for tx + } + + txDidComplete (txMeta, onTxDoneCb, cb, err) { + if (err) return cb(err) + this.addTx(txMeta, onTxDoneCb) + cb(null, txMeta) + } + getUnapprovedTxList () { var txList = this.getTxList() - return txList.filter((tx) => { - return tx.status === 'unapproved' - }).reduce((result, tx) => { + return txList.filter((txMeta) => txMeta.status === 'unapproved') + .reduce((result, tx) => { result[tx.id] = tx return result }, {}) } + approveTransaction (txId, cb) { + this.setTxStatusSigned(txId) + cb() + } + + cancelTransaction (txId, cb) { + this.setTxStatusRejected(txId) + if (cb && typeof cb === 'function') { + cb() + } + } + + resolveSignedTransaction (txPromise) { + const self = this + + txPromise.then(({tx, txParams, cb}) => { + // Add the tx hash to the persisted meta-tx object + var txHash = ethUtil.bufferToHex(tx.hash()) + + var metaTx = self.getTx(txParams.metamaskId) + metaTx.hash = txHash + // return raw serialized tx + var rawTx = ethUtil.bufferToHex(tx.serialize()) + cb(null, rawTx) + }) + } + /* Takes an object of fields to search for eg: var thingsToLookFor = { @@ -92,7 +158,7 @@ module.exports = class TransactionManager extends EventEmitter { or for filltering for all txs from one account and that have been 'confirmed' */ - getFilterdTxList (opts) { + getFilteredTxList (opts) { var filteredTxList Object.keys(opts).forEach((key) => { filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) @@ -101,107 +167,96 @@ module.exports = class TransactionManager extends EventEmitter { } getTxsByMetaData (key, value, txList = this.getTxList()) { - return txList.filter((tx) => { - if (key in tx.txParams) { - return tx.txParams[key] === value + return txList.filter((txMeta) => { + if (key in txMeta.txParams) { + return txMeta.txParams[key] === value } else { - return tx[key] === value + return txMeta[key] === value } }) } -// keeps around the txCbs for later - addOnTxDoneCb (txId, cb) { - this._unconfTxCbs[txId] = cb || noop - } - - execOnTxDoneCb (txId, conf) { - var approvalCb = this._unconfTxCbs[txId] - - approvalCb(null, conf) - // clean up - delete this._unconfTxCbs[txId] - } - - // should return the tx - - // Should find the tx in the tx list and - // update it. - // should set the status in txData - // // - `'unapproved'` the user has not responded - // // - `'rejected'` the user has responded no! - // // - `'signed'` the tx is signed - // // - `'submitted'` the tx is sent to a server - // // - `'confirmed'` the tx has been included in a block. - setTxStatus (txId, status) { - var txData = this.getTx(txId) - txData.status = status - this.emit(status, txId) - this.updateTx(txData, status) - } - - // should return the status of the tx. getTxStatus (txId, cb) { - const txData = this.getTx(txId) - return cb ? cb(txData.staus) : txData.status + const txMeta = this.getTx(txId) + return cb ? cb(txMeta.staus) : txMeta.status } // should update the status of the tx to 'signed'. setTxStatusSigned (txId) { - this.setTxStatus(txId, 'signed') + this._setTxStatus(txId, 'signed') this.emit('update') } // should update the status of the tx to 'rejected'. setTxStatusRejected (txId) { - this.setTxStatus(txId, 'rejected') + this._setTxStatus(txId, 'rejected') this.emit('update') } setTxStatusConfirmed (txId) { - this.setTxStatus(txId, 'confirmed') + this._setTxStatus(txId, 'confirmed') } // merges txParams obj onto txData.txParams // use extend to ensure that all fields are filled updateTxParams (txId, txParams) { - var txData = this.getTx(txId) - txData.txParams = extend(txData, txParams) - this.updateTx(txData) - } - - // sets provider for provider utils and event listener - setProvider (provider) { - this.provider = provider - this.txProviderUtils = new TxProviderUtil(provider) - this.provider.on('block', this.checkForTxInBlock.bind(this)) + var txMeta = this.getTx(txId) + txMeta.txParams = extend(txMeta, txParams) + this.updateTx(txMeta) } // checks if a signed tx is in a block and // if included sets the tx status as 'confirmed' checkForTxInBlock () { - var signedTxList = this.getFilterdTxList({status: 'signed'}) + var signedTxList = this.getFilteredTxList({status: 'signed', err: undefined}) if (!signedTxList.length) return - var self = this signedTxList.forEach((tx) => { var txHash = tx.hash var txId = tx.id if (!txHash) return - // var d - this.txProviderUtils.query.getTransactionByHash(txHash, (err, txData) => { - if (err) { - tx - - return console.error(err) + this.txProviderUtils.query.getTransactionByHash(txHash, (err, txMeta) => { + if (err || !txMeta) { + tx.err = err || 'Tx could possibly have not submitted' + this.updateTx(tx) + return txMeta ? console.error(err) : console.debug(`txMeta is ${txMeta} for:`, tx) } - if (txData.blockNumber !== null) { - self.setTxStatusConfirmed(txId) + if (txMeta.blockNumber) { + this.setTxStatusConfirmed(txId) } }) }) } + + // Private functions + + // Saves the new/updated txList. + // Function is intended only for internal use + _saveTxList (txList) { + this.txList = txList + this._setTxList(txList) + } + + // should return the tx + + // Should find the tx in the tx list and + // update it. + // should set the status in txData + // - `'unapproved'` the user has not responded + // - `'rejected'` the user has responded no! + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + _setTxStatus (txId, status) { + var txMeta = this.getTx(txId) + txMeta.status = status + this.emit(`${txMeta.id}:${status}`, txId) + this.updateTx(txMeta) + } + + } -function noop () {} + +const noop = () => console.warn('noop was used no cb provided') -- cgit