From 088d7930e0895ef1802823c5fc843dd1c19b9661 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 20:46:34 -0700 Subject: network - create provider and block-tracker via json-rpc-engine --- .../controllers/network/createInfuraClient.js | 34 ++++++ .../controllers/network/createJsonRpcClient.js | 35 +++++++ .../controllers/network/createLocalhostClient.js | 33 ++++++ .../network/createMetamaskMiddleware.js | 43 ++++++++ app/scripts/controllers/network/network.js | 116 +++++++++++---------- app/scripts/controllers/transactions/index.js | 1 + .../controllers/transactions/nonce-tracker.js | 18 +--- app/scripts/metamask-controller.js | 7 +- 8 files changed, 217 insertions(+), 70 deletions(-) create mode 100644 app/scripts/controllers/network/createInfuraClient.js create mode 100644 app/scripts/controllers/network/createJsonRpcClient.js create mode 100644 app/scripts/controllers/network/createLocalhostClient.js create mode 100644 app/scripts/controllers/network/createMetamaskMiddleware.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js new file mode 100644 index 000000000..e346f4bcb --- /dev/null +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -0,0 +1,34 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache') +const BlockTracker = require('eth-block-tracker') + +module.exports = createInfuraClient + +function createInfuraClient({ network }) { + const infuraMiddleware = createInfuraMiddleware({ network }) + const blockProvider = providerFromMiddleware(infuraMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockCacheMiddleware({ blockTracker }), + createInflightMiddleware(), + createBlockTrackerInspectorMiddleware({ blockTracker }), + infuraMiddleware, + ]) + return { networkMiddleware, blockTracker } +} + +// inspect if response contains a block ref higher than our latest block +const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt'] +function createBlockTrackerInspectorMiddleware ({ blockTracker }) { + return createAsyncMiddleware(async (req, res, next) => { + if (!futureBlockRefRequests.includes(req.method)) return next() + await next() + const blockNumber = Number.parseInt(res.result.blockNumber, 16) + const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16) + if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock() + }) +} diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js new file mode 100644 index 000000000..5a8e85c23 --- /dev/null +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -0,0 +1,35 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache') +const BlockTracker = require('eth-block-tracker') + +module.exports = createJsonRpcClient + +function createJsonRpcClient({ rpcUrl }) { + const fetchMiddleware = createFetchMiddleware({ rpcUrl }) + const blockProvider = providerFromMiddleware(fetchMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockCacheMiddleware({ blockTracker }), + createInflightMiddleware(), + createBlockTrackerInspectorMiddleware({ blockTracker }), + fetchMiddleware, + ]) + return { networkMiddleware, blockTracker } +} + +// inspect if response contains a block ref higher than our latest block +const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt'] +function createBlockTrackerInspectorMiddleware ({ blockTracker }) { + return createAsyncMiddleware(async (req, res, next) => { + if (!futureBlockRefRequests.includes(req.method)) return next() + await next() + const blockNumber = Number.parseInt(res.result.blockNumber, 16) + const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16) + if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock() + }) +} diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js new file mode 100644 index 000000000..404415532 --- /dev/null +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -0,0 +1,33 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache') +const BlockTracker = require('eth-block-tracker') + +module.exports = createLocalhostClient + +function createLocalhostClient() { + const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' }) + const blockProvider = providerFromMiddleware(fetchMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockTrackerInspectorMiddleware({ blockTracker }), + fetchMiddleware, + ]) + return { networkMiddleware, blockTracker } +} + +// inspect if response contains a block ref higher than our latest block +const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt'] +function createBlockTrackerInspectorMiddleware ({ blockTracker }) { + return createAsyncMiddleware(async (req, res, next) => { + if (!futureBlockRefRequests.includes(req.method)) return next() + await next() + const blockNumber = Number.parseInt(res.result.blockNumber, 16) + const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16) + if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock() + }) +} diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js new file mode 100644 index 000000000..1974c231d --- /dev/null +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -0,0 +1,43 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createScaffoldMiddleware = require('json-rpc-engine/src/scaffold') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const createWalletSubprovider = require('eth-json-rpc-middleware/wallet') + +module.exports = createMetamaskMiddleware + +function createMetamaskMiddleware({ + version, + getAccounts, + processTransaction, + processEthSignMessage, + processTypedMessage, + processPersonalMessage, + getPendingNonce +}) { + const metamaskMiddleware = mergeMiddleware([ + createScaffoldMiddleware({ + // staticSubprovider + eth_syncing: false, + web3_clientVersion: `MetaMask/v${version}`, + }), + createWalletSubprovider({ + getAccounts, + processTransaction, + processEthSignMessage, + processTypedMessage, + processPersonalMessage, + }), + createPendingNonceMiddleware({ getPendingNonce }), + }) + return metamaskMiddleware +} + +function createPendingNonceMiddleware ({ getPendingNonce }) { + return createAsyncMiddleware(async (req, res, next) => { + if (req.method !== 'eth_getTransactionCount') return next() + const address = req.params[0] + const blockRef = req.params[1] + if (blockRef !== 'pending') return next() + req.result = await getPendingNonce(address) + }) +} diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 93fde7c57..c882c7d75 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -1,14 +1,17 @@ const assert = require('assert') const EventEmitter = require('events') -const createMetamaskProvider = require('web3-provider-engine/zero.js') -const SubproviderFromProvider = require('web3-provider-engine/subproviders/provider.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') const EthQuery = require('eth-query') -const createEventEmitterProxy = require('../../lib/events-proxy.js') const log = require('loglevel') +const createMetamaskMiddleware = require('./createMetamaskMiddleware') +const createInfuraClient = require('./createInfuraClient') +const createJsonRpcClient = require('./createJsonRpcClient') +const createLocalhostClient = require('./createLocalhostClient') +// const createEventEmitterProxy = require('../../lib/events-proxy.js') +const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') + const { ROPSTEN, RINKEBY, @@ -38,21 +41,27 @@ module.exports = class NetworkController extends EventEmitter { this.providerStore = new ObservableStore(providerConfig) this.networkStore = new ObservableStore('loading') this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) - // create event emitter proxy - this._proxy = createEventEmitterProxy() - this.on('networkDidChange', this.lookupNetwork) + // provider and block tracker + this._provider = null + this._blockTracker = null + // provider and block tracker proxies - because the network changes + this._providerProxy = null + this._blockTrackerProxy = null } - initializeProvider (_providerParams) { - this._baseProviderParams = _providerParams + initializeProvider (providerParams) { + this._baseProviderParams = providerParams const { type, rpcTarget } = this.providerStore.getState() this._configureProvider({ type, rpcTarget }) - this._proxy.on('block', this._logBlock.bind(this)) - this._proxy.on('error', this.verifyNetwork.bind(this)) - this.ethQuery = new EthQuery(this._proxy) this.lookupNetwork() - return this._proxy + } + + // return the proxies so the references will always be good + getProviderAndBlockTracker() { + const provider = this._providerProxy + const blockTracker = this._blockTracker + return { provider, blockTracker } } verifyNetwork () { @@ -74,10 +83,11 @@ module.exports = class NetworkController extends EventEmitter { lookupNetwork () { // Prevent firing when provider is not defined. - if (!this.ethQuery || !this.ethQuery.sendAsync) { - return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery') + if (!this._provider) { + return log.warn('NetworkController - lookupNetwork aborted due to missing provider') } - this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { + const ethQuery = new EthQuery(this._provider) + ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) @@ -123,7 +133,7 @@ module.exports = class NetworkController extends EventEmitter { this._configureInfuraProvider(opts) // other type-based rpc endpoints } else if (type === LOCALHOST) { - this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL }) + this._configureLocalhostProvider() // url-based rpc endpoints } else if (type === 'rpc'){ this._configureStandardProvider({ rpcUrl: rpcTarget }) @@ -133,47 +143,47 @@ module.exports = class NetworkController extends EventEmitter { } _configureInfuraProvider ({ type }) { - log.info('_configureInfuraProvider', type) - const infuraProvider = createInfuraProvider({ network: type }) - const infuraSubprovider = new SubproviderFromProvider(infuraProvider) - const providerParams = extend(this._baseProviderParams, { - engineParams: { - pollingInterval: 8000, - blockTrackerProvider: infuraProvider, - }, - dataSubprovider: infuraSubprovider, - }) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) + log.info('NetworkController - configureInfuraProvider', type) + const networkClient = createInfuraClient({ network: type }) + this._setNetworkClient(networkClient) + } + + _configureLocalhostProvider () { + log.info('NetworkController - configureLocalhostProvider') + const networkClient = createLocalhostClient() + this._setNetworkClient(networkClient) } _configureStandardProvider ({ rpcUrl }) { - const providerParams = extend(this._baseProviderParams, { - rpcUrl, - engineParams: { - pollingInterval: 8000, - }, - }) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) - } - - _setProvider (provider) { - // collect old block tracker events - const oldProvider = this._provider - let blockTrackerHandlers - if (oldProvider) { - // capture old block handlers - blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers - // tear down - oldProvider.removeAllListeners() - oldProvider.stop() + log.info('NetworkController - configureStandardProvider', rpcUrl) + const networkClient = createJsonRpcClient({ rpcUrl }) + this._setNetworkClient(networkClient) + } + + _setNetworkClient ({ networkMiddleware, blockTracker }) { + const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams) + const engine = new JsonRpcEngine() + engine.push(metamaskMiddleware) + engine.push(networkMiddleware) + const provider = providerFromEngine(engine) + this._setProviderAndBlockTracker({ provider, blockTracker }) + } + + _setProviderAndBlockTracker ({ provider, blockTracker }) { + // update or intialize proxies + if (this._providerProxy) { + this._providerProxy.setTarget(provider) + } else { + this._providerProxy = createSwappableProxy(provider) + } + if (this._blockTrackerProxy) { + this._blockTrackerProxy.setTarget(blockTracker) + } else { + this._blockTrackerProxy = createEventEmitterProxy(blockTracker) } - // override block tracler - provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers) - // set as new provider + // set new provider and blockTracker this._provider = provider - this._proxy.setTarget(provider) + this._blockTracker = blockTracker } _logBlock (block) { diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 3886db104..cb3d28f1d 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -63,6 +63,7 @@ class TransactionController extends EventEmitter { this.store = this.txStateManager.store this.nonceTracker = new NonceTracker({ provider: this.provider, + blockTracker: this.blockTracker, getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js index f8cdc5523..490118c89 100644 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -12,8 +12,9 @@ const Mutex = require('await-semaphore').Mutex */ class NonceTracker { - constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { + constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { this.provider = provider + this.blockTracker = blockTracker this.ethQuery = new EthQuery(provider) this.getPendingTransactions = getPendingTransactions this.getConfirmedTransactions = getConfirmedTransactions @@ -75,11 +76,10 @@ class NonceTracker { } async _getCurrentBlock () { - const blockTracker = this._getBlockTracker() - const currentBlock = blockTracker.getCurrentBlock() + const currentBlock = this.blockTracker.getCurrentBlock() if (currentBlock) return currentBlock return await new Promise((reject, resolve) => { - blockTracker.once('latest', resolve) + this.blockTracker.once('latest', resolve) }) } @@ -171,16 +171,6 @@ class NonceTracker { return { name: 'local', nonce: highest, details: { startPoint, highest } } } - - // this is a hotfix for the fact that the blockTracker will - // change when the network changes - - /** - @returns {Object} the current blockTracker - */ - _getBlockTracker () { - return this.provider._blockTracker - } } module.exports = NonceTracker diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index a6b5d3453..7f4f2fc9b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -103,8 +103,9 @@ module.exports = class MetamaskController extends EventEmitter { this.blacklistController.scheduleUpdates() // rpc provider - this.provider = this.initializeProvider() - this.blockTracker = this.provider._blockTracker + this.initializeProvider() + this.provider = this.networkController.getProviderAndBlockTracker().provider + this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker // token exchange rate tracker this.tokenRatesController = new TokenRatesController({ @@ -1033,7 +1034,7 @@ module.exports = class MetamaskController extends EventEmitter { // create filter polyfill middleware const filterMiddleware = createFilterMiddleware({ provider: this.provider, - blockTracker: this.provider._blockTracker, + blockTracker: this.blockTracker, }) engine.push(createOriginMiddleware({ origin })) -- cgit From b6eff15bd25ca30ba8a746eff2beec1c820b8855 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 21:08:19 -0700 Subject: lint fix --- app/scripts/controllers/network/createInfuraClient.js | 7 +++++-- app/scripts/controllers/network/createJsonRpcClient.js | 6 ++++-- app/scripts/controllers/network/createLocalhostClient.js | 4 ++-- app/scripts/controllers/network/createMetamaskMiddleware.js | 6 +++--- app/scripts/controllers/network/network.js | 8 ++++---- 5 files changed, 18 insertions(+), 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index e346f4bcb..528be80ce 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -1,12 +1,15 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') -const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache') +const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const createInfuraMiddleware = require('eth-json-rpc-infura') const BlockTracker = require('eth-block-tracker') module.exports = createInfuraClient -function createInfuraClient({ network }) { +function createInfuraClient ({ network }) { const infuraMiddleware = createInfuraMiddleware({ network }) const blockProvider = providerFromMiddleware(infuraMiddleware) const blockTracker = new BlockTracker({ provider: blockProvider }) diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index 5a8e85c23..f8b67aa26 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -2,12 +2,14 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') -const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache') +const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const BlockTracker = require('eth-block-tracker') module.exports = createJsonRpcClient -function createJsonRpcClient({ rpcUrl }) { +function createJsonRpcClient ({ rpcUrl }) { const fetchMiddleware = createFetchMiddleware({ rpcUrl }) const blockProvider = providerFromMiddleware(fetchMiddleware) const blockTracker = new BlockTracker({ provider: blockProvider }) diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js index 404415532..990dc6a95 100644 --- a/app/scripts/controllers/network/createLocalhostClient.js +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -2,12 +2,12 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') -const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-cache') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const BlockTracker = require('eth-block-tracker') module.exports = createLocalhostClient -function createLocalhostClient() { +function createLocalhostClient () { const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' }) const blockProvider = providerFromMiddleware(fetchMiddleware) const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js index 1974c231d..7dbd90d7f 100644 --- a/app/scripts/controllers/network/createMetamaskMiddleware.js +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -5,14 +5,14 @@ const createWalletSubprovider = require('eth-json-rpc-middleware/wallet') module.exports = createMetamaskMiddleware -function createMetamaskMiddleware({ +function createMetamaskMiddleware ({ version, getAccounts, processTransaction, processEthSignMessage, processTypedMessage, processPersonalMessage, - getPendingNonce + getPendingNonce, }) { const metamaskMiddleware = mergeMiddleware([ createScaffoldMiddleware({ @@ -28,7 +28,7 @@ function createMetamaskMiddleware({ processPersonalMessage, }), createPendingNonceMiddleware({ getPendingNonce }), - }) + ]) return metamaskMiddleware } diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index c882c7d75..a8c45a79c 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -2,8 +2,9 @@ const assert = require('assert') const EventEmitter = require('events') const ObservableStore = require('obs-store') const ComposedStore = require('obs-store/lib/composed') -const extend = require('xtend') const EthQuery = require('eth-query') +const JsonRpcEngine = require('json-rpc-engine') +const providerFromEngine = require('eth-json-rpc-middle/providerFromEngine') const log = require('loglevel') const createMetamaskMiddleware = require('./createMetamaskMiddleware') const createInfuraClient = require('./createInfuraClient') @@ -19,7 +20,6 @@ const { MAINNET, LOCALHOST, } = require('./enums') -const LOCALHOST_RPC_URL = 'http://localhost:8545' const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] const env = process.env.METAMASK_ENV @@ -58,7 +58,7 @@ module.exports = class NetworkController extends EventEmitter { } // return the proxies so the references will always be good - getProviderAndBlockTracker() { + getProviderAndBlockTracker () { const provider = this._providerProxy const blockTracker = this._blockTracker return { provider, blockTracker } @@ -135,7 +135,7 @@ module.exports = class NetworkController extends EventEmitter { } else if (type === LOCALHOST) { this._configureLocalhostProvider() // url-based rpc endpoints - } else if (type === 'rpc'){ + } else if (type === 'rpc') { this._configureStandardProvider({ rpcUrl: rpcTarget }) } else { throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`) -- cgit From 3e04840a7104849e9329b8faa4730554b2438217 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 21:10:41 -0700 Subject: remove unused events-proxy, was replaced with module swappable-obj-proxy --- app/scripts/controllers/network/network.js | 1 - app/scripts/lib/events-proxy.js | 42 ------------------------------ 2 files changed, 43 deletions(-) delete mode 100644 app/scripts/lib/events-proxy.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index a8c45a79c..213013cca 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -10,7 +10,6 @@ const createMetamaskMiddleware = require('./createMetamaskMiddleware') const createInfuraClient = require('./createInfuraClient') const createJsonRpcClient = require('./createJsonRpcClient') const createLocalhostClient = require('./createLocalhostClient') -// const createEventEmitterProxy = require('../../lib/events-proxy.js') const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') const { diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js deleted file mode 100644 index f83773ccc..000000000 --- a/app/scripts/lib/events-proxy.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Returns an EventEmitter that proxies events from the given event emitter - * @param {any} eventEmitter - * @param {object} listeners - The listeners to proxy to - * @returns {any} - */ -module.exports = function createEventEmitterProxy (eventEmitter, listeners) { - let target = eventEmitter - const eventHandlers = listeners || {} - const proxy = /** @type {any} */ (new Proxy({}, { - get: (_, name) => { - // intercept listeners - if (name === 'on') return addListener - if (name === 'setTarget') return setTarget - if (name === 'proxyEventHandlers') return eventHandlers - return (/** @type {any} */ (target))[name] - }, - set: (_, name, value) => { - target[name] = value - return true - }, - })) - function setTarget (/** @type {EventEmitter} */ eventEmitter) { - target = eventEmitter - // migrate listeners - Object.keys(eventHandlers).forEach((name) => { - /** @type {Array} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler)) - }) - } - /** - * Attaches a function to be called whenever the specified event is emitted - * @param {string} name - * @param {Function} handler - */ - function addListener (name, handler) { - if (!eventHandlers[name]) eventHandlers[name] = [] - eventHandlers[name].push(handler) - target.on(name, handler) - } - if (listeners) proxy.setTarget(eventEmitter) - return proxy -} -- cgit From 623533ab1508c93f0c347f0c4a1002ce93ff1ae2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 21:13:53 -0700 Subject: recent-blocks - update for eth-block-tracker@4 --- app/scripts/controllers/recent-blocks.js | 41 +++++++++++--------------------- 1 file changed, 14 insertions(+), 27 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 1377c1ba9..4101814eb 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -3,6 +3,8 @@ const extend = require('xtend') const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') const log = require('loglevel') +const pify = require('pify') +const timeout = (duration) => new Promise(resolve => setTimeout(duration, resolve)) class RecentBlocksController { @@ -34,7 +36,7 @@ class RecentBlocksController { }, opts.initState) this.store = new ObservableStore(initState) - this.blockTracker.on('block', this.processBlock.bind(this)) + this.blockTracker.on('latest', this.processBlock.bind(this)) this.backfill() } @@ -55,7 +57,10 @@ class RecentBlocksController { * @param {object} newBlock The new block to modify and add to the recentBlocks array * */ - processBlock (newBlock) { + async processBlock (newBlockNumberHex) { + const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) + const newBlock = await this.getBlockByNumber(newBlockNumber) + const block = this.mapTransactionsToPrices(newBlock) const state = this.store.getState() @@ -118,17 +123,16 @@ class RecentBlocksController { * @returns {Promise} Promises undefined */ async backfill() { - this.blockTracker.once('block', async (block) => { - let blockNum = block.number + this.blockTracker.once('latest', async (blockNumberHex) => { let recentBlocks + const blockNumber = Number.parseInt(blockNumberHex, 16) let state = this.store.getState() recentBlocks = state.recentBlocks while (recentBlocks.length < this.historyLength) { try { - let blockNumBn = new BN(blockNum.substr(2), 16) - const newNum = blockNumBn.subn(1).toString(10) - const newBlock = await this.getBlockByNumber(newNum) + const prevBlockNumber = blockNumber - 1 + const newBlock = await this.getBlockByNumber(prevBlockNumber) if (newBlock) { this.backfillBlock(newBlock) @@ -140,23 +144,11 @@ class RecentBlocksController { } catch (e) { log.error(e) } - await this.wait() + await timeout(100) } }) } - /** - * A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await - * - * @returns {Promise} Promises undefined - * - */ - async wait () { - return new Promise((resolve) => { - setTimeout(resolve, 100) - }) - } - /** * Uses EthQuery to get a block that has a given block number. * @@ -165,13 +157,8 @@ class RecentBlocksController { * */ async getBlockByNumber (number) { - const bn = new BN(number) - return new Promise((resolve, reject) => { - this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => { - if (err) reject(err) - resolve(block) - }) - }) + const blockNumberHex = '0x' + number.toString(16) + return await pify(this.ethQuery.getBlockByNumber).call(this.ethQuery, blockNumberHex, true) } } -- cgit From 32b3b8f2a76b4a67fde2a3af2ddd239998c5dfcd Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 21:16:05 -0700 Subject: controllers - balance - update for eth-block-tracker@4 --- app/scripts/controllers/balance.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 86619fce1..465751e61 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -60,7 +60,7 @@ class BalanceController { * Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include: * - when a transaction changes state to 'submitted', 'confirmed' or 'failed' * - when the current account changes (i.e. a new account is selected) - * - when there is a block update + * - when there is a block update * * @private * @@ -80,7 +80,7 @@ class BalanceController { } }) this.accountTracker.store.subscribe(update) - this.blockTracker.on('block', update) + this.blockTracker.on('latest', update) } /** @@ -100,7 +100,7 @@ class BalanceController { /** * Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the - * TransactionController passed to this BalanceController during construction. + * TransactionController passed to this BalanceController during construction. * * @private * @returns {Promise} Promises an array of transaction objects. -- cgit From 08dc238c9fdb23997a7fbdf57de5305455d1d930 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 22:46:11 -0700 Subject: deps - fix incorrect dep paths and versions --- app/scripts/controllers/network/createInfuraClient.js | 2 +- app/scripts/controllers/network/createJsonRpcClient.js | 2 +- app/scripts/controllers/network/createMetamaskMiddleware.js | 2 +- app/scripts/controllers/network/network.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 528be80ce..adbf4c001 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -2,7 +2,7 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') -const createInflightMiddleware = require('eth-json-rpc-middleware/inflight') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const createInfuraMiddleware = require('eth-json-rpc-infura') const BlockTracker = require('eth-block-tracker') diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index f8b67aa26..d712ed135 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -3,7 +3,7 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') -const createInflightMiddleware = require('eth-json-rpc-middleware/inflight') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const BlockTracker = require('eth-block-tracker') diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js index 7dbd90d7f..8b17829b7 100644 --- a/app/scripts/controllers/network/createMetamaskMiddleware.js +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -1,5 +1,5 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') -const createScaffoldMiddleware = require('json-rpc-engine/src/scaffold') +const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createWalletSubprovider = require('eth-json-rpc-middleware/wallet') diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 213013cca..9e622981b 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -4,7 +4,7 @@ const ObservableStore = require('obs-store') const ComposedStore = require('obs-store/lib/composed') const EthQuery = require('eth-query') const JsonRpcEngine = require('json-rpc-engine') -const providerFromEngine = require('eth-json-rpc-middle/providerFromEngine') +const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') const log = require('loglevel') const createMetamaskMiddleware = require('./createMetamaskMiddleware') const createInfuraClient = require('./createInfuraClient') -- cgit From 41c04ef7219eafeca71bcd577abe6bca3a637a89 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 16 May 2018 23:13:25 -0700 Subject: controllers - recent-blocks - fix pifyd setTimeout args --- app/scripts/controllers/recent-blocks.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 4101814eb..28eba0f26 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -4,7 +4,7 @@ const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') const log = require('loglevel') const pify = require('pify') -const timeout = (duration) => new Promise(resolve => setTimeout(duration, resolve)) +const timeout = (duration) => new Promise(resolve => setTimeout(resolve, duration)) class RecentBlocksController { -- cgit From 3084dc47d10e3e455c924e5aad0b0961c500ec8d Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 17 May 2018 00:13:20 -0700 Subject: recent-blocks - fix backfill blockNumber tracking --- app/scripts/controllers/recent-blocks.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 28eba0f26..9a215d0e5 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -1,6 +1,5 @@ const ObservableStore = require('obs-store') const extend = require('xtend') -const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') const log = require('loglevel') const pify = require('pify') @@ -125,7 +124,7 @@ class RecentBlocksController { async backfill() { this.blockTracker.once('latest', async (blockNumberHex) => { let recentBlocks - const blockNumber = Number.parseInt(blockNumberHex, 16) + let blockNumber = Number.parseInt(blockNumberHex, 16) let state = this.store.getState() recentBlocks = state.recentBlocks @@ -136,7 +135,7 @@ class RecentBlocksController { if (newBlock) { this.backfillBlock(newBlock) - blockNum = newBlock.number + blockNumber = Number.parseInt(newBlock.number, 16) } state = this.store.getState() -- cgit From 10aecf49227098fb7fdbd7db193a9dcc6fecf5af Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 22 May 2018 16:40:01 -0700 Subject: remove dependance on the even tx:confirmed --- app/scripts/controllers/transactions/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index cb3d28f1d..313b20675 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -57,6 +57,7 @@ class TransactionController extends EventEmitter { initState: opts.initState, txHistoryLimit: opts.txHistoryLimit, getNetwork: this.getNetwork.bind(this), + confirmTransaction: this.confirmTransaction.bind(this), }) this._onBootCleanUp() @@ -316,6 +317,11 @@ class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId) } + confirmTransaction (txId) { + this.txStateManager.setTxStatusConfirmed(txId) + this._markNonceDuplicatesDropped(txId) + } + /** Convenience method for the ui thats sets the transaction to rejected @param txId {number} - the tx's Id @@ -396,8 +402,6 @@ class TransactionController extends EventEmitter { this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) - this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId)) - this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { -- cgit From c4b09da34e9950ea485dfecb810726e49b683dcd Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 22 May 2018 16:41:00 -0700 Subject: transactions - update pending-tx-tracker to use the new block tracker --- .../controllers/transactions/pending-tx-tracker.js | 25 ++++++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 6e2fcb40b..bd26a72d9 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -27,6 +27,7 @@ class PendingTransactionTracker extends EventEmitter { this.getPendingTransactions = config.getPendingTransactions this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction + this.confirmTransaction = config.confirmTransaction this._checkPendingTxs() } @@ -37,7 +38,8 @@ class PendingTransactionTracker extends EventEmitter { @emits tx:confirmed @emits tx:failed */ - checkForTxInBlock (block) { + async checkForTxInBlock (blockNumber) { + const block = await this._getBlock(blockNumber) const signedTxList = this.getPendingTransactions() if (!signedTxList.length) return signedTxList.forEach((txMeta) => { @@ -51,9 +53,12 @@ class PendingTransactionTracker extends EventEmitter { return } + if (!block.transactions.length) return - block.transactions.forEach((tx) => { - if (tx.hash === txHash) this.emit('tx:confirmed', txId) + block.transactions.forEach((hash) => { + if (hash === txHash) { + this.confirmTransaction(txId) + } }) }) } @@ -70,7 +75,7 @@ class PendingTransactionTracker extends EventEmitter { return } // if we synced by more than one block, check for missed pending transactions - const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16) + const diff = Number.parseInt(newBlock, 16) - Number.parseInt(oldBlock, 16) if (diff > 1) this._checkPendingTxs() } @@ -79,11 +84,11 @@ class PendingTransactionTracker extends EventEmitter { @param block {object} - a block object @emits tx:warning */ - resubmitPendingTxs (block) { + resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit if (!pending.length) return - pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => { + pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning "there is already a transaction with the same sender-nonce @@ -179,7 +184,7 @@ class PendingTransactionTracker extends EventEmitter { txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return if (txParams.blockNumber) { - this.emit('tx:confirmed', txId) + this.confirmTransaction(txId) } } catch (err) { txMeta.warning = { @@ -206,11 +211,17 @@ class PendingTransactionTracker extends EventEmitter { nonceGlobalLock.releaseLock() } + async _getBlock (blockNumber) { + return await this.query.getBlockByNumber(blockNumber, false) + } + /** checks to see if a confirmed txMeta has the same nonce @param txMeta {Object} - txMeta object @returns {boolean} */ + + async _checkIfNonceIsTaken (txMeta) { const address = txMeta.txParams.from const completed = this.getCompletedTransactions(address) -- cgit From 53b946362a59fbaea1a3ae3a7e7af97f427afed4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 23 May 2018 16:20:35 -0700 Subject: controllers - recent-blocks - doc update --- app/scripts/controllers/recent-blocks.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index a6208b89c..e0b5551dd 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -8,7 +8,7 @@ class RecentBlocksController { /** * Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled - * upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event + * upon the controller's construction and then the list is updated when the given block tracker gets a 'latest' event * (indicating that there is a new block to process). * * @typedef {Object} RecentBlocksController @@ -16,7 +16,7 @@ class RecentBlocksController { * @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain * @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance. * @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction, - * listens for 'block' events so that new blocks can be processed and added to storage. + * listens for 'latest' events so that new blocks can be processed and added to storage. * @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider * @property {number} historyLength The maximum length of blocks to track * @property {object} store Stores the recentBlocks @@ -111,9 +111,9 @@ class RecentBlocksController { } /** - * On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks + * On this.blockTracker's first 'latest' event after this RecentBlocksController's instantiation, the store.recentBlocks * array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first - * 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying + * 'latest' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying * the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest. * * Each iteration over the block numbers is delayed by 100 milliseconds. -- cgit From eb2423799d472863b5a74443ac32b5c1d59ce9fc Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 23 May 2018 16:22:40 -0700 Subject: controllers - account-tracker - refactor + update for eth-block-tracker@4 --- app/scripts/lib/account-tracker.js | 123 +++++++++++++++++-------------------- 1 file changed, 56 insertions(+), 67 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 0f7b3d865..c9db65710 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -7,14 +7,13 @@ * on each new block. */ -const async = require('async') const EthQuery = require('eth-query') const ObservableStore = require('obs-store') -const EventEmitter = require('events').EventEmitter -function noop () {} +const log = require('loglevel') +const pify = require('pify') -class AccountTracker extends EventEmitter { +class AccountTracker { /** * This module is responsible for tracking any number of accounts and caching their current balances & transaction @@ -35,8 +34,6 @@ class AccountTracker extends EventEmitter { * */ constructor (opts = {}) { - super() - const initState = { accounts: {}, currentBlockGasLimit: '', @@ -44,12 +41,12 @@ class AccountTracker extends EventEmitter { this.store = new ObservableStore(initState) this._provider = opts.provider - this._query = new EthQuery(this._provider) + this._query = pify(new EthQuery(this._provider)) this._blockTracker = opts.blockTracker // subscribe to latest block - this._blockTracker.on('block', this._updateForBlock.bind(this)) + this._blockTracker.on('latest', this._updateForBlock.bind(this)) // blockTracker.currentBlock may be null - this._currentBlockNumber = this._blockTracker.currentBlock + this._currentBlockNumber = this._blockTracker.getCurrentBlock() } /** @@ -67,49 +64,57 @@ class AccountTracker extends EventEmitter { const accounts = this.store.getState().accounts const locals = Object.keys(accounts) - const toAdd = [] + const accountsToAdd = [] addresses.forEach((upstream) => { if (!locals.includes(upstream)) { - toAdd.push(upstream) + accountsToAdd.push(upstream) } }) - const toRemove = [] + const accountsToRemove = [] locals.forEach((local) => { if (!addresses.includes(local)) { - toRemove.push(local) + accountsToRemove.push(local) } }) - toAdd.forEach(upstream => this.addAccount(upstream)) - toRemove.forEach(local => this.removeAccount(local)) - this._updateAccounts() + this.addAccounts(accountsToAdd) + this.removeAccounts(accountsToRemove) } /** - * Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be + * Adds new addresses to track the balances of * given a balance as long this._currentBlockNumber is defined. * - * @param {string} address A hex address of a new account to store in this AccountTracker's accounts object + * @param {array} addresses An array of hex addresses of new accounts to track * */ - addAccount (address) { + addAccounts (addresses) { const accounts = this.store.getState().accounts - accounts[address] = {} + // add initial state for addresses + addresses.forEach(address => { + accounts[address] = {} + }) + // save accounts state this.store.updateState({ accounts }) + // fetch balances for the accounts if there is block number ready if (!this._currentBlockNumber) return - this._updateAccount(address) + addresses.forEach(address => this._updateAccount(address)) } /** - * Removes an account from this AccountTracker's accounts object + * Removes accounts from being tracked * - * @param {string} address A hex address of a the account to remove + * @param {array} an array of hex addresses to stop tracking * */ - removeAccount (address) { + removeAccounts (addresses) { const accounts = this.store.getState().accounts - delete accounts[address] + // remove each state object + addresses.forEach(address => { + delete accounts[address] + }) + // save accounts state this.store.updateState({ accounts }) } @@ -118,71 +123,55 @@ class AccountTracker extends EventEmitter { * via EthQuery * * @private - * @param {object} block Data about the block that contains the data to update to. + * @param {number} blockNumber the block number to update to. * @fires 'block' The updated state, if all account updates are successful * */ - _updateForBlock (block) { - this._currentBlockNumber = block.number - const currentBlockGasLimit = block.gasLimit + async _updateForBlock (blockNumber) { + this._currentBlockNumber = blockNumber + // this shouldn't be here... + const currentBlock = await this._query.getBlockByNumber(blockNumber, false) + const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) - async.parallel([ - this._updateAccounts.bind(this), - ], (err) => { - if (err) return console.error(err) - this.emit('block', this.store.getState()) - }) + try { + await this._updateAccounts() + } catch (err) { + log.error(err) + } } /** * Calls this._updateAccount for each account in this.store * - * @param {Function} cb A callback to pass to this._updateAccount, called after each account is successfully updated + * @returns {Promise} after all account balances updated * */ - _updateAccounts (cb = noop) { + async _updateAccounts () { const accounts = this.store.getState().accounts const addresses = Object.keys(accounts) - async.each(addresses, this._updateAccount.bind(this), cb) + await Promise.all(addresses.map(this._updateAccount.bind(this))) } /** - * Updates the current balance of an account. Gets an updated balance via this._getAccount. + * Updates the current balance of an account. * * @private * @param {string} address A hex address of a the account to be updated - * @param {Function} cb A callback to call once the account at address is successfully update + * @returns {Promise} after the account balance is updated * */ - _updateAccount (address, cb = noop) { - this._getAccount(address, (err, result) => { - if (err) return cb(err) - result.address = address - const accounts = this.store.getState().accounts - // only populate if the entry is still present - if (accounts[address]) { - accounts[address] = result - this.store.updateState({ accounts }) - } - cb(null, result) - }) - } - - /** - * Gets the current balance of an account via EthQuery. - * - * @private - * @param {string} address A hex address of a the account to query - * @param {Function} cb A callback to call once the account at address is successfully update - * - */ - _getAccount (address, cb = noop) { - const query = this._query - async.parallel({ - balance: query.getBalance.bind(query, address), - }, cb) + async _updateAccount (address) { + // query balance + const balance = await this._query.getBalance(address) + const result = { address, balance } + // update accounts state + const { accounts } = this.store.getState() + // only populate if the entry is still present + if (!accounts[address]) return + accounts[address] = result + this.store.updateState({ accounts }) } } -- cgit From 22e59af741a43ab9a9dc8e96415788f681a8efda Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 23 May 2018 22:32:33 -0700 Subject: controllers - recent-blocks - ensure full blocks --- app/scripts/controllers/recent-blocks.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index e0b5551dd..215638666 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -57,7 +57,7 @@ class RecentBlocksController { */ async processBlock (newBlockNumberHex) { const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) - const newBlock = await this.getBlockByNumber(newBlockNumber) + const newBlock = await this.getBlockByNumber(newBlockNumber, true) const block = this.mapTransactionsToPrices(newBlock) @@ -128,7 +128,7 @@ class RecentBlocksController { const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index) await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { - const newBlock = await this.getBlockByNumber(targetBlockNumber) + const newBlock = await this.getBlockByNumber(targetBlockNumber, true) if (newBlock) { this.backfillBlock(newBlock) -- cgit From f41198fbb6f4e72e331bb4e3d326534f90000ce9 Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 23 May 2018 22:33:02 -0700 Subject: sentry - setupRaven - ensure message is truthy --- app/scripts/lib/setupRaven.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index d164827ab..30d0a3680 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -66,11 +66,11 @@ function simplifyErrorMessages(report) { function rewriteErrorMessages(report, rewriteFn) { // rewrite top level message - report.message = rewriteFn(report.message) + if (typeof report.message === 'string') report.message = rewriteFn(report.message) // rewrite each exception message if (report.exception && report.exception.values) { report.exception.values.forEach(item => { - item.value = rewriteFn(item.value) + if (typeof item.value === 'string') item.value = rewriteFn(item.value) }) } } -- cgit From ee800de025397af7958d8e8e382b31b1d09c338c Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 23 May 2018 22:46:20 -0700 Subject: controllers - recent-blocks - wrap block-tracker event in try-catch --- app/scripts/controllers/recent-blocks.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 215638666..e256c62e0 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -34,7 +34,13 @@ class RecentBlocksController { }, opts.initState) this.store = new ObservableStore(initState) - this.blockTracker.on('latest', this.processBlock.bind(this)) + this.blockTracker.on('latest', async (newBlockNumberHex) => { + try { + await this.processBlock(newBlockNumberHex) + } catch (err) { + log.error(err) + } + }) this.backfill() } -- cgit From 49ef93b99158b9fae21d841828d7e7d105f837df Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 May 2018 13:43:16 -0700 Subject: controllers - recent-blocks - guard against empty block --- app/scripts/controllers/recent-blocks.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index e256c62e0..4009c35ae 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -64,6 +64,7 @@ class RecentBlocksController { async processBlock (newBlockNumberHex) { const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) const newBlock = await this.getBlockByNumber(newBlockNumber, true) + if (!newBlock) return const block = this.mapTransactionsToPrices(newBlock) @@ -135,10 +136,9 @@ class RecentBlocksController { await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { const newBlock = await this.getBlockByNumber(targetBlockNumber, true) + if (!newBlock) return - if (newBlock) { - this.backfillBlock(newBlock) - } + this.backfillBlock(newBlock) } catch (e) { log.error(e) } -- cgit From 91accee2c65e2b9c9e278451f1d6794f74b27c24 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 May 2018 13:43:36 -0700 Subject: account-tracker - guard against empty block --- app/scripts/lib/account-tracker.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index c9db65710..1a2354463 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -130,8 +130,9 @@ class AccountTracker { async _updateForBlock (blockNumber) { this._currentBlockNumber = blockNumber - // this shouldn't be here... + // block gasLimit polling shouldn't be in account-tracker shouldn't be here... const currentBlock = await this._query.getBlockByNumber(blockNumber, false) + if (!currentBlock) return const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) -- cgit From 66a62dfd0c2efbb9596bdc4b4befe03b25fbd7e9 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 May 2018 13:44:33 -0700 Subject: metamask-controller - fix account lookup hook --- app/scripts/metamask-controller.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 14eb749e7..152ad13a0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -241,7 +241,7 @@ module.exports = class MetamaskController extends EventEmitter { }, }, // account mgmt - getAccounts: (cb) => { + getAccounts: async () => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked const result = [] const selectedAddress = this.preferencesController.getSelectedAddress() @@ -250,7 +250,7 @@ module.exports = class MetamaskController extends EventEmitter { if (isUnlocked && selectedAddress) { result.push(selectedAddress) } - cb(null, result) + return result }, // tx signing // old style msg signing -- cgit From aab9691c42184b81cdf7086d389bb74279e867bf Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 May 2018 15:51:46 -0700 Subject: provider - update wallet hooks for new wallet middleware --- app/scripts/lib/message-manager.js | 31 +++++++++++- app/scripts/lib/personal-message-manager.js | 35 ++++++++++++- app/scripts/lib/typed-message-manager.js | 31 +++++++++++- app/scripts/metamask-controller.js | 77 +++++++---------------------- 4 files changed, 110 insertions(+), 64 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 901367f04..47925b94b 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -69,10 +69,39 @@ module.exports = class MessageManager extends EventEmitter { * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} after signature has been + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + const msgId = this.addUnapprovedMessage(msgParams, req) + // await finished + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the + * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object where the origin may be specificied * @returns {number} The id of the newly created message. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { + // add origin from request + if (req) msgParams.origin = req.origin msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data var time = (new Date()).getTime() diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index e96ced1f2..fc2cccdf1 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -73,11 +73,43 @@ module.exports = class PersonalMessageManager extends EventEmitter { * this.memStore. * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + if (!msgParams.from) { + reject(new Error('MetaMask Message Signature: from field is required.')) + } + const msgId = this.addUnapprovedMessage(msgParams, req) + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to + * this.memStore. + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin * @returns {number} The id of the newly created PersonalMessage. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + // add origin from request + if (req) msgParams.origin = req.origin msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data var time = (new Date()).getTime() @@ -257,4 +289,3 @@ module.exports = class PersonalMessageManager extends EventEmitter { } } - diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index c58921610..e5e1c94b3 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -72,11 +72,40 @@ module.exports = class TypedMessageManager extends EventEmitter { * this.memStore. Before any of this is done, msgParams are validated * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + const msgId = this.addUnapprovedMessage(msgParams, req) + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to + * this.memStore. Before any of this is done, msgParams are validated + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin * @returns {number} The id of the newly created TypedMessage. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { this.validateParams(msgParams) + // add origin from request + if (req) msgParams.origin = req.origin log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 152ad13a0..8c3c70c5c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -234,28 +234,22 @@ module.exports = class MetamaskController extends EventEmitter { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, - eth_sendTransaction: (payload, next, end) => { - const origin = payload.origin - const txParams = payload.params[0] - nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end) - }, }, // account mgmt getAccounts: async () => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked - const result = [] const selectedAddress = this.preferencesController.getSelectedAddress() - // only show address if account is unlocked if (isUnlocked && selectedAddress) { - result.push(selectedAddress) + return [selectedAddress] + } else { + return [] } - return result }, // tx signing - // old style msg signing - processMessage: this.newUnsignedMessage.bind(this), - // personal_sign msg signing + processTransaction: this.txController.newUnapprovedTransaction.bind(this.txController), + // msg signing + processEthSignMessage: this.newUnsignedMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processTypedMessage: this.newUnsignedTypedMessage.bind(this), } @@ -634,20 +628,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_sign. * @param {Function} cb = The callback function called with the signature. */ - newUnsignedMessage (msgParams, cb) { - const msgId = this.messageManager.addUnapprovedMessage(msgParams) + newUnsignedMessage (msgParams, req) { + const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req) this.sendUpdate() this.opts.showUnconfirmedMessage() - this.messageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied message signature.')) - default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) - } - }) + return promise } /** @@ -701,24 +686,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} cb - The callback function called with the signature. * Passed back to the requesting Dapp. */ - newUnsignedPersonalMessage (msgParams, cb) { - if (!msgParams.from) { - return cb(new Error('MetaMask Message Signature: from field is required.')) - } - - const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + async newUnsignedPersonalMessage (msgParams, req) { + const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req) this.sendUpdate() this.opts.showUnconfirmedMessage() - this.personalMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied message signature.')) - default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) - } - }) + return promise } /** @@ -767,26 +739,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_signTypedData. * @param {Function} cb - The callback function, called with the signature. */ - newUnsignedTypedMessage (msgParams, cb) { - let msgId - try { - msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) - this.sendUpdate() - this.opts.showUnconfirmedMessage() - } catch (e) { - return cb(e) - } - - this.typedMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(new Error('MetaMask Message Signature: User denied message signature.')) - default: - return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) - } - }) + newUnsignedTypedMessage (msgParams, req) { + const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + return promise } /** -- cgit From 76cfb10864364d53efcdfa5f646f9947c83b6fb2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 24 May 2018 16:05:07 -0700 Subject: metamask-controller - wrap txController.addUnapprovedTx for wallet middleware reference before txController is instantiated --- app/scripts/metamask-controller.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 8c3c70c5c..796c9385a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -247,7 +247,7 @@ module.exports = class MetamaskController extends EventEmitter { } }, // tx signing - processTransaction: this.txController.newUnapprovedTransaction.bind(this.txController), + processTransaction: this.newUnapprovedTransaction.bind(this), // msg signing processEthSignMessage: this.newUnsignedMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), @@ -617,6 +617,18 @@ module.exports = class MetamaskController extends EventEmitter { // --------------------------------------------------------------------------- // Identity Management (signature operations) + /** + * Called when a Dapp suggests a new tx to be signed. + * this wrapper needs to exist so we can provide a reference to + * "newUnapprovedTransaction" before "txController" is instantiated + * + * @param {Object} msgParams - The params passed to eth_sign. + * @param {Object} req - (optional) the original request, containing the origin + */ + async newUnapprovedTransaction(txParams, req) { + return await this.txController.newUnapprovedTransaction(txParams, req) + } + // eth_sign methods: /** -- cgit From 61ef4f1f29169c553b6a8fc36803e6f7596fc9ad Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 25 May 2018 13:21:42 -0700 Subject: tx-gas-utils - query for block without tx bodies --- app/scripts/controllers/transactions/tx-gas-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 36b5cdbc9..9bf2ae1e2 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -25,7 +25,7 @@ class TxGasUtil { @returns {object} the txMeta object with the gas written to the txParams */ async analyzeGasUsage (txMeta) { - const block = await this.query.getBlockByNumber('latest', true) + const block = await this.query.getBlockByNumber('latest', false) let estimatedGasHex try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) @@ -126,4 +126,4 @@ class TxGasUtil { } } -module.exports = TxGasUtil \ No newline at end of file +module.exports = TxGasUtil -- cgit From 9f8d5f05470d68a7a9a5474a5b1f4587398e94a3 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 25 May 2018 13:30:26 -0700 Subject: controllers - transactions - pending-tx-tracker - _getBlock - poll until block is truthy --- app/scripts/controllers/transactions/pending-tx-tracker.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index bd26a72d9..e1bb67c90 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -1,6 +1,7 @@ const EventEmitter = require('events') const log = require('loglevel') const EthQuery = require('ethjs-query') +const timeout = (duration) => new Promise(resolve => setTimeout(resolve, duration)) /** Event emitter utility class for tracking the transactions as they
@@ -212,7 +213,15 @@ class PendingTransactionTracker extends EventEmitter { } async _getBlock (blockNumber) { - return await this.query.getBlockByNumber(blockNumber, false) + let block + while (!block) { + // block requests will sometimes return null due do the infura api + // being backed by multiple out-of-sync clients + block = await this.query.getBlockByNumber(blockNumber, false) + // if block is null, wait 1 sec then try again + if (!block) await timeout(1000) + } + return block } /** -- cgit From 5be154ea2035810462ff0e7051e537870bfc1afb Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 14:29:31 -0700 Subject: controllers - transactions - merge @frankiebee's work with mine --- app/scripts/controllers/transactions/index.js | 46 ++++++++--- .../controllers/transactions/nonce-tracker.js | 16 +--- .../controllers/transactions/pending-tx-tracker.js | 89 ++++------------------ app/scripts/lib/util.js | 14 ++++ 4 files changed, 69 insertions(+), 96 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 7cb8af3a8..f84fd95ff 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -78,7 +78,7 @@ class TransactionController extends EventEmitter { }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) - this._setupListners() + this._setupListeners() // memstore is computed from a few different stores this._updateMemstore() this.txStateManager.store.subscribe(() => this._updateMemstore()) @@ -382,8 +382,9 @@ class TransactionController extends EventEmitter { is called in constructor applies the listeners for pendingTxTracker txStateManager and blockTracker */ - _setupListners () { + _setupListeners () { this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update')) + this._setupBlockTrackerListener() this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) @@ -399,13 +400,6 @@ class TransactionController extends EventEmitter { txMeta.retryCount++ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry') }) - - this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker)) - // this is a little messy but until ethstore has been either - // removed or redone this is to guard against the race condition - this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) - this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker)) - } /** @@ -429,6 +423,40 @@ class TransactionController extends EventEmitter { }) } + _setupBlockTrackerListener () { + let listenersAreActive = false + const latestBlockHandler = this._onLatestBlock.bind(this) + const blockTracker = this.blockTracker + const txStateManager = this.txStateManager + + txStateManager.on('tx:status-update', updateSubscription) + updateSubscription() + + function updateSubscription() { + const pendingTxs = txStateManager.getPendingTransactions() + if (!listenersAreActive && pendingTxs.length > 0) { + blockTracker.on('latest', latestBlockHandler) + listenersAreActive = true + } else if (listenersAreActive && !pendingTxs.length) { + blockTracker.removeListener('latest', latestBlockHandler) + listenersAreActive = false + } + } + } + + async _onLatestBlock (blockNumber) { + try { + await this.pendingTxTracker.updatePendingTxs() + } catch (err) { + log.error(err) + } + try { + await this.pendingTxTracker.resubmitPendingTxs(blockNumber) + } catch (err) { + log.error(err) + } + } + /** Updates the memStore in transaction controller */ diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js index 490118c89..fe2d25fca 100644 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -35,7 +35,7 @@ class NonceTracker { * @typedef NonceDetails * @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction. * @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method. - * @property {number} highetSuggested - The maximum between the other two, the number returned. + * @property {number} highestSuggested - The maximum between the other two, the number returned. */ /** @@ -75,14 +75,6 @@ class NonceTracker { return { nextNonce, nonceDetails, releaseLock } } - async _getCurrentBlock () { - const currentBlock = this.blockTracker.getCurrentBlock() - if (currentBlock) return currentBlock - return await new Promise((reject, resolve) => { - this.blockTracker.once('latest', resolve) - }) - } - async _globalMutexFree () { const globalMutex = this._lookupMutex('global') const release = await globalMutex.acquire() @@ -108,9 +100,8 @@ class NonceTracker { // 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 blockNumber = await this.blockTracker.getLatestBlock() + const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber) const baseCount = baseCountBN.toNumber() assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) const nonceDetails = { blockNumber, baseCount } @@ -171,6 +162,7 @@ class NonceTracker { return { name: 'local', nonce: highest, details: { startPoint, highest } } } + } module.exports = NonceTracker diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index e1bb67c90..e981e2991 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -24,60 +24,27 @@ class PendingTransactionTracker extends EventEmitter { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker - // default is one day this.getPendingTransactions = config.getPendingTransactions this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction this.confirmTransaction = config.confirmTransaction - this._checkPendingTxs() + this.updatePendingTxs() } /** - checks if a signed tx is in a block and - if it is included emits tx status as 'confirmed' - @param block {object}, a full block - @emits tx:confirmed - @emits tx:failed - */ - async checkForTxInBlock (blockNumber) { - const block = await this._getBlock(blockNumber) - const signedTxList = this.getPendingTransactions() - if (!signedTxList.length) return - signedTxList.forEach((txMeta) => { - const txHash = txMeta.hash - const txId = txMeta.id - - if (!txHash) { - const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') - noTxHashErr.name = 'NoTxHashError' - this.emit('tx:failed', txId, noTxHashErr) - return - } - - if (!block.transactions.length) return - - block.transactions.forEach((hash) => { - if (hash === txHash) { - this.confirmTransaction(txId) - } - }) - }) - } - - /** - asks the network for the transaction to see if a block number is included on it - if we have skipped/missed blocks - @param object - oldBlock newBlock + checks the network for signed txs and releases the nonce global lock if it is */ - queryPendingTxs ({ oldBlock, newBlock }) { - // check pending transactions on start - if (!oldBlock) { - this._checkPendingTxs() - return + async updatePendingTxs () { + const pendingTxs = this.getPendingTransactions() + // in order to keep the nonceTracker accurate we block it while updating pending transactions + const nonceGlobalLock = await this.nonceTracker.getGlobalLock() + try { + await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta))) + } catch (err) { + log.error('PendingTransactionTracker - Error updating pending transactions') + log.error(err) } - // if we synced by more than one block, check for missed pending transactions - const diff = Number.parseInt(newBlock, 16) - Number.parseInt(oldBlock, 16) - if (diff > 1) this._checkPendingTxs() + nonceGlobalLock.releaseLock() } /** @@ -151,6 +118,7 @@ class PendingTransactionTracker extends EventEmitter { this.emit('tx:retry', txMeta) return txHash } + /** Ask the network for the transaction to see if it has been include in a block @param txMeta {Object} - the txMeta object @@ -180,9 +148,8 @@ class PendingTransactionTracker extends EventEmitter { } // get latest transaction status - let txParams try { - txParams = await this.query.getTransactionByHash(txHash) + const txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return if (txParams.blockNumber) { this.confirmTransaction(txId) @@ -196,34 +163,6 @@ class PendingTransactionTracker extends EventEmitter { } } - /** - checks the network for signed txs and releases the nonce global lock if it is - */ - async _checkPendingTxs () { - const signedTxList = this.getPendingTransactions() - // in order to keep the nonceTracker accurate we block it while updating pending transactions - const nonceGlobalLock = await this.nonceTracker.getGlobalLock() - try { - await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta))) - } catch (err) { - log.error('PendingTransactionWatcher - Error updating pending transactions') - log.error(err) - } - nonceGlobalLock.releaseLock() - } - - async _getBlock (blockNumber) { - let block - while (!block) { - // block requests will sometimes return null due do the infura api - // being backed by multiple out-of-sync clients - block = await this.query.getBlockByNumber(blockNumber, false) - // if block is null, wait 1 sec then try again - if (!block) await timeout(1000) - } - return block - } - /** checks to see if a confirmed txMeta has the same nonce @param txMeta {Object} - txMeta object diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 431d1e59c..7ceb9da3c 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -99,7 +99,21 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) { return targetBN.mul(numBN).div(denomBN) } +function applyListeners (listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.on(key, listeners[key]) + }) +} + +function removeListeners (listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.removeListener(key, listeners[key]) + }) +} + module.exports = { + removeListeners, + applyListeners, getStack, getEnvironmentType, sufficientBalance, -- cgit From 1b3fedd10d6209fe4c7dfcdc9e90a23c3972bf16 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 15:54:47 -0700 Subject: controllers - transaction - pending-tx-tracker - lint fix --- app/scripts/controllers/transactions/pending-tx-tracker.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index e981e2991..68f016d79 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -1,7 +1,7 @@ const EventEmitter = require('events') const log = require('loglevel') const EthQuery = require('ethjs-query') -const timeout = (duration) => new Promise(resolve => setTimeout(resolve, duration)) + /** Event emitter utility class for tracking the transactions as they
-- cgit From fe42de46422c89156e91fb08ee06f50511c8601f Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 22:58:14 -0700 Subject: metamask-controller - update preferences controller addresses after import account --- app/scripts/metamask-controller.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 796c9385a..ed606d4ab 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -345,7 +345,7 @@ module.exports = class MetamaskController extends EventEmitter { verifySeedPhrase: nodeify(this.verifySeedPhrase, this), clearSeedWordCache: this.clearSeedWordCache.bind(this), resetAccount: nodeify(this.resetAccount, this), - importAccountWithStrategy: this.importAccountWithStrategy.bind(this), + importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this), // vault management submitPassword: nodeify(keyringController.submitPassword, keyringController), @@ -603,15 +603,15 @@ module.exports = class MetamaskController extends EventEmitter { * @param {any} args - The data required by that strategy to import an account. * @param {Function} cb - A callback function called with a state update on success. */ - importAccountWithStrategy (strategy, args, cb) { - accountImporter.importAccount(strategy, args) - .then((privateKey) => { - return this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) - }) - .then(keyring => keyring.getAccounts()) - .then((accounts) => this.preferencesController.setSelectedAddress(accounts[0])) - .then(() => { cb(null, this.keyringController.fullUpdate()) }) - .catch((reason) => { cb(reason) }) + async importAccountWithStrategy (strategy, args) { + const privateKey = await accountImporter.importAccount(strategy, args) + const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) + const accounts = await keyring.getAccounts() + // update accounts in preferences controller + const allAccounts = await keyringController.getAccounts() + this.preferencesController.setAddresses(allAccounts) + // set new account as selected + await this.preferencesController.setSelectedAddress(accounts[0]) } // --------------------------------------------------------------------------- -- cgit From c9f3404ca5b04df859f765f9f8e90f21737142c1 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 28 May 2018 23:14:38 -0700 Subject: metamask-controller - lint fix --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ed606d4ab..2e74b3a20 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -608,7 +608,7 @@ module.exports = class MetamaskController extends EventEmitter { const keyring = await this.keyringController.addNewKeyring('Simple Key Pair', [ privateKey ]) const accounts = await keyring.getAccounts() // update accounts in preferences controller - const allAccounts = await keyringController.getAccounts() + const allAccounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(allAccounts) // set new account as selected await this.preferencesController.setSelectedAddress(accounts[0]) -- cgit From 16d0db15e05ec97adfcb050901d84a5130e88892 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 29 May 2018 00:41:28 -0700 Subject: controllers - transactions - fix tx confirmation --- app/scripts/controllers/transactions/index.js | 2 +- app/scripts/controllers/transactions/pending-tx-tracker.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index f84fd95ff..6266fea16 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -57,7 +57,6 @@ class TransactionController extends EventEmitter { initState: opts.initState, txHistoryLimit: opts.txHistoryLimit, getNetwork: this.getNetwork.bind(this), - confirmTransaction: this.confirmTransaction.bind(this), }) this._onBootCleanUp() @@ -389,6 +388,7 @@ class TransactionController extends EventEmitter { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) + this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 68f016d79..9a764b962 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -28,7 +28,6 @@ class PendingTransactionTracker extends EventEmitter { this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction this.confirmTransaction = config.confirmTransaction - this.updatePendingTxs() } /** @@ -37,6 +36,7 @@ class PendingTransactionTracker extends EventEmitter { async updatePendingTxs () { const pendingTxs = this.getPendingTransactions() // in order to keep the nonceTracker accurate we block it while updating pending transactions + console.log('updating pending txs....', pendingTxs) const nonceGlobalLock = await this.nonceTracker.getGlobalLock() try { await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta))) @@ -152,7 +152,7 @@ class PendingTransactionTracker extends EventEmitter { const txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return if (txParams.blockNumber) { - this.confirmTransaction(txId) + this.emit('tx:confirmed', txId) } } catch (err) { txMeta.warning = { -- cgit From 58de5671cc26a8848b9e0e02bcd6d18bdfcd3ea8 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 29 May 2018 00:53:44 -0700 Subject: controllers - transactions - fix tx status update on boot --- app/scripts/controllers/transactions/index.js | 12 ++++++++++++ app/scripts/controllers/transactions/pending-tx-tracker.js | 3 +-- 2 files changed, 13 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 6266fea16..71e7ea920 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -83,7 +83,11 @@ class TransactionController extends EventEmitter { this.txStateManager.store.subscribe(() => this._updateMemstore()) this.networkStore.subscribe(() => this._updateMemstore()) this.preferencesStore.subscribe(() => this._updateMemstore()) + + // request state update to finalize initialization + this._updatePendingTxsAfterFirstBlock() } + /** @returns {number} the chainId*/ getChainId () { const networkState = this.networkStore.getState() @@ -349,6 +353,14 @@ class TransactionController extends EventEmitter { this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts) } + // called once on startup + async _updatePendingTxsAfterFirstBlock () { + // wait for first block so we know we're ready + await this.blockTracker.getLatestBlock() + // get status update for all pending transactions (for the current network) + await this.pendingTxTracker.updatePendingTxs() + } + /** If transaction controller was rebooted with transactions that are uncompleted in steps of the transaction signing or user confirmation process it will either diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 9a764b962..70cac096b 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -34,11 +34,10 @@ class PendingTransactionTracker extends EventEmitter { checks the network for signed txs and releases the nonce global lock if it is */ async updatePendingTxs () { - const pendingTxs = this.getPendingTransactions() // in order to keep the nonceTracker accurate we block it while updating pending transactions - console.log('updating pending txs....', pendingTxs) const nonceGlobalLock = await this.nonceTracker.getGlobalLock() try { + const pendingTxs = this.getPendingTransactions() await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta))) } catch (err) { log.error('PendingTransactionTracker - Error updating pending transactions') -- cgit From ffb8fa16497d0c4a48e3bd4b918dde1ac3adcd5d Mon Sep 17 00:00:00 2001 From: kumavis Date: Wed, 6 Jun 2018 11:17:52 -0700 Subject: lint - remove unused require --- app/scripts/metamask-controller.js | 1 - 1 file changed, 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b206c6b67..b1d0217fd 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -45,7 +45,6 @@ const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') -const cleanErrorStack = require('./lib/cleanErrorStack') const DiagnosticsReporter = require('./lib/diagnostics-reporter') const log = require('loglevel') -- cgit From 3ce83570ee336524e1ab0da50a9f47744cddb193 Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 7 Jun 2018 12:26:37 -0700 Subject: network - provider - infura - use block-reemit middleware --- app/scripts/controllers/network/createInfuraClient.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index adbf4c001..663a2595a 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -1,6 +1,6 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') -const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') @@ -15,7 +15,7 @@ function createInfuraClient ({ network }) { const blockTracker = new BlockTracker({ provider: blockProvider }) const networkMiddleware = mergeMiddleware([ - createBlockRefMiddleware({ blockTracker }), + createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }), createBlockCacheMiddleware({ blockTracker }), createInflightMiddleware(), createBlockTrackerInspectorMiddleware({ blockTracker }), -- cgit From 0db776c3cc58e817d108d38ca389893cb13e3f92 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Jun 2018 10:17:09 -0700 Subject: lint - controllers - whitepace fix --- app/scripts/controllers/currency.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index 480c08b1c..d5bc5fe2b 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -1,4 +1,4 @@ - const ObservableStore = require('obs-store') +const ObservableStore = require('obs-store') const extend = require('xtend') const log = require('loglevel') @@ -16,9 +16,9 @@ class CurrencyController { * currentCurrency, conversionRate and conversionDate properties * @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently * selected by the user - * @property {number} conversionRate The conversion rate from ETH to the selected currency. + * @property {number} conversionRate The conversion rate from ETH to the selected currency. * @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds - * since midnight of January 1, 1970 + * since midnight of January 1, 1970 * @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method. * Used to clear an existing interval on subsequent calls of that method. * @@ -59,7 +59,7 @@ class CurrencyController { /** * A getter for the conversionRate property * - * @returns {string} The conversion rate from ETH to the selected currency. + * @returns {string} The conversion rate from ETH to the selected currency. * */ getConversionRate () { @@ -80,7 +80,7 @@ class CurrencyController { * A getter for the conversionDate property * * @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of - * January 1, 1970 + * January 1, 1970 * */ getConversionDate () { -- cgit From 02f5502e16fefc8d92392e614861e3f672c4f909 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Jun 2018 11:04:28 -0700 Subject: test - e2e - inject metamask config to point at localhost --- app/scripts/background.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 56e190f97..41fd89016 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -18,7 +18,7 @@ const migrations = require('./migrations/') const PortStream = require('./lib/port-stream.js') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const firstTimeState = require('./first-time-state') +const rawFirstTimeState = require('./first-time-state') const setupRaven = require('./lib/setupRaven') const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry') const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') @@ -31,6 +31,8 @@ const { ENVIRONMENT_TYPE_FULLSCREEN, } = require('./lib/enums') +const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_CONFIG) + const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = process.env.METAMASK_DEBUG -- cgit From ebb9447593a877cd299e701ddfcb217070068fac Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 11 Jun 2018 14:25:49 -0700 Subject: test - e2e - factor out setup phase + rename METAMASK_CONFIG to METAMASK_TEST_CONFIG --- app/scripts/background.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 41fd89016..9866ff0b0 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -31,7 +31,8 @@ const { ENVIRONMENT_TYPE_FULLSCREEN, } = require('./lib/enums') -const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_CONFIG) +// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost +const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG) const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = process.env.METAMASK_DEBUG -- cgit From c86f93588965291837ae905632ba9a8855f496f2 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 12 Jun 2018 10:55:54 -0700 Subject: nonce-tracker - wrap nonce calculations in try-catch and release lock on error --- .../controllers/transactions/nonce-tracker.js | 50 ++++++++++++---------- 1 file changed, 28 insertions(+), 22 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js index fe2d25fca..14581c998 100644 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -50,29 +50,35 @@ class NonceTracker { await this._globalMutexFree() // await lock free, then take lock const releaseLock = await this._takeMutex(address) - // evaluate multiple nextNonce strategies - const nonceDetails = {} - const networkNonceResult = await this._getNetworkNextNonce(address) - const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) - const nextNetworkNonce = networkNonceResult.nonce - const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed) - - const pendingTxs = this.getPendingTransactions(address) - const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 - - nonceDetails.params = { - highestLocallyConfirmed, - highestSuggested, - nextNetworkNonce, + try { + // evaluate multiple nextNonce strategies + const nonceDetails = {} + const networkNonceResult = await this._getNetworkNextNonce(address) + const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) + const nextNetworkNonce = networkNonceResult.nonce + const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed) + + const pendingTxs = this.getPendingTransactions(address) + const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 + + nonceDetails.params = { + highestLocallyConfirmed, + 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}"`) + + // return nonce and release cb + return { nextNonce, nonceDetails, releaseLock } + } catch (err) { + // release lock if we encounter an error + releaseLock() + throw err } - 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}"`) - - // return nonce and release cb - return { nextNonce, nonceDetails, releaseLock } } async _globalMutexFree () { -- cgit From 6a2649a90f5147b505ebc33b97a75d2e90956fca Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 12 Jun 2018 11:12:32 -0700 Subject: network - import createBlockTrackerInspectorMiddleware and rearrange cache middleware order --- app/scripts/controllers/network/createInfuraClient.js | 15 ++------------- app/scripts/controllers/network/createJsonRpcClient.js | 13 +------------ app/scripts/controllers/network/createLocalhostClient.js | 13 +------------ 3 files changed, 4 insertions(+), 37 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 663a2595a..6976e6022 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -3,6 +3,7 @@ const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const createInfuraMiddleware = require('eth-json-rpc-infura') const BlockTracker = require('eth-block-tracker') @@ -15,23 +16,11 @@ function createInfuraClient ({ network }) { const blockTracker = new BlockTracker({ provider: blockProvider }) const networkMiddleware = mergeMiddleware([ - createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }), createBlockCacheMiddleware({ blockTracker }), createInflightMiddleware(), + createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }), createBlockTrackerInspectorMiddleware({ blockTracker }), infuraMiddleware, ]) return { networkMiddleware, blockTracker } } - -// inspect if response contains a block ref higher than our latest block -const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt'] -function createBlockTrackerInspectorMiddleware ({ blockTracker }) { - return createAsyncMiddleware(async (req, res, next) => { - if (!futureBlockRefRequests.includes(req.method)) return next() - await next() - const blockNumber = Number.parseInt(res.result.blockNumber, 16) - const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16) - if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock() - }) -} diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index d712ed135..b9aca0bcf 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -4,6 +4,7 @@ const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const BlockTracker = require('eth-block-tracker') @@ -23,15 +24,3 @@ function createJsonRpcClient ({ rpcUrl }) { ]) return { networkMiddleware, blockTracker } } - -// inspect if response contains a block ref higher than our latest block -const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt'] -function createBlockTrackerInspectorMiddleware ({ blockTracker }) { - return createAsyncMiddleware(async (req, res, next) => { - if (!futureBlockRefRequests.includes(req.method)) return next() - await next() - const blockNumber = Number.parseInt(res.result.blockNumber, 16) - const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16) - if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock() - }) -} diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js index 990dc6a95..ae42ac48b 100644 --- a/app/scripts/controllers/network/createLocalhostClient.js +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -2,6 +2,7 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const BlockTracker = require('eth-block-tracker') @@ -19,15 +20,3 @@ function createLocalhostClient () { ]) return { networkMiddleware, blockTracker } } - -// inspect if response contains a block ref higher than our latest block -const futureBlockRefRequests = ['eth_getTransactionByHash', 'eth_getTransactionReceipt'] -function createBlockTrackerInspectorMiddleware ({ blockTracker }) { - return createAsyncMiddleware(async (req, res, next) => { - if (!futureBlockRefRequests.includes(req.method)) return next() - await next() - const blockNumber = Number.parseInt(res.result.blockNumber, 16) - const currentBlockNumber = Number.parseInt(blockTracker.getCurrentBlock(), 16) - if (blockNumber > currentBlockNumber) await blockTracker.checkForLatestBlock() - }) -} -- cgit From 055346843bc90a5168151ba2adc9deacedf8afd4 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 12 Jun 2018 11:27:32 -0700 Subject: lint - fix lint for network --- app/scripts/controllers/network/createInfuraClient.js | 1 - app/scripts/controllers/network/createJsonRpcClient.js | 1 - app/scripts/controllers/network/createLocalhostClient.js | 1 - 3 files changed, 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 6976e6022..41af4d9f9 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -1,5 +1,4 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index b9aca0bcf..40c353f7f 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -1,5 +1,4 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js index ae42ac48b..fecc512e8 100644 --- a/app/scripts/controllers/network/createLocalhostClient.js +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -1,5 +1,4 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') -const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') -- cgit From 5d7c2810a701097ef1a4f9de23948418340f9cb4 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Jun 2018 15:05:41 -0700 Subject: Begin adding eth_watchToken --- app/scripts/background.js | 2 ++ app/scripts/controllers/preferences.js | 25 +++++++++++++++++++++++++ app/scripts/metamask-controller.js | 1 + 3 files changed, 28 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 2451cddb6..2be600c4b 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -248,6 +248,7 @@ function setupController (initState, initLangCode) { showUnconfirmedMessage: triggerUi, unlockAccountMessage: triggerUi, showUnapprovedTx: triggerUi, + showAddTokenUi: triggerUi, // initial state initState, // initial locale code @@ -436,3 +437,4 @@ extension.runtime.onInstalled.addListener(function (details) { extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) } }) + diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 8411e3a28..8a8b9a335 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -25,6 +25,7 @@ class PreferencesController { frequentRpcList: [], currentAccountTab: 'history', tokens: [], + suggestedTokens: [], useBlockie: false, featureFlags: {}, currentLocale: opts.initLangCode, @@ -48,6 +49,30 @@ class PreferencesController { this.store.updateState({ useBlockie: val }) } + getSuggestedTokens () { + return this.store.getState().suggestedTokens + } + + /** + * RPC engine middleware for requesting new token added + * + * @param req + * @param res + * @param {Function} - next + * @param {Function} - end + */ + requestAddToken(req, res, next, end) { + if (req.method === 'eth_watchToken') { + // Validate params! + // this.suggestedTokens.push(req.params) + const [ rawAddress, symbol, decimals ] = req.params + this.addToken(rawAddress, symbol, decimals) + end(null, rawAddress) + } else { + next() + } + } + /** * Getter for the `useBlockie` property * diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d40a351a5..0af7c5051 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1077,6 +1077,7 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(createOriginMiddleware({ origin })) engine.push(createLoggerMiddleware({ origin })) engine.push(filterMiddleware) + engine.push(this.preferencesController.requestAddToken.bind(this.preferencesController)) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection -- cgit From f14ed329801ab65c31e84f8e9d8d93700ed56670 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Jun 2018 15:33:50 -0700 Subject: Begin letting UI show suggested tokens --- app/scripts/controllers/preferences.js | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 8a8b9a335..b76141be4 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -25,7 +25,7 @@ class PreferencesController { frequentRpcList: [], currentAccountTab: 'history', tokens: [], - suggestedTokens: [], + suggestedTokens: {}, useBlockie: false, featureFlags: {}, currentLocale: opts.initLangCode, @@ -53,6 +53,13 @@ class PreferencesController { return this.store.getState().suggestedTokens } + addSuggestedToken (tokenOpts) { + // TODO: Validate params + const suggested = this.getSuggestedTokens() + suggested[tokenOpts.address] = suggested + this.store.updateState({ suggestedTokens: suggested }) + } + /** * RPC engine middleware for requesting new token added * @@ -63,13 +70,24 @@ class PreferencesController { */ requestAddToken(req, res, next, end) { if (req.method === 'eth_watchToken') { - // Validate params! - // this.suggestedTokens.push(req.params) + // TODO: Validate params! const [ rawAddress, symbol, decimals ] = req.params - this.addToken(rawAddress, symbol, decimals) - end(null, rawAddress) + + const tokenOpts = { + address: rawAddress, + decimals, + symbol, + } + + this.suggestWatchToken() + + return end(null, { + result: rawAddress, + "jsonrpc": "2.0", + id: req.id, + }) } else { - next() + return next() } } -- cgit From 5e4f3e430a9057b073cfc82255fe62c5e8550e44 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Jun 2018 15:37:37 -0700 Subject: Get popup appearing when suggesting new token --- app/scripts/controllers/preferences.js | 4 +++- app/scripts/metamask-controller.js | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index b76141be4..e33501cd0 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -36,6 +36,7 @@ class PreferencesController { this.diagnostics = opts.diagnostics this.store = new ObservableStore(initState) + this.showAddTokenUi = opts.showAddTokenUi } // PUBLIC METHODS @@ -79,7 +80,8 @@ class PreferencesController { symbol, } - this.suggestWatchToken() + this.addSuggestedToken(tokenOpts) + this.showAddTokenUi() return end(null, { result: rawAddress, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 0af7c5051..d5627a0d1 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -85,6 +85,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, + showAddTokenUi: opts.showAddTokenUi, }) // currency controller -- cgit From 0481335dda447ba4c228d146834952bac0ad641b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 18 Jun 2018 15:50:27 -0700 Subject: Improved rpc-engine usage --- app/scripts/controllers/preferences.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index e33501cd0..f1bd66889 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -83,11 +83,7 @@ class PreferencesController { this.addSuggestedToken(tokenOpts) this.showAddTokenUi() - return end(null, { - result: rawAddress, - "jsonrpc": "2.0", - id: req.id, - }) + return end(rawAddress) } else { return next() } -- cgit From 081884bd8095b2027e88fabdfe297f6d2fc8c38e Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Fri, 3 Aug 2018 16:42:13 -0400 Subject: rpc-engine not crashing when eth_watchToken --- app/scripts/controllers/preferences.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 8a4a63bb6..50f716852 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -57,7 +57,7 @@ class PreferencesController { addSuggestedToken (tokenOpts) { // TODO: Validate params const suggested = this.getSuggestedTokens() - suggested[tokenOpts.address] = suggested + suggested[tokenOpts.address] = tokenOpts this.store.updateState({ suggestedTokens: suggested }) } @@ -69,11 +69,10 @@ class PreferencesController { * @param {Function} - next * @param {Function} - end */ - requestAddToken(req, res, next, end) { + requestAddToken (req, res, next, end) { if (req.method === 'eth_watchToken') { // TODO: Validate params! const [ rawAddress, symbol, decimals ] = req.params - const tokenOpts = { address: rawAddress, decimals, @@ -82,8 +81,8 @@ class PreferencesController { this.addSuggestedToken(tokenOpts) this.showAddTokenUi() - - return end(rawAddress) + res.result = rawAddress + return end() } else { return next() } -- cgit From 9ac9f53a73357238ed2ee0ce57c65de592cfd968 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Fri, 3 Aug 2018 19:24:12 -0400 Subject: eth_watchToken working --- app/scripts/controllers/preferences.js | 7 +++++++ app/scripts/metamask-controller.js | 1 + 2 files changed, 8 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 50f716852..521a68a66 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -211,6 +211,13 @@ class PreferencesController { return selected } + removeSuggestedTokens () { + return new Promise((resolve, reject) => { + this.store.updateState({ suggestedTokens: {} }) + resolve() + }) + } + /** * Setter for the `selectedAddress` property * diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index e843ec660..801363cb0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -392,6 +392,7 @@ module.exports = class MetamaskController extends EventEmitter { setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), addToken: nodeify(preferencesController.addToken, preferencesController), removeToken: nodeify(preferencesController.removeToken, preferencesController), + removeSuggestedTokens: nodeify(preferencesController.removeSuggestedTokens, preferencesController), setCurrentAccountTab: nodeify(preferencesController.setCurrentAccountTab, preferencesController), setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), -- cgit From 78ad3c38e2c9cfce8b0756c7d0df8264316d1d21 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Mon, 6 Aug 2018 18:28:47 -0400 Subject: add suggested token params validation --- app/scripts/controllers/preferences.js | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 521a68a66..3bbd48f06 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize +const isValidAddress = require('ethereumjs-util').isValidAddress const extend = require('xtend') @@ -55,9 +56,12 @@ class PreferencesController { } addSuggestedToken (tokenOpts) { - // TODO: Validate params + this._validateSuggestedTokenParams(tokenOpts) const suggested = this.getSuggestedTokens() - suggested[tokenOpts.address] = tokenOpts + const { rawAddress, symbol, decimals } = tokenOpts + const address = normalizeAddress(rawAddress) + const newEntry = { address, symbol, decimals } + suggested[address] = newEntry this.store.updateState({ suggestedTokens: suggested }) } @@ -71,10 +75,10 @@ class PreferencesController { */ requestAddToken (req, res, next, end) { if (req.method === 'eth_watchToken') { - // TODO: Validate params! const [ rawAddress, symbol, decimals ] = req.params + this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) const tokenOpts = { - address: rawAddress, + rawAddress, decimals, symbol, } @@ -423,6 +427,23 @@ class PreferencesController { // // PRIVATE METHODS // + + /** + * Validates that the passed options for suggested token have all required properties. + * + * @param {Object} opts The options object to validate + * @throws {string} Throw a custom error indicating that address, symbol and/or decimals + * doesn't fulfill requirements + * + */ + _validateSuggestedTokenParams (opts) { + const { rawAddress, symbol, decimals } = opts + if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`) + if (!(symbol.length < 5)) throw new Error(`Invalid symbol ${symbol} more than four characters`) + const numDecimals = parseInt(decimals, 10) + if (isNaN(numDecimals) || numDecimals > 18 || numDecimals < 0) throw new Error(`Invalid decimals ${decimals}`) + if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) + } } module.exports = PreferencesController -- cgit From c2d4b237ebef45ff19ddcec5f364a251cd7662bf Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 7 Aug 2018 01:35:30 -0700 Subject: network - fix blockTracker reference to return the blockTrackerProxy instead of the direct blockTracker reference --- app/scripts/controllers/network/network.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 628e32fa4..76fdc3391 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -59,7 +59,7 @@ module.exports = class NetworkController extends EventEmitter { // return the proxies so the references will always be good getProviderAndBlockTracker () { const provider = this._providerProxy - const blockTracker = this._blockTracker + const blockTracker = this._blockTrackerProxy return { provider, blockTracker } } -- cgit From 15ea8c04b28a9f89999c96caf188d157e5230a55 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 7 Aug 2018 17:53:36 -0400 Subject: fix merge --- app/scripts/controllers/preferences.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index a42bb77b3..a6530424d 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -454,6 +454,7 @@ class PreferencesController { const numDecimals = parseInt(decimals, 10) if (isNaN(numDecimals) || numDecimals > 18 || numDecimals < 0) throw new Error(`Invalid decimals ${decimals}`) if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) + } /** * Subscription to network provider type. -- cgit From 33357e3538b5157a852323d5f1e2db7f19b3303e Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 7 Aug 2018 19:12:16 -0400 Subject: refactor unused code --- app/scripts/controllers/preferences.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index a6530424d..4aa91534d 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -452,10 +452,12 @@ class PreferencesController { if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`) if (!(symbol.length < 5)) throw new Error(`Invalid symbol ${symbol} more than four characters`) const numDecimals = parseInt(decimals, 10) - if (isNaN(numDecimals) || numDecimals > 18 || numDecimals < 0) throw new Error(`Invalid decimals ${decimals}`) + if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { + throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) + } if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) } - + /** * Subscription to network provider type. * -- cgit From 9666eaba668f7b288c42366ba04add951e6aa3b5 Mon Sep 17 00:00:00 2001 From: hahnmichaelf Date: Sun, 12 Aug 2018 18:57:58 -0400 Subject: base - working. fixes #4774 --- app/scripts/contentscript.js | 2 +- app/scripts/esdb-replace.js | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 app/scripts/esdb-replace.js (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index e0a2b0061..60ef97e5e 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -199,5 +199,5 @@ function blacklistedDomainCheck () { function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') - window.location.href = extensionURL + window.location.href = extensionURL + "#" + window.location.hostname } diff --git a/app/scripts/esdb-replace.js b/app/scripts/esdb-replace.js new file mode 100644 index 000000000..ae5991586 --- /dev/null +++ b/app/scripts/esdb-replace.js @@ -0,0 +1,5 @@ +window.onload = function() { + if (window.location.pathname === "/phishing.html") { + document.getElementById("esdbLink").innerHTML = "To read more about this scam, navigate to: https://etherscamdb.info/domain/" + window.location.hash.substring(1) + "" + } +} -- cgit From 7d2d71bcbc9779b7a25f56a67264cac876356869 Mon Sep 17 00:00:00 2001 From: hahnmichaelf Date: Mon, 13 Aug 2018 12:40:16 -0400 Subject: change name --- app/scripts/esdb-replace.js | 5 ----- app/scripts/phishing-detect.js | 5 +++++ 2 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 app/scripts/esdb-replace.js create mode 100644 app/scripts/phishing-detect.js (limited to 'app/scripts') diff --git a/app/scripts/esdb-replace.js b/app/scripts/esdb-replace.js deleted file mode 100644 index ae5991586..000000000 --- a/app/scripts/esdb-replace.js +++ /dev/null @@ -1,5 +0,0 @@ -window.onload = function() { - if (window.location.pathname === "/phishing.html") { - document.getElementById("esdbLink").innerHTML = "To read more about this scam, navigate to: https://etherscamdb.info/domain/" + window.location.hash.substring(1) + "" - } -} diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js new file mode 100644 index 000000000..ae5991586 --- /dev/null +++ b/app/scripts/phishing-detect.js @@ -0,0 +1,5 @@ +window.onload = function() { + if (window.location.pathname === "/phishing.html") { + document.getElementById("esdbLink").innerHTML = "To read more about this scam, navigate to: https://etherscamdb.info/domain/" + window.location.hash.substring(1) + "" + } +} -- cgit From 4808ce25ebdc566a13acbb9cb1c6815368b7c4a1 Mon Sep 17 00:00:00 2001 From: hahnmichaelf Date: Mon, 13 Aug 2018 13:16:37 -0400 Subject: fix for lint-test --- app/scripts/contentscript.js | 2 +- app/scripts/phishing-detect.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 60ef97e5e..b6d5cfe9e 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -199,5 +199,5 @@ function blacklistedDomainCheck () { function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') - window.location.href = extensionURL + "#" + window.location.hostname + window.location.href = extensionURL + '#' + window.location.hostname } diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index ae5991586..a66cdb5ac 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -1,5 +1,5 @@ window.onload = function() { - if (window.location.pathname === "/phishing.html") { - document.getElementById("esdbLink").innerHTML = "To read more about this scam, navigate to: https://etherscamdb.info/domain/" + window.location.hash.substring(1) + "" + if (window.location.pathname === '/phishing.html') { + document.getElementById("esdbLink").innerHTML = 'To read more about this scam, navigate to: https://etherscamdb.info/domain/' + window.location.hash.substring(1) + '' } } -- cgit From e19eb7bc035670d0f05e34840e6565bc1f907de3 Mon Sep 17 00:00:00 2001 From: hahnmichaelf Date: Mon, 13 Aug 2018 13:21:18 -0400 Subject: additional lint-test fix --- app/scripts/phishing-detect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index a66cdb5ac..4168b6618 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -1,5 +1,5 @@ window.onload = function() { if (window.location.pathname === '/phishing.html') { - document.getElementById("esdbLink").innerHTML = 'To read more about this scam, navigate to: https://etherscamdb.info/domain/' + window.location.hash.substring(1) + '' + document.getElementById('esdbLink').innerHTML = 'To read more about this scam, navigate to: https://etherscamdb.info/domain/' + window.location.hash.substring(1) + '' } } -- cgit From 8f5b80a0fe13c53a602a5b2883ae1cdfba0123e1 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 14 Aug 2018 13:58:47 -0300 Subject: update method to metamask_watchToken --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 4aa91534d..4cc08a9af 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -77,7 +77,7 @@ class PreferencesController { * @param {Function} - end */ requestAddToken (req, res, next, end) { - if (req.method === 'eth_watchToken') { + if (req.method === 'metamask_watchToken') { const [ rawAddress, symbol, decimals ] = req.params this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) const tokenOpts = { -- cgit From a4c3f6b65c9a25da0319b9077d830c23f729b32f Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 14 Aug 2018 20:08:12 -0300 Subject: add support for images base64 and urls on new ui --- app/scripts/controllers/preferences.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 4cc08a9af..a92db15c7 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -61,9 +61,9 @@ class PreferencesController { addSuggestedToken (tokenOpts) { this._validateSuggestedTokenParams(tokenOpts) const suggested = this.getSuggestedTokens() - const { rawAddress, symbol, decimals } = tokenOpts + const { rawAddress, symbol, decimals, imageUrl } = tokenOpts const address = normalizeAddress(rawAddress) - const newEntry = { address, symbol, decimals } + const newEntry = { address, symbol, decimals, imageUrl } suggested[address] = newEntry this.store.updateState({ suggestedTokens: suggested }) } @@ -78,12 +78,13 @@ class PreferencesController { */ requestAddToken (req, res, next, end) { if (req.method === 'metamask_watchToken') { - const [ rawAddress, symbol, decimals ] = req.params + const [ rawAddress, symbol, decimals, imageUrl ] = req.params this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) const tokenOpts = { rawAddress, decimals, symbol, + imageUrl, } this.addSuggestedToken(tokenOpts) @@ -283,10 +284,9 @@ class PreferencesController { * @returns {Promise} Promises the new array of AddedToken objects. * */ - async addToken (rawAddress, symbol, decimals) { + async addToken (rawAddress, symbol, decimals, imageUrl) { const address = normalizeAddress(rawAddress) - const newEntry = { address, symbol, decimals } - + const newEntry = { address, symbol, decimals, imageUrl } const tokens = this.store.getState().tokens const previousEntry = tokens.find((token, index) => { return token.address === address @@ -299,6 +299,7 @@ class PreferencesController { tokens.push(newEntry) } this._updateAccountTokens(tokens) + return Promise.resolve(tokens) } -- cgit From a4b6b2357a2eee7a4286a8490b8d31aac487120d Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 14 Aug 2018 20:09:56 -0300 Subject: watchToken to watchAsset --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index a92db15c7..04c9a3254 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -77,7 +77,7 @@ class PreferencesController { * @param {Function} - end */ requestAddToken (req, res, next, end) { - if (req.method === 'metamask_watchToken') { + if (req.method === 'metamask_watchAsset') { const [ rawAddress, symbol, decimals, imageUrl ] = req.params this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) const tokenOpts = { -- cgit From b766104c8d8fc4d4b1c5660af54b791243836f30 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 15 Aug 2018 18:34:57 -0300 Subject: add suggested tokens objects in metamask state --- app/scripts/controllers/preferences.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 04c9a3254..bda521bdd 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -15,6 +15,7 @@ class PreferencesController { * @property {string} store.currentAccountTab Indicates the selected tab in the ui * @property {array} store.tokens The tokens the user wants display in their token lists * @property {object} store.accountTokens The tokens stored per account and then per network type + * @property {object} store.objects Contains assets objects related to * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature @@ -28,6 +29,7 @@ class PreferencesController { currentAccountTab: 'history', accountTokens: {}, tokens: [], + objects: {}, suggestedTokens: {}, useBlockie: false, featureFlags: {}, @@ -58,6 +60,10 @@ class PreferencesController { return this.store.getState().suggestedTokens } + getObjects () { + return this.store.getState().objects + } + addSuggestedToken (tokenOpts) { this._validateSuggestedTokenParams(tokenOpts) const suggested = this.getSuggestedTokens() @@ -286,8 +292,9 @@ class PreferencesController { */ async addToken (rawAddress, symbol, decimals, imageUrl) { const address = normalizeAddress(rawAddress) - const newEntry = { address, symbol, decimals, imageUrl } + const newEntry = { address, symbol, decimals } const tokens = this.store.getState().tokens + const objects = this.getObjects() const previousEntry = tokens.find((token, index) => { return token.address === address }) @@ -298,8 +305,9 @@ class PreferencesController { } else { tokens.push(newEntry) } - this._updateAccountTokens(tokens) - + objects[address] = imageUrl + this._updateAccountTokens(tokens, objects) + console.log('OBJECTS OBJET', this.getObjects()) return Promise.resolve(tokens) } @@ -312,8 +320,10 @@ class PreferencesController { */ removeToken (rawAddress) { const tokens = this.store.getState().tokens + const objects = this.getObjects() const updatedTokens = tokens.filter(token => token.address !== rawAddress) - this._updateAccountTokens(updatedTokens) + const updatedObjects = Object.keys(objects).filter(key => key !== rawAddress) + this._updateAccountTokens(updatedTokens, updatedObjects) return Promise.resolve(updatedTokens) } @@ -477,10 +487,10 @@ class PreferencesController { * @param {array} tokens Array of tokens to be updated. * */ - _updateAccountTokens (tokens) { + _updateAccountTokens (tokens, objects) { const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates() accountTokens[selectedAddress][providerType] = tokens - this.store.updateState({ accountTokens, tokens }) + this.store.updateState({ accountTokens, tokens, objects }) } /** -- cgit From 5289a36664f180fae1dc6da07ccc80d307f7408c Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 15 Aug 2018 20:01:59 -0300 Subject: change watchAsset to new spec for type ERC20 --- app/scripts/controllers/preferences.js | 42 ++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 15 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index bda521bdd..9f5826dd9 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -75,7 +75,7 @@ class PreferencesController { } /** - * RPC engine middleware for requesting new token added + * RPC engine middleware for requesting new asset added * * @param req * @param res @@ -84,21 +84,18 @@ class PreferencesController { */ requestAddToken (req, res, next, end) { if (req.method === 'metamask_watchAsset') { - const [ rawAddress, symbol, decimals, imageUrl ] = req.params - this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) - const tokenOpts = { - rawAddress, - decimals, - symbol, - imageUrl, + const { type, options } = req.params + switch (type) { + case 'ERC20': + this._handleWatchAssetERC20(options, res) + res.result = options.address + break + default: + // TODO return promise for not handled assets } - - this.addSuggestedToken(tokenOpts) - this.showAddTokenUi() - res.result = rawAddress - return end() + end() } else { - return next() + next() } } @@ -307,7 +304,6 @@ class PreferencesController { } objects[address] = imageUrl this._updateAccountTokens(tokens, objects) - console.log('OBJECTS OBJET', this.getObjects()) return Promise.resolve(tokens) } @@ -520,6 +516,22 @@ class PreferencesController { const tokens = accountTokens[selectedAddress][providerType] return { tokens, accountTokens, providerType, selectedAddress } } + + /** + * Handle the suggestion of an ERC20 asset through `watchAsset` + * * + * @param {Object} options Parameters according to addition of ERC20 token + * + */ + _handleWatchAssetERC20 (options) { + // TODO handle bad parameters + const { address, symbol, decimals, imageUrl } = options + const rawAddress = address + this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) + const tokenOpts = { rawAddress, decimals, symbol, imageUrl } + this.addSuggestedToken(tokenOpts) + this.showAddTokenUi() + } } module.exports = PreferencesController -- cgit From a36ea0e2328e6ffedd5b526470dc1133c4f2f556 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 16 Aug 2018 12:04:43 -0300 Subject: show watch asset image from hide token modal --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 9f5826dd9..59c24f987 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -15,7 +15,7 @@ class PreferencesController { * @property {string} store.currentAccountTab Indicates the selected tab in the ui * @property {array} store.tokens The tokens the user wants display in their token lists * @property {object} store.accountTokens The tokens stored per account and then per network type - * @property {object} store.objects Contains assets objects related to + * @property {object} store.objects Contains assets objects related to assets added * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature -- cgit From bb868f58348962d4a85415380d11f72892a2e28c Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 16 Aug 2018 20:19:19 -0300 Subject: correct behavior when notification is closed when popup --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 59c24f987..1438d6f7f 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -237,7 +237,7 @@ class PreferencesController { removeSuggestedTokens () { return new Promise((resolve, reject) => { this.store.updateState({ suggestedTokens: {} }) - resolve() + resolve({}) }) } -- cgit From dbab9a007fc9663427cebdbe1d41c51df67fd1fe Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 16 Aug 2018 21:17:02 -0300 Subject: delete according image when token added with watchToken deleted --- app/scripts/controllers/preferences.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 1438d6f7f..611d2d067 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -15,7 +15,7 @@ class PreferencesController { * @property {string} store.currentAccountTab Indicates the selected tab in the ui * @property {array} store.tokens The tokens the user wants display in their token lists * @property {object} store.accountTokens The tokens stored per account and then per network type - * @property {object} store.objects Contains assets objects related to assets added + * @property {object} store.imageObjects Contains assets objects related to assets added * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature @@ -28,8 +28,8 @@ class PreferencesController { frequentRpcList: [], currentAccountTab: 'history', accountTokens: {}, + imageObjects: {}, tokens: [], - objects: {}, suggestedTokens: {}, useBlockie: false, featureFlags: {}, @@ -60,8 +60,8 @@ class PreferencesController { return this.store.getState().suggestedTokens } - getObjects () { - return this.store.getState().objects + getImageObjects () { + return this.store.getState().imageObjects } addSuggestedToken (tokenOpts) { @@ -89,11 +89,12 @@ class PreferencesController { case 'ERC20': this._handleWatchAssetERC20(options, res) res.result = options.address + end() break default: // TODO return promise for not handled assets + end(new Error(`Asset of type ${type} not supported`)) } - end() } else { next() } @@ -291,7 +292,7 @@ class PreferencesController { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } const tokens = this.store.getState().tokens - const objects = this.getObjects() + const imageObjects = this.getImageObjects() const previousEntry = tokens.find((token, index) => { return token.address === address }) @@ -302,8 +303,8 @@ class PreferencesController { } else { tokens.push(newEntry) } - objects[address] = imageUrl - this._updateAccountTokens(tokens, objects) + imageObjects[address] = imageUrl + this._updateAccountTokens(tokens, imageObjects) return Promise.resolve(tokens) } @@ -316,10 +317,10 @@ class PreferencesController { */ removeToken (rawAddress) { const tokens = this.store.getState().tokens - const objects = this.getObjects() + const imageObjects = this.getImageObjects() const updatedTokens = tokens.filter(token => token.address !== rawAddress) - const updatedObjects = Object.keys(objects).filter(key => key !== rawAddress) - this._updateAccountTokens(updatedTokens, updatedObjects) + delete imageObjects[rawAddress] + this._updateAccountTokens(updatedTokens, imageObjects) return Promise.resolve(updatedTokens) } @@ -483,10 +484,10 @@ class PreferencesController { * @param {array} tokens Array of tokens to be updated. * */ - _updateAccountTokens (tokens, objects) { + _updateAccountTokens (tokens, imageObjects) { const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates() accountTokens[selectedAddress][providerType] = tokens - this.store.updateState({ accountTokens, tokens, objects }) + this.store.updateState({ accountTokens, tokens, imageObjects }) } /** -- cgit From df799d7fd6c49888e3eb659adbfca18030b85975 Mon Sep 17 00:00:00 2001 From: Dan Matthews Date: Thu, 26 Jul 2018 23:40:11 -0400 Subject: Restores accounts until one with a zero balance is found --- app/scripts/metamask-controller.js | 49 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 81bb080ab..166be720f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -48,6 +48,7 @@ const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') +const EthQuery = require('eth-query') module.exports = class MetamaskController extends EventEmitter { @@ -475,12 +476,32 @@ module.exports = class MetamaskController extends EventEmitter { async createNewVaultAndRestore (password, seed) { const releaseLock = await this.createVaultMutex.acquire() try { + let accounts, lastBalance + + const keyringController = this.keyringController + // clear known identities this.preferencesController.setAddresses([]) // create new vault - const vault = await this.keyringController.createNewVaultAndRestore(password, seed) + const vault = await keyringController.createNewVaultAndRestore(password, seed) + + const ethQuery = new EthQuery(this.provider) + accounts = await keyringController.getAccounts() + lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery) + + const primaryKeyring = keyringController.getKeyringsByType('HD Key Tree')[0] + if (!primaryKeyring) { + throw new Error('MetamaskController - No HD Key Tree found') + } + + // seek out the first zero balance + while (lastBalance !== '0x0') { + await keyringController.addNewAccount(primaryKeyring) + accounts = await keyringController.getAccounts() + lastBalance = await this.getBalance(accounts[accounts.length - 1], ethQuery) + } + // set new identities - const accounts = await this.keyringController.getAccounts() this.preferencesController.setAddresses(accounts) this.selectFirstIdentity() releaseLock() @@ -491,6 +512,30 @@ module.exports = class MetamaskController extends EventEmitter { } } + /** + * Get an account balance from the AccountTracker or request it directly from the network. + * @param {string} address - The account address + * @param {EthQuery} ethQuery - The EthQuery instance to use when asking the network + */ + getBalance (address, ethQuery) { + return new Promise((resolve, reject) => { + const cached = this.accountTracker.store.getState().accounts[address] + + if (cached && cached.balance) { + resolve(cached.balance) + } else { + ethQuery.getBalance(address, (error, balance) => { + if (error) { + reject(error) + log.error(error) + } else { + resolve(balance || '0x0') + } + }) + } + }) + } + /* * Submits the user's password and attempts to unlock the vault. * Also synchronizes the preferencesController, to ensure its schema -- cgit From 992e7f1b5aae5ae4a96c67dd40b6626f181b51c1 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Fri, 17 Aug 2018 12:56:07 -0400 Subject: fix merge conflicts --- app/scripts/background.js | 5 +- app/scripts/controllers/balance.js | 2 +- app/scripts/controllers/currency.js | 2 +- .../controllers/network/createInfuraClient.js | 25 +++++ .../controllers/network/createJsonRpcClient.js | 25 +++++ .../controllers/network/createLocalhostClient.js | 21 ++++ .../network/createMetamaskMiddleware.js | 43 +++++++ app/scripts/controllers/network/network.js | 122 ++++++++++---------- app/scripts/controllers/recent-blocks.js | 56 ++++------ app/scripts/controllers/transactions/index.js | 67 +++++++++-- .../controllers/transactions/nonce-tracker.js | 28 +---- .../controllers/transactions/pending-tx-tracker.js | 80 ++++--------- .../controllers/transactions/tx-gas-utils.js | 2 +- app/scripts/lib/account-tracker.js | 124 ++++++++++----------- app/scripts/lib/events-proxy.js | 42 ------- app/scripts/lib/message-manager.js | 31 +++++- app/scripts/lib/personal-message-manager.js | 35 +++++- app/scripts/lib/setupRaven.js | 4 +- app/scripts/lib/typed-message-manager.js | 31 +++++- app/scripts/lib/util.js | 14 +++ app/scripts/metamask-controller.js | 99 ++++++---------- 21 files changed, 489 insertions(+), 369 deletions(-) create mode 100644 app/scripts/controllers/network/createInfuraClient.js create mode 100644 app/scripts/controllers/network/createJsonRpcClient.js create mode 100644 app/scripts/controllers/network/createLocalhostClient.js create mode 100644 app/scripts/controllers/network/createMetamaskMiddleware.js delete mode 100644 app/scripts/lib/events-proxy.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 3d3afdd4e..c7395c810 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -19,7 +19,7 @@ const PortStream = require('./lib/port-stream.js') const createStreamSink = require('./lib/createStreamSink') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') -const firstTimeState = require('./first-time-state') +const rawFirstTimeState = require('./first-time-state') const setupRaven = require('./lib/setupRaven') const reportFailedTxToSentry = require('./lib/reportFailedTxToSentry') const setupMetamaskMeshMetrics = require('./lib/setupMetamaskMeshMetrics') @@ -34,6 +34,9 @@ const { ENVIRONMENT_TYPE_FULLSCREEN, } = require('./lib/enums') +// METAMASK_TEST_CONFIG is used in e2e tests to set the default network to localhost +const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG) + const STORAGE_KEY = 'metamask-config' const METAMASK_DEBUG = process.env.METAMASK_DEBUG diff --git a/app/scripts/controllers/balance.js b/app/scripts/controllers/balance.js index 4c97810a3..465751e61 100644 --- a/app/scripts/controllers/balance.js +++ b/app/scripts/controllers/balance.js @@ -80,7 +80,7 @@ class BalanceController { } }) this.accountTracker.store.subscribe(update) - this.blockTracker.on('block', update) + this.blockTracker.on('latest', update) } /** diff --git a/app/scripts/controllers/currency.js b/app/scripts/controllers/currency.js index a93aff49b..d5bc5fe2b 100644 --- a/app/scripts/controllers/currency.js +++ b/app/scripts/controllers/currency.js @@ -1,4 +1,4 @@ - const ObservableStore = require('obs-store') +const ObservableStore = require('obs-store') const extend = require('xtend') const log = require('loglevel') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js new file mode 100644 index 000000000..41af4d9f9 --- /dev/null +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -0,0 +1,25 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit') +const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const createInfuraMiddleware = require('eth-json-rpc-infura') +const BlockTracker = require('eth-block-tracker') + +module.exports = createInfuraClient + +function createInfuraClient ({ network }) { + const infuraMiddleware = createInfuraMiddleware({ network }) + const blockProvider = providerFromMiddleware(infuraMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider }) + + const networkMiddleware = mergeMiddleware([ + createBlockCacheMiddleware({ blockTracker }), + createInflightMiddleware(), + createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }), + createBlockTrackerInspectorMiddleware({ blockTracker }), + infuraMiddleware, + ]) + return { networkMiddleware, blockTracker } +} diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js new file mode 100644 index 000000000..40c353f7f --- /dev/null +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -0,0 +1,25 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const BlockTracker = require('eth-block-tracker') + +module.exports = createJsonRpcClient + +function createJsonRpcClient ({ rpcUrl }) { + const fetchMiddleware = createFetchMiddleware({ rpcUrl }) + const blockProvider = providerFromMiddleware(fetchMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockCacheMiddleware({ blockTracker }), + createInflightMiddleware(), + createBlockTrackerInspectorMiddleware({ blockTracker }), + fetchMiddleware, + ]) + return { networkMiddleware, blockTracker } +} diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js new file mode 100644 index 000000000..fecc512e8 --- /dev/null +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -0,0 +1,21 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') +const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') +const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') +const BlockTracker = require('eth-block-tracker') + +module.exports = createLocalhostClient + +function createLocalhostClient () { + const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' }) + const blockProvider = providerFromMiddleware(fetchMiddleware) + const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) + + const networkMiddleware = mergeMiddleware([ + createBlockRefMiddleware({ blockTracker }), + createBlockTrackerInspectorMiddleware({ blockTracker }), + fetchMiddleware, + ]) + return { networkMiddleware, blockTracker } +} diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js new file mode 100644 index 000000000..8b17829b7 --- /dev/null +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -0,0 +1,43 @@ +const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') +const createScaffoldMiddleware = require('json-rpc-engine/src/createScaffoldMiddleware') +const createAsyncMiddleware = require('json-rpc-engine/src/createAsyncMiddleware') +const createWalletSubprovider = require('eth-json-rpc-middleware/wallet') + +module.exports = createMetamaskMiddleware + +function createMetamaskMiddleware ({ + version, + getAccounts, + processTransaction, + processEthSignMessage, + processTypedMessage, + processPersonalMessage, + getPendingNonce, +}) { + const metamaskMiddleware = mergeMiddleware([ + createScaffoldMiddleware({ + // staticSubprovider + eth_syncing: false, + web3_clientVersion: `MetaMask/v${version}`, + }), + createWalletSubprovider({ + getAccounts, + processTransaction, + processEthSignMessage, + processTypedMessage, + processPersonalMessage, + }), + createPendingNonceMiddleware({ getPendingNonce }), + ]) + return metamaskMiddleware +} + +function createPendingNonceMiddleware ({ getPendingNonce }) { + return createAsyncMiddleware(async (req, res, next) => { + if (req.method !== 'eth_getTransactionCount') return next() + const address = req.params[0] + const blockRef = req.params[1] + if (blockRef !== 'pending') return next() + req.result = await getPendingNonce(address) + }) +} diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index b6f7705b5..76fdc3391 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -1,15 +1,17 @@ const assert = require('assert') const EventEmitter = require('events') -const createMetamaskProvider = require('web3-provider-engine/zero.js') -const SubproviderFromProvider = require('web3-provider-engine/subproviders/provider.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') const EthQuery = require('eth-query') -const createEventEmitterProxy = require('../../lib/events-proxy.js') +const JsonRpcEngine = require('json-rpc-engine') +const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') const log = require('loglevel') -const urlUtil = require('url') +const createMetamaskMiddleware = require('./createMetamaskMiddleware') +const createInfuraClient = require('./createInfuraClient') +const createJsonRpcClient = require('./createJsonRpcClient') +const createLocalhostClient = require('./createLocalhostClient') +const { createSwappableProxy, createEventEmitterProxy } = require('swappable-obj-proxy') + const { ROPSTEN, RINKEBY, @@ -17,7 +19,6 @@ const { MAINNET, LOCALHOST, } = require('./enums') -const LOCALHOST_RPC_URL = 'http://localhost:8545' const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET] const env = process.env.METAMASK_ENV @@ -39,21 +40,27 @@ module.exports = class NetworkController extends EventEmitter { this.providerStore = new ObservableStore(providerConfig) this.networkStore = new ObservableStore('loading') this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) - // create event emitter proxy - this._proxy = createEventEmitterProxy() - this.on('networkDidChange', this.lookupNetwork) + // provider and block tracker + this._provider = null + this._blockTracker = null + // provider and block tracker proxies - because the network changes + this._providerProxy = null + this._blockTrackerProxy = null } - initializeProvider (_providerParams) { - this._baseProviderParams = _providerParams + initializeProvider (providerParams) { + this._baseProviderParams = providerParams const { type, rpcTarget } = this.providerStore.getState() this._configureProvider({ type, rpcTarget }) - this._proxy.on('block', this._logBlock.bind(this)) - this._proxy.on('error', this.verifyNetwork.bind(this)) - this.ethQuery = new EthQuery(this._proxy) this.lookupNetwork() - return this._proxy + } + + // return the proxies so the references will always be good + getProviderAndBlockTracker () { + const provider = this._providerProxy + const blockTracker = this._blockTrackerProxy + return { provider, blockTracker } } verifyNetwork () { @@ -75,10 +82,11 @@ module.exports = class NetworkController extends EventEmitter { lookupNetwork () { // Prevent firing when provider is not defined. - if (!this.ethQuery || !this.ethQuery.sendAsync) { - return log.warn('NetworkController - lookupNetwork aborted due to missing ethQuery') + if (!this._provider) { + return log.warn('NetworkController - lookupNetwork aborted due to missing provider') } - this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { + const ethQuery = new EthQuery(this._provider) + ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) @@ -131,7 +139,7 @@ module.exports = class NetworkController extends EventEmitter { this._configureInfuraProvider(opts) // other type-based rpc endpoints } else if (type === LOCALHOST) { - this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL }) + this._configureLocalhostProvider() // url-based rpc endpoints } else if (type === 'rpc') { this._configureStandardProvider({ rpcUrl: rpcTarget }) @@ -141,49 +149,47 @@ module.exports = class NetworkController extends EventEmitter { } _configureInfuraProvider ({ type }) { - log.info('_configureInfuraProvider', type) - const infuraProvider = createInfuraProvider({ network: type }) - const infuraSubprovider = new SubproviderFromProvider(infuraProvider) - const providerParams = extend(this._baseProviderParams, { - engineParams: { - pollingInterval: 8000, - blockTrackerProvider: infuraProvider, - }, - dataSubprovider: infuraSubprovider, - }) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) + log.info('NetworkController - configureInfuraProvider', type) + const networkClient = createInfuraClient({ network: type }) + this._setNetworkClient(networkClient) + } + + _configureLocalhostProvider () { + log.info('NetworkController - configureLocalhostProvider') + const networkClient = createLocalhostClient() + this._setNetworkClient(networkClient) } _configureStandardProvider ({ rpcUrl }) { - // urlUtil handles malformed urls - rpcUrl = urlUtil.parse(rpcUrl).format() - const providerParams = extend(this._baseProviderParams, { - rpcUrl, - engineParams: { - pollingInterval: 8000, - }, - }) - const provider = createMetamaskProvider(providerParams) - this._setProvider(provider) - } - - _setProvider (provider) { - // collect old block tracker events - const oldProvider = this._provider - let blockTrackerHandlers - if (oldProvider) { - // capture old block handlers - blockTrackerHandlers = oldProvider._blockTracker.proxyEventHandlers - // tear down - oldProvider.removeAllListeners() - oldProvider.stop() + log.info('NetworkController - configureStandardProvider', rpcUrl) + const networkClient = createJsonRpcClient({ rpcUrl }) + this._setNetworkClient(networkClient) + } + + _setNetworkClient ({ networkMiddleware, blockTracker }) { + const metamaskMiddleware = createMetamaskMiddleware(this._baseProviderParams) + const engine = new JsonRpcEngine() + engine.push(metamaskMiddleware) + engine.push(networkMiddleware) + const provider = providerFromEngine(engine) + this._setProviderAndBlockTracker({ provider, blockTracker }) + } + + _setProviderAndBlockTracker ({ provider, blockTracker }) { + // update or intialize proxies + if (this._providerProxy) { + this._providerProxy.setTarget(provider) + } else { + this._providerProxy = createSwappableProxy(provider) + } + if (this._blockTrackerProxy) { + this._blockTrackerProxy.setTarget(blockTracker) + } else { + this._blockTrackerProxy = createEventEmitterProxy(blockTracker) } - // override block tracler - provider._blockTracker = createEventEmitterProxy(provider._blockTracker, blockTrackerHandlers) - // set as new provider + // set new provider and blockTracker this._provider = provider - this._proxy.setTarget(provider) + this._blockTracker = blockTracker } _logBlock (block) { diff --git a/app/scripts/controllers/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 926268691..d270f6f44 100644 --- a/app/scripts/controllers/recent-blocks.js +++ b/app/scripts/controllers/recent-blocks.js @@ -1,14 +1,14 @@ const ObservableStore = require('obs-store') const extend = require('xtend') -const BN = require('ethereumjs-util').BN const EthQuery = require('eth-query') const log = require('loglevel') +const pify = require('pify') class RecentBlocksController { /** * Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled - * upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event + * upon the controller's construction and then the list is updated when the given block tracker gets a 'latest' event * (indicating that there is a new block to process). * * @typedef {Object} RecentBlocksController @@ -16,7 +16,7 @@ class RecentBlocksController { * @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain * @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance. * @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction, - * listens for 'block' events so that new blocks can be processed and added to storage. + * listens for 'latest' events so that new blocks can be processed and added to storage. * @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider * @property {number} historyLength The maximum length of blocks to track * @property {object} store Stores the recentBlocks @@ -34,7 +34,13 @@ class RecentBlocksController { }, opts.initState) this.store = new ObservableStore(initState) - this.blockTracker.on('block', this.processBlock.bind(this)) + this.blockTracker.on('latest', async (newBlockNumberHex) => { + try { + await this.processBlock(newBlockNumberHex) + } catch (err) { + log.error(err) + } + }) this.backfill() } @@ -55,7 +61,11 @@ class RecentBlocksController { * @param {object} newBlock The new block to modify and add to the recentBlocks array * */ - processBlock (newBlock) { + async processBlock (newBlockNumberHex) { + const newBlockNumber = Number.parseInt(newBlockNumberHex, 16) + const newBlock = await this.getBlockByNumber(newBlockNumber, true) + if (!newBlock) return + const block = this.mapTransactionsToPrices(newBlock) const state = this.store.getState() @@ -108,9 +118,9 @@ class RecentBlocksController { } /** - * On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks + * On this.blockTracker's first 'latest' event after this RecentBlocksController's instantiation, the store.recentBlocks * array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first - * 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying + * 'latest' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying * the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest. * * Each iteration over the block numbers is delayed by 100 milliseconds. @@ -118,18 +128,17 @@ class RecentBlocksController { * @returns {Promise} Promises undefined */ async backfill () { - this.blockTracker.once('block', async (block) => { - const currentBlockNumber = Number.parseInt(block.number, 16) + this.blockTracker.once('latest', async (blockNumberHex) => { + const currentBlockNumber = Number.parseInt(blockNumberHex, 16) const blocksToFetch = Math.min(currentBlockNumber, this.historyLength) const prevBlockNumber = currentBlockNumber - 1 const targetBlockNumbers = Array(blocksToFetch).fill().map((_, index) => prevBlockNumber - index) await Promise.all(targetBlockNumbers.map(async (targetBlockNumber) => { try { - const newBlock = await this.getBlockByNumber(targetBlockNumber) + const newBlock = await this.getBlockByNumber(targetBlockNumber, true) + if (!newBlock) return - if (newBlock) { - this.backfillBlock(newBlock) - } + this.backfillBlock(newBlock) } catch (e) { log.error(e) } @@ -137,18 +146,6 @@ class RecentBlocksController { }) } - /** - * A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await - * - * @returns {Promise} Promises undefined - * - */ - async wait () { - return new Promise((resolve) => { - setTimeout(resolve, 100) - }) - } - /** * Uses EthQuery to get a block that has a given block number. * @@ -157,13 +154,8 @@ class RecentBlocksController { * */ async getBlockByNumber (number) { - const bn = new BN(number) - return new Promise((resolve, reject) => { - this.ethQuery.getBlockByNumber('0x' + bn.toString(16), true, (err, block) => { - if (err) reject(err) - resolve(block) - }) - }) + const blockNumberHex = '0x' + number.toString(16) + return await pify(this.ethQuery.getBlockByNumber).call(this.ethQuery, blockNumberHex, true) } } diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 8e2288aed..5d7d6d6da 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -65,6 +65,7 @@ class TransactionController extends EventEmitter { this.store = this.txStateManager.store this.nonceTracker = new NonceTracker({ provider: this.provider, + blockTracker: this.blockTracker, getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), getConfirmedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) @@ -78,13 +79,17 @@ class TransactionController extends EventEmitter { }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) - this._setupListners() + this._setupListeners() // memstore is computed from a few different stores this._updateMemstore() this.txStateManager.store.subscribe(() => this._updateMemstore()) this.networkStore.subscribe(() => this._updateMemstore()) this.preferencesStore.subscribe(() => this._updateMemstore()) + + // request state update to finalize initialization + this._updatePendingTxsAfterFirstBlock() } + /** @returns {number} the chainId*/ getChainId () { const networkState = this.networkStore.getState() @@ -311,6 +316,11 @@ class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId) } + confirmTransaction (txId) { + this.txStateManager.setTxStatusConfirmed(txId) + this._markNonceDuplicatesDropped(txId) + } + /** Convenience method for the ui thats sets the transaction to rejected @param txId {number} - the tx's Id @@ -354,6 +364,14 @@ class TransactionController extends EventEmitter { this.getFilteredTxList = (opts) => this.txStateManager.getFilteredTxList(opts) } + // called once on startup + async _updatePendingTxsAfterFirstBlock () { + // wait for first block so we know we're ready + await this.blockTracker.getLatestBlock() + // get status update for all pending transactions (for the current network) + await this.pendingTxTracker.updatePendingTxs() + } + /** If transaction controller was rebooted with transactions that are uncompleted in steps of the transaction signing or user confirmation process it will either @@ -386,14 +404,14 @@ class TransactionController extends EventEmitter { is called in constructor applies the listeners for pendingTxTracker txStateManager and blockTracker */ - _setupListners () { + _setupListeners () { this.txStateManager.on('tx:status-update', this.emit.bind(this, 'tx:status-update')) + this._setupBlockTrackerListener() this.pendingTxTracker.on('tx:warning', (txMeta) => { this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:warning') }) - this.pendingTxTracker.on('tx:confirmed', (txId) => this.txStateManager.setTxStatusConfirmed(txId)) - this.pendingTxTracker.on('tx:confirmed', (txId) => this._markNonceDuplicatesDropped(txId)) this.pendingTxTracker.on('tx:failed', this.txStateManager.setTxStatusFailed.bind(this.txStateManager)) + this.pendingTxTracker.on('tx:confirmed', (txId) => this.confirmTransaction(txId)) this.pendingTxTracker.on('tx:block-update', (txMeta, latestBlockNumber) => { if (!txMeta.firstRetryBlockNumber) { txMeta.firstRetryBlockNumber = latestBlockNumber @@ -405,13 +423,6 @@ class TransactionController extends EventEmitter { txMeta.retryCount++ this.txStateManager.updateTx(txMeta, 'transactions/pending-tx-tracker#event: tx:retry') }) - - this.blockTracker.on('block', this.pendingTxTracker.checkForTxInBlock.bind(this.pendingTxTracker)) - // this is a little messy but until ethstore has been either - // removed or redone this is to guard against the race condition - this.blockTracker.on('latest', this.pendingTxTracker.resubmitPendingTxs.bind(this.pendingTxTracker)) - this.blockTracker.on('sync', this.pendingTxTracker.queryPendingTxs.bind(this.pendingTxTracker)) - } /** @@ -435,6 +446,40 @@ class TransactionController extends EventEmitter { }) } + _setupBlockTrackerListener () { + let listenersAreActive = false + const latestBlockHandler = this._onLatestBlock.bind(this) + const blockTracker = this.blockTracker + const txStateManager = this.txStateManager + + txStateManager.on('tx:status-update', updateSubscription) + updateSubscription() + + function updateSubscription () { + const pendingTxs = txStateManager.getPendingTransactions() + if (!listenersAreActive && pendingTxs.length > 0) { + blockTracker.on('latest', latestBlockHandler) + listenersAreActive = true + } else if (listenersAreActive && !pendingTxs.length) { + blockTracker.removeListener('latest', latestBlockHandler) + listenersAreActive = false + } + } + } + + async _onLatestBlock (blockNumber) { + try { + await this.pendingTxTracker.updatePendingTxs() + } catch (err) { + log.error(err) + } + try { + await this.pendingTxTracker.resubmitPendingTxs(blockNumber) + } catch (err) { + log.error(err) + } + } + /** Updates the memStore in transaction controller */ diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js index 06f336eaa..421036368 100644 --- a/app/scripts/controllers/transactions/nonce-tracker.js +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -12,8 +12,9 @@ const Mutex = require('await-semaphore').Mutex */ class NonceTracker { - constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { + constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) { this.provider = provider + this.blockTracker = blockTracker this.ethQuery = new EthQuery(provider) this.getPendingTransactions = getPendingTransactions this.getConfirmedTransactions = getConfirmedTransactions @@ -34,7 +35,7 @@ class NonceTracker { * @typedef NonceDetails * @property {number} highestLocallyConfirmed - A hex string of the highest nonce on a confirmed transaction. * @property {number} nextNetworkNonce - The next nonce suggested by the eth_getTransactionCount method. - * @property {number} highetSuggested - The maximum between the other two, the number returned. + * @property {number} highestSuggested - The maximum between the other two, the number returned. */ /** @@ -80,15 +81,6 @@ class NonceTracker { } } - async _getCurrentBlock () { - const blockTracker = this._getBlockTracker() - const currentBlock = blockTracker.getCurrentBlock() - if (currentBlock) return currentBlock - return await new Promise((reject, resolve) => { - blockTracker.once('latest', resolve) - }) - } - async _globalMutexFree () { const globalMutex = this._lookupMutex('global') const releaseLock = await globalMutex.acquire() @@ -114,9 +106,8 @@ class NonceTracker { // 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 blockNumber = await this.blockTracker.getLatestBlock() + const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber) const baseCount = baseCountBN.toNumber() assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) const nonceDetails = { blockNumber, baseCount } @@ -165,15 +156,6 @@ class NonceTracker { return { name: 'local', nonce: highest, details: { startPoint, highest } } } - // this is a hotfix for the fact that the blockTracker will - // change when the network changes - - /** - @returns {Object} the current blockTracker - */ - _getBlockTracker () { - return this.provider._blockTracker - } } module.exports = NonceTracker diff --git a/app/scripts/controllers/transactions/pending-tx-tracker.js b/app/scripts/controllers/transactions/pending-tx-tracker.js index 4e41cdaf8..70cac096b 100644 --- a/app/scripts/controllers/transactions/pending-tx-tracker.js +++ b/app/scripts/controllers/transactions/pending-tx-tracker.js @@ -1,6 +1,7 @@ const EventEmitter = require('events') const log = require('loglevel') const EthQuery = require('ethjs-query') + /** Event emitter utility class for tracking the transactions as they
@@ -23,55 +24,26 @@ class PendingTransactionTracker extends EventEmitter { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker - // default is one day this.getPendingTransactions = config.getPendingTransactions this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction - this._checkPendingTxs() + this.confirmTransaction = config.confirmTransaction } /** - checks if a signed tx is in a block and - if it is included emits tx status as 'confirmed' - @param block {object}, a full block - @emits tx:confirmed - @emits tx:failed - */ - checkForTxInBlock (block) { - const signedTxList = this.getPendingTransactions() - if (!signedTxList.length) return - signedTxList.forEach((txMeta) => { - const txHash = txMeta.hash - const txId = txMeta.id - - if (!txHash) { - const noTxHashErr = new Error('We had an error while submitting this transaction, please try again.') - noTxHashErr.name = 'NoTxHashError' - this.emit('tx:failed', txId, noTxHashErr) - return - } - - - block.transactions.forEach((tx) => { - if (tx.hash === txHash) this.emit('tx:confirmed', txId) - }) - }) - } - - /** - asks the network for the transaction to see if a block number is included on it - if we have skipped/missed blocks - @param object - oldBlock newBlock + checks the network for signed txs and releases the nonce global lock if it is */ - queryPendingTxs ({ oldBlock, newBlock }) { - // check pending transactions on start - if (!oldBlock) { - this._checkPendingTxs() - return + async updatePendingTxs () { + // in order to keep the nonceTracker accurate we block it while updating pending transactions + const nonceGlobalLock = await this.nonceTracker.getGlobalLock() + try { + const pendingTxs = this.getPendingTransactions() + await Promise.all(pendingTxs.map((txMeta) => this._checkPendingTx(txMeta))) + } catch (err) { + log.error('PendingTransactionTracker - Error updating pending transactions') + log.error(err) } - // if we synced by more than one block, check for missed pending transactions - const diff = Number.parseInt(newBlock.number, 16) - Number.parseInt(oldBlock.number, 16) - if (diff > 1) this._checkPendingTxs() + nonceGlobalLock.releaseLock() } /** @@ -79,11 +51,11 @@ class PendingTransactionTracker extends EventEmitter { @param block {object} - a block object @emits tx:warning */ - resubmitPendingTxs (block) { + resubmitPendingTxs (blockNumber) { const pending = this.getPendingTransactions() // only try resubmitting if their are transactions to resubmit if (!pending.length) return - pending.forEach((txMeta) => this._resubmitTx(txMeta, block.number).catch((err) => { + pending.forEach((txMeta) => this._resubmitTx(txMeta, blockNumber).catch((err) => { /* Dont marked as failed if the error is a "known" transaction warning "there is already a transaction with the same sender-nonce @@ -145,6 +117,7 @@ class PendingTransactionTracker extends EventEmitter { this.emit('tx:retry', txMeta) return txHash } + /** Ask the network for the transaction to see if it has been include in a block @param txMeta {Object} - the txMeta object @@ -174,9 +147,8 @@ class PendingTransactionTracker extends EventEmitter { } // get latest transaction status - let txParams try { - txParams = await this.query.getTransactionByHash(txHash) + const txParams = await this.query.getTransactionByHash(txHash) if (!txParams) return if (txParams.blockNumber) { this.emit('tx:confirmed', txId) @@ -190,27 +162,13 @@ class PendingTransactionTracker extends EventEmitter { } } - /** - checks the network for signed txs and releases the nonce global lock if it is - */ - async _checkPendingTxs () { - const signedTxList = this.getPendingTransactions() - // in order to keep the nonceTracker accurate we block it while updating pending transactions - const { releaseLock } = await this.nonceTracker.getGlobalLock() - try { - await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta))) - } catch (err) { - log.error('PendingTransactionWatcher - Error updating pending transactions') - log.error(err) - } - releaseLock() - } - /** checks to see if a confirmed txMeta has the same nonce @param txMeta {Object} - txMeta object @returns {boolean} */ + + async _checkIfNonceIsTaken (txMeta) { const address = txMeta.txParams.from const completed = this.getCompletedTransactions(address) diff --git a/app/scripts/controllers/transactions/tx-gas-utils.js b/app/scripts/controllers/transactions/tx-gas-utils.js index 5cd0f5407..3dd45507f 100644 --- a/app/scripts/controllers/transactions/tx-gas-utils.js +++ b/app/scripts/controllers/transactions/tx-gas-utils.js @@ -25,7 +25,7 @@ class TxGasUtil { @returns {object} the txMeta object with the gas written to the txParams */ async analyzeGasUsage (txMeta) { - const block = await this.query.getBlockByNumber('latest', true) + const block = await this.query.getBlockByNumber('latest', false) let estimatedGasHex try { estimatedGasHex = await this.estimateTxGas(txMeta, block.gasLimit) diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 0f7b3d865..b7e2c7cbe 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -7,14 +7,13 @@ * on each new block. */ -const async = require('async') const EthQuery = require('eth-query') const ObservableStore = require('obs-store') -const EventEmitter = require('events').EventEmitter -function noop () {} +const log = require('loglevel') +const pify = require('pify') -class AccountTracker extends EventEmitter { +class AccountTracker { /** * This module is responsible for tracking any number of accounts and caching their current balances & transaction @@ -35,8 +34,6 @@ class AccountTracker extends EventEmitter { * */ constructor (opts = {}) { - super() - const initState = { accounts: {}, currentBlockGasLimit: '', @@ -44,12 +41,12 @@ class AccountTracker extends EventEmitter { this.store = new ObservableStore(initState) this._provider = opts.provider - this._query = new EthQuery(this._provider) + this._query = pify(new EthQuery(this._provider)) this._blockTracker = opts.blockTracker // subscribe to latest block - this._blockTracker.on('block', this._updateForBlock.bind(this)) + this._blockTracker.on('latest', this._updateForBlock.bind(this)) // blockTracker.currentBlock may be null - this._currentBlockNumber = this._blockTracker.currentBlock + this._currentBlockNumber = this._blockTracker.getCurrentBlock() } /** @@ -67,49 +64,57 @@ class AccountTracker extends EventEmitter { const accounts = this.store.getState().accounts const locals = Object.keys(accounts) - const toAdd = [] + const accountsToAdd = [] addresses.forEach((upstream) => { if (!locals.includes(upstream)) { - toAdd.push(upstream) + accountsToAdd.push(upstream) } }) - const toRemove = [] + const accountsToRemove = [] locals.forEach((local) => { if (!addresses.includes(local)) { - toRemove.push(local) + accountsToRemove.push(local) } }) - toAdd.forEach(upstream => this.addAccount(upstream)) - toRemove.forEach(local => this.removeAccount(local)) - this._updateAccounts() + this.addAccounts(accountsToAdd) + this.removeAccount(accountsToRemove) } /** - * Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be + * Adds new addresses to track the balances of * given a balance as long this._currentBlockNumber is defined. * - * @param {string} address A hex address of a new account to store in this AccountTracker's accounts object + * @param {array} addresses An array of hex addresses of new accounts to track * */ - addAccount (address) { + addAccounts (addresses) { const accounts = this.store.getState().accounts - accounts[address] = {} + // add initial state for addresses + addresses.forEach(address => { + accounts[address] = {} + }) + // save accounts state this.store.updateState({ accounts }) + // fetch balances for the accounts if there is block number ready if (!this._currentBlockNumber) return - this._updateAccount(address) + addresses.forEach(address => this._updateAccount(address)) } /** - * Removes an account from this AccountTracker's accounts object + * Removes accounts from being tracked * - * @param {string} address A hex address of a the account to remove + * @param {array} an array of hex addresses to stop tracking * */ - removeAccount (address) { + removeAccount (addresses) { const accounts = this.store.getState().accounts - delete accounts[address] + // remove each state object + addresses.forEach(address => { + delete accounts[address] + }) + // save accounts state this.store.updateState({ accounts }) } @@ -118,71 +123,56 @@ class AccountTracker extends EventEmitter { * via EthQuery * * @private - * @param {object} block Data about the block that contains the data to update to. + * @param {number} blockNumber the block number to update to. * @fires 'block' The updated state, if all account updates are successful * */ - _updateForBlock (block) { - this._currentBlockNumber = block.number - const currentBlockGasLimit = block.gasLimit + async _updateForBlock (blockNumber) { + this._currentBlockNumber = blockNumber + // block gasLimit polling shouldn't be in account-tracker shouldn't be here... + const currentBlock = await this._query.getBlockByNumber(blockNumber, false) + if (!currentBlock) return + const currentBlockGasLimit = currentBlock.gasLimit this.store.updateState({ currentBlockGasLimit }) - async.parallel([ - this._updateAccounts.bind(this), - ], (err) => { - if (err) return console.error(err) - this.emit('block', this.store.getState()) - }) + try { + await this._updateAccounts() + } catch (err) { + log.error(err) + } } /** * Calls this._updateAccount for each account in this.store * - * @param {Function} cb A callback to pass to this._updateAccount, called after each account is successfully updated + * @returns {Promise} after all account balances updated * */ - _updateAccounts (cb = noop) { + async _updateAccounts () { const accounts = this.store.getState().accounts const addresses = Object.keys(accounts) - async.each(addresses, this._updateAccount.bind(this), cb) + await Promise.all(addresses.map(this._updateAccount.bind(this))) } /** - * Updates the current balance of an account. Gets an updated balance via this._getAccount. + * Updates the current balance of an account. * * @private * @param {string} address A hex address of a the account to be updated - * @param {Function} cb A callback to call once the account at address is successfully update + * @returns {Promise} after the account balance is updated * */ - _updateAccount (address, cb = noop) { - this._getAccount(address, (err, result) => { - if (err) return cb(err) - result.address = address - const accounts = this.store.getState().accounts - // only populate if the entry is still present - if (accounts[address]) { - accounts[address] = result - this.store.updateState({ accounts }) - } - cb(null, result) - }) - } - - /** - * Gets the current balance of an account via EthQuery. - * - * @private - * @param {string} address A hex address of a the account to query - * @param {Function} cb A callback to call once the account at address is successfully update - * - */ - _getAccount (address, cb = noop) { - const query = this._query - async.parallel({ - balance: query.getBalance.bind(query, address), - }, cb) + async _updateAccount (address) { + // query balance + const balance = await this._query.getBalance(address) + const result = { address, balance } + // update accounts state + const { accounts } = this.store.getState() + // only populate if the entry is still present + if (!accounts[address]) return + accounts[address] = result + this.store.updateState({ accounts }) } } diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js deleted file mode 100644 index f83773ccc..000000000 --- a/app/scripts/lib/events-proxy.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Returns an EventEmitter that proxies events from the given event emitter - * @param {any} eventEmitter - * @param {object} listeners - The listeners to proxy to - * @returns {any} - */ -module.exports = function createEventEmitterProxy (eventEmitter, listeners) { - let target = eventEmitter - const eventHandlers = listeners || {} - const proxy = /** @type {any} */ (new Proxy({}, { - get: (_, name) => { - // intercept listeners - if (name === 'on') return addListener - if (name === 'setTarget') return setTarget - if (name === 'proxyEventHandlers') return eventHandlers - return (/** @type {any} */ (target))[name] - }, - set: (_, name, value) => { - target[name] = value - return true - }, - })) - function setTarget (/** @type {EventEmitter} */ eventEmitter) { - target = eventEmitter - // migrate listeners - Object.keys(eventHandlers).forEach((name) => { - /** @type {Array} */ (eventHandlers[name]).forEach((handler) => target.on(name, handler)) - }) - } - /** - * Attaches a function to be called whenever the specified event is emitted - * @param {string} name - * @param {Function} handler - */ - function addListener (name, handler) { - if (!eventHandlers[name]) eventHandlers[name] = [] - eventHandlers[name].push(handler) - target.on(name, handler) - } - if (listeners) proxy.setTarget(eventEmitter) - return proxy -} diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 901367f04..47925b94b 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -69,10 +69,39 @@ module.exports = class MessageManager extends EventEmitter { * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} after signature has been + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + const msgId = this.addUnapprovedMessage(msgParams, req) + // await finished + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the + * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object where the origin may be specificied * @returns {number} The id of the newly created message. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { + // add origin from request + if (req) msgParams.origin = req.origin msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data var time = (new Date()).getTime() diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index e96ced1f2..fc2cccdf1 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -73,11 +73,43 @@ module.exports = class PersonalMessageManager extends EventEmitter { * this.memStore. * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + if (!msgParams.from) { + reject(new Error('MetaMask Message Signature: from field is required.')) + } + const msgId = this.addUnapprovedMessage(msgParams, req) + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to + * this.memStore. + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin * @returns {number} The id of the newly created PersonalMessage. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + // add origin from request + if (req) msgParams.origin = req.origin msgParams.data = this.normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data var time = (new Date()).getTime() @@ -257,4 +289,3 @@ module.exports = class PersonalMessageManager extends EventEmitter { } } - diff --git a/app/scripts/lib/setupRaven.js b/app/scripts/lib/setupRaven.js index 3651524f1..e6e511640 100644 --- a/app/scripts/lib/setupRaven.js +++ b/app/scripts/lib/setupRaven.js @@ -70,11 +70,11 @@ function simplifyErrorMessages (report) { function rewriteErrorMessages (report, rewriteFn) { // rewrite top level message - if (report.message) report.message = rewriteFn(report.message) + if (typeof report.message === 'string') report.message = rewriteFn(report.message) // rewrite each exception message if (report.exception && report.exception.values) { report.exception.values.forEach(item => { - item.value = rewriteFn(item.value) + if (typeof item.value === 'string') item.value = rewriteFn(item.value) }) } } diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index c58921610..e5e1c94b3 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -72,11 +72,40 @@ module.exports = class TypedMessageManager extends EventEmitter { * this.memStore. Before any of this is done, msgParams are validated * * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin + * @returns {promise} When the message has been signed or rejected + * + */ + addUnapprovedMessageAsync (msgParams, req) { + return new Promise((resolve, reject) => { + const msgId = this.addUnapprovedMessage(msgParams, req) + this.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return resolve(data.rawSig) + case 'rejected': + return reject(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + }) + } + + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to + * this.memStore. Before any of this is done, msgParams are validated + * + * @param {Object} msgParams The params for the eth_sign call to be made after the message is approved. + * @param {Object} req (optional) The original request object possibly containing the origin * @returns {number} The id of the newly created TypedMessage. * */ - addUnapprovedMessage (msgParams) { + addUnapprovedMessage (msgParams, req) { this.validateParams(msgParams) + // add origin from request + if (req) msgParams.origin = req.origin log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) // create txData obj with parameters and meta data diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index d7423f2ad..ea13b26be 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -127,7 +127,21 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) { return targetBN.mul(numBN).div(denomBN) } +function applyListeners (listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.on(key, listeners[key]) + }) +} + +function removeListeners (listeners, emitter) { + Object.keys(listeners).forEach((key) => { + emitter.removeListener(key, listeners[key]) + }) +} + module.exports = { + removeListeners, + applyListeners, getPlatform, getStack, getEnvironmentType, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1e1aa035f..585bb005e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -46,7 +46,6 @@ const BN = require('ethereumjs-util').BN const GWEI_BN = new BN('1000000000') const percentile = require('percentile') const seedPhraseVerifier = require('./lib/seed-phrase-verifier') -const cleanErrorStack = require('./lib/cleanErrorStack') const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') @@ -108,8 +107,9 @@ module.exports = class MetamaskController extends EventEmitter { this.blacklistController.scheduleUpdates() // rpc provider - this.provider = this.initializeProvider() - this.blockTracker = this.provider._blockTracker + this.initializeProvider() + this.provider = this.networkController.getProviderAndBlockTracker().provider + this.blockTracker = this.networkController.getProviderAndBlockTracker().blockTracker // token exchange rate tracker this.tokenRatesController = new TokenRatesController({ @@ -253,28 +253,22 @@ module.exports = class MetamaskController extends EventEmitter { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, - eth_sendTransaction: (payload, next, end) => { - const origin = payload.origin - const txParams = payload.params[0] - nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end) - }, }, // account mgmt - getAccounts: (cb) => { + getAccounts: async () => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked - const result = [] const selectedAddress = this.preferencesController.getSelectedAddress() - // only show address if account is unlocked if (isUnlocked && selectedAddress) { - result.push(selectedAddress) + return [selectedAddress] + } else { + return [] } - cb(null, result) }, // tx signing - // old style msg signing - processMessage: this.newUnsignedMessage.bind(this), - // personal_sign msg signing + processTransaction: this.newUnapprovedTransaction.bind(this), + // msg signing + processEthSignMessage: this.newUnsignedMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), processTypedMessage: this.newUnsignedTypedMessage.bind(this), } @@ -791,6 +785,18 @@ module.exports = class MetamaskController extends EventEmitter { // --------------------------------------------------------------------------- // Identity Management (signature operations) + /** + * Called when a Dapp suggests a new tx to be signed. + * this wrapper needs to exist so we can provide a reference to + * "newUnapprovedTransaction" before "txController" is instantiated + * + * @param {Object} msgParams - The params passed to eth_sign. + * @param {Object} req - (optional) the original request, containing the origin + */ + async newUnapprovedTransaction (txParams, req) { + return await this.txController.newUnapprovedTransaction(txParams, req) + } + // eth_sign methods: /** @@ -802,20 +808,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_sign. * @param {Function} cb = The callback function called with the signature. */ - newUnsignedMessage (msgParams, cb) { - const msgId = this.messageManager.addUnapprovedMessage(msgParams) + newUnsignedMessage (msgParams, req) { + const promise = this.messageManager.addUnapprovedMessageAsync(msgParams, req) this.sendUpdate() this.opts.showUnconfirmedMessage() - this.messageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) - default: - return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) - } - }) + return promise } /** @@ -869,24 +866,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} cb - The callback function called with the signature. * Passed back to the requesting Dapp. */ - newUnsignedPersonalMessage (msgParams, cb) { - if (!msgParams.from) { - return cb(cleanErrorStack(new Error('MetaMask Message Signature: from field is required.'))) - } - - const msgId = this.personalMessageManager.addUnapprovedMessage(msgParams) + async newUnsignedPersonalMessage (msgParams, req) { + const promise = this.personalMessageManager.addUnapprovedMessageAsync(msgParams, req) this.sendUpdate() this.opts.showUnconfirmedMessage() - this.personalMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) - default: - return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) - } - }) + return promise } /** @@ -935,26 +919,11 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_signTypedData. * @param {Function} cb - The callback function, called with the signature. */ - newUnsignedTypedMessage (msgParams, cb) { - let msgId - try { - msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) - this.sendUpdate() - this.opts.showUnconfirmedMessage() - } catch (e) { - return cb(e) - } - - this.typedMessageManager.once(`${msgId}:finished`, (data) => { - switch (data.status) { - case 'signed': - return cb(null, data.rawSig) - case 'rejected': - return cb(cleanErrorStack(new Error('MetaMask Message Signature: User denied message signature.'))) - default: - return cb(cleanErrorStack(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`))) - } - }) + newUnsignedTypedMessage (msgParams, req) { + const promise = this.typedMessageManager.addUnapprovedMessageAsync(msgParams, req) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + return promise } /** @@ -1219,7 +1188,7 @@ module.exports = class MetamaskController extends EventEmitter { // create filter polyfill middleware const filterMiddleware = createFilterMiddleware({ provider: this.provider, - blockTracker: this.provider._blockTracker, + blockTracker: this.blockTracker, }) engine.push(createOriginMiddleware({ origin })) -- cgit From 6ce119d1fbf458fa93c63198da7e5bff3045d955 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Mon, 20 Aug 2018 15:39:03 -0700 Subject: Move inpage-provider and port-stream outside With the creation of the [metamask-extension-provider](https://github.com/MetaMask/metamask-extension-provider) we have our first non-core module that is dependent on the inpage-provider and port-stream. To reduce the size of its dependencies, I have moved the [metamask-inpage-provider](https://github.com/MetaMask/metamask-inpage-provider) into its own module, as well as [extension-port-stream](https://github.com/MetaMask/extension-port-stream). This allows them to be more easily depended & iterated on by external projects. --- app/scripts/background.js | 2 +- app/scripts/contentscript.js | 2 +- app/scripts/inpage.js | 2 +- app/scripts/lib/createErrorMiddleware.js | 67 ----------------- app/scripts/lib/inpage-provider.js | 125 ------------------------------- app/scripts/lib/port-stream.js | 80 -------------------- app/scripts/ui.js | 2 +- 7 files changed, 4 insertions(+), 276 deletions(-) delete mode 100644 app/scripts/lib/createErrorMiddleware.js delete mode 100644 app/scripts/lib/inpage-provider.js delete mode 100644 app/scripts/lib/port-stream.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index c7395c810..d4d87e0d5 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -15,7 +15,7 @@ const asStream = require('obs-store/lib/asStream') const ExtensionPlatform = require('./platforms/extension') const Migrator = require('./lib/migrator/') const migrations = require('./migrations/') -const PortStream = require('./lib/port-stream.js') +const PortStream = require('extension-port-stream') const createStreamSink = require('./lib/createStreamSink') const NotificationManager = require('./lib/notification-manager.js') const MetamaskController = require('./metamask-controller') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index e0a2b0061..6eee1987a 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -5,7 +5,7 @@ const LocalMessageDuplexStream = require('post-message-stream') const PongStream = require('ping-pong-stream/pong') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') -const PortStream = require('./lib/port-stream.js') +const PortStream = require('extension-port-stream') const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 7dd7fda02..1a170c617 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -4,7 +4,7 @@ require('web3/dist/web3.min.js') const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') -const MetamaskInpageProvider = require('./lib/inpage-provider.js') +const MetamaskInpageProvider = require('metamask-inpage-provider') restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') diff --git a/app/scripts/lib/createErrorMiddleware.js b/app/scripts/lib/createErrorMiddleware.js deleted file mode 100644 index 7f6a4bd73..000000000 --- a/app/scripts/lib/createErrorMiddleware.js +++ /dev/null @@ -1,67 +0,0 @@ -const log = require('loglevel') - -/** - * JSON-RPC error object - * - * @typedef {Object} RpcError - * @property {number} code - Indicates the error type that occurred - * @property {Object} [data] - Contains additional information about the error - * @property {string} [message] - Short description of the error - */ - -/** - * Middleware configuration object - * - * @typedef {Object} MiddlewareConfig - * @property {boolean} [override] - Use RPC_ERRORS message in place of provider message - */ - -/** - * Map of standard and non-standard RPC error codes to messages - */ -const RPC_ERRORS = { - 1: 'An unauthorized action was attempted.', - 2: 'A disallowed action was attempted.', - 3: 'An execution error occurred.', - [-32600]: 'The JSON sent is not a valid Request object.', - [-32601]: 'The method does not exist / is not available.', - [-32602]: 'Invalid method parameter(s).', - [-32603]: 'Internal JSON-RPC error.', - [-32700]: 'Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.', - internal: 'Internal server error.', - unknown: 'Unknown JSON-RPC error.', -} - -/** - * Modifies a JSON-RPC error object in-place to add a human-readable message, - * optionally overriding any provider-supplied message - * - * @param {RpcError} error - JSON-RPC error object - * @param {boolean} override - Use RPC_ERRORS message in place of provider message - */ -function sanitizeRPCError (error, override) { - if (error.message && !override) { return error } - const message = error.code > -31099 && error.code < -32100 ? RPC_ERRORS.internal : RPC_ERRORS[error.code] - error.message = message || RPC_ERRORS.unknown -} - -/** - * json-rpc-engine middleware that both logs standard and non-standard error - * messages and ends middleware stack traversal if an error is encountered - * - * @param {MiddlewareConfig} [config={override:true}] - Middleware configuration - * @returns {Function} json-rpc-engine middleware function - */ -function createErrorMiddleware ({ override = true } = {}) { - return (req, res, next) => { - next(done => { - const { error } = res - if (!error) { return done() } - sanitizeRPCError(error) - log.error(`MetaMask - RPC Error: ${error.message}`, error) - done() - }) - } -} - -module.exports = createErrorMiddleware diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js deleted file mode 100644 index 6ef511453..000000000 --- a/app/scripts/lib/inpage-provider.js +++ /dev/null @@ -1,125 +0,0 @@ -const pump = require('pump') -const RpcEngine = require('json-rpc-engine') -const createErrorMiddleware = require('./createErrorMiddleware') -const createIdRemapMiddleware = require('json-rpc-engine/src/idRemapMiddleware') -const createStreamMiddleware = require('json-rpc-middleware-stream') -const LocalStorageStore = require('obs-store') -const asStream = require('obs-store/lib/asStream') -const ObjectMultiplex = require('obj-multiplex') - -module.exports = MetamaskInpageProvider - -function MetamaskInpageProvider (connectionStream) { - const self = this - - // setup connectionStream multiplexing - const mux = self.mux = new ObjectMultiplex() - pump( - connectionStream, - mux, - connectionStream, - (err) => logStreamDisconnectWarning('MetaMask', err) - ) - - // subscribe to metamask public config (one-way) - self.publicConfigStore = new LocalStorageStore({ storageKey: 'MetaMask-Config' }) - - pump( - mux.createStream('publicConfig'), - asStream(self.publicConfigStore), - (err) => logStreamDisconnectWarning('MetaMask PublicConfigStore', err) - ) - - // ignore phishing warning message (handled elsewhere) - mux.ignoreStream('phishing') - - // connect to async provider - const streamMiddleware = createStreamMiddleware() - pump( - streamMiddleware.stream, - mux.createStream('provider'), - streamMiddleware.stream, - (err) => logStreamDisconnectWarning('MetaMask RpcProvider', err) - ) - - // handle sendAsync requests via dapp-side rpc engine - const rpcEngine = new RpcEngine() - rpcEngine.push(createIdRemapMiddleware()) - rpcEngine.push(createErrorMiddleware()) - rpcEngine.push(streamMiddleware) - self.rpcEngine = rpcEngine -} - -// handle sendAsync requests via asyncProvider -// also remap ids inbound and outbound -MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) { - const self = this - - if (payload.method === 'eth_signTypedData') { - console.warn('MetaMask: This experimental version of eth_signTypedData will be deprecated in the next release in favor of the standard as defined in EIP-712. See https://git.io/fNzPl for more information on the new standard.') - } - - self.rpcEngine.handle(payload, cb) -} - - -MetamaskInpageProvider.prototype.send = function (payload) { - const self = this - - let selectedAddress - let result = null - switch (payload.method) { - - case 'eth_accounts': - // read from localStorage - selectedAddress = self.publicConfigStore.getState().selectedAddress - result = selectedAddress ? [selectedAddress] : [] - break - - case 'eth_coinbase': - // read from localStorage - selectedAddress = self.publicConfigStore.getState().selectedAddress - result = selectedAddress || null - break - - case 'eth_uninstallFilter': - self.sendAsync(payload, noop) - result = true - break - - case 'net_version': - const networkVersion = self.publicConfigStore.getState().networkVersion - result = networkVersion || null - break - - // throw not-supported Error - default: - var link = 'https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#dizzy-all-async---think-of-metamask-as-a-light-client' - var message = `The MetaMask Web3 object does not support synchronous methods like ${payload.method} without a callback parameter. See ${link} for details.` - throw new Error(message) - - } - - // return the result - return { - id: payload.id, - jsonrpc: payload.jsonrpc, - result: result, - } -} - -MetamaskInpageProvider.prototype.isConnected = function () { - return true -} - -MetamaskInpageProvider.prototype.isMetaMask = true - -// util - -function logStreamDisconnectWarning (remoteLabel, err) { - let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` - if (err) warningMsg += '\n' + err.stack - console.warn(warningMsg) -} - -function noop () {} diff --git a/app/scripts/lib/port-stream.js b/app/scripts/lib/port-stream.js deleted file mode 100644 index fd65d94f3..000000000 --- a/app/scripts/lib/port-stream.js +++ /dev/null @@ -1,80 +0,0 @@ -const Duplex = require('readable-stream').Duplex -const inherits = require('util').inherits -const noop = function () {} - -module.exports = PortDuplexStream - -inherits(PortDuplexStream, Duplex) - -/** - * Creates a stream that's both readable and writable. - * The stream supports arbitrary objects. - * - * @class - * @param {Object} port Remote Port object - */ -function PortDuplexStream (port) { - Duplex.call(this, { - objectMode: true, - }) - this._port = port - port.onMessage.addListener(this._onMessage.bind(this)) - port.onDisconnect.addListener(this._onDisconnect.bind(this)) -} - -/** - * Callback triggered when a message is received from - * the remote Port associated with this Stream. - * - * @private - * @param {Object} msg - Payload from the onMessage listener of Port - */ -PortDuplexStream.prototype._onMessage = function (msg) { - if (Buffer.isBuffer(msg)) { - delete msg._isBuffer - var data = new Buffer(msg) - this.push(data) - } else { - this.push(msg) - } -} - -/** - * Callback triggered when the remote Port - * associated with this Stream disconnects. - * - * @private - */ -PortDuplexStream.prototype._onDisconnect = function () { - this.destroy() -} - -/** - * Explicitly sets read operations to a no-op - */ -PortDuplexStream.prototype._read = noop - - -/** - * Called internally when data should be written to - * this writable stream. - * - * @private - * @param {*} msg Arbitrary object to write - * @param {string} encoding Encoding to use when writing payload - * @param {Function} cb Called when writing is complete or an error occurs - */ -PortDuplexStream.prototype._write = function (msg, encoding, cb) { - try { - if (Buffer.isBuffer(msg)) { - var data = msg.toJSON() - data._isBuffer = true - this._port.postMessage(data) - } else { - this._port.postMessage(msg) - } - } catch (err) { - return cb(new Error('PortDuplexStream - disconnected')) - } - cb() -} diff --git a/app/scripts/ui.js b/app/scripts/ui.js index da100f928..98a036338 100644 --- a/app/scripts/ui.js +++ b/app/scripts/ui.js @@ -2,7 +2,7 @@ const injectCss = require('inject-css') const OldMetaMaskUiCss = require('../../old-ui/css') const NewMetaMaskUiCss = require('../../ui/css') const startPopup = require('./popup-core') -const PortStream = require('./lib/port-stream.js') +const PortStream = require('extension-port-stream') const { getEnvironmentType } = require('./lib/util') const { ENVIRONMENT_TYPE_NOTIFICATION } = require('./lib/enums') const extension = require('extensionizer') -- cgit From 68c1b4c17049e3ef18397ae83b0eb9da8cccab2c Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Mon, 20 Aug 2018 22:32:14 -0300 Subject: watchAsset returns result wether token was added or not --- app/scripts/background.js | 20 +++++++++++++++++++- app/scripts/controllers/preferences.js | 16 +++++++++------- app/scripts/metamask-controller.js | 4 ++-- 3 files changed, 30 insertions(+), 10 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 029ad139a..1913d35dd 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -256,7 +256,7 @@ function setupController (initState, initLangCode) { showUnconfirmedMessage: triggerUi, unlockAccountMessage: triggerUi, showUnapprovedTx: triggerUi, - showAddTokenUi: triggerUi, + showWatchAssetUi: showWatchAssetUi, // initial state initState, // initial locale code @@ -444,6 +444,24 @@ function triggerUi () { }) } +/** + * Opens the browser popup for user confirmation of watchAsset + * then it waits until user interact with the UI + */ +function showWatchAssetUi () { + triggerUi() + return new Promise( + (resolve) => { + var interval = setInterval(() => { + if (!notificationIsOpen) { + clearInterval(interval) + resolve() + } + }, 1000) + } + ) +} + // On first install, open a window to MetaMask website to how-it-works. extension.runtime.onInstalled.addListener(function (details) { if ((details.reason === 'install') && (!METAMASK_DEBUG)) { diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 611d2d067..11f36e284 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -41,7 +41,7 @@ class PreferencesController { this.diagnostics = opts.diagnostics this.network = opts.network this.store = new ObservableStore(initState) - this.showAddTokenUi = opts.showAddTokenUi + this.showWatchAssetUi = opts.showWatchAssetUi this._subscribeProviderType() } // PUBLIC METHODS @@ -82,13 +82,12 @@ class PreferencesController { * @param {Function} - next * @param {Function} - end */ - requestAddToken (req, res, next, end) { + async requestWatchAsset (req, res, next, end) { if (req.method === 'metamask_watchAsset') { const { type, options } = req.params switch (type) { case 'ERC20': - this._handleWatchAssetERC20(options, res) - res.result = options.address + res.result = await this._handleWatchAssetERC20(options) end() break default: @@ -521,17 +520,20 @@ class PreferencesController { /** * Handle the suggestion of an ERC20 asset through `watchAsset` * * - * @param {Object} options Parameters according to addition of ERC20 token + * @param {Boolean} assetAdded Boolean according to addition of ERC20 token * */ - _handleWatchAssetERC20 (options) { + async _handleWatchAssetERC20 (options) { // TODO handle bad parameters const { address, symbol, decimals, imageUrl } = options const rawAddress = address this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) const tokenOpts = { rawAddress, decimals, symbol, imageUrl } this.addSuggestedToken(tokenOpts) - this.showAddTokenUi() + return this.showWatchAssetUi().then(() => { + const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress)) + return tokenAddresses.length > 0 + }) } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 57001fdff..0ee9d730c 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -88,7 +88,7 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, initLangCode: opts.initLangCode, - showAddTokenUi: opts.showAddTokenUi, + showWatchAssetUi: opts.showWatchAssetUi, network: this.networkController, }) @@ -1241,7 +1241,7 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(createOriginMiddleware({ origin })) engine.push(createLoggerMiddleware({ origin })) engine.push(filterMiddleware) - engine.push(this.preferencesController.requestAddToken.bind(this.preferencesController)) + engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection -- cgit From 34e3ec60927c60e8db49fb69f70c189e66b1490c Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 21 Aug 2018 00:04:07 -0400 Subject: fix account removal --- app/scripts/metamask-controller.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 2d7d2c671..cee7bf398 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -800,7 +800,8 @@ module.exports = class MetamaskController extends EventEmitter { // Remove account from the preferences controller this.preferencesController.removeAddress(address) // Remove account from the account tracker controller - this.accountTracker.removeAccount(address) + this.accountTracker.removeAccount([address]) + // Remove account from the keyring await this.keyringController.removeAccount(address) return address -- cgit From cee57832835b279f9676c35c8ccc27d94368d13f Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 21 Aug 2018 00:04:30 -0400 Subject: fix hardware wallets account name --- app/scripts/metamask-controller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index cee7bf398..29838ad2d 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -668,7 +668,9 @@ module.exports = class MetamaskController extends EventEmitter { this.preferencesController.setAddresses(newAccounts) newAccounts.forEach(address => { if (!oldAccounts.includes(address)) { - this.preferencesController.setAccountLabel(address, `${deviceName.toUpperCase()} ${parseInt(index, 10) + 1}`) + // Set the account label to Trezor 1 / Ledger 1, etc + this.preferencesController.setAccountLabel(address, `${deviceName[0].toUpperCase()}${deviceName.slice(1)} ${parseInt(index, 10) + 1}`) + // Select the account this.preferencesController.setSelectedAddress(address) } }) -- cgit From 6fa889abcb2e907073e227379e1fc930d22bfe2d Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 21 Aug 2018 12:59:42 -0300 Subject: refactor watchToken related functions --- app/scripts/controllers/preferences.js | 73 +++++++++++++++++----------------- 1 file changed, 36 insertions(+), 37 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 11f36e284..04a9f2e75 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -15,7 +15,7 @@ class PreferencesController { * @property {string} store.currentAccountTab Indicates the selected tab in the ui * @property {array} store.tokens The tokens the user wants display in their token lists * @property {object} store.accountTokens The tokens stored per account and then per network type - * @property {object} store.imageObjects Contains assets objects related to assets added + * @property {object} store.assetImages Contains assets objects related to assets added * @property {boolean} store.useBlockie The users preference for blockie identicons within the UI * @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the * user wishes to see that feature @@ -28,7 +28,7 @@ class PreferencesController { frequentRpcList: [], currentAccountTab: 'history', accountTokens: {}, - imageObjects: {}, + assetImages: {}, tokens: [], suggestedTokens: {}, useBlockie: false, @@ -60,12 +60,12 @@ class PreferencesController { return this.store.getState().suggestedTokens } - getImageObjects () { - return this.store.getState().imageObjects + getAssetImages () { + return this.store.getState().assetImages } - addSuggestedToken (tokenOpts) { - this._validateSuggestedTokenParams(tokenOpts) + addSuggestedERC20Asset (tokenOpts) { + this._validateERC20AssetParams(tokenOpts) const suggested = this.getSuggestedTokens() const { rawAddress, symbol, decimals, imageUrl } = tokenOpts const address = normalizeAddress(rawAddress) @@ -291,7 +291,7 @@ class PreferencesController { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } const tokens = this.store.getState().tokens - const imageObjects = this.getImageObjects() + const assetImages = this.getAssetImages() const previousEntry = tokens.find((token, index) => { return token.address === address }) @@ -302,8 +302,8 @@ class PreferencesController { } else { tokens.push(newEntry) } - imageObjects[address] = imageUrl - this._updateAccountTokens(tokens, imageObjects) + assetImages[address] = imageUrl + this._updateAccountTokens(tokens, assetImages) return Promise.resolve(tokens) } @@ -316,10 +316,10 @@ class PreferencesController { */ removeToken (rawAddress) { const tokens = this.store.getState().tokens - const imageObjects = this.getImageObjects() + const assetImages = this.getAssetImages() const updatedTokens = tokens.filter(token => token.address !== rawAddress) - delete imageObjects[rawAddress] - this._updateAccountTokens(updatedTokens, imageObjects) + delete assetImages[rawAddress] + this._updateAccountTokens(updatedTokens, assetImages) return Promise.resolve(updatedTokens) } @@ -446,25 +446,6 @@ class PreferencesController { // PRIVATE METHODS // - /** - * Validates that the passed options for suggested token have all required properties. - * - * @param {Object} opts The options object to validate - * @throws {string} Throw a custom error indicating that address, symbol and/or decimals - * doesn't fulfill requirements - * - */ - _validateSuggestedTokenParams (opts) { - const { rawAddress, symbol, decimals } = opts - if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`) - if (!(symbol.length < 5)) throw new Error(`Invalid symbol ${symbol} more than four characters`) - const numDecimals = parseInt(decimals, 10) - if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { - throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) - } - if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) - } - /** * Subscription to network provider type. * @@ -483,10 +464,10 @@ class PreferencesController { * @param {array} tokens Array of tokens to be updated. * */ - _updateAccountTokens (tokens, imageObjects) { + _updateAccountTokens (tokens, assetImages) { const { accountTokens, providerType, selectedAddress } = this._getTokenRelatedStates() accountTokens[selectedAddress][providerType] = tokens - this.store.updateState({ accountTokens, tokens, imageObjects }) + this.store.updateState({ accountTokens, tokens, assetImages }) } /** @@ -520,21 +501,39 @@ class PreferencesController { /** * Handle the suggestion of an ERC20 asset through `watchAsset` * * - * @param {Boolean} assetAdded Boolean according to addition of ERC20 token + * @param {Promise} promise Promise according to addition of ERC20 token * */ async _handleWatchAssetERC20 (options) { - // TODO handle bad parameters const { address, symbol, decimals, imageUrl } = options const rawAddress = address - this._validateSuggestedTokenParams({ rawAddress, symbol, decimals }) + this._validateERC20AssetParams({ rawAddress, symbol, decimals }) const tokenOpts = { rawAddress, decimals, symbol, imageUrl } - this.addSuggestedToken(tokenOpts) + this.addSuggestedERC20Asset(tokenOpts) return this.showWatchAssetUi().then(() => { const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress)) return tokenAddresses.length > 0 }) } + + /** + * Validates that the passed options for suggested token have all required properties. + * + * @param {Object} opts The options object to validate + * @throws {string} Throw a custom error indicating that address, symbol and/or decimals + * doesn't fulfill requirements + * + */ + _validateERC20AssetParams (opts) { + const { rawAddress, symbol, decimals } = opts + if (!rawAddress || !symbol || !decimals) throw new Error(`Cannot suggest token without address, symbol, and decimals`) + if (!(symbol.length < 6)) throw new Error(`Invalid symbol ${symbol} more than five characters`) + const numDecimals = parseInt(decimals, 10) + if (isNaN(numDecimals) || numDecimals > 36 || numDecimals < 0) { + throw new Error(`Invalid decimals ${decimals} must be at least 0, and not over 36`) + } + if (!isValidAddress(rawAddress)) throw new Error(`Invalid address ${rawAddress}`) + } } module.exports = PreferencesController -- cgit From 3a3732eb2471c83722d41f2389f34c8759b5e2cb Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Tue, 21 Aug 2018 13:12:45 -0300 Subject: returning error in watchAsset --- app/scripts/controllers/preferences.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 04a9f2e75..a03abbf79 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -87,11 +87,15 @@ class PreferencesController { const { type, options } = req.params switch (type) { case 'ERC20': - res.result = await this._handleWatchAssetERC20(options) - end() + const result = await this._handleWatchAssetERC20(options) + if (result instanceof Error) { + end(result) + } else { + res.result = result + end() + } break default: - // TODO return promise for not handled assets end(new Error(`Asset of type ${type} not supported`)) } } else { @@ -507,7 +511,11 @@ class PreferencesController { async _handleWatchAssetERC20 (options) { const { address, symbol, decimals, imageUrl } = options const rawAddress = address - this._validateERC20AssetParams({ rawAddress, symbol, decimals }) + try { + this._validateERC20AssetParams({ rawAddress, symbol, decimals }) + } catch (err) { + return err + } const tokenOpts = { rawAddress, decimals, symbol, imageUrl } this.addSuggestedERC20Asset(tokenOpts) return this.showWatchAssetUi().then(() => { -- cgit From 13dfea7f2da56d46a4ef3af63cda8ecaae6b04da Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 21 Aug 2018 14:13:23 -0700 Subject: bugfix - prevents old blocktracker from getting internal hooks migrated --- app/scripts/controllers/network/network.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/network.js b/app/scripts/controllers/network/network.js index 76fdc3391..c1667d9a6 100644 --- a/app/scripts/controllers/network/network.js +++ b/app/scripts/controllers/network/network.js @@ -185,7 +185,7 @@ module.exports = class NetworkController extends EventEmitter { if (this._blockTrackerProxy) { this._blockTrackerProxy.setTarget(blockTracker) } else { - this._blockTrackerProxy = createEventEmitterProxy(blockTracker) + this._blockTrackerProxy = createEventEmitterProxy(blockTracker, { eventFilter: 'skipInternal' }) } // set new provider and blockTracker this._provider = provider -- cgit From 3ac2b40dcf4b60f13184ff8b97f9099a6a22852e Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 21 Aug 2018 16:30:11 -0700 Subject: metamask controller - track active controller connections --- app/scripts/metamask-controller.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 29838ad2d..9082daac9 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -67,6 +67,10 @@ module.exports = class MetamaskController extends EventEmitter { const initState = opts.initState || {} this.recordFirstTimeInfo(initState) + // this keeps track of how many "controllerStream" connections are open + // the only thing that uses controller connections are open metamask UI instances + this.activeControllerConnections = 0 + // platform-specific api this.platform = opts.platform @@ -1209,11 +1213,19 @@ module.exports = class MetamaskController extends EventEmitter { setupControllerConnection (outStream) { const api = this.getApi() const dnode = Dnode(api) + // report new active controller connection + this.activeControllerConnections++ + this.emit('controllerConnectionChanged', this.activeControllerConnections) + // connect dnode api to remote connection pump( outStream, dnode, outStream, (err) => { + // report new active controller connection + this.activeControllerConnections-- + this.emit('controllerConnectionChanged', this.activeControllerConnections) + // report any error if (err) log.error(err) } ) -- cgit From a2654108bed88db5cd09c22049471c5c3f1199d6 Mon Sep 17 00:00:00 2001 From: kumavis Date: Tue, 21 Aug 2018 16:49:24 -0700 Subject: account-tracker - only track blocks when there are activeControllerConnections --- app/scripts/lib/account-tracker.js | 18 ++++++++++++++++-- app/scripts/metamask-controller.js | 8 ++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index b7e2c7cbe..3a52d5e8d 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -43,10 +43,24 @@ class AccountTracker { this._provider = opts.provider this._query = pify(new EthQuery(this._provider)) this._blockTracker = opts.blockTracker - // subscribe to latest block - this._blockTracker.on('latest', this._updateForBlock.bind(this)) // blockTracker.currentBlock may be null this._currentBlockNumber = this._blockTracker.getCurrentBlock() + // bind function for easier listener syntax + this._updateForBlock = this._updateForBlock.bind(this) + } + + start () { + // remove first to avoid double add + this._blockTracker.removeListener('latest', this._updateForBlock) + // add listener + this._blockTracker.addListener('latest', this._updateForBlock) + // fetch account balances + this._updateAccounts() + } + + stop () { + // remove listener + this._blockTracker.removeListener('latest', this._updateForBlock) } /** diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 9082daac9..71df45ba0 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -131,6 +131,14 @@ module.exports = class MetamaskController extends EventEmitter { provider: this.provider, blockTracker: this.blockTracker, }) + // start and stop polling for balances based on activeControllerConnections + this.on('controllerConnectionChanged', (activeControllerConnections) => { + if (activeControllerConnections > 0) { + this.accountTracker.start() + } else { + this.accountTracker.stop() + } + }) // key mgmt const additionalKeyrings = [TrezorKeyring, LedgerBridgeKeyring] -- cgit From 003d445a98164dac0c0529dfd69f5e3987d7d05c Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Thu, 16 Aug 2018 12:59:39 -0230 Subject: Update unlock logic to not overwrite existing selected address --- app/scripts/metamask-controller.js | 42 ++++++++++++++++++++++++++------------ 1 file changed, 29 insertions(+), 13 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 29838ad2d..4ee88186a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -137,19 +137,7 @@ module.exports = class MetamaskController extends EventEmitter { encryptor: opts.encryptor || undefined, }) - // If only one account exists, make sure it is selected. - this.keyringController.memStore.subscribe((state) => { - const addresses = state.keyrings.reduce((res, keyring) => { - return res.concat(keyring.accounts) - }, []) - if (addresses.length === 1) { - const address = addresses[0] - this.preferencesController.setSelectedAddress(address) - } - // ensure preferences + identities controller know about all addresses - this.preferencesController.addAddresses(addresses) - this.accountTracker.syncWithAddresses(addresses) - }) + this.keyringController.memStore.subscribe((s) => this._onKeyringControllerUpdate(s)) // detect tokens controller this.detectTokensController = new DetectTokensController({ @@ -1278,6 +1266,34 @@ module.exports = class MetamaskController extends EventEmitter { ) } + /** + * Handle a KeyringController update + * @param {object} state the KC state + * @return {Promise} + * @private + */ + async _onKeyringControllerUpdate (state) { + const {isUnlocked, keyrings} = state + const addresses = keyrings.reduce((acc, {accounts}) => acc.concat(accounts), []) + + if (!addresses.length) { + return + } + + // Ensure preferences + identities controller know about all addresses + this.preferencesController.addAddresses(addresses) + this.accountTracker.syncWithAddresses(addresses) + + const wasLocked = !isUnlocked + if (wasLocked) { + const oldSelectedAddress = this.preferencesController.getSelectedAddress() + if (!addresses.includes(oldSelectedAddress)) { + const address = addresses[0] + await this.preferencesController.setSelectedAddress(address) + } + } + } + /** * A method for emitting the full MetaMask state to all registered listeners. * @private -- cgit From bf6d624e769eb5486b5c491165fad1862435669b Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Wed, 22 Aug 2018 12:05:41 -0700 Subject: Add todo to dedupe UI tracking in background --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 71df45ba0..4aa901e31 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1444,6 +1444,7 @@ module.exports = class MetamaskController extends EventEmitter { } } + // TODO: Replace isClientOpen methods with `controllerConnectionChanged` events. /** * A method for recording whether the MetaMask user interface is open or not. * @private -- cgit From 56bed3f1bce3cde176784026b10bc3bbe8e819d0 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Wed, 22 Aug 2018 18:14:10 -0300 Subject: expose web3.metamask.watchAsset --- app/scripts/lib/auto-reload.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index cce31c3d2..f3c89ecdb 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -14,6 +14,23 @@ function setupDappAutoReload (web3, observable) { 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 } + // setup wallet + if (key === 'metamask') { + return { + watchAsset: (params) => { + return new Promise((resolve, reject) => { + web3.currentProvider.sendAsync({ + jsonrpc: '2.0', + method: 'metamask_watchAsset', + params, + }, (err, res) => { + if (err) reject(err) + resolve(res) + }) + }) + }, + } + } // get the time of use lastTimeUsed = Date.now() // return value normally -- cgit From b23cca14699b6c6a8c843c9cc020ec96fe758822 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Sun, 19 Aug 2018 20:25:33 +0300 Subject: implemented improvements to RPC history --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 707fd7de9..7456a7a7c 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -338,7 +338,7 @@ class PreferencesController { if (_url !== 'http://localhost:8545') { rpcList.push(_url) } - if (rpcList.length > 2) { + if (rpcList.length > 3) { rpcList.shift() } return Promise.resolve(rpcList) -- cgit From 9a80d6e8598850fec00471c6101c194e90c30353 Mon Sep 17 00:00:00 2001 From: Evgeniy Filatov Date: Thu, 23 Aug 2018 01:26:30 +0300 Subject: updated docs, small improvement of recent RPC rendering --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 7456a7a7c..1b85e4fd1 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -322,7 +322,7 @@ class PreferencesController { /** * Returns an updated rpcList based on the passed url and the current list. - * The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the + * The returned list will have a max length of 3. If the _url currently exists it the list, it will be moved to the * end of the list. The current list is modified and returned as a promise. * * @param {string} _url The rpc url to add to the frequentRpcList. -- cgit From b59a1e91b8f4b595500a0785f325e833fa35407d Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 23 Aug 2018 15:54:40 -0300 Subject: typo watchAsset imageUrl to image --- app/scripts/controllers/preferences.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index a03abbf79..d57aec71a 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -67,9 +67,9 @@ class PreferencesController { addSuggestedERC20Asset (tokenOpts) { this._validateERC20AssetParams(tokenOpts) const suggested = this.getSuggestedTokens() - const { rawAddress, symbol, decimals, imageUrl } = tokenOpts + const { rawAddress, symbol, decimals, image } = tokenOpts const address = normalizeAddress(rawAddress) - const newEntry = { address, symbol, decimals, imageUrl } + const newEntry = { address, symbol, decimals, image } suggested[address] = newEntry this.store.updateState({ suggestedTokens: suggested }) } @@ -291,7 +291,7 @@ class PreferencesController { * @returns {Promise} Promises the new array of AddedToken objects. * */ - async addToken (rawAddress, symbol, decimals, imageUrl) { + async addToken (rawAddress, symbol, decimals, image) { const address = normalizeAddress(rawAddress) const newEntry = { address, symbol, decimals } const tokens = this.store.getState().tokens @@ -306,7 +306,7 @@ class PreferencesController { } else { tokens.push(newEntry) } - assetImages[address] = imageUrl + assetImages[address] = image this._updateAccountTokens(tokens, assetImages) return Promise.resolve(tokens) } @@ -509,14 +509,14 @@ class PreferencesController { * */ async _handleWatchAssetERC20 (options) { - const { address, symbol, decimals, imageUrl } = options + const { address, symbol, decimals, image } = options const rawAddress = address try { this._validateERC20AssetParams({ rawAddress, symbol, decimals }) } catch (err) { return err } - const tokenOpts = { rawAddress, decimals, symbol, imageUrl } + const tokenOpts = { rawAddress, decimals, symbol, image } this.addSuggestedERC20Asset(tokenOpts) return this.showWatchAssetUi().then(() => { const tokenAddresses = this.getTokens().filter(token => token.address === normalizeAddress(rawAddress)) -- cgit From 053e262ae738af29cfe67e702350f1f046b4b311 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Thu, 23 Aug 2018 16:32:04 -0300 Subject: delete web3.metamassk.watchAsset --- app/scripts/lib/auto-reload.js | 17 ----------------- 1 file changed, 17 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index f3c89ecdb..cce31c3d2 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -14,23 +14,6 @@ function setupDappAutoReload (web3, observable) { 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 } - // setup wallet - if (key === 'metamask') { - return { - watchAsset: (params) => { - return new Promise((resolve, reject) => { - web3.currentProvider.sendAsync({ - jsonrpc: '2.0', - method: 'metamask_watchAsset', - params, - }, (err, res) => { - if (err) reject(err) - resolve(res) - }) - }) - }, - } - } // get the time of use lastTimeUsed = Date.now() // return value normally -- cgit From 803a79f8367080141e38ed73065b5a3467f5a196 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Fri, 24 Aug 2018 12:40:37 -0400 Subject: Do not resolve .test domains using ENS --- app/scripts/lib/ipfsContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 5db63f47d..38682b916 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -34,7 +34,7 @@ module.exports = function (provider) { return { cancel: true } } - extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/', '*://*.test/']}) + extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/']}) return { remove () { -- cgit From 3106374cc31b66e5a0faadd657b4430e21aa48b2 Mon Sep 17 00:00:00 2001 From: Esteban MIno Date: Mon, 27 Aug 2018 22:10:14 -0300 Subject: watchAsset small changes --- app/scripts/controllers/preferences.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index d57aec71a..4798b2ad6 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -1,6 +1,6 @@ const ObservableStore = require('obs-store') const normalizeAddress = require('eth-sig-util').normalize -const isValidAddress = require('ethereumjs-util').isValidAddress +const { isValidAddress } = require('ethereumjs-util') const extend = require('xtend') -- cgit From 7c3b69e1e495ed0d44cd1ed43db55828f3e05642 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Sat, 8 Sep 2018 19:59:50 -0230 Subject: Attach the RPC error value to txMeta --- app/scripts/controllers/transactions/tx-state-manager.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/tx-state-manager.js b/app/scripts/controllers/transactions/tx-state-manager.js index 28a18ca2e..daa6cc388 100644 --- a/app/scripts/controllers/transactions/tx-state-manager.js +++ b/app/scripts/controllers/transactions/tx-state-manager.js @@ -353,6 +353,7 @@ class TransactionStateManager extends EventEmitter { const txMeta = this.getTx(txId) txMeta.err = { message: err.toString(), + rpc: err.value, stack: err.stack, } this.updateTx(txMeta) -- cgit From ee568d5f5a3d04f32969fd2ba3113b9eeb175d63 Mon Sep 17 00:00:00 2001 From: Connor Christie Date: Sun, 9 Sep 2018 19:33:09 -0500 Subject: Upgrade obs-store and fix memory leaks --- app/scripts/metamask-controller.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 98cb62bfa..1060f508a 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -177,7 +177,7 @@ module.exports = class MetamaskController extends EventEmitter { blockTracker: this.blockTracker, getGasPrice: this.getGasPrice.bind(this), }) - this.txController.on('newUnapprovedTx', opts.showUnapprovedTx.bind(opts)) + this.txController.on('newUnapprovedTx', () => opts.showUnapprovedTx()) this.txController.on(`tx:status-update`, (txId, status) => { if (status === 'confirmed' || status === 'failed') { @@ -1229,8 +1229,10 @@ module.exports = class MetamaskController extends EventEmitter { ) dnode.on('remote', (remote) => { // push updates to popup - const sendUpdate = remote.sendUpdate.bind(remote) + const sendUpdate = (update) => remote.sendUpdate(update) this.on('update', sendUpdate) + // remove update listener once the connection ends + dnode.on('end', () => this.removeListener('update', sendUpdate)) }) } @@ -1280,10 +1282,12 @@ module.exports = class MetamaskController extends EventEmitter { * @param {*} outStream - The stream to provide public config over. */ setupPublicConfig (outStream) { + const configStream = asStream(this.publicConfigStore) pump( - asStream(this.publicConfigStore), + configStream, outStream, (err) => { + configStream.destroy() if (err) log.error(err) } ) -- cgit From 43de189d067f8cf03cdd97380cbe2487319271eb Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Sun, 9 Sep 2018 10:07:23 -0700 Subject: Add createCancelTransaction method --- app/scripts/controllers/transactions/enums.js | 12 +++++++ app/scripts/controllers/transactions/index.js | 49 +++++++++++++++++++++++++-- app/scripts/metamask-controller.js | 14 ++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 app/scripts/controllers/transactions/enums.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/enums.js b/app/scripts/controllers/transactions/enums.js new file mode 100644 index 000000000..be6f16e0d --- /dev/null +++ b/app/scripts/controllers/transactions/enums.js @@ -0,0 +1,12 @@ +const TRANSACTION_TYPE_CANCEL = 'cancel' +const TRANSACTION_TYPE_RETRY = 'retry' +const TRANSACTION_TYPE_STANDARD = 'standard' + +const TRANSACTION_STATUS_APPROVED = 'approved' + +module.exports = { + TRANSACTION_TYPE_CANCEL, + TRANSACTION_TYPE_RETRY, + TRANSACTION_TYPE_STANDARD, + TRANSACTION_STATUS_APPROVED, +} diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 5d7d6d6da..59e30cdde 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -11,6 +11,14 @@ const txUtils = require('./lib/util') const cleanErrorStack = require('../../lib/cleanErrorStack') const log = require('loglevel') const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker') +const { + TRANSACTION_TYPE_CANCEL, + TRANSACTION_TYPE_RETRY, + TRANSACTION_TYPE_STANDARD, + TRANSACTION_STATUS_APPROVED, +} = require('./enums') + +const { hexToBn, bnToHex } = require('../../lib/util') /** Transaction Controller is an aggregate of sub-controllers and trackers @@ -160,7 +168,10 @@ class TransactionController extends EventEmitter { const normalizedTxParams = txUtils.normalizeTxParams(txParams) txUtils.validateTxParams(normalizedTxParams) // construct txMeta - let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams }) + let txMeta = this.txStateManager.generateTxMeta({ + txParams: normalizedTxParams, + type: TRANSACTION_TYPE_STANDARD, + }) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) @@ -214,12 +225,46 @@ class TransactionController extends EventEmitter { txParams: originalTxMeta.txParams, lastGasPrice, loadingDefaults: false, + type: TRANSACTION_TYPE_RETRY, }) this.addTx(txMeta) this.emit('newUnapprovedTx', txMeta) return txMeta } + /** + * Creates a new approved transaction to attempt to cancel a previously submitted transaction. The + * new transaction contains the same nonce as the previous, is a basic ETH transfer of 0x value to + * the sender's address, and has a higher gasPrice than that of the previous transaction. + * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel + * @param {string=} customGasPrice - the hex value to use for the cancel transaction + * @returns {txMeta} + */ + async createCancelTransaction (originalTxId, customGasPrice) { + const originalTxMeta = this.txStateManager.getTx(originalTxId) + const { txParams } = originalTxMeta + const { gasPrice: lastGasPrice, from, nonce } = txParams + const newGasPrice = customGasPrice || bnToHex(hexToBn(lastGasPrice).mul(1.1)) + const newTxMeta = this.txStateManager.generateTxMeta({ + txParams: { + from, + to: from, + nonce, + gas: '0x5208', + value: '0x0', + gasPrice: newGasPrice, + }, + lastGasPrice, + loadingDefaults: false, + status: TRANSACTION_STATUS_APPROVED, + type: TRANSACTION_TYPE_CANCEL, + }) + + this.addTx(newTxMeta) + await this.approveTransaction(newTxMeta.id) + return newTxMeta + } + /** updates the txMeta in the txStateManager @param txMeta {Object} - the updated txMeta @@ -393,7 +438,7 @@ class TransactionController extends EventEmitter { }) this.txStateManager.getFilteredTxList({ - status: 'approved', + status: TRANSACTION_STATUS_APPROVED, }).forEach((txMeta) => { const txSignError = new Error('Transaction found as "approved" during boot - possibly stuck during signing') this.txStateManager.setTxStatusFailed(txMeta.id, txSignError) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 98cb62bfa..9b373de9b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -407,6 +407,7 @@ module.exports = class MetamaskController extends EventEmitter { updateTransaction: nodeify(txController.updateTransaction, txController), updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController), retryTransaction: nodeify(this.retryTransaction, this), + createCancelTransaction: nodeify(this.createCancelTransaction, this), getFilteredTxList: nodeify(txController.getFilteredTxList, txController), isNonceTaken: nodeify(txController.isNonceTaken, txController), estimateGas: nodeify(this.estimateGas, this), @@ -1109,6 +1110,19 @@ module.exports = class MetamaskController extends EventEmitter { return state } + /** + * Allows a user to attempt to cancel a previously submitted transaction by creating a new + * transaction. + * @param {number} originalTxId - the id of the txMeta that you want to attempt to cancel + * @param {string=} customGasPrice - the hex value to use for the cancel transaction + * @returns {object} MetaMask state + */ + async createCancelTransaction (originalTxId, customGasPrice, cb) { + await this.txController.createCancelTransaction(originalTxId, customGasPrice) + const state = await this.getState() + return state + } + estimateGas (estimateGasParams) { return new Promise((resolve, reject) => { return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => { -- cgit From eb32ccb0c77c6e480c93f02c29faafec3cd93ec0 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Tue, 11 Sep 2018 12:51:49 -0230 Subject: Ensure account-tracker currentBlockNumber is set on first block update. --- app/scripts/lib/account-tracker.js | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 3a52d5e8d..2e9340018 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -45,6 +45,9 @@ class AccountTracker { this._blockTracker = opts.blockTracker // blockTracker.currentBlock may be null this._currentBlockNumber = this._blockTracker.getCurrentBlock() + this._blockTracker.once('latest', blockNumber => { + this._currentBlockNumber = blockNumber + }) // bind function for easier listener syntax this._updateForBlock = this._updateForBlock.bind(this) } -- cgit From d60991ec88fe88be4041eb4a9392df6dfc1aa973 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 10 Sep 2018 17:01:15 -0700 Subject: Delete ConfigManager, replacing its usages with PreferencesController --- app/scripts/controllers/preferences.js | 18 +++ app/scripts/lib/config-manager.js | 254 ------------------------------- app/scripts/metamask-controller.js | 46 ++---- app/scripts/migrations/_multi-keyring.js | 50 ------ 4 files changed, 27 insertions(+), 341 deletions(-) delete mode 100644 app/scripts/lib/config-manager.js delete mode 100644 app/scripts/migrations/_multi-keyring.js (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 464a37017..928ebdf1f 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -36,6 +36,8 @@ class PreferencesController { currentLocale: opts.initLangCode, identities: {}, lostIdentities: {}, + seedWords: null, + forgottenPassword: false, }, opts.initState) this.diagnostics = opts.diagnostics @@ -46,6 +48,22 @@ class PreferencesController { } // PUBLIC METHODS + /** + * Sets the {@code forgottenPassword} state property + * @param {boolean} forgottenPassword whether or not the user has forgotten their password + */ + setPasswordForgotten (forgottenPassword) { + this.store.updateState({ forgottenPassword }) + } + + /** + * Sets the {@code seedWords} seed words + * @param {string|null} seedWords the seed words + */ + setSeedWords (seedWords) { + this.store.updateState({ seedWords }) + } + /** * Setter for the `useBlockie` property * diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js deleted file mode 100644 index 221746467..000000000 --- a/app/scripts/lib/config-manager.js +++ /dev/null @@ -1,254 +0,0 @@ -const ethUtil = require('ethereumjs-util') -const normalize = require('eth-sig-util').normalize -const { - MAINNET_RPC_URL, - ROPSTEN_RPC_URL, - KOVAN_RPC_URL, - RINKEBY_RPC_URL, -} = require('../controllers/network/enums') - -/* The config-manager is a convenience object - * wrapping a pojo-migrator. - * - * It exists mostly to allow the creation of - * convenience methods to access and persist - * particular portions of the state. - */ -module.exports = ConfigManager -function ConfigManager (opts) { - // ConfigManager is observable and will emit updates - this._subs = [] - this.store = opts.store -} - -ConfigManager.prototype.setConfig = function (config) { - var data = this.getData() - data.config = config - this.setData(data) - this._emitUpdates(config) -} - -ConfigManager.prototype.getConfig = function () { - var data = this.getData() - return data.config -} - -ConfigManager.prototype.setData = function (data) { - this.store.putState(data) -} - -ConfigManager.prototype.getData = function () { - return this.store.getState() -} - -ConfigManager.prototype.setPasswordForgotten = function (passwordForgottenState) { - const data = this.getData() - data.forgottenPassword = passwordForgottenState - this.setData(data) -} - -ConfigManager.prototype.getPasswordForgotten = function (passwordForgottenState) { - const data = this.getData() - return data.forgottenPassword -} - -ConfigManager.prototype.setWallet = function (wallet) { - var data = this.getData() - data.wallet = wallet - this.setData(data) -} - -ConfigManager.prototype.setVault = function (encryptedString) { - var data = this.getData() - data.vault = encryptedString - this.setData(data) -} - -ConfigManager.prototype.getVault = function () { - var data = this.getData() - return data.vault -} - -ConfigManager.prototype.getKeychains = function () { - return this.getData().keychains || [] -} - -ConfigManager.prototype.setKeychains = function (keychains) { - var data = this.getData() - data.keychains = keychains - this.setData(data) -} - -ConfigManager.prototype.getSelectedAccount = function () { - var config = this.getConfig() - return config.selectedAccount -} - -ConfigManager.prototype.setSelectedAccount = function (address) { - var config = this.getConfig() - config.selectedAccount = ethUtil.addHexPrefix(address) - this.setConfig(config) -} - -ConfigManager.prototype.getWallet = function () { - return this.getData().wallet -} - -// Takes a boolean -ConfigManager.prototype.setShowSeedWords = function (should) { - var data = this.getData() - data.showSeedWords = should - this.setData(data) -} - - -ConfigManager.prototype.getShouldShowSeedWords = function () { - var data = this.getData() - return data.showSeedWords -} - -ConfigManager.prototype.setSeedWords = function (words) { - var data = this.getData() - data.seedWords = words - this.setData(data) -} - -ConfigManager.prototype.getSeedWords = function () { - var data = this.getData() - return data.seedWords -} -ConfigManager.prototype.setRpcTarget = function (rpcUrl) { - var config = this.getConfig() - config.provider = { - type: 'rpc', - rpcTarget: rpcUrl, - } - this.setConfig(config) -} - -ConfigManager.prototype.setProviderType = function (type) { - var config = this.getConfig() - config.provider = { - type: type, - } - this.setConfig(config) -} - -ConfigManager.prototype.useEtherscanProvider = function () { - var config = this.getConfig() - config.provider = { - type: 'etherscan', - } - this.setConfig(config) -} - -ConfigManager.prototype.getProvider = function () { - var config = this.getConfig() - return config.provider -} - -ConfigManager.prototype.getCurrentRpcAddress = function () { - var provider = this.getProvider() - if (!provider) return null - switch (provider.type) { - - case 'mainnet': - return MAINNET_RPC_URL - - case 'ropsten': - return ROPSTEN_RPC_URL - - case 'kovan': - return KOVAN_RPC_URL - - case 'rinkeby': - return RINKEBY_RPC_URL - - default: - return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC_URL - } -} - -// -// Tx -// - -ConfigManager.prototype.getTxList = function () { - var data = this.getData() - if (data.transactions !== undefined) { - return data.transactions - } else { - return [] - } -} - -ConfigManager.prototype.setTxList = function (txList) { - var data = this.getData() - data.transactions = txList - this.setData(data) -} - - -// wallet nickname methods - -ConfigManager.prototype.getWalletNicknames = function () { - var data = this.getData() - const nicknames = ('walletNicknames' in data) ? data.walletNicknames : {} - return nicknames -} - -ConfigManager.prototype.nicknameForWallet = function (account) { - const address = normalize(account) - const nicknames = this.getWalletNicknames() - return nicknames[address] -} - -ConfigManager.prototype.setNicknameForWallet = function (account, nickname) { - const address = normalize(account) - const nicknames = this.getWalletNicknames() - nicknames[address] = nickname - var data = this.getData() - data.walletNicknames = nicknames - this.setData(data) -} - -// observable - -ConfigManager.prototype.getSalt = function () { - var data = this.getData() - return data.salt -} - -ConfigManager.prototype.setSalt = function (salt) { - var data = this.getData() - data.salt = salt - this.setData(data) -} - -ConfigManager.prototype.subscribe = function (fn) { - this._subs.push(fn) - var unsubscribe = this.unsubscribe.bind(this, fn) - return unsubscribe -} - -ConfigManager.prototype.unsubscribe = function (fn) { - var index = this._subs.indexOf(fn) - if (index !== -1) this._subs.splice(index, 1) -} - -ConfigManager.prototype._emitUpdates = function (state) { - this._subs.forEach(function (handler) { - handler(state) - }) -} - -ConfigManager.prototype.setLostAccounts = function (lostAccounts) { - var data = this.getData() - data.lostAccounts = lostAccounts - this.setData(data) -} - -ConfigManager.prototype.getLostAccounts = function () { - var data = this.getData() - return data.lostAccounts || [] -} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 3ab256314..f9a12628b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -36,7 +36,6 @@ const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') const TokenRatesController = require('./controllers/token-rates') const DetectTokensController = require('./controllers/detect-tokens') -const ConfigManager = require('./lib/config-manager') const nodeify = require('./lib/nodeify') const accountImporter = require('./account-import-strategies') const getBuyEthUrl = require('./lib/buy-eth-url') @@ -83,11 +82,6 @@ module.exports = class MetamaskController extends EventEmitter { // network store this.networkController = new NetworkController(initState.NetworkController) - // config manager - this.configManager = new ConfigManager({ - store: this.store, - }) - // preferences controller this.preferencesController = new PreferencesController({ initState: initState.PreferencesController, @@ -314,18 +308,15 @@ module.exports = class MetamaskController extends EventEmitter { * @returns {Object} status */ getState () { - const wallet = this.configManager.getWallet() const vault = this.keyringController.store.getState().vault - const isInitialized = (!!wallet || !!vault) + const isInitialized = !!vault return { ...{ isInitialized }, ...this.memStore.getFlatState(), - ...this.configManager.getConfig(), ...{ - lostAccounts: this.configManager.getLostAccounts(), - seedWords: this.configManager.getSeedWords(), - forgottenPassword: this.configManager.getPasswordForgotten(), + // TODO: Remove usages of lost accounts + lostAccounts: [], }, } } @@ -727,7 +718,7 @@ module.exports = class MetamaskController extends EventEmitter { this.verifySeedPhrase() .then((seedWords) => { - this.configManager.setSeedWords(seedWords) + this.preferencesController.setSeedWords(seedWords) return cb(null, seedWords) }) .catch((err) => { @@ -776,7 +767,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {function} cb Callback function called with the current address. */ clearSeedWordCache (cb) { - this.configManager.setSeedWords(null) + this.preferencesController.setSeedWords(null) cb(null, this.preferencesController.getSelectedAddress()) } @@ -1037,35 +1028,16 @@ module.exports = class MetamaskController extends EventEmitter { /** * A legacy method used to record user confirmation that they understand * that some of their accounts have been recovered but should be backed up. + * This function no longer does anything and will be removed. * * @deprecated * @param {Function} cb - A callback function called with a full state update. */ markAccountsFound (cb) { - this.configManager.setLostAccounts([]) - this.sendUpdate() + // TODO Remove me cb(null, this.getState()) } - /** - * A legacy method (probably dead code) that was used when we swapped out our - * key management library that we depended on. - * - * Described in: - * https://medium.com/metamask/metamask-3-migration-guide-914b79533cdd - * - * @deprecated - * @param {} migratorOutput - */ - restoreOldLostAccounts (migratorOutput) { - const { lostAccounts } = migratorOutput - if (lostAccounts) { - this.configManager.setLostAccounts(lostAccounts.map(acct => acct.address)) - return this.importLostAccounts(migratorOutput) - } - return Promise.resolve(migratorOutput) - } - /** * An account object * @typedef Account @@ -1144,7 +1116,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} cb - A callback function called when complete. */ markPasswordForgotten (cb) { - this.configManager.setPasswordForgotten(true) + this.preferencesController.setPasswordForgotten(true) this.sendUpdate() cb() } @@ -1154,7 +1126,7 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} cb - A callback function called when complete. */ unMarkPasswordForgotten (cb) { - this.configManager.setPasswordForgotten(false) + this.preferencesController.setPasswordForgotten(false) this.sendUpdate() cb() } diff --git a/app/scripts/migrations/_multi-keyring.js b/app/scripts/migrations/_multi-keyring.js deleted file mode 100644 index 7a4578ea7..000000000 --- a/app/scripts/migrations/_multi-keyring.js +++ /dev/null @@ -1,50 +0,0 @@ -const version = 5 - -/* - -This is an incomplete migration bc it requires post-decrypted data -which we dont have access to at the time of this writing. - -*/ - -const ObservableStore = require('obs-store') -const ConfigManager = require('../../app/scripts/lib/config-manager') -const IdentityStoreMigrator = require('../../app/scripts/lib/idStore-migrator') -const KeyringController = require('eth-keyring-controller') - -const password = 'obviously not correct' - -module.exports = { - version, - - migrate: function (versionedData) { - versionedData.meta.version = version - - const store = new ObservableStore(versionedData.data) - const configManager = new ConfigManager({ store }) - const idStoreMigrator = new IdentityStoreMigrator({ configManager }) - const keyringController = new KeyringController({ - configManager: configManager, - }) - - // attempt to migrate to multiVault - return idStoreMigrator.migratedVaultForPassword(password) - .then((result) => { - // skip if nothing to migrate - if (!result) return Promise.resolve(versionedData) - delete versionedData.data.wallet - // create new keyrings - const privKeys = result.lostAccounts.map(acct => acct.privateKey) - return Promise.all([ - keyringController.restoreKeyring(result.serialized), - keyringController.restoreKeyring({ type: 'Simple Key Pair', data: privKeys }), - ]).then(() => { - return keyringController.persistAllKeyrings(password) - }).then(() => { - // copy result on to state object - versionedData.data = store.get() - return Promise.resolve(versionedData) - }) - }) - }, -} -- cgit From 13bc46d8243b434268db04b58500720b4884a969 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 11 Sep 2018 12:12:35 -0700 Subject: Default NoticeController ctor opts to empty obj --- app/scripts/notice-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/notice-controller.js b/app/scripts/notice-controller.js index 2def4371e..ce686d9d1 100644 --- a/app/scripts/notice-controller.js +++ b/app/scripts/notice-controller.js @@ -7,7 +7,7 @@ const uniqBy = require('lodash.uniqby') module.exports = class NoticeController extends EventEmitter { - constructor (opts) { + constructor (opts = {}) { super() this.noticePoller = null this.firstVersion = opts.firstVersion -- cgit From 36dd0354e777e6786ae0d2284ffcb1adbc6d85f7 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Mon, 10 Sep 2018 17:11:57 -0400 Subject: Implement latest EIP-712 protocol --- app/scripts/lib/typed-message-manager.js | 74 +++++++++++++++++++++--------- app/scripts/metamask-controller.js | 77 +++++++++++++++++++++++++------- 2 files changed, 112 insertions(+), 39 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index e5e1c94b3..3e97023f5 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -4,6 +4,7 @@ const createId = require('./random-id') const assert = require('assert') const sigUtil = require('eth-sig-util') const log = require('loglevel') +const jsonschema = require('jsonschema') /** * Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a @@ -17,7 +18,7 @@ const log = require('loglevel') * @property {Object} msgParams.from The address that is making the signature request. * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request * @property {number} time The epoch time at which the this message was created - * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected' + * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed', 'rejected', or 'errored' * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will * always have a 'eth_signTypedData' type. * @@ -26,17 +27,10 @@ const log = require('loglevel') module.exports = class TypedMessageManager extends EventEmitter { /** * Controller in charge of managing - storing, adding, removing, updating - TypedMessage. - * - * @typedef {Object} TypedMessage - * @param {Object} opts @deprecated - * @property {Object} memStore The observable store where TypedMessage are saved. - * @property {Object} memStore.unapprovedTypedMessages A collection of all TypedMessages in the 'unapproved' state - * @property {number} memStore.unapprovedTypedMessagesCount The count of all TypedMessages in this.memStore.unapprobedMsgs - * @property {array} messages Holds all messages that have been created by this TypedMessage - * */ - constructor (opts) { + constructor ({ networkController }) { super() + this.networkController = networkController this.memStore = new ObservableStore({ unapprovedTypedMessages: {}, unapprovedTypedMessagesCount: 0, @@ -76,15 +70,17 @@ module.exports = class TypedMessageManager extends EventEmitter { * @returns {promise} When the message has been signed or rejected * */ - addUnapprovedMessageAsync (msgParams, req) { + addUnapprovedMessageAsync (msgParams, req, version) { return new Promise((resolve, reject) => { - const msgId = this.addUnapprovedMessage(msgParams, req) + const msgId = this.addUnapprovedMessage(msgParams, req, version) this.once(`${msgId}:finished`, (data) => { switch (data.status) { case 'signed': return resolve(data.rawSig) case 'rejected': return reject(new Error('MetaMask Message Signature: User denied message signature.')) + case 'errored': + return reject(new Error(`MetaMask Message Signature: ${data.error}`)) default: return reject(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) } @@ -102,7 +98,8 @@ module.exports = class TypedMessageManager extends EventEmitter { * @returns {number} The id of the newly created TypedMessage. * */ - addUnapprovedMessage (msgParams, req) { + addUnapprovedMessage (msgParams, req, version) { + msgParams.version = version this.validateParams(msgParams) // add origin from request if (req) msgParams.origin = req.origin @@ -132,14 +129,33 @@ module.exports = class TypedMessageManager extends EventEmitter { * */ validateParams (params) { - assert.equal(typeof params, 'object', 'Params should ben an object.') - assert.ok('data' in params, 'Params must include a data field.') - assert.ok('from' in params, 'Params must include a from field.') - assert.ok(Array.isArray(params.data), 'Data should be an array.') - assert.equal(typeof params.from, 'string', 'From field must be a string.') - assert.doesNotThrow(() => { - sigUtil.typedSignatureHash(params.data) - }, 'Expected EIP712 typed data') + switch (params.version) { + case 'V1': + assert.equal(typeof params, 'object', 'Params should ben an object.') + assert.ok('data' in params, 'Params must include a data field.') + assert.ok('from' in params, 'Params must include a from field.') + assert.ok(Array.isArray(params.data), 'Data should be an array.') + assert.equal(typeof params.from, 'string', 'From field must be a string.') + assert.doesNotThrow(() => { + sigUtil.typedSignatureHash(params.data) + }, 'Expected EIP712 typed data') + break + case 'V2': + let data + assert.equal(typeof params, 'object', 'Params should be an object.') + assert.ok('data' in params, 'Params must include a data field.') + assert.ok('from' in params, 'Params must include a from field.') + assert.equal(typeof params.from, 'string', 'From field must be a string.') + assert.equal(typeof params.data, 'string', 'Data must be passed as a valid JSON string.') + assert.doesNotThrow(() => { data = JSON.parse(params.data) }, 'Data must be passed as a valid JSON string.') + const validation = jsonschema.validate(data, sigUtil.TYPED_MESSAGE_SCHEMA) + assert.ok(data.primaryType in data.types, `Primary type of "${data.primaryType}" has no type definition.`) + assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.') + const chainId = data.domain.chainId + const activeChainId = parseInt(this.networkController.getNetworkState()) + chainId && assert.equal(chainId, activeChainId, `Provided chainId (${activeChainId}) must match the active chainId (${activeChainId})`) + break + } } /** @@ -214,6 +230,7 @@ module.exports = class TypedMessageManager extends EventEmitter { */ prepMsgForSigning (msgParams) { delete msgParams.metamaskId + delete msgParams.version return Promise.resolve(msgParams) } @@ -227,6 +244,19 @@ module.exports = class TypedMessageManager extends EventEmitter { this._setMsgStatus(msgId, 'rejected') } + /** + * Sets a TypedMessage status to 'errored' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the TypedMessage to error + * + */ + errorMessage (msgId, error) { + const msg = this.getMsg(msgId) + msg.error = error + this._updateMsg(msg) + this._setMsgStatus(msgId, 'errored') + } + // // PRIVATE METHODS // @@ -250,7 +280,7 @@ module.exports = class TypedMessageManager extends EventEmitter { msg.status = status this._updateMsg(msg) this.emit(`${msgId}:${status}`, msg) - if (status === 'rejected' || status === 'signed') { + if (status === 'rejected' || status === 'signed' || status === 'errored') { this.emit(`${msgId}:finished`, msg) } } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f9a12628b..d5005d977 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -49,6 +49,8 @@ const log = require('loglevel') const TrezorKeyring = require('eth-trezor-keyring') const LedgerBridgeKeyring = require('eth-ledger-bridge-keyring') const EthQuery = require('eth-query') +const ethUtil = require('ethereumjs-util') +const sigUtil = require('eth-sig-util') module.exports = class MetamaskController extends EventEmitter { @@ -205,7 +207,7 @@ module.exports = class MetamaskController extends EventEmitter { this.networkController.lookupNetwork() this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() - this.typedMessageManager = new TypedMessageManager() + this.typedMessageManager = new TypedMessageManager({ networkController: this.networkController }) this.publicConfigStore = this.initPublicConfigStore() this.store.updateStructure({ @@ -266,7 +268,6 @@ module.exports = class MetamaskController extends EventEmitter { // msg signing processEthSignMessage: this.newUnsignedMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), - processTypedMessage: this.newUnsignedTypedMessage.bind(this), } const providerProxy = this.networkController.initializeProvider(providerOpts) return providerProxy @@ -975,22 +976,31 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Object} msgParams - The params passed to eth_signTypedData. * @returns {Object} Full state update. */ - signTypedMessage (msgParams) { - log.info('MetaMaskController - signTypedMessage') + async signTypedMessage (msgParams) { + log.info('MetaMaskController - eth_signTypedData') const msgId = msgParams.metamaskId - // sets the status op the message to 'approved' - // and removes the metamaskId for signing - return this.typedMessageManager.approveMessage(msgParams) - .then((cleanMsgParams) => { - // signs the message - return this.keyringController.signTypedMessage(cleanMsgParams) - }) - .then((rawSig) => { - // tells the listener that the message has been signed - // and can be returned to the dapp - this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) - return this.getState() - }) + const version = msgParams.version + try { + const cleanMsgParams = await this.typedMessageManager.approveMessage(msgParams) + const address = sigUtil.normalize(cleanMsgParams.from) + const keyring = await this.keyringController.getKeyringForAccount(address) + const wallet = keyring._getWalletForAccount(address) + const privKey = ethUtil.toBuffer(wallet.getPrivateKey()) + let signature + switch (version) { + case 'V1': + signature = sigUtil.signTypedDataLegacy(privKey, { data: cleanMsgParams.data }) + break + case 'V2': + signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) }) + break + } + this.typedMessageManager.setMsgStatusSigned(msgId, signature) + return this.getState() + } catch (error) { + log.info('MetaMaskController - eth_signTypedData failed.', error) + this.typedMessageManager.errorMessage(msgId, error) + } } /** @@ -1241,6 +1251,9 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(createLoggerMiddleware({ origin })) engine.push(filterMiddleware) engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) + engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this)) + engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this)) + engine.push(this.createTypedDataMiddleware('eth_signTypedData_v2', 'V2').bind(this)) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection @@ -1474,4 +1487,34 @@ module.exports = class MetamaskController extends EventEmitter { set isClientOpenAndUnlocked (active) { this.tokenRatesController.isActive = active } + + /** + * Creates RPC engine middleware for processing eth_signTypedData requests + * + * @param {Object} req - request object + * @param {Object} res - response object + * @param {Function} - next + * @param {Function} - end + */ + createTypedDataMiddleware (methodName, version) { + return async (req, res, next, end) => { + const { method, params } = req + if (method === methodName) { + const promise = this.typedMessageManager.addUnapprovedMessageAsync({ + data: params.length >= 1 && params[0], + from: params.length >= 2 && params[1], + }, req, version) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + try { + res.result = await promise + end() + } catch (error) { + end(error) + } + } else { + next() + } + } + } } -- cgit From 68c25542965ae89badc284bf30e1f426f27aa5ae Mon Sep 17 00:00:00 2001 From: bitpshr Date: Tue, 11 Sep 2018 09:33:37 -0400 Subject: Update error message for chainId mis-match --- app/scripts/lib/typed-message-manager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 3e97023f5..01e1c9331 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -153,7 +153,7 @@ module.exports = class TypedMessageManager extends EventEmitter { assert.equal(validation.errors.length, 0, 'Data must conform to EIP-712 schema. See https://git.io/fNtcx.') const chainId = data.domain.chainId const activeChainId = parseInt(this.networkController.getNetworkState()) - chainId && assert.equal(chainId, activeChainId, `Provided chainId (${activeChainId}) must match the active chainId (${activeChainId})`) + chainId && assert.equal(chainId, activeChainId, `Provided chainId (${chainId}) must match the active chainId (${activeChainId})`) break } } -- cgit From 42fdcf6239fc9278cfa85b6ae6cc025cef0e35ae Mon Sep 17 00:00:00 2001 From: bitpshr Date: Thu, 13 Sep 2018 15:12:08 -0400 Subject: Update new method namespace from v2 to v3 --- app/scripts/lib/typed-message-manager.js | 2 +- app/scripts/metamask-controller.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 01e1c9331..b10145f3b 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -140,7 +140,7 @@ module.exports = class TypedMessageManager extends EventEmitter { sigUtil.typedSignatureHash(params.data) }, 'Expected EIP712 typed data') break - case 'V2': + case 'V3': let data assert.equal(typeof params, 'object', 'Params should be an object.') assert.ok('data' in params, 'Params must include a data field.') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index d5005d977..431a49dde 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -991,7 +991,7 @@ module.exports = class MetamaskController extends EventEmitter { case 'V1': signature = sigUtil.signTypedDataLegacy(privKey, { data: cleanMsgParams.data }) break - case 'V2': + case 'V3': signature = sigUtil.signTypedData(privKey, { data: JSON.parse(cleanMsgParams.data) }) break } @@ -1253,7 +1253,7 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this)) engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this)) - engine.push(this.createTypedDataMiddleware('eth_signTypedData_v2', 'V2').bind(this)) + engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3').bind(this)) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection -- cgit From 2ec09362c59958a457939724003312024f97393c Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Fri, 14 Sep 2018 19:26:03 -0400 Subject: EIP-1102: Transitionary API (#5256) --- app/scripts/inpage.js | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 1a170c617..d9fda1feb 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -22,6 +22,25 @@ var metamaskStream = new LocalMessageDuplexStream({ // compose the inpage provider var inpageProvider = new MetamaskInpageProvider(metamaskStream) +// Augment the provider with its enable method +inpageProvider.enable = function (options = {}) { + return new Promise((resolve, reject) => { + if (options.mockRejection) { + reject('User rejected account access') + } else { + inpageProvider.sendAsync({ method: 'eth_accounts', params: [] }, (error, response) => { + if (error) { + reject(error) + } else { + resolve(response.result) + } + }) + } + }) +} + +window.ethereum = inpageProvider + // // setup web3 // -- cgit From 5a6c333506e4000602c1a1106cee6d06fe83afa8 Mon Sep 17 00:00:00 2001 From: Alexander Tseung Date: Mon, 17 Sep 2018 10:34:29 -0700 Subject: Switch existing modals from using Notification to Modal. Remove Notification component. Add CancelTransaction modal --- app/scripts/controllers/transactions/index.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 59e30cdde..e2965ceb6 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -18,7 +18,7 @@ const { TRANSACTION_STATUS_APPROVED, } = require('./enums') -const { hexToBn, bnToHex } = require('../../lib/util') +const { hexToBn, bnToHex, BnMultiplyByFraction } = require('../../lib/util') /** Transaction Controller is an aggregate of sub-controllers and trackers @@ -244,7 +244,8 @@ class TransactionController extends EventEmitter { const originalTxMeta = this.txStateManager.getTx(originalTxId) const { txParams } = originalTxMeta const { gasPrice: lastGasPrice, from, nonce } = txParams - const newGasPrice = customGasPrice || bnToHex(hexToBn(lastGasPrice).mul(1.1)) + + const newGasPrice = customGasPrice || bnToHex(BnMultiplyByFraction(hexToBn(lastGasPrice), 11, 10)) const newTxMeta = this.txStateManager.generateTxMeta({ txParams: { from, -- cgit From 19d72c9b0b4539f55624f6e9d41ded46c31d38d5 Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 21 Sep 2018 15:04:21 -0230 Subject: Adds getPendingNonce method to provider initialization options in metamask-controller. --- .../controllers/network/createMetamaskMiddleware.js | 2 +- app/scripts/metamask-controller.js | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createMetamaskMiddleware.js b/app/scripts/controllers/network/createMetamaskMiddleware.js index 8b17829b7..9e6a45888 100644 --- a/app/scripts/controllers/network/createMetamaskMiddleware.js +++ b/app/scripts/controllers/network/createMetamaskMiddleware.js @@ -38,6 +38,6 @@ function createPendingNonceMiddleware ({ getPendingNonce }) { const address = req.params[0] const blockRef = req.params[1] if (blockRef !== 'pending') return next() - req.result = await getPendingNonce(address) + res.result = await getPendingNonce(address) }) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 431a49dde..f11626c78 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -268,6 +268,7 @@ module.exports = class MetamaskController extends EventEmitter { // msg signing processEthSignMessage: this.newUnsignedMessage.bind(this), processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), + getPendingNonce: this.getPendingNonce.bind(this), } const providerProxy = this.networkController.initializeProvider(providerOpts) return providerProxy @@ -1362,6 +1363,19 @@ module.exports = class MetamaskController extends EventEmitter { return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) } + /** + * Returns the nonce that will be associated with a transaction once approved + * @param address {string} - The hex string address for the transaction + * @returns Promise + */ + async getPendingNonce (address) { + const { nonceDetails, releaseLock} = await this.txController.nonceTracker.getNonceLock(address) + const pendingNonce = nonceDetails.params.highestSuggested + + releaseLock() + return pendingNonce + } + //============================================================================= // CONFIG //============================================================================= -- cgit From 4d82df69959ab370ba4bf6acc324ed756322ef4f Mon Sep 17 00:00:00 2001 From: Esteban Miño Date: Tue, 25 Sep 2018 16:14:37 -0300 Subject: Fix MetaMask web3 version (#5352) --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f11626c78..ddc13c918 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -252,6 +252,7 @@ module.exports = class MetamaskController extends EventEmitter { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, + version, // account mgmt getAccounts: async () => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked -- cgit From c4caba131776ff7397d3a4071d7cc84907ac9a43 Mon Sep 17 00:00:00 2001 From: bitpshr Date: Wed, 26 Sep 2018 10:00:12 -0400 Subject: bugfix: update eth_signTypedData_v3 parameter order --- app/scripts/metamask-controller.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ddc13c918..123e17569 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1255,7 +1255,7 @@ module.exports = class MetamaskController extends EventEmitter { engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this)) engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this)) - engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3').bind(this)) + engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this)) engine.push(createProviderMiddleware({ provider: this.provider })) // setup connection @@ -1511,13 +1511,13 @@ module.exports = class MetamaskController extends EventEmitter { * @param {Function} - next * @param {Function} - end */ - createTypedDataMiddleware (methodName, version) { + createTypedDataMiddleware (methodName, version, reverse) { return async (req, res, next, end) => { const { method, params } = req if (method === methodName) { const promise = this.typedMessageManager.addUnapprovedMessageAsync({ - data: params.length >= 1 && params[0], - from: params.length >= 2 && params[1], + data: reverse ? params[1] : params[0], + from: reverse ? params[0] : params[1], }, req, version) this.sendUpdate() this.opts.showUnconfirmedMessage() -- cgit From 9359fc875df061c39807d90c3ca92356960ec4c3 Mon Sep 17 00:00:00 2001 From: Paul Bouchon Date: Wed, 26 Sep 2018 10:48:17 -0400 Subject: EIP-1102: Add deprecation message (#5353) --- app/scripts/inpage.js | 6 ++++++ app/scripts/lib/auto-reload.js | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index d9fda1feb..d924be516 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -9,6 +9,11 @@ restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') +console.warn('ATTENTION: In an effort to improve user privacy, MetaMask will ' + +'stop exposing user accounts to dapps by default beginning November 2nd, 2018. ' + +'Dapps should call provider.enable() in order to view and use accounts. Please see ' + +'https://bit.ly/2QQHXvF for complete information and up-to-date example code.') + // // setup plugin communication // @@ -52,6 +57,7 @@ if (typeof window.web3 !== 'undefined') { or MetaMask and another web3 extension. Please remove one and try again.`) } + var web3 = new Web3(inpageProvider) web3.setProvider = function () { log.debug('MetaMask - overrode web3.setProvider') diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index cce31c3d2..558391a06 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -2,18 +2,12 @@ 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, 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 - } // get the time of use lastTimeUsed = Date.now() // return value normally -- cgit From 386110ef0fda622e87fef0f82bb6282f35c420e9 Mon Sep 17 00:00:00 2001 From: Phyrex Tsai Date: Thu, 27 Sep 2018 23:21:50 +0800 Subject: fix tld bug (#5250) --- app/scripts/lib/ipfsContent.js | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 38682b916..62a808b90 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -5,6 +5,8 @@ module.exports = function (provider) { function ipfsContent (details) { const name = details.url.substring(7, details.url.length - 1) let clearTime = null + if (/^.+\.eth$/.test(name) === false) return + extension.tabs.query({active: true}, tab => { extension.tabs.update(tab.id, { url: 'loading.html' }) -- cgit From 83666e8d28d7bf7aa6cea140be359622ec781bd8 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 25 Sep 2018 11:05:17 -0230 Subject: Update extension badge with correct signTypedData count --- app/scripts/background.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 546fef569..ae450352e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -406,6 +406,7 @@ function setupController (initState, initLangCode) { controller.txController.on('update:badge', updateBadge) controller.messageManager.on('updateBadge', updateBadge) controller.personalMessageManager.on('updateBadge', updateBadge) + controller.typedMessageManager.on('updateBadge', updateBadge) /** * Updates the Web Extension's "badge" number, on the little fox in the toolbar. -- cgit From 13a1d4672045371f6366bf1fc48b77cb880eb4f8 Mon Sep 17 00:00:00 2001 From: HackyMiner Date: Sat, 29 Sep 2018 04:53:58 +0900 Subject: support editable customRPC (#5267) * support editable customRPC #5246 * remove rpcList size restriction --- app/scripts/controllers/preferences.js | 13 ++++++------- app/scripts/metamask-controller.js | 9 +++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/preferences.js b/app/scripts/controllers/preferences.js index 928ebdf1f..fd6a4866d 100644 --- a/app/scripts/controllers/preferences.js +++ b/app/scripts/controllers/preferences.js @@ -375,11 +375,12 @@ class PreferencesController { * Gets an updated rpc list from this.addToFrequentRpcList() and sets the `frequentRpcList` to this update list. * * @param {string} _url The the new rpc url to add to the updated list + * @param {bool} remove Remove selected url * @returns {Promise} Promise resolves with undefined * */ - updateFrequentRpcList (_url) { - return this.addToFrequentRpcList(_url) + updateFrequentRpcList (_url, remove = false) { + return this.addToFrequentRpcList(_url, remove) .then((rpcList) => { this.store.updateState({ frequentRpcList: rpcList }) return Promise.resolve() @@ -406,21 +407,19 @@ class PreferencesController { * end of the list. The current list is modified and returned as a promise. * * @param {string} _url The rpc url to add to the frequentRpcList. + * @param {bool} remove Remove selected url * @returns {Promise} The updated frequentRpcList. * */ - addToFrequentRpcList (_url) { + addToFrequentRpcList (_url, remove = false) { const rpcList = this.getFrequentRpcList() const index = rpcList.findIndex((element) => { return element === _url }) if (index !== -1) { rpcList.splice(index, 1) } - if (_url !== 'http://localhost:8545') { + if (!remove && _url !== 'http://localhost:8545') { rpcList.push(_url) } - if (rpcList.length > 3) { - rpcList.shift() - } return Promise.resolve(rpcList) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 123e17569..1f0527c7e 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -376,6 +376,7 @@ module.exports = class MetamaskController extends EventEmitter { // network management setProviderType: nodeify(networkController.setProviderType, networkController), setCustomRpc: nodeify(this.setCustomRpc, this), + delCustomRpc: nodeify(this.delCustomRpc, this), // PreferencesController setSelectedAddress: nodeify(preferencesController.setSelectedAddress, preferencesController), @@ -1439,6 +1440,14 @@ module.exports = class MetamaskController extends EventEmitter { return rpcTarget } + /** + * A method for deleting a selected custom URL. + * @param {string} rpcTarget - A RPC URL to delete. + */ + async delCustomRpc (rpcTarget) { + await this.preferencesController.updateFrequentRpcList(rpcTarget, true) + } + /** * Sets whether or not to use the blockie identicon format. * @param {boolean} val - True for bockie, false for jazzicon. -- cgit From 4dd6c8168f3dbec5a0e910069a4a29a640e39942 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 1 Oct 2018 20:28:46 -0230 Subject: Add ability to whitelist a blacklisted domain at runtime --- app/scripts/controllers/blacklist.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/blacklist.js b/app/scripts/controllers/blacklist.js index 1d2191433..89c7cc888 100644 --- a/app/scripts/controllers/blacklist.js +++ b/app/scripts/controllers/blacklist.js @@ -29,6 +29,7 @@ class BlacklistController { constructor (opts = {}) { const initState = extend({ phishing: PHISHING_DETECTION_CONFIG, + whitelist: [], }, opts.initState) this.store = new ObservableStore(initState) // phishing detector @@ -38,6 +39,21 @@ class BlacklistController { this._phishingUpdateIntervalRef = null } + /** + * Adds the given hostname to the runtime whitelist + * @param {string} hostname the hostname to whitelist + */ + whitelistDomain (hostname) { + if (!hostname) { + return + } + + const { whitelist } = this.store.getState() + this.store.updateState({ + whitelist: [...new Set([hostname, ...whitelist])], + }) + } + /** * Given a url, returns the result of checking if that url is in the store.phishing blacklist * @@ -48,6 +64,12 @@ class BlacklistController { */ checkForPhishing (hostname) { if (!hostname) return false + + const { whitelist } = this.store.getState() + if (whitelist.some((e) => e === hostname)) { + return false + } + const { result } = this._phishingDetector.check(hostname) return result } -- cgit From 58d856bd84a06228706fce3d2a13348e25f1a4ae Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 1 Oct 2018 21:05:57 -0230 Subject: Add domain whitelist method to MetaMaskController --- app/scripts/metamask-controller.js | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 1f0527c7e..34ca80dd7 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -387,6 +387,9 @@ module.exports = class MetamaskController extends EventEmitter { setAccountLabel: nodeify(preferencesController.setAccountLabel, preferencesController), setFeatureFlag: nodeify(preferencesController.setFeatureFlag, preferencesController), + // BlacklistController + whitelistPhishingDomain: this.whitelistPhishingDomain.bind(this), + // AddressController setAddressBook: nodeify(addressBookController.setAddressBook, addressBookController), @@ -1541,4 +1544,12 @@ module.exports = class MetamaskController extends EventEmitter { } } } + + /** + * Adds a domain to the {@link BlacklistController} whitelist + * @param {string} hostname the domain to whitelist + */ + whitelistPhishingDomain (hostname) { + return this.blacklistController.whitelistDomain(hostname) + } } -- cgit From c92fee7771ee1c12729a5a480d63134722332789 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Mon, 1 Oct 2018 21:30:40 -0230 Subject: Hook MetaMaskController up with phishing detection page --- app/scripts/contentscript.js | 6 ++++- app/scripts/phishing-detect.js | 56 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 2cbfb811e..d870741d6 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,6 +1,7 @@ const fs = require('fs') const path = require('path') const pump = require('pump') +const querystring = require('querystring') const LocalMessageDuplexStream = require('post-message-stream') const PongStream = require('ping-pong-stream/pong') const ObjectMultiplex = require('obj-multiplex') @@ -199,5 +200,8 @@ function blacklistedDomainCheck () { function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') const extensionURL = extension.runtime.getURL('phishing.html') - window.location.href = extensionURL + '#' + window.location.hostname + window.location.href = `${extensionURL}#${querystring.stringify({ + hostname: window.location.hostname, + href: window.location.href, + })}` } diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index 4168b6618..6baf868c0 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -1,5 +1,59 @@ window.onload = function() { if (window.location.pathname === '/phishing.html') { - document.getElementById('esdbLink').innerHTML = 'To read more about this scam, navigate to: https://etherscamdb.info/domain/' + window.location.hash.substring(1) + '' + const {hostname} = parseHash() + document.getElementById('esdbLink').innerHTML = 'To read more about this scam, navigate to: https://etherscamdb.info/domain/' + hostname + '' } } + +const querystring = require('querystring') +const dnode = require('dnode') +const { EventEmitter } = require('events') +const PortStream = require('extension-port-stream') +const extension = require('extensionizer') +const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex +const { getEnvironmentType } = require('./lib/util') +const ExtensionPlatform = require('./platforms/extension') + +document.addEventListener('DOMContentLoaded', start) + +function start () { + const windowType = getEnvironmentType(window.location.href) + + global.platform = new ExtensionPlatform() + global.METAMASK_UI_TYPE = windowType + + const extensionPort = extension.runtime.connect({ name: windowType }) + const connectionStream = new PortStream(extensionPort) + const mx = setupMultiplex(connectionStream) + setupControllerConnection(mx.createStream('controller'), (err, metaMaskController) => { + if (err) { + return + } + + const suspect = parseHash() + const unsafeContinue = () => { + window.location.href = suspect.href + } + const continueLink = document.getElementById('unsafe-continue') + continueLink.addEventListener('click', () => { + metaMaskController.whitelistPhishingDomain(suspect.hostname) + unsafeContinue() + }) + }) +} + +function setupControllerConnection (connectionStream, cb) { + const eventEmitter = new EventEmitter() + const accountManagerDnode = dnode({ + sendUpdate (state) { + eventEmitter.emit('update', state) + }, + }) + connectionStream.pipe(accountManagerDnode).pipe(connectionStream) + accountManagerDnode.once('remote', (accountManager) => cb(null, accountManager)) +} + +function parseHash () { + const hash = window.location.hash.substring(1) + return querystring.parse(hash) +} -- cgit From ba39fbeb49c0691885a92e3df70b3df912144531 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Tue, 2 Oct 2018 11:27:47 -0230 Subject: Don't open metamask.io after install anymore --- app/scripts/background.js | 9 --------- 1 file changed, 9 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index ae450352e..0343e134c 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -38,7 +38,6 @@ const { const firstTimeState = Object.assign({}, rawFirstTimeState, global.METAMASK_TEST_CONFIG) const STORAGE_KEY = 'metamask-config' -const METAMASK_DEBUG = process.env.METAMASK_DEBUG log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') @@ -462,11 +461,3 @@ function showWatchAssetUi () { } ) } - -// On first install, open a window to MetaMask website to how-it-works. -extension.runtime.onInstalled.addListener(function (details) { - if ((details.reason === 'install') && (!METAMASK_DEBUG)) { - extension.tabs.create({url: 'https://metamask.io/#how-it-works'}) - } -}) - -- cgit From c49d854b55b3efd34c7fd0414b76f7feaa2eec7c Mon Sep 17 00:00:00 2001 From: Dan Finlay <542863+danfinlay@users.noreply.github.com> Date: Thu, 4 Oct 2018 14:08:05 -0700 Subject: Increase suggested gas percentile to 65 (#5359) * Increase suggested gas percentile to 65 I keep submitting transactions then waiting a long time. Apparently 50th percentile isn't enough. * Update test for getGasPrice --- app/scripts/metamask-controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 34ca80dd7..78dc57972 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -1363,7 +1363,7 @@ module.exports = class MetamaskController extends EventEmitter { }) .map(number => number.div(GWEI_BN).toNumber()) - const percentileNum = percentile(50, lowestPrices) + const percentileNum = percentile(65, lowestPrices) const percentileNumBn = new BN(percentileNum) return '0x' + percentileNumBn.mul(GWEI_BN).toString(16) } -- cgit From bc6d32e51eef892f30d9913f3f62472df490f7d9 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Fri, 5 Oct 2018 10:32:28 -0700 Subject: Update all balances on password unlock --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) (limited to 'app/scripts') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 78dc57972..a7754c388 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -555,6 +555,7 @@ module.exports = class MetamaskController extends EventEmitter { } await this.preferencesController.syncAddresses(accounts) + await this.balancesController.updateAllBalances() return this.keyringController.fullUpdate() } -- cgit From 507397f6c366d570af5c6846d7e6a6f6ad3117ed Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Thu, 4 Oct 2018 15:57:38 -0700 Subject: Fix updating of pending transactions Transaction statuses were not being properly updated when: - MetaMask was unlocked - The network was changed This PR fixes both of those. Fixes #5174 --- app/scripts/controllers/transactions/index.js | 1 + app/scripts/metamask-controller.js | 1 + 2 files changed, 2 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index e2965ceb6..ebd49f882 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -530,6 +530,7 @@ class TransactionController extends EventEmitter { Updates the memStore in transaction controller */ _updateMemstore () { + this.pendingTxTracker.updatePendingTxs() const unapprovedTxs = this.txStateManager.getUnapprovedTxList() const selectedAddressTxList = this.txStateManager.getFilteredTxList({ from: this.getSelectedAddress(), diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 78dc57972..921a58e91 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -555,6 +555,7 @@ module.exports = class MetamaskController extends EventEmitter { } await this.preferencesController.syncAddresses(accounts) + await this.txController.pendingTxTracker.updatePendingTxs() return this.keyringController.fullUpdate() } -- cgit From 354f8c0d7de8ff2611988f9839c2072295c955ae Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 8 Oct 2018 11:55:07 -0400 Subject: provider - enable subscription support (newHeads, logs) --- .../controllers/network/createInfuraClient.js | 10 ++++++---- app/scripts/metamask-controller.js | 20 +++++++++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js index 41af4d9f9..326bcb355 100644 --- a/app/scripts/controllers/network/createInfuraClient.js +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -1,5 +1,6 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') -const createBlockReEmitMiddleware = require('eth-json-rpc-middleware/block-reemit') +const createBlockReRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createRetryOnEmptyMiddleware = require('eth-json-rpc-middleware/retryOnEmpty') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') @@ -11,13 +12,14 @@ module.exports = createInfuraClient function createInfuraClient ({ network }) { const infuraMiddleware = createInfuraMiddleware({ network }) - const blockProvider = providerFromMiddleware(infuraMiddleware) - const blockTracker = new BlockTracker({ provider: blockProvider }) + const infuraProvider = providerFromMiddleware(infuraMiddleware) + const blockTracker = new BlockTracker({ provider: infuraProvider }) const networkMiddleware = mergeMiddleware([ createBlockCacheMiddleware({ blockTracker }), createInflightMiddleware(), - createBlockReEmitMiddleware({ blockTracker, provider: blockProvider }), + createBlockReRefMiddleware({ blockTracker, provider: infuraProvider }), + createRetryOnEmptyMiddleware({ blockTracker, provider: infuraProvider }), createBlockTrackerInspectorMiddleware({ blockTracker }), infuraMiddleware, ]) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 34ca80dd7..a59be7744 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -15,6 +15,7 @@ const RpcEngine = require('json-rpc-engine') const debounce = require('debounce') const createEngineStream = require('json-rpc-middleware-stream/engineStream') const createFilterMiddleware = require('eth-json-rpc-filters') +const createSubscriptionManager = require('eth-json-rpc-filters/subscriptionManager') const createOriginMiddleware = require('./lib/createOriginMiddleware') const createLoggerMiddleware = require('./lib/createLoggerMiddleware') const createProviderMiddleware = require('./lib/createProviderMiddleware') @@ -1246,24 +1247,33 @@ module.exports = class MetamaskController extends EventEmitter { setupProviderConnection (outStream, origin) { // setup json rpc engine stack const engine = new RpcEngine() + const provider = this.provider + const blockTracker = this.blockTracker // create filter polyfill middleware - const filterMiddleware = createFilterMiddleware({ - provider: this.provider, - blockTracker: this.blockTracker, - }) + const filterMiddleware = createFilterMiddleware({ provider, blockTracker }) + // create subscription polyfill middleware + const subscriptionManager = createSubscriptionManager({ provider, blockTracker }) + subscriptionManager.events.on('notification', (message) => engine.emit('notification', message)) + // metadata engine.push(createOriginMiddleware({ origin })) engine.push(createLoggerMiddleware({ origin })) + // filter and subscription polyfills engine.push(filterMiddleware) + engine.push(subscriptionManager.middleware) + // watch asset engine.push(this.preferencesController.requestWatchAsset.bind(this.preferencesController)) + // sign typed data middleware engine.push(this.createTypedDataMiddleware('eth_signTypedData', 'V1').bind(this)) engine.push(this.createTypedDataMiddleware('eth_signTypedData_v1', 'V1').bind(this)) engine.push(this.createTypedDataMiddleware('eth_signTypedData_v3', 'V3', true).bind(this)) - engine.push(createProviderMiddleware({ provider: this.provider })) + // forward to metamask primary provider + engine.push(createProviderMiddleware({ provider })) // setup connection const providerStream = createEngineStream({ engine }) + pump( outStream, providerStream, -- cgit From fe82c4a9fbf3b543779ebc980fedc178777fed12 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 8 Oct 2018 12:39:18 -0400 Subject: provider - network - restore block-ref-rewrite middleware references --- app/scripts/controllers/network/createJsonRpcClient.js | 4 ++-- app/scripts/controllers/network/createLocalhostClient.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/network/createJsonRpcClient.js b/app/scripts/controllers/network/createJsonRpcClient.js index 40c353f7f..a8cbf2aaf 100644 --- a/app/scripts/controllers/network/createJsonRpcClient.js +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -1,6 +1,6 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') -const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite') const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') @@ -15,7 +15,7 @@ function createJsonRpcClient ({ rpcUrl }) { const blockTracker = new BlockTracker({ provider: blockProvider }) const networkMiddleware = mergeMiddleware([ - createBlockRefMiddleware({ blockTracker }), + createBlockRefRewriteMiddleware({ blockTracker }), createBlockCacheMiddleware({ blockTracker }), createInflightMiddleware(), createBlockTrackerInspectorMiddleware({ blockTracker }), diff --git a/app/scripts/controllers/network/createLocalhostClient.js b/app/scripts/controllers/network/createLocalhostClient.js index fecc512e8..09b1d3c1c 100644 --- a/app/scripts/controllers/network/createLocalhostClient.js +++ b/app/scripts/controllers/network/createLocalhostClient.js @@ -1,6 +1,6 @@ const mergeMiddleware = require('json-rpc-engine/src/mergeMiddleware') const createFetchMiddleware = require('eth-json-rpc-middleware/fetch') -const createBlockRefMiddleware = require('eth-json-rpc-middleware/block-ref') +const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const BlockTracker = require('eth-block-tracker') @@ -13,7 +13,7 @@ function createLocalhostClient () { const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) const networkMiddleware = mergeMiddleware([ - createBlockRefMiddleware({ blockTracker }), + createBlockRefRewriteMiddleware({ blockTracker }), createBlockTrackerInspectorMiddleware({ blockTracker }), fetchMiddleware, ]) -- cgit From 8279916ee415157dbda89578e6fd1b934e9d344e Mon Sep 17 00:00:00 2001 From: Eduardo Antuña Díez Date: Mon, 8 Oct 2018 21:50:15 +0200 Subject: Added webRequest.RequestFilter to filter main_frame .eth requests --- app/scripts/lib/ipfsContent.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/ipfsContent.js b/app/scripts/lib/ipfsContent.js index 62a808b90..8b08453c4 100644 --- a/app/scripts/lib/ipfsContent.js +++ b/app/scripts/lib/ipfsContent.js @@ -36,7 +36,7 @@ module.exports = function (provider) { return { cancel: true } } - extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/']}) + extension.webRequest.onErrorOccurred.addListener(ipfsContent, {urls: ['*://*.eth/'], types: ['main_frame']}) return { remove () { -- cgit From 45feb43f306f4b21ef007e0c8897cda384c28fc5 Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 8 Oct 2018 16:54:13 -0400 Subject: workaround - fix for drizzle --- app/scripts/inpage.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index d924be516..431702d63 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -5,6 +5,7 @@ const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') + restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') @@ -44,7 +45,15 @@ inpageProvider.enable = function (options = {}) { }) } -window.ethereum = inpageProvider +// Work around for web3@1.0 deleting the bound `sendAsync` but not the unbound +// `sendAsync` method on the prototype, causing `this` reference issues with drizzle +const proxiedInpageProvider = new Proxy(inpageProvider, { + // straight up lie that we deleted the property so that it doesnt + // throw an error in strict mode + deleteProperty: () => true, +}) + +window.ethereum = proxiedInpageProvider // // setup web3 @@ -58,7 +67,7 @@ if (typeof window.web3 !== 'undefined') { and try again.`) } -var web3 = new Web3(inpageProvider) +var web3 = new Web3(proxiedInpageProvider) web3.setProvider = function () { log.debug('MetaMask - overrode web3.setProvider') } -- cgit From ebdefe81a10595bb3341afa2ab0851c8269c19d8 Mon Sep 17 00:00:00 2001 From: Noel Yoo Date: Tue, 9 Oct 2018 21:02:48 +0900 Subject: Refactor buffer constructor (#5468) --- app/scripts/lib/message-manager.js | 2 +- app/scripts/lib/personal-message-manager.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index 47925b94b..e86629590 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -272,6 +272,6 @@ function normalizeMsgData (data) { return data } else { // data is unicode, convert to hex - return ethUtil.bufferToHex(new Buffer(data, 'utf8')) + return ethUtil.bufferToHex(Buffer.from(data, 'utf8')) } } diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index fc2cccdf1..fdb94f5ec 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -285,7 +285,7 @@ module.exports = class PersonalMessageManager extends EventEmitter { log.debug(`Message was not hex encoded, interpreting as utf8.`) } - return ethUtil.bufferToHex(new Buffer(data, 'utf8')) + return ethUtil.bufferToHex(Buffer.from(data, 'utf8')) } } -- cgit From ccab4ee1a408d93f38765e9b6ef3dc33a18befa9 Mon Sep 17 00:00:00 2001 From: Bruno Barbieri Date: Wed, 10 Oct 2018 01:12:43 -0400 Subject: tests - integration - Add Drizzle tests (#5467) * added drizzle app for testing * working * clean up * clean up script * make build step required * add drizzle-tests to .eslintignore * clean up drizzle run script * lint * use truffle unbox * undo eslintignore changes * revert change * dont use global * dont need this steps * use the new account flow * restore package-lock.json --- app/scripts/phishing-detect.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/phishing-detect.js b/app/scripts/phishing-detect.js index 6baf868c0..0889c831e 100644 --- a/app/scripts/phishing-detect.js +++ b/app/scripts/phishing-detect.js @@ -1,4 +1,4 @@ -window.onload = function() { +window.onload = function () { if (window.location.pathname === '/phishing.html') { const {hostname} = parseHash() document.getElementById('esdbLink').innerHTML = 'To read more about this scam, navigate to: https://etherscamdb.info/domain/' + hostname + '' -- cgit From ff67293a8ef61308d602d09f26b163b9b9ec90d3 Mon Sep 17 00:00:00 2001 From: Frankie Date: Wed, 10 Oct 2018 04:26:38 -1000 Subject: transactions - add txReceipt to the txMeta body for confirmed txs (#5375) --- app/scripts/controllers/transactions/index.js | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index ebd49f882..1d566522c 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -362,7 +362,29 @@ class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId) } - confirmTransaction (txId) { + /** + Sets the status of the transaction to confirmed + and sets the status of nonce duplicates as dropped + if the txParams have data it will fetch the txReceipt + @param txId {number} - the tx's Id + @returns {Promise} + */ + + async confirmTransaction (txId) { + // get the txReceipt before marking the transaction confirmed + // to ensure the receipt is gotten before the ui revives the tx + const txMeta = this.txStateManager.getTx(txId) + if (txMeta.txParams.data) { + try { + const txReceipt = await this.query.getTransactionReceipt() + txMeta.txReceipt = txReceipt + this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt') + + } catch (err) { + log.error(err) + } + } + this.txStateManager.setTxStatusConfirmed(txId) this._markNonceDuplicatesDropped(txId) } -- cgit From 428a7cacdfbb3080a860bf1fdb774cc8ae218c04 Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 10 Oct 2018 15:28:56 -0230 Subject: Revert "transactions - add txReceipt to the txMeta body for confirmed txs (#5375)" This reverts commit ff67293a8ef61308d602d09f26b163b9b9ec90d3. --- app/scripts/controllers/transactions/index.js | 24 +----------------------- 1 file changed, 1 insertion(+), 23 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index 1d566522c..ebd49f882 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -362,29 +362,7 @@ class TransactionController extends EventEmitter { this.txStateManager.setTxStatusSubmitted(txId) } - /** - Sets the status of the transaction to confirmed - and sets the status of nonce duplicates as dropped - if the txParams have data it will fetch the txReceipt - @param txId {number} - the tx's Id - @returns {Promise} - */ - - async confirmTransaction (txId) { - // get the txReceipt before marking the transaction confirmed - // to ensure the receipt is gotten before the ui revives the tx - const txMeta = this.txStateManager.getTx(txId) - if (txMeta.txParams.data) { - try { - const txReceipt = await this.query.getTransactionReceipt() - txMeta.txReceipt = txReceipt - this.txStateManager.updateTx(txMeta, 'transactions#confirmTransaction - add txReceipt') - - } catch (err) { - log.error(err) - } - } - + confirmTransaction (txId) { this.txStateManager.setTxStatusConfirmed(txId) this._markNonceDuplicatesDropped(txId) } -- cgit From af43b7d6c97e10018e564ce8ee37bc9ad380234c Mon Sep 17 00:00:00 2001 From: Whymarrh Whitby Date: Wed, 10 Oct 2018 16:11:17 -0230 Subject: Ensure that new transactions added are using the selected address --- app/scripts/controllers/transactions/index.js | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions/index.js b/app/scripts/controllers/transactions/index.js index ebd49f882..a57c85f50 100644 --- a/app/scripts/controllers/transactions/index.js +++ b/app/scripts/controllers/transactions/index.js @@ -166,6 +166,10 @@ class TransactionController extends EventEmitter { async addUnapprovedTransaction (txParams) { // validate const normalizedTxParams = txUtils.normalizeTxParams(txParams) + // Assert the from address is the selected address + if (normalizedTxParams.from !== this.getSelectedAddress()) { + throw new Error(`Transaction from address isn't valid for this account`) + } txUtils.validateTxParams(normalizedTxParams) // construct txMeta let txMeta = this.txStateManager.generateTxMeta({ -- cgit