commit
b88edf9d0a
9
.editorconfig
Normal file
9
.editorconfig
Normal file
@ -0,0 +1,9 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
11
.env.example
11
.env.example
@ -8,5 +8,14 @@ REDIS_URL=redis://127.0.0.1:6379
|
||||
PRIVATE_KEY=
|
||||
# 2.5 means 2.5%
|
||||
RELAYER_FEE=2.5
|
||||
APP_PORT=8000
|
||||
|
||||
APP_PORT=8000
|
||||
# Resubmitter params:
|
||||
# how often the watcher will check the first pending tx (in seconds)
|
||||
NONCE_WATCHER_INTERVAL=30
|
||||
# how long a tx can be in pending pool (in seconds)
|
||||
ALLOWABLE_PENDING_TX_TIMEOUT=180
|
||||
# in GWEI
|
||||
MAX_GAS_PRICE=100
|
||||
# how much to increase the gas price for a stuck tx
|
||||
GAS_PRICE_BUMP_PERCENTAGE=20
|
||||
|
@ -147,5 +147,9 @@ module.exports = {
|
||||
defaultGasPrice: 20,
|
||||
gasOracleUrls: ['https://ethgasstation.info/json/ethgasAPI.json', 'https://gas-oracle.zoltu.io/'],
|
||||
port: process.env.APP_PORT,
|
||||
relayerServiceFee: Number(process.env.RELAYER_FEE)
|
||||
}
|
||||
relayerServiceFee: Number(process.env.RELAYER_FEE),
|
||||
maxGasPrice: process.env.MAX_GAS_PRICE,
|
||||
watherInterval: Number(process.env.NONCE_WATCHER_INTERVAL) * 1000,
|
||||
pendingTxTimeout: Number(process.env.ALLOWABLE_PENDING_TX_TIMEOUT) * 1000,
|
||||
gasBumpPercentage: process.env.GAS_PRICE_BUMP_PERCENTAGE
|
||||
}
|
||||
|
@ -55,7 +55,7 @@ class Fetcher {
|
||||
if (Number(json.fast) === 0) {
|
||||
throw new Error('Fetch gasPrice failed')
|
||||
}
|
||||
|
||||
|
||||
if (json.fast) {
|
||||
this.gasPrices.fast = Number(json.fast) / delimiter
|
||||
}
|
||||
@ -63,7 +63,7 @@ class Fetcher {
|
||||
if (json.percentile_97) {
|
||||
this.gasPrices.fast = parseInt(json.percentile_90) + 1 / delimiter
|
||||
}
|
||||
console.log('gas price fetch', this.gasPrices)
|
||||
// console.log('gas price fetch', this.gasPrices)
|
||||
} else {
|
||||
throw Error('Fetch gasPrice failed')
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
const Fetcher = require('./Fetcher')
|
||||
const Sender = require('./Sender')
|
||||
const web3 = require('./setupWeb3')
|
||||
const fetcher = new Fetcher(web3)
|
||||
const sender = new Sender(web3)
|
||||
|
||||
module.exports = {
|
||||
fetcher,
|
||||
web3
|
||||
}
|
||||
web3,
|
||||
sender
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
const Queue = require('bull')
|
||||
const { numberToHex, toWei, toHex, toBN, toChecksumAddress } = require('web3-utils')
|
||||
const mixerABI = require('../abis/mixerABI.json')
|
||||
const {
|
||||
const {
|
||||
isValidProof, isValidArgs, isKnownContract, isEnoughFee
|
||||
} = require('./utils')
|
||||
const config = require('../config')
|
||||
const { redisClient, redisOpts } = require('./redis')
|
||||
|
||||
const { web3, fetcher } = require('./instances')
|
||||
const { web3, fetcher, sender } = require('./instances')
|
||||
const withdrawQueue = new Queue('withdraw', redisOpts)
|
||||
|
||||
const reponseCbs = {}
|
||||
@ -20,7 +20,7 @@ withdrawQueue.on('completed', respLambda)
|
||||
|
||||
async function relayController(req, resp) {
|
||||
let requestJob
|
||||
|
||||
|
||||
const { proof, args, contract } = req.body
|
||||
let { valid , reason } = isValidProof(proof)
|
||||
if (!valid) {
|
||||
@ -59,7 +59,7 @@ async function relayController(req, resp) {
|
||||
return resp.status(400).json({ error: 'Relayer address is invalid' })
|
||||
}
|
||||
|
||||
requestJob = await withdrawQueue.add({
|
||||
requestJob = await withdrawQueue.add({
|
||||
contract, nullifierHash, root, proof, args, currency, amount, fee: fee.toString(), refund: refund.toString()
|
||||
}, { removeOnComplete: true })
|
||||
reponseCbs[requestJob.id] = resp
|
||||
@ -97,7 +97,7 @@ withdrawQueue.process(async function(job, done){
|
||||
|
||||
let gas = await mixer.methods.withdraw(proof, ...args).estimateGas({
|
||||
from: web3.eth.defaultAccount,
|
||||
value: refund
|
||||
value: refund
|
||||
})
|
||||
|
||||
gas += 50000
|
||||
@ -120,14 +120,18 @@ withdrawQueue.process(async function(job, done){
|
||||
value: numberToHex(refund),
|
||||
gas: numberToHex(gas),
|
||||
gasPrice: toHex(toWei(gasPrices.fast.toString(), 'gwei')),
|
||||
// you can use this gasPrice to test watcher
|
||||
// gasPrice: numberToHex(100000000),
|
||||
to: mixer._address,
|
||||
netId: config.netId,
|
||||
data,
|
||||
nonce
|
||||
}
|
||||
tx.date = Date.now()
|
||||
await redisClient.set('tx:' + nonce, JSON.stringify(tx) )
|
||||
nonce += 1
|
||||
await redisClient.set('nonce', nonce)
|
||||
sendTx(tx, done)
|
||||
sender.sendTx(tx, done)
|
||||
} catch (e) {
|
||||
console.error(e, 'estimate gas failed')
|
||||
done(null, {
|
||||
@ -137,38 +141,4 @@ withdrawQueue.process(async function(job, done){
|
||||
}
|
||||
})
|
||||
|
||||
async function sendTx(tx, done, retryAttempt = 1) {
|
||||
|
||||
let signedTx = await web3.eth.accounts.signTransaction(tx, config.privateKey)
|
||||
let result = web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
|
||||
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.'
|
||||
|| 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)
|
||||
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
|
||||
module.exports = relayController
|
||||
|
76
src/sender.js
Normal file
76
src/sender.js
Normal file
@ -0,0 +1,76 @@
|
||||
const { redisClient } = require('./redis')
|
||||
const config = require('../config')
|
||||
const { toBN, toHex, toWei, BN, fromWei } = require('web3-utils')
|
||||
|
||||
class Sender {
|
||||
constructor(web3) {
|
||||
this.web3 = web3
|
||||
this.watherInterval = config.watherInterval
|
||||
this.pendingTxTimeout = config.pendingTxTimeout
|
||||
this.gasBumpPercentage = 100 + Number(config.gasBumpPercentage)
|
||||
this.watcher()
|
||||
}
|
||||
|
||||
async watcher() {
|
||||
try {
|
||||
const networkNonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount)
|
||||
let tx = await redisClient.get('tx:' + networkNonce)
|
||||
if (tx) {
|
||||
tx = JSON.parse(tx)
|
||||
if (Date.now() - tx.date > this.pendingTxTimeout) {
|
||||
const newGasPrice = toBN(tx.gasPrice).mul(toBN(this.gasBumpPercentage)).div(toBN(100))
|
||||
const maxGasPrice = toBN(toWei(config.maxGasPrice))
|
||||
tx.gasPrice = toHex(BN.min(newGasPrice, maxGasPrice))
|
||||
tx.date = Date.now()
|
||||
await redisClient.set('tx:' + tx.nonce, JSON.stringify(tx) )
|
||||
console.log('resubmitting with gas price', fromWei(tx.gasPrice.toString(), 'gwei'), ' gwei')
|
||||
this.sendTx(tx, null, 9999)
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error('watcher error:', e)
|
||||
} finally {
|
||||
setTimeout(() => this.watcher(), this.watherInterval)
|
||||
}
|
||||
}
|
||||
|
||||
async sendTx(tx, done, retryAttempt = 1) {
|
||||
let signedTx = await this.web3.eth.accounts.signTransaction(tx, config.privateKey)
|
||||
let result = this.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
|
||||
|
||||
result.once('transactionHash', function(txHash){
|
||||
console.log(`A new successfully sent tx ${txHash}`)
|
||||
if (done) {
|
||||
done(null, {
|
||||
status: 200,
|
||||
msg: { txHash }
|
||||
})
|
||||
}
|
||||
}).on('error', async function(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' }
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Sender
|
Loading…
Reference in New Issue
Block a user