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