2019-12-12 05:58:58 -05:00
const Queue = require ( 'bull' )
2019-09-19 17:46:20 -04:00
const { numberToHex , toWei , toHex , toBN , toChecksumAddress } = require ( 'web3-utils' )
2019-12-03 08:26:16 -05:00
const mixerABI = require ( '../abis/mixerABI.json' )
2019-11-23 03:18:54 -05:00
const {
isValidProof , isValidArgs , isKnownContract , isEnoughFee
} = require ( './utils' )
2019-12-03 08:26:16 -05:00
const config = require ( '../config' )
2019-12-12 05:58:58 -05:00
const { redisClient , redisOpts } = require ( './redis' )
2019-07-18 10:45:33 -04:00
2019-11-23 03:18:54 -05:00
const { web3 , fetcher } = require ( './instances' )
2019-12-12 05:58:58 -05:00
const withdrawQueue = new Queue ( 'withdraw' , redisOpts )
2019-07-24 13:19:00 -04:00
2019-12-12 13:56:58 -05:00
const reponseCbs = { }
let respLambda = ( job , { msg , status } ) => {
const resp = reponseCbs [ job . id ]
resp . status ( status ) . json ( msg )
delete reponseCbs [ job . id ]
}
withdrawQueue . on ( 'completed' , respLambda )
2019-12-12 05:58:58 -05:00
async function relayController ( req , resp ) {
let requestJob
2019-12-12 13:56:58 -05:00
2019-11-14 08:24:01 -05:00
const { proof , args , contract } = req . body
let { valid , reason } = isValidProof ( proof )
2019-07-18 08:43:26 -04:00
if ( ! valid ) {
console . log ( 'Proof is invalid:' , reason )
2019-11-07 20:25:43 -05:00
return resp . status ( 400 ) . json ( { error : 'Proof format is invalid' } )
2019-07-18 08:43:26 -04:00
}
2019-09-19 17:46:20 -04:00
2019-11-14 08:24:01 -05:00
( { valid , reason } = isValidArgs ( args ) )
2019-09-19 17:46:20 -04:00
if ( ! valid ) {
2019-11-14 08:24:01 -05:00
console . log ( 'Args are invalid:' , reason )
return resp . status ( 400 ) . json ( { error : 'Withdraw arguments are invalid' } )
2019-09-19 17:46:20 -04:00
}
2019-07-18 08:43:26 -04:00
2019-11-26 10:01:37 -05:00
let currency , amount
( { valid , currency , amount } = isKnownContract ( contract ) )
2019-11-14 08:24:01 -05:00
if ( ! valid ) {
console . log ( 'Contract does not exist:' , contract )
return resp . status ( 400 ) . json ( { error : 'This relayer does not support the token' } )
2019-11-07 20:25:43 -05:00
}
2019-11-14 08:24:01 -05:00
const [ root , nullifierHash , recipient , relayer , fee , refund ] = [
args [ 0 ] ,
args [ 1 ] ,
toChecksumAddress ( args [ 2 ] ) ,
toChecksumAddress ( args [ 3 ] ) ,
toBN ( args [ 4 ] ) ,
toBN ( args [ 5 ] )
]
2019-11-23 04:20:08 -05:00
console . log ( 'fee, refund' , fee . toString ( ) , refund . toString ( ) )
2019-11-15 04:01:59 -05:00
if ( currency === 'eth' && ! refund . isZero ( ) ) {
return resp . status ( 400 ) . json ( { error : 'Cannot send refund for eth currency.' } )
}
2019-11-14 08:24:01 -05:00
if ( relayer !== web3 . eth . defaultAccount ) {
console . log ( 'This proof is for different relayer:' , relayer )
2019-09-19 17:46:20 -04:00
return resp . status ( 400 ) . json ( { error : 'Relayer address is invalid' } )
}
2019-07-18 08:43:26 -04:00
2019-12-12 05:58:58 -05:00
await redisClient . set ( 'foo' , 'bar' )
requestJob = await withdrawQueue . add ( {
2019-12-12 13:56:58 -05:00
contract , nullifierHash , root , proof , args , currency , amount , fee : fee . toString ( )
2019-12-12 05:58:58 -05:00
} , { removeOnComplete : true } )
2019-12-12 13:56:58 -05:00
reponseCbs [ requestJob . id ] = resp
2019-12-12 05:58:58 -05:00
}
withdrawQueue . process ( async function ( job , done ) {
console . log ( Date . now ( ) , ' withdraw started' , job . id )
const gasPrices = fetcher . gasPrices
2019-12-12 13:56:58 -05:00
const { contract , nullifierHash , root , proof , args , refund , currency , amount , fee } = job . data
// cb()
2019-12-12 05:58:58 -05:00
// job.data contains the custom data passed when the job was created
// job.id contains id of this job.
2019-07-18 08:43:26 -04:00
try {
2019-12-12 05:58:58 -05:00
const mixer = new web3 . eth . Contract ( mixerABI , contract )
2019-11-14 08:24:01 -05:00
const isSpent = await mixer . methods . isSpent ( nullifierHash ) . call ( )
2019-07-18 08:43:26 -04:00
if ( isSpent ) {
2019-12-12 05:58:58 -05:00
done ( null , {
status : 400 ,
msg : {
error : 'The note has been spent.'
}
} )
2019-07-18 08:43:26 -04:00
}
2019-11-14 08:24:01 -05:00
const isKnownRoot = await mixer . methods . isKnownRoot ( root ) . call ( )
2019-07-18 09:30:24 -04:00
if ( ! isKnownRoot ) {
2019-12-12 05:58:58 -05:00
done ( null , {
status : 400 ,
msg : {
error : 'The merkle root is too old or invalid.'
}
} )
2019-07-18 09:30:24 -04:00
}
2019-11-15 04:01:59 -05:00
2019-12-03 08:26:16 -05:00
let gas = await mixer . methods . withdraw ( proof , ... args ) . estimateGas ( {
2019-11-14 08:24:01 -05:00
from : web3 . eth . defaultAccount ,
value : refund
} )
2019-11-15 04:01:59 -05:00
gas += 50000
2019-11-23 03:18:54 -05:00
const ethPrices = fetcher . ethPrices
2019-12-12 05:58:58 -05:00
const { isEnough , reason } = isEnoughFee ( { gas , gasPrices , currency , amount , refund , ethPrices , fee : toBN ( fee ) } )
2019-11-15 04:01:59 -05:00
if ( ! isEnough ) {
console . log ( ` Wrong fee: ${ reason } ` )
2019-12-12 05:58:58 -05:00
done ( null , {
status : 400 ,
msg : { error : reason }
} )
2019-11-15 04:01:59 -05:00
}
2019-12-03 08:26:16 -05:00
const data = mixer . methods . withdraw ( proof , ... args ) . encodeABI ( )
2019-12-12 05:58:58 -05:00
let nonce = Number ( await redisClient . get ( 'nonce' ) )
console . log ( 'nonce' , nonce )
2019-12-03 08:26:16 -05:00
const tx = {
2019-11-09 20:03:46 -05:00
from : web3 . eth . defaultAccount ,
2019-12-03 08:26:16 -05:00
value : numberToHex ( refund ) ,
2019-11-15 04:01:59 -05:00
gas : numberToHex ( gas ) ,
2019-07-18 08:43:26 -04:00
gasPrice : toHex ( toWei ( gasPrices . fast . toString ( ) , 'gwei' ) ) ,
2019-12-03 08:26:16 -05:00
to : mixer . _address ,
netId : config . netId ,
data ,
2019-12-12 05:58:58 -05:00
nonce
2019-12-03 08:26:16 -05:00
}
2019-12-12 05:58:58 -05:00
nonce += 1
await redisClient . set ( 'nonce' , nonce )
sendTx ( tx , done )
2019-07-18 08:43:26 -04:00
} catch ( e ) {
2019-12-03 08:26:16 -05:00
console . error ( e , 'estimate gas failed' )
2019-12-12 05:58:58 -05:00
done ( null , {
status : 400 ,
msg : { error : 'Internal Relayer Error. Please use a different relayer service' }
} )
2019-07-18 08:43:26 -04:00
}
2019-12-12 05:58:58 -05:00
} )
async function sendTx ( tx , done , retryAttempt = 1 ) {
let signedTx = await web3 . eth . accounts . signTransaction ( tx , config . privateKey )
let result = web3 . eth . sendSignedTransaction ( signedTx . rawTransaction )
2019-11-14 08:24:01 -05:00
2019-12-12 05:58:58 -05:00
result . once ( 'transactionHash' , function ( txHash ) {
done ( null , {
status : 200 ,
msg : { txHash }
} )
console . log ( ` A new successfully sent tx ${ txHash } ` )
} ) . on ( 'error' , async function ( e ) {
console . log ( 'error' , 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.' ) {
console . log ( 'nonce too low, retrying' )
if ( retryAttempt <= 10 ) {
retryAttempt ++
const newNonce = tx . nonce + 1
tx . nonce = newNonce
await redisClient . set ( 'nonce' , newNonce )
sendTx ( tx , done , retryAttempt )
return
}
}
console . error ( 'on transactionHash error' , e . message )
done ( null , {
status : 400 ,
msg : { error : 'Internal Relayer Error. Please use a different relayer service' }
} )
} )
}
module . exports = relayController