From 1772d34e947ec5e940cc99f53ff0a102e048d69c Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 11 May 2017 10:10:50 +0200 Subject: fix migrator --- app/scripts/lib/migrator/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index c40c347b5..caa0ef318 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -13,10 +13,10 @@ class Migrator { // run all pending migrations on meta in place migrateData (versionedData = this.generateInitialState()) { const remaining = this.migrations.filter(migrationIsPending) - + if (remaining.length === 0) return versionedData return ( asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) - .then(() => versionedData) + .then((migratedData) => migratedData.pop()) ) // migration is "pending" if hit has a higher @@ -27,14 +27,13 @@ class Migrator { } runMigration (versionedData, migration) { - return ( - migration.migrate(versionedData) - .then((versionedData) => { - if (!versionedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) - if (migration.version !== undefined && versionedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) - return Promise.resolve(versionedData) + return migration.migrate(versionedData) + .then((migratedData) => { + if (!migratedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) + if (migration.version !== undefined && migratedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) + + return Promise.resolve(migratedData) }) - ) } generateInitialState (initState) { -- cgit v1.2.3 From 6c01b26845cfb8e113d3016ebd595a9098623f62 Mon Sep 17 00:00:00 2001 From: frankiebee Date: Thu, 11 May 2017 10:46:17 +0200 Subject: use asyncQ.waterfall instead of asyncQ.eachSeries --- app/scripts/lib/migrator/index.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index caa0ef318..0bf88dbec 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -14,9 +14,15 @@ class Migrator { migrateData (versionedData = this.generateInitialState()) { const remaining = this.migrations.filter(migrationIsPending) if (remaining.length === 0) return versionedData + + const migrations = remaining.map((migration, i) => { + if (i === 0) return this.runMigration.bind(this, migration, versionedData) + return this.runMigration.bind(this, migration) + }) + return ( - asyncQ.eachSeries(remaining, (migration) => this.runMigration(versionedData, migration)) - .then((migratedData) => migratedData.pop()) + asyncQ.waterfall(migrations) + .then((migratedData) => Promise.resolve(migratedData)) ) // migration is "pending" if hit has a higher @@ -26,10 +32,10 @@ class Migrator { } } - runMigration (versionedData, migration) { + runMigration (migration, versionedData) { return migration.migrate(versionedData) .then((migratedData) => { - if (!migratedData.data) return Promise.reject(new Error('Migrator - Migration returned empty data')) + if (!migratedData.data) return Promise.reject(new Error('Migrator - migration returned empty data')) if (migration.version !== undefined && migratedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) return Promise.resolve(migratedData) -- cgit v1.2.3 From 70a328e028230f8bffbd88a28d961fde6a4b819f Mon Sep 17 00:00:00 2001 From: kumavis Date: Thu, 11 May 2017 18:15:59 -0700 Subject: migrator - cleaner migration runner with es7 --- app/scripts/lib/migrator/index.js | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index 0bf88dbec..ed07a0c60 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -1,47 +1,35 @@ -const asyncQ = require('async-q') - class Migrator { constructor (opts = {}) { const migrations = opts.migrations || [] + // sort migrations by version this.migrations = migrations.sort((a, b) => a.version - b.version) + // grab migration with highest version const lastMigration = this.migrations.slice(-1)[0] // use specified defaultVersion or highest migration version this.defaultVersion = opts.defaultVersion || (lastMigration && lastMigration.version) || 0 } // run all pending migrations on meta in place - migrateData (versionedData = this.generateInitialState()) { - const remaining = this.migrations.filter(migrationIsPending) - if (remaining.length === 0) return versionedData - - const migrations = remaining.map((migration, i) => { - if (i === 0) return this.runMigration.bind(this, migration, versionedData) - return this.runMigration.bind(this, migration) - }) + async migrateData (versionedData = this.generateInitialState()) { + const pendingMigrations = this.migrations.filter(migrationIsPending) + + for (let index in pendingMigrations) { + let migration = pendingMigrations[index] + versionedData = await migration.migrate(versionedData) + if (!versionedData.data) throw new Error('Migrator - migration returned empty data') + if (versionedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + } - return ( - asyncQ.waterfall(migrations) - .then((migratedData) => Promise.resolve(migratedData)) - ) + return versionedData - // migration is "pending" if hit has a higher + // migration is "pending" if it has a higher // version number than currentVersion function migrationIsPending (migration) { return migration.version > versionedData.meta.version } } - runMigration (migration, versionedData) { - return migration.migrate(versionedData) - .then((migratedData) => { - if (!migratedData.data) return Promise.reject(new Error('Migrator - migration returned empty data')) - if (migration.version !== undefined && migratedData.meta.version !== migration.version) return Promise.reject(new Error('Migrator - Migration did not update version number correctly')) - - return Promise.resolve(migratedData) - }) - } - generateInitialState (initState) { return { meta: { -- cgit v1.2.3 From 2c8bbe3b25c726b8a0cebb572c3c9d962136a693 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 12 May 2017 12:27:40 -0700 Subject: migrator - fix typo --- app/scripts/lib/migrator/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/scripts') diff --git a/app/scripts/lib/migrator/index.js b/app/scripts/lib/migrator/index.js index ed07a0c60..de6f5d5cd 100644 --- a/app/scripts/lib/migrator/index.js +++ b/app/scripts/lib/migrator/index.js @@ -18,7 +18,7 @@ class Migrator { let migration = pendingMigrations[index] versionedData = await migration.migrate(versionedData) if (!versionedData.data) throw new Error('Migrator - migration returned empty data') - if (versionedData.version !== undefined && migratedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') + if (versionedData.version !== undefined && versionedData.meta.version !== migration.version) throw new Error('Migrator - Migration did not update version number correctly') } return versionedData -- cgit v1.2.3 From 5c9449dec1a4662ebb3e1ce18ede018a9e874c39 Mon Sep 17 00:00:00 2001 From: kumavis Date: Fri, 12 May 2017 13:09:23 -0700 Subject: background - drop async-q in favor of async/await --- app/scripts/background.js | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index 58f8e7556..e738a9712 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,6 +1,5 @@ const urlUtil = require('url') const endOfStream = require('end-of-stream') -const asyncQ = require('async-q') const pipe = require('pump') const LocalStorageStore = require('obs-store/lib/localStorage') const storeTransform = require('obs-store/lib/transform') @@ -30,34 +29,29 @@ let popupIsOpen = false const diskStore = new LocalStorageStore({ storageKey: STORAGE_KEY }) // initialization flow -asyncQ.waterfall([ - () => loadStateFromPersistence(), - (initState) => setupController(initState), -]) -.then(() => console.log('MetaMask initialization complete.')) -.catch((err) => { console.error(err) }) +initialize().catch(console.error) + +async function initialize() { + const initState = await loadStateFromPersistence() + await setupController(initState) + console.log('MetaMask initialization complete.') +} // // State and Persistence // -function loadStateFromPersistence () { +async function loadStateFromPersistence () { // migrations const migrator = new Migrator({ migrations }) - const initialState = migrator.generateInitialState(firstTimeState) - return asyncQ.waterfall([ - // read from disk - () => Promise.resolve(diskStore.getState() || initialState), - // migrate data - (versionedData) => migrator.migrateData(versionedData), - // write to disk - (versionedData) => { - diskStore.putState(versionedData) - return Promise.resolve(versionedData) - }, - // resolve to just data - (versionedData) => Promise.resolve(versionedData.data), - ]) + // read from disk + let versionedData = diskStore.getState() || migrator.generateInitialState(firstTimeState) + // migrate data + versionedData = await migrator.migrateData(versionedData) + // write to disk + diskStore.putState(versionedData) + // return just the data + return versionedData.data } function setupController (initState) { -- cgit v1.2.3 From 4c10e2021aa0cdc4f34a22368a37c76e0e1fea22 Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 15 May 2017 18:05:11 -0700 Subject: Change default network to rinkeby --- app/scripts/config.js | 6 +++--- app/scripts/lib/config-manager.js | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/config.js b/app/scripts/config.js index 391c67230..67067cfd7 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,7 +1,7 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' -const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' +const TESTNET_RPC_URL = 'https://rinkeby.infura.io/metamask' const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' -const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' +const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' const DEFAULT_RPC_URL = TESTNET_RPC_URL global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' @@ -13,6 +13,6 @@ module.exports = { testnet: TESTNET_RPC_URL, morden: TESTNET_RPC_URL, kovan: KOVAN_RPC_URL, - rinkeby: RINKEBY_RPC_URL, + ropsten: ROPSTEN_RPC_URL, }, } diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index ab9410842..4ca02135a 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -6,7 +6,7 @@ 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 ROPSTEN_RPC = MetamaskConfig.network.ropsten /* The config-manager is a convenience object @@ -147,8 +147,8 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'mainnet': return MAINNET_RPC - case 'testnet': - return TESTNET_RPC + case 'ropsten': + return ROPSTEN_RPC case 'morden': return MORDEN_RPC @@ -156,8 +156,8 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'kovan': return KOVAN_RPC - case 'rinkeby': - return RINKEBY_RPC + case 'testnet': + return TESTNET_RPC default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC -- cgit v1.2.3 From 3367363b1234a076695758762d7f1220fe4a7f8c Mon Sep 17 00:00:00 2001 From: Thomas Huang Date: Mon, 15 May 2017 19:11:16 -0700 Subject: Remove all traces of testnet --- app/scripts/config.js | 11 ++++------- app/scripts/first-time-state.js | 2 +- app/scripts/lib/config-manager.js | 15 +++++---------- 3 files changed, 10 insertions(+), 18 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/config.js b/app/scripts/config.js index 67067cfd7..8e28db80e 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,18 +1,15 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' -const TESTNET_RPC_URL = 'https://rinkeby.infura.io/metamask' -const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' -const DEFAULT_RPC_URL = TESTNET_RPC_URL +const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' +const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' module.exports = { network: { - default: DEFAULT_RPC_URL, mainnet: MAINET_RPC_URL, - testnet: TESTNET_RPC_URL, - morden: TESTNET_RPC_URL, - kovan: KOVAN_RPC_URL, ropsten: ROPSTEN_RPC_URL, + kovan: KOVAN_RPC_URL, + rinkeby: RINKEBY_RPC_URL, }, } diff --git a/app/scripts/first-time-state.js b/app/scripts/first-time-state.js index 87a7bb7b5..29ec1d8d3 100644 --- a/app/scripts/first-time-state.js +++ b/app/scripts/first-time-state.js @@ -5,7 +5,7 @@ module.exports = { config: { provider: { - type: 'testnet', + type: 'rinkeby', }, }, } diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 4ca02135a..d77cd2126 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -2,12 +2,10 @@ 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 ROPSTEN_RPC = MetamaskConfig.network.ropsten - +const KOVAN_RPC = MetamaskConfig.network.kovan +const RINKEBY_RPC = MetamaskConfig.network.rinkeby /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -150,17 +148,14 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'ropsten': return ROPSTEN_RPC - case 'morden': - return MORDEN_RPC - case 'kovan': return KOVAN_RPC - case 'testnet': - return TESTNET_RPC + case 'rinkeby': + return RINKEBY_RPC default: - return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC + return provider && provider.rpcTarget ? provider.rpcTarget : RINKEBY_RPC } } -- cgit v1.2.3 From 28aba6e9dea52b66534d6ecb9713a7d20947c57c Mon Sep 17 00:00:00 2001 From: kumavis Date: Mon, 15 May 2017 23:56:13 -0700 Subject: migration 13 - change provider from testnet to ropsten --- app/scripts/migrations/013.js | 34 ++++++++++++++++++++++++++++++++++ app/scripts/migrations/index.js | 1 + 2 files changed, 35 insertions(+) create mode 100644 app/scripts/migrations/013.js (limited to 'app/scripts') diff --git a/app/scripts/migrations/013.js b/app/scripts/migrations/013.js new file mode 100644 index 000000000..8f11e510e --- /dev/null +++ b/app/scripts/migrations/013.js @@ -0,0 +1,34 @@ +const version = 13 + +/* + +This migration modifies the network config from ambiguous 'testnet' to explicit 'ropsten' + +*/ + +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 + if (newState.config.provider.type === 'testnet') { + newState.config.provider.type = 'ropsten' + } + return newState +} diff --git a/app/scripts/migrations/index.js b/app/scripts/migrations/index.js index 019b4d13d..3a95cf88e 100644 --- a/app/scripts/migrations/index.js +++ b/app/scripts/migrations/index.js @@ -23,4 +23,5 @@ module.exports = [ require('./010'), require('./011'), require('./012'), + require('./013'), ] -- cgit v1.2.3 From 2df9344be5bf1c65daae2ca7ea47982fe2a1c2fb Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 10:27:41 -0700 Subject: Rename tx manager to tx controller --- app/scripts/background.js | 4 +- app/scripts/controllers/transactions.js | 404 ++++++++++++++++++++++++++++++++ app/scripts/metamask-controller.js | 32 +-- app/scripts/transaction-manager.js | 404 -------------------------------- 4 files changed, 422 insertions(+), 422 deletions(-) create mode 100644 app/scripts/controllers/transactions.js delete mode 100644 app/scripts/transaction-manager.js (limited to 'app/scripts') diff --git a/app/scripts/background.js b/app/scripts/background.js index e738a9712..63c8a7252 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -114,13 +114,13 @@ function setupController (initState) { // updateBadge() - controller.txManager.on('updateBadge', updateBadge) + controller.txController.on('updateBadge', updateBadge) controller.messageManager.on('updateBadge', updateBadge) // plugin badge text function updateBadge () { var label = '' - var unapprovedTxCount = controller.txManager.unapprovedTxCount + var unapprovedTxCount = controller.txController.unapprovedTxCount var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount var count = unapprovedTxCount + unapprovedMsgCount if (count) { diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js new file mode 100644 index 000000000..9f267160f --- /dev/null +++ b/app/scripts/controllers/transactions.js @@ -0,0 +1,404 @@ +const EventEmitter = require('events') +const async = require('async') +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') + +module.exports = class TransactionManager extends EventEmitter { + constructor (opts) { + super() + this.store = new ObservableStore(extend({ + transactions: [], + }, opts.initState)) + this.memStore = new ObservableStore({}) + 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.signEthTx = opts.signTransaction + this.nonceLock = Semaphore(1) + + // memstore is computed from a few different stores + this._updateMemstore() + this.store.subscribe(() => this._updateMemstore()) + this.networkStore.subscribe(() => this._updateMemstore()) + this.preferencesStore.subscribe(() => this._updateMemstore()) + } + + getState () { + return this.memStore.getState() + } + + getNetwork () { + return this.networkStore.getState().network + } + + getSelectedAddress () { + return this.preferencesStore.getState().selectedAddress + } + + // Returns the tx list + getTxList () { + const network = this.getNetwork() + const fullTxList = this.getFullTxList() + return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) + } + + // Returns the number of txs for the current network. + getTxCount () { + return this.getTxList().length + } + + // Returns the full tx list across all networks + getFullTxList () { + return this.store.getState().transactions + } + + // Adds a tx to the txlist + addTx (txMeta) { + const txCount = this.getTxCount() + const network = this.getNetwork() + const fullTxList = this.getFullTxList() + const txHistoryLimit = this.txHistoryLimit + + // checks if the length of the tx history is + // longer then desired persistence limit + // and then if it is removes only confirmed + // or rejected tx's. + // not tx's that are pending or unapproved + if (txCount > txHistoryLimit - 1) { + var index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) + fullTxList.splice(index, 1) + } + fullTxList.push(txMeta) + this._saveTxList(fullTxList) + this.emit('update') + + this.once(`${txMeta.id}:signed`, function (txId) { + this.removeAllListeners(`${txMeta.id}:rejected`) + }) + this.once(`${txMeta.id}:rejected`, function (txId) { + this.removeAllListeners(`${txMeta.id}:signed`) + }) + + this.emit('updateBadge') + this.emit(`${txMeta.id}:unapproved`, txMeta) + } + + // gets tx by Id and returns it + getTx (txId, cb) { + var txList = this.getTxList() + var txMeta = txList.find(txData => txData.id === txId) + return cb ? cb(txMeta) : txMeta + } + + // + updateTx (txMeta) { + var txId = txMeta.id + var txList = this.getFullTxList() + var index = txList.findIndex(txData => txData.id === txId) + txList[index] = txMeta + this._saveTxList(txList) + this.emit('update') + } + + get unapprovedTxCount () { + return Object.keys(this.getUnapprovedTxList()).length + } + + get pendingTxCount () { + return this.getTxsByMetaData('status', 'signed').length + } + + addUnapprovedTransaction (txParams, done) { + let txMeta + async.waterfall([ + // validate + (cb) => this.txProviderUtils.validateTxParams(txParams, cb), + // construct txMeta + (cb) => { + txMeta = { + id: createId(), + time: (new Date()).getTime(), + status: 'unapproved', + metamaskNetworkId: this.getNetwork(), + txParams: txParams, + } + cb() + }, + // add default tx params + (cb) => this.addTxDefaults(txMeta, cb), + // save txMeta + (cb) => { + this.addTx(txMeta) + cb(null, txMeta) + }, + ], done) + } + + addTxDefaults (txMeta, cb) { + const txParams = txMeta.txParams + // ensure value + txParams.value = txParams.value || '0x0' + this.query.gasPrice((err, gasPrice) => { + if (err) return cb(err) + // set gasPrice + txParams.gasPrice = gasPrice + // set gasLimit + this.txProviderUtils.analyzeGasUsage(txMeta, cb) + }) + } + + getUnapprovedTxList () { + var txList = this.getTxList() + return txList.filter((txMeta) => txMeta.status === 'unapproved') + .reduce((result, tx) => { + result[tx.id] = tx + return result + }, {}) + } + + approveTransaction (txId, cb = warn) { + const self = this + // approve + self.setTxStatusApproved(txId) + // only allow one tx at a time for atomic nonce usage + self.nonceLock.take(() => { + // begin signature process + async.waterfall([ + (cb) => self.fillInTxParams(txId, cb), + (cb) => self.signTransaction(txId, cb), + (rawTx, cb) => self.publishTransaction(txId, rawTx, cb), + ], (err) => { + self.nonceLock.leave() + if (err) { + this.setTxStatusFailed(txId, { + errCode: err.errCode || err, + message: err.message || 'Transaction failed during approval', + }) + return cb(err) + } + cb() + }) + }) + } + + cancelTransaction (txId, cb = warn) { + this.setTxStatusRejected(txId) + cb() + } + + fillInTxParams (txId, cb) { + const txMeta = this.getTx(txId) + this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => { + if (err) return cb(err) + this.updateTx(txMeta) + cb() + }) + } + + getChainId () { + const networkState = this.networkStore.getState() + const getChainId = parseInt(networkState.network) + if (Number.isNaN(getChainId)) { + return 0 + } else { + return getChainId + } + } + + signTransaction (txId, cb) { + const txMeta = this.getTx(txId) + const txParams = txMeta.txParams + const fromAddress = txParams.from + // add network/chain id + txParams.chainId = this.getChainId() + const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) + this.signEthTx(ethTx, fromAddress).then(() => { + this.setTxStatusSigned(txMeta.id) + cb(null, ethUtil.bufferToHex(ethTx.serialize())) + }).catch((err) => { + cb(err) + }) + } + + publishTransaction (txId, rawTx, cb) { + this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { + if (err) return cb(err) + this.setTxHash(txId, txHash) + this.setTxStatusSubmitted(txId) + cb() + }) + } + + // receives a txHash records the tx as signed + setTxHash (txId, txHash) { + // Add the tx hash to the persisted meta-tx object + const txMeta = this.getTx(txId) + txMeta.hash = txHash + this.updateTx(txMeta) + } + + /* + Takes an object of fields to search for eg: + var thingsToLookFor = { + to: '0x0..', + from: '0x0..', + status: 'signed', + } + and returns a list of tx with all + options matching + + this is for things like filtering a the tx list + for only tx's from 1 account + or for filltering for all txs from one account + and that have been 'confirmed' + */ + getFilteredTxList (opts) { + var filteredTxList + Object.keys(opts).forEach((key) => { + filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) + }) + return filteredTxList + } + + getTxsByMetaData (key, value, txList = this.getTxList()) { + return txList.filter((txMeta) => { + if (txMeta.txParams[key]) { + return txMeta.txParams[key] === value + } else { + return txMeta[key] === value + } + }) + } + + // STATUS METHODS + // get::set status + + // should return the status of the tx. + getTxStatus (txId) { + const txMeta = this.getTx(txId) + return txMeta.status + } + + // should update the status of the tx to 'rejected'. + setTxStatusRejected (txId) { + this._setTxStatus(txId, 'rejected') + } + + // should update the status of the tx to 'approved'. + setTxStatusApproved (txId) { + this._setTxStatus(txId, 'approved') + } + + // should update the status of the tx to 'signed'. + setTxStatusSigned (txId) { + this._setTxStatus(txId, 'signed') + } + + // should update the status of the tx to 'submitted'. + setTxStatusSubmitted (txId) { + this._setTxStatus(txId, 'submitted') + } + + // should update the status of the tx to 'confirmed'. + setTxStatusConfirmed (txId) { + this._setTxStatus(txId, 'confirmed') + } + + setTxStatusFailed (txId, reason) { + const txMeta = this.getTx(txId) + txMeta.err = reason + this.updateTx(txMeta) + this._setTxStatus(txId, 'failed') + } + + // merges txParams obj onto txData.txParams + // use extend to ensure that all fields are filled + updateTxParams (txId, txParams) { + var txMeta = this.getTx(txId) + txMeta.txParams = extend(txMeta.txParams, txParams) + this.updateTx(txMeta) + } + + // checks if a signed tx is in a block and + // if included sets the tx status as 'confirmed' + checkForTxInBlock () { + var signedTxList = this.getFilteredTxList({status: 'submitted'}) + if (!signedTxList.length) return + signedTxList.forEach((txMeta) => { + var txHash = txMeta.hash + var txId = txMeta.id + if (!txHash) { + const errReason = { + errCode: 'No hash was provided', + message: 'We had an error while submitting this transaction, please try again.', + } + return this.setTxStatusFailed(txId, errReason) + } + this.query.getTransactionByHash(txHash, (err, txParams) => { + if (err || !txParams) { + if (!txParams) return + txMeta.err = { + isWarning: true, + errorCode: err, + message: 'There was a problem loading this transaction.', + } + this.updateTx(txMeta) + return console.error(err) + } + if (txParams.blockNumber) { + this.setTxStatusConfirmed(txId) + } + }) + }) + } + + // PRIVATE METHODS + + // Should find the tx in the tx list and + // update it. + // should set the status in txData + // - `'unapproved'` the user has not responded + // - `'rejected'` the user has responded no! + // - `'approved'` the user has approved the tx + // - `'signed'` the tx is signed + // - `'submitted'` the tx is sent to a server + // - `'confirmed'` the tx has been included in a block. + _setTxStatus (txId, status) { + var txMeta = this.getTx(txId) + txMeta.status = status + this.emit(`${txMeta.id}:${status}`, txId) + if (status === 'submitted' || status === 'rejected') { + this.emit(`${txMeta.id}:finished`, txMeta) + } + this.updateTx(txMeta) + this.emit('updateBadge') + } + + // Saves the new/updated txList. + // Function is intended only for internal use + _saveTxList (transactions) { + this.store.updateState({ transactions }) + } + + _updateMemstore () { + const unapprovedTxs = this.getUnapprovedTxList() + const selectedAddressTxList = this.getFilteredTxList({ + from: this.getSelectedAddress(), + metamaskNetworkId: this.getNetwork(), + }) + this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) + } +} + + +const warn = () => console.warn('warn was used no cb provided') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 175602ec1..f18da9033 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -17,7 +17,7 @@ const ShapeShiftController = require('./controllers/shapeshift') const AddressBookController = require('./controllers/address-book') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') -const TxManager = require('./transaction-manager') +const TransactionController = require('./controllers/transactions') const ConfigManager = require('./lib/config-manager') const autoFaucet = require('./lib/auto-faucet') const nodeify = require('./lib/nodeify') @@ -90,8 +90,8 @@ module.exports = class MetamaskController extends EventEmitter { }, this.keyringController) // tx mgmt - this.txManager = new TxManager({ - initState: initState.TransactionManager, + this.txController = new TransactionController({ + initState: initState.TransactionController || initState.TransactionManager, networkStore: this.networkStore, preferencesStore: this.preferencesController.store, txHistoryLimit: 40, @@ -119,8 +119,8 @@ module.exports = class MetamaskController extends EventEmitter { this.publicConfigStore = this.initPublicConfigStore() // manual disk state subscriptions - this.txManager.store.subscribe((state) => { - this.store.updateState({ TransactionManager: state }) + this.txController.store.subscribe((state) => { + this.store.updateState({ TransactionController: state }) }) this.keyringController.store.subscribe((state) => { this.store.updateState({ KeyringController: state }) @@ -144,7 +144,7 @@ module.exports = class MetamaskController extends EventEmitter { // manual mem state subscriptions this.networkStore.subscribe(this.sendUpdate.bind(this)) this.ethStore.subscribe(this.sendUpdate.bind(this)) - this.txManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.txController.memStore.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) @@ -223,7 +223,7 @@ module.exports = class MetamaskController extends EventEmitter { }, this.networkStore.getState(), this.ethStore.getState(), - this.txManager.memStore.getState(), + this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), this.keyringController.memStore.getState(), @@ -248,7 +248,7 @@ module.exports = class MetamaskController extends EventEmitter { getApi () { const keyringController = this.keyringController const preferencesController = this.preferencesController - const txManager = this.txManager + const txController = this.txController const noticeController = this.noticeController const addressBookController = this.addressBookController @@ -289,9 +289,9 @@ module.exports = class MetamaskController extends EventEmitter { saveAccountLabel: nodeify(keyringController.saveAccountLabel).bind(keyringController), exportAccount: nodeify(keyringController.exportAccount).bind(keyringController), - // txManager - approveTransaction: txManager.approveTransaction.bind(txManager), - cancelTransaction: txManager.cancelTransaction.bind(txManager), + // txController + approveTransaction: txController.approveTransaction.bind(txController), + cancelTransaction: txController.cancelTransaction.bind(txController), updateAndApproveTransaction: this.updateAndApproveTx.bind(this), // messageManager @@ -421,12 +421,12 @@ module.exports = class MetamaskController extends EventEmitter { newUnapprovedTransaction (txParams, cb) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const self = this - self.txManager.addUnapprovedTransaction(txParams, (err, txMeta) => { + self.txController.addUnapprovedTransaction(txParams, (err, txMeta) => { if (err) return cb(err) self.sendUpdate() self.opts.showUnapprovedTx(txMeta) // listen for tx completion (success, fail) - self.txManager.once(`${txMeta.id}:finished`, (completedTx) => { + self.txController.once(`${txMeta.id}:finished`, (completedTx) => { switch (completedTx.status) { case 'submitted': return cb(null, completedTx.hash) @@ -477,9 +477,9 @@ module.exports = class MetamaskController extends EventEmitter { updateAndApproveTx (txMeta, cb) { log.debug(`MetaMaskController - updateAndApproveTx: ${JSON.stringify(txMeta)}`) - const txManager = this.txManager - txManager.updateTx(txMeta) - txManager.approveTransaction(txMeta.id, cb) + const txController = this.txController + txController.updateTx(txMeta) + txController.approveTransaction(txMeta.id, cb) } signMessage (msgParams, cb) { diff --git a/app/scripts/transaction-manager.js b/app/scripts/transaction-manager.js deleted file mode 100644 index 9f267160f..000000000 --- a/app/scripts/transaction-manager.js +++ /dev/null @@ -1,404 +0,0 @@ -const EventEmitter = require('events') -const async = require('async') -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') - -module.exports = class TransactionManager extends EventEmitter { - constructor (opts) { - super() - this.store = new ObservableStore(extend({ - transactions: [], - }, opts.initState)) - this.memStore = new ObservableStore({}) - 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.signEthTx = opts.signTransaction - this.nonceLock = Semaphore(1) - - // memstore is computed from a few different stores - this._updateMemstore() - this.store.subscribe(() => this._updateMemstore()) - this.networkStore.subscribe(() => this._updateMemstore()) - this.preferencesStore.subscribe(() => this._updateMemstore()) - } - - getState () { - return this.memStore.getState() - } - - getNetwork () { - return this.networkStore.getState().network - } - - getSelectedAddress () { - return this.preferencesStore.getState().selectedAddress - } - - // Returns the tx list - getTxList () { - const network = this.getNetwork() - const fullTxList = this.getFullTxList() - return fullTxList.filter(txMeta => txMeta.metamaskNetworkId === network) - } - - // Returns the number of txs for the current network. - getTxCount () { - return this.getTxList().length - } - - // Returns the full tx list across all networks - getFullTxList () { - return this.store.getState().transactions - } - - // Adds a tx to the txlist - addTx (txMeta) { - const txCount = this.getTxCount() - const network = this.getNetwork() - const fullTxList = this.getFullTxList() - const txHistoryLimit = this.txHistoryLimit - - // checks if the length of the tx history is - // longer then desired persistence limit - // and then if it is removes only confirmed - // or rejected tx's. - // not tx's that are pending or unapproved - if (txCount > txHistoryLimit - 1) { - var index = fullTxList.findIndex((metaTx) => ((metaTx.status === 'confirmed' || metaTx.status === 'rejected') && network === txMeta.metamaskNetworkId)) - fullTxList.splice(index, 1) - } - fullTxList.push(txMeta) - this._saveTxList(fullTxList) - this.emit('update') - - this.once(`${txMeta.id}:signed`, function (txId) { - this.removeAllListeners(`${txMeta.id}:rejected`) - }) - this.once(`${txMeta.id}:rejected`, function (txId) { - this.removeAllListeners(`${txMeta.id}:signed`) - }) - - this.emit('updateBadge') - this.emit(`${txMeta.id}:unapproved`, txMeta) - } - - // gets tx by Id and returns it - getTx (txId, cb) { - var txList = this.getTxList() - var txMeta = txList.find(txData => txData.id === txId) - return cb ? cb(txMeta) : txMeta - } - - // - updateTx (txMeta) { - var txId = txMeta.id - var txList = this.getFullTxList() - var index = txList.findIndex(txData => txData.id === txId) - txList[index] = txMeta - this._saveTxList(txList) - this.emit('update') - } - - get unapprovedTxCount () { - return Object.keys(this.getUnapprovedTxList()).length - } - - get pendingTxCount () { - return this.getTxsByMetaData('status', 'signed').length - } - - addUnapprovedTransaction (txParams, done) { - let txMeta - async.waterfall([ - // validate - (cb) => this.txProviderUtils.validateTxParams(txParams, cb), - // construct txMeta - (cb) => { - txMeta = { - id: createId(), - time: (new Date()).getTime(), - status: 'unapproved', - metamaskNetworkId: this.getNetwork(), - txParams: txParams, - } - cb() - }, - // add default tx params - (cb) => this.addTxDefaults(txMeta, cb), - // save txMeta - (cb) => { - this.addTx(txMeta) - cb(null, txMeta) - }, - ], done) - } - - addTxDefaults (txMeta, cb) { - const txParams = txMeta.txParams - // ensure value - txParams.value = txParams.value || '0x0' - this.query.gasPrice((err, gasPrice) => { - if (err) return cb(err) - // set gasPrice - txParams.gasPrice = gasPrice - // set gasLimit - this.txProviderUtils.analyzeGasUsage(txMeta, cb) - }) - } - - getUnapprovedTxList () { - var txList = this.getTxList() - return txList.filter((txMeta) => txMeta.status === 'unapproved') - .reduce((result, tx) => { - result[tx.id] = tx - return result - }, {}) - } - - approveTransaction (txId, cb = warn) { - const self = this - // approve - self.setTxStatusApproved(txId) - // only allow one tx at a time for atomic nonce usage - self.nonceLock.take(() => { - // begin signature process - async.waterfall([ - (cb) => self.fillInTxParams(txId, cb), - (cb) => self.signTransaction(txId, cb), - (rawTx, cb) => self.publishTransaction(txId, rawTx, cb), - ], (err) => { - self.nonceLock.leave() - if (err) { - this.setTxStatusFailed(txId, { - errCode: err.errCode || err, - message: err.message || 'Transaction failed during approval', - }) - return cb(err) - } - cb() - }) - }) - } - - cancelTransaction (txId, cb = warn) { - this.setTxStatusRejected(txId) - cb() - } - - fillInTxParams (txId, cb) { - const txMeta = this.getTx(txId) - this.txProviderUtils.fillInTxParams(txMeta.txParams, (err) => { - if (err) return cb(err) - this.updateTx(txMeta) - cb() - }) - } - - getChainId () { - const networkState = this.networkStore.getState() - const getChainId = parseInt(networkState.network) - if (Number.isNaN(getChainId)) { - return 0 - } else { - return getChainId - } - } - - signTransaction (txId, cb) { - const txMeta = this.getTx(txId) - const txParams = txMeta.txParams - const fromAddress = txParams.from - // add network/chain id - txParams.chainId = this.getChainId() - const ethTx = this.txProviderUtils.buildEthTxFromParams(txParams) - this.signEthTx(ethTx, fromAddress).then(() => { - this.setTxStatusSigned(txMeta.id) - cb(null, ethUtil.bufferToHex(ethTx.serialize())) - }).catch((err) => { - cb(err) - }) - } - - publishTransaction (txId, rawTx, cb) { - this.txProviderUtils.publishTransaction(rawTx, (err, txHash) => { - if (err) return cb(err) - this.setTxHash(txId, txHash) - this.setTxStatusSubmitted(txId) - cb() - }) - } - - // receives a txHash records the tx as signed - setTxHash (txId, txHash) { - // Add the tx hash to the persisted meta-tx object - const txMeta = this.getTx(txId) - txMeta.hash = txHash - this.updateTx(txMeta) - } - - /* - Takes an object of fields to search for eg: - var thingsToLookFor = { - to: '0x0..', - from: '0x0..', - status: 'signed', - } - and returns a list of tx with all - options matching - - this is for things like filtering a the tx list - for only tx's from 1 account - or for filltering for all txs from one account - and that have been 'confirmed' - */ - getFilteredTxList (opts) { - var filteredTxList - Object.keys(opts).forEach((key) => { - filteredTxList = this.getTxsByMetaData(key, opts[key], filteredTxList) - }) - return filteredTxList - } - - getTxsByMetaData (key, value, txList = this.getTxList()) { - return txList.filter((txMeta) => { - if (txMeta.txParams[key]) { - return txMeta.txParams[key] === value - } else { - return txMeta[key] === value - } - }) - } - - // STATUS METHODS - // get::set status - - // should return the status of the tx. - getTxStatus (txId) { - const txMeta = this.getTx(txId) - return txMeta.status - } - - // should update the status of the tx to 'rejected'. - setTxStatusRejected (txId) { - this._setTxStatus(txId, 'rejected') - } - - // should update the status of the tx to 'approved'. - setTxStatusApproved (txId) { - this._setTxStatus(txId, 'approved') - } - - // should update the status of the tx to 'signed'. - setTxStatusSigned (txId) { - this._setTxStatus(txId, 'signed') - } - - // should update the status of the tx to 'submitted'. - setTxStatusSubmitted (txId) { - this._setTxStatus(txId, 'submitted') - } - - // should update the status of the tx to 'confirmed'. - setTxStatusConfirmed (txId) { - this._setTxStatus(txId, 'confirmed') - } - - setTxStatusFailed (txId, reason) { - const txMeta = this.getTx(txId) - txMeta.err = reason - this.updateTx(txMeta) - this._setTxStatus(txId, 'failed') - } - - // merges txParams obj onto txData.txParams - // use extend to ensure that all fields are filled - updateTxParams (txId, txParams) { - var txMeta = this.getTx(txId) - txMeta.txParams = extend(txMeta.txParams, txParams) - this.updateTx(txMeta) - } - - // checks if a signed tx is in a block and - // if included sets the tx status as 'confirmed' - checkForTxInBlock () { - var signedTxList = this.getFilteredTxList({status: 'submitted'}) - if (!signedTxList.length) return - signedTxList.forEach((txMeta) => { - var txHash = txMeta.hash - var txId = txMeta.id - if (!txHash) { - const errReason = { - errCode: 'No hash was provided', - message: 'We had an error while submitting this transaction, please try again.', - } - return this.setTxStatusFailed(txId, errReason) - } - this.query.getTransactionByHash(txHash, (err, txParams) => { - if (err || !txParams) { - if (!txParams) return - txMeta.err = { - isWarning: true, - errorCode: err, - message: 'There was a problem loading this transaction.', - } - this.updateTx(txMeta) - return console.error(err) - } - if (txParams.blockNumber) { - this.setTxStatusConfirmed(txId) - } - }) - }) - } - - // PRIVATE METHODS - - // Should find the tx in the tx list and - // update it. - // should set the status in txData - // - `'unapproved'` the user has not responded - // - `'rejected'` the user has responded no! - // - `'approved'` the user has approved the tx - // - `'signed'` the tx is signed - // - `'submitted'` the tx is sent to a server - // - `'confirmed'` the tx has been included in a block. - _setTxStatus (txId, status) { - var txMeta = this.getTx(txId) - txMeta.status = status - this.emit(`${txMeta.id}:${status}`, txId) - if (status === 'submitted' || status === 'rejected') { - this.emit(`${txMeta.id}:finished`, txMeta) - } - this.updateTx(txMeta) - this.emit('updateBadge') - } - - // Saves the new/updated txList. - // Function is intended only for internal use - _saveTxList (transactions) { - this.store.updateState({ transactions }) - } - - _updateMemstore () { - const unapprovedTxs = this.getUnapprovedTxList() - const selectedAddressTxList = this.getFilteredTxList({ - from: this.getSelectedAddress(), - metamaskNetworkId: this.getNetwork(), - }) - this.memStore.updateState({ unapprovedTxs, selectedAddressTxList }) - } -} - - -const warn = () => console.warn('warn was used no cb provided') -- cgit v1.2.3 From 68d6ea44a0c9f3d75415ccadefe182f9a0872db1 Mon Sep 17 00:00:00 2001 From: Dan Finlay Date: Tue, 16 May 2017 11:39:00 -0700 Subject: Fix path references --- app/scripts/controllers/transactions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/scripts') diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 9f267160f..21dd25b30 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -5,8 +5,8 @@ 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') +const TxProviderUtil = require('../lib/tx-utils') +const createId = require('../lib/random-id') module.exports = class TransactionManager extends EventEmitter { constructor (opts) { -- cgit v1.2.3