Fix eip1559 transaction sending problems (#632)
This commit is contained in:
parent
8d732adba1
commit
8ec11d0476
@ -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')
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user