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:
commit
6c83ba762e
19
CHANGELOG.md
19
CHANGELOG.md
@ -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.
|
||||||
|
@ -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",
|
||||||
|
@ -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({
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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.')
|
||||||
|
83
app/scripts/migrations/019.js
Normal file
83
app/scripts/migrations/019.js
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -29,4 +29,5 @@ module.exports = [
|
|||||||
require('./016'),
|
require('./016'),
|
||||||
require('./017'),
|
require('./017'),
|
||||||
require('./018'),
|
require('./018'),
|
||||||
|
require('./019'),
|
||||||
]
|
]
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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",
|
||||||
|
@ -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
40
test/lib/mock-tx-gen.js
Normal 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
|
@ -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,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
26
test/unit/tx-state-history-helper-test.js
Normal file
26
test/unit/tx-state-history-helper-test.js
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
@ -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),
|
||||||
|
@ -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`
|
||||||
|
@ -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, {
|
||||||
|
@ -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'),
|
||||||
]),
|
]),
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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 = ''
|
||||||
|
Loading…
Reference in New Issue
Block a user