13 Commits

Author SHA1 Message Date
Danil Kovtonyuk
07752e0714 update gas price oracle 2021-08-25 22:35:05 +10:00
Danil Kovtonyuk
37f6faa42d bump gas price oracle 2021-08-17 17:23:54 +10:00
Danil Kovtonyuk
9599788224 bump gas price oracle 2021-06-15 13:31:41 +03:00
Danil Kovtonyuk
72a665a19a add gasPriceOracleConfig 2021-06-03 16:16:31 +03:00
Roman Storm
f6a4e93a23 fix mutex 2021-02-16 22:08:12 -08:00
Roman Storm
af7c597af9 Merge branch 'master' of github.com:tornadocash/tx-manager 2021-02-16 21:48:51 -08:00
Roman Storm
221dce3d73 add MAX_GAS_PRICE 2021-02-16 21:48:44 -08:00
poma
8d4bab7fc2 handle bump gas price error 2021-02-17 08:40:14 +03:00
poma
af65d78be9 fix await 2021-02-17 08:39:50 +03:00
Roman Storm
bc0b369095 fix gas price bump 2021-02-16 20:01:30 -08:00
Alexey Pertsev
e1620e15c1 Block gas limit (#1)
update gas-price-oracle dep, add BLOCK_GAS_LIMIT const
2020-12-24 08:39:07 +03:00
poma
414fb28a5e more general fix for tx error 2020-11-26 10:34:40 +03:00
Alexey
780df01b43 _handleSendError fix 2020-11-25 22:36:51 +01:00
5 changed files with 60 additions and 22 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "tx-manager",
"version": "0.2.7",
"version": "0.3.3",
"description": "",
"main": "index.js",
"scripts": {
@@ -23,7 +23,7 @@
"dependencies": {
"async-mutex": "^0.2.4",
"ethers": "^5.0.17",
"gas-price-oracle": "^0.2.0",
"gas-price-oracle": "^0.3.5",
"web3-core-promievent": "^1.3.0"
},
"devDependencies": {

View File

@@ -13,12 +13,14 @@ const nonceErrors = [
const gasPriceErrors = [
'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.',
'replacement transaction underpriced',
'transaction underpriced',
/Transaction gas price \d+wei is too low. There is another transaction with same nonce in the queue with gas price: \d+wei. Try increasing the gas price or incrementing the nonce./,
]
// prettier-ignore
const sameTxErrors = [
'Transaction with the same hash was already imported.',
'already known',
]
class Transaction {
@@ -64,6 +66,7 @@ class Transaction {
if (!tx.gasLimit) {
tx.gasLimit = await this._wallet.estimateGas(tx)
tx.gasLimit = Math.floor(tx.gasLimit * this.config.GAS_LIMIT_MULTIPLIER)
tx.gasLimit = Math.min(tx.gasLimit, this.config.BLOCK_GAS_LIMIT)
}
tx.nonce = this.tx.nonce // can be different from `this.manager._nonce`
tx.gasPrice = Math.max(this.tx.gasPrice, tx.gasPrice || 0) // start no less than current tx gas price
@@ -93,16 +96,16 @@ class Transaction {
* @private
*/
async _execute() {
await this.manager._mutex.acquire()
const mutexRelease = await this.manager._mutex.acquire()
try {
await this._prepare()
await this._send()
const receipt = this._waitForConfirmations()
const receipt = await this._waitForConfirmations()
// we could have bumped nonce during execution, so get the latest one + 1
this.manager._nonce = this.tx.nonce + 1
return receipt
} finally {
this.manager._mutex.release()
mutexRelease()
}
}
@@ -113,14 +116,22 @@ class Transaction {
* @private
*/
async _prepare() {
if (!this.config.BLOCK_GAS_LIMIT) {
const lastBlock = await this._provider.getBlock('latest')
this.config.BLOCK_GAS_LIMIT = Math.floor(lastBlock.gasLimit.toNumber() * 0.95)
}
if (!this.tx.gasLimit || this.config.ESTIMATE_GAS) {
const gas = await this._wallet.estimateGas(this.tx)
if (!this.tx.gasLimit) {
this.tx.gasLimit = Math.floor(gas * this.config.GAS_LIMIT_MULTIPLIER)
const gasLimit = Math.floor(gas * this.config.GAS_LIMIT_MULTIPLIER)
this.tx.gasLimit = Math.min(gasLimit, this.config.BLOCK_GAS_LIMIT)
}
}
if (!this.tx.gasPrice) {
this.tx.gasPrice = await this._getGasPrice('fast')
const fastGasPrice = BigNumber.from(await this._getGasPrice('fast'))
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
this.tx.gasPrice = min(fastGasPrice, maxGasPrice).toHexString()
}
if (!this.manager._nonce) {
this.manager._nonce = await this._getLastNonce()
@@ -268,7 +279,12 @@ class Transaction {
}
_handleSendError(e) {
if (e.code === 'SERVER_ERROR' && e.error) {
if (e.error.error) {
// Sometimes ethers wraps known errors, unwrap it in this case
e = e.error
}
if (e.error && e.code === 'SERVER_ERROR') {
const message = e.error.message
// nonce is too low, trying to increase and resubmit
@@ -286,8 +302,11 @@ class Transaction {
console.log(
`Gas price ${formatUnits(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`,
)
this._increaseGasPrice()
if (this._increaseGasPrice()) {
return this._send()
} else {
throw new Error('Already at max gas price, but still not enough to submit the transaction')
}
}
if (this._hasError(message, sameTxErrors)) {
@@ -312,17 +331,17 @@ class Transaction {
}
_increaseGasPrice() {
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei')
const oldGasPrice = BigNumber.from(this.tx.gasPrice)
if (oldGasPrice.gte(maxGasPrice)) {
console.log('Already at max gas price, not bumping')
return false
}
const newGasPrice = max(
oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
oldGasPrice.add(minGweiBump),
)
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
if (oldGasPrice.eq(maxGasPrice)) {
console.log('Already at max gas price, not bumping')
return false
}
this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString()
console.log(`Increasing gas price to ${formatUnits(this.tx.gasPrice, 'gwei')} gwei`)
return true

View File

@@ -14,17 +14,18 @@ const defaultConfig = {
CONFIRMATIONS: 8,
ESTIMATE_GAS: true,
THROW_ON_REVERT: true,
BLOCK_GAS_LIMIT: null,
}
class TxManager {
constructor({ privateKey, rpcUrl, broadcastNodes = [], config = {} }) {
constructor({ privateKey, rpcUrl, broadcastNodes = [], config = {}, gasPriceOracleConfig = {} }) {
this.config = Object.assign({ ...defaultConfig }, config)
this._privateKey = privateKey.startsWith('0x') ? privateKey : '0x' + privateKey
this._provider = new ethers.providers.JsonRpcProvider(rpcUrl)
this._wallet = new ethers.Wallet(this._privateKey, this._provider)
this.address = this._wallet.address
this._broadcastNodes = broadcastNodes
this._gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl })
this._gasPriceOracle = new GasPriceOracle({ defaultRpc: rpcUrl, ...gasPriceOracleConfig })
this._mutex = new Mutex()
this._nonce = null
}

View File

@@ -10,7 +10,7 @@ describe('TxManager', () => {
privateKey: PRIVATE_KEY,
rpcUrl: RPC_URL,
config: {
CONFIRMATIONS: 3,
CONFIRMATIONS: 1,
GAS_BUMP_INTERVAL: 1000 * 15,
},
})
@@ -101,5 +101,23 @@ describe('TxManager', () => {
console.log('receipt', receipt)
})
it.only('should send multiple txs', async () => {
const genTx = value => ({
value,
to: '0x0039F22efB07A647557C7C5d17854CFD6D489eF3',
})
await Promise.all([
manager.createTx(genTx(1)).send(),
manager.createTx(genTx(2)).send(),
manager.createTx(genTx(3)).send(),
manager.createTx(genTx(4)).send(),
manager.createTx(genTx(5)).send(),
manager.createTx(genTx(6)).send(),
manager.createTx(genTx(7)).send(),
manager.createTx(genTx(8)).send(),
manager.createTx(genTx(9)).send(),
manager.createTx(genTx(10)).send(),
])
})
})
})

View File

@@ -1141,10 +1141,10 @@ functional-red-black-tree@^1.0.1:
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gas-price-oracle@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.2.0.tgz#981926c96089497115113162b03151aacfe44a5a"
integrity sha512-2+mMyunV/pMJrmKl/IeEtX860NaE/bQ7H4D8PO2dc0OQd8ZAj/e4WJ+C9F/uOeG3dwm8SEFjofOvcYRHeGxo/Q==
gas-price-oracle@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.3.5.tgz#b7bff364e37fb7f30a2297cb65be80fceb4b8da3"
integrity sha512-9NAKzmGgWLjGUc4XsqNJjh0JXthETucijNrkV47FrZIjP8YMzq4jhBvlNeMBt6VVGnr64qjIY2RWb+In7qNsFA==
dependencies:
axios "^0.19.2"
bignumber.js "^9.0.0"