12 Commits

Author SHA1 Message Date
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", "name": "tx-manager",
"version": "0.2.7", "version": "0.3.2",
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
@@ -23,7 +23,7 @@
"dependencies": { "dependencies": {
"async-mutex": "^0.2.4", "async-mutex": "^0.2.4",
"ethers": "^5.0.17", "ethers": "^5.0.17",
"gas-price-oracle": "^0.2.0", "gas-price-oracle": "^0.3.4",
"web3-core-promievent": "^1.3.0" "web3-core-promievent": "^1.3.0"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -13,12 +13,14 @@ const nonceErrors = [
const gasPriceErrors = [ 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.', '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', '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./, /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 // prettier-ignore
const sameTxErrors = [ const sameTxErrors = [
'Transaction with the same hash was already imported.', 'Transaction with the same hash was already imported.',
'already known',
] ]
class Transaction { class Transaction {
@@ -64,6 +66,7 @@ class Transaction {
if (!tx.gasLimit) { if (!tx.gasLimit) {
tx.gasLimit = await this._wallet.estimateGas(tx) tx.gasLimit = await this._wallet.estimateGas(tx)
tx.gasLimit = Math.floor(tx.gasLimit * this.config.GAS_LIMIT_MULTIPLIER) 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.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 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 * @private
*/ */
async _execute() { async _execute() {
await this.manager._mutex.acquire() const mutexRelease = await this.manager._mutex.acquire()
try { try {
await this._prepare() await this._prepare()
await this._send() 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 // we could have bumped nonce during execution, so get the latest one + 1
this.manager._nonce = this.tx.nonce + 1 this.manager._nonce = this.tx.nonce + 1
return receipt return receipt
} finally { } finally {
this.manager._mutex.release() mutexRelease()
} }
} }
@@ -113,14 +116,22 @@ class Transaction {
* @private * @private
*/ */
async _prepare() { 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) { if (!this.tx.gasLimit || this.config.ESTIMATE_GAS) {
const gas = await this._wallet.estimateGas(this.tx) const gas = await this._wallet.estimateGas(this.tx)
if (!this.tx.gasLimit) { 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) { 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) { if (!this.manager._nonce) {
this.manager._nonce = await this._getLastNonce() this.manager._nonce = await this._getLastNonce()
@@ -268,7 +279,12 @@ class Transaction {
} }
_handleSendError(e) { _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 const message = e.error.message
// nonce is too low, trying to increase and resubmit // nonce is too low, trying to increase and resubmit
@@ -286,8 +302,11 @@ class Transaction {
console.log( console.log(
`Gas price ${formatUnits(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`, `Gas price ${formatUnits(this.tx.gasPrice, 'gwei')} gwei is too low, increasing and retrying`,
) )
this._increaseGasPrice() if (this._increaseGasPrice()) {
return this._send() 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)) { if (this._hasError(message, sameTxErrors)) {
@@ -312,17 +331,17 @@ class Transaction {
} }
_increaseGasPrice() { _increaseGasPrice() {
const maxGasPrice = parseUnits(this.config.MAX_GAS_PRICE.toString(), 'gwei')
const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei') const minGweiBump = parseUnits(this.config.MIN_GWEI_BUMP.toString(), 'gwei')
const oldGasPrice = BigNumber.from(this.tx.gasPrice) 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( const newGasPrice = max(
oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100), oldGasPrice.mul(100 + this.config.GAS_BUMP_PERCENTAGE).div(100),
oldGasPrice.add(minGweiBump), 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() this.tx.gasPrice = min(newGasPrice, maxGasPrice).toHexString()
console.log(`Increasing gas price to ${formatUnits(this.tx.gasPrice, 'gwei')} gwei`) console.log(`Increasing gas price to ${formatUnits(this.tx.gasPrice, 'gwei')} gwei`)
return true return true

View File

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

View File

@@ -10,7 +10,7 @@ describe('TxManager', () => {
privateKey: PRIVATE_KEY, privateKey: PRIVATE_KEY,
rpcUrl: RPC_URL, rpcUrl: RPC_URL,
config: { config: {
CONFIRMATIONS: 3, CONFIRMATIONS: 1,
GAS_BUMP_INTERVAL: 1000 * 15, GAS_BUMP_INTERVAL: 1000 * 15,
}, },
}) })
@@ -101,5 +101,23 @@ describe('TxManager', () => {
console.log('receipt', receipt) 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" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=
gas-price-oracle@^0.2.0: gas-price-oracle@^0.3.4:
version "0.2.0" version "0.3.4"
resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.2.0.tgz#981926c96089497115113162b03151aacfe44a5a" resolved "https://registry.yarnpkg.com/gas-price-oracle/-/gas-price-oracle-0.3.4.tgz#c98f6e24cf4bea96be2fe85383d53601b2ab5761"
integrity sha512-2+mMyunV/pMJrmKl/IeEtX860NaE/bQ7H4D8PO2dc0OQd8ZAj/e4WJ+C9F/uOeG3dwm8SEFjofOvcYRHeGxo/Q== integrity sha512-bba9zWd2hQ32AuPFDzL8E5QxAzXNfGKR1UZCa14fSYJWc9yo3k7wabITMkGEv0cbKpHmyK/kHhhDTBtugI6eAg==
dependencies: dependencies:
axios "^0.19.2" axios "^0.19.2"
bignumber.js "^9.0.0" bignumber.js "^9.0.0"