Merge pull request #339 from poanetwork/refactor
Refactoring 3 before EIP - 1193
This commit is contained in:
commit
2d6753d182
|
@ -12,9 +12,9 @@ abiDecoder.addABI(abi)
|
||||||
import TransactionStateManager from './tx-state-manager'
|
import TransactionStateManager from './tx-state-manager'
|
||||||
const TxGasUtil = require('./tx-gas-utils')
|
const TxGasUtil = require('./tx-gas-utils')
|
||||||
const PendingTransactionTracker = require('./pending-tx-tracker')
|
const PendingTransactionTracker = require('./pending-tx-tracker')
|
||||||
const NonceTracker = require('./nonce-tracker')
|
import NonceTracker from 'nonce-tracker'
|
||||||
import * as txUtils from './lib/util'
|
import * as txUtils from './lib/util'
|
||||||
const cleanErrorStack = require('../../lib/cleanErrorStack')
|
import cleanErrorStack from '../../lib/cleanErrorStack'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
|
||||||
const {
|
const {
|
||||||
|
@ -228,18 +228,9 @@ class TransactionController extends EventEmitter {
|
||||||
@return {txMeta}
|
@return {txMeta}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
async retryTransaction (originalTxId, gasPrice) {
|
async retryTransaction (originalTxId) {
|
||||||
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
const originalTxMeta = this.txStateManager.getTx(originalTxId)
|
||||||
const { txParams } = originalTxMeta
|
const lastGasPrice = originalTxMeta.txParams.gasPrice
|
||||||
const lastGasPrice = gasPrice || originalTxMeta.txParams.gasPrice
|
|
||||||
const suggestedGasPriceBN = new ethUtil.BN(ethUtil.stripHexPrefix(this.getGasPrice()), 16)
|
|
||||||
const lastGasPriceBN = new ethUtil.BN(ethUtil.stripHexPrefix(lastGasPrice), 16)
|
|
||||||
// essentially lastGasPrice * 1.1 but
|
|
||||||
// dont trust decimals so a round about way of doing that
|
|
||||||
const lastGasPriceBNBumped = lastGasPriceBN.mul(new ethUtil.BN(110, 10)).div(new ethUtil.BN(100, 10))
|
|
||||||
// transactions that are being retried require a >=%10 bump or the clients will throw an error
|
|
||||||
txParams.gasPrice = suggestedGasPriceBN.gt(lastGasPriceBNBumped) ? `0x${suggestedGasPriceBN.toString(16)}` : `0x${lastGasPriceBNBumped.toString(16)}`
|
|
||||||
|
|
||||||
const txMeta = this.txStateManager.generateTxMeta({
|
const txMeta = this.txStateManager.generateTxMeta({
|
||||||
txParams: originalTxMeta.txParams,
|
txParams: originalTxMeta.txParams,
|
||||||
lastGasPrice,
|
lastGasPrice,
|
||||||
|
|
|
@ -1,161 +0,0 @@
|
||||||
const EthQuery = require('ethjs-query')
|
|
||||||
const assert = require('assert')
|
|
||||||
const Mutex = require('await-semaphore').Mutex
|
|
||||||
/**
|
|
||||||
@param opts {Object}
|
|
||||||
@param {Object} opts.provider a ethereum provider
|
|
||||||
@param {Function} opts.getPendingTransactions a function that returns an array of txMeta
|
|
||||||
whosee status is `submitted`
|
|
||||||
@param {Function} opts.getConfirmedTransactions a function that returns an array of txMeta
|
|
||||||
whose status is `confirmed`
|
|
||||||
@class
|
|
||||||
*/
|
|
||||||
class NonceTracker {
|
|
||||||
|
|
||||||
constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
|
|
||||||
this.provider = provider
|
|
||||||
this.blockTracker = blockTracker
|
|
||||||
this.ethQuery = new EthQuery(provider)
|
|
||||||
this.getPendingTransactions = getPendingTransactions
|
|
||||||
this.getConfirmedTransactions = getConfirmedTransactions
|
|
||||||
this.lockMap = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
@returns {Promise<Object>} with the key releaseLock (the gloabl mutex)
|
|
||||||
*/
|
|
||||||
async getGlobalLock () {
|
|
||||||
const globalMutex = this._lookupMutex('global')
|
|
||||||
// await global mutex free
|
|
||||||
const releaseLock = await globalMutex.acquire()
|
|
||||||
return { releaseLock }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @typedef NonceDetails
|
|
||||||
* @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction.
|
|
||||||
* @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method.
|
|
||||||
* @property {number} highestSuggested - The maximum between the other two, the number returned.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
this will return an object with the `nextNonce` `nonceDetails` of type NonceDetails, and the releaseLock
|
|
||||||
Note: releaseLock must be called after adding a signed tx to pending transactions (or discarding).
|
|
||||||
|
|
||||||
@param address {string} the hex string for the address whose nonce we are calculating
|
|
||||||
@returns {Promise<NonceDetails>}
|
|
||||||
*/
|
|
||||||
async getNonceLock (address) {
|
|
||||||
// await global mutex free
|
|
||||||
await this._globalMutexFree()
|
|
||||||
// await lock free, then take lock
|
|
||||||
const releaseLock = await this._takeMutex(address)
|
|
||||||
try {
|
|
||||||
// evaluate multiple nextNonce strategies
|
|
||||||
const nonceDetails = {}
|
|
||||||
const networkNonceResult = await this._getNetworkNextNonce(address)
|
|
||||||
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
|
|
||||||
const nextNetworkNonce = networkNonceResult.nonce
|
|
||||||
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
|
|
||||||
|
|
||||||
const pendingTxs = this.getPendingTransactions(address)
|
|
||||||
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
|
|
||||||
|
|
||||||
nonceDetails.params = {
|
|
||||||
highestLocallyConfirmed,
|
|
||||||
highestSuggested,
|
|
||||||
nextNetworkNonce,
|
|
||||||
}
|
|
||||||
nonceDetails.local = localNonceResult
|
|
||||||
nonceDetails.network = networkNonceResult
|
|
||||||
|
|
||||||
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
|
|
||||||
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
|
|
||||||
|
|
||||||
// return nonce and release cb
|
|
||||||
return { nextNonce, nonceDetails, releaseLock }
|
|
||||||
} catch (err) {
|
|
||||||
// release lock if we encounter an error
|
|
||||||
releaseLock()
|
|
||||||
throw err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _globalMutexFree () {
|
|
||||||
const globalMutex = this._lookupMutex('global')
|
|
||||||
const releaseLock = await globalMutex.acquire()
|
|
||||||
releaseLock()
|
|
||||||
}
|
|
||||||
|
|
||||||
async _takeMutex (lockId) {
|
|
||||||
const mutex = this._lookupMutex(lockId)
|
|
||||||
const releaseLock = await mutex.acquire()
|
|
||||||
return releaseLock
|
|
||||||
}
|
|
||||||
|
|
||||||
_lookupMutex (lockId) {
|
|
||||||
let mutex = this.lockMap[lockId]
|
|
||||||
if (!mutex) {
|
|
||||||
mutex = new Mutex()
|
|
||||||
this.lockMap[lockId] = mutex
|
|
||||||
}
|
|
||||||
return mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
async _getNetworkNextNonce (address) {
|
|
||||||
// calculate next nonce
|
|
||||||
// we need to make sure our base count
|
|
||||||
// and pending count are from the same block
|
|
||||||
const blockNumber = await this.blockTracker.getLatestBlock()
|
|
||||||
const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber)
|
|
||||||
const baseCount = baseCountBN.toNumber()
|
|
||||||
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
|
|
||||||
const nonceDetails = { blockNumber, baseCount }
|
|
||||||
return { name: 'network', nonce: baseCount, details: nonceDetails }
|
|
||||||
}
|
|
||||||
|
|
||||||
_getHighestLocallyConfirmed (address) {
|
|
||||||
const confirmedTransactions = this.getConfirmedTransactions(address)
|
|
||||||
const highest = this._getHighestNonce(confirmedTransactions)
|
|
||||||
return Number.isInteger(highest) ? highest + 1 : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
_getHighestNonce (txList) {
|
|
||||||
const nonces = txList.map((txMeta) => {
|
|
||||||
const nonce = txMeta.txParams.nonce
|
|
||||||
assert(typeof nonce, 'string', 'nonces should be hex strings')
|
|
||||||
return parseInt(nonce, 16)
|
|
||||||
})
|
|
||||||
const highestNonce = Math.max.apply(null, nonces)
|
|
||||||
return highestNonce
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
@typedef {object} highestContinuousFrom
|
|
||||||
@property {string} - name the name for how the nonce was calculated based on the data used
|
|
||||||
@property {number} - nonce the next suggested nonce
|
|
||||||
@property {object} - details the provided starting nonce that was used (for debugging)
|
|
||||||
*/
|
|
||||||
/**
|
|
||||||
@param txList {array} - list of txMeta's
|
|
||||||
@param startPoint {number} - the highest known locally confirmed nonce
|
|
||||||
@returns {highestContinuousFrom}
|
|
||||||
*/
|
|
||||||
_getHighestContinuousFrom (txList, startPoint) {
|
|
||||||
const nonces = txList.map((txMeta) => {
|
|
||||||
const nonce = txMeta.txParams.nonce
|
|
||||||
assert(typeof nonce, 'string', 'nonces should be hex strings')
|
|
||||||
return parseInt(nonce, 16)
|
|
||||||
})
|
|
||||||
|
|
||||||
let highest = startPoint
|
|
||||||
while (nonces.includes(highest)) {
|
|
||||||
highest++
|
|
||||||
}
|
|
||||||
|
|
||||||
return { name: 'local', nonce: highest, details: { startPoint, highest } }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = NonceTracker
|
|
|
@ -1,11 +1,9 @@
|
||||||
import ethUtil from 'ethereumjs-util'
|
|
||||||
import extend from 'extend'
|
|
||||||
import EventEmitter from 'safe-event-emitter'
|
import EventEmitter from 'safe-event-emitter'
|
||||||
import ObservableStore from 'obs-store'
|
import ObservableStore from 'obs-store'
|
||||||
import log from 'loglevel'
|
import log from 'loglevel'
|
||||||
import txStateHistoryHelper from './lib/tx-state-history-helper'
|
import txStateHistoryHelper from './lib/tx-state-history-helper'
|
||||||
import createId from '../../lib/random-id'
|
import createId from '../../lib/random-id'
|
||||||
import { getFinalStates } from './lib/util'
|
import { getFinalStates, normalizeTxParams } from './lib/util'
|
||||||
/**
|
/**
|
||||||
TransactionStateManager is responsible for the state of a transaction and
|
TransactionStateManager is responsible for the state of a transaction and
|
||||||
storing the transaction
|
storing the transaction
|
||||||
|
@ -21,8 +19,8 @@ import { getFinalStates } from './lib/util'
|
||||||
<br> - `'confirmed'` the tx has been included in a block.
|
<br> - `'confirmed'` the tx has been included in a block.
|
||||||
<br> - `'failed'` the tx failed for some reason, included on tx data.
|
<br> - `'failed'` the tx failed for some reason, included on tx data.
|
||||||
<br> - `'dropped'` the tx nonce was already used
|
<br> - `'dropped'` the tx nonce was already used
|
||||||
@param opts {object}
|
@param {Object} opts
|
||||||
@param {object} [opts.initState={ transactions: [] }] initial transactions list with the key transaction {array}
|
@param {Object} [opts.initState={ transactions: [] }] initial transactions list with the key transaction {array}
|
||||||
@param {number} [opts.txHistoryLimit] limit for how many finished
|
@param {number} [opts.txHistoryLimit] limit for how many finished
|
||||||
transactions can hang around in state
|
transactions can hang around in state
|
||||||
@param {function} opts.getNetwork return network number
|
@param {function} opts.getNetwork return network number
|
||||||
|
@ -33,7 +31,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this.store = new ObservableStore(
|
this.store = new ObservableStore(
|
||||||
extend({
|
Object.assign({
|
||||||
transactions: [],
|
transactions: [],
|
||||||
}, initState))
|
}, initState))
|
||||||
this.txHistoryLimit = txHistoryLimit
|
this.txHistoryLimit = txHistoryLimit
|
||||||
|
@ -41,21 +39,25 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param opts {object} - the object to use when overwriting defaults
|
@param {Object} opts - the object to use when overwriting defaults
|
||||||
@returns {txMeta} the default txMeta object
|
@returns {txMeta} - the default txMeta object
|
||||||
*/
|
*/
|
||||||
generateTxMeta (opts) {
|
generateTxMeta (opts) {
|
||||||
return extend({
|
const netId = this.getNetwork()
|
||||||
|
if (netId === 'loading') {
|
||||||
|
throw new Error('MetaMask is having trouble connecting to the network')
|
||||||
|
}
|
||||||
|
return Object.assign({
|
||||||
id: createId(),
|
id: createId(),
|
||||||
time: (new Date()).getTime(),
|
time: (new Date()).getTime(),
|
||||||
status: 'unapproved',
|
status: 'unapproved',
|
||||||
metamaskNetworkId: this.getNetwork(),
|
metamaskNetworkId: netId,
|
||||||
loadingDefaults: true,
|
loadingDefaults: true,
|
||||||
}, opts)
|
}, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} of txMetas that have been filtered for only the current network
|
@returns {array} - of txMetas that have been filtered for only the current network
|
||||||
*/
|
*/
|
||||||
getTxList () {
|
getTxList () {
|
||||||
const network = this.getNetwork()
|
const network = this.getNetwork()
|
||||||
|
@ -64,14 +66,14 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} of all the txMetas in store
|
@returns {array} - of all the txMetas in store
|
||||||
*/
|
*/
|
||||||
getFullTxList () {
|
getFullTxList () {
|
||||||
return this.store.getState().transactions
|
return this.store.getState().transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@returns {array} the tx list whos status is unapproved
|
@returns {array} - the tx list whos status is unapproved
|
||||||
*/
|
*/
|
||||||
getUnapprovedTxList () {
|
getUnapprovedTxList () {
|
||||||
const txList = this.getTxsByMetaData('status', 'unapproved')
|
const txList = this.getTxsByMetaData('status', 'unapproved')
|
||||||
|
@ -83,23 +85,40 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} the tx list whos status is submitted if no address is provide
|
@returns {array} - the tx list whos status is approved if no address is provide
|
||||||
returns all txMetas who's status is submitted for the current network
|
returns all txMetas who's status is approved for the current network
|
||||||
*/
|
*/
|
||||||
getPendingTransactions (address) {
|
getApprovedTransactions (address) {
|
||||||
const opts = { status: 'submitted' }
|
const opts = { status: 'approved' }
|
||||||
if (address) opts.from = address
|
if (address) {
|
||||||
|
opts.from = address
|
||||||
|
}
|
||||||
return this.getFilteredTxList(opts)
|
return this.getFilteredTxList(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
||||||
@returns {array} the tx list whos status is confirmed if no address is provide
|
@returns {array} - the tx list whos status is submitted if no address is provide
|
||||||
|
returns all txMetas who's status is submitted for the current network
|
||||||
|
*/
|
||||||
|
getPendingTransactions (address) {
|
||||||
|
const opts = { status: 'submitted' }
|
||||||
|
if (address) {
|
||||||
|
opts.from = address
|
||||||
|
}
|
||||||
|
return this.getFilteredTxList(opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
@param [address] {string} - hex prefixed address to sort the txMetas for [optional]
|
||||||
|
@returns {array} - the tx list whos status is confirmed if no address is provide
|
||||||
returns all txMetas who's status is confirmed for the current network
|
returns all txMetas who's status is confirmed for the current network
|
||||||
*/
|
*/
|
||||||
getConfirmedTransactions (address) {
|
getConfirmedTransactions (address) {
|
||||||
const opts = { status: 'confirmed' }
|
const opts = { status: 'confirmed' }
|
||||||
if (address) opts.from = address
|
if (address) {
|
||||||
|
opts.from = address
|
||||||
|
}
|
||||||
return this.getFilteredTxList(opts)
|
return this.getFilteredTxList(opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -109,14 +128,19 @@ class TransactionStateManager extends EventEmitter {
|
||||||
is in its final state
|
is in its final state
|
||||||
it will allso add the key `history` to the txMeta with the snap shot of the original
|
it will allso add the key `history` to the txMeta with the snap shot of the original
|
||||||
object
|
object
|
||||||
@param txMeta {Object}
|
@param {Object} txMeta
|
||||||
@returns {object} the txMeta
|
@returns {Object} - the txMeta
|
||||||
*/
|
*/
|
||||||
addTx (txMeta) {
|
addTx (txMeta) {
|
||||||
this.once(`${txMeta.id}:signed`, function (txId) {
|
// normalize and validate txParams if present
|
||||||
|
if (txMeta.txParams) {
|
||||||
|
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.once(`${txMeta.id}:signed`, function () {
|
||||||
this.removeAllListeners(`${txMeta.id}:rejected`)
|
this.removeAllListeners(`${txMeta.id}:rejected`)
|
||||||
})
|
})
|
||||||
this.once(`${txMeta.id}:rejected`, function (txId) {
|
this.once(`${txMeta.id}:rejected`, function () {
|
||||||
this.removeAllListeners(`${txMeta.id}:signed`)
|
this.removeAllListeners(`${txMeta.id}:signed`)
|
||||||
})
|
})
|
||||||
// initialize history
|
// initialize history
|
||||||
|
@ -142,13 +166,18 @@ class TransactionStateManager extends EventEmitter {
|
||||||
transactions.splice(index, 1)
|
transactions.splice(index, 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transactions.push(txMeta)
|
const newTxIndex = transactions
|
||||||
|
.findIndex((currentTxMeta) => currentTxMeta.time > txMeta.time)
|
||||||
|
|
||||||
|
newTxIndex === -1
|
||||||
|
? transactions.push(txMeta)
|
||||||
|
: transactions.splice(newTxIndex, 0, txMeta)
|
||||||
this._saveTxList(transactions)
|
this._saveTxList(transactions)
|
||||||
return txMeta
|
return txMeta
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@param txId {number}
|
@param {number} txId
|
||||||
@returns {object} the txMeta who matches the given id if none found
|
@returns {Object} - the txMeta who matches the given id if none found
|
||||||
for the network returns undefined
|
for the network returns undefined
|
||||||
*/
|
*/
|
||||||
getTx (txId) {
|
getTx (txId) {
|
||||||
|
@ -158,17 +187,13 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
updates the txMeta in the list and adds a history entry
|
updates the txMeta in the list and adds a history entry
|
||||||
@param txMeta {Object} - the txMeta to update
|
@param {Object} txMeta - the txMeta to update
|
||||||
@param [note] {string} - a note about the update for history
|
@param {string} [note] - a note about the update for history
|
||||||
*/
|
*/
|
||||||
updateTx (txMeta, note) {
|
updateTx (txMeta, note) {
|
||||||
// validate txParams
|
// normalize and validate txParams if present
|
||||||
if (txMeta.txParams) {
|
if (txMeta.txParams) {
|
||||||
if (typeof txMeta.txParams.data === 'undefined') {
|
txMeta.txParams = this.normalizeAndValidateTxParams(txMeta.txParams)
|
||||||
delete txMeta.txParams.data
|
|
||||||
}
|
|
||||||
|
|
||||||
this.validateTxParams(txMeta.txParams)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// create txMeta snapshot for history
|
// create txMeta snapshot for history
|
||||||
|
@ -182,7 +207,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
// commit txMeta to state
|
// commit txMeta to state
|
||||||
const txId = txMeta.id
|
const txId = txMeta.id
|
||||||
const txList = this.getFullTxList()
|
const txList = this.getFullTxList()
|
||||||
const index = txList.findIndex(txData => txData.id === txId)
|
const index = txList.findIndex((txData) => txData.id === txId)
|
||||||
txList[index] = txMeta
|
txList[index] = txMeta
|
||||||
this._saveTxList(txList)
|
this._saveTxList(txList)
|
||||||
}
|
}
|
||||||
|
@ -191,18 +216,31 @@ class TransactionStateManager extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
merges txParams obj onto txMeta.txParams
|
merges txParams obj onto txMeta.txParams
|
||||||
use extend to ensure that all fields are filled
|
use extend to ensure that all fields are filled
|
||||||
@param txId {number} - the id of the txMeta
|
@param {number} txId - the id of the txMeta
|
||||||
@param txParams {object} - the updated txParams
|
@param {Object} txParams - the updated txParams
|
||||||
*/
|
*/
|
||||||
updateTxParams (txId, txParams) {
|
updateTxParams (txId, txParams) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.txParams = extend(txMeta.txParams, txParams)
|
txMeta.txParams = { ...txMeta.txParams, ...txParams }
|
||||||
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
this.updateTx(txMeta, `txStateManager#updateTxParams`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* normalize and validate txParams members
|
||||||
|
* @param {Object} txParams - txParams
|
||||||
|
*/
|
||||||
|
normalizeAndValidateTxParams (txParams) {
|
||||||
|
if (typeof txParams.data === 'undefined') {
|
||||||
|
delete txParams.data
|
||||||
|
}
|
||||||
|
txParams = normalizeTxParams(txParams, false)
|
||||||
|
this.validateTxParams(txParams)
|
||||||
|
return txParams
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
validates txParams members by type
|
validates txParams members by type
|
||||||
@param txParams {object} - txParams to validate
|
@param {Object} txParams - txParams to validate
|
||||||
*/
|
*/
|
||||||
validateTxParams (txParams) {
|
validateTxParams (txParams) {
|
||||||
Object.keys(txParams).forEach((key) => {
|
Object.keys(txParams).forEach((key) => {
|
||||||
|
@ -210,26 +248,31 @@ class TransactionStateManager extends EventEmitter {
|
||||||
// validate types
|
// validate types
|
||||||
switch (key) {
|
switch (key) {
|
||||||
case 'chainId':
|
case 'chainId':
|
||||||
if (typeof value !== 'number' && typeof value !== 'string') throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
|
if (typeof value !== 'number' && typeof value !== 'string') {
|
||||||
|
throw new Error(`${key} in txParams is not a Number or hex string. got: (${value})`)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (typeof value !== 'string') throw new Error(`${key} in txParams is not a string. got: (${value})`)
|
if (typeof value !== 'string') {
|
||||||
if (!ethUtil.isHexPrefixed(value)) throw new Error(`${key} in txParams is not hex prefixed. got: (${value})`)
|
throw new Error(`${key} in txParams is not a string. got: (${value})`)
|
||||||
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param opts {object} - an object of fields to search for eg:<br>
|
@param {Object} opts - an object of fields to search for eg:<br>
|
||||||
let <code>thingsToLookFor = {<br>
|
let <code>thingsToLookFor = {<br>
|
||||||
to: '0x0..',<br>
|
to: '0x0..',<br>
|
||||||
from: '0x0..',<br>
|
from: '0x0..',<br>
|
||||||
status: 'signed',<br>
|
status: 'signed', \\ (status) => status !== 'rejected' give me all txs who's status is not rejected<br>
|
||||||
err: undefined,<br>
|
err: undefined,<br>
|
||||||
}<br></code>
|
}<br></code>
|
||||||
|
optionally the values of the keys can be functions for situations like where
|
||||||
|
you want all but one status.
|
||||||
@param [initialList=this.getTxList()]
|
@param [initialList=this.getTxList()]
|
||||||
@returns a {array} of txMeta with all
|
@returns {array} - array of txMeta with all
|
||||||
options matching
|
options matching
|
||||||
*/
|
*/
|
||||||
/*
|
/*
|
||||||
|
@ -243,7 +286,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
this is for things like filtering a the tx list
|
this is for things like filtering a the tx list
|
||||||
for only tx's from 1 account
|
for only tx's from 1 account
|
||||||
or for filltering for all txs from one account
|
or for filtering for all txs from one account
|
||||||
and that have been 'confirmed'
|
and that have been 'confirmed'
|
||||||
*/
|
*/
|
||||||
getFilteredTxList (opts, initialList) {
|
getFilteredTxList (opts, initialList) {
|
||||||
|
@ -255,18 +298,20 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|
||||||
@param key {string} - the key to check
|
@param {string} key - the key to check
|
||||||
@param value - the value your looking for
|
@param value - the value your looking for can also be a function that returns a bool
|
||||||
@param [txList=this.getTxList()] {array} - the list to search. default is the txList
|
@param [txList=this.getTxList()] {array} - the list to search. default is the txList
|
||||||
from txStateManager#getTxList
|
from txStateManager#getTxList
|
||||||
@returns {array} a list of txMetas who matches the search params
|
@returns {array} - a list of txMetas who matches the search params
|
||||||
*/
|
*/
|
||||||
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
getTxsByMetaData (key, value, txList = this.getTxList()) {
|
||||||
|
const filter = typeof value === 'function' ? value : (v) => v === value
|
||||||
|
|
||||||
return txList.filter((txMeta) => {
|
return txList.filter((txMeta) => {
|
||||||
if (key in txMeta.txParams) {
|
if (key in txMeta.txParams) {
|
||||||
return txMeta.txParams[key] === value
|
return filter(txMeta.txParams[key])
|
||||||
} else {
|
} else {
|
||||||
return txMeta[key] === value
|
return filter(txMeta[key])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -274,8 +319,8 @@ class TransactionStateManager extends EventEmitter {
|
||||||
// get::set status
|
// get::set status
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
@return {string} the status of the tx.
|
@returns {string} - the status of the tx.
|
||||||
*/
|
*/
|
||||||
getTxStatus (txId) {
|
getTxStatus (txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
|
@ -284,7 +329,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'rejected'.
|
should update the status of the tx to 'rejected'.
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusRejected (txId) {
|
setTxStatusRejected (txId) {
|
||||||
this._setTxStatus(txId, 'rejected')
|
this._setTxStatus(txId, 'rejected')
|
||||||
|
@ -293,14 +338,14 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'unapproved'.
|
should update the status of the tx to 'unapproved'.
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusUnapproved (txId) {
|
setTxStatusUnapproved (txId) {
|
||||||
this._setTxStatus(txId, 'unapproved')
|
this._setTxStatus(txId, 'unapproved')
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'approved'.
|
should update the status of the tx to 'approved'.
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusApproved (txId) {
|
setTxStatusApproved (txId) {
|
||||||
this._setTxStatus(txId, 'approved')
|
this._setTxStatus(txId, 'approved')
|
||||||
|
@ -308,7 +353,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'signed'.
|
should update the status of the tx to 'signed'.
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusSigned (txId) {
|
setTxStatusSigned (txId) {
|
||||||
this._setTxStatus(txId, 'signed')
|
this._setTxStatus(txId, 'signed')
|
||||||
|
@ -317,7 +362,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'submitted'.
|
should update the status of the tx to 'submitted'.
|
||||||
and add a time stamp for when it was called
|
and add a time stamp for when it was called
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusSubmitted (txId) {
|
setTxStatusSubmitted (txId) {
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
|
@ -328,7 +373,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'confirmed'.
|
should update the status of the tx to 'confirmed'.
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusConfirmed (txId) {
|
setTxStatusConfirmed (txId) {
|
||||||
this._setTxStatus(txId, 'confirmed')
|
this._setTxStatus(txId, 'confirmed')
|
||||||
|
@ -336,7 +381,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'dropped'.
|
should update the status of the tx to 'dropped'.
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
*/
|
*/
|
||||||
setTxStatusDropped (txId) {
|
setTxStatusDropped (txId) {
|
||||||
this._setTxStatus(txId, 'dropped')
|
this._setTxStatus(txId, 'dropped')
|
||||||
|
@ -346,24 +391,26 @@ class TransactionStateManager extends EventEmitter {
|
||||||
/**
|
/**
|
||||||
should update the status of the tx to 'failed'.
|
should update the status of the tx to 'failed'.
|
||||||
and put the error on the txMeta
|
and put the error on the txMeta
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
@param err {erroObject} - error object
|
@param {erroObject} err - error object
|
||||||
*/
|
*/
|
||||||
setTxStatusFailed (txId, err) {
|
setTxStatusFailed (txId, err) {
|
||||||
|
const error = !err ? new Error('Internal metamask failure') : err
|
||||||
|
|
||||||
const txMeta = this.getTx(txId)
|
const txMeta = this.getTx(txId)
|
||||||
txMeta.err = {
|
txMeta.err = {
|
||||||
message: (err ? (err.message || err.error || err) : '').toString(),
|
message: error.toString(),
|
||||||
rpc: err.value,
|
rpc: error.value,
|
||||||
stack: err.stack,
|
stack: error.stack,
|
||||||
}
|
}
|
||||||
this.updateTx(txMeta)
|
this.updateTx(txMeta, 'transactions:tx-state-manager#fail - add error')
|
||||||
this._setTxStatus(txId, 'failed')
|
this._setTxStatus(txId, 'failed')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Removes transaction from the given address for the current network
|
Removes transaction from the given address for the current network
|
||||||
from the txList
|
from the txList
|
||||||
@param address {string} - hex string of the from address on the txParams to remove
|
@param {string} address - hex string of the from address on the txParams to remove
|
||||||
*/
|
*/
|
||||||
wipeTransactions (address) {
|
wipeTransactions (address) {
|
||||||
// network only tx
|
// network only tx
|
||||||
|
@ -392,8 +439,8 @@ class TransactionStateManager extends EventEmitter {
|
||||||
// - `'dropped'` the tx nonce was already used
|
// - `'dropped'` the tx nonce was already used
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@param txId {number} - the txMeta Id
|
@param {number} txId - the txMeta Id
|
||||||
@param status {string} - the status to set on the txMeta
|
@param {string} status - the status to set on the txMeta
|
||||||
@emits tx:status-update - passes txId and status
|
@emits tx:status-update - passes txId and status
|
||||||
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
@emits ${txMeta.id}:finished - if it is a finished state. Passes the txMeta
|
||||||
@emits update:badge
|
@emits update:badge
|
||||||
|
@ -423,7 +470,7 @@ class TransactionStateManager extends EventEmitter {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Saves the new/updated txList.
|
Saves the new/updated txList.
|
||||||
@param transactions {array} - the list of transactions to save
|
@param {array} transactions - the list of transactions to save
|
||||||
*/
|
*/
|
||||||
// Function is intended only for internal use
|
// Function is intended only for internal use
|
||||||
_saveTxList (transactions) {
|
_saveTxList (transactions) {
|
||||||
|
@ -436,4 +483,4 @@ class TransactionStateManager extends EventEmitter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = TransactionStateManager
|
export default TransactionStateManager
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/**
|
/**
|
||||||
* Returns error without stack trace for better UI display
|
* Returns error without stack trace for better UI display
|
||||||
* @param {Error} err - error
|
* @param {Error} err - error
|
||||||
* @returns {Error} Error with clean stack trace.
|
* @returns {Error} - Error with clean stack trace.
|
||||||
*/
|
*/
|
||||||
function cleanErrorStack (err) {
|
function cleanErrorStack (err) {
|
||||||
var name = err.name
|
let name = err.name
|
||||||
name = (name === undefined) ? 'Error' : String(name)
|
name = (name === undefined) ? 'Error' : String(name)
|
||||||
|
|
||||||
var msg = err.message
|
let msg = err.message
|
||||||
msg = (msg === undefined) ? '' : String(msg)
|
msg = (msg === undefined) ? '' : String(msg)
|
||||||
|
|
||||||
if (name === '') {
|
if (name === '') {
|
||||||
|
@ -21,4 +21,4 @@ function cleanErrorStack (err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = cleanErrorStack
|
export default cleanErrorStack
|
||||||
|
|
|
@ -4674,7 +4674,6 @@
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz",
|
||||||
"integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
|
"integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"object-assign": "^4.1.1",
|
"object-assign": "^4.1.1",
|
||||||
"util": "0.10.3"
|
"util": "0.10.3"
|
||||||
|
@ -4683,14 +4682,12 @@
|
||||||
"inherits": {
|
"inherits": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz",
|
||||||
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=",
|
"integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"util": {
|
"util": {
|
||||||
"version": "0.10.3",
|
"version": "0.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
"resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz",
|
||||||
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
"integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"inherits": "2.0.1"
|
"inherits": "2.0.1"
|
||||||
}
|
}
|
||||||
|
@ -45273,6 +45270,16 @@
|
||||||
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=",
|
"integrity": "sha1-WuVUHQJGRdMqWPzdyc7s6nrjrC8=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"nonce-tracker": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/nonce-tracker/-/nonce-tracker-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-hxKokxgLvOZx9A5qPQKwL34G1/YwMC5xJWZHFUKfvwxypkn2nP0KVJjbcoXwY6pXsRRa11KdFEPW61N4YCGnWQ==",
|
||||||
|
"requires": {
|
||||||
|
"assert": "^1.4.1",
|
||||||
|
"await-semaphore": "^0.1.3",
|
||||||
|
"ethjs-query": "^0.3.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"nopt": {
|
"nopt": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz",
|
||||||
|
|
|
@ -157,6 +157,7 @@
|
||||||
"multihashes": "^0.4.12",
|
"multihashes": "^0.4.12",
|
||||||
"nanoid": "^2.1.6",
|
"nanoid": "^2.1.6",
|
||||||
"nifty-wallet-inpage-provider": "github:poanetwork/nifty-wallet-inpage-provider#1.2.3",
|
"nifty-wallet-inpage-provider": "github:poanetwork/nifty-wallet-inpage-provider#1.2.3",
|
||||||
|
"nonce-tracker": "^1.0.0",
|
||||||
"number-to-bn": "^1.7.0",
|
"number-to-bn": "^1.7.0",
|
||||||
"obj-multiplex": "^1.0.0",
|
"obj-multiplex": "^1.0.0",
|
||||||
"obs-store": "^4.0.3",
|
"obs-store": "^4.0.3",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const assert = require('assert')
|
import assert from 'assert'
|
||||||
const cleanErrorStack = require('../../../app/scripts/lib/cleanErrorStack')
|
import cleanErrorStack from '../../../app/scripts/lib/cleanErrorStack'
|
||||||
|
|
||||||
describe('Clean Error Stack', () => {
|
describe('Clean Error Stack', function () {
|
||||||
|
|
||||||
const testMessage = 'Test Message'
|
const testMessage = 'Test Message'
|
||||||
const testError = new Error(testMessage)
|
const testError = new Error(testMessage)
|
||||||
|
@ -9,24 +9,24 @@ describe('Clean Error Stack', () => {
|
||||||
const blankErrorName = new Error(testMessage)
|
const blankErrorName = new Error(testMessage)
|
||||||
const blankMsgError = new Error()
|
const blankMsgError = new Error()
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(function () {
|
||||||
undefinedErrorName.name = undefined
|
undefinedErrorName.name = undefined
|
||||||
blankErrorName.name = ''
|
blankErrorName.name = ''
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests error with message', () => {
|
it('tests error with message', function () {
|
||||||
assert.equal(cleanErrorStack(testError), 'Error: Test Message')
|
assert.equal(cleanErrorStack(testError), 'Error: Test Message')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests error with undefined name', () => {
|
it('tests error with undefined name', function () {
|
||||||
assert.equal(cleanErrorStack(undefinedErrorName).toString(), 'Error: Test Message')
|
assert.equal(cleanErrorStack(undefinedErrorName).toString(), 'Error: Test Message')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests error with blank name', () => {
|
it('tests error with blank name', function () {
|
||||||
assert.equal(cleanErrorStack(blankErrorName).toString(), 'Test Message')
|
assert.equal(cleanErrorStack(blankErrorName).toString(), 'Test Message')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('tests error with blank message', () => {
|
it('tests error with blank message', function () {
|
||||||
assert.equal(cleanErrorStack(blankMsgError), 'Error')
|
assert.equal(cleanErrorStack(blankMsgError), 'Error')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,238 +0,0 @@
|
||||||
const assert = require('assert')
|
|
||||||
const NonceTracker = require('../../../../../app/scripts/controllers/transactions/nonce-tracker')
|
|
||||||
const MockTxGen = require('../../../../lib/mock-tx-gen')
|
|
||||||
const providerResultStub = {}
|
|
||||||
|
|
||||||
describe('Nonce Tracker', function () {
|
|
||||||
let nonceTracker, pendingTxs, confirmedTxs
|
|
||||||
|
|
||||||
describe('#getNonceLock', function () {
|
|
||||||
|
|
||||||
describe('with 3 confirmed and 1 pending', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 1 })
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x1')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return 4', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '4', `nonce should be 4 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should use localNonce if network returns a nonce lower then a confirmed tx in state', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '4', 'nonce should be 4')
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('sentry issue 476304902', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, {
|
|
||||||
fromNonce: 3,
|
|
||||||
count: 29,
|
|
||||||
})
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x3')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return 9', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '32', `nonce should be 32 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('issue 3670', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, {
|
|
||||||
fromNonce: 6,
|
|
||||||
count: 3,
|
|
||||||
})
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x6')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return 9', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '9', `nonce should be 9 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with no previous txs', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
nonceTracker = generateNonceTrackerWith([], [])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return 0', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 returned ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with multiple previous txs with same nonce', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 1 })
|
|
||||||
pendingTxs = txGen.generate({
|
|
||||||
status: 'submitted',
|
|
||||||
txParams: { nonce: '0x01' },
|
|
||||||
}, { count: 5 })
|
|
||||||
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x0')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after those', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when local confirmed count is higher than network nonce', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 3 })
|
|
||||||
nonceTracker = generateNonceTrackerWith([], confirmedTxs, '0x1')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after those', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '3', `nonce should be 3 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when local pending count is higher than other metrics', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 })
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [])
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after those', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '2', `nonce should be 2 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when provider nonce is higher than other metrics', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 2 })
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x05')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after those', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when there are some pending nonces below the remote one and some over.', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 })
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x03')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after those', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '5', `nonce should be 5 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when there are pending nonces non sequentially over the network nonce.', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
txGen.generate({ status: 'submitted' }, { count: 5 })
|
|
||||||
// 5 over that number
|
|
||||||
pendingTxs = txGen.generate({ status: 'submitted' }, { count: 5 })
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x00')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after network nonce', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '0', `nonce should be 0 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('When all three return different values', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 })
|
|
||||||
pendingTxs = txGen.generate({
|
|
||||||
status: 'submitted',
|
|
||||||
nonce: 100,
|
|
||||||
}, { count: 1 })
|
|
||||||
// 0x32 is 50 in hex:
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, confirmedTxs, '0x32')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after network nonce', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '50', `nonce should be 50 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Faq issue 67', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const txGen = new MockTxGen()
|
|
||||||
confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 64 })
|
|
||||||
pendingTxs = txGen.generate({
|
|
||||||
status: 'submitted',
|
|
||||||
}, { count: 10 })
|
|
||||||
// 0x40 is 64 in hex:
|
|
||||||
nonceTracker = generateNonceTrackerWith(pendingTxs, [], '0x40')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return nonce after network nonce', async function () {
|
|
||||||
this.timeout(15000)
|
|
||||||
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
|
|
||||||
assert.equal(nonceLock.nextNonce, '74', `nonce should be 74 got ${nonceLock.nextNonce}`)
|
|
||||||
await nonceLock.releaseLock()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
|
|
||||||
const getPendingTransactions = () => pending
|
|
||||||
const getConfirmedTransactions = () => confirmed
|
|
||||||
providerResultStub.result = providerStub
|
|
||||||
const provider = {
|
|
||||||
sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
|
|
||||||
}
|
|
||||||
const blockTracker = {
|
|
||||||
getCurrentBlock: () => '0x11b568',
|
|
||||||
getLatestBlock: async () => '0x11b568',
|
|
||||||
}
|
|
||||||
return new NonceTracker({
|
|
||||||
provider,
|
|
||||||
blockTracker,
|
|
||||||
getPendingTransactions,
|
|
||||||
getConfirmedTransactions,
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -417,20 +417,19 @@ describe('Transaction Controller', function () {
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#retryTransaction', function () {
|
describe('#retryTransaction', function () {
|
||||||
it('should create a new txMeta with the same txParams as the original one but with a higher gasPrice', function (done) {
|
it('should create a new txMeta with the same txParams as the original one', function (done) {
|
||||||
const txParams = {
|
const txParams = {
|
||||||
gasPrice: '0xee6b2800',
|
gasPrice: '0xee6b2800',
|
||||||
nonce: '0x00',
|
nonce: '0x00',
|
||||||
from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4',
|
from: '0xb09d8505e1f4ef1cea089d47094f5dd3464083d4',
|
||||||
to: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4',
|
to: '0xb09d8505e1f4ef1cea089d47094f5dd3464083d4',
|
||||||
data: '0x0',
|
data: '0x0',
|
||||||
}
|
}
|
||||||
txController.txStateManager._saveTxList([
|
txController.txStateManager._saveTxList([
|
||||||
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [{}] },
|
{ id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] },
|
||||||
])
|
])
|
||||||
txController.retryTransaction(1)
|
txController.retryTransaction(1)
|
||||||
.then((txMeta) => {
|
.then((txMeta) => {
|
||||||
assert.equal(txMeta.txParams.gasPrice, '0x10642ac00', 'gasPrice should have a %10 gasPrice bump')
|
|
||||||
assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same')
|
assert.equal(txMeta.txParams.nonce, txParams.nonce, 'nonce should be the same')
|
||||||
assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same')
|
assert.equal(txMeta.txParams.from, txParams.from, 'from should be the same')
|
||||||
assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same')
|
assert.equal(txMeta.txParams.to, txParams.to, 'to should be the same')
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const assert = require('assert')
|
import assert from 'assert'
|
||||||
const TxStateManager = require('../../../../../app/scripts/controllers/transactions/tx-state-manager')
|
import TxStateManager from '../../../../../app/scripts/controllers/transactions/tx-state-manager'
|
||||||
import txStateHistoryHelper from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper'
|
import txStateHistoryHelper from '../../../../../app/scripts/controllers/transactions/lib/tx-state-history-helper'
|
||||||
const noop = () => true
|
const noop = () => true
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ describe('TransactionStateManager', function () {
|
||||||
assert.equal(result[0].status, 'signed')
|
assert.equal(result[0].status, 'signed')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should emit a signed event to signal the exciton of callback', (done) => {
|
it('should emit a signed event to signal the exciton of callback', function (done) {
|
||||||
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
|
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
|
||||||
const noop = function () {
|
const noop = function () {
|
||||||
assert(true, 'event listener has been triggered and noop executed')
|
assert(true, 'event listener has been triggered and noop executed')
|
||||||
|
@ -52,13 +52,10 @@ describe('TransactionStateManager', function () {
|
||||||
assert.equal(result.length, 0)
|
assert.equal(result.length, 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should emit a rejected event to signal the exciton of callback', (done) => {
|
it('should emit a rejected event to signal the exciton of callback', function (done) {
|
||||||
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
|
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
|
||||||
txStateManager.addTx(tx)
|
txStateManager.addTx(tx)
|
||||||
const noop = function (err, txId) {
|
const noop = () => {
|
||||||
if (err) {
|
|
||||||
console.log('Error: ', err)
|
|
||||||
}
|
|
||||||
assert(true, 'event listener has been triggered and noop executed')
|
assert(true, 'event listener has been triggered and noop executed')
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
|
@ -93,6 +90,37 @@ describe('TransactionStateManager', function () {
|
||||||
assert.equal(result[0].id, 1)
|
assert.equal(result[0].id, 1)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('throws error and does not add tx if txParams are invalid', function () {
|
||||||
|
const validTxParams = {
|
||||||
|
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
|
||||||
|
to: '0x0039f22efb07a647557c7c5d17854cfd6d489ef3',
|
||||||
|
nonce: '0x3',
|
||||||
|
gas: '0x77359400',
|
||||||
|
gasPrice: '0x77359400',
|
||||||
|
value: '0x0',
|
||||||
|
data: '0x0',
|
||||||
|
}
|
||||||
|
const invalidValues = [1, true, {}, Symbol('1')]
|
||||||
|
|
||||||
|
for (const key in validTxParams) {
|
||||||
|
for (const value of invalidValues) {
|
||||||
|
const tx = {
|
||||||
|
id: 1,
|
||||||
|
status: 'unapproved',
|
||||||
|
metamaskNetworkId: currentNetworkId,
|
||||||
|
txParams: {
|
||||||
|
...validTxParams,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.throws(txStateManager.addTx.bind(txStateManager, tx), 'addTx should throw error')
|
||||||
|
const result = txStateManager.getTxList()
|
||||||
|
assert.ok(Array.isArray(result), 'txList should be an array')
|
||||||
|
assert.equal(result.length, 0, 'txList should be empty')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('does not override txs from other networks', function () {
|
it('does not override txs from other networks', function () {
|
||||||
const tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
|
const tx = { id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams: {} }
|
||||||
const tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} }
|
const tx2 = { id: 2, status: 'confirmed', metamaskNetworkId: otherNetworkId, txParams: {} }
|
||||||
|
@ -153,6 +181,37 @@ describe('TransactionStateManager', function () {
|
||||||
assert.equal(result.hash, 'foo')
|
assert.equal(result.hash, 'foo')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('throws error and does not update tx if txParams are invalid', function () {
|
||||||
|
const validTxParams = {
|
||||||
|
from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc',
|
||||||
|
to: '0x0039f22efb07a647557c7c5d17854cfd6d489ef3',
|
||||||
|
nonce: '0x3',
|
||||||
|
gas: '0x77359400',
|
||||||
|
gasPrice: '0x77359400',
|
||||||
|
value: '0x0',
|
||||||
|
data: '0x0',
|
||||||
|
}
|
||||||
|
const invalidValues = [1, true, {}, Symbol('1')]
|
||||||
|
|
||||||
|
txStateManager.addTx({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: validTxParams })
|
||||||
|
|
||||||
|
for (const key in validTxParams) {
|
||||||
|
for (const value of invalidValues) {
|
||||||
|
const originalTx = txStateManager.getTx(1)
|
||||||
|
const newTx = {
|
||||||
|
...originalTx,
|
||||||
|
txParams: {
|
||||||
|
...originalTx.txParams,
|
||||||
|
[key]: value,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
assert.throws(txStateManager.updateTx.bind(txStateManager, newTx), 'updateTx should throw an error')
|
||||||
|
const result = txStateManager.getTx(1)
|
||||||
|
assert.deepEqual(result, originalTx, 'tx should not be updated')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
it('updates gas price and adds history items', function () {
|
it('updates gas price and adds history items', function () {
|
||||||
const originalGasPrice = '0x01'
|
const originalGasPrice = '0x01'
|
||||||
const desiredGasPrice = '0x02'
|
const desiredGasPrice = '0x02'
|
||||||
|
@ -241,6 +300,8 @@ describe('TransactionStateManager', function () {
|
||||||
assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
|
assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
|
||||||
filterParams = { to: '0xaa' }
|
filterParams = { to: '0xaa' }
|
||||||
assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
|
assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
|
||||||
|
filterParams = { status: (status) => status !== 'confirmed' }
|
||||||
|
assert.equal(txStateManager.getFilteredTxList(filterParams).length, 5, `getFilteredTxList - ${JSON.stringify(filterParams)}`)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -283,18 +344,17 @@ describe('TransactionStateManager', function () {
|
||||||
|
|
||||||
assert.equal(txsFromCurrentNetworkAndAddress.length, 0)
|
assert.equal(txsFromCurrentNetworkAndAddress.length, 0)
|
||||||
assert.equal(txFromOtherNetworks.length, 2)
|
assert.equal(txFromOtherNetworks.length, 2)
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#_removeTx', function () {
|
describe('#_removeTx', function () {
|
||||||
it('should remove the transaction from the storage', () => {
|
it('should remove the transaction from the storage', function () {
|
||||||
txStateManager._saveTxList([ { id: 1 } ])
|
txStateManager._saveTxList([ { id: 1 } ])
|
||||||
txStateManager._removeTx(1)
|
txStateManager._removeTx(1)
|
||||||
assert(!txStateManager.getFullTxList().length, 'txList should be empty')
|
assert(!txStateManager.getFullTxList().length, 'txList should be empty')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should only remove the transaction with ID 1 from the storage', () => {
|
it('should only remove the transaction with ID 1 from the storage', function () {
|
||||||
txStateManager._saveTxList([ { id: 1 }, { id: 2 } ])
|
txStateManager._saveTxList([ { id: 1 }, { id: 2 } ])
|
||||||
txStateManager._removeTx(1)
|
txStateManager._removeTx(1)
|
||||||
assert.equal(txStateManager.getFullTxList()[0].id, 2, 'txList should have a id of 2')
|
assert.equal(txStateManager.getFullTxList()[0].id, 2, 'txList should have a id of 2')
|
||||||
|
|
Loading…
Reference in New Issue