From 7be718d77333698ce025e6abbc0ebc6a3424e28e Mon Sep 17 00:00:00 2001 From: Roman Storm Date: Thu, 12 Dec 2019 02:58:58 -0800 Subject: [PATCH] add bullmq --- .env.example | 1 + config.js | 2 +- docker-compose.yml | 8 +- package.json | 2 + src/Fetcher.js | 8 +- src/index.js | 7 +- src/redis.js | 19 +++ src/relayController.js | 111 +++++++++++++---- yarn.lock | 268 ++++++++++++++++++++++++++++++++++++++++- 9 files changed, 391 insertions(+), 35 deletions(-) create mode 100644 src/redis.js diff --git a/.env.example b/.env.example index 4f304e1..177cd99 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ NET_ID=42 RPC_URL=https://kovan.infura.io +REDIS_URL=redis://127.0.0.1:6379 # without 0x prefix PRIVATE_KEY= diff --git a/config.js b/config.js index b14e1ed..8676ebd 100644 --- a/config.js +++ b/config.js @@ -2,9 +2,9 @@ require('dotenv').config() module.exports = { netId: Number(process.env.NET_ID) || 42, + redisUrl: process.env.REDIS_URL, rpcUrl: process.env.RPC_URL || 'https://kovan.infura.io/v3/a3f4d001c1fc4a359ea70dd27fd9cb51', privateKey: process.env.PRIVATE_KEY, - nonce: 0, mixers: { netId1: { dai: { diff --git a/docker-compose.yml b/docker-compose.yml index 153e851..62c6538 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,10 @@ services: NET_ID: 42 RPC_URL: https://kovan.infura.io # without 0x prefix - PRIVATE_KEY: + PRIVATE_KEY: # 2.5 means 2.5% RELAYER_FEE: 2.5 + REDIS_URL: redis://redis:6379 nginx: image: jwilder/nginx-proxy restart: always @@ -29,4 +30,7 @@ services: volumes_from: - nginx volumes: - - /var/run/docker.sock:/var/run/docker.sock:ro \ No newline at end of file + - /var/run/docker.sock:/var/run/docker.sock:ro + redis: + image: redis + restart: always \ No newline at end of file diff --git a/package.json b/package.json index 4966f1b..ca86941 100644 --- a/package.json +++ b/package.json @@ -11,9 +11,11 @@ "author": "tornado.cash", "license": "MIT", "dependencies": { + "bull": "^3.12.1", "coingecko-api": "^1.0.6", "dotenv": "^8.2.0", "express": "^4.17.1", + "ioredis": "^4.14.1", "node-fetch": "^2.6.0", "web3": "^1.2.2", "web3-utils": "^1.2.2" diff --git a/src/Fetcher.js b/src/Fetcher.js index b3a2485..5e68a5e 100644 --- a/src/Fetcher.js +++ b/src/Fetcher.js @@ -3,8 +3,7 @@ const fetch = require('node-fetch') const { toWei } = require('web3-utils') const { gasOracleUrls, defaultGasPrice } = require('../config') const { getMainnetTokens } = require('./utils') -const config = require ('../config') - +const { redisClient } = require('./redis') class Fetcher { constructor(web3) { @@ -70,8 +69,9 @@ class Fetcher { } async fetchNonce() { try { - config.nonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount) - console.log(`Current nonce: ${config.nonce}`) + const nonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount) + await redisClient.set('nonce', nonce) + console.log(`Current nonce: ${nonce}`) } catch(e) { console.error('fetchNonce failed', e.message) setTimeout(this.fetchNonce, 3000) diff --git a/src/index.js b/src/index.js index 819c6d2..4912476 100644 --- a/src/index.js +++ b/src/index.js @@ -4,7 +4,7 @@ const relayController = require('./relayController') const { fetcher, web3 } = require('./instances') const { getMixers } = require('./utils') const mixers = getMixers() - +const { redisClient } = require('./redis') const app = express() app.use(express.json()) @@ -28,9 +28,10 @@ app.get('/', function (req, res) { res.send('This is tornado.cash Relayer service. Check the /status for settings') }) -app.get('/status', function (req, res) { +app.get('/status', async function (req, res) { + let nonce = await redisClient.get('nonce') const { ethPrices, gasPrices } = fetcher - res.json({ relayerAddress: web3.eth.defaultAccount, mixers, gasPrices, netId, ethPrices, relayerServiceFee }) + res.json({ relayerAddress: web3.eth.defaultAccount, mixers, gasPrices, netId, ethPrices, relayerServiceFee, nonce }) }) app.post('/relay', relayController) diff --git a/src/redis.js b/src/redis.js new file mode 100644 index 0000000..0f007b2 --- /dev/null +++ b/src/redis.js @@ -0,0 +1,19 @@ +const { redisUrl } = require('../config') +const Redis = require('ioredis') +const redisClient = new Redis(redisUrl) +const subscriber = new Redis(redisUrl) + +const redisOpts = { + createClient: function (type) { + switch (type) { + case 'client': + return redisClient + case 'subscriber': + return subscriber + default: + return new Redis(redisUrl) + } + } +} + +module.exports = { redisOpts, redisClient } \ No newline at end of file diff --git a/src/relayController.js b/src/relayController.js index 6e986ae..5dc5042 100644 --- a/src/relayController.js +++ b/src/relayController.js @@ -1,15 +1,26 @@ +const Queue = require('bull') const { numberToHex, toWei, toHex, toBN, toChecksumAddress } = require('web3-utils') const mixerABI = require('../abis/mixerABI.json') const { isValidProof, isValidArgs, isKnownContract, isEnoughFee } = require('./utils') const config = require('../config') +const { redisClient, redisOpts } = require('./redis') const { web3, fetcher } = require('./instances') +const withdrawQueue = new Queue('withdraw', redisOpts) -async function relay (req, resp) { +async function relayController(req, resp) { + let requestJob + let respLambda = (job, { msg, status }) => { + console.log('response', job.id, requestJob.id) + if(requestJob.id === job.id) { + resp.status(status).json(msg) + withdrawQueue.removeListener('completed', respLambda) + } + } + withdrawQueue.on('completed', respLambda) const { proof, args, contract } = req.body - const gasPrices = fetcher.gasPrices let { valid , reason } = isValidProof(proof) if (!valid) { console.log('Proof is invalid:', reason) @@ -47,15 +58,38 @@ async function relay (req, resp) { return resp.status(400).json({ error: 'Relayer address is invalid' }) } + await redisClient.set('foo', 'bar') + requestJob = await withdrawQueue.add({ + contract, nullifierHash, root, proof, args, currency, amount, fee: fee.toString(), recipient + }, { removeOnComplete: true }) + console.log('id', requestJob.id) +} + +withdrawQueue.process(async function(job, done){ + console.log(Date.now(), ' withdraw started', job.id) + const gasPrices = fetcher.gasPrices + const { contract, nullifierHash, root, proof, args, refund, currency, amount, fee, recipient } = job.data + // job.data contains the custom data passed when the job was created + // job.id contains id of this job. try { - const mixer = new web3.eth.Contract(mixerABI, req.body.contract) + const mixer = new web3.eth.Contract(mixerABI, contract) const isSpent = await mixer.methods.isSpent(nullifierHash).call() if (isSpent) { - return resp.status(400).json({ error: 'The note has been spent.' }) + done(null, { + status: 400, + msg: { + error: 'The note has been spent.' + } + }) } const isKnownRoot = await mixer.methods.isKnownRoot(root).call() if (!isKnownRoot) { - return resp.status(400).json({ error: 'The merkle root is too old or invalid.' }) + done(null, { + status: 400, + msg: { + error: 'The merkle root is too old or invalid.' + } + }) } let gas = await mixer.methods.withdraw(proof, ...args).estimateGas({ @@ -65,13 +99,18 @@ async function relay (req, resp) { gas += 50000 const ethPrices = fetcher.ethPrices - const { isEnough, reason } = isEnoughFee({ gas, gasPrices, currency, amount, refund, ethPrices, fee }) + const { isEnough, reason } = isEnoughFee({ gas, gasPrices, currency, amount, refund, ethPrices, fee: toBN(fee) }) if (!isEnough) { console.log(`Wrong fee: ${reason}`) - return resp.status(400).json({ error: reason }) + done(null, { + status: 400, + msg: { error: reason } + }) } const data = mixer.methods.withdraw(proof, ...args).encodeABI() + let nonce = Number(await redisClient.get('nonce')) + console.log('nonce', nonce) const tx = { from: web3.eth.defaultAccount, value: numberToHex(refund), @@ -80,24 +119,50 @@ async function relay (req, resp) { to: mixer._address, netId: config.netId, data, - nonce: config.nonce + nonce } - config.nonce++ - let signedTx = await web3.eth.accounts.signTransaction(tx, config.privateKey) - let result = web3.eth.sendSignedTransaction(signedTx.rawTransaction) - - result.once('transactionHash', function(txHash){ - resp.json({ txHash }) - console.log(`A new successfully sent tx ${txHash} for the ${recipient}`) - }).on('error', function(e){ - config.nonce-- - console.error('on transactionHash error', e.message) - return resp.status(400).json({ error: 'Proof is malformed.' }) - }) + nonce += 1 + await redisClient.set('nonce', nonce) + sendTx(tx, done) } catch (e) { console.error(e, 'estimate gas failed') - return resp.status(400).json({ error: 'Proof is malformed or spent.' }) + done(null, { + status: 400, + msg: { error: 'Internal Relayer Error. Please use a different relayer service' } + }) } -} +}) -module.exports = relay \ No newline at end of file +async function sendTx(tx, done, retryAttempt = 1) { + + let signedTx = await web3.eth.accounts.signTransaction(tx, config.privateKey) + let result = web3.eth.sendSignedTransaction(signedTx.rawTransaction) + + result.once('transactionHash', function(txHash){ + done(null, { + status: 200, + msg: { txHash } + }) + console.log(`A new successfully sent tx ${txHash}`) + }).on('error', async function(e){ + console.log('error', e.message) + if(e.message === 'Returned error: Transaction gas price supplied is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.' + || e.message === 'Returned error: Transaction nonce is too low. Try incrementing the nonce.') { + console.log('nonce too low, retrying') + if(retryAttempt <= 10) { + retryAttempt++ + const newNonce = tx.nonce + 1 + tx.nonce = newNonce + await redisClient.set('nonce', newNonce) + sendTx(tx, done, retryAttempt) + return + } + } + console.error('on transactionHash error', e.message) + done(null, { + status: 400, + msg: { error: 'Internal Relayer Error. Please use a different relayer service' } + }) + }) +} +module.exports = relayController \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index 1acd5bb..8afa542 100644 --- a/yarn.lock +++ b/yarn.lock @@ -382,6 +382,22 @@ buffer@^5.0.5, buffer@^5.2.1: base64-js "^1.0.2" ieee754 "^1.1.4" +bull@^3.12.1: + version "3.12.1" + resolved "https://registry.yarnpkg.com/bull/-/bull-3.12.1.tgz#ced62d0afca81c9264b44f1b6f39243df5d2e73f" + integrity sha512-X3bSP7gTqPXLYVSyUtQuTOqZuU0GwVbV304Et84Z8bxYP60R1VD3FUOLsESVRA9LIUEOWVH3hE8MFqlszmO0Gw== + dependencies: + cron-parser "^2.13.0" + debuglog "^1.0.0" + get-port "^5.0.0" + ioredis "^4.14.1" + lodash "^4.17.15" + p-timeout "^3.1.0" + promise.prototype.finally "^3.1.1" + semver "^6.3.0" + util.promisify "^1.0.0" + uuid "^3.3.3" + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -456,6 +472,11 @@ clone-response@^1.0.2: dependencies: mimic-response "^1.0.0" +cluster-key-slot@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d" + integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw== + coingecko-api@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/coingecko-api/-/coingecko-api-1.0.6.tgz#ecc42eb96fb1cc721e319c3d06244a642394ab34" @@ -563,6 +584,14 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" +cron-parser@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/cron-parser/-/cron-parser-2.13.0.tgz#6f930bb6f2931790d2a9eec83b3ec276e27a6725" + integrity sha512-UWeIpnRb0eyoWPVk+pD3TDpNx3KCFQeezO224oJIkktBrcW6RoAPOx5zIKprZGfk6vcYSmA8yQXItejSaDBhbQ== + dependencies: + is-nan "^1.2.1" + moment-timezone "^0.5.25" + cross-spawn@^6.0.5: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" @@ -613,13 +642,18 @@ debug@2.6.9, debug@^2.2.0: dependencies: ms "2.0.0" -debug@^4.0.1: +debug@^4.0.1, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" +debuglog@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" + integrity sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI= + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" @@ -695,11 +729,23 @@ defer-to-connect@^1.0.1: resolved "https://registry.yarnpkg.com/defer-to-connect/-/defer-to-connect-1.1.0.tgz#b41bd7efa8508cef13f8456975f7a278c72833fd" integrity sha512-WE2sZoctWm/v4smfCAdjYbrfS55JiMRdlY9ZubFhsYbteCK9+BvAx4YV7nPjYM6ZnX5BcoVKwfmyx9sIFTgQMQ== +define-properties@^1.1.1, define-properties@^1.1.2, define-properties@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" + delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +denque@^1.1.0: + version "1.4.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" + integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== + depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -816,6 +862,47 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" +es-abstract@^1.17.0-next.0: + version "1.17.0-next.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.0-next.0.tgz#1c75d39bc20bb93ac9248915c439f2204ef510e1" + integrity sha512-7t0Ywrgy60w0u1bL1Zc9f6yj0re25yaEA6o+bQOxv6AmzKGWmRkHCXA9i+FtfnxL50mMxV1P3oLC+4vHLwfLPA== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-inspect "^1.7.0" + object-keys "^1.1.1" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" + +es-abstract@^1.5.1: + version "1.16.3" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.16.3.tgz#52490d978f96ff9f89ec15b5cf244304a5bca161" + integrity sha512-WtY7Fx5LiOnSYgF5eg/1T+GONaGmpvpPdCpSnYij+U2gDTL0UPfWrhDw7b2IYb+9NQJsYpCA0wOQvZfsd6YwRw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-inspect "^1.7.0" + object-keys "^1.1.1" + string.prototype.trimleft "^2.1.0" + string.prototype.trimright "^2.1.0" + +es-to-primitive@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" + integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" + es5-ext@^0.10.35, es5-ext@^0.10.50: version "0.10.52" resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.52.tgz#bb21777e919a04263736ded120a9d665f10ea63f" @@ -1266,11 +1353,23 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= +get-port@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/get-port/-/get-port-5.0.0.tgz#aa22b6b86fd926dd7884de3e23332c9f70c031a6" + integrity sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ== + dependencies: + type-fest "^0.3.0" + get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -1407,6 +1506,11 @@ has-symbol-support-x@^1.4.1: resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw== +has-symbols@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" + integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== + has-to-string-tag-x@^1.2.0: version "1.4.1" resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" @@ -1414,6 +1518,13 @@ has-to-string-tag-x@^1.2.0: dependencies: has-symbol-support-x "^1.4.1" +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" @@ -1562,11 +1673,36 @@ inquirer@^7.0.0: strip-ansi "^5.1.0" through "^2.3.6" +ioredis@^4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.14.1.tgz#b73ded95fcf220f106d33125a92ef6213aa31318" + integrity sha512-94W+X//GHM+1GJvDk6JPc+8qlM7Dul+9K+lg3/aHixPN7ZGkW6qlvX0DG6At9hWtH2v3B32myfZqWoANUJYGJA== + dependencies: + cluster-key-slot "^1.1.0" + debug "^4.1.1" + denque "^1.1.0" + lodash.defaults "^4.2.0" + lodash.flatten "^4.4.0" + redis-commands "1.5.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + standard-as-callback "^2.0.1" + ipaddr.js@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== +is-callable@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== + +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1599,6 +1735,13 @@ is-hex-prefixed@1.0.0: resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" integrity sha1-fY035q135dEnFIkTxXPggtd39VQ= +is-nan@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/is-nan/-/is-nan-1.2.1.tgz#9faf65b6fb6db24b7f5c0628475ea71f988401e2" + integrity sha1-n69ltvttskt/XAYoR16nH5iEAeI= + dependencies: + define-properties "^1.1.1" + is-natural-number@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" @@ -1619,6 +1762,13 @@ is-promise@^2.1.0: resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" + is-retry-allowed@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4" @@ -1629,6 +1779,13 @@ is-stream@^1.0.0, is-stream@^1.1.0: resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= +is-symbol@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" + integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== + dependencies: + has-symbols "^1.0.1" + is-typedarray@^1.0.0, is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" @@ -1765,6 +1922,16 @@ levn@^0.3.0, levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" +lodash.defaults@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c" + integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw= + +lodash.flatten@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f" + integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8= + lodash@^4.17.14, lodash@^4.17.15: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" @@ -1909,6 +2076,18 @@ mock-fs@^4.1.0: resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.10.3.tgz#d0550663dd2b5d33a7c1b8713c6925aab07a04ae" integrity sha512-bcukePBvuA3qovmq0Qtqu9+1APCIGkFHnsozrPIVromt5XFGGgkQSfaN0H6RI8gStHkO/hRgimvS3tooNes4pQ== +moment-timezone@^0.5.25: + version "0.5.27" + resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.27.tgz#73adec8139b6fe30452e78f210f27b1f346b8877" + integrity sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw== + dependencies: + moment ">= 2.9.0" + +"moment@>= 2.9.0": + version "2.24.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" + integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1992,6 +2171,24 @@ object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1 resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= +object-inspect@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" + integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== + +object-keys@^1.0.12, object-keys@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" + integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== + +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + oboe@2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.4.tgz#20c88cdb0c15371bb04119257d4fdd34b0aa49f6" @@ -2059,6 +2256,13 @@ p-timeout@^1.1.1: dependencies: p-finally "^1.0.0" +p-timeout@^3.1.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe" + integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg== + dependencies: + p-finally "^1.0.0" + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -2176,6 +2380,15 @@ progress@^2.0.0: resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== +promise.prototype.finally@^3.1.1: + version "3.1.2" + resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.2.tgz#b8af89160c9c673cefe3b4c4435b53cfd0287067" + integrity sha512-A2HuJWl2opDH0EafgdjwEw7HysI8ff/n4lW4QEVBCUXFk9QeGecBWv0Deph0UmLe3tTNYegz8MOjsVuE6SMoJA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.0" + function-bind "^1.1.1" + proxy-addr@~2.0.5: version "2.0.5" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.5.tgz#34cbd64a2d81f4b1fd21e76f9f06c8a45299ee34" @@ -2286,6 +2499,23 @@ readable-stream@^2.3.0, readable-stream@^2.3.5: string_decoder "~1.1.1" util-deprecate "~1.0.1" +redis-commands@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785" + integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -2559,6 +2789,11 @@ sshpk@^1.7.0: safer-buffer "^2.0.2" tweetnacl "~0.14.0" +standard-as-callback@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126" + integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg== + "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -2587,6 +2822,22 @@ string-width@^4.1.0: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.0" +string.prototype.trimleft@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz#6cc47f0d7eb8d62b0f3701611715a3954591d634" + integrity sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + +string.prototype.trimright@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz#669d164be9df9b6f7559fa8e89945b168a5a6c58" + integrity sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg== + dependencies: + define-properties "^1.1.3" + function-bind "^1.1.1" + string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -2757,6 +3008,11 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" +type-fest@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.3.1.tgz#63d00d204e059474fe5e1b7c011112bbd1dc29e1" + integrity sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ== + type-fest@^0.8.1: version "0.8.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" @@ -2856,6 +3112,14 @@ util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" + utils-merge@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" @@ -2871,7 +3135,7 @@ uuid@3.3.2: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== -uuid@^3.3.2: +uuid@^3.3.2, uuid@^3.3.3: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==