diff options
author | frankiebee <frankie.diamond@gmail.com> | 2017-09-07 04:45:03 +0800 |
---|---|---|
committer | frankiebee <frankie.diamond@gmail.com> | 2017-09-07 04:45:03 +0800 |
commit | 6c83ba762e73419c6db444dde05b3ebe31d7028e (patch) | |
tree | 0a1655190af8d8076de514452a70a8b54d27d371 | |
parent | 15c12ca4bb6996f43518630ded12c0d7be2d571b (diff) | |
parent | 47340ca550cf8bc6738a6970e10b599416da3a39 (diff) | |
download | tangerine-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.md | 19 | ||||
-rw-r--r-- | app/manifest.json | 2 | ||||
-rw-r--r-- | app/scripts/controllers/transactions.js | 13 | ||||
-rw-r--r-- | app/scripts/lib/auto-reload.js | 62 | ||||
-rw-r--r-- | app/scripts/lib/inpage-provider.js | 11 | ||||
-rw-r--r-- | app/scripts/lib/nonce-tracker.js | 107 | ||||
-rw-r--r-- | app/scripts/lib/pending-tx-tracker.js | 8 | ||||
-rw-r--r-- | app/scripts/migrations/019.js | 83 | ||||
-rw-r--r-- | app/scripts/migrations/index.js | 1 | ||||
-rw-r--r-- | development/uiStore.js | 4 | ||||
-rw-r--r-- | mascara/src/lib/setup-iframe.js | 4 | ||||
-rw-r--r-- | mascara/src/proxy.js | 4 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | test/lib/mock-store.js | 4 | ||||
-rw-r--r-- | test/lib/mock-tx-gen.js | 40 | ||||
-rw-r--r-- | test/unit/nonce-tracker-test.js | 207 | ||||
-rw-r--r-- | test/unit/tx-state-history-helper-test.js | 26 | ||||
-rw-r--r-- | ui/app/components/pending-msg.js | 15 | ||||
-rw-r--r-- | ui/app/components/token-list.js | 2 | ||||
-rw-r--r-- | ui/app/components/transaction-list-item.js | 11 | ||||
-rw-r--r-- | ui/app/info.js | 2 | ||||
-rw-r--r-- | ui/app/reducers.js | 5 | ||||
-rw-r--r-- | ui/lib/account-link.js | 10 |
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 = '' |