aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorfrankiebee <frankie.diamond@gmail.com>2017-09-07 04:45:03 +0800
committerfrankiebee <frankie.diamond@gmail.com>2017-09-07 04:45:03 +0800
commit6c83ba762e73419c6db444dde05b3ebe31d7028e (patch)
tree0a1655190af8d8076de514452a70a8b54d27d371
parent15c12ca4bb6996f43518630ded12c0d7be2d571b (diff)
parent47340ca550cf8bc6738a6970e10b599416da3a39 (diff)
downloadtangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.tar
tangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.tar.gz
tangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.tar.bz2
tangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.tar.lz
tangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.tar.xz
tangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.tar.zst
tangerine-wallet-browser-6c83ba762e73419c6db444dde05b3ebe31d7028e.zip
Merge branch 'master' into transactionControllerRefractorPt3
-rw-r--r--CHANGELOG.md19
-rw-r--r--app/manifest.json2
-rw-r--r--app/scripts/controllers/transactions.js13
-rw-r--r--app/scripts/lib/auto-reload.js62
-rw-r--r--app/scripts/lib/inpage-provider.js11
-rw-r--r--app/scripts/lib/nonce-tracker.js107
-rw-r--r--app/scripts/lib/pending-tx-tracker.js8
-rw-r--r--app/scripts/migrations/019.js83
-rw-r--r--app/scripts/migrations/index.js1
-rw-r--r--development/uiStore.js4
-rw-r--r--mascara/src/lib/setup-iframe.js4
-rw-r--r--mascara/src/proxy.js4
-rw-r--r--package.json2
-rw-r--r--test/lib/mock-store.js4
-rw-r--r--test/lib/mock-tx-gen.js40
-rw-r--r--test/unit/nonce-tracker-test.js207
-rw-r--r--test/unit/tx-state-history-helper-test.js26
-rw-r--r--ui/app/components/pending-msg.js15
-rw-r--r--ui/app/components/token-list.js2
-rw-r--r--ui/app/components/transaction-list-item.js11
-rw-r--r--ui/app/info.js2
-rw-r--r--ui/app/reducers.js5
-rw-r--r--ui/lib/account-link.js10
23 files changed, 527 insertions, 115 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 157af097b..aa9e1ab6c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
## Current Master
+- Make eth_sign deprecation warning less noisy
+- Add useful link to eth_sign deprecation warning.
+- Fix bug with network version serialization over synchronous RPC
+- Add MetaMask version to state logs.
+- Add the total amount of tokens when multiple tokens are added under the token list
+- Use HTTPS links for Etherscan.
+- Update Support center link to new one with HTTPS.
+- Make web3 deprecation notice more useful by linking to a descriptive article.
+
+## 3.9.11 2017-8-24
+
+- Fix nonce calculation bug that would sometimes generate very wrong nonces.
+- Give up resubmitting a transaction after 3500 blocks.
+
+## 3.9.10 2017-8-23
+
+- Improve nonce calculation, to prevent bug where people are unable to send transactions reliably.
+- Remove link to eth-tx-viz from identicons in tx history.
+
## 3.9.9 2017-8-18
- Fix bug where some transaction submission errors would show an empty screen.
diff --git a/app/manifest.json b/app/manifest.json
index 293981d70..7ec32ea78 100644
--- a/app/manifest.json
+++ b/app/manifest.json
@@ -1,7 +1,7 @@
{
"name": "MetaMask",
"short_name": "Metamask",
- "version": "3.9.9",
+ "version": "3.9.11",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "Ethereum Browser Extension",
diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js
index 6c4701283..3f2a7ce16 100644
--- a/app/scripts/controllers/transactions.js
+++ b/app/scripts/controllers/transactions.js
@@ -55,6 +55,17 @@ module.exports = class TransactionController extends EventEmitter {
status: 'submitted',
})
},
+ getConfirmedTransactions: (address) => {
+ return this.getFilteredTxList({
+ from: address,
+ status: 'confirmed',
+ err: undefined,
+ })
+ },
+ giveUpOnTransaction: (txId) => {
+ const msg = `Gave up submitting after 3500 blocks un-mined.`
+ this.setTxStatusFailed(txId, msg)
+ },
})
this.pendingTxTracker = new PendingTransactionTracker({
@@ -259,4 +270,4 @@ module.exports = class TransactionController extends EventEmitter {
})
this.memStore.updateState({ unapprovedTxs, selectedAddressTxList })
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js
index 6abce73ea..cce31c3d2 100644
--- a/app/scripts/lib/auto-reload.js
+++ b/app/scripts/lib/auto-reload.js
@@ -2,33 +2,55 @@ module.exports = setupDappAutoReload
function setupDappAutoReload (web3, observable) {
// export web3 as a global, checking for usage
+ let hasBeenWarned = false
+ let reloadInProgress = false
+ let lastTimeUsed
+ let lastSeenNetwork
+
global.web3 = new Proxy(web3, {
- get: (_web3, name) => {
- // get the time of use
- if (name !== '_used') {
- console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/ethereum/mist/releases/tag/v0.9.0')
- _web3._used = Date.now()
+ get: (_web3, key) => {
+ // show warning once on web3 access
+ if (!hasBeenWarned && key !== 'currentProvider') {
+ console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
+ hasBeenWarned = true
}
- return _web3[name]
+ // get the time of use
+ lastTimeUsed = Date.now()
+ // return value normally
+ return _web3[key]
},
- set: (_web3, name, value) => {
- _web3[name] = value
+ set: (_web3, key, value) => {
+ // set value normally
+ _web3[key] = value
},
})
- var networkVersion
observable.subscribe(function (state) {
- // get the initial network
- const curentNetVersion = state.networkVersion
- if (!networkVersion) networkVersion = curentNetVersion
-
- if (curentNetVersion !== networkVersion && web3._used) {
- const timeSinceUse = Date.now() - web3._used
- // if web3 was recently used then delay the reloading of the page
- timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500)
- // prevent reentry into if statement if state updates again before
- // reload
- networkVersion = curentNetVersion
+ // if reload in progress, no need to check reload logic
+ if (reloadInProgress) return
+
+ const currentNetwork = state.networkVersion
+
+ // set the initial network
+ if (!lastSeenNetwork) {
+ lastSeenNetwork = currentNetwork
+ return
+ }
+
+ // skip reload logic if web3 not used
+ if (!lastTimeUsed) return
+
+ // if network did not change, exit
+ if (currentNetwork === lastSeenNetwork) return
+
+ // initiate page reload
+ reloadInProgress = true
+ const timeSinceUse = Date.now() - lastTimeUsed
+ // if web3 was recently used then delay the reloading of the page
+ if (timeSinceUse > 500) {
+ triggerReset()
+ } else {
+ setTimeout(triggerReset, 500)
}
})
}
diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js
index fd032a673..c63af06dc 100644
--- a/app/scripts/lib/inpage-provider.js
+++ b/app/scripts/lib/inpage-provider.js
@@ -27,7 +27,7 @@ function MetamaskInpageProvider (connectionStream) {
)
// ignore phishing warning message (handled elsewhere)
- multiStream.ignoreStream('phishing')
+ multiStream.ignoreStream('phishing')
// connect to async provider
const asyncProvider = self.asyncProvider = new StreamProvider()
@@ -43,8 +43,9 @@ function MetamaskInpageProvider (connectionStream) {
// handle sendAsync requests via asyncProvider
self.sendAsync = function (payload, cb) {
// rewrite request ids
- var request = eachJsonMessage(payload, (message) => {
- var newId = createRandomId()
+ var request = eachJsonMessage(payload, (_message) => {
+ const message = Object.assign({}, _message)
+ const newId = createRandomId()
self.idMap[newId] = message.id
message.id = newId
return message
@@ -80,7 +81,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'eth_coinbase':
// read from localStorage
selectedAddress = self.publicConfigStore.getState().selectedAddress
- result = selectedAddress
+ result = selectedAddress || null
break
case 'eth_uninstallFilter':
@@ -90,7 +91,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'net_version':
const networkVersion = self.publicConfigStore.getState().networkVersion
- result = networkVersion
+ result = networkVersion || null
break
// throw not-supported Error
diff --git a/app/scripts/lib/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js
index 8328e81ec..0029ac953 100644
--- a/app/scripts/lib/nonce-tracker.js
+++ b/app/scripts/lib/nonce-tracker.js
@@ -1,13 +1,14 @@
-const EthQuery = require('eth-query')
+const EthQuery = require('ethjs-query')
const assert = require('assert')
const Mutex = require('await-semaphore').Mutex
class NonceTracker {
- constructor ({ provider, getPendingTransactions }) {
+ constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
this.provider = provider
this.ethQuery = new EthQuery(provider)
this.getPendingTransactions = getPendingTransactions
+ this.getConfirmedTransactions = getConfirmedTransactions
this.lockMap = {}
}
@@ -25,21 +26,28 @@ class NonceTracker {
await this._globalMutexFree()
// await lock free, then take lock
const releaseLock = await this._takeMutex(address)
- // calculate next nonce
- // we need to make sure our base count
- // and pending count are from the same block
- const currentBlock = await this._getCurrentBlock()
- const pendingTransactions = this.getPendingTransactions(address)
- const pendingCount = pendingTransactions.length
- assert(Number.isInteger(pendingCount), `nonce-tracker - pendingCount is not an integer - got: (${typeof pendingCount}) "${pendingCount}"`)
- const baseCountHex = await this._getTxCount(address, currentBlock)
- const baseCount = parseInt(baseCountHex, 16)
- assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`)
- const nextNonce = baseCount + pendingCount
+ // evaluate multiple nextNonce strategies
+ const nonceDetails = {}
+ const networkNonceResult = await this._getNetworkNextNonce(address)
+ const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
+ const nextNetworkNonce = networkNonceResult.nonce
+ const highestLocalNonce = highestLocallyConfirmed
+ const highestSuggested = Math.max(nextNetworkNonce, highestLocalNonce)
+
+ const pendingTxs = this.getPendingTransactions(address)
+ const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
+
+ nonceDetails.params = {
+ highestLocalNonce,
+ 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}"`)
- // collect the numbers used to calculate the nonce for debugging
- const blockNumber = currentBlock.number
- const nonceDetails = { blockNumber, baseCount, baseCountHex, pendingCount }
+
// return nonce and release cb
return { nextNonce, nonceDetails, releaseLock }
}
@@ -53,15 +61,6 @@ class NonceTracker {
})
}
- async _getTxCount (address, currentBlock) {
- const blockNumber = currentBlock.number
- return new Promise((resolve, reject) => {
- this.ethQuery.getTransactionCount(address, blockNumber, (err, result) => {
- err ? reject(err) : resolve(result)
- })
- })
- }
-
async _globalMutexFree () {
const globalMutex = this._lookupMutex('global')
const release = await globalMutex.acquire()
@@ -83,12 +82,68 @@ class NonceTracker {
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 currentBlock = await this._getCurrentBlock()
+ const blockNumber = currentBlock.blockNumber
+ const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber || 'latest')
+ 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
+ }
+
+ _reduceTxListToUniqueNonces (txList) {
+ const reducedTxList = txList.reduce((reducedList, txMeta, index) => {
+ if (!index) return [txMeta]
+ const nonceMatches = txList.filter((txData) => {
+ return txMeta.txParams.nonce === txData.txParams.nonce
+ })
+ if (nonceMatches.length > 1) return reducedList
+ reducedList.push(txMeta)
+ return reducedList
+ }, [])
+ return reducedTxList
+ }
+
+ _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
+ }
+
+ _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 } }
+ }
+
// this is a hotfix for the fact that the blockTracker will
// change when the network changes
_getBlockTracker () {
return this.provider._blockTracker
}
-
}
module.exports = NonceTracker
diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js
index 19720db3f..b90851b58 100644
--- a/app/scripts/lib/pending-tx-tracker.js
+++ b/app/scripts/lib/pending-tx-tracker.js
@@ -1,6 +1,7 @@
const EventEmitter = require('events')
const EthQuery = require('ethjs-query')
const sufficientBalance = require('./util').sufficientBalance
+const RETRY_LIMIT = 3500 // Retry 3500 blocks, or about 1 day.
/*
Utility class for tracking the transactions as they
@@ -28,6 +29,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.getBalance = config.getBalance
this.getPendingTransactions = config.getPendingTransactions
this.publishTransaction = config.publishTransaction
+ this.giveUpOnTransaction = config.giveUpOnTransaction
}
// checks if a signed tx is in a block and
@@ -100,6 +102,10 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
if (balance === undefined) return
if (!('retryCount' in txMeta)) txMeta.retryCount = 0
+ if (txMeta.retryCount > RETRY_LIMIT) {
+ return this.giveUpOnTransaction(txMeta.id)
+ }
+
// if the value of the transaction is greater then the balance, fail.
if (!sufficientBalance(txMeta.txParams, balance)) {
const insufficientFundsError = new Error('Insufficient balance during rebroadcast.')
@@ -160,4 +166,4 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
}
nonceGlobalLock.releaseLock()
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/migrations/019.js b/app/scripts/migrations/019.js
new file mode 100644
index 000000000..072c96370
--- /dev/null
+++ b/app/scripts/migrations/019.js
@@ -0,0 +1,83 @@
+
+const version = 19
+
+/*
+
+This migration sets transactions as failed
+whos nonce is too high
+
+*/
+
+const clone = require('clone')
+
+module.exports = {
+ version,
+
+ migrate: function (originalVersionedData) {
+ const versionedData = clone(originalVersionedData)
+ versionedData.meta.version = version
+ try {
+ const state = versionedData.data
+ const newState = transformState(state)
+ versionedData.data = newState
+ } catch (err) {
+ console.warn(`MetaMask Migration #${version}` + err.stack)
+ }
+ return Promise.resolve(versionedData)
+ },
+}
+
+function transformState (state) {
+ const newState = state
+ const transactions = newState.TransactionController.transactions
+
+ newState.TransactionController.transactions = transactions.map((txMeta, _, txList) => {
+ if (txMeta.status !== 'submitted') return txMeta
+
+ const confirmedTxs = txList.filter((tx) => tx.status === 'confirmed')
+ .filter((tx) => tx.txParams.from === txMeta.txParams.from)
+ .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
+ const highestConfirmedNonce = getHighestNonce(confirmedTxs)
+
+ const pendingTxs = txList.filter((tx) => tx.status === 'submitted')
+ .filter((tx) => tx.txParams.from === txMeta.txParams.from)
+ .filter((tx) => tx.metamaskNetworkId.from === txMeta.metamaskNetworkId.from)
+ const highestContinuousNonce = getHighestContinuousFrom(pendingTxs, highestConfirmedNonce)
+
+ const maxNonce = Math.max(highestContinuousNonce, highestConfirmedNonce)
+
+ if (parseInt(txMeta.txParams.nonce, 16) > maxNonce + 1) {
+ txMeta.status = 'failed'
+ txMeta.err = {
+ message: 'nonce too high',
+ note: 'migration 019 custom error',
+ }
+ }
+ return txMeta
+ })
+ return newState
+}
+
+function getHighestContinuousFrom (txList, startPoint) {
+ const nonces = txList.map((txMeta) => {
+ const nonce = txMeta.txParams.nonce
+ return parseInt(nonce, 16)
+ })
+
+ let highest = startPoint
+ while (nonces.includes(highest)) {
+ highest++
+ }
+
+ return highest
+}
+
+function getHighestNonce (txList) {
+ const nonces = txList.map((txMeta) => {
+ const nonce = txMeta.txParams.nonce
+ return parseInt(nonce || '0x0', 16)
+ })
+ const highestNonce = Math.max.apply(null, nonces)
+ return highestNonce
+}
+
diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js
index 6bfc622f7..e9cbd7b98 100644
--- a/app/scripts/migrations/index.js
+++ b/app/scripts/migrations/index.js
@@ -29,4 +29,5 @@ module.exports = [
require('./016'),
require('./017'),
require('./018'),
+ require('./019'),
]
diff --git a/development/uiStore.js b/development/uiStore.js
index 1299ee1dc..c71d66d3b 100644
--- a/development/uiStore.js
+++ b/development/uiStore.js
@@ -1,7 +1,7 @@
const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware
-const thunkMiddleware = require('redux-thunk')
-const createLogger = require('redux-logger')
+const thunkMiddleware = require('redux-thunk').default
+const createLogger = require('redux-logger').createLogger
const rootReducer = require('../ui/app/reducers')
module.exports = configureStore
diff --git a/mascara/src/lib/setup-iframe.js b/mascara/src/lib/setup-iframe.js
index db67163df..dcf404574 100644
--- a/mascara/src/lib/setup-iframe.js
+++ b/mascara/src/lib/setup-iframe.js
@@ -1,5 +1,5 @@
const Iframe = require('iframe')
-const IframeStream = require('iframe-stream').IframeStream
+const createIframeStream = require('iframe-stream').IframeStream
module.exports = setupIframe
@@ -13,7 +13,7 @@ function setupIframe(opts) {
})
var iframe = frame.iframe
iframe.style.setProperty('display', 'none')
- var iframeStream = new IframeStream(iframe)
+ var iframeStream = createIframeStream(iframe)
return iframeStream
}
diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js
index eabc547b4..5b95175f1 100644
--- a/mascara/src/proxy.js
+++ b/mascara/src/proxy.js
@@ -1,4 +1,4 @@
-const ParentStream = require('iframe-stream').ParentStream
+const createParentStream = require('iframe-stream').ParentStream
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
const SwStream = require('sw-stream/lib/sw-stream.js')
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js')
@@ -11,7 +11,7 @@ const background = new SWcontroller({
intervalDelay,
})
-const pageStream = new ParentStream()
+const pageStream = createParentStream()
background.on('ready', (_) => {
let swStream = SwStream({
serviceWorker: background.controller,
diff --git a/package.json b/package.json
index 9a09e6305..9afc181a3 100644
--- a/package.json
+++ b/package.json
@@ -186,7 +186,7 @@
"react-addons-test-utils": "^15.5.1",
"react-test-renderer": "^15.5.4",
"react-testutils-additions": "^15.2.0",
- "sinon": "^2.3.8",
+ "sinon": "^3.2.0",
"tape": "^4.5.1",
"testem": "^1.10.3",
"uglifyify": "^4.0.2",
diff --git a/test/lib/mock-store.js b/test/lib/mock-store.js
index 0d50e2d9c..8af8f6d23 100644
--- a/test/lib/mock-store.js
+++ b/test/lib/mock-store.js
@@ -1,7 +1,7 @@
const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware
-const thunkMiddleware = require('redux-thunk')
-const createLogger = require('redux-logger')
+const thunkMiddleware = require('redux-thunk').default
+const createLogger = require('redux-logger').createLogger
const rootReducer = function () {}
module.exports = configureStore
diff --git a/test/lib/mock-tx-gen.js b/test/lib/mock-tx-gen.js
new file mode 100644
index 000000000..7aea09c59
--- /dev/null
+++ b/test/lib/mock-tx-gen.js
@@ -0,0 +1,40 @@
+const extend = require('xtend')
+const BN = require('ethereumjs-util').BN
+const template = {
+ 'status': 'submitted',
+ 'txParams': {
+ 'from': '0x7d3517b0d011698406d6e0aed8453f0be2697926',
+ 'gas': '0x30d40',
+ 'value': '0x0',
+ 'nonce': '0x3',
+ },
+}
+
+class TxGenerator {
+
+ constructor () {
+ this.txs = []
+ }
+
+ generate (tx = {}, opts = {}) {
+ let { count, fromNonce } = opts
+ let nonce = fromNonce || this.txs.length
+ let txs = []
+ for (let i = 0; i < count; i++) {
+ txs.push(extend(template, {
+ txParams: {
+ nonce: hexify(nonce++),
+ }
+ }, tx))
+ }
+ this.txs = this.txs.concat(txs)
+ return txs
+ }
+
+}
+
+function hexify (number) {
+ return '0x' + (new BN(number)).toString(16)
+}
+
+module.exports = TxGenerator
diff --git a/test/unit/nonce-tracker-test.js b/test/unit/nonce-tracker-test.js
index 36025a360..3312a3bd0 100644
--- a/test/unit/nonce-tracker-test.js
+++ b/test/unit/nonce-tracker-test.js
@@ -1,41 +1,184 @@
const assert = require('assert')
const NonceTracker = require('../../app/scripts/lib/nonce-tracker')
+const MockTxGen = require('../lib/mock-tx-gen')
+let providerResultStub = {}
describe('Nonce Tracker', function () {
- let nonceTracker, provider, getPendingTransactions, pendingTxs
-
-
- beforeEach(function () {
- pendingTxs = [{
- 'status': 'submitted',
- 'txParams': {
- 'from': '0x7d3517b0d011698406d6e0aed8453f0be2697926',
- 'gas': '0x30d40',
- 'value': '0x0',
- 'nonce': '0x0',
- },
- }]
-
-
- getPendingTransactions = () => pendingTxs
- provider = {
- sendAsync: (_, cb) => { cb(undefined, {result: '0x0'}) },
- _blockTracker: {
- getCurrentBlock: () => '0x11b568',
- },
- }
- nonceTracker = new NonceTracker({
- provider,
- getPendingTransactions,
- })
- })
+ let nonceTracker, provider
+ let getPendingTransactions, pendingTxs
+ let getConfirmedTransactions, confirmedTxs
describe('#getNonceLock', function () {
- it('should work', async function () {
- this.timeout(15000)
- const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926')
- assert.equal(nonceLock.nextNonce, '1', 'nonce should be 1')
- await nonceLock.releaseLock()
+
+ 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('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()
+ const confirmedTxs = txGen.generate({ status: 'confirmed' }, { count: 10 })
+ const 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()
+ })
})
})
})
+
+function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') {
+ const getPendingTransactions = () => pending
+ const getConfirmedTransactions = () => confirmed
+ providerResultStub.result = providerStub
+ const provider = {
+ sendAsync: (_, cb) => { cb(undefined, providerResultStub) },
+ _blockTracker: {
+ getCurrentBlock: () => '0x11b568',
+ },
+ }
+ return new NonceTracker({
+ provider,
+ getPendingTransactions,
+ getConfirmedTransactions,
+ })
+}
+
diff --git a/test/unit/tx-state-history-helper-test.js b/test/unit/tx-state-history-helper-test.js
new file mode 100644
index 000000000..90cb10713
--- /dev/null
+++ b/test/unit/tx-state-history-helper-test.js
@@ -0,0 +1,26 @@
+const assert = require('assert')
+const clone = require('clone')
+const txStateHistoryHelper = require('../../app/scripts/lib/tx-state-history-helper')
+
+describe('deepCloneFromTxMeta', function () {
+ it('should clone deep', function () {
+ const input = {
+ foo: {
+ bar: {
+ bam: 'baz'
+ }
+ }
+ }
+ const output = txStateHistoryHelper.snapshotFromTxMeta(input)
+ assert('foo' in output, 'has a foo key')
+ assert('bar' in output.foo, 'has a bar key')
+ assert('bam' in output.foo.bar, 'has a bar key')
+ assert.equal(output.foo.bar.bam, 'baz', 'has a baz value')
+ })
+
+ it('should remove the history key', function () {
+ const input = { foo: 'bar', history: 'remembered' }
+ const output = txStateHistoryHelper.snapshotFromTxMeta(input)
+ assert(typeof output.history, 'undefined', 'should remove history')
+ })
+})
diff --git a/ui/app/components/pending-msg.js b/ui/app/components/pending-msg.js
index b7133cda8..834719c53 100644
--- a/ui/app/components/pending-msg.js
+++ b/ui/app/components/pending-msg.js
@@ -35,10 +35,21 @@ PendingMsg.prototype.render = function () {
style: {
margin: '10px',
},
- }, `Signing this message can have
+ }, [
+ `Signing this message can have
dangerous side effects. Only sign messages from
sites you fully trust with your entire account.
- This dangerous method will be removed in a future version.`),
+ This dangerous method will be removed in a future version. `,
+ h('a', {
+ href: 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527',
+ style: { color: 'rgb(247, 134, 28)' },
+ onClick: (event) => {
+ event.preventDefault()
+ const url = 'https://medium.com/metamask/the-new-secure-way-to-sign-data-in-your-browser-6af9dd2a1527'
+ global.platform.openWindow({ url })
+ },
+ }, 'Read more here.'),
+ ]),
// message details
h(PendingTxDetails, state),
diff --git a/ui/app/components/token-list.js b/ui/app/components/token-list.js
index 2346568bc..998ec901d 100644
--- a/ui/app/components/token-list.js
+++ b/ui/app/components/token-list.js
@@ -95,7 +95,7 @@ TokenList.prototype.renderTokenStatusBar = function () {
let msg
if (tokens.length === 1) {
msg = `You own 1 token`
- } else if (tokens.length === 1) {
+ } else if (tokens.length > 1) {
msg = `You own ${tokens.length} tokens`
} else {
msg = `No tokens found`
diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js
index 9018bab06..5d5d0bcc5 100644
--- a/ui/app/components/transaction-list-item.js
+++ b/ui/app/components/transaction-list-item.js
@@ -60,16 +60,7 @@ TransactionListItem.prototype.render = function () {
}, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [
- h('.pop-hover', {
- onClick: (event) => {
- event.stopPropagation()
- if (!isTx || isPending) return
- var url = `https://metamask.github.io/eth-tx-viz/?tx=${transaction.hash}`
- global.platform.openWindow({ url })
- },
- }, [
- h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
- ]),
+ h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
]),
h(Tooltip, {
diff --git a/ui/app/info.js b/ui/app/info.js
index 899841c83..c69d83715 100644
--- a/ui/app/info.js
+++ b/ui/app/info.js
@@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () {
[
h('div.fa.fa-support', [
h('a.info', {
- href: 'http://metamask.consensyssupport.happyfox.com',
+ href: 'https://support.metamask.com',
target: '_blank',
}, 'Visit our Support Center'),
]),
diff --git a/ui/app/reducers.js b/ui/app/reducers.js
index 36045772f..6a2f44534 100644
--- a/ui/app/reducers.js
+++ b/ui/app/reducers.js
@@ -42,7 +42,10 @@ function rootReducer (state, action) {
}
window.logState = function () {
- var stateString = JSON.stringify(window.METAMASK_CACHED_LOG_STATE, removeSeedWords, 2)
+ let state = window.METAMASK_CACHED_LOG_STATE
+ const version = global.platform.getVersion()
+ state.version = version
+ let stateString = JSON.stringify(state, removeSeedWords, 2)
return stateString
}
diff --git a/ui/lib/account-link.js b/ui/lib/account-link.js
index d061d0ad1..037d990fa 100644
--- a/ui/lib/account-link.js
+++ b/ui/lib/account-link.js
@@ -3,19 +3,19 @@ module.exports = function (address, network) {
let link
switch (net) {
case 1: // main net
- link = `http://etherscan.io/address/${address}`
+ link = `https://etherscan.io/address/${address}`
break
case 2: // morden test net
- link = `http://morden.etherscan.io/address/${address}`
+ link = `https://morden.etherscan.io/address/${address}`
break
case 3: // ropsten test net
- link = `http://ropsten.etherscan.io/address/${address}`
+ link = `https://ropsten.etherscan.io/address/${address}`
break
case 4: // rinkeby test net
- link = `http://rinkeby.etherscan.io/address/${address}`
+ link = `https://rinkeby.etherscan.io/address/${address}`
break
case 42: // kovan test net
- link = `http://kovan.etherscan.io/address/${address}`
+ link = `https://kovan.etherscan.io/address/${address}`
break
default:
link = ''