mirror of
https://github.com/tornadocash/tornado-relayer
synced 2024-02-02 15:04:06 +01:00
one process method for all job types
This commit is contained in:
parent
9cd97feef0
commit
00e17b1687
1
.gitignore
vendored
1
.gitignore
vendored
@ -4,3 +4,4 @@ node_modules/
|
|||||||
.env.mainnet
|
.env.mainnet
|
||||||
.env.kovan
|
.env.kovan
|
||||||
kovan.*
|
kovan.*
|
||||||
|
dump.rdb
|
||||||
|
@ -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 })
|
||||||
}
|
}
|
||||||
|
10
src/queue.js
10
src/queue.js
@ -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 = {
|
||||||
|
17
src/utils.js
17
src/utils.js
@ -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,
|
||||||
}
|
}
|
||||||
|
146
src/worker.js
146
src/worker.js
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
async function process(job) {
|
// may be this looks better
|
||||||
switch (job.data.type) {
|
// const isTornadoWithdraw = type === jobType.TORNADO_WITHDRAW
|
||||||
case 'tornadoWithdraw':
|
// const ABI = isTornadoWithdraw ? tornadoABI : miningABI
|
||||||
await processTornadoWithdraw(job)
|
// const contractAddress = isTornadoWithdraw ? data.contract : minerAddress
|
||||||
break
|
// const value = isTornadoWithdraw ? data.args[5] : 0 // refund
|
||||||
case 'miningReward':
|
function getTxObject({ data, type }) {
|
||||||
await processMiningReward(job)
|
let ABI,
|
||||||
break
|
contractAddress,
|
||||||
case 'miningWithdraw':
|
value =
|
||||||
await processMiningWithdraw(job)
|
type === jobType.TORNADO_WITHDRAW
|
||||||
break
|
? [tornadoABI, data.contract, data.args[5]]
|
||||||
default:
|
: [miningABI, minerAddress, 0]
|
||||||
throw new Error(`Unknown job type: ${job.data.type}`)
|
const method = type !== jobType.MINING_REWARD ? 'withdraw' : 'reward'
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function processTornadoWithdraw(job) {
|
const contract = new web3.eth.Contract(ABI, contractAddress)
|
||||||
currentJob = job
|
const calldata = contract.methods[method](data.proof, ...data.args).encodeABI()
|
||||||
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 instance = new web3.eth.Contract(tornadoABI, contract)
|
return {
|
||||||
const data = instance.methods.withdraw(proof, ...args).encodeABI()
|
value,
|
||||||
currentTx = await txManager.createTx({
|
|
||||||
value: numberToHex(refund),
|
|
||||||
to: contract,
|
to: contract,
|
||||||
data,
|
data: calldata,
|
||||||
})
|
|
||||||
|
|
||||||
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) {
|
async function process(job) {
|
||||||
currentJob = job
|
if (!jobType[job.data.type]) {
|
||||||
console.log(`Start processing a new Mining Reward job #${job.id}`)
|
throw new Error(`Unknown job type: ${job.data.type}`)
|
||||||
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}`)
|
|
||||||
}
|
}
|
||||||
}
|
await updateStatus(status.ACCEPTED)
|
||||||
|
|
||||||
async function processMiningWithdraw(job) {
|
|
||||||
currentJob = job
|
currentJob = job
|
||||||
console.log(`Start processing a new Mining Withdraw job #${job.id}`)
|
console.log(`Start processing a new ${job.data.type} job #${job.id}`)
|
||||||
const { proof, args } = job.data.data
|
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)
|
currentTx = await txManager.createTx(getTxObject(job))
|
||||||
const data = contract.methods.withdraw(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)
|
||||||
|
|
||||||
|
await updateStatus(status.CONFIRMED)
|
||||||
} 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 }
|
||||||
|
Loading…
Reference in New Issue
Block a user