aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts/lib
diff options
context:
space:
mode:
authorDan Finlay <dan@danfinlay.com>2017-10-13 00:59:28 +0800
committerDan Finlay <dan@danfinlay.com>2017-10-13 01:25:19 +0800
commitd0d082d70c1e256aeb70f90fcdc864aeca00aed4 (patch)
treea487d9bb888eebeba9c68d570cc55a5541384451 /app/scripts/lib
parent1cba6543a42561c6691736d58f45e97f4832912b (diff)
parent29ee33359e818525549b5241adb6f5903a054bba (diff)
downloadtangerine-wallet-browser-d0d082d70c1e256aeb70f90fcdc864aeca00aed4.tar.gz
tangerine-wallet-browser-d0d082d70c1e256aeb70f90fcdc864aeca00aed4.tar.zst
tangerine-wallet-browser-d0d082d70c1e256aeb70f90fcdc864aeca00aed4.zip
Merge branch 'master' into i1340-SynchronousInjection
Diffstat (limited to 'app/scripts/lib')
-rw-r--r--app/scripts/lib/events-proxy.js10
-rw-r--r--app/scripts/lib/nodeify.js12
-rw-r--r--app/scripts/lib/nonce-tracker.js10
-rw-r--r--app/scripts/lib/obj-proxy.js19
-rw-r--r--app/scripts/lib/pending-tx-tracker.js29
-rw-r--r--app/scripts/lib/tx-state-manager.js8
-rw-r--r--app/scripts/lib/typed-message-manager.js123
7 files changed, 193 insertions, 18 deletions
diff --git a/app/scripts/lib/events-proxy.js b/app/scripts/lib/events-proxy.js
index d1199a278..840b06b1a 100644
--- a/app/scripts/lib/events-proxy.js
+++ b/app/scripts/lib/events-proxy.js
@@ -1,6 +1,5 @@
-module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
+module.exports = function createEventEmitterProxy(eventEmitter, eventHandlers = {}) {
let target = eventEmitter
- const eventHandlers = listeners || {}
const proxy = new Proxy({}, {
get: (obj, name) => {
// intercept listeners
@@ -14,9 +13,12 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
return true
},
})
+ proxy.setTarget(eventEmitter)
+ return proxy
+
function setTarget (eventEmitter) {
target = eventEmitter
- // migrate listeners
+ // migrate eventHandlers
Object.keys(eventHandlers).forEach((name) => {
eventHandlers[name].forEach((handler) => target.on(name, handler))
})
@@ -26,6 +28,4 @@ module.exports = function createEventEmitterProxy(eventEmitter, listeners) {
eventHandlers[name].push(handler)
target.on(name, handler)
}
- if (listeners) proxy.setTarget(eventEmitter)
- return proxy
} \ No newline at end of file
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/nonce-tracker.js b/app/scripts/lib/nonce-tracker.js
index 0029ac953..2af40a27f 100644
--- a/app/scripts/lib/nonce-tracker.js
+++ b/app/scripts/lib/nonce-tracker.js
@@ -4,8 +4,9 @@ const Mutex = require('await-semaphore').Mutex
class NonceTracker {
- constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) {
+ constructor ({ provider, blockTracker, getPendingTransactions, getConfirmedTransactions }) {
this.provider = provider
+ this.blockTracker = blockTracker
this.ethQuery = new EthQuery(provider)
this.getPendingTransactions = getPendingTransactions
this.getConfirmedTransactions = getConfirmedTransactions
@@ -53,7 +54,7 @@ class NonceTracker {
}
async _getCurrentBlock () {
- const blockTracker = this._getBlockTracker()
+ const blockTracker = this.blockTracker
const currentBlock = blockTracker.getCurrentBlock()
if (currentBlock) return currentBlock
return await Promise((reject, resolve) => {
@@ -139,11 +140,6 @@ class NonceTracker {
return { name: 'local', nonce: highest, details: { startPoint, highest } }
}
- // this is a hotfix for the fact that the blockTracker will
- // change when the network changes
- _getBlockTracker () {
- return this.provider._blockTracker
- }
}
module.exports = NonceTracker
diff --git a/app/scripts/lib/obj-proxy.js b/app/scripts/lib/obj-proxy.js
new file mode 100644
index 000000000..29ca1269f
--- /dev/null
+++ b/app/scripts/lib/obj-proxy.js
@@ -0,0 +1,19 @@
+module.exports = function createObjectProxy(obj) {
+ let target = obj
+ const proxy = new Proxy({}, {
+ get: (obj, name) => {
+ // intercept setTarget
+ if (name === 'setTarget') return setTarget
+ return target[name]
+ },
+ set: (obj, name, value) => {
+ target[name] = value
+ return true
+ },
+ })
+ return proxy
+
+ function setTarget (obj) {
+ target = obj
+ }
+} \ No newline at end of file
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')
+ }
+
+}