2020-10-02 08:38:31 +03:00
|
|
|
const fs = require('fs')
|
2020-09-28 05:28:34 +03:00
|
|
|
const Web3 = require('web3')
|
2020-10-02 15:09:33 +03:00
|
|
|
const { toBN } = require('web3-utils')
|
2020-09-28 05:28:34 +03:00
|
|
|
const MerkleTree = require('fixed-merkle-tree')
|
|
|
|
const Redis = require('ioredis')
|
|
|
|
const { GasPriceOracle } = require('gas-price-oracle')
|
2020-09-30 18:35:48 +03:00
|
|
|
|
|
|
|
const tornadoABI = require('../abis/tornadoABI.json')
|
2020-10-01 09:30:50 +03:00
|
|
|
const miningABI = require('../abis/mining.abi.json')
|
2020-10-02 08:38:31 +03:00
|
|
|
const swapABI = require('../abis/swap.abi.json')
|
2020-09-30 18:35:48 +03:00
|
|
|
const { queue } = require('./queue')
|
2020-10-02 15:09:33 +03:00
|
|
|
const { poseidonHash2, jobType } = require('./utils')
|
2020-10-02 13:16:43 +03:00
|
|
|
const { rpcUrl, redisUrl, privateKey, updateConfig, swapAddress, minerAddress } = require('../config')
|
|
|
|
const { TxManager } = require('tx-manager')
|
2020-10-02 08:38:31 +03:00
|
|
|
const { Controller } = require('tornado-cash-anonymity-mining')
|
2020-09-28 05:28:34 +03:00
|
|
|
|
|
|
|
let web3
|
|
|
|
let currentTx
|
2020-09-29 06:17:42 +03:00
|
|
|
let currentJob
|
2020-09-28 05:28:34 +03:00
|
|
|
let tree
|
2020-09-30 18:35:48 +03:00
|
|
|
let txManager
|
2020-10-02 08:38:31 +03:00
|
|
|
let controller
|
2020-09-30 18:35:48 +03:00
|
|
|
const redis = new Redis(redisUrl)
|
|
|
|
const redisSubscribe = new Redis(redisUrl)
|
|
|
|
const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
|
2020-09-28 05:28:34 +03:00
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
const status = Object.freeze({
|
|
|
|
ACCEPTED: 'ACCEPTED',
|
|
|
|
SENT: 'SENT',
|
|
|
|
MINED: 'MINED',
|
|
|
|
CONFIRMED: 'CONFIRMED',
|
|
|
|
})
|
|
|
|
|
2020-09-28 05:28:34 +03:00
|
|
|
async function fetchTree() {
|
2020-10-02 08:38:31 +03:00
|
|
|
console.log('got tree update')
|
2020-09-28 05:28:34 +03:00
|
|
|
const elements = await redis.get('tree:elements')
|
2020-09-30 18:35:48 +03:00
|
|
|
const convert = (_, val) => (typeof val === 'string' ? toBN(val) : val)
|
2020-09-28 05:28:34 +03:00
|
|
|
tree = MerkleTree.deserialize(JSON.parse(elements, convert), poseidonHash2)
|
|
|
|
|
2020-10-02 08:38:31 +03:00
|
|
|
if (currentTx && currentJob && ['miningReward', 'miningWithdraw'].includes(currentJob.data.type)) {
|
2020-10-02 15:09:33 +03:00
|
|
|
const { proof, args } = currentJob.data
|
2020-10-02 13:16:43 +03:00
|
|
|
if (toBN(args.account.inputRoot).eq(toBN(tree.root()))) {
|
2020-10-02 08:38:31 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const update = await controller.treeUpdate(args.account.outputCommitment, tree)
|
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
const instance = new web3.eth.Contract(miningABI, minerAddress)
|
2020-10-02 13:16:43 +03:00
|
|
|
const data =
|
|
|
|
currentJob.data.type === 'miningReward'
|
|
|
|
? instance.methods.reward(proof, args, update.proof, update.args).encodeABI()
|
|
|
|
: instance.methods.withdraw(proof, args, update.proof, update.args).encodeABI()
|
2020-10-02 08:38:31 +03:00
|
|
|
currentTx = await currentTx.replace({
|
|
|
|
to: minerAddress,
|
|
|
|
data,
|
|
|
|
})
|
|
|
|
console.log('replaced pending tx')
|
2020-09-28 05:28:34 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-30 18:35:48 +03:00
|
|
|
async function start() {
|
2020-10-01 07:01:02 +03:00
|
|
|
web3 = new Web3(rpcUrl)
|
2020-09-30 18:35:48 +03:00
|
|
|
txManager = new TxManager({ privateKey, rpcUrl })
|
2020-10-01 07:01:02 +03:00
|
|
|
updateConfig({ rewardAccount: txManager.address })
|
2020-09-30 18:35:48 +03:00
|
|
|
redisSubscribe.subscribe('treeUpdate', fetchTree)
|
2020-09-28 05:28:34 +03:00
|
|
|
await fetchTree()
|
2020-10-02 08:38:31 +03:00
|
|
|
const provingKeys = {
|
|
|
|
treeUpdateCircuit: require('../keys/TreeUpdate.json'),
|
|
|
|
treeUpdateProvingKey: fs.readFileSync('./keys/TreeUpdate_proving_key.bin').buffer,
|
|
|
|
}
|
|
|
|
controller = new Controller({ provingKeys })
|
|
|
|
await controller.init()
|
|
|
|
queue.process(process)
|
2020-09-30 18:35:48 +03:00
|
|
|
console.log('Worker started')
|
2020-09-28 05:28:34 +03:00
|
|
|
}
|
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
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)
|
2020-09-30 18:35:48 +03:00
|
|
|
const { fast } = await gasPriceOracle.gasPrices()
|
|
|
|
console.log('fast', fast)
|
2020-09-28 05:28:34 +03:00
|
|
|
}
|
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
async function checkMiningFee({ args }) {
|
2020-10-02 08:38:31 +03:00
|
|
|
const swap = new web3.eth.Contract(swapABI, swapAddress)
|
2020-10-03 10:02:38 +03:00
|
|
|
const TornAmount = await swap.methods.getExpectedReturn(args.fee).call()
|
2020-10-02 15:09:33 +03:00
|
|
|
console.log('TornAmount', TornAmount)
|
2020-10-02 08:38:31 +03:00
|
|
|
|
|
|
|
// todo: use desired torn/eth rate and compute the same way as in tornado
|
|
|
|
}
|
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
// 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,
|
2020-09-28 05:28:34 +03:00
|
|
|
to: contract,
|
2020-10-02 15:09:33 +03:00
|
|
|
data: calldata,
|
2020-09-29 06:17:42 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
async function process(job) {
|
|
|
|
if (!jobType[job.data.type]) {
|
|
|
|
throw new Error(`Unknown job type: ${job.data.type}`)
|
2020-10-01 09:30:50 +03:00
|
|
|
}
|
2020-10-02 15:09:33 +03:00
|
|
|
await updateStatus(status.ACCEPTED)
|
2020-10-01 09:30:50 +03:00
|
|
|
currentJob = job
|
2020-10-02 15:09:33 +03:00
|
|
|
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
|
|
|
|
}
|
2020-10-01 09:30:50 +03:00
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
currentTx = await txManager.createTx(getTxObject(job))
|
2020-10-01 09:30:50 +03:00
|
|
|
|
|
|
|
try {
|
|
|
|
await currentTx
|
|
|
|
.send()
|
2020-10-02 15:09:33 +03:00
|
|
|
.on('transactionHash', txHash => {
|
|
|
|
updateTxHash(txHash)
|
|
|
|
updateStatus(status.SENT)
|
|
|
|
})
|
2020-10-02 13:26:05 +03:00
|
|
|
.on('mined', receipt => {
|
2020-10-01 09:30:50 +03:00
|
|
|
console.log('Mined in block', receipt.blockNumber)
|
2020-10-02 15:09:33 +03:00
|
|
|
updateStatus(status.MINED)
|
2020-10-01 09:30:50 +03:00
|
|
|
})
|
|
|
|
.on('confirmations', updateConfirmations)
|
2020-10-02 15:09:33 +03:00
|
|
|
|
|
|
|
await updateStatus(status.CONFIRMED)
|
2020-10-01 09:30:50 +03:00
|
|
|
} catch (e) {
|
|
|
|
console.error('Revert', e)
|
|
|
|
throw new Error(`Revert by smart contract ${e.message}`)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-29 06:17:42 +03:00
|
|
|
async function updateTxHash(txHash) {
|
|
|
|
console.log(`A new successfully sent tx ${txHash}`)
|
|
|
|
currentJob.data.txHash = txHash
|
|
|
|
await currentJob.update(currentJob.data)
|
|
|
|
}
|
|
|
|
|
2020-09-30 18:35:48 +03:00
|
|
|
async function updateConfirmations(confirmations) {
|
|
|
|
console.log(`Confirmations count ${confirmations}`)
|
|
|
|
currentJob.data.confirmations = confirmations
|
|
|
|
await currentJob.update(currentJob.data)
|
2020-09-28 05:28:34 +03:00
|
|
|
}
|
|
|
|
|
2020-10-02 15:09:33 +03:00
|
|
|
async function updateStatus(status) {
|
|
|
|
console.log(`Job status updated ${status}`)
|
|
|
|
currentJob.data.status = status
|
|
|
|
await currentJob.update(currentJob.data)
|
|
|
|
}
|
|
|
|
|
2020-09-30 18:35:48 +03:00
|
|
|
module.exports = { start, process }
|