diff options
author | frankiebee <frankie.diamond@gmail.com> | 2018-04-07 02:07:20 +0800 |
---|---|---|
committer | frankiebee <frankie.diamond@gmail.com> | 2018-04-11 05:28:05 +0800 |
commit | 2d7c3c2b00a698b19ac015624154c3c1cd2619b2 (patch) | |
tree | 3cfb7821143431735821954f54e90ce71ef173ef /app/scripts/controllers/transactions/nonce-tracker.js | |
parent | 2b787f2833d4f4cfda74ca22d3d340f0f924c94e (diff) | |
download | tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.tar tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.tar.gz tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.tar.bz2 tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.tar.lz tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.tar.xz tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.tar.zst tangerine-wallet-browser-2d7c3c2b00a698b19ac015624154c3c1cd2619b2.zip |
meta - transactions - create a transactions dir in controller and move relevant files into it
Diffstat (limited to 'app/scripts/controllers/transactions/nonce-tracker.js')
-rw-r--r-- | app/scripts/controllers/transactions/nonce-tracker.js | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/app/scripts/controllers/transactions/nonce-tracker.js b/app/scripts/controllers/transactions/nonce-tracker.js new file mode 100644 index 000000000..5b1cd7f43 --- /dev/null +++ b/app/scripts/controllers/transactions/nonce-tracker.js @@ -0,0 +1,148 @@ +const EthQuery = require('ethjs-query') +const assert = require('assert') +const Mutex = require('await-semaphore').Mutex + +class NonceTracker { + + constructor ({ provider, getPendingTransactions, getConfirmedTransactions }) { + this.provider = provider + this.ethQuery = new EthQuery(provider) + this.getPendingTransactions = getPendingTransactions + this.getConfirmedTransactions = getConfirmedTransactions + this.lockMap = {} + } + + async getGlobalLock () { + const globalMutex = this._lookupMutex('global') + // await global mutex free + const releaseLock = await globalMutex.acquire() + return { releaseLock } + } + + // releaseLock must be called + // releaseLock must be called after adding signed tx to pending transactions (or discarding) + async getNonceLock (address) { + // await global mutex free + await this._globalMutexFree() + // await lock free, then take lock + const releaseLock = await this._takeMutex(address) + // evaluate multiple nextNonce strategies + const nonceDetails = {} + const networkNonceResult = await this._getNetworkNextNonce(address) + const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address) + const nextNetworkNonce = networkNonceResult.nonce + const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed) + + const pendingTxs = this.getPendingTransactions(address) + const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0 + + nonceDetails.params = { + highestLocallyConfirmed, + highestSuggested, + nextNetworkNonce, + } + nonceDetails.local = localNonceResult + nonceDetails.network = networkNonceResult + + const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce) + assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`) + + // return nonce and release cb + return { nextNonce, nonceDetails, releaseLock } + } + + async _getCurrentBlock () { + const blockTracker = this._getBlockTracker() + const currentBlock = blockTracker.getCurrentBlock() + if (currentBlock) return currentBlock + return await new Promise((reject, resolve) => { + blockTracker.once('latest', resolve) + }) + } + + async _globalMutexFree () { + const globalMutex = this._lookupMutex('global') + const release = await globalMutex.acquire() + release() + } + + async _takeMutex (lockId) { + const mutex = this._lookupMutex(lockId) + const releaseLock = await mutex.acquire() + return releaseLock + } + + _lookupMutex (lockId) { + let mutex = this.lockMap[lockId] + if (!mutex) { + mutex = new Mutex() + this.lockMap[lockId] = mutex + } + return mutex + } + + async _getNetworkNextNonce (address) { + // calculate next nonce + // we need to make sure our base count + // and pending count are from the same block + const currentBlock = await this._getCurrentBlock() + const blockNumber = currentBlock.blockNumber + const baseCountBN = await this.ethQuery.getTransactionCount(address, blockNumber || 'latest') + const baseCount = baseCountBN.toNumber() + assert(Number.isInteger(baseCount), `nonce-tracker - baseCount is not an integer - got: (${typeof baseCount}) "${baseCount}"`) + const nonceDetails = { blockNumber, baseCount } + return { name: 'network', nonce: baseCount, details: nonceDetails } + } + + _getHighestLocallyConfirmed (address) { + const confirmedTransactions = this.getConfirmedTransactions(address) + const highest = this._getHighestNonce(confirmedTransactions) + return Number.isInteger(highest) ? highest + 1 : 0 + } + + _reduceTxListToUniqueNonces (txList) { + const reducedTxList = txList.reduce((reducedList, txMeta, index) => { + if (!index) return [txMeta] + const nonceMatches = txList.filter((txData) => { + return txMeta.txParams.nonce === txData.txParams.nonce + }) + if (nonceMatches.length > 1) return reducedList + reducedList.push(txMeta) + return reducedList + }, []) + return reducedTxList + } + + _getHighestNonce (txList) { + const nonces = txList.map((txMeta) => { + const nonce = txMeta.txParams.nonce + assert(typeof nonce, 'string', 'nonces should be hex strings') + return parseInt(nonce, 16) + }) + const highestNonce = Math.max.apply(null, nonces) + return highestNonce + } + + _getHighestContinuousFrom (txList, startPoint) { + const nonces = txList.map((txMeta) => { + const nonce = txMeta.txParams.nonce + assert(typeof nonce, 'string', 'nonces should be hex strings') + return parseInt(nonce, 16) + }) + + let highest = startPoint + while (nonces.includes(highest)) { + highest++ + } + + 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 |