one process method for all job types

This commit is contained in:
Alexey 2020-10-02 15:09:33 +03:00
parent 9cd97feef0
commit 00e17b1687
5 changed files with 86 additions and 103 deletions

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ node_modules/
.env.mainnet .env.mainnet
.env.kovan .env.kovan
kovan.* kovan.*
dump.rdb

View File

@ -4,6 +4,7 @@ const {
getMiningWithdrawInputError, getMiningWithdrawInputError,
} = require('./validator') } = require('./validator')
const { postJob } = require('./queue') const { postJob } = require('./queue')
const { jobType } = require('./utils')
async function tornadoWithdraw(req, res) { async function tornadoWithdraw(req, res) {
const inputError = getTornadoWithdrawInputError(req.body) const inputError = getTornadoWithdrawInputError(req.body)
@ -13,8 +14,8 @@ async function tornadoWithdraw(req, res) {
} }
const id = await postJob({ const id = await postJob({
type: 'tornadoWithdraw', type: jobType.TORNADO_WITHDRAW,
data: req.body, request: req.body,
}) })
return res.json({ id }) return res.json({ id })
} }
@ -27,8 +28,8 @@ async function miningReward(req, res) {
} }
const id = await postJob({ const id = await postJob({
type: 'miningReward', type: jobType.MINING_REWARD,
data: req.body, request: req.body,
}) })
return res.json({ id }) return res.json({ id })
} }
@ -41,8 +42,8 @@ async function miningWithdraw(req, res) {
} }
const id = await postJob({ const id = await postJob({
type: 'miningWithdraw', type: jobType.MINING_WITHDRAW,
data: req.body, request: req.body,
}) })
return res.json({ id }) return res.json({ id })
} }

View File

@ -6,14 +6,14 @@ const redis = new Redis(redisUrl)
const queue = new Queue('proofs', redisUrl) const queue = new Queue('proofs', redisUrl)
async function postJob({ type, data }) { async function postJob({ type, request }) {
const id = uuid() const id = uuid()
const job = await queue.add( const job = await queue.add(
{ {
id, id,
type, type,
data, ...request, // proof, args, ?contract
}, },
// { removeOnComplete: true }, // { removeOnComplete: true },
) )
@ -28,8 +28,10 @@ async function getJob(uuid) {
async function getJobStatus(uuid) { async function getJobStatus(uuid) {
const job = await getJob(uuid) const job = await getJob(uuid)
// todo job.data doesn't contain current status and other stuff? return {
return job.data ...job.data,
failedReason: job.failedReason,
}
} }
module.exports = { module.exports = {

View File

@ -2,6 +2,12 @@ const { instances, netId } = require('../config')
const { poseidon } = require('circomlib') const { poseidon } = require('circomlib')
const { toBN, toChecksumAddress } = require('web3-utils') 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)) const sleep = ms => new Promise(res => setTimeout(res, ms))
function getInstance(address) { function getInstance(address) {
@ -17,16 +23,6 @@ function getInstance(address) {
return null 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 poseidonHash = items => toBN(poseidon(items).toString())
const poseidonHash2 = (a, b) => poseidonHash([a, b]) const poseidonHash2 = (a, b) => poseidonHash([a, b])
@ -59,4 +55,5 @@ module.exports = {
poseidonHash2, poseidonHash2,
sleep, sleep,
when, when,
jobType,
} }

View File

@ -1,6 +1,6 @@
const fs = require('fs') const fs = require('fs')
const Web3 = require('web3') const Web3 = require('web3')
const { numberToHex, toBN } = require('web3-utils') const { toBN } = require('web3-utils')
const MerkleTree = require('fixed-merkle-tree') const MerkleTree = require('fixed-merkle-tree')
const Redis = require('ioredis') const Redis = require('ioredis')
const { GasPriceOracle } = require('gas-price-oracle') const { GasPriceOracle } = require('gas-price-oracle')
@ -9,7 +9,7 @@ const tornadoABI = require('../abis/tornadoABI.json')
const miningABI = require('../abis/mining.abi.json') const miningABI = require('../abis/mining.abi.json')
const swapABI = require('../abis/swap.abi.json') const swapABI = require('../abis/swap.abi.json')
const { queue } = require('./queue') const { queue } = require('./queue')
const { poseidonHash2 } = require('./utils') const { poseidonHash2, jobType } = require('./utils')
const { rpcUrl, redisUrl, privateKey, updateConfig, swapAddress, minerAddress } = require('../config') const { rpcUrl, redisUrl, privateKey, updateConfig, swapAddress, minerAddress } = require('../config')
const { TxManager } = require('tx-manager') const { TxManager } = require('tx-manager')
const { Controller } = require('tornado-cash-anonymity-mining') const { Controller } = require('tornado-cash-anonymity-mining')
@ -24,6 +24,13 @@ const redis = new Redis(redisUrl)
const redisSubscribe = new Redis(redisUrl) const redisSubscribe = new Redis(redisUrl)
const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl }) const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
const status = Object.freeze({
ACCEPTED: 'ACCEPTED',
SENT: 'SENT',
MINED: 'MINED',
CONFIRMED: 'CONFIRMED',
})
async function fetchTree() { async function fetchTree() {
console.log('got tree update') console.log('got tree update')
const elements = await redis.get('tree:elements') const elements = await redis.get('tree:elements')
@ -31,14 +38,14 @@ async function fetchTree() {
tree = MerkleTree.deserialize(JSON.parse(elements, convert), poseidonHash2) tree = MerkleTree.deserialize(JSON.parse(elements, convert), poseidonHash2)
if (currentTx && currentJob && ['miningReward', 'miningWithdraw'].includes(currentJob.data.type)) { 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()))) { if (toBN(args.account.inputRoot).eq(toBN(tree.root()))) {
return return
} }
const update = await controller.treeUpdate(args.account.outputCommitment, tree) 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 = const data =
currentJob.data.type === 'miningReward' currentJob.data.type === 'miningReward'
? instance.methods.reward(proof, args, update.proof, update.args).encodeABI() ? instance.methods.reward(proof, args, update.proof, update.args).encodeABI()
@ -67,110 +74,79 @@ async function start() {
console.log('Worker started') 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() const { fast } = await gasPriceOracle.gasPrices()
console.log('fast', fast) console.log('fast', fast)
} }
async function checkMiningFee(points) { async function checkMiningFee({ args }) {
const swap = new web3.eth.Contract(swapABI, swapAddress) 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 // todo: use desired torn/eth rate and compute the same way as in tornado
} }
// 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'
const contract = new web3.eth.Contract(ABI, contractAddress)
const calldata = contract.methods[method](data.proof, ...data.args).encodeABI()
return {
value,
to: contract,
data: calldata,
}
}
async function process(job) { async function process(job) {
switch (job.data.type) { if (!jobType[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}`) throw new Error(`Unknown job type: ${job.data.type}`)
} }
} await updateStatus(status.ACCEPTED)
async function processTornadoWithdraw(job) {
currentJob = job currentJob = job
console.log(`Start processing a new Tornado Withdraw job #${job.id}`) console.log(`Start processing a new ${job.data.type} job #${job.id}`)
const { proof, args, contract } = job.data.data await checkFee(job)
const fee = toBN(args[4]) if (job.data.type !== jobType.TORNADO_WITHDRAW) {
const refund = toBN(args[5]) // precheck if root is up to date
await checkTornadoFee(contract, fee, refund)
const instance = new web3.eth.Contract(tornadoABI, contract)
const data = instance.methods.withdraw(proof, ...args).encodeABI()
currentTx = await txManager.createTx({
value: numberToHex(refund),
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}`)
} }
}
async function processMiningReward(job) { currentTx = await txManager.createTx(getTxObject(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 { try {
await currentTx await currentTx
.send() .send()
.on('transactionHash', updateTxHash) .on('transactionHash', txHash => {
updateTxHash(txHash)
updateStatus(status.SENT)
})
.on('mined', receipt => { .on('mined', receipt => {
console.log('Mined in block', receipt.blockNumber) console.log('Mined in block', receipt.blockNumber)
updateStatus(status.MINED)
}) })
.on('confirmations', updateConfirmations) .on('confirmations', updateConfirmations)
} catch (e) {
console.error('Revert', e)
throw new Error(`Revert by smart contract ${e.message}`)
}
}
async function processMiningWithdraw(job) { await updateStatus(status.CONFIRMED)
currentJob = job
console.log(`Start processing a new Mining Withdraw job #${job.id}`)
const { proof, args } = job.data.data
const contract = new web3.eth.Contract(miningABI, minerAddress)
const data = contract.methods.withdraw(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) { } catch (e) {
console.error('Revert', e) console.error('Revert', e)
throw new Error(`Revert by smart contract ${e.message}`) throw new Error(`Revert by smart contract ${e.message}`)
@ -189,4 +165,10 @@ async function updateConfirmations(confirmations) {
await currentJob.update(currentJob.data) 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 } module.exports = { start, process }