From 50054e051628c24b6f3165598b8e77df88bfe487 Mon Sep 17 00:00:00 2001 From: smart_ex Date: Thu, 24 Mar 2022 01:35:41 +1000 Subject: [PATCH] bit of refactor, add RelayerError class --- src/{ => contollers}/controller.js | 6 ++-- src/contollers/index.js | 4 +++ src/{ => contollers}/status.js | 9 +++-- src/healthWatcher.js | 18 +++++----- src/modules/redis.js | 11 ++++++ src/{ => modules}/resolver.js | 11 +++--- src/{ => modules}/validator.js | 4 +-- src/modules/web3.js | 30 ++++++++++++++++ src/priceWatcher.js | 18 +++------- src/queue.js | 4 +-- src/router.js | 29 ++++++++++++++++ src/server.js | 37 +++----------------- src/treeWatcher.js | 24 ++++--------- src/utils.js | 16 ++++++++- src/worker.js | 55 ++++++++++++++++++------------ test/validator.js | 2 +- 16 files changed, 164 insertions(+), 114 deletions(-) rename src/{ => contollers}/controller.js (91%) create mode 100644 src/contollers/index.js rename src/{ => contollers}/status.js (81%) create mode 100644 src/modules/redis.js rename src/{ => modules}/resolver.js (71%) rename src/{ => modules}/validator.js (98%) create mode 100644 src/modules/web3.js create mode 100644 src/router.js diff --git a/src/controller.js b/src/contollers/controller.js similarity index 91% rename from src/controller.js rename to src/contollers/controller.js index 7b0d831..1591ece 100644 --- a/src/controller.js +++ b/src/contollers/controller.js @@ -2,9 +2,9 @@ const { getTornadoWithdrawInputError, getMiningRewardInputError, getMiningWithdrawInputError, -} = require('./validator') -const { postJob } = require('./queue') -const { jobType } = require('./constants') +} = require('../modules/validator') +const { postJob } = require('../queue') +const { jobType } = require('../constants') async function tornadoWithdraw(req, res) { const inputError = getTornadoWithdrawInputError(req.body) diff --git a/src/contollers/index.js b/src/contollers/index.js new file mode 100644 index 0000000..73f5273 --- /dev/null +++ b/src/contollers/index.js @@ -0,0 +1,4 @@ +module.exports = { + controller: require('./controller'), + status: require('./status'), +} diff --git a/src/status.js b/src/contollers/status.js similarity index 81% rename from src/status.js rename to src/contollers/status.js index df4f0f6..518ca0a 100644 --- a/src/status.js +++ b/src/contollers/status.js @@ -1,8 +1,7 @@ -const queue = require('./queue') -const { netId, tornadoServiceFee, miningServiceFee, instances, redisUrl, rewardAccount } = require('./config') -const { version } = require('../package.json') -const Redis = require('ioredis') -const redis = new Redis(redisUrl) +const queue = require('../queue') +const { netId, tornadoServiceFee, miningServiceFee, instances, rewardAccount } = require('../config') +const { version } = require('../../package.json') +const { redis } = require('../modules/redis') async function status(req, res) { const ethPrices = await redis.hgetall('prices') diff --git a/src/healthWatcher.js b/src/healthWatcher.js index 6a8f02f..b39e0d8 100644 --- a/src/healthWatcher.js +++ b/src/healthWatcher.js @@ -1,18 +1,18 @@ -const Web3 = require('web3') -const Redis = require('ioredis') -const { toBN, fromWei } = require('web3-utils') - -const { setSafeInterval } = require('./utils') -const { redisUrl, httpRpcUrl, privateKey, minimumBalance } = require('./config') - -const web3 = new Web3(httpRpcUrl) -const redis = new Redis(redisUrl) +const { setSafeInterval, toBN, fromWei } = require('./utils') +const { privateKey, minimumBalance } = require('./config') +const { redis } = require('./modules/redis') +const web3 = require('./modules/web3')() async function main() { try { const { address } = web3.eth.accounts.privateKeyToAccount(privateKey) const balance = await web3.eth.getBalance(address) + const errors = await redis.zrevrange('errors', 0, -1) + if (errors.length > 3) { + console.log({ errors }) + throw new Error('Too many errors on relayer') + } if (toBN(balance).lt(toBN(minimumBalance))) { throw new Error(`Not enough balance, less than ${fromWei(minimumBalance)} ETH`) } diff --git a/src/modules/redis.js b/src/modules/redis.js new file mode 100644 index 0000000..721c623 --- /dev/null +++ b/src/modules/redis.js @@ -0,0 +1,11 @@ +const Redis = require('ioredis') +const { redisUrl } = require('../config') + +const redis = new Redis(redisUrl) +const redisSubscribe = new Redis(redisUrl) + +module.exports = { + redis, + redisSubscribe, + redisUrl, +} diff --git a/src/resolver.js b/src/modules/resolver.js similarity index 71% rename from src/resolver.js rename to src/modules/resolver.js index edc9466..29693fb 100644 --- a/src/resolver.js +++ b/src/modules/resolver.js @@ -1,7 +1,7 @@ -const { httpRpcUrl, aggregatorAddress } = require('./config') -const Web3 = require('web3') -const web3 = new Web3(httpRpcUrl) -const aggregator = new web3.eth.Contract(require('../abis/Aggregator.abi.json'), aggregatorAddress) +const { aggregatorAddress } = require('../config') +const web3 = require('./web3')() + +const aggregator = new web3.eth.Contract(require('../../abis/Aggregator.abi.json'), aggregatorAddress) const ens = require('eth-ens-namehash') class ENSResolver { @@ -26,5 +26,4 @@ class ENSResolver { return addresses.length === 1 ? addresses[0] : addresses } } - -module.exports = ENSResolver +module.exports = new ENSResolver() diff --git a/src/validator.js b/src/modules/validator.js similarity index 98% rename from src/validator.js rename to src/modules/validator.js index 092b9f1..577dc24 100644 --- a/src/validator.js +++ b/src/modules/validator.js @@ -1,6 +1,6 @@ const { isAddress, toChecksumAddress } = require('web3-utils') -const { getInstance } = require('./utils') -const { rewardAccount } = require('./config') +const { getInstance } = require('../utils') +const { rewardAccount } = require('../config') const Ajv = require('ajv') const ajv = new Ajv({ format: 'fast' }) diff --git a/src/modules/web3.js b/src/modules/web3.js new file mode 100644 index 0000000..5b2f612 --- /dev/null +++ b/src/modules/web3.js @@ -0,0 +1,30 @@ +const Web3 = require('web3') +const { oracleRpcUrl, httpRpcUrl, wsRpcUrl } = require('../config') +const getWeb3 = (type = 'http') => { + let url + switch (type) { + case 'oracle': + url = oracleRpcUrl + break + case 'ws': + url = wsRpcUrl + return new Web3( + new Web3.providers.WebsocketProvider(wsRpcUrl, { + clientConfig: { + maxReceivedFrameSize: 100000000, + maxReceivedMessageSize: 100000000, + }, + }), + ) + case 'http': + default: + url = httpRpcUrl + break + } + return new Web3( + new Web3.providers.HttpProvider(url, { + timeout: 200000, // ms + }), + ) +} +module.exports = getWeb3 diff --git a/src/priceWatcher.js b/src/priceWatcher.js index 96624c2..41c4608 100644 --- a/src/priceWatcher.js +++ b/src/priceWatcher.js @@ -1,22 +1,13 @@ -const Redis = require('ioredis') -const { redisUrl, offchainOracleAddress, oracleRpcUrl } = require('./config') -const { getArgsForOracle, setSafeInterval } = require('./utils') -const { toChecksumAddress } = require('web3-utils') -const redis = new Redis(redisUrl) -const Web3 = require('web3') -const web3 = new Web3( - new Web3.providers.HttpProvider(oracleRpcUrl, { - timeout: 200000, // ms - }), -) +const { offchainOracleAddress } = require('./config') +const { getArgsForOracle, setSafeInterval, toChecksumAddress, toBN } = require('./utils') +const { redis } = require('./modules/redis') +const web3 = require('./modules/web3')() const offchainOracleABI = require('../abis/OffchainOracle.abi.json') const offchainOracle = new web3.eth.Contract(offchainOracleABI, offchainOracleAddress) const { tokenAddresses, oneUintAmount, currencyLookup } = getArgsForOracle() -const { toBN } = require('web3-utils') - async function main() { try { const ethPrices = {} @@ -40,6 +31,7 @@ async function main() { await redis.hmset('prices', ethPrices) console.log('Wrote following prices to redis', ethPrices) } catch (e) { + redis.zadd('errors', new Date().getTime(), e.message) console.error('priceWatcher error', e) } } diff --git a/src/queue.js b/src/queue.js index dbd2ad4..7935e01 100644 --- a/src/queue.js +++ b/src/queue.js @@ -1,9 +1,7 @@ const { v4: uuid } = require('uuid') const Queue = require('bull') -const Redis = require('ioredis') -const { redisUrl } = require('./config') const { status } = require('./constants') -const redis = new Redis(redisUrl) +const { redis, redisUrl } = require('./modules/redis') const queue = new Queue('proofs', redisUrl, { lockDuration: 300000, // Key expiration time for job locks. diff --git a/src/router.js b/src/router.js new file mode 100644 index 0000000..16c8baa --- /dev/null +++ b/src/router.js @@ -0,0 +1,29 @@ +const { controller, status } = require('./contollers') +const router = require('express').Router() + +// Add CORS headers +router.use((req, res, next) => { + res.header('Access-Control-Allow-Origin', '*') + res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept') + next() +}) + +// Log error to console but don't send it to the client to avoid leaking data +router.use((err, req, res, next) => { + if (err) { + console.error(err) + return res.sendStatus(500) + } + next() +}) + +router.get('/', status.index) +router.get('/v1/status', status.status) +router.get('/v1/jobs/:id', status.getJob) +router.post('/v1/tornadoWithdraw', controller.tornadoWithdraw) +router.get('/status', status.status) +router.post('/relay', controller.tornadoWithdraw) +router.post('/v1/miningReward', controller.miningReward) +router.post('/v1/miningWithdraw', controller.miningWithdraw) + +module.exports = router diff --git a/src/server.js b/src/server.js index 8516a7d..8f9fb4d 100644 --- a/src/server.js +++ b/src/server.js @@ -1,41 +1,14 @@ const express = require('express') -const status = require('./status') -const controller = require('./controller') const { port, rewardAccount } = require('./config') const { version } = require('../package.json') -const { isAddress } = require('web3-utils') - -const app = express() -app.use(express.json()) - -// Add CORS headers -app.use((req, res, next) => { - res.header('Access-Control-Allow-Origin', '*') - res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept') - next() -}) - -// Log error to console but don't send it to the client to avoid leaking data -app.use((err, req, res, next) => { - if (err) { - console.error(err) - return res.sendStatus(500) - } - next() -}) - -app.get('/', status.index) -app.get('/v1/status', status.status) -app.get('/v1/jobs/:id', status.getJob) -app.post('/v1/tornadoWithdraw', controller.tornadoWithdraw) -app.get('/status', status.status) -app.post('/relay', controller.tornadoWithdraw) -app.post('/v1/miningReward', controller.miningReward) -app.post('/v1/miningWithdraw', controller.miningWithdraw) +const { isAddress } = require('./utils') +const router = require('./router') if (!isAddress(rewardAccount)) { throw new Error('No REWARD_ACCOUNT specified') } - +const app = express() +app.use(express.json()) +app.use(router) app.listen(port) console.log(`Relayer ${version} started on port ${port}`) diff --git a/src/treeWatcher.js b/src/treeWatcher.js index 4fe6c09..401dbc5 100644 --- a/src/treeWatcher.js +++ b/src/treeWatcher.js @@ -1,21 +1,10 @@ const MerkleTree = require('fixed-merkle-tree') -const { redisUrl, wsRpcUrl, minerMerkleTreeHeight, torn, netId } = require('./config') -const { poseidonHash2 } = require('./utils') -const { toBN } = require('web3-utils') -const Redis = require('ioredis') -const redis = new Redis(redisUrl) -const ENSResolver = require('./resolver') -const resolver = new ENSResolver() -const Web3 = require('web3') -const web3 = new Web3( - new Web3.providers.WebsocketProvider(wsRpcUrl, { - clientConfig: { - maxReceivedFrameSize: 100000000, - maxReceivedMessageSize: 100000000, - }, - }), -) +const { minerMerkleTreeHeight, torn, netId } = require('./config') +const { poseidonHash2, toBN } = require('./utils') +const resolver = require('./modules/resolver') +const web3 = require('./modules/web3')('ws') const MinerABI = require('../abis/mining.abi.json') +const { redis } = require('./modules/redis') let contract // eslint-disable-next-line no-unused-vars @@ -123,7 +112,7 @@ async function init() { const newCommitments = newEvents .sort((a, b) => a.returnValues.index - b.returnValues.index) .map(e => toBN(e.returnValues.commitment)) - .filter((item, index, arr) => !index || item != arr[index - 1]) + .filter((item, index, arr) => !index || item !== arr[index - 1]) const commitments = cachedCommitments.concat(newCommitments) @@ -134,6 +123,7 @@ async function init() { eventSubscription = contract.events.NewAccount({ fromBlock: toBlock + 1 }, processNewEvent) blockSubscription = web3.eth.subscribe('newBlockHeaders', processNewBlock) } catch (e) { + redis.zadd('errors', new Date().getTime(), e.message) console.error('error on init treeWatcher', e.message) } } diff --git a/src/utils.js b/src/utils.js index 2d6486f..6b8bbc6 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,6 @@ const { instances, netId } = require('./config') const { poseidon } = require('circomlib') -const { toBN, toChecksumAddress, BN } = require('web3-utils') +const { toBN, toChecksumAddress, BN, fromWei, isAddress, toWei } = require('web3-utils') const TOKENS = { torn: { @@ -118,6 +118,13 @@ function fromDecimals(value, decimals) { return new BN(wei.toString(10), 10) } +class RelayerError extends Error { + constructor(message, score = 0) { + super(message) + this.score = score + } +} + module.exports = { getInstance, setSafeInterval, @@ -126,4 +133,11 @@ module.exports = { when, getArgsForOracle, fromDecimals, + toBN, + toChecksumAddress, + fromWei, + toWei, + BN, + isAddress, + RelayerError, } diff --git a/src/worker.js b/src/worker.js index d17b05c..6e5fb59 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,8 +1,5 @@ const fs = require('fs') -const Web3 = require('web3') -const { toBN, toWei, fromWei, toChecksumAddress } = require('web3-utils') const MerkleTree = require('fixed-merkle-tree') -const Redis = require('ioredis') const { GasPriceOracle } = require('gas-price-oracle') const { Utils, Controller } = require('tornado-anonymity-mining') @@ -10,14 +7,22 @@ const swapABI = require('../abis/swap.abi.json') const miningABI = require('../abis/mining.abi.json') const tornadoABI = require('../abis/tornadoABI.json') const tornadoProxyABI = require('../abis/tornadoProxyABI.json') -const aggregatorAbi = require('../abis/Aggregator.abi.json') const { queue } = require('./queue') -const { poseidonHash2, getInstance, fromDecimals, sleep } = require('./utils') +const { + poseidonHash2, + getInstance, + fromDecimals, + sleep, + toBN, + toWei, + fromWei, + toChecksumAddress, + RelayerError, +} = require('./utils') const { jobType, status } = require('./constants') const { torn, netId, - redisUrl, gasLimits, instances, privateKey, @@ -28,9 +33,10 @@ const { tornadoServiceFee, tornadoGoerliProxy, } = require('./config') -const ENSResolver = require('./resolver') -const resolver = new ENSResolver() +const resolver = require('./modules/resolver') const { TxManager } = require('tx-manager') +const { redis, redisSubscribe } = require('./modules/redis') +const getWeb3 = require('./modules/web3') let web3 let currentTx @@ -40,8 +46,6 @@ let txManager let controller let swap let minerContract -const redis = new Redis(redisUrl) -const redisSubscribe = new Redis(redisUrl) const gasPriceOracle = new GasPriceOracle({ defaultRpc: oracleRpcUrl }) async function fetchTree() { @@ -76,7 +80,8 @@ async function fetchTree() { async function start() { try { - web3 = new Web3(httpRpcUrl) + await clearErrors() + web3 = getWeb3() const { CONFIRMATIONS, MAX_GAS_PRICE } = process.env txManager = new TxManager({ privateKey, @@ -101,6 +106,7 @@ async function start() { queue.process(processJob) console.log('Worker started') } catch (e) { + redis.zadd('errors', new Date().getTime(), e.message) console.error('error on start worker', e.message) } } @@ -116,13 +122,11 @@ async function getGasPrice() { const block = await web3.eth.getBlock('latest') if (block && block.baseFeePerGas) { - const baseFeePerGas = toBN(block.baseFeePerGas) - return baseFeePerGas + return toBN(block.baseFeePerGas) } const { fast } = await gasPriceOracle.gasPrices() - const gasPrice = toBN(toWei(fast.toString(), 'gwei')) - return gasPrice + return toBN(toWei(fast.toString(), 'gwei')) } async function checkTornadoFee({ args, contract }) { @@ -160,7 +164,10 @@ async function checkTornadoFee({ args, contract }) { fromWei(feePercent.toString()), ) if (fee.lt(desiredFee)) { - throw new Error('Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.') + throw new RelayerError( + 'Provided fee is not enough. Probably it is a Gas Price spike, try to resubmit.', + 0, + ) } } @@ -196,7 +203,6 @@ async function checkMiningFee({ args }) { } } - async function getProxyContract() { let proxyAddress if (netId === 5) { @@ -262,7 +268,7 @@ async function isOutdatedTreeRevert(receipt, currentTx) { async function processJob(job) { try { if (!jobType[job.data.type]) { - throw new Error(`Unknown job type: ${job.data.type}`) + throw new RelayerError(`Unknown job type: ${job.data.type}`) } currentJob = job await updateStatus(status.ACCEPTED) @@ -304,10 +310,10 @@ async function submitTx(job, retry = 0) { await updateStatus(status.RESUBMITTED) await submitTx(job, retry + 1) } else { - throw new Error('Tree update retry limit exceeded') + throw new RelayerError('Tree update retry limit exceeded') } } else { - throw new Error('Submitted transaction failed') + throw new RelayerError('Submitted transaction failed') } } } catch (e) { @@ -323,10 +329,10 @@ async function submitTx(job, retry = 0) { console.log('Tree is still not up to date, resubmitting') await submitTx(job, retry + 1) } else { - throw new Error('Tree update retry limit exceeded') + throw new RelayerError('Tree update retry limit exceeded') } } else { - throw new Error(`Revert by smart contract ${e.message}`) + throw new RelayerError(`Revert by smart contract ${e.message}`) } } } @@ -349,4 +355,9 @@ async function updateStatus(status) { await currentJob.update(currentJob.data) } +async function clearErrors() { + console.log('Errors list cleared') + await redis.del('errors') +} + start() diff --git a/test/validator.js b/test/validator.js index d329233..9d5cf93 100644 --- a/test/validator.js +++ b/test/validator.js @@ -4,7 +4,7 @@ const { getTornadoWithdrawInputError, getMiningRewardInputError, getMiningWithdrawInputError, -} = require('../src/validator') +} = require('../src/modules/validator') describe('Validator', () => { describe('#getTornadoWithdrawInputError', () => {