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==