diff options
Merge branch 'master' into NewUI-flat
Diffstat (limited to 'app')
-rw-r--r-- | app/scripts/contentscript.js | 12 | ||||
-rw-r--r-- | app/scripts/controllers/network.js | 61 | ||||
-rw-r--r-- | app/scripts/controllers/preferences.js | 8 | ||||
-rw-r--r-- | app/scripts/controllers/recent-blocks.js | 44 | ||||
-rw-r--r-- | app/scripts/controllers/transactions.js | 29 | ||||
-rw-r--r-- | app/scripts/lib/account-tracker.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/tx-gas-utils.js | 6 | ||||
-rw-r--r-- | app/scripts/lib/tx-state-manager.js | 6 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 48 |
9 files changed, 179 insertions, 37 deletions
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index ffbbc73cc..2ed7c87b6 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) { } function shouldInjectWeb3 () { - return doctypeCheck() || suffixCheck() + return doctypeCheck() && suffixCheck() && documentElementCheck() } function doctypeCheck () { @@ -104,7 +104,7 @@ function doctypeCheck () { if (doctype) { return doctype.name === 'html' } else { - return false + return true } } @@ -121,6 +121,14 @@ function suffixCheck () { return true } +function documentElementCheck () { + var documentElement = document.documentElement.nodeName + if (documentElement) { + return documentElement.toLowerCase() === 'html' + } + return true +} + function redirectToPhishingWarning () { console.log('MetaMask - redirecting to phishing warning') window.location.href = 'https://metamask.io/phishing.html' diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 045bfcc5d..377ba6eca 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -1,6 +1,7 @@ const assert = require('assert') const EventEmitter = require('events') const createMetamaskProvider = require('web3-provider-engine/zero.js') +const createInfuraProvider = require('eth-json-rpc-infura/src/createProvider') const ObservableStore = require('obs-store') const ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') @@ -8,6 +9,7 @@ const EthQuery = require('eth-query') const createEventEmitterProxy = require('../lib/events-proxy.js') const RPC_ADDRESS_LIST = require('../config.js').network const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] +const INFURA_PROVIDER_TYPES = ['ropsten', 'rinkeby', 'kovan', 'mainnet'] module.exports = class NetworkController extends EventEmitter { @@ -24,8 +26,13 @@ module.exports = class NetworkController extends EventEmitter { initializeProvider (_providerParams) { this._baseProviderParams = _providerParams - const rpcUrl = this.getCurrentRpcAddress() - this._configureStandardProvider({ rpcUrl }) + const { type, rpcTarget } = this.providerStore.getState() + // map rpcTarget to rpcUrl + const opts = { + type, + rpcUrl: rpcTarget, + } + this._configureProvider(opts) this._proxy.on('block', this._logBlock.bind(this)) this._proxy.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this._proxy) @@ -83,7 +90,7 @@ module.exports = class NetworkController extends EventEmitter { const rpcTarget = this.getRpcAddressForType(type) assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`) this.providerStore.updateState({ type, rpcTarget }) - this._switchNetwork({ rpcUrl: rpcTarget }) + this._switchNetwork({ type }) } getProviderConfig () { @@ -99,14 +106,54 @@ module.exports = class NetworkController extends EventEmitter { // Private // - _switchNetwork (providerParams) { + _switchNetwork (opts) { this.setNetworkState('loading') - this._configureStandardProvider(providerParams) + this._configureProvider(opts) this.emit('networkDidChange') } - _configureStandardProvider (_providerParams) { - const providerParams = extend(this._baseProviderParams, _providerParams) + _configureProvider (opts) { + // type-based rpc endpoints + const { type } = opts + if (type) { + // type-based infura rpc endpoints + const isInfura = INFURA_PROVIDER_TYPES.includes(type) + opts.rpcUrl = this.getRpcAddressForType(type) + if (isInfura) { + this._configureInfuraProvider(opts) + // other type-based rpc endpoints + } else { + this._configureStandardProvider(opts) + } + // url-based rpc endpoints + } else { + this._configureStandardProvider(opts) + } + } + + _configureInfuraProvider (opts) { + log.info('_configureInfuraProvider', opts) + const blockTrackerProvider = createInfuraProvider({ + network: opts.type, + }) + const providerParams = extend(this._baseProviderParams, { + rpcUrl: opts.rpcUrl, + engineParams: { + pollingInterval: 8000, + blockTrackerProvider, + }, + }) + const provider = createMetamaskProvider(providerParams) + this._setProvider(provider) + } + + _configureStandardProvider ({ rpcUrl }) { + const providerParams = extend(this._baseProviderParams, { + rpcUrl, + engineParams: { + pollingInterval: 8000, + }, + }) const provider = createMetamaskProvider(providerParams) this._setProvider(provider) } diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index de9006044..39d15fd83 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -36,22 +36,24 @@ class PreferencesController { return this.store.getState().selectedAddress } - addToken (rawAddress, symbol, decimals) { + async addToken (rawAddress, symbol, decimals) { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } const tokens = this.store.getState().tokens - const previousIndex = tokens.find((token, index) => { + const previousEntry = tokens.find((token, index) => { return token.address === address }) + const previousIndex = tokens.indexOf(previousEntry) - if (previousIndex) { + if (previousEntry) { tokens[previousIndex] = newEntry } else { tokens.push(newEntry) } this.store.updateState({ tokens }) + return Promise.resolve(tokens) } diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js new file mode 100644 index 000000000..4a906261e --- /dev/null +++ b/app/scripts/controllers/recent-blocks.js @@ -0,0 +1,44 @@ +const ObservableStore = require('obs-store') +const extend = require('xtend') + +class RecentBlocksController { + + constructor (opts = {}) { + const { blockTracker } = opts + this.blockTracker = blockTracker + this.historyLength = opts.historyLength || 40 + + const initState = extend({ + recentBlocks: [], + }, opts.initState) + this.store = new ObservableStore(initState) + + this.blockTracker.on('block', this.processBlock.bind(this)) + } + + resetState () { + this.store.updateState({ + recentBlocks: [], + }) + } + + processBlock (newBlock) { + const block = extend(newBlock, { + gasPrices: newBlock.transactions.map((tx) => { + return tx.gasPrice + }), + }) + delete block.transactions + + const state = this.store.getState() + state.recentBlocks.push(block) + + while (state.recentBlocks.length > this.historyLength) { + state.recentBlocks.shift() + } + + this.store.updateState(state) + } +} + +module.exports = RecentBlocksController diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index ce709bd28..f95b5e39a 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -138,18 +138,20 @@ module.exports = class TransactionController extends EventEmitter { async newUnapprovedTransaction (txParams) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) - const txMeta = await this.addUnapprovedTransaction(txParams) - this.emit('newUnapprovedTx', txMeta) + const initialTxMeta = await this.addUnapprovedTransaction(txParams) + this.emit('newUnapprovedTx', initialTxMeta) // listen for tx completion (success, fail) return new Promise((resolve, reject) => { - this.txStateManager.once(`${txMeta.id}:finished`, (completedTx) => { - switch (completedTx.status) { + this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { + switch (finishedTxMeta.status) { case 'submitted': - return resolve(completedTx.hash) + return resolve(finishedTxMeta.hash) case 'rejected': return reject(new Error('MetaMask Tx Signature: User denied transaction signature.')) + case 'failed': + return reject(new Error(finishedTxMeta.err.message)) default: - return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(completedTx.txParams)}`)) + return reject(new Error(`MetaMask Tx Signature: Unknown problem: ${JSON.stringify(finishedTxMeta.txParams)}`)) } }) }) @@ -177,6 +179,7 @@ module.exports = class TransactionController extends EventEmitter { const txParams = txMeta.txParams // ensure value txMeta.gasPriceSpecified = Boolean(txParams.gasPrice) + txMeta.nonceSpecified = Boolean(txParams.nonce) const gasPrice = txParams.gasPrice || await this.query.gasPrice() txParams.gasPrice = ethUtil.addHexPrefix(gasPrice.toString(16)) txParams.value = txParams.value || '0x0' @@ -184,6 +187,13 @@ module.exports = class TransactionController extends EventEmitter { return await this.txGasUtil.analyzeGasUsage(txMeta) } + async retryTransaction (txId) { + this.txStateManager.setTxStatusUnapproved(txId) + const txMeta = this.txStateManager.getTx(txId) + txMeta.lastGasPrice = txMeta.txParams.gasPrice + this.txStateManager.updateTx(txMeta, 'retryTransaction: manual retry') + } + async updateAndApproveTransaction (txMeta) { this.txStateManager.updateTx(txMeta, 'confTx: user approved transaction') await this.approveTransaction(txMeta.id) @@ -200,7 +210,12 @@ module.exports = class TransactionController extends EventEmitter { // wait for a nonce nonceLock = await this.nonceTracker.getNonceLock(fromAddress) // add nonce to txParams - txMeta.txParams.nonce = ethUtil.addHexPrefix(nonceLock.nextNonce.toString(16)) + const nonce = txMeta.nonceSpecified ? txMeta.txParams.nonce : nonceLock.nextNonce + if (nonce > nonceLock.nextNonce) { + const message = `Specified nonce may not be larger than account's next valid nonce.` + throw new Error(message) + } + txMeta.txParams.nonce = ethUtil.addHexPrefix(nonce.toString(16)) // add nonce debugging information to txMeta txMeta.nonceDetails = nonceLock.nonceDetails this.txStateManager.updateTx(txMeta, 'transactions#approveTransaction') diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index ce6642150..8c3dd8c71 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -117,8 +117,6 @@ class AccountTracker extends EventEmitter { const query = this._query async.parallel({ balance: query.getBalance.bind(query, address), - nonce: query.getTransactionCount.bind(query, address), - code: query.getCode.bind(query, address), }, cb) } diff --git a/app/scripts/lib/tx-gas-utils.js b/app/scripts/lib/tx-gas-utils.js index 7e72ea71d..56bee19f7 100644 --- a/app/scripts/lib/tx-gas-utils.js +++ b/app/scripts/lib/tx-gas-utils.js @@ -22,7 +22,11 @@ module.exports = class txProvideUtil { try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) } catch (err) { - if (err.message.includes('Transaction execution error.')) { + const simulationFailed = ( + err.message.includes('Transaction execution error.') || + err.message.includes('gas required exceeds allowance or always failing transaction') + ) + if ( simulationFailed ) { txMeta.simulationFails = true return txMeta } diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index 0fd6bed4b..a8ef39891 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -187,6 +187,10 @@ module.exports = class TransactionStateManger extends EventEmitter { this._setTxStatus(txId, 'rejected') } + // should update the status of the tx to 'unapproved'. + setTxStatusUnapproved (txId) { + this._setTxStatus(txId, 'unapproved') + } // should update the status of the tx to 'approved'. setTxStatusApproved (txId) { this._setTxStatus(txId, 'approved') @@ -236,7 +240,7 @@ module.exports = class TransactionStateManger extends EventEmitter { txMeta.status = status this.emit(`${txMeta.id}:${status}`, txId) this.emit(`tx:status-update`, txId, status) - if (status === 'submitted' || status === 'rejected') { + if (['submitted', 'rejected', 'failed'].includes(status)) { this.emit(`${txMeta.id}:finished`, txMeta) } this.updateTx(txMeta, `txStateManager: setting status to ${status}`) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 018eb2c76..54e73d180 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -23,6 +23,7 @@ const ShapeShiftController = require('./controllers/shapeshift') const AddressBookController = require('./controllers/address-book') const InfuraController = require('./controllers/infura') const BlacklistController = require('./controllers/blacklist') +const RecentBlocksController = require('./controllers/recent-blocks') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') const TypedMessageManager = require('./lib/typed-message-manager') @@ -91,6 +92,10 @@ module.exports = class MetamaskController extends EventEmitter { this.provider = this.initializeProvider() this.blockTracker = this.provider._blockTracker + this.recentBlocksController = new RecentBlocksController({ + blockTracker: this.blockTracker, + }) + // eth data query tools this.ethQuery = new EthQuery(this.provider) // account tracker watches balances, nonces, and any code at their address. @@ -196,25 +201,30 @@ module.exports = class MetamaskController extends EventEmitter { this.blacklistController.store.subscribe((state) => { this.store.updateState({ BlacklistController: state }) }) + this.recentBlocksController.store.subscribe((state) => { + this.store.updateState({ RecentBlocks: state }) + }) this.infuraController.store.subscribe((state) => { this.store.updateState({ InfuraController: state }) }) // manual mem state subscriptions - this.networkController.store.subscribe(this.sendUpdate.bind(this)) - this.accountTracker.store.subscribe(this.sendUpdate.bind(this)) - this.txController.memStore.subscribe(this.sendUpdate.bind(this)) - this.balancesController.store.subscribe(this.sendUpdate.bind(this)) - this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) - this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) - this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) - this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) - this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) - this.addressBookController.store.subscribe(this.sendUpdate.bind(this)) - this.currencyController.store.subscribe(this.sendUpdate.bind(this)) - this.noticeController.memStore.subscribe(this.sendUpdate.bind(this)) - this.shapeshiftController.store.subscribe(this.sendUpdate.bind(this)) - this.infuraController.store.subscribe(this.sendUpdate.bind(this)) + const sendUpdate = this.sendUpdate.bind(this) + this.networkController.store.subscribe(sendUpdate) + this.accountTracker.store.subscribe(sendUpdate) + this.txController.memStore.subscribe(sendUpdate) + this.balancesController.store.subscribe(sendUpdate) + this.messageManager.memStore.subscribe(sendUpdate) + this.personalMessageManager.memStore.subscribe(sendUpdate) + this.typedMessageManager.memStore.subscribe(sendUpdate) + this.keyringController.memStore.subscribe(sendUpdate) + this.preferencesController.store.subscribe(sendUpdate) + this.recentBlocksController.store.subscribe(sendUpdate) + this.addressBookController.store.subscribe(sendUpdate) + this.currencyController.store.subscribe(sendUpdate) + this.noticeController.memStore.subscribe(sendUpdate) + this.shapeshiftController.store.subscribe(sendUpdate) + this.infuraController.store.subscribe(sendUpdate) } // @@ -298,6 +308,7 @@ module.exports = class MetamaskController extends EventEmitter { this.currencyController.store.getState(), this.noticeController.memStore.getState(), this.infuraController.store.getState(), + this.recentBlocksController.store.getState(), // config manager this.configManager.getConfig(), this.shapeshiftController.store.getState(), @@ -366,6 +377,7 @@ module.exports = class MetamaskController extends EventEmitter { // txController cancelTransaction: nodeify(txController.cancelTransaction, txController), updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController), + retryTransaction: nodeify(this.retryTransaction, this), // messageManager signMessage: nodeify(this.signMessage, this), @@ -576,6 +588,14 @@ module.exports = class MetamaskController extends EventEmitter { // // Identity Management // + // + + async retryTransaction (txId, cb) { + await this.txController.retryTransaction(txId) + const state = await this.getState() + return state + } + newUnsignedMessage (msgParams, cb) { const msgId = this.messageManager.addUnapprovedMessage(msgParams) |