diff options
author | Chi Kei Chan <chikeichan@gmail.com> | 2017-10-19 14:04:54 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-19 14:04:54 +0800 |
commit | 776e8241f0464939cc3049f7155c150e3375c623 (patch) | |
tree | 017600778515e74eaf15bf4f889e5b26638c56d7 /app | |
parent | de1da7d1b215ade650fc644adf63384a401dc240 (diff) | |
parent | a8bba2f4b7ca714d46b2e1ca405aec3135aa23b2 (diff) | |
download | tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.tar tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.tar.gz tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.tar.bz2 tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.tar.lz tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.tar.xz tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.tar.zst tangerine-wallet-browser-776e8241f0464939cc3049f7155c150e3375c623.zip |
Merge pull request #2402 from chikeichan/merge
[NewUI] Merge master into NewUI-flat
Diffstat (limited to 'app')
-rw-r--r-- | app/manifest.json | 2 | ||||
-rw-r--r-- | app/scripts/background.js | 3 | ||||
-rw-r--r-- | app/scripts/contentscript.js | 7 | ||||
-rw-r--r-- | app/scripts/controllers/transactions.js | 3 | ||||
-rw-r--r-- | app/scripts/lib/nodeify.js | 12 | ||||
-rw-r--r-- | app/scripts/lib/pending-tx-tracker.js | 29 | ||||
-rw-r--r-- | app/scripts/lib/tx-state-manager.js | 8 | ||||
-rw-r--r-- | app/scripts/lib/typed-message-manager.js | 123 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 57 |
9 files changed, 232 insertions, 12 deletions
diff --git a/app/manifest.json b/app/manifest.json index 0fc43c7d4..a0f449c68 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,7 +1,7 @@ { "name": "MetaMask", "short_name": "Metamask", - "version": "3.10.8", + "version": "3.11.0", "manifest_version": 2, "author": "https://metamask.io", "description": "Ethereum Browser Extension", diff --git a/app/scripts/background.js b/app/scripts/background.js index 195881e15..3e560d302 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -124,7 +124,8 @@ function setupController (initState) { var unapprovedTxCount = controller.txController.getUnapprovedTxCount() var unapprovedMsgCount = controller.messageManager.unapprovedMsgCount var unapprovedPersonalMsgs = controller.personalMessageManager.unapprovedPersonalMsgCount - var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + var unapprovedTypedMsgs = controller.typedMessageManager.unapprovedTypedMessagesCount + var count = unapprovedTxCount + unapprovedMsgCount + unapprovedPersonalMsgs + unapprovedTypedMsgs if (count) { label = String(count) } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index b4708189e..ffbbc73cc 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -7,7 +7,9 @@ const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('./lib/port-stream.js') -const inpageText = fs.readFileSync(path.join(__dirname, 'inpage.js')).toString() +const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString() +const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n' +const inpageBundle = inpageContent + inpageSuffix // Eventually this streaming injection could be replaced with: // https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction @@ -25,8 +27,7 @@ function setupInjection () { try { // inject in-page script var scriptTag = document.createElement('script') - scriptTag.src = extension.extension.getURL('scripts/inpage.js') - scriptTag.textContent = inpageText + scriptTag.textContent = inpageBundle scriptTag.onload = function () { this.parentNode.removeChild(this) } var container = document.head || document.documentElement // append as first child diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 94e04c429..ef659a300 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -59,9 +59,10 @@ module.exports = class TransactionController extends EventEmitter { this.pendingTxTracker = new PendingTransactionTracker({ provider: this.provider, nonceTracker: this.nonceTracker, - retryLimit: 3500, // Retry 3500 blocks, or about 1 day. + retryTimePeriod: 86400000, // Retry 3500 blocks, or about 1 day. publishTransaction: (rawTx) => this.query.sendRawTransaction(rawTx), getPendingTransactions: this.txStateManager.getPendingTransactions.bind(this.txStateManager), + getCompletedTransactions: this.txStateManager.getConfirmedTransactions.bind(this.txStateManager), }) this.txStateManager.store.subscribe(() => this.emit('update:badge')) diff --git a/app/scripts/lib/nodeify.js b/app/scripts/lib/nodeify.js index 832d6c6d3..d24e92206 100644 --- a/app/scripts/lib/nodeify.js +++ b/app/scripts/lib/nodeify.js @@ -1,10 +1,18 @@ const promiseToCallback = require('promise-to-callback') +const noop = function(){} module.exports = function nodeify (fn, context) { return function(){ const args = [].slice.call(arguments) - const callback = args.pop() - if (typeof callback !== 'function') throw new Error('callback is not a function') + const lastArg = args[args.length - 1] + const lastArgIsCallback = typeof lastArg === 'function' + let callback + if (lastArgIsCallback) { + callback = lastArg + args.pop() + } else { + callback = noop + } promiseToCallback(fn.apply(context, args))(callback) } } diff --git a/app/scripts/lib/pending-tx-tracker.js b/app/scripts/lib/pending-tx-tracker.js index 6f1601586..df504c126 100644 --- a/app/scripts/lib/pending-tx-tracker.js +++ b/app/scripts/lib/pending-tx-tracker.js @@ -22,9 +22,12 @@ module.exports = class PendingTransactionTracker extends EventEmitter { super() this.query = new EthQuery(config.provider) this.nonceTracker = config.nonceTracker - this.retryLimit = config.retryLimit || Infinity + // default is one day + this.retryTimePeriod = config.retryTimePeriod || 86400000 this.getPendingTransactions = config.getPendingTransactions + this.getCompletedTransactions = config.getCompletedTransactions this.publishTransaction = config.publishTransaction + this._checkPendingTxs() } // checks if a signed tx is in a block and @@ -99,8 +102,9 @@ module.exports = class PendingTransactionTracker extends EventEmitter { } async _resubmitTx (txMeta) { - if (txMeta.retryCount > this.retryLimit) { - const err = new Error(`Gave up submitting after ${this.retryLimit} blocks un-mined.`) + if (Date.now() > txMeta.time + this.retryTimePeriod) { + const hours = (this.retryTimePeriod / 3.6e+6).toFixed(1) + const err = new Error(`Gave up submitting after ${hours} hours.`) return this.emit('tx:failed', txMeta.id, err) } @@ -118,6 +122,7 @@ module.exports = class PendingTransactionTracker extends EventEmitter { async _checkPendingTx (txMeta) { const txHash = txMeta.hash const txId = txMeta.id + // extra check in case there was an uncaught error during the // signature and submission process if (!txHash) { @@ -126,6 +131,15 @@ module.exports = class PendingTransactionTracker extends EventEmitter { this.emit('tx:failed', txId, noTxHashErr) return } + + // If another tx with the same nonce is mined, set as failed. + const taken = await this._checkIfNonceIsTaken(txMeta) + if (taken) { + const nonceTakenErr = new Error('Another transaction with this nonce has been mined.') + nonceTakenErr.name = 'NonceTakenErr' + return this.emit('tx:failed', txId, nonceTakenErr) + } + // get latest transaction status let txParams try { @@ -157,4 +171,13 @@ module.exports = class PendingTransactionTracker extends EventEmitter { } nonceGlobalLock.releaseLock() } + + async _checkIfNonceIsTaken (txMeta) { + const completed = this.getCompletedTransactions() + const sameNonce = completed.filter((otherMeta) => { + return otherMeta.txParams.nonce === txMeta.txParams.nonce + }) + return sameNonce.length > 0 + } + } diff --git a/app/scripts/lib/tx-state-manager.js b/app/scripts/lib/tx-state-manager.js index cf8117864..2250403f6 100644 --- a/app/scripts/lib/tx-state-manager.js +++ b/app/scripts/lib/tx-state-manager.js @@ -46,6 +46,12 @@ module.exports = class TransactionStateManger extends EventEmitter { return this.getFilteredTxList(opts) } + getConfirmedTransactions (address) { + const opts = { status: 'confirmed' } + if (address) opts.from = address + return this.getFilteredTxList(opts) + } + addTx (txMeta) { this.once(`${txMeta.id}:signed`, function (txId) { this.removeAllListeners(`${txMeta.id}:rejected`) @@ -242,4 +248,4 @@ module.exports = class TransactionStateManger extends EventEmitter { _saveTxList (transactions) { this.store.updateState({ transactions }) } -}
\ No newline at end of file +} diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js new file mode 100644 index 000000000..8b760790e --- /dev/null +++ b/app/scripts/lib/typed-message-manager.js @@ -0,0 +1,123 @@ +const EventEmitter = require('events') +const ObservableStore = require('obs-store') +const createId = require('./random-id') +const assert = require('assert') +const sigUtil = require('eth-sig-util') + + +module.exports = class TypedMessageManager extends EventEmitter { + constructor (opts) { + super() + this.memStore = new ObservableStore({ + unapprovedTypedMessages: {}, + unapprovedTypedMessagesCount: 0, + }) + this.messages = [] + } + + get unapprovedTypedMessagesCount () { + return Object.keys(this.getUnapprovedMsgs()).length + } + + getUnapprovedMsgs () { + return this.messages.filter(msg => msg.status === 'unapproved') + .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) + } + + addUnapprovedMessage (msgParams) { + this.validateParams(msgParams) + + log.debug(`TypedMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) + // create txData obj with parameters and meta data + var time = (new Date()).getTime() + var msgId = createId() + var msgData = { + id: msgId, + msgParams: msgParams, + time: time, + status: 'unapproved', + type: 'eth_signTypedData', + } + this.addMsg(msgData) + + // signal update + this.emit('update') + return msgId + } + + validateParams (params) { + assert.equal(typeof params, 'object', 'Params should ben an object.') + assert.ok('data' in params, 'Params must include a data field.') + assert.ok('from' in params, 'Params must include a from field.') + assert.ok(Array.isArray(params.data), 'Data should be an array.') + assert.equal(typeof params.from, 'string', 'From field must be a string.') + assert.doesNotThrow(() => { + sigUtil.typedSignatureHash(params.data) + }, 'Expected EIP712 typed data') + } + + addMsg (msg) { + this.messages.push(msg) + this._saveMsgList() + } + + getMsg (msgId) { + return this.messages.find(msg => msg.id === msgId) + } + + approveMessage (msgParams) { + this.setMsgStatusApproved(msgParams.metamaskId) + return this.prepMsgForSigning(msgParams) + } + + setMsgStatusApproved (msgId) { + this._setMsgStatus(msgId, 'approved') + } + + setMsgStatusSigned (msgId, rawSig) { + const msg = this.getMsg(msgId) + msg.rawSig = rawSig + this._updateMsg(msg) + this._setMsgStatus(msgId, 'signed') + } + + prepMsgForSigning (msgParams) { + delete msgParams.metamaskId + return Promise.resolve(msgParams) + } + + rejectMsg (msgId) { + this._setMsgStatus(msgId, 'rejected') + } + + // + // PRIVATE METHODS + // + + _setMsgStatus (msgId, status) { + const msg = this.getMsg(msgId) + if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') + msg.status = status + this._updateMsg(msg) + this.emit(`${msgId}:${status}`, msg) + if (status === 'rejected' || status === 'signed') { + this.emit(`${msgId}:finished`, msg) + } + } + + _updateMsg (msg) { + const index = this.messages.findIndex((message) => message.id === msg.id) + if (index !== -1) { + this.messages[index] = msg + } + this._saveMsgList() + } + + _saveMsgList () { + const unapprovedTypedMessages = this.getUnapprovedMsgs() + const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length + this.memStore.updateState({ unapprovedTypedMessages, unapprovedTypedMessagesCount }) + this.emit('updateBadge') + } + +} diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index ef37a852b..73db866a2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -25,6 +25,7 @@ const InfuraController = require('./controllers/infura') const BlacklistController = require('./controllers/blacklist') const MessageManager = require('./lib/message-manager') const PersonalMessageManager = require('./lib/personal-message-manager') +const TypedMessageManager = require('./lib/typed-message-manager') const TransactionController = require('./controllers/transactions') const BalancesController = require('./controllers/computed-balances') const ConfigManager = require('./lib/config-manager') @@ -161,6 +162,7 @@ module.exports = class MetamaskController extends EventEmitter { this.networkController.lookupNetwork() this.messageManager = new MessageManager() this.personalMessageManager = new PersonalMessageManager() + this.typedMessageManager = new TypedMessageManager() this.publicConfigStore = this.initPublicConfigStore() // manual disk state subscriptions @@ -202,6 +204,7 @@ module.exports = class MetamaskController extends EventEmitter { this.balancesController.store.subscribe(this.sendUpdate.bind(this)) this.messageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.personalMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) + this.typedMessageManager.memStore.subscribe(this.sendUpdate.bind(this)) this.keyringController.memStore.subscribe(this.sendUpdate.bind(this)) this.preferencesController.store.subscribe(this.sendUpdate.bind(this)) this.addressBookController.store.subscribe(this.sendUpdate.bind(this)) @@ -239,6 +242,7 @@ module.exports = class MetamaskController extends EventEmitter { processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing processPersonalMessage: this.newUnsignedPersonalMessage.bind(this), + processTypedMessage: this.newUnsignedTypedMessage.bind(this), } const providerProxy = this.networkController.initializeProvider(providerOpts) return providerProxy @@ -283,6 +287,7 @@ module.exports = class MetamaskController extends EventEmitter { this.txController.memStore.getState(), this.messageManager.memStore.getState(), this.personalMessageManager.memStore.getState(), + this.typedMessageManager.memStore.getState(), this.keyringController.memStore.getState(), this.balancesController.store.getState(), this.preferencesController.store.getState(), @@ -365,6 +370,10 @@ module.exports = class MetamaskController extends EventEmitter { signPersonalMessage: nodeify(this.signPersonalMessage, this), cancelPersonalMessage: this.cancelPersonalMessage.bind(this), + // personalMessageManager + signTypedMessage: nodeify(this.signTypedMessage, this), + cancelTypedMessage: this.cancelTypedMessage.bind(this), + // notices checkNotices: noticeController.updateNoticesList.bind(noticeController), markNoticeRead: noticeController.markNoticeRead.bind(noticeController), @@ -557,6 +566,28 @@ module.exports = class MetamaskController extends EventEmitter { }) } + newUnsignedTypedMessage (msgParams, cb) { + let msgId + try { + msgId = this.typedMessageManager.addUnapprovedMessage(msgParams) + this.sendUpdate() + this.opts.showUnconfirmedMessage() + } catch (e) { + return cb(e) + } + + this.typedMessageManager.once(`${msgId}:finished`, (data) => { + switch (data.status) { + case 'signed': + return cb(null, data.rawSig) + case 'rejected': + return cb(new Error('MetaMask Message Signature: User denied message signature.')) + default: + return cb(new Error(`MetaMask Message Signature: Unknown problem: ${JSON.stringify(msgParams)}`)) + } + }) + } + signMessage (msgParams, cb) { log.info('MetaMaskController - signMessage') const msgId = msgParams.metamaskId @@ -619,6 +650,24 @@ module.exports = class MetamaskController extends EventEmitter { }) } + signTypedMessage (msgParams) { + log.info('MetaMaskController - signTypedMessage') + const msgId = msgParams.metamaskId + // sets the status op the message to 'approved' + // and removes the metamaskId for signing + return this.typedMessageManager.approveMessage(msgParams) + .then((cleanMsgParams) => { + // signs the message + return this.keyringController.signTypedMessage(cleanMsgParams) + }) + .then((rawSig) => { + // tells the listener that the message has been signed + // and can be returned to the dapp + this.typedMessageManager.setMsgStatusSigned(msgId, rawSig) + return this.getState() + }) + } + cancelPersonalMessage (msgId, cb) { const messageManager = this.personalMessageManager messageManager.rejectMsg(msgId) @@ -627,6 +676,14 @@ module.exports = class MetamaskController extends EventEmitter { } } + cancelTypedMessage (msgId, cb) { + const messageManager = this.typedMessageManager + messageManager.rejectMsg(msgId) + if (cb && typeof cb === 'function') { + cb(null, this.getState()) + } + } + markAccountsFound (cb) { this.configManager.setLostAccounts([]) this.sendUpdate() |