Fix eip1559 transaction sending problems (#632)

This commit is contained in:
Kirill Fedoseev 2022-01-17 16:51:29 +03:00 committed by GitHub
parent 8d732adba1
commit 8ec11d0476
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 105 additions and 9 deletions

@ -9,6 +9,8 @@ const { sendTx } = require('./tx/sendTx')
const { getNonce, getChainId } = require('./tx/web3') const { getNonce, getChainId } = require('./tx/web3')
const { const {
addExtraGas, addExtraGas,
applyMinGasFeeBump,
chooseGasPriceOptions,
checkHTTPS, checkHTTPS,
syncForEach, syncForEach,
waitForFunds, waitForFunds,
@ -19,7 +21,7 @@ const {
isInsufficientBalanceError, isInsufficientBalanceError,
isNonceError isNonceError
} = require('./utils/utils') } = require('./utils/utils')
const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT } = require('./utils/constants') const { EXIT_CODES, EXTRA_GAS_PERCENTAGE, MAX_GAS_LIMIT, MIN_GAS_PRICE_BUMP_FACTOR } = require('./utils/constants')
const { ORACLE_TX_REDUNDANCY } = process.env const { ORACLE_TX_REDUNDANCY } = process.env
@ -146,6 +148,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
} }
try { try {
const newGasPriceOptions = chooseGasPriceOptions(gasPriceOptions, job.gasPriceOptions)
if (isResend) { if (isResend) {
const tx = await web3Fallback.eth.getTransaction(job.txHash) const tx = await web3Fallback.eth.getTransaction(job.txHash)
@ -159,7 +162,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
} }
const oldGasPrice = JSON.stringify(job.gasPriceOptions) const oldGasPrice = JSON.stringify(job.gasPriceOptions)
const newGasPrice = JSON.stringify(gasPriceOptions) const newGasPrice = JSON.stringify(newGasPriceOptions)
logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${oldGasPrice} -> ${newGasPrice}`) logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${oldGasPrice} -> ${newGasPrice}`)
} }
logger.info(`Sending transaction with nonce ${nonce}`) logger.info(`Sending transaction with nonce ${nonce}`)
@ -172,12 +175,12 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
to: job.to, to: job.to,
chainId, chainId,
web3: web3Redundant, web3: web3Redundant,
gasPriceOptions gasPriceOptions: newGasPriceOptions
}) })
const resendJob = { const resendJob = {
...job,
txHash, txHash,
gasPriceOptions, gasPriceOptions: newGasPriceOptions
...job
} }
resendJobs.push(resendJob) resendJobs.push(resendJob)
@ -196,7 +199,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
if (isGasPriceError(e)) { if (isGasPriceError(e)) {
logger.info('Replacement transaction underpriced, forcing gas price update') logger.info('Replacement transaction underpriced, forcing gas price update')
GasPrice.start(config.id, web3) GasPrice.start(config.id, web3)
failedTx.push(job) failedTx.push(applyMinGasFeeBump(job, MIN_GAS_PRICE_BUMP_FACTOR))
} else if (isResend || isSameTransactionError(e)) { } else if (isResend || isSameTransactionError(e)) {
resendJobs.push(job) resendJobs.push(job)
} else { } else {

@ -24,6 +24,7 @@ module.exports = {
MIN: 1, MIN: 1,
MAX: 1000 MAX: 1000
}, },
MIN_GAS_PRICE_BUMP_FACTOR: 0.1,
DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000, DEFAULT_TRANSACTION_RESEND_INTERVAL: 20 * 60 * 1000,
FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000, FALLBACK_RPC_URL_SWITCH_TIMEOUT: 60 * 60 * 1000,
BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT: 10, BLOCK_NUMBER_PROGRESS_ITERATIONS_LIMIT: 10,

@ -2,6 +2,9 @@ const fs = require('fs')
const BigNumber = require('bignumber.js') const BigNumber = require('bignumber.js')
const promiseRetry = require('promise-retry') const promiseRetry = require('promise-retry')
const Web3 = require('web3') const Web3 = require('web3')
const { GAS_PRICE_BOUNDARIES } = require('./constants')
const { toBN, toWei } = Web3.utils
const retrySequence = [1, 2, 3, 5, 8, 13, 21, 34, 55, 60] const retrySequence = [1, 2, 3, 5, 8, 13, 21, 34, 55, 60]
@ -34,8 +37,8 @@ const promiseRetryForever = f => promiseRetry(f, { forever: true, factor: 1 })
async function waitForFunds(web3, address, minimumBalance, cb, logger) { async function waitForFunds(web3, address, minimumBalance, cb, logger) {
promiseRetryForever(async retry => { promiseRetryForever(async retry => {
logger.debug('Getting balance of validator account') logger.debug('Getting balance of validator account')
const newBalance = web3.utils.toBN(await web3.eth.getBalance(address)) const newBalance = toBN(await web3.eth.getBalance(address))
if (newBalance.gte(web3.utils.toBN(minimumBalance.toString(10)))) { if (newBalance.gte(toBN(minimumBalance.toString(10)))) {
logger.debug({ balance: newBalance, minimumBalance }, 'Validator has minimum necessary balance') logger.debug({ balance: newBalance, minimumBalance }, 'Validator has minimum necessary balance')
cb(newBalance) cb(newBalance)
} else { } else {
@ -64,6 +67,48 @@ function addExtraGas(gas, extraPercentage, maxGasLimit = Infinity) {
return BigNumber.min(maxGasLimit, gasWithExtra) return BigNumber.min(maxGasLimit, gasWithExtra)
} }
function applyMinGasFeeBump(job, bumpFactor = 0.1) {
if (!job.gasPriceOptions) {
return job
}
const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = job.gasPriceOptions
const maxGasPrice = toWei(GAS_PRICE_BOUNDARIES.MAX.toString(), 'gwei')
if (gasPrice) {
return {
...job,
gasPriceOptions: {
gasPrice: addExtraGas(gasPrice, bumpFactor, maxGasPrice).toString()
}
}
}
if (maxFeePerGas && maxPriorityFeePerGas) {
return {
...job,
gasPriceOptions: {
maxFeePerGas: addExtraGas(maxFeePerGas, bumpFactor, maxGasPrice).toString(),
maxPriorityFeePerGas: addExtraGas(maxPriorityFeePerGas, bumpFactor, maxGasPrice).toString()
}
}
}
return job
}
function chooseGasPriceOptions(a, b) {
if (!a) {
return b
}
if (a && b && a.gasPrice && b.gasPrice) {
return { gasPrice: BigNumber.max(a.gasPrice, b.gasPrice).toString() }
}
if (a && b && a.maxFeePerGas && b.maxFeePerGas && a.maxPriorityFeePerGas && b.maxPriorityFeePerGas) {
return {
maxFeePerGas: BigNumber.max(a.maxFeePerGas, b.maxFeePerGas).toString(),
maxPriorityFeePerGas: BigNumber.max(a.maxPriorityFeePerGas, b.maxPriorityFeePerGas).toString()
}
}
return a
}
async function setIntervalAndRun(f, interval) { async function setIntervalAndRun(f, interval) {
const handler = setInterval(f, interval) const handler = setInterval(f, interval)
await f() await f()
@ -183,6 +228,8 @@ module.exports = {
waitForFunds, waitForFunds,
waitForUnsuspend, waitForUnsuspend,
addExtraGas, addExtraGas,
chooseGasPriceOptions,
applyMinGasFeeBump,
setIntervalAndRun, setIntervalAndRun,
watchdog, watchdog,
add0xPrefix, add0xPrefix,

@ -3,7 +3,13 @@ const chai = require('chai')
const chaiAsPromised = require('chai-as-promised') const chaiAsPromised = require('chai-as-promised')
const BigNumber = require('bignumber.js') const BigNumber = require('bignumber.js')
const proxyquire = require('proxyquire') const proxyquire = require('proxyquire')
const { addExtraGas, syncForEach, promiseAny } = require('../src/utils/utils') const {
addExtraGas,
applyMinGasFeeBump,
chooseGasPriceOptions,
syncForEach,
promiseAny
} = require('../src/utils/utils')
chai.use(chaiAsPromised) chai.use(chaiAsPromised)
chai.should() chai.should()
@ -173,4 +179,43 @@ describe('utils', () => {
await promiseAny(array.map(f)).should.be.rejected await promiseAny(array.map(f)).should.be.rejected
}) })
}) })
describe('applyMinGasFeeBump', () => {
it('should bump pre-eip1559 fee', () => {
const job = { gasPriceOptions: { gasPrice: '100000000000' } }
const newJob = applyMinGasFeeBump(job)
expect(newJob.gasPriceOptions.gasPrice).to.be.equal('110000000000')
})
it('should bump eip1559 fee', () => {
const job = { gasPriceOptions: { maxFeePerGas: '100000000000', maxPriorityFeePerGas: '20000000000' } }
const newJob = applyMinGasFeeBump(job)
expect(newJob.gasPriceOptions.maxFeePerGas).to.be.equal('110000000000')
expect(newJob.gasPriceOptions.maxPriorityFeePerGas).to.be.equal('22000000000')
})
})
describe('chooseGasPriceOptions', () => {
it('should choose max pre-eip1559 fee', () => {
const opts1 = { gasPrice: '100000000000' }
const opts2 = { gasPrice: '101000000000' }
expect(chooseGasPriceOptions(opts1, opts2).gasPrice).to.be.equal('101000000000')
expect(chooseGasPriceOptions(opts2, opts1).gasPrice).to.be.equal('101000000000')
expect(chooseGasPriceOptions(opts2, undefined).gasPrice).to.be.equal('101000000000')
expect(chooseGasPriceOptions(undefined, opts2).gasPrice).to.be.equal('101000000000')
})
it('should choose max eip1559 fee', () => {
const opts1 = { maxFeePerGas: '100000000000', maxPriorityFeePerGas: '21000000000' }
const opts2 = { maxFeePerGas: '101000000000', maxPriorityFeePerGas: '20000000000' }
expect(chooseGasPriceOptions(opts1, opts2).maxFeePerGas).to.be.equal('101000000000')
expect(chooseGasPriceOptions(opts1, opts2).maxPriorityFeePerGas).to.be.equal('21000000000')
expect(chooseGasPriceOptions(opts2, opts1).maxFeePerGas).to.be.equal('101000000000')
expect(chooseGasPriceOptions(opts2, opts1).maxPriorityFeePerGas).to.be.equal('21000000000')
expect(chooseGasPriceOptions(opts2, undefined).maxFeePerGas).to.be.equal('101000000000')
expect(chooseGasPriceOptions(opts2, undefined).maxPriorityFeePerGas).to.be.equal('20000000000')
expect(chooseGasPriceOptions(undefined, opts2).maxFeePerGas).to.be.equal('101000000000')
expect(chooseGasPriceOptions(undefined, opts2).maxPriorityFeePerGas).to.be.equal('20000000000')
})
})
}) })