This commit is contained in:
Alexey 2020-05-08 20:29:31 +03:00
parent f50379a391
commit a49746180e
7 changed files with 86 additions and 51 deletions

9
.editorconfig Normal file
View 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

View File

@ -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

View File

@ -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
}

View File

@ -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')
}

View File

@ -2,10 +2,10 @@ const Fetcher = require('./Fetcher')
const Sender = require('./Sender')
const web3 = require('./setupWeb3')
const fetcher = new Fetcher(web3)
const sender = new Sender(1, web3)
const sender = new Sender(web3)
module.exports = {
fetcher,
web3,
sender
}
}

View File

@ -1,7 +1,7 @@
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')
@ -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,27 +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
}
await redisClient.set('tx:' + nonce, JSON.stringify(tx))
tx.date = Date.now()
await redisClient.set('tx:' + nonce, JSON.stringify(tx) )
nonce += 1
await redisClient.set('nonce', nonce)
try {
const txHash = await sender.sendTx(tx)
done(null, {
status: 200,
msg: { txHash }
})
} catch (e) {
console.error('on transactionHash error', e.message)
done(null, {
status: 400,
msg: { error: 'Internal Relayer Error. Please use a different relayer service' }
})
}
sender.sendTx(tx, done)
} catch (e) {
console.error(e, 'estimate gas failed')
done(null, {
@ -150,4 +141,4 @@ withdrawQueue.process(async function(job, done){
}
})
module.exports = relayController
module.exports = relayController

View File

@ -1,37 +1,54 @@
const { redisClient } = require('./redis')
const config = require('../config')
const { toBN, toHex, toWei, BN, fromWei } = require('web3-utils')
class Sender {
constructor(minedNonce, web3) {
this.minedNonce = Number(minedNonce)
constructor(web3) {
this.web3 = web3
this.watherInterval = config.watherInterval
this.pendingTxTimeout = config.pendingTxTimeout
this.gasBumpPercentage = config.gasBumpPercentage
this.watcher()
}
async main() {
const lastNonce = await redisClient.get('nonce')
for(let nonce = this.minedNonce; nonce < lastNonce + 1; nonce++) {
let tx = await redisClient.get('tx' + nonce)
tx = JSON.parse(tx)
const isMined = await this.checkTx(tx)
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()))
this.sendTx(tx, null, 9999)
}
}
} catch(e) {
console.error('watcher error:', e)
} finally {
setTimeout(() => this.watcher(), this.watherInterval)
}
}
async checkTx(tx) {
const networkNonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount)
if ()
}
async sendTx(tx, retryAttempt = 1) {
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)
let txHash
result.once('transactionHash', function(_txHash){
console.log(`A new successfully sent tx ${_txHash}`)
txHash = _txHash
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', 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.'
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') {
@ -41,14 +58,19 @@ class Sender {
const newNonce = tx.nonce + 1
tx.nonce = newNonce
await redisClient.set('nonce', newNonce)
txHash = this.sendTx(tx, retryAttempt)
await redisClient.set('tx:' + newNonce, JSON.stringify(tx))
this.sendTx(tx, done, retryAttempt)
return
}
}
throw new Error(e.message)
if (done) {
done(null, {
status: 400,
msg: { error: 'Internal Relayer Error. Please use a different relayer service' }
})
}
})
return txHash
}
}
module.exports = Sender
module.exports = Sender