This commit is contained in:
Alexey 2020-08-04 10:39:56 +03:00
parent cb6cd89665
commit 850cfb3f7e
11 changed files with 155 additions and 117 deletions

View file

@ -30,9 +30,7 @@ class Fetcher {
}
async fetchPrices() {
try {
let prices = await this.oracle.methods
.getPricesInETH(this.tokenAddresses, this.oneUintAmount)
.call()
let prices = await this.oracle.methods.getPricesInETH(this.tokenAddresses, this.oneUintAmount).call()
this.ethPrices = prices.reduce((acc, price, i) => {
acc[this.currencyLookup[this.tokenAddresses[i]]] = price
return acc

View file

@ -34,16 +34,17 @@ app.use(function (req, res, next) {
app.get('/', function (req, res) {
// just for testing purposes
res.send('This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/status>/status</a> for settings')
res.send(
'This is <a href=https://tornado.cash>tornado.cash</a> Relayer service. Check the <a href=/status>/status</a> for settings'
)
})
app.get('/status', async function (req, res) {
let nonce = await redisClient.get('nonce')
let latestBlock = null
try {
latestBlock = await web3.eth.getBlockNumber()
} catch(e) {
} catch (e) {
console.error('Problem with RPC', e)
}
const { ethPrices } = fetcher
@ -74,7 +75,12 @@ console.log(`mixers: ${JSON.stringify(mixers)}`)
console.log(`netId: ${netId}`)
console.log(`ethPrices: ${JSON.stringify(fetcher.ethPrices)}`)
const { GAS_PRICE_BUMP_PERCENTAGE, ALLOWABLE_PENDING_TX_TIMEOUT, NONCE_WATCHER_INTERVAL, MAX_GAS_PRICE } = process.env
const {
GAS_PRICE_BUMP_PERCENTAGE,
ALLOWABLE_PENDING_TX_TIMEOUT,
NONCE_WATCHER_INTERVAL,
MAX_GAS_PRICE
} = process.env
if (!NONCE_WATCHER_INTERVAL) {
console.log(`NONCE_WATCHER_INTERVAL is not set. Using default value ${watherInterval / 1000} sec`)
}

View file

@ -16,4 +16,4 @@ const redisOpts = {
}
}
module.exports = { redisOpts, redisClient }
module.exports = { redisOpts, redisClient }

View file

@ -1,9 +1,7 @@
const Queue = require('bull')
const { numberToHex, toWei, toHex, toBN, toChecksumAddress } = require('web3-utils')
const mixerABI = require('../abis/mixerABI.json')
const {
isValidProof, isValidArgs, isKnownContract, isEnoughFee
} = require('./utils')
const { isValidProof, isValidArgs, isKnownContract, isEnoughFee } = require('./utils')
const config = require('../config')
const { redisClient, redisOpts } = require('./redis')
@ -28,14 +26,15 @@ async function relayController(req, resp) {
return resp.status(400).json({ error: 'Proof format is invalid' })
}
({ valid, reason } = isValidArgs(args))
// eslint-disable-next-line no-extra-semi
;({ valid, reason } = isValidArgs(args))
if (!valid) {
console.log('Args are invalid:', reason)
return resp.status(400).json({ error: 'Withdraw arguments are invalid' })
}
let currency, amount
({ valid, currency, amount } = isKnownContract(contract))
;({ valid, currency, amount } = isKnownContract(contract))
if (!valid) {
console.log('Contract does not exist:', contract)
return resp.status(400).json({ error: 'This relayer does not support the token' })
@ -59,9 +58,20 @@ async function relayController(req, resp) {
return resp.status(400).json({ error: 'Relayer address is invalid' })
}
requestJob = await withdrawQueue.add({
contract, nullifierHash, root, proof, args, currency, amount, fee: fee.toString(), refund: refund.toString()
}, { removeOnComplete: true })
requestJob = await withdrawQueue.add(
{
contract,
nullifierHash,
root,
proof,
args,
currency,
amount,
fee: fee.toString(),
refund: refund.toString()
},
{ removeOnComplete: true }
)
reponseCbs[requestJob.id] = resp
}
@ -102,7 +112,15 @@ withdrawQueue.process(async function (job, done) {
gas += 50000
const ethPrices = fetcher.ethPrices
const { isEnough, reason } = isEnoughFee({ gas, gasPrices, currency, amount, refund: toBN(refund), ethPrices, fee: toBN(fee) })
const { isEnough, reason } = isEnoughFee({
gas,
gasPrices,
currency,
amount,
refund: toBN(refund),
ethPrices,
fee: toBN(fee)
})
if (!isEnough) {
console.log(`Wrong fee: ${reason}`)
done(null, {

View file

@ -38,38 +38,43 @@ class Sender {
let signedTx = await this.web3.eth.accounts.signTransaction(tx, config.privateKey)
let result = this.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
result.once('transactionHash', (txHash) => {
console.log(`A new successfully sent tx ${txHash}`)
if (done) {
done(null, {
status: 200,
msg: { txHash }
})
}
}).on('error', async (e) => {
console.log(`Error for tx with nonce ${tx.nonce}\n${e.message}`)
if (e.message === '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.'
|| e.message === 'Returned error: Transaction nonce is too low. Try incrementing the nonce.'
|| e.message === 'Returned error: nonce too low'
|| e.message === 'Returned error: replacement transaction underpriced') {
console.log('nonce too low, retrying')
if (retryAttempt <= 10) {
retryAttempt++
const newNonce = tx.nonce + 1
tx.nonce = newNonce
await redisClient.set('nonce', newNonce)
await redisClient.set('tx:' + newNonce, JSON.stringify(tx))
this.sendTx(tx, done, retryAttempt)
return
result
.once('transactionHash', (txHash) => {
console.log(`A new successfully sent tx ${txHash}`)
if (done) {
done(null, {
status: 200,
msg: { txHash }
})
}
}
if (done) {
done(null, {
status: 400,
msg: { error: 'Internal Relayer Error. Please use a different relayer service' }
})
}
})
})
.on('error', async (e) => {
console.log(`Error for tx with nonce ${tx.nonce}\n${e.message}`)
if (
e.message ===
'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.' ||
e.message === 'Returned error: Transaction nonce is too low. Try incrementing the nonce.' ||
e.message === 'Returned error: nonce too low' ||
e.message === 'Returned error: replacement transaction underpriced'
) {
console.log('nonce too low, retrying')
if (retryAttempt <= 10) {
retryAttempt++
const newNonce = tx.nonce + 1
tx.nonce = newNonce
await redisClient.set('nonce', newNonce)
await redisClient.set('tx:' + newNonce, JSON.stringify(tx))
this.sendTx(tx, done, retryAttempt)
return
}
}
if (done) {
done(null, {
status: 400,
msg: { error: 'Internal Relayer Error. Please use a different relayer service' }
})
}
})
}
}

View file

@ -8,9 +8,9 @@ function setup() {
web3.eth.accounts.wallet.add('0x' + privateKey)
web3.eth.defaultAccount = account.address
return web3
} catch(e) {
} catch (e) {
console.error('web3 failed')
}
}
const web3 = setup()
module.exports = web3
module.exports = web3

View file

@ -4,7 +4,7 @@ const { netId, mixers, relayerServiceFee } = require('../config')
function isValidProof(proof) {
// validator expects `websnarkUtils.toSolidityInput(proof)` output
if (!(proof)) {
if (!proof) {
return { valid: false, reason: 'The proof is empty.' }
}
@ -16,8 +16,7 @@ function isValidProof(proof) {
}
function isValidArgs(args) {
if (!(args)) {
if (!args) {
return { valid: false, reason: 'Args are empty' }
}
@ -25,18 +24,20 @@ function isValidArgs(args) {
return { valid: false, reason: 'Length of args is lower than 6' }
}
for(let signal of args) {
for (let signal of args) {
if (!isHexStrict(signal)) {
return { valid: false, reason: `Corrupted signal ${signal}` }
}
}
if (args[0].length !== 66 ||
args[1].length !== 66 ||
args[2].length !== 42 ||
args[3].length !== 42 ||
args[4].length !== 66 ||
args[5].length !== 66) {
if (
args[0].length !== 66 ||
args[1].length !== 66 ||
args[2].length !== 42 ||
args[3].length !== 42 ||
args[4].length !== 66 ||
args[5].length !== 66
) {
return { valid: false, reason: 'The length one of the signals is incorrect' }
}
@ -56,7 +57,7 @@ function isKnownContract(contract) {
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
return new Promise((resolve) => setTimeout(resolve, ms))
}
function fromDecimals(value, decimals) {
@ -77,9 +78,7 @@ function fromDecimals(value, decimals) {
// Split it into a whole and fractional part
const comps = ether.split('.')
if (comps.length > 2) {
throw new Error(
'[ethjs-unit] while converting number ' + value + ' to wei, too many decimal points'
)
throw new Error('[ethjs-unit] while converting number ' + value + ' to wei, too many decimal points')
}
let whole = comps[0]
@ -92,9 +91,7 @@ function fromDecimals(value, decimals) {
fraction = '0'
}
if (fraction.length > baseLength) {
throw new Error(
'[ethjs-unit] while converting number ' + value + ' to wei, too many decimal places'
)
throw new Error('[ethjs-unit] while converting number ' + value + ' to wei, too many decimal places')
}
while (fraction.length < baseLength) {
@ -114,9 +111,15 @@ function fromDecimals(value, decimals) {
function isEnoughFee({ gas, gasPrices, currency, amount, refund, ethPrices, fee }) {
const { decimals } = mixers[`netId${netId}`][currency]
const decimalsPoint = Math.floor(relayerServiceFee) === relayerServiceFee ? 0 : relayerServiceFee.toString().split('.')[1].length
const decimalsPoint =
Math.floor(relayerServiceFee) === relayerServiceFee
? 0
: relayerServiceFee.toString().split('.')[1].length
const roundDecimal = 10 ** decimalsPoint
const feePercent = toBN(fromDecimals(amount, decimals)).mul(toBN(relayerServiceFee * roundDecimal)).div(toBN(roundDecimal * 100))
const feePercent = toBN(fromDecimals(amount, decimals))
.mul(toBN(relayerServiceFee * roundDecimal))
.div(toBN(roundDecimal * 100))
const expense = toBN(toWei(gasPrices.fast.toString(), 'gwei')).mul(toBN(gas))
let desiredFee
switch (currency) {
@ -125,18 +128,23 @@ function isEnoughFee({ gas, gasPrices, currency, amount, refund, ethPrices, fee
break
}
default: {
desiredFee =
expense.add(refund)
.mul(toBN(10 ** decimals))
.div(toBN(ethPrices[currency]))
desiredFee = expense
.add(refund)
.mul(toBN(10 ** decimals))
.div(toBN(ethPrices[currency]))
desiredFee = desiredFee.add(feePercent)
break
}
}
console.log('sent fee, desired fee, feePercent', fee.toString(), desiredFee.toString(), feePercent.toString())
console.log(
'sent fee, desired fee, feePercent',
fee.toString(),
desiredFee.toString(),
feePercent.toString()
)
if (fee.lt(desiredFee)) {
return { isEnough: false, reason: 'Not enough fee' }
}
}
return { isEnough: true }
}
@ -148,11 +156,7 @@ function getArgsForOracle() {
Object.entries(tokens).map(([currency, data]) => {
if (currency !== 'eth') {
tokenAddresses.push(data.tokenAddress)
oneUintAmount.push(
toBN('10')
.pow(toBN(data.decimals.toString()))
.toString()
)
oneUintAmount.push(toBN('10').pow(toBN(data.decimals.toString())).toString())
currencyLookup[data.tokenAddress] = currency
}
})
@ -163,4 +167,12 @@ function getMixers() {
return mixers[`netId${netId}`]
}
module.exports = { isValidProof, isValidArgs, sleep, isKnownContract, isEnoughFee, getMixers, getArgsForOracle }
module.exports = {
isValidProof,
isValidArgs,
sleep,
isKnownContract,
isEnoughFee,
getMixers,
getArgsForOracle
}