diff options
33 files changed, 942 insertions, 735 deletions
diff --git a/.gitignore b/.gitignore index 0e91a7d04..21a13c904 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ package # IDEs .idea .vscode +.sublime-project temp .tmp @@ -37,4 +38,4 @@ ui/app/css/output/ notes.txt .coveralls.yml -.nyc_output +.nyc_output
\ No newline at end of file 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<array>} Promises an array of transaction objects. diff --git a/app/scripts/controllers/network/createInfuraClient.js b/app/scripts/controllers/network/createInfuraClient.js new file mode 100644 index 000000000..adbf4c001 --- /dev/null +++ b/app/scripts/controllers/network/createInfuraClient.js @@ -0,0 +1,37 @@ +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-cache') +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([ + 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..d712ed135 --- /dev/null +++ b/app/scripts/controllers/network/createJsonRpcClient.js @@ -0,0 +1,37 @@ +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') +const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') +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 } +} + +// 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..990dc6a95 --- /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 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 } +} + +// 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..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 93fde7c57..9e622981b 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 JsonRpcEngine = require('json-rpc-engine') +const providerFromEngine = require('eth-json-rpc-middleware/providerFromEngine') const log = require('loglevel') +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, @@ -16,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 @@ -38,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._blockTracker + return { provider, blockTracker } } verifyNetwork () { @@ -74,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) @@ -123,9 +132,9 @@ 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'){ + } else if (type === 'rpc') { this._configureStandardProvider({ rpcUrl: rpcTarget }) } else { throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`) @@ -133,47 +142,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/recent-blocks.js b/app/scripts/controllers/recent-blocks.js index 033ef1d7e..4009c35ae 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<void>} 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) } @@ -138,18 +147,6 @@ class RecentBlocksController { } /** - * A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await - * - * @returns {Promise<void>} Promises undefined - * - */ - async wait () { - return new Promise((resolve) => { - setTimeout(resolve, 100) - }) - } - - /** * Uses EthQuery to get a block that has a given block number. * * @param {number} number The number of the block to get @@ -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 b53947e27..c270be294 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() @@ -306,6 +311,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 @@ -349,6 +359,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 @@ -381,14 +399,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 @@ -400,13 +418,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)) - } /** @@ -430,6 +441,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 f8cdc5523..fe2d25fca 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. */ /** @@ -74,15 +75,6 @@ class NonceTracker { return { nextNonce, nonceDetails, releaseLock } } - 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 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 } @@ -172,15 +163,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 6e2fcb40b..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<br> @@ -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) @@ -191,26 +163,12 @@ 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() - } - - /** 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 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 diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 0f7b3d865..1a2354463 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,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<Function>} */ (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 77aefb00a..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 - 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 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, diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 532cfbd61..c00d31949 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') @@ -112,8 +111,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({ @@ -244,28 +244,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), } @@ -661,6 +655,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: /** @@ -672,20 +678,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 } /** @@ -739,24 +736,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 } /** @@ -805,26 +789,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 } /** @@ -1089,7 +1058,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 })) diff --git a/docs/developing-on-deps.md b/docs/developing-on-deps.md index 7de3f67a8..e2e07cb4e 100644 --- a/docs/developing-on-deps.md +++ b/docs/developing-on-deps.md @@ -1,10 +1,9 @@ ### Developing on Dependencies -To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: +To enjoy the live-reloading that `gulp dev` offers while working on the dependencies: 1. Clone the dependency locally. 2. `npm install` in its folder. 3. Run `npm link` in its folder. 4. Run `npm link $DEP_NAME` in this project folder. 5. Next time you `npm start` it will watch the dependency for changes as well! - diff --git a/docs/porting_to_new_environment.md b/docs/porting_to_new_environment.md index 3b1e46052..983c81f3e 100644 --- a/docs/porting_to_new_environment.md +++ b/docs/porting_to_new_environment.md @@ -89,8 +89,6 @@ MetaMask has two kinds of [duplex stream APIs](https://github.com/substack/strea If you are making a MetaMask-powered browser for a new platform, one of the trickiest tasks will be injecting the Web3 API into websites that are visited. On WebExtensions, we actually have to pipe data through a total of three JS contexts just to let sites talk to our background process (site -> contentscript -> background). -To make this as easy as possible, we use one of our favorite internal tools, [web3-provider-engine](https://www.npmjs.com/package/web3-provider-engine) to construct a custom web3 provider object whose source of truth is a stream that we connect to remotely. - To see how we do that, you can refer to the [inpage script](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/inpage.js) that we inject into every website. There you can see it creates a multiplex stream to the background, and uses it to initialize what we call the [inpage-provider](https://github.com/MetaMask/metamask-extension/blob/master/app/scripts/lib/inpage-provider.js), which you can see stubs a few methods out, but mostly just passes calls to `sendAsync` through the stream it's passed! That's really all the magic that's needed to create a web3-like API in a remote context, once you have a stream to MetaMask available. In `inpage.js` you can see we create a `PortStream`, that's just a class we use to wrap WebExtension ports as streams, so we can reuse our favorite stream abstraction over the more irregular API surface of the WebExtension. In a new platform, you will probably need to construct this stream differently. The key is that you need to construct a stream that talks from the site context to the background. Once you have that set up, it works like magic! diff --git a/package-lock.json b/package-lock.json index 62ada9459..a32b86e4d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3734,14 +3734,6 @@ "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=", "dev": true }, - "backoff": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", - "integrity": "sha1-9hbtqdPktmuMp/ynn2lXIsX44m8=", - "requires": { - "precond": "0.2.3" - } - }, "bail": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bail/-/bail-1.0.2.tgz", @@ -5848,22 +5840,6 @@ "is-windows": "1.0.2" } }, - "cross-fetch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.1.0.tgz", - "integrity": "sha512-FTIt2WK44RiafWQ62xIvd+oBoVd392abh1lF872trLlA74JCR1s4oTHlixwoIKy44ehn8WbQ0Ds2P16sw7ZQxg==", - "requires": { - "node-fetch": "2.1.1", - "whatwg-fetch": "2.0.3" - }, - "dependencies": { - "node-fetch": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.1.tgz", - "integrity": "sha1-NpynC4L1DIZJYQSmx3bSdPTkotQ=" - } - } - }, "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", @@ -7998,40 +7974,62 @@ } }, "eth-block-tracker": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.0.tgz", - "integrity": "sha512-yrNyBIBKC7WfUjrXSG/CZVy0gW2aF8+MnjnrkOxkZOR+BAtL6JgYOnzVnrU8KE6mKJETlA/1dYMygvLXWyJGGw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-4.0.1.tgz", + "integrity": "sha512-ytJxddJ0TMcJHYxPlgGhMyr5EH6/Kyp3bg0WsjXgY9X0uYX3xVHTTeU5WVX6KX+9oJ37ZLUjh5PZ6VYnF1Fx/Q==", "requires": { - "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "eth-json-rpc-infura": "3.1.2", "eth-query": "2.1.2", - "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "5.1.3", - "ethjs-util": "0.1.4", - "json-rpc-engine": "3.6.0", - "pify": "2.3.0", - "tape": "4.8.0" + "pify": "3.0.0" }, "dependencies": { - "async-eventemitter": { - "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", - "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "cross-fetch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-2.2.0.tgz", + "integrity": "sha512-P0tdN3ZcwhZQsqUiBnyH02mduL2sBIG1lESy+rUALVDXobpSxNzJhzx4cbzRcSsy3FcJ40Ogc9sjIYrrPs3BVg==", "requires": { - "async": "2.6.0" + "node-fetch": "2.1.2", + "whatwg-fetch": "2.0.4" } }, - "babelify": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", - "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", + "eth-json-rpc-infura": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.1.2.tgz", + "integrity": "sha512-IuK5Iowfs6taluA/3Okmu6EfZcFMq6MQuyrUL1PrCoJstuuBr3TvVeSy3keDyxfbrjFB34nCo538I8G+qMtsbw==", "requires": { - "babel-core": "6.26.0", - "object-assign": "4.1.1" + "cross-fetch": "2.2.0", + "eth-json-rpc-middleware": "1.6.0", + "json-rpc-engine": "3.7.3", + "json-rpc-error": "2.0.0", + "tape": "4.8.0" + }, + "dependencies": { + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "requires": { + "async": "2.6.0", + "eth-query": "2.1.2", + "eth-tx-summary": "3.2.1", + "ethereumjs-block": "1.7.0", + "ethereumjs-tx": "1.3.3", + "ethereumjs-util": "5.2.0", + "ethereumjs-vm": "2.3.2", + "fetch-ponyfill": "4.1.0", + "json-rpc-engine": "3.7.3", + "json-rpc-error": "2.0.0", + "json-stable-stringify": "1.0.1", + "promise-to-callback": "1.0.0", + "tape": "4.8.0" + } + } } }, "ethereumjs-util": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.3.tgz", - "integrity": "sha512-U/wmHagElZVxnpo3bFsvk5beFADegUcEzqtA/NfQbitAPOs6JoYq8M4SY9NfH4HD8236i63UOkkXafd7bqBL9A==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "requires": { "bn.js": "4.11.8", "create-hash": "1.1.3", @@ -8042,22 +8040,15 @@ "secp256k1": "3.4.0" } }, - "json-rpc-engine": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.6.0.tgz", - "integrity": "sha512-QGEIIPMaG4lQ8iKQgzKq7Ra6hscqSL+6S+xiUFbNAoVaZII8iyN1l6tJHmUWIdbnl2o0rbwCnOPFAhTn9AJObw==", - "requires": { - "async": "2.6.0", - "babel-preset-env": "1.6.1", - "babelify": "7.3.0", - "json-rpc-error": "2.0.0", - "promise-to-callback": "1.0.0" - } + "node-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.1.2.tgz", + "integrity": "sha1-q4hOjn5X44qUR1POxwb3iNF2i7U=" }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "whatwg-fetch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz", + "integrity": "sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng==" } } }, @@ -8112,13 +8103,70 @@ } }, "eth-json-rpc-filters": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-1.2.6.tgz", - "integrity": "sha512-6G9t43s3lxJckeSfNduc3Ww/40BGm1Cf8MU1nL8rrumZbEg44ZSexWUowB00D4kJ9qSOH+CbzdI+m3oVMi4xFw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-2.1.1.tgz", + "integrity": "sha512-FSFcCMJ1lRS8az1LhIK5Klima8Hyfv4keKSdW2MA3zwiN/wX1NKb/sQSEFO3nstPUqR2Aa02lVokp85UnnrBlA==", "requires": { "await-semaphore": "0.1.3", - "json-rpc-engine": "3.6.1", + "eth-json-rpc-middleware": "1.6.0", + "ethjs-query": "0.3.8", + "json-rpc-engine": "3.7.3", "lodash.flatmap": "4.5.0" + }, + "dependencies": { + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "requires": { + "async": "2.6.0", + "eth-query": "2.1.2", + "eth-tx-summary": "3.2.1", + "ethereumjs-block": "1.7.0", + "ethereumjs-tx": "1.3.3", + "ethereumjs-util": "5.2.0", + "ethereumjs-vm": "2.3.2", + "fetch-ponyfill": "4.1.0", + "json-rpc-engine": "3.7.3", + "json-rpc-error": "2.0.0", + "json-stable-stringify": "1.0.1", + "promise-to-callback": "1.0.0", + "tape": "4.8.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "4.11.8", + "create-hash": "1.1.3", + "ethjs-util": "0.1.4", + "keccak": "1.4.0", + "rlp": "2.0.0", + "safe-buffer": "5.1.1", + "secp256k1": "3.4.0" + } + }, + "ethjs-query": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/ethjs-query/-/ethjs-query-0.3.8.tgz", + "integrity": "sha512-/J5JydqrOzU8O7VBOwZKUWXxHDGr46VqNjBCJgBVNNda+tv7Xc8Y2uJc6aMHHVbeN3YOQ7YRElgIc0q1CI02lQ==", + "requires": { + "babel-runtime": "6.26.0", + "ethjs-format": "0.2.7", + "ethjs-rpc": "0.2.0", + "promise-to-callback": "1.0.0" + } + }, + "ethjs-rpc": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/ethjs-rpc/-/ethjs-rpc-0.2.0.tgz", + "integrity": "sha512-RINulkNZTKnj4R/cjYYtYMnFFaBcVALzbtEJEONrrka8IeoarNB9Jbzn+2rT00Cv8y/CxAI+GgY1d0/i2iQeOg==", + "requires": { + "promise-to-callback": "1.0.0" + } + } } }, "eth-json-rpc-infura": { @@ -8127,27 +8175,65 @@ "integrity": "sha512-Ab6170AxlF4DK+HDImh52+AetwHPHstgg8uWtX4im26rqK7u4ziSfvUIUK2+/LK0pi0wbIFb8hZm5jPKAXDmBA==", "requires": { "eth-json-rpc-middleware": "1.6.0", - "json-rpc-engine": "3.6.1", + "json-rpc-engine": "3.7.3", "json-rpc-error": "2.0.0", "tape": "4.8.0" + }, + "dependencies": { + "eth-json-rpc-middleware": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", + "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "requires": { + "async": "2.6.0", + "eth-query": "2.1.2", + "eth-tx-summary": "3.2.1", + "ethereumjs-block": "1.7.0", + "ethereumjs-tx": "1.3.3", + "ethereumjs-util": "5.2.0", + "ethereumjs-vm": "2.3.2", + "fetch-ponyfill": "4.1.0", + "json-rpc-engine": "3.7.3", + "json-rpc-error": "2.0.0", + "json-stable-stringify": "1.0.1", + "promise-to-callback": "1.0.0", + "tape": "4.8.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "4.11.8", + "create-hash": "1.1.3", + "ethjs-util": "0.1.4", + "keccak": "1.4.0", + "rlp": "2.0.0", + "safe-buffer": "5.1.1", + "secp256k1": "3.4.0" + } + } } }, "eth-json-rpc-middleware": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-1.6.0.tgz", - "integrity": "sha512-tDVCTlrUvdqHKqivYMjtFZsdD7TtpNLBCfKAcOpaVs7orBMS/A8HWro6dIzNtTZIR05FAbJ3bioFOnZpuCew9Q==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/eth-json-rpc-middleware/-/eth-json-rpc-middleware-2.2.2.tgz", + "integrity": "sha512-ZyFnMxjYKucDC8GHIIE7Fhwj4psdRr7xrGONHolH3uqV+4V1iE8VJphlQpDkRTx2Ar1oM2gKJCQsZCuhG8Wucw==", "requires": { "async": "2.6.0", "eth-query": "2.1.2", + "eth-sig-util": "1.4.2", "eth-tx-summary": "3.2.1", "ethereumjs-block": "1.7.0", "ethereumjs-tx": "1.3.3", "ethereumjs-util": "5.2.0", "ethereumjs-vm": "2.3.2", "fetch-ponyfill": "4.1.0", - "json-rpc-engine": "3.6.1", + "json-rpc-engine": "3.7.3", "json-rpc-error": "2.0.0", "json-stable-stringify": "1.0.1", + "pify": "3.0.0", "promise-to-callback": "1.0.0", "tape": "4.8.0" }, @@ -8172,25 +8258,23 @@ "version": "3.1.4", "resolved": "https://registry.npmjs.org/eth-keyring-controller/-/eth-keyring-controller-3.1.4.tgz", "integrity": "sha512-NNlVB/TBc8p9CblwECjPlUR+7MNQKiBa7tEFxIzZ9MjjNCEYPWDXTm0vJZzuDtVmFxYwIA53UD0QEn0QNxWNEQ==", - "dev": true, "requires": { - "bip39": "2.4.0", - "bluebird": "3.5.1", - "browser-passworder": "2.0.3", - "eth-hd-keyring": "1.2.2", - "eth-sig-util": "1.4.2", - "eth-simple-keyring": "1.2.2", - "ethereumjs-util": "5.2.0", - "loglevel": "1.6.0", - "obs-store": "2.4.1", - "promise-filter": "1.1.0" + "bip39": "^2.4.0", + "bluebird": "^3.5.0", + "browser-passworder": "^2.0.3", + "eth-hd-keyring": "^1.2.2", + "eth-sig-util": "^1.4.0", + "eth-simple-keyring": "^1.2.2", + "ethereumjs-util": "^5.1.2", + "loglevel": "^1.5.0", + "obs-store": "^2.4.1", + "promise-filter": "^1.1.0" }, "dependencies": { "babelify": { "version": "7.3.0", "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", - "dev": true, "requires": { "babel-core": "^6.0.14", "object-assign": "^4.0.0" @@ -8200,7 +8284,6 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", - "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -8215,7 +8298,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/obs-store/-/obs-store-2.4.1.tgz", "integrity": "sha512-wpA8G4uSn8cnCKZ0pFTvqsamvy0Sm1hR2ot0Qonbfj5yBMwdAp/eD4vDI+U/ZCbV1hb2V5GapL8YKUdGCvahgg==", - "dev": true, "requires": { "babel-preset-es2015": "^6.22.0", "babelify": "^7.3.0", @@ -8294,20 +8376,18 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/eth-simple-keyring/-/eth-simple-keyring-1.2.2.tgz", "integrity": "sha512-uQVBYshHUOaXVoat1BpLA/QNMCr4hgdFBgwIB7rRmQ+m3vQQAseUsOM+biPDYzq6end+6LjcccElLpQaIZe6dg==", - "dev": true, "requires": { - "eth-sig-util": "1.4.2", - "ethereumjs-util": "5.2.0", - "ethereumjs-wallet": "0.6.0", - "events": "1.1.1", - "xtend": "4.0.1" + "eth-sig-util": "^1.4.2", + "ethereumjs-util": "^5.1.1", + "ethereumjs-wallet": "^0.6.0", + "events": "^1.1.1", + "xtend": "^4.0.1" }, "dependencies": { "ethereumjs-util": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", - "dev": true, "requires": { "bn.js": "^4.11.0", "create-hash": "^1.1.2", @@ -8542,48 +8622,6 @@ } } } - }, - "web3-provider-engine": { - "version": "13.8.0", - "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz", - "integrity": "sha512-fZXhX5VWwWpoFfrfocslyg6P7cN3YWPG/ASaevNfeO80R+nzgoPUBXcWQekSGSsNDkeRTis4aMmpmofYf1TNtQ==", - "requires": { - "async": "2.6.0", - "clone": "2.1.1", - "eth-block-tracker": "2.3.0", - "eth-sig-util": "1.4.2", - "ethereumjs-block": "1.7.0", - "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "5.2.0", - "ethereumjs-vm": "2.3.5", - "fetch-ponyfill": "4.1.0", - "json-rpc-error": "2.0.0", - "json-stable-stringify": "1.0.1", - "promise-to-callback": "1.0.0", - "readable-stream": "2.3.3", - "request": "2.83.0", - "semaphore": "1.1.0", - "solc": "0.4.23", - "tape": "4.8.0", - "xhr": "2.4.1", - "xtend": "4.0.1" - }, - "dependencies": { - "ethereumjs-util": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", - "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", - "requires": { - "bn.js": "4.11.8", - "create-hash": "1.1.3", - "ethjs-util": "0.1.4", - "keccak": "1.4.0", - "rlp": "2.0.0", - "safe-buffer": "5.1.1", - "secp256k1": "3.4.0" - } - } - } } } }, @@ -11152,7 +11190,7 @@ "requires": { "async": "2.6.0", "clone": "2.1.1", - "eth-block-tracker": "2.3.0", + "eth-block-tracker": "2.3.1", "eth-sig-util": "1.4.2", "ethereumjs-block": "1.2.2", "ethereumjs-tx": "1.3.3", @@ -11169,6 +11207,35 @@ "tape": "4.8.0", "xhr": "2.4.1", "xtend": "4.0.1" + }, + "dependencies": { + "async-eventemitter": { + "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "requires": { + "async": "2.6.0" + } + }, + "eth-block-tracker": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.1.tgz", + "integrity": "sha512-NamWuMBIl8kmkJFVj8WzGatySTzQPQag4Xr677yFxdVtIxACFbL/dQowk0MzEqIKk93U1TwY3MjVU6mOcwZnKA==", + "requires": { + "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "eth-query": "2.1.2", + "ethereumjs-tx": "1.3.3", + "ethereumjs-util": "5.2.0", + "ethjs-util": "0.1.4", + "json-rpc-engine": "3.7.3", + "pify": "2.3.0", + "tape": "4.8.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + } } }, "yargs": { @@ -15442,13 +15509,14 @@ "dev": true }, "json-rpc-engine": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.6.1.tgz", - "integrity": "sha512-xYuD9M1pcld5OKPzVAoEG5MKtnR8iKMyNzRpeS3/mCJ7dcAcS67vqfOmYLoaIQfVRU5uClThbjri3VFR0vEwYg==", + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-3.7.3.tgz", + "integrity": "sha512-+FO3UWu/wafh/+MZ6BXy0HZU+f5plwUn82FgxpC0scJkEh5snOjFrAAtqCITPDfvfLHRUFOG5pQDUx2pspfERQ==", "requires": { "async": "2.6.0", "babel-preset-env": "1.6.1", "babelify": "7.3.0", + "clone": "2.1.1", "json-rpc-error": "2.0.0", "promise-to-callback": "1.0.0" }, @@ -15478,17 +15546,53 @@ "integrity": "sha512-IR6cOO6B21NdLpiYblueB3O+g3UAYLIZd6ZgZfddVPl0z6vSECcpuiYnV5MmIMJY3D0fLYpJqOxYaEmLYQqTtA==", "requires": { "end-of-stream": "1.4.0", - "eth-block-tracker": "2.3.0", + "eth-block-tracker": "2.3.1", "ethjs-query": "0.2.9", - "json-rpc-engine": "3.6.1", + "json-rpc-engine": "3.7.3", "readable-stream": "2.3.3" }, "dependencies": { + "async-eventemitter": { + "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "requires": { + "async": "2.6.0" + } + }, "bn.js": { "version": "4.11.6", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha1-UzRK2xRhehP26N0s4okF0cC6MhU=" }, + "eth-block-tracker": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.1.tgz", + "integrity": "sha512-NamWuMBIl8kmkJFVj8WzGatySTzQPQag4Xr677yFxdVtIxACFbL/dQowk0MzEqIKk93U1TwY3MjVU6mOcwZnKA==", + "requires": { + "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "eth-query": "2.1.2", + "ethereumjs-tx": "1.3.3", + "ethereumjs-util": "5.2.0", + "ethjs-util": "0.1.3", + "json-rpc-engine": "3.7.3", + "pify": "2.3.0", + "tape": "4.8.0" + } + }, + "ethereumjs-util": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", + "requires": { + "bn.js": "4.11.6", + "create-hash": "1.1.3", + "ethjs-util": "0.1.3", + "keccak": "1.4.0", + "rlp": "2.0.0", + "safe-buffer": "5.1.1", + "secp256k1": "3.4.0" + } + }, "ethjs-format": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/ethjs-format/-/ethjs-format-0.2.2.tgz", @@ -15524,6 +15628,11 @@ "is-hex-prefixed": "1.0.0", "strip-hex-prefix": "1.0.0" } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" } } }, @@ -18248,7 +18357,7 @@ "requires": { "iframe": "1.0.0", "iframe-stream": "3.0.0", - "json-rpc-engine": "3.6.1", + "json-rpc-engine": "3.7.3", "json-rpc-middleware-stream": "1.0.1", "obj-multiplex": "1.0.0", "obs-store": "2.4.1", @@ -24298,11 +24407,6 @@ } } }, - "precond": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/precond/-/precond-0.2.3.tgz", - "integrity": "sha1-qpWRvKokkj8eD0hJ0kD0fvwQdaw=" - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -27620,9 +27724,9 @@ } }, "solc": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.23.tgz", - "integrity": "sha512-AT7anLHY6uIRg2It6N0UlCHeZ7YeecIkUhnlirrCgCPCUevtnoN48BxvgigN/4jJTRljv5oFhAJtI6gvHzT5DQ==", + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.24.tgz", + "integrity": "sha512-2xd7Cf1HeVwrIb6Bu1cwY2/TaLRodrppCq3l7rhLimFQgmxptXhTC3+/wesVLpB09F1A2kZgvbMOgH7wvhFnBQ==", "requires": { "fs-extra": "0.30.0", "memorystream": "0.3.1", @@ -29077,6 +29181,11 @@ } } }, + "swappable-obj-proxy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/swappable-obj-proxy/-/swappable-obj-proxy-1.0.2.tgz", + "integrity": "sha512-IDrfIgZr09yK9j8XSoeHACf9IaM03izjIiNBq7lZrXQYr2eXwjcRXJUcUmkOkTs3QrXigAGbVgaq86hsRH9DAg==" + }, "swarm-js": { "version": "0.1.37", "resolved": "https://registry.npmjs.org/swarm-js/-/swarm-js-0.1.37.tgz", @@ -31614,63 +31723,57 @@ } }, "web3-provider-engine": { - "version": "14.0.5", - "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-14.0.5.tgz", - "integrity": "sha512-1W/ue7VOwOMnmKgMY3HCpbixi6qhfl4r1dK8W597AwJLbrQ+twJKwWlFAedDpJjCc9MwRCCB3pyexW4HJVSiBg==", + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz", + "integrity": "sha512-fZXhX5VWwWpoFfrfocslyg6P7cN3YWPG/ASaevNfeO80R+nzgoPUBXcWQekSGSsNDkeRTis4aMmpmofYf1TNtQ==", "requires": { "async": "2.6.0", - "backoff": "2.5.0", "clone": "2.1.1", - "cross-fetch": "2.1.0", - "eth-block-tracker": "3.0.0", - "eth-json-rpc-infura": "3.1.0", + "eth-block-tracker": "2.3.1", "eth-sig-util": "1.4.2", "ethereumjs-block": "1.7.0", "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "5.1.5", - "ethereumjs-vm": "2.3.5", + "ethereumjs-util": "5.2.0", + "ethereumjs-vm": "2.3.2", + "fetch-ponyfill": "4.1.0", "json-rpc-error": "2.0.0", "json-stable-stringify": "1.0.1", "promise-to-callback": "1.0.0", "readable-stream": "2.3.3", "request": "2.83.0", "semaphore": "1.1.0", + "solc": "0.4.24", "tape": "4.8.0", - "ws": "5.1.1", "xhr": "2.4.1", "xtend": "4.0.1" }, "dependencies": { + "async-eventemitter": { + "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "requires": { + "async": "2.6.0" + } + }, "eth-block-tracker": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-3.0.0.tgz", - "integrity": "sha512-Lhhu/+1GOeekMRDRhUcM7VSJRmX279DByrwzEbmG0JL1tcT3xRo6GLNXnidyJ7ahHJm+0JFhw/RqtTeIxagQwA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-2.3.1.tgz", + "integrity": "sha512-NamWuMBIl8kmkJFVj8WzGatySTzQPQag4Xr677yFxdVtIxACFbL/dQowk0MzEqIKk93U1TwY3MjVU6mOcwZnKA==", "requires": { + "async-eventemitter": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", "eth-query": "2.1.2", "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "5.1.5", + "ethereumjs-util": "5.2.0", "ethjs-util": "0.1.4", - "json-rpc-engine": "3.6.1", + "json-rpc-engine": "3.7.3", "pify": "2.3.0", "tape": "4.8.0" } }, - "eth-json-rpc-infura": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eth-json-rpc-infura/-/eth-json-rpc-infura-3.1.0.tgz", - "integrity": "sha512-uMYkEP6fga8CyNo8TMoA/7cxi6bL3V8pTvjKQikOi9iYl6/AO5xlfgniyAMElSiq2mmXz3lYa/9VYDMzt/J5aA==", - "requires": { - "cross-fetch": "2.1.0", - "eth-json-rpc-middleware": "1.6.0", - "json-rpc-engine": "3.6.1", - "json-rpc-error": "2.0.0", - "tape": "4.8.0" - } - }, "ethereumjs-util": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz", - "integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", + "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "requires": { "bn.js": "4.11.8", "create-hash": "1.1.3", @@ -31681,36 +31784,10 @@ "secp256k1": "3.4.0" } }, - "ethereumjs-vm": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/ethereumjs-vm/-/ethereumjs-vm-2.3.5.tgz", - "integrity": "sha512-AJ7x44+xqyE5+UO3Nns19WkTdZfyqFZ+sEjIEpvme7Ipbe3iBU1uwCcHEdiu/yY9bdhr3IfSa/NfIKNeXPaRVQ==", - "requires": { - "async": "2.6.0", - "async-eventemitter": "0.2.4", - "ethereum-common": "0.2.0", - "ethereumjs-account": "2.0.4", - "ethereumjs-block": "1.7.0", - "ethereumjs-util": "5.1.5", - "fake-merkle-patricia-tree": "1.0.1", - "functional-red-black-tree": "1.0.1", - "merkle-patricia-tree": "2.3.0", - "rustbn.js": "0.1.1", - "safe-buffer": "5.1.1" - } - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "ws": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-5.1.1.tgz", - "integrity": "sha512-bOusvpCb09TOBLbpMKszd45WKC2KPtxiyiHanv+H2DE3Az+1db5a/L7sVJZVDPUC1Br8f0SKRr1KjLpD1U/IAw==", - "requires": { - "async-limiter": "1.0.0" - } } } }, diff --git a/package.json b/package.json index c08ae455b..707a90ffe 100644 --- a/package.json +++ b/package.json @@ -94,10 +94,13 @@ "ensnare": "^1.0.0", "eslint-plugin-react": "^7.4.0", "eth-bin-to-ops": "^1.0.1", + "eth-block-tracker": "^4.0.1", "eth-contract-metadata": "^1.1.5", "eth-hd-keyring": "^1.2.1", - "eth-json-rpc-filters": "^1.2.6", + "eth-json-rpc-filters": "^2.1.1", "eth-json-rpc-infura": "^3.0.0", + "eth-json-rpc-middleware": "^2.2.2", + "eth-keyring-controller": "^3.1.4", "eth-phishing-detect": "^1.1.4", "eth-query": "^2.1.2", "eth-sig-util": "^1.4.2", @@ -131,7 +134,7 @@ "iframe-stream": "^3.0.0", "inject-css": "^0.1.1", "jazzicon": "^1.2.0", - "json-rpc-engine": "^3.6.1", + "json-rpc-engine": "^3.7.3", "json-rpc-middleware-stream": "^1.0.1", "lodash.debounce": "^4.0.8", "lodash.memoize": "^4.1.2", @@ -186,11 +189,11 @@ "shallow-copy": "0.0.1", "sw-controller": "^1.0.3", "sw-stream": "^2.0.2", + "swappable-obj-proxy": "^1.0.2", "textarea-caret": "^3.0.1", "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "^0.20.1", - "web3-provider-engine": "^14.0.5", "web3-stream-provider": "^3.0.1", "xtend": "^4.0.1" }, @@ -228,8 +231,6 @@ "eslint-plugin-json": "^1.2.0", "eslint-plugin-mocha": "^5.0.0", "eslint-plugin-react": "^7.4.0", - "eth-json-rpc-middleware": "^1.6.0", - "eth-keyring-controller": "^3.1.4", "file-loader": "^1.1.11", "fs-promise": "^2.0.3", "ganache-cli": "^6.1.0", diff --git a/test/helper.js b/test/helper.js index a3abbebf2..2225be37a 100644 --- a/test/helper.js +++ b/test/helper.js @@ -1,3 +1,4 @@ +const Ganache = require('ganache-core') import Enzyme from 'enzyme' import Adapter from 'enzyme-adapter-react-15' @@ -5,6 +6,12 @@ Enzyme.configure({ adapter: new Adapter() }) // disallow promises from swallowing errors enableFailureOnUnhandledPromiseRejection() +// ganache server +const server = Ganache.server() +server.listen(8545, () => { + console.log('Ganache Testrpc is running on "http://localhost:8545"') +}) + // logging util var log = require('loglevel') log.setDefaultLevel(5) @@ -14,6 +21,9 @@ global.log = log // polyfills // +// fetch +global.fetch = require('isomorphic-fetch') + // dom require('jsdom-global')() diff --git a/test/lib/createTxMeta.js b/test/lib/createTxMeta.js new file mode 100644 index 000000000..246ea58a5 --- /dev/null +++ b/test/lib/createTxMeta.js @@ -0,0 +1,16 @@ +const txStateHistoryHelper = require('../../app/scripts/controllers/transactions/lib/tx-state-history-helper') + +module.exports = createTxMeta + +function createTxMeta(partialMeta) { + const txMeta = Object.assign({ + status: 'unapproved', + txParams: {}, + }, partialMeta) + // initialize history + txMeta.history = [] + // capture initial snapshot of txMeta for history + const snapshot = txStateHistoryHelper.snapshotFromTxMeta(txMeta) + txMeta.history.push(snapshot) + return txMeta +} diff --git a/test/unit/app/controllers/currency-controller-test.js b/test/unit/app/controllers/currency-controller-test.js index 1941d1c43..7c4644d9f 100644 --- a/test/unit/app/controllers/currency-controller-test.js +++ b/test/unit/app/controllers/currency-controller-test.js @@ -1,6 +1,3 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - const assert = require('assert') const nock = require('nock') const CurrencyController = require('../../../../app/scripts/controllers/currency') diff --git a/test/unit/app/controllers/metamask-controller-test.js b/test/unit/app/controllers/metamask-controller-test.js index 0dda4609b..cbd949e69 100644 --- a/test/unit/app/controllers/metamask-controller-test.js +++ b/test/unit/app/controllers/metamask-controller-test.js @@ -3,9 +3,10 @@ const sinon = require('sinon') const clone = require('clone') const nock = require('nock') const createThoughStream = require('through2').obj -const MetaMaskController = require('../../../../app/scripts/metamask-controller') const blacklistJSON = require('eth-phishing-detect/src/config') +const MetaMaskController = require('../../../../app/scripts/metamask-controller') const firstTimeState = require('../../../../app/scripts/first-time-state') +const createTxMeta = require('../../../lib/createTxMeta') const currentNetworkId = 42 const DEFAULT_LABEL = 'Account 1' @@ -13,6 +14,7 @@ const TEST_SEED = 'debris dizzy just program just float decrease vacant alarm re const TEST_ADDRESS = '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc' const TEST_SEED_ALT = 'setup olympic issue mobile velvet surge alcohol burger horse view reopen gentle' const TEST_ADDRESS_ALT = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' +const CUSTOM_RPC_URL = 'http://localhost:8545' describe('MetaMaskController', function () { let metamaskController @@ -223,29 +225,19 @@ describe('MetaMaskController', function () { }) describe('#setCustomRpc', function () { - const customRPC = 'https://custom.rpc/' let rpcTarget beforeEach(function () { - - nock('https://custom.rpc') - .post('/') - .reply(200) - - rpcTarget = metamaskController.setCustomRpc(customRPC) - }) - - afterEach(function () { - nock.cleanAll() + rpcTarget = metamaskController.setCustomRpc(CUSTOM_RPC_URL) }) it('returns custom RPC that when called', async function () { - assert.equal(await rpcTarget, customRPC) + assert.equal(await rpcTarget, CUSTOM_RPC_URL) }) it('changes the network controller rpc', function () { const networkControllerState = metamaskController.networkController.store.getState() - assert.equal(networkControllerState.provider.rpcTarget, customRPC) + assert.equal(networkControllerState.provider.rpcTarget, CUSTOM_RPC_URL) }) }) @@ -350,9 +342,10 @@ describe('MetaMaskController', function () { getNetworkstub.returns(42) metamaskController.txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }, - { id: 2, status: 'rejected', metamaskNetworkId: 32, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }, + createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }), + createTxMeta({ id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {from: '0x0dcd5d886577d5081b0c52e242ef29e70be3e7bc'} }), + createTxMeta({ id: 2, status: 'rejected', metamaskNetworkId: 32 }), + createTxMeta({ id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {from: '0xB09d8505E1F4EF1CeA089D47094f5DD3464083d4'} }), ]) }) @@ -396,14 +389,14 @@ describe('MetaMaskController', function () { }) - describe('#newUnsignedMessage', function () { + describe('#newUnsignedMessage', () => { let msgParams, metamaskMsgs, messages, msgId const address = '0xc42edfcc21ed14dda456aa0756c153f7985d8813' const data = '0x43727970746f6b697474696573' - beforeEach(async function () { + beforeEach(async () => { await metamaskController.createNewVaultAndRestore('foobar1337', TEST_SEED_ALT) @@ -412,7 +405,10 @@ describe('MetaMaskController', function () { 'data': data, } - metamaskController.newUnsignedMessage(msgParams, noop) + const promise = metamaskController.newUnsignedMessage(msgParams) + // handle the promise so it doesn't throw an unhandledRejection + promise.then(noop).catch(noop) + metamaskMsgs = metamaskController.messageManager.getUnapprovedMsgs() messages = metamaskController.messageManager.messages msgId = Object.keys(metamaskMsgs)[0] @@ -452,13 +448,16 @@ describe('MetaMaskController', function () { describe('#newUnsignedPersonalMessage', function () { - it('errors with no from in msgParams', function () { + it('errors with no from in msgParams', async () => { const msgParams = { 'data': data, } - metamaskController.newUnsignedPersonalMessage(msgParams, function (error) { + try { + await metamaskController.newUnsignedPersonalMessage(msgParams) + assert.fail('should have thrown') + } catch (error) { assert.equal(error.message, 'MetaMask Message Signature: from field is required.') - }) + } }) let msgParams, metamaskPersonalMsgs, personalMessages, msgId @@ -475,7 +474,10 @@ describe('MetaMaskController', function () { 'data': data, } - metamaskController.newUnsignedPersonalMessage(msgParams, noop) + const promise = metamaskController.newUnsignedPersonalMessage(msgParams) + // handle the promise so it doesn't throw an unhandledRejection + promise.then(noop).catch(noop) + metamaskPersonalMsgs = metamaskController.personalMessageManager.getUnapprovedMsgs() personalMessages = metamaskController.personalMessageManager.messages msgId = Object.keys(metamaskPersonalMsgs)[0] @@ -514,22 +516,27 @@ describe('MetaMaskController', function () { describe('#setupUntrustedCommunication', function () { let streamTest - const phishingUrl = 'decentral.market' + const phishingUrl = 'myethereumwalletntw.com' afterEach(function () { streamTest.end() }) - it('sets up phishing stream for untrusted communication ', async function () { + it('sets up phishing stream for untrusted communication ', async () => { await metamaskController.blacklistController.updatePhishingList() + console.log(blacklistJSON.blacklist.includes(phishingUrl)) + + const { promise, resolve } = deferredPromise() streamTest = createThoughStream((chunk, enc, cb) => { - assert.equal(chunk.name, 'phishing') + if (chunk.name !== 'phishing') return cb() assert.equal(chunk.data.hostname, phishingUrl) - cb() - }) - // console.log(streamTest) - metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + resolve() + cb() + }) + metamaskController.setupUntrustedCommunication(streamTest, phishingUrl) + + await promise }) }) @@ -576,3 +583,9 @@ describe('MetaMaskController', function () { }) }) + +function deferredPromise () { + let resolve + const promise = new Promise(_resolve => { resolve = _resolve }) + return { promise, resolve } +} diff --git a/test/unit/app/controllers/network-contoller-test.js b/test/unit/app/controllers/network-contoller-test.js index 789850ef3..a87ffb904 100644 --- a/test/unit/app/controllers/network-contoller-test.js +++ b/test/unit/app/controllers/network-contoller-test.js @@ -35,9 +35,10 @@ describe('# Network Controller', function () { describe('#provider', function () { it('provider should be updatable without reassignment', function () { networkController.initializeProvider(networkControllerProviderConfig) - const proxy = networkController._proxy - proxy.setTarget({ test: true, on: () => {} }) - assert.ok(proxy.test) + const providerProxy = networkController.getProviderAndBlockTracker().provider + assert.equal(providerProxy.test, undefined) + providerProxy.setTarget({ test: true }) + assert.equal(providerProxy.test, true) }) }) describe('#getNetworkState', function () { diff --git a/test/unit/app/controllers/transactions/nonce-tracker-test.js b/test/unit/app/controllers/transactions/nonce-tracker-test.js index fc852458c..aef606bab 100644 --- a/test/unit/app/controllers/transactions/nonce-tracker-test.js +++ b/test/unit/app/controllers/transactions/nonce-tracker-test.js @@ -226,14 +226,15 @@ function generateNonceTrackerWith (pending, confirmed, providerStub = '0x0') { providerResultStub.result = providerStub const provider = { sendAsync: (_, cb) => { cb(undefined, providerResultStub) }, - _blockTracker: { - getCurrentBlock: () => '0x11b568', - }, + } + const blockTracker = { + getCurrentBlock: () => '0x11b568', + getLatestBlock: async () => '0x11b568', } return new NonceTracker({ provider, + blockTracker, getPendingTransactions, getConfirmedTransactions, }) } - diff --git a/test/unit/app/controllers/transactions/pending-tx-test.js b/test/unit/app/controllers/transactions/pending-tx-test.js index e7705c594..f0096dadd 100644 --- a/test/unit/app/controllers/transactions/pending-tx-test.js +++ b/test/unit/app/controllers/transactions/pending-tx-test.js @@ -7,7 +7,7 @@ const { createTestProviderTools } = require('../../../../stub/provider') const PendingTransactionTracker = require('../../../../../app/scripts/controllers/transactions/pending-tx-tracker') const MockTxGen = require('../../../../lib/mock-tx-gen') const sinon = require('sinon') -const noop = () => true +const noop =()=>true const currentNetworkId = 42 const otherNetworkId = 36 const privKey = new Buffer('8718b9618a37d1fc78c436511fc6df3c8258d3250635bba617f33003270ec03e', 'hex') @@ -17,6 +17,7 @@ describe('PendingTransactionTracker', function () { let pendingTxTracker, txMeta, txMetaNoHash, txMetaNoRawTx, providerResultStub, provider, txMeta3, txList, knownErrors this.timeout(10000) + beforeEach(function () { txMeta = { id: 1, @@ -52,7 +53,10 @@ describe('PendingTransactionTracker', function () { getPendingTransactions: () => {return []}, getCompletedTransactions: () => {return []}, publishTransaction: () => {}, + confirmTransaction: () => {}, }) + + pendingTxTracker._getBlock = (blockNumber) => { return {number: blockNumber, transactions: []} } }) describe('_checkPendingTx state management', function () { @@ -104,60 +108,6 @@ describe('PendingTransactionTracker', function () { }) }) - describe('#checkForTxInBlock', function () { - it('should return if no pending transactions', function () { - // throw a type error if it trys to do anything on the block - // thus failing the test - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { - const block = Proxy.revocable({}, {}).revoke() - pendingTxTracker.getPendingTransactions = () => [txMetaNoHash] - pendingTxTracker.once('tx:failed', (txId, err) => { - assert(txId, txMetaNoHash.id, 'should pass txId') - done() - }) - pendingTxTracker.checkForTxInBlock(block) - }) - it('should emit \'txConfirmed\' if the tx is in the block', function (done) { - const block = { transactions: [txMeta]} - pendingTxTracker.getPendingTransactions = () => [txMeta] - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker.checkForTxInBlock(block) - }) - }) - describe('#queryPendingTxs', function () { - it('should call #_checkPendingTxs if their is no oldBlock', function (done) { - let newBlock, oldBlock - newBlock = { number: '0x01' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should call #_checkPendingTxs if oldBlock and the newBlock have a diff of greater then 1', function (done) { - let newBlock, oldBlock - oldBlock = { number: '0x01' } - newBlock = { number: '0x03' } - pendingTxTracker._checkPendingTxs = done - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - }) - it('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less', function (done) { - let newBlock, oldBlock - oldBlock = { number: '0x1' } - newBlock = { number: '0x2' } - pendingTxTracker._checkPendingTxs = () => { - const err = new Error('should not call #_checkPendingTxs if oldBlock and the newBlock have a diff of 1 or less') - done(err) - } - pendingTxTracker.queryPendingTxs({ oldBlock, newBlock }) - done() - }) - }) - describe('#_checkPendingTx', function () { it('should emit \'tx:failed\' if the txMeta does not have a hash', function (done) { pendingTxTracker.once('tx:failed', (txId, err) => { @@ -171,16 +121,6 @@ describe('PendingTransactionTracker', function () { providerResultStub.eth_getTransactionByHash = null pendingTxTracker._checkPendingTx(txMeta) }) - - it('should emit \'txConfirmed\'', function (done) { - providerResultStub.eth_getTransactionByHash = {blockNumber: '0x01'} - pendingTxTracker.once('tx:confirmed', (txId) => { - assert(txId, txMeta.id, 'should pass txId') - done() - }) - pendingTxTracker.once('tx:failed', (_, err) => { done(err) }) - pendingTxTracker._checkPendingTx(txMeta) - }) }) describe('#_checkPendingTxs', function () { @@ -194,24 +134,23 @@ describe('PendingTransactionTracker', function () { }) }) - it('should warp all txMeta\'s in #_checkPendingTx', function (done) { + it('should warp all txMeta\'s in #updatePendingTxs', function (done) { pendingTxTracker.getPendingTransactions = () => txList pendingTxTracker._checkPendingTx = (tx) => { tx.resolve(tx) } - const list = txList.map Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxTracker._checkPendingTxs() + pendingTxTracker.updatePendingTxs() }) }) describe('#resubmitPendingTxs', function () { - const blockStub = { number: '0x0' }; + const blockNuberStub = '0x0' beforeEach(function () { const txMeta2 = txMeta3 = txMeta txList = [txMeta, txMeta2, txMeta3].map((tx) => { - tx.processed = new Promise ((resolve) => { tx.resolve = resolve }) + tx.processed = new Promise((resolve) => { tx.resolve = resolve }) return tx }) }) @@ -225,10 +164,10 @@ describe('PendingTransactionTracker', function () { Promise.all(txList.map((tx) => tx.processed)) .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNuberStub) }) it('should not emit \'tx:failed\' if the txMeta throws a known txError', function (done) { - knownErrors =[ + knownErrors = [ // geth ' Replacement transaction Underpriced ', ' known transaction', @@ -252,7 +191,7 @@ describe('PendingTransactionTracker', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNuberStub) }) it('should emit \'tx:warning\' if it encountered a real error', function (done) { pendingTxTracker.once('tx:warning', (txMeta, err) => { @@ -270,7 +209,7 @@ describe('PendingTransactionTracker', function () { .then((txCompletedList) => done()) .catch(done) - pendingTxTracker.resubmitPendingTxs(blockStub) + pendingTxTracker.resubmitPendingTxs(blockNuberStub) }) }) describe('#_resubmitTx', function () { @@ -342,7 +281,7 @@ describe('PendingTransactionTracker', function () { }) describe('#_checkIfNonceIsTaken', function () { - beforeEach ( function () { + beforeEach(function () { let confirmedTxList = [{ id: 1, hash: '0x0593ee121b92e10d63150ad08b4b8f9c7857d1bd160195ee648fb9a0f8d00eeb', diff --git a/test/unit/app/controllers/transactions/tx-controller-test.js b/test/unit/app/controllers/transactions/tx-controller-test.js index 9bdfe7c1a..f94b0cc44 100644 --- a/test/unit/app/controllers/transactions/tx-controller-test.js +++ b/test/unit/app/controllers/transactions/tx-controller-test.js @@ -1,4 +1,5 @@ const assert = require('assert') +const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') const EthjsQuery = require('ethjs-query') @@ -26,12 +27,14 @@ describe('Transaction Controller', function () { provider = createTestProviderTools({ scaffold: providerResultStub }).provider query = new EthjsQuery(provider) fromAccount = getTestAccounts()[0] - + const blockTrackerStub = new EventEmitter() + blockTrackerStub.getCurrentBlock = noop + blockTrackerStub.getLatestBlock = noop txController = new TransactionController({ provider, networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, - blockTracker: { getCurrentBlock: noop, on: noop, once: noop }, + blockTracker: blockTrackerStub, signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(fromAccount.key) resolve() @@ -53,9 +56,9 @@ describe('Transaction Controller', function () { describe('#getUnapprovedTxCount', function () { it('should return the number of unapproved txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 1, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 2, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, ]) const unapprovedTxCount = txController.getUnapprovedTxCount() assert.equal(unapprovedTxCount, 3, 'should be 3') @@ -65,9 +68,9 @@ describe('Transaction Controller', function () { describe('#getPendingTxCount', function () { it('should return the number of pending txs', function () { txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, - { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {} }, + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 2, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, + { id: 3, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams: {}, history: [] }, ]) const pendingTxCount = txController.getPendingTxCount() assert.equal(pendingTxCount, 3, 'should be 3') @@ -83,15 +86,15 @@ describe('Transaction Controller', function () { 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', } txController.txStateManager._saveTxList([ - {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams}, - {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams}, - {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams}, - {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams}, - {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams}, + {id: 0, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 1, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 2, status: 'confirmed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 3, status: 'unapproved', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 4, status: 'rejected', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 5, status: 'approved', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 6, status: 'signed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 7, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] }, + {id: 8, status: 'failed', metamaskNetworkId: currentNetworkId, txParams, history: [] }, ]) }) @@ -205,24 +208,22 @@ describe('Transaction Controller', function () { }) describe('#addTxGasDefaults', function () { - it('should add the tx defaults if their are none', function (done) { + it('should add the tx defaults if their are none', async () => { const txMeta = { - 'txParams': { - 'from': '0xc684832530fcbddae4b4230a47e991ddcec2831d', - 'to': '0xc684832530fcbddae4b4230a47e991ddcec2831d', + txParams: { + from: '0xc684832530fcbddae4b4230a47e991ddcec2831d', + to: '0xc684832530fcbddae4b4230a47e991ddcec2831d', }, + history: [], } - providerResultStub.eth_gasPrice = '4a817c800' - providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } - providerResultStub.eth_estimateGas = '5209' - txController.addTxGasDefaults(txMeta) - .then((txMetaWithDefaults) => { - assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') - assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') - assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') - done() - }) - .catch(done) + providerResultStub.eth_gasPrice = '4a817c800' + providerResultStub.eth_getBlockByNumber = { gasLimit: '47b784' } + providerResultStub.eth_estimateGas = '5209' + + const txMetaWithDefaults = await txController.addTxGasDefaults(txMeta) + assert(txMetaWithDefaults.txParams.value, '0x0', 'should have added 0x0 as the value') + assert(txMetaWithDefaults.txParams.gasPrice, 'should have added the gas price') + assert(txMetaWithDefaults.txParams.gas, 'should have added the gas field') }) }) @@ -395,7 +396,7 @@ describe('Transaction Controller', function () { data: '0x0', } txController.txStateManager._saveTxList([ - { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams }, + { id: 1, status: 'submitted', metamaskNetworkId: currentNetworkId, txParams, history: [] }, ]) txController.retryTransaction(1) .then((txMeta) => { diff --git a/test/unit/config-manager-test.js b/test/unit/config-manager-test.js index b710e2dfb..df0c549d0 100644 --- a/test/unit/config-manager-test.js +++ b/test/unit/config-manager-test.js @@ -1,6 +1,3 @@ -// polyfill fetch -global.fetch = global.fetch || require('isomorphic-fetch') - const assert = require('assert') const configManagerGen = require('../lib/mock-config-manager') |