From f87ea49b5ac2d66d8f281f08f42e8cfd2d701ba7 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 18 May 2017 23:54:02 +0200 Subject: [PATCH 1/6] Create a network controller to manage switcing networks an updating the provider --- app/scripts/controllers/network.js | 152 +++++++++++++++++++++++++++++ app/scripts/keyring-controller.js | 6 ++ app/scripts/lib/config-manager.js | 67 ------------- app/scripts/lib/tx-utils.js | 8 +- app/scripts/metamask-controller.js | 124 ++++++++++------------- app/scripts/transaction-manager.js | 25 +++-- 6 files changed, 232 insertions(+), 150 deletions(-) create mode 100644 app/scripts/controllers/network.js diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js new file mode 100644 index 000000000..82eabb573 --- /dev/null +++ b/app/scripts/controllers/network.js @@ -0,0 +1,152 @@ +const EventEmitter = require('events') +const MetaMaskProvider = require('web3-provider-engine/zero.js') +const ObservableStore = require('obs-store') +const extend = require('xtend') +const EthQuery = require('eth-query') +const MetamaskConfig = require('../config.js') + +const TESTNET_RPC = MetamaskConfig.network.testnet +const MAINNET_RPC = MetamaskConfig.network.mainnet +const MORDEN_RPC = MetamaskConfig.network.morden +const KOVAN_RPC = MetamaskConfig.network.kovan +const RINKEBY_RPC = MetamaskConfig.network.rinkeby + +module.exports = class NetworkController extends EventEmitter { + constructor (providerOpts) { + super() + this.networkStore = new ObservableStore({ network: 'loading' }) + providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type) + this.providerStore = new ObservableStore(providerOpts) + this._claimed = 0 + } + + getState () { + return extend({}, + this.networkStore.getState(), + this.providerStore.getState() + ) + } + + initializeProvider (opts) { + this.providerConfig = opts + this.provider = MetaMaskProvider(opts) + this.ethQuery = new EthQuery(this.provider) + this.lookupNetwork() + return Promise.resolve(this.provider) + } + switchNetwork (providerConfig) { + delete this.provider + delete this.ethQuery + const newConfig = extend(this.providerConfig, providerConfig) + this.providerConfig = newConfig + this.provider = MetaMaskProvider(newConfig) + this.ethQuery = new EthQuery(this.provider) + this.emit('networkSwitch', { + provider: this.provider, + ethQuery: this.ethQuery, + }, this.claim.bind(this)) + } + + subscribe (cb) { + this.networkStore.subscribe(cb) + this.providerStore.subscribe(cb) + } + + verifyNetwork () { + // Check network when restoring connectivity: + if (this.isNetworkLoading()) this.lookupNetwork() + } + + getNetworkState () { + return this.networkStore.getState().network + } + + setNetworkState (network) { + return this.networkStore.updateState({ network }) + } + + isNetworkLoading () { + return this.getNetworkState() === 'loading' + } + + lookupNetwork (err) { + if (err) this.setNetworkState('loading') + + this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { + if (err) return this.setNetworkState('loading') + + log.info('web3.getNetwork returned ' + network) + this.setNetworkState(network) + }) + } + + setRpcTarget (rpcUrl) { + this.providerStore.updateState({ + provider: { + type: 'rpc', + rpcTarget: rpcUrl, + }, + }) + } + + getCurrentRpcAddress () { + var provider = this.getProvider() + if (!provider) return null + return this.getRpcAddressForType(provider.type) + } + + setProviderType (type) { + if (type === this.getProvider().type) return + const rpcTarget = this.getRpcAddressForType(type) + this.networkStore.updateState({network: 'loading'}) + this.switchNetwork({ + rpcUrl: rpcTarget, + }) + this.once('claimed', () => { + this.providerStore.updateState({provider: {type, rpcTarget}}) + console.log('CLAIMED') + this.lookupNetwork() + }) + + } + + useEtherscanProvider () { + this.setProviderType('etherscan') + } + + getProvider () { + return this.providerStore.getState().provider + } + + getRpcAddressForType (type) { + const provider = this.getProvider() + switch (type) { + + case 'mainnet': + return MAINNET_RPC + + case 'testnet': + return TESTNET_RPC + + case 'morden': + return MORDEN_RPC + + case 'kovan': + return KOVAN_RPC + + case 'rinkeby': + return RINKEBY_RPC + + default: + return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC + } + } + + claim () { + this._claimed += 1 + if (this._claimed === this.listenerCount('networkSwitch')) { + this.emit('claimed') + this._claimed = 0 + } + } +} diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index 5b3c80e40..bb699ca8b 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -41,6 +41,12 @@ class KeyringController extends EventEmitter { this.getNetwork = opts.getNetwork } + setEthStore (ethStore) { + delete this.ethStore + this.ethStore = ethStore + return this.setupAccounts() + } + // Full Update // returns Promise( @object state ) // diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ab9410842..1098cc474 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,14 +1,6 @@ -const MetamaskConfig = require('../config.js') const ethUtil = require('ethereumjs-util') const normalize = require('eth-sig-util').normalize -const TESTNET_RPC = MetamaskConfig.network.testnet -const MAINNET_RPC = MetamaskConfig.network.mainnet -const MORDEN_RPC = MetamaskConfig.network.morden -const KOVAN_RPC = MetamaskConfig.network.kovan -const RINKEBY_RPC = MetamaskConfig.network.rinkeby - - /* The config-manager is a convenience object * wrapping a pojo-migrator. * @@ -35,36 +27,6 @@ ConfigManager.prototype.getConfig = function () { return data.config } -ConfigManager.prototype.setRpcTarget = function (rpcUrl) { - var config = this.getConfig() - config.provider = { - type: 'rpc', - rpcTarget: rpcUrl, - } - this.setConfig(config) -} - -ConfigManager.prototype.setProviderType = function (type) { - var config = this.getConfig() - config.provider = { - type: type, - } - this.setConfig(config) -} - -ConfigManager.prototype.useEtherscanProvider = function () { - var config = this.getConfig() - config.provider = { - type: 'etherscan', - } - this.setConfig(config) -} - -ConfigManager.prototype.getProvider = function () { - var config = this.getConfig() - return config.provider -} - ConfigManager.prototype.setData = function (data) { this.store.putState(data) } @@ -139,35 +101,6 @@ ConfigManager.prototype.getSeedWords = function () { return data.seedWords } -ConfigManager.prototype.getCurrentRpcAddress = function () { - var provider = this.getProvider() - if (!provider) return null - switch (provider.type) { - - case 'mainnet': - return MAINNET_RPC - - case 'testnet': - return TESTNET_RPC - - case 'morden': - return MORDEN_RPC - - case 'kovan': - return KOVAN_RPC - - case 'rinkeby': - return RINKEBY_RPC - - default: - return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC - } -} - -// -// Tx -// - ConfigManager.prototype.getTxList = function () { var data = this.getData() if (data.transactions !== undefined) { diff --git a/app/scripts/lib/tx-utils.js b/app/scripts/lib/tx-utils.js index 084ca3721..76b311653 100644 --- a/app/scripts/lib/tx-utils.js +++ b/app/scripts/lib/tx-utils.js @@ -1,5 +1,4 @@ const async = require('async') -const EthQuery = require('eth-query') const ethUtil = require('ethereumjs-util') const Transaction = require('ethereumjs-tx') const normalize = require('eth-sig-util').normalize @@ -7,15 +6,14 @@ const BN = ethUtil.BN /* tx-utils are utility methods for Transaction manager -its passed a provider and that is passed to ethquery +its passed ethquery and used to do things like calculate gas of a tx. */ module.exports = class txProviderUtils { - constructor (provider) { - this.provider = provider - this.query = new EthQuery(provider) + constructor (ethQuery) { + this.query = ethQuery } analyzeGasUsage (txMeta, cb) { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 175602ec1..71293d05f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -7,9 +7,9 @@ const ObservableStore = require('obs-store') const EthStore = require('./lib/eth-store') const EthQuery = require('eth-query') const streamIntoProvider = require('web3-stream-provider/handler') -const MetaMaskProvider = require('web3-provider-engine/zero.js') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const KeyringController = require('./keyring-controller') +const NetworkController = require('./controllers/network') const PreferencesController = require('./controllers/preferences') const CurrencyController = require('./controllers/currency') const NoticeController = require('./notice-controller') @@ -40,8 +40,7 @@ module.exports = class MetamaskController extends EventEmitter { this.store = new ObservableStore(initState) // network store - this.networkStore = new ObservableStore({ network: 'loading' }) - + this.networkController = new NetworkController(initState.NetworkController) // config manager this.configManager = new ConfigManager({ store: this.store, @@ -62,7 +61,7 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.verifyNetwork.bind(this)) + this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) // eth data query tools this.ethQuery = new EthQuery(this.provider) @@ -75,7 +74,7 @@ module.exports = class MetamaskController extends EventEmitter { this.keyringController = new KeyringController({ initState: initState.KeyringController, ethStore: this.ethStore, - getNetwork: this.getNetworkState.bind(this), + getNetwork: this.networkController.getNetworkState.bind(this.networkController), }) this.keyringController.on('newAccount', (address) => { this.preferencesController.setSelectedAddress(address) @@ -92,10 +91,10 @@ module.exports = class MetamaskController extends EventEmitter { // tx mgmt this.txManager = new TxManager({ initState: initState.TransactionManager, - networkStore: this.networkStore, + networkStore: this.networkController.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, - getNetwork: this.getNetworkState.bind(this), + getNetwork: this.networkController.getNetworkState.bind(this), signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.provider, @@ -112,8 +111,34 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController = new ShapeShiftController({ initState: initState.ShapeShiftController, }) + this.networkController.on('networkSwitch', (providerUtil, claimed) => { + delete this.provider + delete this.ethQuery + delete this.ethStore + console.log('order:@? 1') + this.provider = providerUtil.provider + this.provider.on('block', this.logBlock.bind(this)) + this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) - this.lookupNetwork() + this.ethQuery = providerUtil.ethQuery + this.ethStore = new EthStore({ + provider: this.provider, + blockTracker: this.provider, + }) + this.provider.once('block', claimed) + }) + this.networkController.on('networkSwitch', (_, claimed) => { + console.log('order:@? 2') + this.txManager.setupProviderAndEthQuery({ + provider: this.provider, + blockTracker: this.provider, + ethQuery: this.ethQuery, + }) + this.keyringController.setEthStore(this.ethStore) + .then(claimed) + }) + + this.networkController.lookupNetwork() this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() this.publicConfigStore = this.initPublicConfigStore() @@ -140,9 +165,12 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController.store.subscribe((state) => { this.store.updateState({ ShapeShiftController: state }) }) + this.networkController.providerStore.subscribe((state) => { + this.store.updateState({ NetworkController: state }) + }) // manual mem state subscriptions - this.networkStore.subscribe(this.sendUpdate.bind(this)) + this.networkController.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -160,12 +188,12 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - const provider = MetaMaskProvider({ + this.networkController.initializeProvider({ static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, }, - rpcUrl: this.configManager.getCurrentRpcAddress(), + rpcUrl: this.networkController.getCurrentRpcAddress(), // account mgmt getAccounts: (cb) => { const isUnlocked = this.keyringController.memStore.getState().isUnlocked @@ -185,7 +213,7 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), }) - return provider + return this.networkController.provider } initPublicConfigStore () { @@ -221,7 +249,7 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.networkStore.getState(), + this.networkController.getState(), this.ethStore.getState(), this.txManager.memStore.getState(), this.messageManager.memStore.getState(), @@ -255,8 +283,8 @@ module.exports = class MetamaskController extends EventEmitter { return { // etc getState: (cb) => cb(null, this.getState()), - setProviderType: this.setProviderType.bind(this), - useEtherscanProvider: this.useEtherscanProvider.bind(this), + setProviderType: this.networkController.setProviderType.bind(this.networkController), + useEtherscanProvider: this.networkController.useEtherscanProvider.bind(this.networkController), setCurrentCurrency: this.setCurrentCurrency.bind(this), markAccountsFound: this.markAccountsFound.bind(this), // coinbase @@ -592,7 +620,7 @@ module.exports = class MetamaskController extends EventEmitter { // Log blocks logBlock (block) { log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) - this.verifyNetwork() + this.networkController.verifyNetwork() } setCurrentCurrency (currencyCode, cb) { @@ -612,7 +640,7 @@ module.exports = class MetamaskController extends EventEmitter { buyEth (address, amount) { if (!amount) amount = '5' - const network = this.getNetworkState() + const network = this.networkController.getNetworkState() const url = getBuyEthUrl({ network, address, amount }) if (url) this.platform.openWindow({ url }) } @@ -620,69 +648,21 @@ module.exports = class MetamaskController extends EventEmitter { createShapeShiftTx (depositAddress, depositType) { this.shapeshiftController.createShapeShiftTx(depositAddress, depositType) } - - // - // network - // - - verifyNetwork () { - // Check network when restoring connectivity: - if (this.isNetworkLoading()) this.lookupNetwork() - } +// network setDefaultRpc () { - this.configManager.setRpcTarget('http://localhost:8545') - this.platform.reload() - this.lookupNetwork() + this.networkController.setRpcTarget('http://localhost:8545') return Promise.resolve('http://localhost:8545') } setCustomRpc (rpcTarget, rpcList) { - this.configManager.setRpcTarget(rpcTarget) + this.networkController.setRpcTarget(rpcTarget) + return this.preferencesController.updateFrequentRpcList(rpcTarget) - .then(() => { - this.platform.reload() - this.lookupNetwork() - return Promise.resolve(rpcTarget) - }) - } - - setProviderType (type) { - this.configManager.setProviderType(type) - this.platform.reload() - this.lookupNetwork() - } - - useEtherscanProvider () { - this.configManager.useEtherscanProvider() - this.platform.reload() - } - - getNetworkState () { - return this.networkStore.getState().network - } - - setNetworkState (network) { - return this.networkStore.updateState({ network }) - } - - isNetworkLoading () { - return this.getNetworkState() === 'loading' - } - - lookupNetwork (err) { - if (err) { - this.setNetworkState('loading') - } - - this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { - if (err) { - this.setNetworkState('loading') - return - } - log.info('web3.getNetwork returned ' + network) - this.setNetworkState(network) + .then(() => { + return Promise.resolve(rpcTarget) }) } + } diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js index 9f267160f..1e15128f9 100644 --- a/app/scripts/transaction-manager.js +++ b/app/scripts/transaction-manager.js @@ -4,7 +4,6 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const EthQuery = require('eth-query') const TxProviderUtil = require('./lib/tx-utils') const createId = require('./lib/random-id') @@ -18,11 +17,11 @@ module.exports = class TransactionManager extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.txHistoryLimit = opts.txHistoryLimit - this.provider = opts.provider - this.blockTracker = opts.blockTracker - this.query = new EthQuery(this.provider) - this.txProviderUtils = new TxProviderUtil(this.provider) - this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + this.setupProviderAndEthQuery({ + provider: opts.provider, + blockTracker: opts.blockTracker, + ethQuery: opts.ethQuery, + }) this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -41,6 +40,20 @@ module.exports = class TransactionManager extends EventEmitter { return this.networkStore.getState().network } + setupProviderAndEthQuery ({provider, blockTracker, ethQuery}) { + if (this.provider) { + delete this.provider + delete this.blockTracker + delete this.query + delete this.txProviderUtils + } + this.provider = provider + this.query = ethQuery + this.txProviderUtils = new TxProviderUtil(ethQuery) + blockTracker ? this.blockTracker = blockTracker : this.blockTracker = provider + this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + } + getSelectedAddress () { return this.preferencesStore.getState().selectedAddress } From e08c1541e5a91d7958b15753982d22066c1a0a7d Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 01:55:20 -0400 Subject: [PATCH 2/6] Add a migration for the network controller --- app/scripts/migrations/014.js | 34 +++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + 2 files changed, 35 insertions(+) create mode 100644 app/scripts/migrations/014.js diff --git a/app/scripts/migrations/014.js b/app/scripts/migrations/014.js new file mode 100644 index 000000000..0fe92125b --- /dev/null +++ b/app/scripts/migrations/014.js @@ -0,0 +1,34 @@ +const version = 14 + +/* + +This migration removes provider from config and moves it too NetworkController. + +*/ + +const clone = require('clone') + +module.exports = { + version, + + migrate: function (originalVersionedData) { + const versionedData = clone(originalVersionedData) + versionedData.meta.version = version + try { + const state = versionedData.data + const newState = transformState(state) + versionedData.data = newState + } catch (err) { + console.warn(`MetaMask Migration #${version}` + err.stack) + } + return Promise.resolve(versionedData) + }, +} + +function transformState (state) { + const newState = state + newState.NetworkController = {} + newState.NetworkController.provider = newState.config.provider + delete newState.config.provider + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 3a95cf88e..fb1ad7863 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -24,4 +24,5 @@ module.exports = [ require('./011'), require('./012'), require('./013'), + require('./014'), ] From 529304c005318852b60bb93846a58d6eb3da2066 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 01:56:10 -0400 Subject: [PATCH 3/6] Wrap the provider in a proxy --- app/scripts/controllers/network.js | 120 +++++++++++------------- app/scripts/controllers/transactions.js | 27 ++---- app/scripts/keyring-controller.js | 6 -- app/scripts/lib/config-manager.js | 5 +- app/scripts/metamask-controller.js | 42 +-------- 5 files changed, 69 insertions(+), 131 deletions(-) diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 82eabb573..97c2ccbc2 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -3,21 +3,29 @@ const MetaMaskProvider = require('web3-provider-engine/zero.js') const ObservableStore = require('obs-store') const extend = require('xtend') const EthQuery = require('eth-query') -const MetamaskConfig = require('../config.js') - -const TESTNET_RPC = MetamaskConfig.network.testnet -const MAINNET_RPC = MetamaskConfig.network.mainnet -const MORDEN_RPC = MetamaskConfig.network.morden -const KOVAN_RPC = MetamaskConfig.network.kovan -const RINKEBY_RPC = MetamaskConfig.network.rinkeby +const RPC_ADDRESS_LIST = require('../config.js').network module.exports = class NetworkController extends EventEmitter { constructor (providerOpts) { super() this.networkStore = new ObservableStore({ network: 'loading' }) - providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type) + providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type, providerOpts.provider) this.providerStore = new ObservableStore(providerOpts) - this._claimed = 0 + this.store = new ObservableStore(extend(this.networkStore.getState(), this.providerStore.getState())) + + this._providerListners = {} + + this.networkStore.subscribe((state) => this.store.updateState(state)) + this.providerStore.subscribe((state) => this.store.updateState(state)) + this.on('networkSwitch', this.lookupNetwork) + } + + get provider () { + return this._proxy + } + + set provider (provider) { + this._provider = provider } getState () { @@ -29,29 +37,36 @@ module.exports = class NetworkController extends EventEmitter { initializeProvider (opts) { this.providerConfig = opts - this.provider = MetaMaskProvider(opts) + this._provider = MetaMaskProvider(opts) + this._proxy = new Proxy(this._provider, { + get: (obj, name) => { + if (name === 'on') return this._on.bind(this) + return this._provider[name] + }, + set: (obj, name, value) => { + this._provider[name] = value + }, + }) + this.provider.on('block', this._logBlock.bind(this)) + this.provider.on('error', this.verifyNetwork.bind(this)) this.ethQuery = new EthQuery(this.provider) this.lookupNetwork() - return Promise.resolve(this.provider) - } - switchNetwork (providerConfig) { - delete this.provider - delete this.ethQuery - const newConfig = extend(this.providerConfig, providerConfig) - this.providerConfig = newConfig - this.provider = MetaMaskProvider(newConfig) - this.ethQuery = new EthQuery(this.provider) - this.emit('networkSwitch', { - provider: this.provider, - ethQuery: this.ethQuery, - }, this.claim.bind(this)) + return this.provider } - subscribe (cb) { - this.networkStore.subscribe(cb) - this.providerStore.subscribe(cb) + switchNetwork (providerConfig) { + const newConfig = extend(this.providerConfig, providerConfig) + this.providerConfig = newConfig + + this.provider = MetaMaskProvider(newConfig) + // apply the listners created by other controllers + Object.keys(this._providerListners).forEach((key) => { + this._providerListners[key].forEach((handler) => this._provider.addListener(key, handler)) + }) + this.emit('networkSwitch', this.provider) } + verifyNetwork () { // Check network when restoring connectivity: if (this.isNetworkLoading()) this.lookupNetwork() @@ -74,7 +89,6 @@ module.exports = class NetworkController extends EventEmitter { this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') - log.info('web3.getNetwork returned ' + network) this.setNetworkState(network) }) @@ -102,51 +116,27 @@ module.exports = class NetworkController extends EventEmitter { this.switchNetwork({ rpcUrl: rpcTarget, }) - this.once('claimed', () => { - this.providerStore.updateState({provider: {type, rpcTarget}}) - console.log('CLAIMED') - this.lookupNetwork() - }) - - } - - useEtherscanProvider () { - this.setProviderType('etherscan') + this.providerStore.updateState({provider: {type, rpcTarget}}) } getProvider () { return this.providerStore.getState().provider } - getRpcAddressForType (type) { - const provider = this.getProvider() - switch (type) { - - case 'mainnet': - return MAINNET_RPC - - case 'testnet': - return TESTNET_RPC - - case 'morden': - return MORDEN_RPC - - case 'kovan': - return KOVAN_RPC - - case 'rinkeby': - return RINKEBY_RPC - - default: - return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC - } + getRpcAddressForType (type, provider = this.getProvider()) { + console.log(`#getRpcAddressForType: ${type}`) + if (type in RPC_ADDRESS_LIST) return RPC_ADDRESS_LIST[type] + return provider && provider.rpcTarget ? provider.rpcTarget : RPC_ADDRESS_LIST['rinkeby'] } - claim () { - this._claimed += 1 - if (this._claimed === this.listenerCount('networkSwitch')) { - this.emit('claimed') - this._claimed = 0 - } + _logBlock (block) { + log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) + this.verifyNetwork() + } + + _on (event, handler) { + if (!this._providerListners[event]) this._providerListners[event] = [] + this._providerListners[event].push(handler) + this._provider.on(event, handler) } } diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index cfeeab6e6..b9bea2f1c 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -4,7 +4,6 @@ const extend = require('xtend') const Semaphore = require('semaphore') const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') -const EthQuery = require('eth-query') const TxProviderUtil = require('../lib/tx-utils') const createId = require('../lib/random-id') @@ -18,11 +17,13 @@ module.exports = class TransactionManager extends EventEmitter { this.networkStore = opts.networkStore || new ObservableStore({}) this.preferencesStore = opts.preferencesStore || new ObservableStore({}) this.txHistoryLimit = opts.txHistoryLimit - this.setupProviderAndEthQuery({ - provider: opts.provider, - blockTracker: opts.blockTracker, - ethQuery: opts.ethQuery, - }) + this.provider = opts.provider + this.blockTracker = opts.blockTracker + this.query = opts.ethQuery + this.txProviderUtils = new TxProviderUtil(this.query) + this.networkStore.subscribe((_) => this.blockTracker.on('block', this.checkForTxInBlock.bind(this))) + this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) + this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -41,20 +42,6 @@ module.exports = class TransactionManager extends EventEmitter { return this.networkStore.getState().network } - setupProviderAndEthQuery ({provider, blockTracker, ethQuery}) { - if (this.provider) { - delete this.provider - delete this.blockTracker - delete this.query - delete this.txProviderUtils - } - this.provider = provider - this.query = ethQuery - this.txProviderUtils = new TxProviderUtil(ethQuery) - blockTracker ? this.blockTracker = blockTracker : this.blockTracker = provider - this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) - } - getSelectedAddress () { return this.preferencesStore.getState().selectedAddress } diff --git a/app/scripts/keyring-controller.js b/app/scripts/keyring-controller.js index bb699ca8b..5b3c80e40 100644 --- a/app/scripts/keyring-controller.js +++ b/app/scripts/keyring-controller.js @@ -41,12 +41,6 @@ class KeyringController extends EventEmitter { this.getNetwork = opts.getNetwork } - setEthStore (ethStore) { - delete this.ethStore - this.ethStore = ethStore - return this.setupAccounts() - } - // Full Update // returns Promise( @object state ) // diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 6ca9bd9ea..fee8423fa 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,14 +1,13 @@ const ethUtil = require('ethereumjs-util') const normalize = require('eth-sig-util').normalize +const MetamaskConfig = require('../config.js') + -<<<<<<< HEAD -======= const MAINNET_RPC = MetamaskConfig.network.mainnet const ROPSTEN_RPC = MetamaskConfig.network.ropsten const KOVAN_RPC = MetamaskConfig.network.kovan const RINKEBY_RPC = MetamaskConfig.network.rinkeby ->>>>>>> master /* The config-manager is a convenience object * wrapping a pojo-migrator. * diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index b93f627bb..ef82da0d3 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -40,6 +40,7 @@ module.exports = class MetamaskController extends EventEmitter { this.store = new ObservableStore(initState) // network store + this.networkController = new NetworkController(initState.NetworkController) // config manager this.configManager = new ConfigManager({ @@ -60,12 +61,11 @@ module.exports = class MetamaskController extends EventEmitter { // rpc provider this.provider = this.initializeProvider() - this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ + network: this.networkController.networkStore, provider: this.provider, blockTracker: this.provider, }) @@ -111,32 +111,6 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController = new ShapeShiftController({ initState: initState.ShapeShiftController, }) - this.networkController.on('networkSwitch', (providerUtil, claimed) => { - delete this.provider - delete this.ethQuery - delete this.ethStore - console.log('order:@? 1') - this.provider = providerUtil.provider - this.provider.on('block', this.logBlock.bind(this)) - this.provider.on('error', this.networkController.verifyNetwork.bind(this.networkController)) - - this.ethQuery = providerUtil.ethQuery - this.ethStore = new EthStore({ - provider: this.provider, - blockTracker: this.provider, - }) - this.provider.once('block', claimed) - }) - this.networkController.on('networkSwitch', (_, claimed) => { - console.log('order:@? 2') - this.txManager.setupProviderAndEthQuery({ - provider: this.provider, - blockTracker: this.provider, - ethQuery: this.ethQuery, - }) - this.keyringController.setEthStore(this.ethStore) - .then(claimed) - }) this.networkController.lookupNetwork() this.messageManager = new MessageManager() @@ -170,7 +144,7 @@ module.exports = class MetamaskController extends EventEmitter { }) // manual mem state subscriptions - this.networkController.subscribe(this.sendUpdate.bind(this)) + this.networkController.store.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) @@ -188,7 +162,7 @@ module.exports = class MetamaskController extends EventEmitter { // initializeProvider () { - this.networkController.initializeProvider({ + return this.networkController.initializeProvider({ static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, @@ -213,7 +187,6 @@ module.exports = class MetamaskController extends EventEmitter { // new style msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), }) - return this.networkController.provider } initPublicConfigStore () { @@ -249,7 +222,7 @@ module.exports = class MetamaskController extends EventEmitter { { isInitialized, }, - this.networkController.getState(), + this.networkController.store.getState(), this.ethStore.getState(), this.txController.memStore.getState(), this.messageManager.memStore.getState(), @@ -284,7 +257,6 @@ module.exports = class MetamaskController extends EventEmitter { // etc getState: (cb) => cb(null, this.getState()), setProviderType: this.networkController.setProviderType.bind(this.networkController), - useEtherscanProvider: this.networkController.useEtherscanProvider.bind(this.networkController), setCurrentCurrency: this.setCurrentCurrency.bind(this), markAccountsFound: this.markAccountsFound.bind(this), // coinbase @@ -618,10 +590,6 @@ module.exports = class MetamaskController extends EventEmitter { // // Log blocks - logBlock (block) { - log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) - this.networkController.verifyNetwork() - } setCurrentCurrency (currencyCode, cb) { try { From 243eeff7cb0d4c5d613a9250d234f81fdccbbf15 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 02:12:28 -0400 Subject: [PATCH 4/6] Fix for tests --- app/scripts/controllers/network.js | 84 +++++++++++-------------- app/scripts/controllers/transactions.js | 4 +- app/scripts/first-time-state.js | 3 +- app/scripts/lib/config-manager.js | 29 +++++++++ app/scripts/metamask-controller.js | 3 +- test/unit/network-contoller-test.js | 74 ++++++++++++++++++++++ test/unit/tx-controller-test.js | 4 +- test/unit/tx-utils-test.js | 6 +- 8 files changed, 150 insertions(+), 57 deletions(-) create mode 100644 test/unit/network-contoller-test.js diff --git a/app/scripts/controllers/network.js b/app/scripts/controllers/network.js index 97c2ccbc2..4fdd92921 100644 --- a/app/scripts/controllers/network.js +++ b/app/scripts/controllers/network.js @@ -1,23 +1,23 @@ const EventEmitter = require('events') const MetaMaskProvider = require('web3-provider-engine/zero.js') const ObservableStore = require('obs-store') +const ComposedStore = require('obs-store/lib/composed') const extend = require('xtend') const EthQuery = require('eth-query') const RPC_ADDRESS_LIST = require('../config.js').network +const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] module.exports = class NetworkController extends EventEmitter { - constructor (providerOpts) { + constructor (config) { super() - this.networkStore = new ObservableStore({ network: 'loading' }) - providerOpts.provider.rpcTarget = this.getRpcAddressForType(providerOpts.provider.type, providerOpts.provider) - this.providerStore = new ObservableStore(providerOpts) - this.store = new ObservableStore(extend(this.networkStore.getState(), this.providerStore.getState())) + this.networkStore = new ObservableStore('loading') + config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) + this.providerStore = new ObservableStore(config.provider) + this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) + this._providerListeners = {} - this._providerListners = {} - - this.networkStore.subscribe((state) => this.store.updateState(state)) - this.providerStore.subscribe((state) => this.store.updateState(state)) - this.on('networkSwitch', this.lookupNetwork) + this.on('networkDidChange', this.lookupNetwork) + this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) } get provider () { @@ -28,15 +28,8 @@ module.exports = class NetworkController extends EventEmitter { this._provider = provider } - getState () { - return extend({}, - this.networkStore.getState(), - this.providerStore.getState() - ) - } - initializeProvider (opts) { - this.providerConfig = opts + this.providerInit = opts this._provider = MetaMaskProvider(opts) this._proxy = new Proxy(this._provider, { get: (obj, name) => { @@ -54,16 +47,18 @@ module.exports = class NetworkController extends EventEmitter { return this.provider } - switchNetwork (providerConfig) { - const newConfig = extend(this.providerConfig, providerConfig) - this.providerConfig = newConfig + switchNetwork (providerInit) { + this.setNetworkState('loading') + const newInit = extend(this.providerInit, providerInit) + this.providerInit = newInit - this.provider = MetaMaskProvider(newConfig) + this._provider.removeAllListeners() + this.provider = MetaMaskProvider(newInit) // apply the listners created by other controllers - Object.keys(this._providerListners).forEach((key) => { - this._providerListners[key].forEach((handler) => this._provider.addListener(key, handler)) + Object.keys(this._providerListeners).forEach((key) => { + this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) }) - this.emit('networkSwitch', this.provider) + this.emit('networkDidChange') } @@ -73,20 +68,18 @@ module.exports = class NetworkController extends EventEmitter { } getNetworkState () { - return this.networkStore.getState().network + return this.networkStore.getState() } setNetworkState (network) { - return this.networkStore.updateState({ network }) + return this.networkStore.putState(network) } isNetworkLoading () { return this.getNetworkState() === 'loading' } - lookupNetwork (err) { - if (err) this.setNetworkState('loading') - + lookupNetwork () { this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { if (err) return this.setNetworkState('loading') log.info('web3.getNetwork returned ' + network) @@ -96,37 +89,30 @@ module.exports = class NetworkController extends EventEmitter { setRpcTarget (rpcUrl) { this.providerStore.updateState({ - provider: { - type: 'rpc', - rpcTarget: rpcUrl, - }, + type: 'rpc', + rpcTarget: rpcUrl, }) } getCurrentRpcAddress () { - var provider = this.getProvider() + const provider = this.getProviderConfig() if (!provider) return null return this.getRpcAddressForType(provider.type) } setProviderType (type) { - if (type === this.getProvider().type) return + if (type === this.getProviderConfig().type) return const rpcTarget = this.getRpcAddressForType(type) - this.networkStore.updateState({network: 'loading'}) - this.switchNetwork({ - rpcUrl: rpcTarget, - }) - this.providerStore.updateState({provider: {type, rpcTarget}}) + this.providerStore.updateState({type, rpcTarget}) } - getProvider () { - return this.providerStore.getState().provider + getProviderConfig () { + return this.providerStore.getState() } - getRpcAddressForType (type, provider = this.getProvider()) { - console.log(`#getRpcAddressForType: ${type}`) - if (type in RPC_ADDRESS_LIST) return RPC_ADDRESS_LIST[type] - return provider && provider.rpcTarget ? provider.rpcTarget : RPC_ADDRESS_LIST['rinkeby'] + getRpcAddressForType (type, provider = this.getProviderConfig()) { + if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] + return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC } _logBlock (block) { @@ -135,8 +121,8 @@ module.exports = class NetworkController extends EventEmitter { } _on (event, handler) { - if (!this._providerListners[event]) this._providerListners[event] = [] - this._providerListners[event].push(handler) + if (!this._providerListeners[event]) this._providerListeners[event] = [] + this._providerListeners[event].push(handler) this._provider.on(event, handler) } } diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index b9bea2f1c..2ebeed3ab 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -21,9 +21,7 @@ module.exports = class TransactionManager extends EventEmitter { this.blockTracker = opts.blockTracker this.query = opts.ethQuery this.txProviderUtils = new TxProviderUtil(this.query) - this.networkStore.subscribe((_) => this.blockTracker.on('block', this.checkForTxInBlock.bind(this))) this.blockTracker.on('block', this.checkForTxInBlock.bind(this)) - this.signEthTx = opts.signTransaction this.nonceLock = Semaphore(1) @@ -39,7 +37,7 @@ module.exports = class TransactionManager extends EventEmitter { } getNetwork () { - return this.networkStore.getState().network + return this.networkStore.getState() } getSelectedAddress () { diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 29ec1d8d3..dc7788311 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -3,7 +3,8 @@ // module.exports = { - config: { + config: {}, + NetworkController: { provider: { type: 'rinkeby', }, diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index fee8423fa..9c0dffe9c 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -107,6 +107,35 @@ ConfigManager.prototype.getSeedWords = function () { var data = this.getData() return data.seedWords } +ConfigManager.prototype.setRpcTarget = function (rpcUrl) { + var config = this.getConfig() + config.provider = { + type: 'rpc', + rpcTarget: rpcUrl, + } + this.setConfig(config) +} + +ConfigManager.prototype.setProviderType = function (type) { + var config = this.getConfig() + config.provider = { + type: type, + } + this.setConfig(config) +} + +ConfigManager.prototype.useEtherscanProvider = function () { + var config = this.getConfig() + config.provider = { + type: 'etherscan', + } + this.setConfig(config) +} + +ConfigManager.prototype.getProvider = function () { + var config = this.getConfig() + return config.provider +} ConfigManager.prototype.getCurrentRpcAddress = function () { var provider = this.getProvider() diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ef82da0d3..c0ad1e93b 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -65,7 +65,6 @@ module.exports = class MetamaskController extends EventEmitter { // eth data query tools this.ethQuery = new EthQuery(this.provider) this.ethStore = new EthStore({ - network: this.networkController.networkStore, provider: this.provider, blockTracker: this.provider, }) @@ -139,7 +138,7 @@ module.exports = class MetamaskController extends EventEmitter { this.shapeshiftController.store.subscribe((state) => { this.store.updateState({ ShapeShiftController: state }) }) - this.networkController.providerStore.subscribe((state) => { + this.networkController.store.subscribe((state) => { this.store.updateState({ NetworkController: state }) }) diff --git a/test/unit/network-contoller-test.js b/test/unit/network-contoller-test.js new file mode 100644 index 000000000..183e69cab --- /dev/null +++ b/test/unit/network-contoller-test.js @@ -0,0 +1,74 @@ +const EventEmitter = require('events') +const assert = require('assert') +const NetworkController = require('../../app/scripts/controllers/network') + +describe('# Network Controller', function () { + let networkController + + beforeEach(function () { + networkController = new NetworkController({ + provider: { + type: 'rinkeby', + }, + }) + // stub out provider + networkController._provider = new EventEmitter() + networkController.providerInit = { + getAccounts: () => {}, + } + + networkController.ethQuery = new Proxy({}, { + get: (obj, name) => { + return () => {} + }, + }) + }) + describe('network', function () { + describe('#provider', function() { + it('provider should be updatable without reassignment', function () { + networkController.initializeProvider(networkController.providerInit) + const provider = networkController.provider + networkController._provider = {test: true} + assert.ok(provider.test) + }) + }) + describe('#getNetworkState', function () { + it('should return loading when new', function () { + let networkState = networkController.getNetworkState() + assert.equal(networkState, 'loading', 'network is loading') + }) + }) + + describe('#setNetworkState', function () { + it('should update the network', function () { + networkController.setNetworkState(1) + let networkState = networkController.getNetworkState() + assert.equal(networkState, 1, 'network is 1') + }) + }) + + describe('#getRpcAddressForType', function () { + it('should return the right rpc address', function () { + let rpcTarget = networkController.getRpcAddressForType('mainnet') + assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') + }) + }) + describe('#setProviderType', function () { + it('should update provider.type', function () { + networkController.setProviderType('mainnet') + const type = networkController.getProviderConfig().type + assert.equal(type, 'mainnet', 'provider type is updated') + }) + it('should set the network to loading', function () { + networkController.setProviderType('mainnet') + const loading = networkController.isNetworkLoading() + assert.ok(loading, 'network is loading') + }) + it('should set the right rpcTarget', function () { + networkController.setProviderType('mainnet') + const rpcTarget = networkController.getProviderConfig().rpcTarget + assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress') + }) + }) + }) +}) diff --git a/test/unit/tx-controller-test.js b/test/unit/tx-controller-test.js index d4e8d79f0..711e1ea79 100644 --- a/test/unit/tx-controller-test.js +++ b/test/unit/tx-controller-test.js @@ -2,6 +2,7 @@ const assert = require('assert') const EventEmitter = require('events') const ethUtil = require('ethereumjs-util') const EthTx = require('ethereumjs-tx') +const EthQuery = require('eth-query') const ObservableStore = require('obs-store') const clone = require('clone') const sinon = require('sinon') @@ -16,9 +17,10 @@ describe('Transaction Controller', function () { beforeEach(function () { txController = new TransactionController({ - networkStore: new ObservableStore({ network: currentNetworkId }), + networkStore: new ObservableStore(currentNetworkId), txHistoryLimit: 10, blockTracker: new EventEmitter(), + ethQuery: new EthQuery(new EventEmitter()), signTransaction: (ethTx) => new Promise((resolve) => { ethTx.sign(privKey) resolve() diff --git a/test/unit/tx-utils-test.js b/test/unit/tx-utils-test.js index 57d4638a0..7ace1f587 100644 --- a/test/unit/tx-utils-test.js +++ b/test/unit/tx-utils-test.js @@ -9,7 +9,11 @@ describe('txUtils', function () { let txUtils before(function () { - txUtils = new TxUtils() + txUtils = new TxUtils(new Proxy({}, { + get: (obj, name) => { + return () => {} + }, + })) }) describe('chain Id', function () { From c5d74e6421486140eba801375eaeeb3c9c0a3091 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 20:06:19 -0400 Subject: [PATCH 5/6] include ethQuery in txController --- app/scripts/metamask-controller.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index c0ad1e93b..a7eb3d056 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -97,6 +97,7 @@ module.exports = class MetamaskController extends EventEmitter { signTransaction: this.keyringController.signTransaction.bind(this.keyringController), provider: this.provider, blockTracker: this.provider, + ethQuery: this.ethQuery, }) // notices From c1239608a62a4283d138b1a6586653ac791eb655 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Tue, 23 May 2017 20:27:51 -0400 Subject: [PATCH 6/6] add to CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 371b348ca..85dc9ce81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Current Master +- Now when switching networks the extension does not restart + ## 3.6.5 2017-5-17 - Fix bug where edited gas parameters would not take effect.