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

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

@ -8,5 +8,14 @@ REDIS_URL=redis://127.0.0.1:6379
PRIVATE_KEY= PRIVATE_KEY=
# 2.5 means 2.5% # 2.5 means 2.5%
RELAYER_FEE=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, defaultGasPrice: 20,
gasOracleUrls: ['https://ethgasstation.info/json/ethgasAPI.json', 'https://gas-oracle.zoltu.io/'], gasOracleUrls: ['https://ethgasstation.info/json/ethgasAPI.json', 'https://gas-oracle.zoltu.io/'],
port: process.env.APP_PORT, 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
} }

@ -63,7 +63,7 @@ class Fetcher {
if (json.percentile_97) { if (json.percentile_97) {
this.gasPrices.fast = parseInt(json.percentile_90) + 1 / delimiter this.gasPrices.fast = parseInt(json.percentile_90) + 1 / delimiter
} }
console.log('gas price fetch', this.gasPrices) // console.log('gas price fetch', this.gasPrices)
} else { } else {
throw Error('Fetch gasPrice failed') throw Error('Fetch gasPrice failed')
} }

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

@ -120,27 +120,18 @@ withdrawQueue.process(async function(job, done){
value: numberToHex(refund), value: numberToHex(refund),
gas: numberToHex(gas), gas: numberToHex(gas),
gasPrice: toHex(toWei(gasPrices.fast.toString(), 'gwei')), gasPrice: toHex(toWei(gasPrices.fast.toString(), 'gwei')),
// you can use this gasPrice to test watcher
// gasPrice: numberToHex(100000000),
to: mixer._address, to: mixer._address,
netId: config.netId, netId: config.netId,
data, data,
nonce nonce
} }
await redisClient.set('tx:' + nonce, JSON.stringify(tx)) tx.date = Date.now()
await redisClient.set('tx:' + nonce, JSON.stringify(tx) )
nonce += 1 nonce += 1
await redisClient.set('nonce', nonce) await redisClient.set('nonce', nonce)
try { sender.sendTx(tx, done)
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' }
})
}
} catch (e) { } catch (e) {
console.error(e, 'estimate gas failed') console.error(e, 'estimate gas failed')
done(null, { done(null, {

@ -1,36 +1,53 @@
const { redisClient } = require('./redis') const { redisClient } = require('./redis')
const config = require('../config') const config = require('../config')
const { toBN, toHex, toWei, BN, fromWei } = require('web3-utils')
class Sender { class Sender {
constructor(minedNonce, web3) { constructor(web3) {
this.minedNonce = Number(minedNonce)
this.web3 = web3 this.web3 = web3
this.watherInterval = config.watherInterval
this.pendingTxTimeout = config.pendingTxTimeout
this.gasBumpPercentage = config.gasBumpPercentage
this.watcher()
} }
async main() { async watcher() {
const lastNonce = await redisClient.get('nonce') try {
for(let nonce = this.minedNonce; nonce < lastNonce + 1; nonce++) { const networkNonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount)
let tx = await redisClient.get('tx' + nonce) let tx = await redisClient.get('tx:' + networkNonce)
tx = JSON.parse(tx) if (tx) {
const isMined = await this.checkTx(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) { async sendTx(tx, done, retryAttempt = 1) {
const networkNonce = await this.web3.eth.getTransactionCount(this.web3.eth.defaultAccount)
if ()
}
async sendTx(tx, retryAttempt = 1) {
let signedTx = await this.web3.eth.accounts.signTransaction(tx, config.privateKey) let signedTx = await this.web3.eth.accounts.signTransaction(tx, config.privateKey)
let result = this.web3.eth.sendSignedTransaction(signedTx.rawTransaction) let result = this.web3.eth.sendSignedTransaction(signedTx.rawTransaction)
let txHash
result.once('transactionHash', function(_txHash){ result.once('transactionHash', function(txHash){
console.log(`A new successfully sent tx ${_txHash}`) console.log(`A new successfully sent tx ${txHash}`)
txHash = _txHash if (done) {
done(null, {
status: 200,
msg: { txHash }
})
}
}).on('error', async function(e){ }).on('error', async function(e){
console.log('error', e.message) 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.' 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: Transaction nonce is too low. Try incrementing the nonce.'
|| e.message === 'Returned error: nonce too low' || e.message === 'Returned error: nonce too low'
@ -41,13 +58,18 @@ class Sender {
const newNonce = tx.nonce + 1 const newNonce = tx.nonce + 1
tx.nonce = newNonce tx.nonce = newNonce
await redisClient.set('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 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
} }
} }