Merge pull request #339 from poanetwork/refactor

Refactoring 3 before EIP - 1193
This commit is contained in:
Victor Baranov 2020-03-28 00:09:29 +03:00 committed by GitHub
commit 2d6753d182
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 241 additions and 535 deletions

View File

@ -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,

View File

@ -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

View File

@ -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,29 +31,33 @@ 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
this.getNetwork = getNetwork this.getNetwork = getNetwork
} }
/** /**
@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
@ -376,9 +423,9 @@ class TransactionStateManager extends EventEmitter {
// Update state // Update state
this._saveTxList(otherAccountTxs) this._saveTxList(otherAccountTxs)
} }
// //
// PRIVATE METHODS // PRIVATE METHODS
// //
// STATUS METHODS // STATUS METHODS
// statuses: // statuses:
@ -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

View File

@ -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

15
package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -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')
}) })

View File

@ -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,
})
}

View File

@ -417,28 +417,27 @@ 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') assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same')
assert.equal(txMeta.txParams.data, txParams.data, 'data should be the same') assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`')
assert.ok(('lastGasPrice' in txMeta), 'should have the key `lastGasPrice`') assert.equal(txController.txStateManager.getTxList().length, 2)
assert.equal(txController.txStateManager.getTxList().length, 2) done()
done() }).catch(done)
}).catch(done)
}) })
}) })

View File

@ -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')
@ -43,7 +43,7 @@ describe('TransactionStateManager', function () {
}) })
describe('#setTxStatusRejected', function () { describe('#setTxStatusRejected', function () {
it('sets the tx status to rejected and removes it from history', function () { it('sets the tx status to rejected and removes it from history', function () {
const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} } const tx = { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }
txStateManager.addTx(tx) txStateManager.addTx(tx)
txStateManager.setTxStatusRejected(1) txStateManager.setTxStatusRejected(1)
@ -52,15 +52,12 @@ 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) { assert(true, 'event listener has been triggered and noop executed')
console.log('Error: ', err) done()
}
assert(true, 'event listener has been triggered and noop executed')
done()
} }
txStateManager.on('1:rejected', noop) txStateManager.on('1:rejected', noop)
txStateManager.setTxStatusRejected(1) txStateManager.setTxStatusRejected(1)
@ -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,19 +344,18 @@ 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')
}) })