diff --git a/config.js b/config.js
index 7543f0b..3b32a15 100644
--- a/config.js
+++ b/config.js
@@ -1,6 +1,11 @@
require('dotenv').config()
-module.exports = {
+function updateConfig(options) {
+ config = Object.assign(config, options)
+}
+
+let config = {
+ updateConfig,
netId: Number(process.env.NET_ID) || 42,
redisUrl: process.env.REDIS_URL || 'redis://127.0.0.1:6379',
rpcUrl: process.env.RPC_URL || 'https://kovan.infura.io/',
@@ -154,3 +159,5 @@ module.exports = {
gasBumpPercentage: process.env.GAS_PRICE_BUMP_PERCENTAGE || 20,
rewardAccount: '0x0000000000000000000000000000000000000000',
}
+
+module.exports = config
diff --git a/src/controller.js b/src/controller.js
index 025b9aa..3f34463 100644
--- a/src/controller.js
+++ b/src/controller.js
@@ -10,7 +10,7 @@ async function tornadoWithdraw(req, res) {
const { proof, args, contract } = req.body
const id = await postJob({
- type: 'withdraw',
+ type: 'tornadoWithdraw',
data: { proof, args, contract },
})
return res.json({ id })
diff --git a/src/queue.js b/src/queue.js
index bf82abb..cc44ecc 100644
--- a/src/queue.js
+++ b/src/queue.js
@@ -6,11 +6,10 @@ const redis = new Redis(redisUrl)
const queue = new Queue('proofs', redisUrl)
-async function postJob(type, data) {
+async function postJob({ type, data }) {
const id = uuid()
const job = await queue.add(
- 'proofs',
{
id,
type,
diff --git a/src/server.js b/src/server.js
index c610169..4b3ef25 100644
--- a/src/server.js
+++ b/src/server.js
@@ -1,8 +1,9 @@
const express = require('express')
-const status = require('status')
-const controller = require('controller')
+const status = require('./status')
+const controller = require('./controller')
const { port } = require('../config')
const { version } = require('../package.json')
+const worker = require('./worker')
const app = express()
app.use(express.json())
@@ -27,8 +28,11 @@ app.get('/', status.index)
app.get('/v1/status', status.status)
app.post('/v1/jobs/:id', status.getJob)
app.post('/v1/tornadoWithdraw', controller.tornadoWithdraw)
-app.post('/v1/miningReward', controller.miningReward)
-app.post('/v1/miningWithdraw', controller.miningWithdraw)
+app.get('/status', status.status)
+app.post('/relay', controller.tornadoWithdraw)
+// app.post('/v1/miningReward', controller.miningReward)
+// app.post('/v1/miningWithdraw', controller.miningWithdraw)
-console.log('Version:', version)
-app.listen(port)
+worker.start()
+app.listen(port || 8000)
+console.log(`Relayer ${version} started on port ${port || 8000}`)
diff --git a/src/status.js b/src/status.js
index 47115db..73bbf08 100644
--- a/src/status.js
+++ b/src/status.js
@@ -1,29 +1,34 @@
-const queue = require('queue')
+const queue = require('./queue')
+const { GasPriceOracle } = require('gas-price-oracle')
+const gasPriceOracle = new GasPriceOracle()
+const { netId, relayerServiceFee, instances } = require('../config')
+const { version } = require('../package.json')
async function status(req, res) {
- let nonce = await redisClient.get('nonce')
- let latestBlock = null
- try {
- latestBlock = await web3.eth.getBlockNumber()
- } catch (e) {
- console.error('Problem with RPC', e)
+ const ethPrices = {
+ dai: '6700000000000000', // 0.0067
+ cdai: '157380000000000',
+ cusdc: '164630000000000',
+ usdc: '7878580000000000',
+ usdt: '7864940000000000',
}
- const { ethPrices } = fetcher
res.json({
- relayerAddress: web3.eth.defaultAccount,
- mixers,
+ relayerAddress: require('../config').rewardAccount,
+ instances: instances.netId42,
gasPrices: await gasPriceOracle.gasPrices(),
netId,
ethPrices,
relayerServiceFee,
- nonce,
+ nonce: 123,
version,
- latestBlock
+ latestBlock: 12312312,
})
}
function index(req, res) {
- res.send('This is tornado.cash Relayer service. Check the /status for settings')
+ res.send(
+ 'This is tornado.cash Relayer service. Check the /status for settings',
+ )
}
async function getJob(req, res) {
diff --git a/src/validator.js b/src/validator.js
index 949ad7c..7d5353d 100644
--- a/src/validator.js
+++ b/src/validator.js
@@ -1,6 +1,5 @@
-const { isAddress } = require('web3-utils')
+const { isAddress, toChecksumAddress } = require('web3-utils')
const { getInstance } = require('./utils')
-const { rewardAccount } = require('../config')
const Ajv = require('ajv')
const ajv = new Ajv({ format: 'fast' })
@@ -13,7 +12,7 @@ ajv.addKeyword('isAddress', {
return false
}
},
- errors: true
+ errors: true,
})
ajv.addKeyword('isKnownContract', {
@@ -24,18 +23,18 @@ ajv.addKeyword('isKnownContract', {
return false
}
},
- errors: true
+ errors: true,
})
ajv.addKeyword('isFeeRecipient', {
validate: (schema, data) => {
try {
- return rewardAccount === data
+ return require('../config').rewardAccount === toChecksumAddress(data)
} catch (e) {
return false
}
},
- errors: true
+ errors: true,
})
const addressType = { type: 'string', pattern: '^0x[a-fA-F0-9]{40}$', isAddress: true }
@@ -54,11 +53,11 @@ const tornadoWithdrawSchema = {
type: 'array',
maxItems: 6,
minItems: 6,
- items: [bytes32Type, bytes32Type, addressType, relayerType, bytes32Type, bytes32Type]
- }
+ items: [bytes32Type, bytes32Type, addressType, relayerType, bytes32Type, bytes32Type],
+ },
},
additionalProperties: false,
- required: ['proof', 'contract', 'args']
+ required: ['proof', 'contract', 'args'],
}
const miningRewardSchema = {
@@ -79,10 +78,10 @@ const miningRewardSchema = {
type: 'object',
properties: {
relayer: relayerType,
- encryptedAccount: encryptedAccountType
+ encryptedAccount: encryptedAccountType,
},
additionalProperties: false,
- required: ['relayer', 'encryptedAccount']
+ required: ['relayer', 'encryptedAccount'],
},
account: {
type: 'object',
@@ -91,11 +90,17 @@ const miningRewardSchema = {
inputNullifierHash: bytes32Type,
outputRoot: bytes32Type,
outputPathIndices: bytes32Type,
- outputCommitment: bytes32Type
+ outputCommitment: bytes32Type,
},
additionalProperties: false,
- required: ['inputRoot', 'inputNullifierHash', 'outputRoot', 'outputPathIndices', 'outputCommitment']
- }
+ required: [
+ 'inputRoot',
+ 'inputNullifierHash',
+ 'outputRoot',
+ 'outputPathIndices',
+ 'outputCommitment',
+ ],
+ },
},
additionalProperties: false,
required: [
@@ -107,12 +112,12 @@ const miningRewardSchema = {
'depositRoot',
'withdrawalRoot',
'extData',
- 'account'
- ]
- }
+ 'account',
+ ],
+ },
},
additionalProperties: false,
- required: ['proof', 'args']
+ required: ['proof', 'args'],
}
const miningWithdrawSchema = {
@@ -130,10 +135,10 @@ const miningWithdrawSchema = {
properties: {
recipient: addressType,
relayer: relayerType,
- encryptedAccount: encryptedAccountType
+ encryptedAccount: encryptedAccountType,
},
additionalProperties: false,
- required: ['relayer', 'encryptedAccount', 'recipient']
+ required: ['relayer', 'encryptedAccount', 'recipient'],
},
account: {
type: 'object',
@@ -142,18 +147,24 @@ const miningWithdrawSchema = {
inputNullifierHash: bytes32Type,
outputRoot: bytes32Type,
outputPathIndices: bytes32Type,
- outputCommitment: bytes32Type
+ outputCommitment: bytes32Type,
},
additionalProperties: false,
- required: ['inputRoot', 'inputNullifierHash', 'outputRoot', 'outputPathIndices', 'outputCommitment']
- }
+ required: [
+ 'inputRoot',
+ 'inputNullifierHash',
+ 'outputRoot',
+ 'outputPathIndices',
+ 'outputCommitment',
+ ],
+ },
},
additionalProperties: false,
- required: ['amount', 'fee', 'extDataHash', 'extData', 'account']
- }
+ required: ['amount', 'fee', 'extDataHash', 'extData', 'account'],
+ },
},
additionalProperties: false,
- required: ['proof', 'args']
+ required: ['proof', 'args'],
}
const validateTornadoWithdraw = ajv.compile(tornadoWithdrawSchema)
@@ -184,5 +195,5 @@ function getMiningWithdrawInputError(data) {
module.exports = {
getTornadoWithdrawInputError,
getMiningRewardInputError,
- getMiningWithdrawInputError
+ getMiningWithdrawInputError,
}
diff --git a/src/worker.js b/src/worker.js
index 6914ee9..861b96e 100644
--- a/src/worker.js
+++ b/src/worker.js
@@ -1,27 +1,27 @@
-const { queue } = require('./queue')
const Web3 = require('web3')
-const { rpcUrl, redisUrl, privateKey, netId, gasBumpInterval, gasBumpPercentage, maxGasPrice } = require('../config')
-const { numberToHex, toWei, toHex, toBN, fromWei, toChecksumAddress, BN } = require('web3-utils')
-const tornadoABI = require('../abis/tornadoABI.json')
+const { numberToHex, toBN } = require('web3-utils')
const MerkleTree = require('fixed-merkle-tree')
-const { setSafeInterval, poseidonHash2 } = require('./utils')
const Redis = require('ioredis')
-const redis = new Redis(redisUrl)
-const redisSubscribe = new Redis(redisUrl)
const { GasPriceOracle } = require('gas-price-oracle')
-const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
-queue.process(process)
-redisSubscribe.subscribe('treeUpdate', fetchTree)
+
+const tornadoABI = require('../abis/tornadoABI.json')
+const { queue } = require('./queue')
+const { poseidonHash2 } = require('./utils')
+const { rpcUrl, redisUrl, privateKey, updateConfig, rewardAccount } = require('../config')
+const TxManager = require('./TxManager')
let web3
-let nonce
let currentTx
let currentJob
let tree
+let txManager
+const redis = new Redis(redisUrl)
+const redisSubscribe = new Redis(redisUrl)
+const gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
async function fetchTree() {
const elements = await redis.get('tree:elements')
- const convert = (_, val) => typeof(val) === 'string' ? toBN(val) : val
+ const convert = (_, val) => (typeof val === 'string' ? toBN(val) : val)
tree = MerkleTree.deserialize(JSON.parse(elements, convert), poseidonHash2)
if (currentTx) {
@@ -29,75 +29,55 @@ async function fetchTree() {
}
}
-async function watcher() {
- if (currentTx && Date.now() - currentTx.date > gasBumpInterval) {
- bumpGasPrice()
- }
-}
-
-async function bumpGasPrice() {
- const newGasPrice = toBN(currentTx.gasPrice).mul(toBN(gasBumpPercentage)).div(toBN(100))
- const maxGasPrice = toBN(toWei(maxGasPrice.toString(), 'Gwei'))
- currentTx.gasPrice = toHex(BN.min(newGasPrice, maxGasPrice))
- currentTx.date = Date.now()
- console.log(`Resubmitting with gas price ${fromWei(currentTx.gasPrice.toString(), 'gwei')} gwei`)
- await sendTx(currentTx, updateTxHash)
-}
-
-async function init() {
+async function start() {
web3 = new Web3(rpcUrl, null, { transactionConfirmationBlocks: 1 })
const account = web3.eth.accounts.privateKeyToAccount('0x' + privateKey)
web3.eth.accounts.wallet.add('0x' + privateKey)
web3.eth.defaultAccount = account.address
- nonce = await web3.eth.getTransactionCount(account.address, 'latest')
+ updateConfig({ rewardAccount: account.address })
+ txManager = new TxManager({ privateKey, rpcUrl })
+ queue.process(process)
+ redisSubscribe.subscribe('treeUpdate', fetchTree)
await fetchTree()
- setSafeInterval(watcher, 1000)
+ console.log('Worker started')
}
-async function checkTornadoFee(contract, fee, refund) {
-
+async function checkTornadoFee(/* contract, fee, refund*/) {
+ const { fast } = await gasPriceOracle.gasPrices()
+ console.log('fast', fast)
}
async function process(job) {
- if (job.type !== 'tornadoWithdraw') {
+ if (job.data.type !== 'tornadoWithdraw') {
throw new Error('not implemented')
}
currentJob = job
console.log(Date.now(), ' withdraw started', job.id)
- const { proof, args, contract } = job.data
+ 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)
const data = instance.methods.withdraw(proof, ...args).encodeABI()
- const gasPrices = await gasPriceOracle.gasPrices()
- currentTx = {
- from: web3.eth.defaultAccount,
+ currentTx = await txManager.createTx({
value: numberToHex(refund),
- gasPrice: toHex(toWei(gasPrices.fast.toString(), 'gwei')),
to: contract,
- netId,
data,
- nonce,
- }
+ })
try {
- // eslint-disable-next-line require-atomic-updates
- currentTx.gas = await web3.eth.estimateGas(currentTx)
- }
- catch (e) {
+ 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}`)
}
-
- nonce++
- await sendTx(currentTx, updateTxHash)
-}
-
-async function waitForTx(hash) {
-
}
async function updateTxHash(txHash) {
@@ -106,46 +86,10 @@ async function updateTxHash(txHash) {
await currentJob.update(currentJob.data)
}
-async function sendTx(tx, onTxHash, retryAttempt) {
- let signedTx = await this.web3.eth.accounts.signTransaction(tx, privateKey)
- let result = this.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
-
- if (onTxHash) {
- result.once('transactionHash', onTxHash)
- }
-
- try { // await returns once tx is mined
- await result
- } catch (e) {
- console.log(`Error for tx with nonce ${tx.nonce}\n${e.message}`)
- if (nonceErrors.includes(e.message)) {
- console.log('nonce too low, retrying')
- if (retryAttempt <= 10) {
- tx.nonce++
- return sendTx(tx, onTxHash, retryAttempt + 1)
- }
- }
- if (gasPriceErrors.includes(e.message)) {
- return bumpGasPrice()
- }
- throw new Error(e)
- }
+async function updateConfirmations(confirmations) {
+ console.log(`Confirmations count ${confirmations}`)
+ currentJob.data.confirmations = confirmations
+ await currentJob.update(currentJob.data)
}
-const nonceErrors = [
- 'Returned error: Transaction nonce is too low. Try incrementing the nonce.',
- 'Returned error: nonce too low',
-]
-
-const gasPriceErrors = [
- 'Returned error: Transaction gas price supplied is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce.',
- 'Returned error: replacement transaction underpriced',
-]
-
-async function main() {
- await init()
-
-}
-
-// main()
-fetchTree()
+module.exports = { start, process }