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 {
addExtraGas,
applyMinGasFeeBump,
chooseGasPriceOptions,
checkHTTPS,
syncForEach,
waitForFunds,
@ -19,7 +21,7 @@ const {
isInsufficientBalanceError,
isNonceError
} = 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
@ -146,6 +148,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
}
try {
const newGasPriceOptions = chooseGasPriceOptions(gasPriceOptions, job.gasPriceOptions)
if (isResend) {
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 newGasPrice = JSON.stringify(gasPriceOptions)
const newGasPrice = JSON.stringify(newGasPriceOptions)
logger.info(`Transaction ${job.txHash} was not mined, updating gasPrice: ${oldGasPrice} -> ${newGasPrice}`)
}
logger.info(`Sending transaction with nonce ${nonce}`)
@ -172,12 +175,12 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
to: job.to,
chainId,
web3: web3Redundant,
gasPriceOptions
gasPriceOptions: newGasPriceOptions
})
const resendJob = {
...job,
txHash,
gasPriceOptions,
...job
gasPriceOptions: newGasPriceOptions
}
resendJobs.push(resendJob)
@ -196,7 +199,7 @@ async function main({ msg, ackMsg, nackMsg, channel, scheduleForRetry, scheduleT
if (isGasPriceError(e)) {
logger.info('Replacement transaction underpriced, forcing gas price update')
GasPrice.start(config.id, web3)
failedTx.push(job)
failedTx.push(applyMinGasFeeBump(job, MIN_GAS_PRICE_BUMP_FACTOR))
} else if (isResend || isSameTransactionError(e)) {
resendJobs.push(job)
} else {

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

@ -2,6 +2,9 @@ const fs = require('fs')
const BigNumber = require('bignumber.js')
const promiseRetry = require('promise-retry')
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]
@ -34,8 +37,8 @@ const promiseRetryForever = f => promiseRetry(f, { forever: true, factor: 1 })
async function waitForFunds(web3, address, minimumBalance, cb, logger) {
promiseRetryForever(async retry => {
logger.debug('Getting balance of validator account')
const newBalance = web3.utils.toBN(await web3.eth.getBalance(address))
if (newBalance.gte(web3.utils.toBN(minimumBalance.toString(10)))) {
const newBalance = toBN(await web3.eth.getBalance(address))
if (newBalance.gte(toBN(minimumBalance.toString(10)))) {
logger.debug({ balance: newBalance, minimumBalance }, 'Validator has minimum necessary balance')
cb(newBalance)
} else {
@ -64,6 +67,48 @@ function addExtraGas(gas, extraPercentage, maxGasLimit = Infinity) {
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) {
const handler = setInterval(f, interval)
await f()
@ -183,6 +228,8 @@ module.exports = {
waitForFunds,
waitForUnsuspend,
addExtraGas,
chooseGasPriceOptions,
applyMinGasFeeBump,
setIntervalAndRun,
watchdog,
add0xPrefix,

@ -3,7 +3,13 @@ const chai = require('chai')
const chaiAsPromised = require('chai-as-promised')
const BigNumber = require('bignumber.js')
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.should()
@ -173,4 +179,43 @@ describe('utils', () => {
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')
})
})
})