Commit a8c9cd67 authored by Karl Floersch's avatar Karl Floersch Committed by GitHub

Merge pull request #1024 from ethereum-optimism/fix/broken-estimate-gas-when-pending-tx-in-mempool

fix: estimate gas when pending tx in mempool
parents 6702ce41 e2985bf7
---
'@eth-optimism/batch-submitter': patch
---
Fix tx resubmission estimateGas bug in batch submitter
/* External Imports */
import { Contract, Signer, utils, providers } from 'ethers'
import { TransactionReceipt } from '@ethersproject/abstract-provider'
import {
Contract,
Signer,
utils,
providers,
PopulatedTransaction,
} from 'ethers'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import { Gauge, Histogram, Counter } from 'prom-client'
import * as ynatm from '@eth-optimism/ynatm'
import { RollupInfo, sleep } from '@eth-optimism/core-utils'
import { Logger, Metrics } from '@eth-optimism/common-ts'
import { getContractFactory } from 'old-contracts'
/* Internal Imports */
import { TxSubmissionHooks } from '..'
export interface BlockRange {
start: number
end: number
}
export interface ResubmissionConfig {
resubmissionTimeout: number
minGasPriceInGwei: number
maxGasPriceInGwei: number
gasRetryIncrement: number
}
interface BatchSubmitterMetrics {
batchSubmitterETHBalance: Gauge<string>
......@@ -49,10 +53,6 @@ export abstract class BatchSubmitter {
readonly finalityConfirmations: number,
readonly addressManagerAddress: string,
readonly minBalanceEther: number,
readonly minGasPriceInGwei: number,
readonly maxGasPriceInGwei: number,
readonly gasRetryIncrement: number,
readonly gasThresholdInGwei: number,
readonly blockOffset: number,
readonly logger: Logger,
readonly defaultMetrics: Metrics
......@@ -190,69 +190,37 @@ export abstract class BatchSubmitter {
return true
}
public static async getReceiptWithResubmission(
txFunc: (gasPrice) => Promise<TransactionReceipt>,
resubmissionConfig: ResubmissionConfig,
logger: Logger
): Promise<TransactionReceipt> {
const {
resubmissionTimeout,
minGasPriceInGwei,
maxGasPriceInGwei,
gasRetryIncrement,
} = resubmissionConfig
const receipt = await ynatm.send({
sendTransactionFunction: txFunc,
minGasPrice: ynatm.toGwei(minGasPriceInGwei),
maxGasPrice: ynatm.toGwei(maxGasPriceInGwei),
gasPriceScalingFunction: ynatm.LINEAR(gasRetryIncrement),
delay: resubmissionTimeout,
protected _makeHooks(txName: string): TxSubmissionHooks {
return {
beforeSendTransaction: (tx: PopulatedTransaction) => {
this.logger.info(`Submitting ${txName} transaction`, {
gasPrice: tx.gasPrice,
nonce: tx.nonce,
contractAddr: this.chainContract.address,
})
logger.debug('Resubmission tx receipt', { receipt })
return receipt
}
private async _getMinGasPriceInGwei(): Promise<number> {
if (this.minGasPriceInGwei !== 0) {
return this.minGasPriceInGwei
}
let minGasPriceInGwei = parseInt(
utils.formatUnits(await this.signer.getGasPrice(), 'gwei'),
10
)
if (minGasPriceInGwei > this.maxGasPriceInGwei) {
this.logger.warn(
'Minimum gas price is higher than max! Ethereum must be congested...'
)
minGasPriceInGwei = this.maxGasPriceInGwei
},
onTransactionResponse: (txResponse: TransactionResponse) => {
this.logger.info(`Submitted ${txName} transaction`, {
txHash: txResponse.hash,
from: txResponse.from,
})
this.logger.debug(`${txName} transaction data`, {
data: txResponse.data,
})
},
}
return minGasPriceInGwei
}
protected async _submitAndLogTx(
txFunc: (gasPrice) => Promise<TransactionReceipt>,
submitTransaction: () => Promise<TransactionReceipt>,
successMessage: string
): Promise<TransactionReceipt> {
this.lastBatchSubmissionTimestamp = Date.now()
this.logger.debug('Waiting for receipt...')
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: this.resubmissionTimeout,
minGasPriceInGwei: await this._getMinGasPriceInGwei(),
maxGasPriceInGwei: this.maxGasPriceInGwei,
gasRetryIncrement: this.gasRetryIncrement,
}
this.logger.debug('Submitting transaction & waiting for receipt...')
let receipt: TransactionReceipt
try {
receipt = await BatchSubmitter.getReceiptWithResubmission(
txFunc,
resubmissionConfig,
this.logger
)
receipt = await submitTransaction()
} catch (err) {
this.metrics.failedSubmissions.inc()
if (err.reason) {
......
......@@ -13,6 +13,7 @@ import { Logger, Metrics } from '@eth-optimism/common-ts'
/* Internal Imports */
import { BlockRange, BatchSubmitter } from '.'
import { TransactionSubmitter } from '../utils'
export class StateBatchSubmitter extends BatchSubmitter {
// TODO: Change this so that we calculate start = scc.totalElements() and end = ctc.totalElements()!
......@@ -23,6 +24,7 @@ export class StateBatchSubmitter extends BatchSubmitter {
protected syncing: boolean
protected ctcContract: Contract
private fraudSubmissionAddress: string
private transactionSubmitter: TransactionSubmitter
constructor(
signer: Signer,
......@@ -36,10 +38,7 @@ export class StateBatchSubmitter extends BatchSubmitter {
finalityConfirmations: number,
addressManagerAddress: string,
minBalanceEther: number,
minGasPriceInGwei: number,
maxGasPriceInGwei: number,
gasRetryIncrement: number,
gasThresholdInGwei: number,
transactionSubmitter: TransactionSubmitter,
blockOffset: number,
logger: Logger,
metrics: Metrics,
......@@ -57,15 +56,12 @@ export class StateBatchSubmitter extends BatchSubmitter {
finalityConfirmations,
addressManagerAddress,
minBalanceEther,
minGasPriceInGwei,
maxGasPriceInGwei,
gasRetryIncrement,
gasThresholdInGwei,
blockOffset,
logger,
metrics
)
this.fraudSubmissionAddress = fraudSubmissionAddress
this.transactionSubmitter = transactionSubmitter
}
/*****************************
......@@ -159,14 +155,14 @@ export class StateBatchSubmitter extends BatchSubmitter {
endBlock: number
): Promise<TransactionReceipt> {
const batch = await this._generateStateCommitmentBatch(startBlock, endBlock)
const tx = this.chainContract.interface.encodeFunctionData(
const calldata = this.chainContract.interface.encodeFunctionData(
'appendStateBatch',
[batch, startBlock]
)
const batchSizeInBytes = remove0x(tx).length / 2
const batchSizeInBytes = remove0x(calldata).length / 2
this.logger.debug('State batch generated', {
batchSizeInBytes,
tx,
calldata,
})
if (!this._shouldSubmitBatch(batchSizeInBytes)) {
......@@ -174,33 +170,23 @@ export class StateBatchSubmitter extends BatchSubmitter {
}
const offsetStartsAtIndex = startBlock - this.blockOffset
this.logger.debug('Submitting batch.', { tx })
this.logger.debug('Submitting batch.', { calldata })
const nonce = await this.signer.getTransactionCount()
const contractFunction = async (gasPrice): Promise<TransactionReceipt> => {
this.logger.info('Submitting appendStateBatch transaction', {
gasPrice,
nonce,
contractAddr: this.chainContract.address,
})
const contractTx = await this.chainContract.appendStateBatch(
// Generate the transaction we will repeatedly submit
const tx = await this.chainContract.populateTransaction.appendStateBatch(
batch,
offsetStartsAtIndex,
{ nonce, gasPrice }
offsetStartsAtIndex
)
this.logger.info('Submitted appendStateBatch transaction', {
txHash: contractTx.hash,
from: contractTx.from,
})
this.logger.debug('appendStateBatch transaction data', {
data: contractTx.data,
})
return this.signer.provider.waitForTransaction(
contractTx.hash,
this.numConfirmations
const submitTransaction = (): Promise<TransactionReceipt> => {
return this.transactionSubmitter.submitTransaction(
tx,
this._makeHooks('appendStateBatch')
)
}
return this._submitAndLogTx(contractFunction, 'Submitted state root batch!')
return this._submitAndLogTx(
submitTransaction,
'Submitted state root batch!'
)
}
/*********************
......
......@@ -22,6 +22,7 @@ import {
} from '../transaction-chain-contract'
import { BlockRange, BatchSubmitter } from '.'
import { TransactionSubmitter } from '../utils'
export interface AutoFixBatchOptions {
fixDoublePlayedDeposits: boolean
......@@ -35,6 +36,8 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
protected syncing: boolean
private disableQueueBatchAppend: boolean
private autoFixBatchOptions: AutoFixBatchOptions
private transactionSubmitter: TransactionSubmitter
private gasThresholdInGwei: number
constructor(
signer: Signer,
......@@ -47,10 +50,8 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
resubmissionTimeout: number,
addressManagerAddress: string,
minBalanceEther: number,
minGasPriceInGwei: number,
maxGasPriceInGwei: number,
gasRetryIncrement: number,
gasThresholdInGwei: number,
transactionSubmitter: TransactionSubmitter,
blockOffset: number,
logger: Logger,
metrics: Metrics,
......@@ -73,16 +74,14 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
0, // Supply dummy value because it is not used.
addressManagerAddress,
minBalanceEther,
minGasPriceInGwei,
maxGasPriceInGwei,
gasRetryIncrement,
gasThresholdInGwei,
blockOffset,
logger,
metrics
)
this.disableQueueBatchAppend = disableQueueBatchAppend
this.autoFixBatchOptions = autoFixBatchOptions
this.gasThresholdInGwei = gasThresholdInGwei
this.transactionSubmitter = transactionSubmitter
}
/*****************************
......@@ -140,34 +139,7 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
)
if (!this.disableQueueBatchAppend) {
const nonce = await this.signer.getTransactionCount()
const contractFunction = async (
gasPrice
): Promise<TransactionReceipt> => {
this.logger.info('Submitting appendQueueBatch transaction', {
gasPrice,
nonce,
contractAddr: this.chainContract.address,
})
const tx = await this.chainContract.appendQueueBatch(99999999, {
nonce,
gasPrice,
})
this.logger.info('Submitted appendQueueBatch transaction', {
txHash: tx.hash,
from: tx.from,
})
this.logger.debug('appendQueueBatch transaction data', {
data: tx.data,
})
return this.signer.provider.waitForTransaction(
tx.hash,
this.numConfirmations
)
}
// Empty the queue with a huge `appendQueueBatch(..)` call
return this._submitAndLogTx(contractFunction, 'Cleared queue!')
return this.submitAppendQueueBatch()
}
}
this.logger.info('Syncing mode enabled but queue is empty. Skipping...')
......@@ -250,36 +222,43 @@ export class TransactionBatchSubmitter extends BatchSubmitter {
l1tipHeight,
})
const nonce = await this.signer.getTransactionCount()
const contractFunction = async (gasPrice): Promise<TransactionReceipt> => {
this.logger.info('Submitting appendSequencerBatch transaction', {
gasPrice,
nonce,
contractAddr: this.chainContract.address,
})
const tx = await this.chainContract.appendSequencerBatch(batchParams, {
nonce,
gasPrice,
})
this.logger.info('Submitted appendSequencerBatch transaction', {
txHash: tx.hash,
from: tx.from,
})
this.logger.debug('appendSequencerBatch transaction data', {
data: tx.data,
})
return this.signer.provider.waitForTransaction(
tx.hash,
this.numConfirmations
)
}
return this._submitAndLogTx(contractFunction, 'Submitted batch!')
return this.submitAppendSequencerBatch(batchParams)
}
/*********************
* Private Functions *
********************/
private async submitAppendQueueBatch(): Promise<TransactionReceipt> {
const tx = await this.chainContract.populateTransaction.appendQueueBatch(
ethers.constants.MaxUint256 // Completely empty the queue by appending (up to) an enormous number of queue elements.
)
const submitTransaction = (): Promise<TransactionReceipt> => {
return this.transactionSubmitter.submitTransaction(
tx,
this._makeHooks('appendQueueBatch')
)
}
// Empty the queue with a huge `appendQueueBatch(..)` call
return this._submitAndLogTx(submitTransaction, 'Cleared queue!')
}
private async submitAppendSequencerBatch(
batchParams: AppendSequencerBatchParams
): Promise<TransactionReceipt> {
const tx =
await this.chainContract.customPopulateTransaction.appendSequencerBatch(
batchParams
)
const submitTransaction = (): Promise<TransactionReceipt> => {
return this.transactionSubmitter.submitTransaction(
tx,
this._makeHooks('appendSequencerBatch')
)
}
return this._submitAndLogTx(submitTransaction, 'Submitted batch!')
}
private async _generateSequencerBatchParams(
startBlock: number,
endBlock: number
......
......@@ -16,6 +16,11 @@ import {
STATE_BATCH_SUBMITTER_LOG_TAG,
TX_BATCH_SUBMITTER_LOG_TAG,
} from '..'
import {
TransactionSubmitter,
YnatmTransactionSubmitter,
ResubmissionConfig,
} from '../utils'
interface RequiredEnvVars {
// The HTTP provider URL for L1.
......@@ -349,6 +354,18 @@ export const run = async () => {
addressManagerAddress: requiredEnvVars.ADDRESS_MANAGER_ADDRESS,
})
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: requiredEnvVars.RESUBMISSION_TIMEOUT * 1_000,
minGasPriceInGwei: MIN_GAS_PRICE_IN_GWEI,
maxGasPriceInGwei: GAS_THRESHOLD_IN_GWEI,
gasRetryIncrement: GAS_RETRY_INCREMENT,
}
const txBatchTxSubmitter: TransactionSubmitter =
new YnatmTransactionSubmitter(
sequencerSigner,
resubmissionConfig,
requiredEnvVars.NUM_CONFIRMATIONS
)
const txBatchSubmitter = new TransactionBatchSubmitter(
sequencerSigner,
l2Provider,
......@@ -360,10 +377,8 @@ export const run = async () => {
requiredEnvVars.RESUBMISSION_TIMEOUT * 1_000,
requiredEnvVars.ADDRESS_MANAGER_ADDRESS,
requiredEnvVars.SAFE_MINIMUM_ETHER_BALANCE,
MIN_GAS_PRICE_IN_GWEI,
MAX_GAS_PRICE_IN_GWEI,
GAS_RETRY_INCREMENT,
GAS_THRESHOLD_IN_GWEI,
txBatchTxSubmitter,
BLOCK_OFFSET,
logger.child({ name: TX_BATCH_SUBMITTER_LOG_TAG }),
metrics,
......@@ -371,6 +386,12 @@ export const run = async () => {
autoFixBatchOptions
)
const stateBatchTxSubmitter: TransactionSubmitter =
new YnatmTransactionSubmitter(
proposerSigner,
resubmissionConfig,
requiredEnvVars.NUM_CONFIRMATIONS
)
const stateBatchSubmitter = new StateBatchSubmitter(
proposerSigner,
l2Provider,
......@@ -383,10 +404,7 @@ export const run = async () => {
requiredEnvVars.FINALITY_CONFIRMATIONS,
requiredEnvVars.ADDRESS_MANAGER_ADDRESS,
requiredEnvVars.SAFE_MINIMUM_ETHER_BALANCE,
MIN_GAS_PRICE_IN_GWEI,
MAX_GAS_PRICE_IN_GWEI,
GAS_RETRY_INCREMENT,
GAS_THRESHOLD_IN_GWEI,
stateBatchTxSubmitter,
BLOCK_OFFSET,
logger.child({ name: STATE_BATCH_SUBMITTER_LOG_TAG }),
metrics,
......
export * from './batch-submitter'
export * from './utils'
export * from './transaction-chain-contract'
/* External Imports */
import { Contract, BigNumber } from 'ethers'
import { Contract, ethers } from 'ethers'
import {
TransactionResponse,
TransactionRequest,
......@@ -9,7 +9,7 @@ import {
AppendSequencerBatchParams,
BatchContext,
encodeAppendSequencerBatch,
encodeHex,
remove0x,
} from '@eth-optimism/core-utils'
export { encodeAppendSequencerBatch, BatchContext, AppendSequencerBatchParams }
......@@ -19,6 +19,27 @@ export { encodeAppendSequencerBatch, BatchContext, AppendSequencerBatchParams }
* where the `appendSequencerBatch(...)` function uses a specialized encoding for improved efficiency.
*/
export class CanonicalTransactionChainContract extends Contract {
public customPopulateTransaction = {
appendSequencerBatch: async (
batch: AppendSequencerBatchParams
): Promise<ethers.PopulatedTransaction> => {
const nonce = await this.signer.getTransactionCount()
const to = this.address
const data = getEncodedCalldata(batch)
const gasLimit = await this.signer.provider.estimateGas({
to,
from: await this.signer.getAddress(),
data,
})
return {
nonce,
to,
data,
gasLimit,
}
},
}
public async appendSequencerBatch(
batch: AppendSequencerBatchParams,
options?: TransactionRequest
......@@ -31,29 +52,24 @@ export class CanonicalTransactionChainContract extends Contract {
* Internal Functions *
*********************/
const APPEND_SEQUENCER_BATCH_METHOD_ID = 'appendSequencerBatch()'
const APPEND_SEQUENCER_BATCH_METHOD_ID = keccak256(
Buffer.from('appendSequencerBatch()')
).slice(2, 10)
const appendSequencerBatch = async (
OVM_CanonicalTransactionChain: Contract,
batch: AppendSequencerBatchParams,
options?: TransactionRequest
): Promise<TransactionResponse> => {
const methodId = keccak256(
Buffer.from(APPEND_SEQUENCER_BATCH_METHOD_ID)
).slice(2, 10)
const calldata = encodeAppendSequencerBatch(batch)
return OVM_CanonicalTransactionChain.signer.sendTransaction({
to: OVM_CanonicalTransactionChain.address,
data: '0x' + methodId + calldata,
data: getEncodedCalldata(batch),
...options,
})
}
const encodeBatchContext = (context: BatchContext): string => {
return (
encodeHex(context.numSequencedTransactions, 6) +
encodeHex(context.numSubsequentQueueTransactions, 6) +
encodeHex(context.timestamp, 10) +
encodeHex(context.blockNumber, 10)
)
const getEncodedCalldata = (batch: AppendSequencerBatchParams): string => {
const methodId = APPEND_SEQUENCER_BATCH_METHOD_ID
const calldata = encodeAppendSequencerBatch(batch)
return '0x' + remove0x(methodId) + remove0x(calldata)
}
export * from './tx-submission'
import { Signer, utils, ethers, PopulatedTransaction } from 'ethers'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
import * as ynatm from '@eth-optimism/ynatm'
export interface ResubmissionConfig {
resubmissionTimeout: number
minGasPriceInGwei: number
maxGasPriceInGwei: number
gasRetryIncrement: number
}
export type SubmitTransactionFn = (
tx: PopulatedTransaction
) => Promise<TransactionReceipt>
export interface TxSubmissionHooks {
beforeSendTransaction: (tx: PopulatedTransaction) => void
onTransactionResponse: (txResponse: TransactionResponse) => void
}
const getGasPriceInGwei = async (signer: Signer): Promise<number> => {
return parseInt(
ethers.utils.formatUnits(await signer.getGasPrice(), 'gwei'),
10
)
}
export const submitTransactionWithYNATM = async (
tx: PopulatedTransaction,
signer: Signer,
config: ResubmissionConfig,
numConfirmations: number,
hooks: TxSubmissionHooks
): Promise<TransactionReceipt> => {
const sendTxAndWaitForReceipt = async (
gasPrice
): Promise<TransactionReceipt> => {
const fullTx = {
...tx,
gasPrice,
}
hooks.beforeSendTransaction(fullTx)
const txResponse = await signer.sendTransaction(fullTx)
hooks.onTransactionResponse(txResponse)
return signer.provider.waitForTransaction(txResponse.hash, numConfirmations)
}
const minGasPrice = await getGasPriceInGwei(signer)
const receipt = await ynatm.send({
sendTransactionFunction: sendTxAndWaitForReceipt,
minGasPrice: ynatm.toGwei(minGasPrice),
maxGasPrice: ynatm.toGwei(config.maxGasPriceInGwei),
gasPriceScalingFunction: ynatm.LINEAR(config.gasRetryIncrement),
delay: config.resubmissionTimeout,
})
return receipt
}
export interface TransactionSubmitter {
submitTransaction(
tx: PopulatedTransaction,
hooks?: TxSubmissionHooks
): Promise<TransactionReceipt>
}
export class YnatmTransactionSubmitter implements TransactionSubmitter {
constructor(
readonly signer: Signer,
readonly ynatmConfig: ResubmissionConfig,
readonly numConfirmations: number
) {}
public async submitTransaction(
tx: PopulatedTransaction,
hooks?: TxSubmissionHooks
): Promise<TransactionReceipt> {
if (!hooks) {
hooks = {
beforeSendTransaction: () => undefined,
onTransactionResponse: () => undefined,
}
}
return submitTransactionWithYNATM(
tx,
this.signer,
this.ynatmConfig,
this.numConfirmations,
hooks
)
}
}
......@@ -27,6 +27,8 @@ import {
TX_BATCH_SUBMITTER_LOG_TAG,
STATE_BATCH_SUBMITTER_LOG_TAG,
BatchSubmitter,
YnatmTransactionSubmitter,
ResubmissionConfig,
} from '../../src'
import {
......@@ -200,8 +202,19 @@ describe('BatchSubmitter', () => {
sinon.restore()
})
const createBatchSubmitter = (timeout: number): TransactionBatchSubmitter =>
new TransactionBatchSubmitter(
const createBatchSubmitter = (timeout: number): TransactionBatchSubmitter => {
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: 100000,
minGasPriceInGwei: MIN_GAS_PRICE_IN_GWEI,
maxGasPriceInGwei: GAS_THRESHOLD_IN_GWEI,
gasRetryIncrement: GAS_RETRY_INCREMENT,
}
const txBatchTxSubmitter = new YnatmTransactionSubmitter(
sequencer,
resubmissionConfig,
1
)
return new TransactionBatchSubmitter(
sequencer,
l2Provider as any,
MIN_TX_SIZE,
......@@ -212,15 +225,14 @@ describe('BatchSubmitter', () => {
100000,
AddressManager.address,
1,
MIN_GAS_PRICE_IN_GWEI,
MAX_GAS_PRICE_IN_GWEI,
GAS_RETRY_INCREMENT,
GAS_THRESHOLD_IN_GWEI,
txBatchTxSubmitter,
1,
new Logger({ name: TX_BATCH_SUBMITTER_LOG_TAG }),
testMetrics,
false
)
}
describe('TransactionBatchSubmitter', () => {
describe('submitNextBatch', () => {
......@@ -375,7 +387,7 @@ describe('BatchSubmitter', () => {
.callsFake(async () => lowGasPriceWei)
const receipt = await batchSubmitter.submitNextBatch()
expect(sequencer.getGasPrice).to.have.been.calledOnce
expect(sequencer.getGasPrice).to.have.been.calledTwice
expect(receipt).to.not.be.undefined
})
})
......@@ -417,6 +429,17 @@ describe('BatchSubmitter', () => {
// submit a batch of transactions to enable state batch submission
await txBatchSubmitter.submitNextBatch()
const resubmissionConfig: ResubmissionConfig = {
resubmissionTimeout: 100000,
minGasPriceInGwei: MIN_GAS_PRICE_IN_GWEI,
maxGasPriceInGwei: GAS_THRESHOLD_IN_GWEI,
gasRetryIncrement: GAS_RETRY_INCREMENT,
}
const stateBatchTxSubmitter = new YnatmTransactionSubmitter(
sequencer,
resubmissionConfig,
1
)
stateBatchSubmitter = new StateBatchSubmitter(
sequencer,
l2Provider as any,
......@@ -429,10 +452,7 @@ describe('BatchSubmitter', () => {
0, // finalityConfirmations
AddressManager.address,
1,
MIN_GAS_PRICE_IN_GWEI,
MAX_GAS_PRICE_IN_GWEI,
GAS_RETRY_INCREMENT,
GAS_THRESHOLD_IN_GWEI,
stateBatchTxSubmitter,
1,
new Logger({ name: STATE_BATCH_SUBMITTER_LOG_TAG }),
testMetrics,
......@@ -473,54 +493,4 @@ describe('Batch Submitter with Ganache', () => {
after(async () => {
await server.close()
})
// Unit test for getReceiptWithResubmission function,
// tests for increasing gas price on resubmission
it('should resubmit a transaction if it is not confirmed', async () => {
const gasPrices = []
const numConfirmations = 2
const sendTxFunc = async (gasPrice) => {
// push the retried gasPrice
gasPrices.push(gasPrice)
const tx = signer.sendTransaction({
to: predeploys.OVM_SequencerEntrypoint,
value: 88,
nonce: 0,
gasPrice,
})
const response = await tx
return signer.provider.waitForTransaction(response.hash, numConfirmations)
}
const resubmissionConfig = {
numConfirmations,
resubmissionTimeout: 1_000, // retry every second
minGasPriceInGwei: 0,
maxGasPriceInGwei: 100,
gasRetryIncrement: 5,
}
BatchSubmitter.getReceiptWithResubmission(
sendTxFunc,
resubmissionConfig,
new Logger({ name: TX_BATCH_SUBMITTER_LOG_TAG })
)
// Wait 1.5s for at least 1 retry
await new Promise((r) => setTimeout(r, 1500))
// Iterate through gasPrices to ensure each entry increases from
// the last
const isIncreasing = gasPrices.reduce(
(isInc, gasPrice, i, gP) =>
(isInc && gasPrice > gP[i - 1]) || Number.NEGATIVE_INFINITY,
true
)
expect(gasPrices).to.have.lengthOf.above(1) // retried at least once
expect(isIncreasing).to.be.true
})
})
import { expect } from '../setup'
import { ethers, BigNumber, Signer } from 'ethers'
import { submitTransactionWithYNATM } from '../../src/utils/tx-submission'
import { ResubmissionConfig } from '../../src'
import {
TransactionReceipt,
TransactionResponse,
} from '@ethersproject/abstract-provider'
const nullFunction = () => undefined
const nullHooks = {
beforeSendTransaction: nullFunction,
onTransactionResponse: nullFunction,
}
describe('submitTransactionWithYNATM', async () => {
it('calls sendTransaction, waitForTransaction, and hooks with correct inputs', async () => {
const called = {
sendTransaction: false,
waitForTransaction: false,
beforeSendTransaction: false,
onTransactionResponse: false,
}
const dummyHash = 'dummy hash'
const numConfirmations = 3
const tx = {
data: 'we here though',
} as ethers.PopulatedTransaction
const sendTransaction = async (
_tx: ethers.PopulatedTransaction
): Promise<TransactionResponse> => {
called.sendTransaction = true
expect(_tx.data).to.equal(tx.data)
return {
hash: dummyHash,
} as TransactionResponse
}
const waitForTransaction = async (
hash: string,
_numConfirmations: number
): Promise<TransactionReceipt> => {
called.waitForTransaction = true
expect(hash).to.equal(dummyHash)
expect(_numConfirmations).to.equal(numConfirmations)
return {
to: '',
from: '',
status: 1,
} as TransactionReceipt
}
const signer = {
getGasPrice: async () => ethers.BigNumber.from(0),
sendTransaction,
provider: {
waitForTransaction,
},
} as Signer
const hooks = {
beforeSendTransaction: (submittingTx: ethers.PopulatedTransaction) => {
called.beforeSendTransaction = true
expect(submittingTx.data).to.equal(tx.data)
},
onTransactionResponse: (txResponse: TransactionResponse) => {
called.onTransactionResponse = true
expect(txResponse.hash).to.equal(dummyHash)
},
}
const config: ResubmissionConfig = {
resubmissionTimeout: 1000,
minGasPriceInGwei: 0,
maxGasPriceInGwei: 0,
gasRetryIncrement: 1,
}
await submitTransactionWithYNATM(
tx,
signer,
config,
numConfirmations,
hooks
)
expect(called.sendTransaction).to.be.true
expect(called.waitForTransaction).to.be.true
expect(called.beforeSendTransaction).to.be.true
expect(called.onTransactionResponse).to.be.true
})
it('repeatedly increases the gas limit of the transaction when wait takes too long', async () => {
// Make transactions take longer to be included
// than our resubmission timeout
const resubmissionTimeout = 100
const txReceiptDelay = resubmissionTimeout * 3
const numConfirmations = 3
let lastGasPrice = BigNumber.from(0)
// Create a transaction which has a gas price that we will watch increment
const tx = {
gasPrice: lastGasPrice.add(1),
data: 'hello world!',
} as ethers.PopulatedTransaction
const sendTransaction = async (
_tx: ethers.PopulatedTransaction
): Promise<TransactionResponse> => {
// Ensure the gas price is always increasing
expect(_tx.gasPrice > lastGasPrice).to.be.true
lastGasPrice = _tx.gasPrice
return {
hash: 'dummy hash',
} as TransactionResponse
}
const waitForTransaction = async (
hash: string,
_numConfirmations: number
): Promise<TransactionReceipt> => {
await new Promise((r) => setTimeout(r, txReceiptDelay))
return {} as TransactionReceipt
}
const signer = {
getGasPrice: async () => ethers.BigNumber.from(0),
sendTransaction,
provider: {
waitForTransaction,
},
} as Signer
const config: ResubmissionConfig = {
resubmissionTimeout,
minGasPriceInGwei: 0,
maxGasPriceInGwei: 1000,
gasRetryIncrement: 1,
}
await submitTransactionWithYNATM(tx, signer, config, 0, nullHooks)
})
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment