1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'master' into transactionControllerRefractorPt3

This commit is contained in:
frankiebee 2017-09-06 13:45:03 -07:00
commit 6c83ba762e
23 changed files with 526 additions and 114 deletions

View File

@ -2,6 +2,25 @@
## Current Master ## 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 ## 3.9.9 2017-8-18
- Fix bug where some transaction submission errors would show an empty screen. - Fix bug where some transaction submission errors would show an empty screen.

View File

@ -1,7 +1,7 @@
{ {
"name": "MetaMask", "name": "MetaMask",
"short_name": "Metamask", "short_name": "Metamask",
"version": "3.9.9", "version": "3.9.11",
"manifest_version": 2, "manifest_version": 2,
"author": "https://metamask.io", "author": "https://metamask.io",
"description": "Ethereum Browser Extension", "description": "Ethereum Browser Extension",

View File

@ -55,6 +55,17 @@ module.exports = class TransactionController extends EventEmitter {
status: 'submitted', 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({ this.pendingTxTracker = new PendingTransactionTracker({

View File

@ -2,33 +2,55 @@ module.exports = setupDappAutoReload
function setupDappAutoReload (web3, observable) { function setupDappAutoReload (web3, observable) {
// export web3 as a global, checking for usage // export web3 as a global, checking for usage
let hasBeenWarned = false
let reloadInProgress = false
let lastTimeUsed
let lastSeenNetwork
global.web3 = new Proxy(web3, { global.web3 = new Proxy(web3, {
get: (_web3, name) => { get: (_web3, key) => {
// get the time of use // show warning once on web3 access
if (name !== '_used') { if (!hasBeenWarned && key !== 'currentProvider') {
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') 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')
_web3._used = Date.now() hasBeenWarned = true
} }
return _web3[name] // get the time of use
lastTimeUsed = Date.now()
// return value normally
return _web3[key]
}, },
set: (_web3, name, value) => { set: (_web3, key, value) => {
_web3[name] = value // set value normally
_web3[key] = value
}, },
}) })
var networkVersion
observable.subscribe(function (state) { observable.subscribe(function (state) {
// get the initial network // if reload in progress, no need to check reload logic
const curentNetVersion = state.networkVersion if (reloadInProgress) return
if (!networkVersion) networkVersion = curentNetVersion
if (curentNetVersion !== networkVersion && web3._used) { const currentNetwork = state.networkVersion
const timeSinceUse = Date.now() - web3._used
// if web3 was recently used then delay the reloading of the page // set the initial network
timeSinceUse > 500 ? triggerReset() : setTimeout(triggerReset, 500) if (!lastSeenNetwork) {
// prevent reentry into if statement if state updates again before lastSeenNetwork = currentNetwork
// reload return
networkVersion = curentNetVersion }
// 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)
} }
}) })
} }

View File

@ -43,8 +43,9 @@ function MetamaskInpageProvider (connectionStream) {
// handle sendAsync requests via asyncProvider // handle sendAsync requests via asyncProvider
self.sendAsync = function (payload, cb) { self.sendAsync = function (payload, cb) {
// rewrite request ids // rewrite request ids
var request = eachJsonMessage(payload, (message) => { var request = eachJsonMessage(payload, (_message) => {
var newId = createRandomId() const message = Object.assign({}, _message)
const newId = createRandomId()
self.idMap[newId] = message.id self.idMap[newId] = message.id
message.id = newId message.id = newId
return message return message
@ -80,7 +81,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'eth_coinbase': case 'eth_coinbase':
// read from localStorage // read from localStorage
selectedAddress = self.publicConfigStore.getState().selectedAddress selectedAddress = self.publicConfigStore.getState().selectedAddress
result = selectedAddress result = selectedAddress || null
break break
case 'eth_uninstallFilter': case 'eth_uninstallFilter':
@ -90,7 +91,7 @@ MetamaskInpageProvider.prototype.send = function (payload) {
case 'net_version': case 'net_version':
const networkVersion = self.publicConfigStore.getState().networkVersion const networkVersion = self.publicConfigStore.getState().networkVersion
result = networkVersion result = networkVersion || null
break break
// throw not-supported Error // throw not-supported Error

View File

@ -1,13 +1,14 @@
const EthQuery = require('eth-query') const EthQuery = require('ethjs-query')
const assert = require('assert') const assert = require('assert')
const Mutex = require('await-semaphore').Mutex const Mutex = require('await-semaphore').Mutex
class NonceTracker { class NonceTracker {
constructor ({ provider, getPendingTransactions }) { constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
this.provider = provider this.provider = provider
this.ethQuery = new EthQuery(provider) this.ethQuery = new EthQuery(provider)
this.getPendingTransactions = getPendingTransactions this.getPendingTransactions = getPendingTransactions
this.getConfirmedTransactions = getConfirmedTransactions
this.lockMap = {} this.lockMap = {}
} }
@ -25,21 +26,28 @@ class NonceTracker {
await this._globalMutexFree() await this._globalMutexFree()
// await lock free, then take lock // await lock free, then take lock
const releaseLock = await this._takeMutex(address) const releaseLock = await this._takeMutex(address)
// calculate next nonce // evaluate multiple nextNonce strategies
// we need to make sure our base count const nonceDetails = {}
// and pending count are from the same block const networkNonceResult = await this._getNetworkNextNonce(address)
const currentBlock = await this._getCurrentBlock() const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
const pendingTransactions = this.getPendingTransactions(address) const nextNetworkNonce = networkNonceResult.nonce
const pendingCount = pendingTransactions.length const highestLocalNonce = highestLocallyConfirmed
assert(Number.isInteger(pendingCount), `nonce-tracker - pendingCount is not an integer - got: (${typeof pendingCount}) "${pendingCount}"`) const highestSuggested = Math.max(nextNetworkNonce, highestLocalNonce)
const baseCountHex = await this._getTxCount(address, currentBlock)
const baseCount = parseInt(baseCountHex, 16) const pendingTxs = this.getPendingTransactions(address)
assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
const nextNonce = baseCount + pendingCount
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}"`) 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 nonce and release cb
return { nextNonce, nonceDetails, releaseLock } 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 () { async _globalMutexFree () {
const globalMutex = this._lookupMutex('global') const globalMutex = this._lookupMutex('global')
const release = await globalMutex.acquire() const release = await globalMutex.acquire()
@ -83,12 +82,68 @@ class NonceTracker {
return 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 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 // this is a hotfix for the fact that the blockTracker will
// change when the network changes // change when the network changes
_getBlockTracker () { _getBlockTracker () {
return this.provider._blockTracker return this.provider._blockTracker
} }
} }
module.exports = NonceTracker module.exports = NonceTracker

View File

@ -1,6 +1,7 @@
const EventEmitter = require('events') const EventEmitter = require('events')
const EthQuery = require('ethjs-query') const EthQuery = require('ethjs-query')
const sufficientBalance = require('./util').sufficientBalance const sufficientBalance = require('./util').sufficientBalance
const RETRY_LIMIT = 3500 // Retry 3500 blocks, or about 1 day.
/* /*
Utility class for tracking the transactions as they Utility class for tracking the transactions as they
@ -28,6 +29,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter {
this.getBalance = config.getBalance this.getBalance = config.getBalance
this.getPendingTransactions = config.getPendingTransactions this.getPendingTransactions = config.getPendingTransactions
this.publishTransaction = config.publishTransaction this.publishTransaction = config.publishTransaction
this.giveUpOnTransaction = config.giveUpOnTransaction
} }
// checks if a signed tx is in a block and // 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 (balance === undefined) return
if (!('retryCount' in txMeta)) txMeta.retryCount = 0 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 the value of the transaction is greater then the balance, fail.
if (!sufficientBalance(txMeta.txParams, balance)) { if (!sufficientBalance(txMeta.txParams, balance)) {
const insufficientFundsError = new Error('Insufficient balance during rebroadcast.') const insufficientFundsError = new Error('Insufficient balance during rebroadcast.')

View File

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

View File

@ -29,4 +29,5 @@ module.exports = [
require('./016'), require('./016'),
require('./017'), require('./017'),
require('./018'), require('./018'),
require('./019'),
] ]

View File

@ -1,7 +1,7 @@
const createStore = require('redux').createStore const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware const applyMiddleware = require('redux').applyMiddleware
const thunkMiddleware = require('redux-thunk') const thunkMiddleware = require('redux-thunk').default
const createLogger = require('redux-logger') const createLogger = require('redux-logger').createLogger
const rootReducer = require('../ui/app/reducers') const rootReducer = require('../ui/app/reducers')
module.exports = configureStore module.exports = configureStore

View File

@ -1,5 +1,5 @@
const Iframe = require('iframe') const Iframe = require('iframe')
const IframeStream = require('iframe-stream').IframeStream const createIframeStream = require('iframe-stream').IframeStream
module.exports = setupIframe module.exports = setupIframe
@ -13,7 +13,7 @@ function setupIframe(opts) {
}) })
var iframe = frame.iframe var iframe = frame.iframe
iframe.style.setProperty('display', 'none') iframe.style.setProperty('display', 'none')
var iframeStream = new IframeStream(iframe) var iframeStream = createIframeStream(iframe)
return iframeStream return iframeStream
} }

View File

@ -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 SWcontroller = require('client-sw-ready-event/lib/sw-client.js')
const SwStream = require('sw-stream/lib/sw-stream.js') const SwStream = require('sw-stream/lib/sw-stream.js')
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js')
@ -11,7 +11,7 @@ const background = new SWcontroller({
intervalDelay, intervalDelay,
}) })
const pageStream = new ParentStream() const pageStream = createParentStream()
background.on('ready', (_) => { background.on('ready', (_) => {
let swStream = SwStream({ let swStream = SwStream({
serviceWorker: background.controller, serviceWorker: background.controller,

View File

@ -186,7 +186,7 @@
"react-addons-test-utils": "^15.5.1", "react-addons-test-utils": "^15.5.1",
"react-test-renderer": "^15.5.4", "react-test-renderer": "^15.5.4",
"react-testutils-additions": "^15.2.0", "react-testutils-additions": "^15.2.0",
"sinon": "^2.3.8", "sinon": "^3.2.0",
"tape": "^4.5.1", "tape": "^4.5.1",
"testem": "^1.10.3", "testem": "^1.10.3",
"uglifyify": "^4.0.2", "uglifyify": "^4.0.2",

View File

@ -1,7 +1,7 @@
const createStore = require('redux').createStore const createStore = require('redux').createStore
const applyMiddleware = require('redux').applyMiddleware const applyMiddleware = require('redux').applyMiddleware
const thunkMiddleware = require('redux-thunk') const thunkMiddleware = require('redux-thunk').default
const createLogger = require('redux-logger') const createLogger = require('redux-logger').createLogger
const rootReducer = function () {} const rootReducer = function () {}
module.exports = configureStore module.exports = configureStore

40
test/lib/mock-tx-gen.js Normal file
View File

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

View File

@ -1,41 +1,184 @@
const assert = require('assert') const assert = require('assert')
const NonceTracker = require('../../app/scripts/lib/nonce-tracker') const NonceTracker = require('../../app/scripts/lib/nonce-tracker')
const MockTxGen = require('../lib/mock-tx-gen')
let providerResultStub = {}
describe('Nonce Tracker', function () { describe('Nonce Tracker', function () {
let nonceTracker, provider, getPendingTransactions, pendingTxs let nonceTracker, provider
let getPendingTransactions, pendingTxs
let getConfirmedTransactions, confirmedTxs
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,
})
})
describe('#getNonceLock', function () { describe('#getNonceLock', function () {
it('should work', async function () {
this.timeout(15000) describe('with 3 confirmed and 1 pending', function () {
const nonceLock = await nonceTracker.getNonceLock('0x7d3517b0d011698406d6e0aed8453f0be2697926') beforeEach(function () {
assert.equal(nonceLock.nextNonce, '1', 'nonce should be 1') const txGen = new MockTxGen()
await nonceLock.releaseLock() 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,
})
}

View File

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

View File

@ -35,10 +35,21 @@ PendingMsg.prototype.render = function () {
style: { style: {
margin: '10px', margin: '10px',
}, },
}, `Signing this message can have }, [
`Signing this message can have
dangerous side effects. Only sign messages from dangerous side effects. Only sign messages from
sites you fully trust with your entire account. 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 // message details
h(PendingTxDetails, state), h(PendingTxDetails, state),

View File

@ -95,7 +95,7 @@ TokenList.prototype.renderTokenStatusBar = function () {
let msg let msg
if (tokens.length === 1) { if (tokens.length === 1) {
msg = `You own 1 token` msg = `You own 1 token`
} else if (tokens.length === 1) { } else if (tokens.length > 1) {
msg = `You own ${tokens.length} tokens` msg = `You own ${tokens.length} tokens`
} else { } else {
msg = `No tokens found` msg = `No tokens found`

View File

@ -60,16 +60,7 @@ TransactionListItem.prototype.render = function () {
}, [ }, [
h('.identicon-wrapper.flex-column.flex-center.select-none', [ h('.identicon-wrapper.flex-column.flex-center.select-none', [
h('.pop-hover', { h(TransactionIcon, { txParams, transaction, isTx, isMsg }),
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(Tooltip, { h(Tooltip, {

View File

@ -103,7 +103,7 @@ InfoScreen.prototype.render = function () {
[ [
h('div.fa.fa-support', [ h('div.fa.fa-support', [
h('a.info', { h('a.info', {
href: 'http://metamask.consensyssupport.happyfox.com', href: 'https://support.metamask.com',
target: '_blank', target: '_blank',
}, 'Visit our Support Center'), }, 'Visit our Support Center'),
]), ]),

View File

@ -42,7 +42,10 @@ function rootReducer (state, action) {
} }
window.logState = function () { 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 return stateString
} }

View File

@ -3,19 +3,19 @@ module.exports = function (address, network) {
let link let link
switch (net) { switch (net) {
case 1: // main net case 1: // main net
link = `http://etherscan.io/address/${address}` link = `https://etherscan.io/address/${address}`
break break
case 2: // morden test net case 2: // morden test net
link = `http://morden.etherscan.io/address/${address}` link = `https://morden.etherscan.io/address/${address}`
break break
case 3: // ropsten test net case 3: // ropsten test net
link = `http://ropsten.etherscan.io/address/${address}` link = `https://ropsten.etherscan.io/address/${address}`
break break
case 4: // rinkeby test net case 4: // rinkeby test net
link = `http://rinkeby.etherscan.io/address/${address}` link = `https://rinkeby.etherscan.io/address/${address}`
break break
case 42: // kovan test net case 42: // kovan test net
link = `http://kovan.etherscan.io/address/${address}` link = `https://kovan.etherscan.io/address/${address}`
break break
default: default:
link = '' link = ''