diff --git a/.gitignore b/.gitignore index 9772695..f295ee4 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules/ .env .env.mainnet .env.kovan -kovan.* \ No newline at end of file +kovan.* +dump.rdb diff --git a/src/controller.js b/src/controller.js index 68bd029..b789c15 100644 --- a/src/controller.js +++ b/src/controller.js @@ -4,6 +4,7 @@ const { getMiningWithdrawInputError, } = require('./validator') const { postJob } = require('./queue') +const { jobType } = require('./utils') async function tornadoWithdraw(req, res) { const inputError = getTornadoWithdrawInputError(req.body) @@ -13,8 +14,8 @@ async function tornadoWithdraw(req, res) { } const id = await postJob({ - type: 'tornadoWithdraw', - data: req.body, + type: jobType.TORNADO_WITHDRAW, + request: req.body, }) return res.json({ id }) } @@ -27,8 +28,8 @@ async function miningReward(req, res) { } const id = await postJob({ - type: 'miningReward', - data: req.body, + type: jobType.MINING_REWARD, + request: req.body, }) return res.json({ id }) } @@ -41,8 +42,8 @@ async function miningWithdraw(req, res) { } const id = await postJob({ - type: 'miningWithdraw', - data: req.body, + type: jobType.MINING_WITHDRAW, + request: req.body, }) return res.json({ id }) } diff --git a/src/queue.js b/src/queue.js index 63cced4..9180833 100644 --- a/src/queue.js +++ b/src/queue.js @@ -6,14 +6,14 @@ const redis = new Redis(redisUrl) const queue = new Queue('proofs', redisUrl) -async function postJob({ type, data }) { +async function postJob({ type, request }) { const id = uuid() const job = await queue.add( { id, type, - data, + ...request, // proof, args, ?contract }, // { removeOnComplete: true }, ) @@ -28,8 +28,10 @@ async function getJob(uuid) { async function getJobStatus(uuid) { const job = await getJob(uuid) - // todo job.data doesn't contain current status and other stuff? - return job.data + return { + ...job.data, + failedReason: job.failedReason, + } } module.exports = { diff --git a/src/utils.js b/src/utils.js index 1201fee..bb682fc 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,12 @@ const { instances, netId } = require('../config') const { poseidon } = require('circomlib') const { toBN, toChecksumAddress } = require('web3-utils') +const jobType = Object.freeze({ + TORNADO_WITHDRAW: 'TORNADO_WITHDRAW', + MINING_REWARD: 'MINING_REWARD', + MINING_WITHDRAW: 'MINING_WITHDRAW', +}) + const sleep = ms => new Promise(res => setTimeout(res, ms)) function getInstance(address) { @@ -17,16 +23,6 @@ function getInstance(address) { return null } -// async function setSafeInterval(func, interval) { -// try { -// await func() -// } catch (e) { -// console.error('Unhandled promise error:', e) -// } finally { -// setTimeout(() => setSafeInterval(func, interval), interval) -// } -// } - const poseidonHash = items => toBN(poseidon(items).toString()) const poseidonHash2 = (a, b) => poseidonHash([a, b]) @@ -59,4 +55,5 @@ module.exports = { poseidonHash2, sleep, when, + jobType, } diff --git a/src/worker.js b/src/worker.js index 9b2c1a5..bbb7e90 100644 --- a/src/worker.js +++ b/src/worker.js @@ -1,6 +1,6 @@ const fs = require('fs') const Web3 = require('web3') -const { numberToHex, toBN } = require('web3-utils') +const { toBN } = require('web3-utils') const MerkleTree = require('fixed-merkle-tree') const Redis = require('ioredis') const { GasPriceOracle } = require('gas-price-oracle') @@ -9,7 +9,7 @@ const tornadoABI = require('../abis/tornadoABI.json') const miningABI = require('../abis/mining.abi.json') const swapABI = require('../abis/swap.abi.json') const { queue } = require('./queue') -const { poseidonHash2 } = require('./utils') +const { poseidonHash2, jobType } = require('./utils') const { rpcUrl, redisUrl, privateKey, updateConfig, swapAddress, minerAddress } = require('../config') const { TxManager } = require('tx-manager') const { Controller } = require('tornado-cash-anonymity-mining') @@ -24,6 +24,13 @@ const redis = new Redis(redisUrl) const redisSubscribe = new Redis(redisUrl) const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl }) +const status = Object.freeze({ + ACCEPTED: 'ACCEPTED', + SENT: 'SENT', + MINED: 'MINED', + CONFIRMED: 'CONFIRMED', +}) + async function fetchTree() { console.log('got tree update') const elements = await redis.get('tree:elements') @@ -31,14 +38,14 @@ async function fetchTree() { tree = MerkleTree.deserialize(JSON.parse(elements, convert), poseidonHash2) if (currentTx && currentJob && ['miningReward', 'miningWithdraw'].includes(currentJob.data.type)) { - const { proof, args } = currentJob.data.data + const { proof, args } = currentJob.data if (toBN(args.account.inputRoot).eq(toBN(tree.root()))) { return } const update = await controller.treeUpdate(args.account.outputCommitment, tree) - const instance = new web3.eth.Contract(tornadoABI, minerAddress) + const instance = new web3.eth.Contract(miningABI, minerAddress) const data = currentJob.data.type === 'miningReward' ? instance.methods.reward(proof, args, update.proof, update.args).encodeABI() @@ -67,110 +74,79 @@ async function start() { console.log('Worker started') } -async function checkTornadoFee(/* contract, fee, refund*/) { +function checkFee({ data, type }) { + if (type === jobType.TORNADO_WITHDRAW) { + return checkTornadoFee(data) + } + return checkMiningFee(data) +} + +async function checkTornadoFee({ args, contract }) { + console.log('args, contract', args, contract) const { fast } = await gasPriceOracle.gasPrices() console.log('fast', fast) } -async function checkMiningFee(points) { +async function checkMiningFee({ args }) { const swap = new web3.eth.Contract(swapABI, swapAddress) - const TornAmount = await swap.getExpectedReturn(points).call() + const TornAmount = await swap.getExpectedReturn(args.fee).call() + console.log('TornAmount', TornAmount) // todo: use desired torn/eth rate and compute the same way as in tornado } -async function process(job) { - switch (job.data.type) { - case 'tornadoWithdraw': - await processTornadoWithdraw(job) - break - case 'miningReward': - await processMiningReward(job) - break - case 'miningWithdraw': - await processMiningWithdraw(job) - break - default: - throw new Error(`Unknown job type: ${job.data.type}`) - } -} +// may be this looks better +// const isTornadoWithdraw = type === jobType.TORNADO_WITHDRAW +// const ABI = isTornadoWithdraw ? tornadoABI : miningABI +// const contractAddress = isTornadoWithdraw ? data.contract : minerAddress +// const value = isTornadoWithdraw ? data.args[5] : 0 // refund +function getTxObject({ data, type }) { + let ABI, + contractAddress, + value = + type === jobType.TORNADO_WITHDRAW + ? [tornadoABI, data.contract, data.args[5]] + : [miningABI, minerAddress, 0] + const method = type !== jobType.MINING_REWARD ? 'withdraw' : 'reward' -async function processTornadoWithdraw(job) { - currentJob = job - console.log(`Start processing a new Tornado Withdraw job #${job.id}`) - const { proof, args, contract } = job.data.data - const fee = toBN(args[4]) - const refund = toBN(args[5]) - await checkTornadoFee(contract, fee, refund) + const contract = new web3.eth.Contract(ABI, contractAddress) + const calldata = contract.methods[method](data.proof, ...data.args).encodeABI() - const instance = new web3.eth.Contract(tornadoABI, contract) - const data = instance.methods.withdraw(proof, ...args).encodeABI() - currentTx = await txManager.createTx({ - value: numberToHex(refund), + return { + value, to: contract, - data, - }) - - try { - await currentTx - .send() - .on('transactionHash', updateTxHash) - .on('mined', receipt => { - console.log('Mined in block', receipt.blockNumber) - }) - .on('confirmations', updateConfirmations) - } catch (e) { - console.error('Revert', e) - throw new Error(`Revert by smart contract ${e.message}`) + data: calldata, } } -async function processMiningReward(job) { - currentJob = job - console.log(`Start processing a new Mining Reward job #${job.id}`) - const { proof, args } = job.data.data - - const contract = new web3.eth.Contract(miningABI, minerAddress) - const data = contract.methods.reward(proof, args).encodeABI() - currentTx = await txManager.createTx({ - to: minerAddress, - data, - }) - - try { - await currentTx - .send() - .on('transactionHash', updateTxHash) - .on('mined', receipt => { - console.log('Mined in block', receipt.blockNumber) - }) - .on('confirmations', updateConfirmations) - } catch (e) { - console.error('Revert', e) - throw new Error(`Revert by smart contract ${e.message}`) +async function process(job) { + if (!jobType[job.data.type]) { + throw new Error(`Unknown job type: ${job.data.type}`) } -} - -async function processMiningWithdraw(job) { + await updateStatus(status.ACCEPTED) currentJob = job - console.log(`Start processing a new Mining Withdraw job #${job.id}`) - const { proof, args } = job.data.data + console.log(`Start processing a new ${job.data.type} job #${job.id}`) + await checkFee(job) + if (job.data.type !== jobType.TORNADO_WITHDRAW) { + // precheck if root is up to date + } - const contract = new web3.eth.Contract(miningABI, minerAddress) - const data = contract.methods.withdraw(proof, args).encodeABI() - currentTx = await txManager.createTx({ - to: minerAddress, - data, - }) + currentTx = await txManager.createTx(getTxObject(job)) try { await currentTx .send() - .on('transactionHash', updateTxHash) + .on('transactionHash', txHash => { + updateTxHash(txHash) + updateStatus(status.SENT) + }) .on('mined', receipt => { console.log('Mined in block', receipt.blockNumber) + updateStatus(status.MINED) }) .on('confirmations', updateConfirmations) + + await updateStatus(status.CONFIRMED) } catch (e) { console.error('Revert', e) throw new Error(`Revert by smart contract ${e.message}`) @@ -189,4 +165,10 @@ async function updateConfirmations(confirmations) { await currentJob.update(currentJob.data) } +async function updateStatus(status) { + console.log(`Job status updated ${status}`) + currentJob.data.status = status + await currentJob.update(currentJob.data) +} + module.exports = { start, process }