aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib
diff options
context:
space:
mode:
authorkumavis <kumavis@users.noreply.github.com>2017-08-09 14:30:58 +0800
committerGitHub <noreply@github.com>2017-08-09 14:30:58 +0800
commit0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e (patch)
tree428154973b91dfb2f8519777e67da7a6c75530b3 /app/scripts/lib
parent5e9926b0d035a5ba946080e94777ac0bd887d396 (diff)
parent57f6fce6b2524c4b36b591da5e600d0652f4077e (diff)
downloadtangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.gz
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.tar.zst
tangerine-wallet-browser-0188e7b94d85b45a783f9e3d5c182a8ffcaeac2e.zip
Merge branch 'master' into NewUI-flat
Diffstat (limited to 'app/scripts/lib')
-rw-r--r--app/scripts/lib/pending-tx-tracker.js163
-rw-r--r--app/scripts/lib/tx-utils.js43
-rw-r--r--app/scripts/lib/util.js36
3 files changed, 209 insertions, 33 deletions
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
new file mode 100644
index 000000000..19720db3f
--- /dev/null
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -0,0 +1,163 @@
+const EventEmitter = require('events')
+const EthQuery = require('ethjs-query')
+const sufficientBalance = require('./util').sufficientBalance
+/*
+
+ Utility class for tracking the transactions as they
+ go from a pending state to a confirmed (mined in a block) state
+
+ As well as continues broadcast while in the pending state
+
+ ~config is not optional~
+ requires a: {
+ provider: //,
+ nonceTracker: //see nonce tracker,
+ getBalnce: //(address) a function for getting balances,
+ getPendingTransactions: //() a function for getting an array of transactions,
+ publishTransaction: //(rawTx) a async function for publishing raw transactions,
+ }
+
+*/
+
+module.exports = class PendingTransactionTracker extends EventEmitter {
+ constructor (config) {
+ super()
+ this.query = new EthQuery(config.provider)
+ this.nonceTracker = config.nonceTracker
+
+ this.getBalance = config.getBalance
+ this.getPendingTransactions = config.getPendingTransactions
+ this.publishTransaction = config.publishTransaction
+ }
+
+ // checks if a signed tx is in a block and
+ // if included sets the tx status as 'confirmed'
+ 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('txFailed', txId, noTxHashErr)
+ return
+ }
+
+
+ block.transactions.forEach((tx) => {
+ if (tx.hash === txHash) this.emit('txConfirmed', txId)
+ })
+ })
+ }
+
+ queryPendingTxs ({oldBlock, newBlock}) {
+ // check pending transactions on start
+ if (!oldBlock) {
+ this._checkPendingTxs()
+ return
+ }
+ // 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()
+ }
+
+
+ resubmitPendingTxs () {
+ const pending = this.getPendingTransactions()
+ // only try resubmitting if their are transactions to resubmit
+ if (!pending.length) return
+ pending.forEach((txMeta) => this._resubmitTx(txMeta).catch((err) => {
+ /*
+ Dont marked as failed if the error is a "known" transaction warning
+ "there is already a transaction with the same sender-nonce
+ but higher/same gas price"
+ */
+ const errorMessage = err.message.toLowerCase()
+ const isKnownTx = (
+ // geth
+ errorMessage.includes('replacement transaction underpriced')
+ || errorMessage.includes('known transaction')
+ // parity
+ || errorMessage.includes('gas price too low to replace')
+ || errorMessage.includes('transaction with the same hash was already imported')
+ // other
+ || errorMessage.includes('gateway timeout')
+ || errorMessage.includes('nonce too low')
+ )
+ // ignore resubmit warnings, return early
+ if (isKnownTx) return
+ // encountered real error - transition to error state
+ this.emit('txFailed', txMeta.id, err)
+ }))
+ }
+
+ async _resubmitTx (txMeta) {
+ const address = txMeta.txParams.from
+ const balance = this.getBalance(address)
+ if (balance === undefined) return
+ if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+
+ // 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)
+ log.error(insufficientFundsError)
+ return
+ }
+
+ // Only auto-submit already-signed txs:
+ if (!('rawTx' in txMeta)) return
+
+ // Increment a try counter.
+ txMeta.retryCount++
+ const rawTx = txMeta.rawTx
+ return await this.publishTransaction(rawTx)
+ }
+
+ async _checkPendingTx (txMeta) {
+ const txHash = txMeta.hash
+ const txId = txMeta.id
+ // extra check in case there was an uncaught error during the
+ // signature and submission process
+ 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)
+ return
+ }
+ // get latest transaction status
+ let txParams
+ try {
+ txParams = await this.query.getTransactionByHash(txHash)
+ if (!txParams) return
+ if (txParams.blockNumber) {
+ this.emit('txConfirmed', txId)
+ }
+ } catch (err) {
+ txMeta.warning = {
+ error: err,
+ message: 'There was a problem loading this transaction.',
+ }
+ this.emit('txWarning', txMeta)
+ throw err
+ }
+ }
+
+ // checks the network for signed txs and
+ // if confirmed sets the tx status as 'confirmed'
+ async _checkPendingTxs () {
+ const signedTxList = this.getPendingTransactions()
+ // in order to keep the nonceTracker accurate we block it while updating pending transactions
+ const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
+ try {
+ await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
+ } catch (err) {
+ console.error('PendingTransactionWatcher - Error updating pending transactions')
+ console.error(err)
+ }
+ nonceGlobalLock.releaseLock()
+ }
+} \ No newline at end of file
diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js
index 3687a9652..b64ea6712 100644
--- a/app/scripts/lib/tx-utils.js
+++ b/app/scripts/lib/tx-utils.js
@@ -1,7 +1,11 @@
-const ethUtil = require('ethereumjs-util')
+const EthQuery = require('ethjs-query')
const Transaction = require('ethereumjs-tx')
const normalize = require('eth-sig-util').normalize
-const BN = ethUtil.BN
+const {
+ hexToBn,
+ BnMultiplyByFraction,
+ bnToHex,
+} = require('./util')
/*
tx-utils are utility methods for Transaction manager
@@ -9,9 +13,9 @@ its passed ethquery
and used to do things like calculate gas of a tx.
*/
-module.exports = class txProvideUtils {
- constructor (ethQuery) {
- this.query = ethQuery
+module.exports = class txProvideUtil {
+ constructor (provider) {
+ this.query = new EthQuery(provider)
}
async analyzeGasUsage (txMeta) {
@@ -91,31 +95,4 @@ module.exports = class txProvideUtils {
throw new Error(`Invalid transaction value of ${txParams.value} not a positive number.`)
}
}
-
- sufficientBalance (txParams, hexBalance) {
- const balance = hexToBn(hexBalance)
- const value = hexToBn(txParams.value)
- const gasLimit = hexToBn(txParams.gas)
- const gasPrice = hexToBn(txParams.gasPrice)
-
- const maxCost = value.add(gasLimit.mul(gasPrice))
- return balance.gte(maxCost)
- }
-
-}
-
-// util
-
-function bnToHex (inputBn) {
- return ethUtil.addHexPrefix(inputBn.toString(16))
-}
-
-function hexToBn (inputHex) {
- return new BN(ethUtil.stripHexPrefix(inputHex), 16)
-}
-
-function BnMultiplyByFraction (targetBN, numerator, denominator) {
- const numBN = new BN(numerator)
- const denomBN = new BN(denominator)
- return targetBN.mul(numBN).div(denomBN)
-}
+} \ No newline at end of file
diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js
index bddd60ee8..6dee9edf0 100644
--- a/app/scripts/lib/util.js
+++ b/app/scripts/lib/util.js
@@ -1,8 +1,44 @@
+const ethUtil = require('ethereumjs-util')
+const assert = require('assert')
+const BN = require('bn.js')
+
module.exports = {
getStack,
+ sufficientBalance,
+ hexToBn,
+ bnToHex,
+ BnMultiplyByFraction,
}
function getStack () {
const stack = new Error('Stack trace generator - not an error').stack
return stack
}
+
+function sufficientBalance (txParams, hexBalance) {
+ // validate hexBalance is a hex string
+ assert.equal(typeof hexBalance, 'string', 'sufficientBalance - hexBalance is not a hex string')
+ assert.equal(hexBalance.slice(0, 2), '0x', 'sufficientBalance - hexBalance is not a hex string')
+
+ const balance = hexToBn(hexBalance)
+ const value = hexToBn(txParams.value)
+ const gasLimit = hexToBn(txParams.gas)
+ const gasPrice = hexToBn(txParams.gasPrice)
+
+ const maxCost = value.add(gasLimit.mul(gasPrice))
+ return balance.gte(maxCost)
+}
+
+function bnToHex (inputBn) {
+ return ethUtil.addHexPrefix(inputBn.toString(16))
+}
+
+function hexToBn (inputHex) {
+ return new BN(ethUtil.stripHexPrefix(inputHex), 16)
+}
+
+function BnMultiplyByFraction (targetBN, numerator, denominator) {
+ const numBN = new BN(numerator)
+ const denomBN = new BN(denominator)
+ return targetBN.mul(numBN).div(denomBN)
+}