diff options
author | kumavis <kumavis@users.noreply.github.com> | 2017-09-28 04:58:54 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-09-28 04:58:54 +0800 |
commit | 4404dfc5d328d57c99190dbcf034102882487177 (patch) | |
tree | 5bae6617ea10d2920983b1bb27eabf01f7da0de4 /app/scripts/lib | |
parent | b41aad6d1ae894ab89380b1c7159da8545ad935b (diff) | |
parent | 5bbea78306f13415f85159f3d23c2b69d8f2a26c (diff) | |
download | tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.tar tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.tar.gz tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.tar.bz2 tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.tar.lz tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.tar.xz tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.tar.zst tangerine-wallet-browser-4404dfc5d328d57c99190dbcf034102882487177.zip |
Merge branch 'master' into direct-block-tracker
Diffstat (limited to 'app/scripts/lib')
-rw-r--r-- | app/scripts/lib/account-tracker.js | 5 | ||||
-rw-r--r-- | app/scripts/lib/pending-tx-tracker.js | 26 | ||||
-rw-r--r-- | app/scripts/lib/tx-gas-utils.js (renamed from app/scripts/lib/tx-utils.js) | 22 | ||||
-rw-r--r-- | app/scripts/lib/tx-state-manager.js | 245 |
4 files changed, 261 insertions, 37 deletions
diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index e550c2758..a108d0d4f 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -11,6 +11,7 @@ const async = require('async') const EthQuery = require('eth-query') const ObservableStore = require('obs-store') const EventEmitter = require('events').EventEmitter +const ethUtil = require('ethereumjs-util') function noop () {} @@ -58,7 +59,9 @@ class AccountTracker extends EventEmitter { _updateForBlock (block) { this._currentBlockNumber = block.number - this.store.updateState({ currentBlockGasLimit: block.gasLimit }) + const currentBlockGasLimit = block.gasLimit + + this.store.updateState({ currentBlockGasLimit }) async.parallel([ this._updateAccounts.bind(this), diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 8da1253a2..b97cec9ce 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -1,7 +1,6 @@ const EventEmitter = require('events') const EthQuery = require('ethjs-query') const sufficientBalance = require('./util').sufficientBalance -const RETRY_LIMIT = 3500 // Retry 3500 blocks, or about 1 day. /* Utility class for tracking the transactions as they @@ -25,11 +24,10 @@ module.exports = class PendingTransactionTracker extends EventEmitter { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker - + this.retryLimit = config.retryLimit || Infinity this.getBalance = config.getBalance this.getPendingTransactions = config.getPendingTransactions this.publishTransaction = config.publishTransaction - this.giveUpOnTransaction = config.giveUpOnTransaction } // checks if a signed tx is in a block and @@ -44,13 +42,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter { if (!txHash) { const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') noTxHashErr.name = 'NoTxHashError' - this.emit('txFailed', txId, noTxHashErr) + this.emit('tx:failed', txId, noTxHashErr) return } block.transactions.forEach((tx) => { - if (tx.hash === txHash) this.emit('txConfirmed', txId) + if (tx.hash === txHash) this.emit('tx:confirmed', txId) }) }) } @@ -96,7 +94,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { // ignore resubmit warnings, return early if (isKnownTx) return // encountered real error - transition to error state - this.emit('txFailed', txMeta.id, err) + this.emit('tx:failed', txMeta.id, err) })) } @@ -104,16 +102,16 @@ module.exports = class PendingTransactionTracker extends EventEmitter { const address = txMeta.txParams.from const balance = this.getBalance(address) if (balance === undefined) return - if (!('retryCount' in txMeta)) txMeta.retryCount = 0 - if (txMeta.retryCount > RETRY_LIMIT) { - return this.giveUpOnTransaction(txMeta.id) + if (txMeta.retryCount > this.retryLimit) { + const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`) + return this.emit('tx:failed', txMeta.id, err) } // if the value of the transaction is greater then the balance, fail. if (!sufficientBalance(txMeta.txParams, balance)) { const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') - this.emit('txFailed', txMeta.id, insufficientFundsError) + this.emit('tx:failed', txMeta.id, insufficientFundsError) log.error(insufficientFundsError) return } @@ -125,7 +123,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { const txHash = await this.publishTransaction(rawTx) // Increment successful tries: - txMeta.retryCount++ + this.emit('tx:retry', txMeta) return txHash } @@ -137,7 +135,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { if (!txHash) { const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') noTxHashErr.name = 'NoTxHashError' - this.emit('txFailed', txId, noTxHashErr) + this.emit('tx:failed', txId, noTxHashErr) return } // get latest transaction status @@ -146,14 +144,14 @@ module.exports = class PendingTransactionTracker extends EventEmitter { txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return if (txParams.blockNumber) { - this.emit('txConfirmed', txId) + this.emit('tx:confirmed', txId) } } catch (err) { txMeta.warning = { error: err, message: 'There was a problem loading this transaction.', } - this.emit('txWarning', txMeta) + this.emit('tx:warning', txMeta) throw err } } diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-gas-utils.js index 5af078dc4..41f67e230 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-gas-utils.js @@ -1,6 +1,4 @@ const EthQuery = require('ethjs-query') -const Transaction = require('ethereumjs-tx') -const normalize = require('eth-sig-util').normalize const { hexToBn, BnMultiplyByFraction, @@ -78,26 +76,6 @@ module.exports = class txProvideUtil { return bnToHex(upperGasLimitBn) } - // builds ethTx from txParams object - buildEthTxFromParams (txParams) { - // normalize values - txParams.to = normalize(txParams.to) - txParams.from = normalize(txParams.from) - txParams.value = normalize(txParams.value) - txParams.data = normalize(txParams.data) - txParams.gas = normalize(txParams.gas || txParams.gasLimit) - txParams.gasPrice = normalize(txParams.gasPrice) - txParams.nonce = normalize(txParams.nonce) - // build ethTx - log.info(`Prepared tx for signing: ${JSON.stringify(txParams)}`) - const ethTx = new Transaction(txParams) - return ethTx - } - - async publishTransaction (rawTx) { - return await this.query.sendRawTransaction(rawTx) - } - async validateTxParams (txParams) { if (('value' in txParams) && txParams.value.indexOf('-') === 0) { throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`) diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js new file mode 100644 index 000000000..abb9d7910 --- /dev/null +++ b/app/scripts/lib/tx-state-manager.js @@ -0,0 +1,245 @@ +const extend = require('xtend') +const EventEmitter = require('events') +const ObservableStore = require('obs-store') +const ethUtil = require('ethereumjs-util') +const txStateHistoryHelper = require('./tx-state-history-helper') + +module.exports = class TransactionStateManger extends EventEmitter { + constructor ({ initState, txHistoryLimit, getNetwork }) { + super() + + this.store = new ObservableStore( + extend({ + transactions: [], + }, initState)) + this.txHistoryLimit = txHistoryLimit + this.getNetwork = getNetwork + } + + // Returns the number of txs for the current network. + getTxCount () { + return this.getTxList().length + } + + getTxList () { + const network = this.getNetwork() + const fullTxList = this.getFullTxList() + return fullTxList.filter((txMeta) => txMeta.metamaskNetworkId === network) + } + + getFullTxList () { + return this.store.getState().transactions + } + + // Returns the tx list + getUnapprovedTxList () { + const txList = this.getTxsByMetaData('status', 'unapproved') + return txList.reduce((result, tx) => { + result[tx.id] = tx + return result + }, {}) + } + + getPendingTransactions (address) { + const opts = { status: 'submitted' } + if (address) opts.from = address + return this.getFilteredTxList(opts) + } + + addTx (txMeta) { + this.once(`${txMeta.id}:signed`, function (txId) { + this.removeAllListeners(`${txMeta.id}:rejected`) + }) + this.once(`${txMeta.id}:rejected`, function (txId) { + this.removeAllListeners(`${txMeta.id}:signed`) + }) + // initialize history + txMeta.history = [] + // capture initial snapshot of txMeta for history + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history.push(snapshot) + + const transactions = this.getFullTxList() + const txCount = this.getTxCount() + const txHistoryLimit = this.txHistoryLimit + + // checks if the length of the tx history is + // longer then desired persistence limit + // and then if it is removes only confirmed + // or rejected tx's. + // not tx's that are pending or unapproved + if (txCount > txHistoryLimit - 1) { + const index = transactions.findIndex((metaTx) => metaTx.status === 'confirmed' || metaTx.status === 'rejected') + transactions.splice(index, 1) + } + transactions.push(txMeta) + this._saveTxList(transactions) + return txMeta + } + // gets tx by Id and returns it + getTx (txId) { + const txMeta = this.getTxsByMetaData('id', txId)[0] + return txMeta + } + + updateTx (txMeta) { + if (txMeta.txParams) { + Object.keys(txMeta.txParams).forEach((key) => { + let value = txMeta.txParams[key] + if (typeof value !== 'string') console.error(`${key}: ${value} in txParams is not a string`) + if (!ethUtil.isHexPrefixed(value)) console.error('is not hex prefixed, anything on txParams must be hex prefixed') + }) + } + + // create txMeta snapshot for history + const currentState = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + // recover previous tx state obj + const previousState = txStateHistoryHelper.replayHistory(txMeta.history) + // generate history entry and add to history + const entry = txStateHistoryHelper.generateHistoryEntry(previousState, currentState) + txMeta.history.push(entry) + + // commit txMeta to state + const txId = txMeta.id + const txList = this.getFullTxList() + const index = txList.findIndex(txData => txData.id === txId) + txList[index] = txMeta + this._saveTxList(txList) + } + + + // merges txParams obj onto txData.txParams + // use extend to ensure that all fields are filled + updateTxParams (txId, txParams) { + const txMeta = this.getTx(txId) + txMeta.txParams = extend(txMeta.txParams, txParams) + this.updateTx(txMeta) + } + +/* + Takes an object of fields to search for eg: + let thingsToLookFor = { + 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 + and that have been 'confirmed' + */ + getFilteredTxList (opts, initialList) { + let filteredTxList = initialList + Object.keys(opts).forEach((key) => { + filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) + }) + return filteredTxList + } + + getTxsByMetaData (key, value, txList = this.getTxList()) { + return txList.filter((txMeta) => { + if (txMeta.txParams[key]) { + return txMeta.txParams[key] === value + } else { + return txMeta[key] === value + } + }) + } + + // STATUS METHODS + // statuses: + // - `'unapproved'` the user has not responded + // - `'rejected'` the user has responded no! + // - `'approved'` the user has approved the tx + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + // - `'failed'` the tx failed for some reason, included on tx data. + + // get::set status + + // should return the status of the tx. + getTxStatus (txId) { + const txMeta = this.getTx(txId) + return txMeta.status + } + + // should update the status of the tx to 'rejected'. + setTxStatusRejected (txId) { + this._setTxStatus(txId, 'rejected') + } + + // should update the status of the tx to 'approved'. + setTxStatusApproved (txId) { + this._setTxStatus(txId, 'approved') + } + + // should update the status of the tx to 'signed'. + setTxStatusSigned (txId) { + this._setTxStatus(txId, 'signed') + } + + // should update the status of the tx to 'submitted'. + setTxStatusSubmitted (txId) { + this._setTxStatus(txId, 'submitted') + } + + // should update the status of the tx to 'confirmed'. + setTxStatusConfirmed (txId) { + this._setTxStatus(txId, 'confirmed') + } + + setTxStatusFailed (txId, err) { + const txMeta = this.getTx(txId) + txMeta.err = { + message: err.toString(), + stack: err.stack, + } + this.updateTx(txMeta) + this._setTxStatus(txId, 'failed') + } + +// +// PRIVATE METHODS +// + + // 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! + // - `'approved'` the user has approved the tx + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + // - `'failed'` the tx failed for some reason, included on tx data. + _setTxStatus (txId, status) { + const txMeta = this.getTx(txId) + txMeta.status = status + this.emit(`${txMeta.id}:${status}`, txId) + this.emit(`tx:status-update`, txId, status) + if (status === 'submitted' || status === 'rejected') { + this.emit(`${txMeta.id}:finished`, txMeta) + } + this.updateTx(txMeta) + this.emit('update:badge') + } + + // Saves the new/updated txList. + // Function is intended only for internal use + _saveTxList (transactions) { + this.store.updateState({ transactions }) + } +}
\ No newline at end of file |