Merge pull request #14 from tornadocash/sender

Sender
This commit is contained in:
Roman Storm 2020-05-08 12:08:01 -07:00 committed by GitHub
commit b88edf9d0a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 48 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=
# 2.5 means 2.5%
RELAYER_FEE=2.5
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
}

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

@ -7,7 +7,7 @@ const {
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 = {}
@ -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

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