diff --git a/packages/batch-submitter/package.json b/packages/batch-submitter/package.json index c11d291008f9a..69e44a79bca4e 100755 --- a/packages/batch-submitter/package.json +++ b/packages/batch-submitter/package.json @@ -58,7 +58,7 @@ "@types/chai": "^4.2.18", "@types/lodash": "^4.14.168", "@types/mocha": "^8.2.2", - "@types/node": "^15.12.2", + "@types/node": "22", "@types/prettier": "^2.2.3", "@types/rimraf": "^3.0.0", "@types/sinon": "^9.0.10", diff --git a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts index 766abdb1ea45e..358f9793535c1 100755 --- a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts @@ -230,6 +230,14 @@ export abstract class BatchSubmitter { beforeSendTransaction: async (tx: ethers.TransactionRequest) => { this.logger.info(`Submitting ${txName} transaction`, { txType: tx.type, + gasPrice: tx.gasPrice ? toNumber(tx.gasPrice) : 0, + maxFeePerGas: tx.maxFeePerGas ? toNumber(tx.maxFeePerGas) : 0, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas + ? toNumber(tx.maxPriorityFeePerGas) + : 0, + maxFeePerBlobGas: tx.maxFeePerBlobGas + ? toNumber(tx.maxFeePerBlobGas) + : 0, gasLimit: tx.gasLimit ? toNumber(tx.gasLimit) : 0, nonce: toNumber(tx.nonce), contractAddr: tx.to, @@ -275,7 +283,6 @@ export abstract class BatchSubmitter { err: any ) => Promise ): Promise { - this.lastBatchSubmissionTimestamp = Date.now() this.logger.debug('Submitting transaction & waiting for receipt...') let receipt: ethers.TransactionReceipt @@ -308,7 +315,13 @@ export abstract class BatchSubmitter { return } - this.logger.info('Received transaction receipt', { receipt }) + // Update last submission timestamp when it's successful + this.lastBatchSubmissionTimestamp = Date.now() + this.logger.info('Received transaction receipt', { + txHash: receipt.hash, + blockNumber: receipt.blockNumber, + status: receipt.status, + }) this.logger.info(successMessage) this.metrics.batchesSubmitted.inc() this.metrics.submissionGasUsed.observe(toNumber(receipt.gasUsed)) diff --git a/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts b/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts index 7061f8e5ef13d..96b6f486b0140 100755 --- a/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts @@ -327,7 +327,7 @@ export class StateBatchSubmitter extends BatchSubmitter { type: 2, to: tx.to, data: tx.data, - value: ethers.parseEther('0'), + value: 0n, } const mpcAddress = mpcInfo.mpc_address txUnsign.nonce = await this.signer.provider.getTransactionCount( @@ -339,39 +339,26 @@ export class StateBatchSubmitter extends BatchSubmitter { data: tx.data, }) txUnsign.chainId = (await this.signer.provider.getNetwork()).chainId - // mpc model can use ynatm - // tx.gasPrice = gasPrice + const replaced = await setTxEIP1559Fees( + txUnsign, + await this.pendingStorage.getPendingTx(mpcAddress), + this.l1Provider, + this.resubmissionTimeout + ) - this.logger.info('submitting state with mpc address', { - mpcAddress, + this.logger.info('submitting state tx with mpc address', { + from: mpcAddress, + nonce: txUnsign.nonce, + to: tx.to, + replaced, startBlock, endBlock, - txUnsign, }) const submitSignedTransaction = (): Promise => { return this.transactionSubmitter.submitSignedTransaction( txUnsign, - async () => { - const replaced = await setTxEIP1559Fees( - txUnsign, - await this.pendingStorage.getPendingTx(mpcAddress), - this.l1Provider, - this.resubmissionTimeout - ) - this.logger.info('fee updated', { - maxFeePerGas: txUnsign.maxFeePerGas.toString(), - maxPriorityFeePerGas: txUnsign.maxPriorityFeePerGas.toString(), - replaced, - }) - - const signedTx = await mpcClient.signTx( - txUnsign, - mpcInfo.mpc_id, - this.mpcSignTimeout - ) - return signedTx - }, + () => mpcClient.signTx(txUnsign, mpcInfo.mpc_id, this.mpcSignTimeout), this._makeHooks('appendSequencerBatch') ) } @@ -502,7 +489,7 @@ export class StateBatchSubmitter extends BatchSubmitter { } this.logger.info('Generated state commitment batch', { - stateRoots, // list of stateRoots + stateRootsCount: stateRoots.length, }) return { stateRoots, diff --git a/packages/batch-submitter/src/batch-submitter/tx-batch-submitter-inbox.ts b/packages/batch-submitter/src/batch-submitter/tx-batch-submitter-inbox.ts index 1eb189df2ffdb..187c758c413f8 100644 --- a/packages/batch-submitter/src/batch-submitter/tx-batch-submitter-inbox.ts +++ b/packages/batch-submitter/src/batch-submitter/tx-batch-submitter-inbox.ts @@ -8,7 +8,6 @@ import { MinioConfig, QueueOrigin, remove0x, - sleep, toHexString, zlibCompressHexString, } from '@metis.io/core-utils' @@ -37,7 +36,7 @@ import { InboxBatchParams, TxData, } from '../da/types' -import { InboxStorage } from '../storage' +import { InboxSteps, InboxStorage } from '../storage' import { PendingStorage } from '../storage/pending-storage' import { MpcClient, setTxEIP1559Fees, TransactionSubmitter } from '../utils' @@ -86,6 +85,29 @@ export class TransactionBatchSubmitterInbox { ) => Promise ) => Promise ): Promise { + const steps = await this.inboxStorage.getStep() + if ( + useBlob && + steps !== null && + steps.txHashes.length < steps.blobs.length + 1 + ) { + this.logger.info( + 'Previous batch submission incomplete, continue submitting it' + ) + return this.submitSequencerBatch( + nextBatchIndex, + steps, + signer, + blobSigner, + mpcUrl, + mpcSignTimeout, + transactionSubmitter, + blobTransactionSubmitter, + hooks, + submitAndLogTx + ) + } + const params = await this._generateSequencerBatchParams( useBlob, startBlock, @@ -101,10 +123,6 @@ export class TransactionBatchSubmitterInbox { const [batchParams, wasBatchTruncated] = params // encodeBatch of calldata for _shouldSubmitBatch let batchSizeInBytes = batchParams.inputData.length / 2 - this.logger.debug('Sequencer batch generated', { - batchSizeInBytes, - }) - if (useBlob) { // when using blob txs, need to calculate the blob txs size, // to avoid the situation that the batch is too small, @@ -112,10 +130,9 @@ export class TransactionBatchSubmitterInbox { if (batchParams.blobTxData.length > 1) { batchSizeInBytes = 0 for (const txData of batchParams.blobTxData) { - batchSizeInBytes += txData.blobs.reduce( - (acc, blob) => acc + blob.data.length, - 0 - ) + for (const blob of txData.blobs) { + batchSizeInBytes += blob.data.length + } } } } @@ -128,15 +145,21 @@ export class TransactionBatchSubmitterInbox { return } metrics.numTxPerBatch.observe(endBlock - startBlock) - const l1tipHeight = await signer.provider.getBlockNumber() - this.logger.debug('Submitting batch to inbox.', { - calldata: batchParams, - l1tipHeight, + this.logger.info('Submitting batch to inbox', { + meta: batchParams.inputMeta, + useBlob, + blobTxes: batchParams.blobTxData.length, + batchSizeInBytes, + wasBatchTruncated, }) return this.submitSequencerBatch( nextBatchIndex, - batchParams, + { + input: batchParams.inputData, + blobs: batchParams.blobTxData.map((tx) => tx.blobs.map((b) => b.data)), + txHashes: [], + }, signer, blobSigner, mpcUrl, @@ -154,7 +177,7 @@ export class TransactionBatchSubmitterInbox { private async submitSequencerBatch( nextBatchIndex: number, - batchParams: InboxBatchParams, + batchParams: InboxSteps, signer: Signer, // need the second signer for blob txs, since blob txs are in a separate tx pool, // in EIP4844, ethereum does not allow one account sending txs to multiple pools at the same time @@ -173,58 +196,54 @@ export class TransactionBatchSubmitterInbox { ) => Promise ) => Promise ): Promise { - // MPC enabled: prepare nonce, gasPrice - const tx: TransactionRequest = { + const { chainId } = await signer.provider.getNetwork() + const inboxTx: TransactionRequest = { + type: 2, + chainId, to: this.inboxAddress, - data: '0x' + batchParams.inputData, - value: ethers.parseEther('0'), + data: '0x' + batchParams.input, + value: 0n, } - const { chainId } = await signer.provider.getNetwork() - // use blob txs if batch params contains blob tx data - const sendBlobTx = batchParams.blobTxData && batchParams.blobTxData.length - if (sendBlobTx) { - let mpcClient: MpcClient + const mpcClient = new MpcClient(mpcUrl, this.logger) + + // if using blob, we need to submit the blob txs before the inbox tx + if (batchParams.blobs && batchParams.blobs.length > 0) { + if (batchParams.txHashes.length === batchParams.blobs.length + 1) { + throw new Error('Batch already submitted') + } + + let signerAddress: string + let mpcId: string if (mpcUrl) { - mpcClient = new MpcClient(mpcUrl, this.logger) + // retrieve mpc info + // blob tx need to use mpc type 3 (specific for blob tx) to sign, + // just need to avoid collision with other tx types + const currentMpcInfo = await mpcClient.getLatestMpc('3') + if (!currentMpcInfo || !currentMpcInfo.mpc_address) { + throw new Error('MPC info get failed') + } + signerAddress = currentMpcInfo.mpc_address + mpcId = currentMpcInfo.mpc_id + } else { + signerAddress = await blobSigner.getAddress() } - // if using blob, we need to submit the blob txs before the inbox tx - const blobTxData = batchParams.blobTxData - // submit the blob txs in order, to simplify the process, - // use serialized operations for now - // TODO: use paralleled submission - for (const txData of blobTxData) { - const blobs = txData.blobs + // submit the blob txs in order + for (const [txIndex, blobs] of batchParams.blobs.entries()) { if (!blobs || !blobs.length) { throw new Error('Invalid blob tx data, empty blobs') } - let signerAddress: string - let mpcId: string - if (mpcUrl) { - // retrieve mpc info - // blob tx need to use mpc type 3 (specific for blob tx) to sign, - // just need to avoid collision with other tx types - const currentMpcInfo = await mpcClient.getLatestMpc('3') - if (!currentMpcInfo || !currentMpcInfo.mpc_address) { - throw new Error('MPC info get failed') - } - signerAddress = currentMpcInfo.mpc_address - mpcId = currentMpcInfo.mpc_id - } else { - signerAddress = await blobSigner.getAddress() + // skip already submitted blob tx + if (batchParams.txHashes[txIndex]) { + this.logger.info('Blob tx already submitted, skipping', { + txIndex, + txHash: batchParams.txHashes[txIndex], + }) + continue } - // async fetch required info - const nonce = await signer.provider.getTransactionCount(signerAddress) - - this.logger.info('submitting blob tx', { - blobCount: blobs.length, - signerAddress, - nonce, - }) - const blobTx: ethers.TransactionRequest = { type: 3, // 3 for blob tx type to: this.inboxAddress, @@ -232,72 +251,50 @@ export class TransactionBatchSubmitterInbox { // so the gas limit is just the default tx gas gasLimit: TX_GAS, chainId, - nonce, - blobs: blobs.map((b) => b.data), + nonce: await signer.provider.getTransactionCount(signerAddress), + blobs, blobVersion: 1, // Osaka is enabled on all the chains } + const replaced = await setTxEIP1559Fees( + blobTx, + await this.pendingStorage.getPendingTx(signerAddress), + this.l1Provider, + this.resubmissionTimeout + ) + + this.logger.info('submitting blob tx', { + blobs: blobs.length, + from: signerAddress, + nonce: blobTx.nonce, + step: `${txIndex + 1}/${batchParams.blobs.length}`, + replaced, + }) + // mpc model can use ynatm - let submitTx: () => Promise - if (mpcUrl) { - submitTx = (): Promise => { - return transactionSubmitter.submitSignedTransaction( - blobTx, - async () => { - const replaced = await setTxEIP1559Fees( - blobTx, - await this.pendingStorage.getPendingTx(signerAddress), - this.l1Provider, - this.resubmissionTimeout - ) - this.logger.info('Blob tx fees updated', { - maxFeePerGas: blobTx.maxFeePerGas.toString(), - maxPriorityFeePerGas: blobTx.maxPriorityFeePerGas.toString(), - maxFeePerBlobGas: blobTx.maxFeePerBlobGas.toString(), - replaced, - }) - - const signedTx = await mpcClient.signTx( - blobTx, - mpcId, - mpcSignTimeout - ) - - // need to append the blob sidecar to the signed tx - const signedTxUnmarshaled = ethers.Transaction.from(signedTx) - signedTxUnmarshaled.type = 3 - signedTxUnmarshaled.kzg = kzg - signedTxUnmarshaled.blobVersion = blobTx.blobVersion - signedTxUnmarshaled.blobs = blobTx.blobs - // repack the tx - return signedTxUnmarshaled.serialized - }, - hooks - ) - } - } else { - submitTx = async (): Promise => { - try { - const replaced = await setTxEIP1559Fees( + const submitTx: () => Promise = mpcUrl + ? () => + transactionSubmitter.submitSignedTransaction( blobTx, - await this.pendingStorage.getPendingTx(signerAddress), - this.l1Provider, - this.resubmissionTimeout + async () => { + const signedTx = await mpcClient.signTx( + blobTx, + mpcId, + mpcSignTimeout + ) + + // need to append the blob sidecar to the signed tx + const signedTxUnmarshaled = ethers.Transaction.from(signedTx) + signedTxUnmarshaled.type = 3 + signedTxUnmarshaled.kzg = kzg + signedTxUnmarshaled.blobVersion = blobTx.blobVersion + signedTxUnmarshaled.blobs = blobTx.blobs + // repack the tx + return signedTxUnmarshaled.serialized + }, + hooks ) - this.logger.info('Blob tx fees updated', { - maxFeePerGas: blobTx.maxFeePerGas.toString(), - maxPriorityFeePerGas: blobTx.maxPriorityFeePerGas.toString(), - maxFeePerBlobGas: blobTx.maxFeePerBlobGas.toString(), - replaced, - }) - - return blobTransactionSubmitter.submitTransaction(blobTx, hooks) - } catch (err) { - this.logger.error('blob tx submission failed', { err }) - throw new Error('Blob tx submission failed') - } - } - } + : () => blobTransactionSubmitter.submitTransaction(blobTx, hooks) const blobTxReceipt = await submitAndLogTx( submitTx, @@ -323,59 +320,49 @@ export class TransactionBatchSubmitterInbox { throw new Error('Blob tx submission failed') } - // append tx hashes to the tx data to the end - tx.data += remove0x(blobTxReceipt.hash) + batchParams.txHashes.push(blobTxReceipt.hash) + await this.inboxStorage.insertStep(batchParams) } + + // append all blob tx hashes to inbox tx data + inboxTx.data += batchParams.txHashes.reduce( + (acc, hash) => acc + remove0x(hash), + '' + ) } + this.logger.info('submit inbox tx', { + nonce: inboxTx.nonce, + blobTxs: batchParams.txHashes.join(','), + }) + + // Build and send inbox transaction // mpc url specified, use mpc to sign tx if (mpcUrl) { - // sleep 3000 ms to avoid mpc signing collision - this.logger.info('sleep 3000 ms to avoid mpc signing collision') - await sleep(3000) - - this.logger.info('submitter with mpc', { url: mpcUrl }) - const mpcClient = new MpcClient(mpcUrl, this.logger) - const mpcInfo = await mpcClient.getLatestMpc() if (!mpcInfo || !mpcInfo.mpc_address) { throw new Error('MPC info get failed') } const mpcAddress = mpcInfo.mpc_address - tx.type = 2 - tx.nonce = await signer.provider.getTransactionCount(mpcAddress) - tx.gasLimit = await signer.provider.estimateGas({ - to: tx.to, + inboxTx.nonce = await signer.provider.getTransactionCount(mpcAddress) + inboxTx.gasLimit = await signer.provider.estimateGas({ + to: inboxTx.to, from: mpcAddress, - data: tx.data, + data: inboxTx.data, }) - tx.chainId = chainId + await setTxEIP1559Fees( + inboxTx, + await this.pendingStorage.getPendingTx(mpcAddress), + this.l1Provider, + this.resubmissionTimeout + ) // mpc model can use ynatm const submitSignedTransaction = (): Promise => { return transactionSubmitter.submitSignedTransaction( - tx, - async () => { - const replaced = await setTxEIP1559Fees( - tx, - await this.pendingStorage.getPendingTx(mpcAddress), - this.l1Provider, - this.resubmissionTimeout - ) - this.logger.info('MPC tx fees updated', { - maxFeePerGas: tx.maxFeePerGas.toString(), - maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toString(), - replaced, - }) - - const signedTx = await mpcClient.signTx( - tx, - mpcInfo.mpc_id, - mpcSignTimeout - ) - return signedTx - }, + inboxTx, + () => mpcClient.signTx(inboxTx, mpcInfo.mpc_id, mpcSignTimeout), hooks ) } @@ -383,50 +370,64 @@ export class TransactionBatchSubmitterInbox { return submitAndLogTx( submitSignedTransaction, 'Submitted batch to inbox with MPC!', - (receipt: TransactionReceipt | null, err: any): Promise => { - return this._setBatchInboxRecord(receipt, err, nextBatchIndex) + async ( + receipt: TransactionReceipt | null, + err: any + ): Promise => { + return this._setBatchInboxRecord( + batchParams, + receipt, + err, + nextBatchIndex + ) } ) } else { - tx.nonce = await signer.getNonce() - tx.gasLimit = await signer.provider.estimateGas({ + inboxTx.nonce = await signer.getNonce() + inboxTx.gasLimit = await signer.provider.estimateGas({ //estimate gas - to: tx.to, + to: inboxTx.to, from: await signer.getAddress(), - data: tx.data, + data: inboxTx.data, }) - const replaced = await setTxEIP1559Fees( - tx, + await setTxEIP1559Fees( + inboxTx, await this.pendingStorage.getPendingTx(await signer.getAddress()), this.l1Provider, this.resubmissionTimeout ) - this.logger.info('Tx fees updated', { - maxFeePerGas: tx.maxFeePerGas.toString(), - maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toString(), - replaced, - }) } const submitTransaction = (): Promise => { - return transactionSubmitter.submitTransaction(tx, hooks) + return transactionSubmitter.submitTransaction(inboxTx, hooks) } return submitAndLogTx( submitTransaction, 'Submitted batch to inbox!', - (receipt: TransactionReceipt | null, err: any): Promise => { - return this._setBatchInboxRecord(receipt, err, nextBatchIndex) + async ( + receipt: TransactionReceipt | null, + err: any + ): Promise => { + return this._setBatchInboxRecord( + batchParams, + receipt, + err, + nextBatchIndex + ) } ) } private async _setBatchInboxRecord( + batchParams: InboxSteps, receipt: TransactionReceipt | null, err: any, batchIndex: number ): Promise { let saveStatus = false if (receipt && (receipt.status === undefined || receipt.status === 1)) { + batchParams.txHashes.push(receipt.hash) + await this.inboxStorage.insertStep(batchParams) saveStatus = await this.inboxStorage.recordConfirmedTx({ batchIndex, blockNumber: receipt.blockNumber, @@ -660,6 +661,13 @@ export class TransactionBatchSubmitterInbox { encoded = `${da}${compressType}${batchIndex}${l2Start}${totalElements}${compressedEncoded}` return { + inputMeta: { + da, + compressType, + batchIndex, + l2Start, + totalElements, + }, inputData: encoded, batch: blocks, blobTxData, diff --git a/packages/batch-submitter/src/batch-submitter/tx-batch-submitter.ts b/packages/batch-submitter/src/batch-submitter/tx-batch-submitter.ts index 28b77d708dafd..2eb8a32e3e3e8 100755 --- a/packages/batch-submitter/src/batch-submitter/tx-batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/tx-batch-submitter.ts @@ -413,25 +413,6 @@ export class TransactionBatchSubmitter extends BatchSubmitter { useInbox?: boolean, nextBatchIndex?: number ): Promise { - // Do not submit batch if gas price above threshold - const gasPriceInGwei = parseInt( - ethers.formatUnits( - (await this.signer.provider.getFeeData()).gasPrice, - 'gwei' - ), - 10 - ) - if (gasPriceInGwei > this.gasThresholdInGwei) { - this.logger.warn( - 'Gas price is higher than gas price threshold; aborting batch submission', - { - gasPriceInGwei, - gasThresholdInGwei: this.gasThresholdInGwei, - } - ) - return - } - if (useInbox) { this.logger.info('Submit batch to inbox address', { startBlock, diff --git a/packages/batch-submitter/src/da/consts.ts b/packages/batch-submitter/src/da/consts.ts index f5338550ab4ee..4ba41c2dbf863 100644 --- a/packages/batch-submitter/src/da/consts.ts +++ b/packages/batch-submitter/src/da/consts.ts @@ -1,6 +1,6 @@ export const FRAME_OVERHEAD_SIZE = 200 export const MAX_RLP_BYTES_PER_CHANNEL = 100_000_000 export const MAX_BLOB_SIZE = (4 * 31 + 3) * 1024 - 4 -export const MAX_BLOB_NUM_PER_TX = 5 +export const MAX_BLOB_NUM_PER_TX = 7 export const TX_GAS = 21_000 export const CHANNEL_FULL_ERR = new Error('Channel is full') diff --git a/packages/batch-submitter/src/da/types.ts b/packages/batch-submitter/src/da/types.ts index 817a88810603c..ecd0460dd4c8b 100644 --- a/packages/batch-submitter/src/da/types.ts +++ b/packages/batch-submitter/src/da/types.ts @@ -155,7 +155,16 @@ export interface BatchToInboxElement { } export declare type BatchToInbox = BatchToInboxElement[] +export interface InboxInputMeta { + da: string + compressType: string + batchIndex: string + l2Start: string + totalElements: string +} + export interface InboxBatchParams { + inputMeta: InboxInputMeta inputData: string batch: BatchToInbox blobTxData: TxData[] diff --git a/packages/batch-submitter/src/storage/inbox-storage.ts b/packages/batch-submitter/src/storage/inbox-storage.ts index 253a7ff098445..cc8e0e4915dff 100644 --- a/packages/batch-submitter/src/storage/inbox-storage.ts +++ b/packages/batch-submitter/src/storage/inbox-storage.ts @@ -1,11 +1,12 @@ /* Imports: External */ import { Logger } from '@eth-optimism/common-ts' -import { toBigInt, toNumber } from 'ethersv6' +import { BytesLike, toNumber } from 'ethersv6' import * as fs from 'fs/promises' import * as path from 'path' const INBOX_OK_FILE = 'inbox_ok.json' const INBOX_FAIL_FILE = 'inbox_fail.json' +const STEPS_FILE = 'steps.json' export interface InboxRecordInfo { batchIndex: number | bigint @@ -13,6 +14,12 @@ export interface InboxRecordInfo { txHash: string } +export interface InboxSteps { + input: string // the inbox tx data input + blobs: Array> // array of blob tx data + txHashes: Array // blob tx hashes + inbox tx hash +} + export class InboxStorage { public storagePath: string private logger: Logger @@ -27,21 +34,14 @@ export class InboxStorage { errMsg: string ): Promise { const jsonData = { - batchIndex: toBigInt(batchIndex), + batchIndex: toNumber(batchIndex), errMsg, } const jsonString = JSON.stringify(jsonData, null, 2) const filePath = path.join(this.storagePath, INBOX_FAIL_FILE) - try { - const fileHandle = await fs.open(filePath, 'w') - await fileHandle.write(jsonString) - await fileHandle.close() - this.logger.info('JSON data has been written to failed tx', { filePath }) - return true - } catch (writeError) { - this.logger.error('Error writing to failed tx file:', writeError) - throw new Error('Error writing to failed tx file') - } + await fs.writeFile(filePath, jsonString) + this.logger.info('JSON data has been written to failed tx', { filePath }) + return true } public async recordConfirmedTx(inbox: InboxRecordInfo): Promise { @@ -52,40 +52,53 @@ export class InboxStorage { } const jsonString = JSON.stringify(jsonData, null, 2) const filePath = path.join(this.storagePath, INBOX_OK_FILE) - try { - const fileHandle = await fs.open(filePath, 'w') - await fileHandle.write(jsonString) - await fileHandle.close() - this.logger.info('JSON data has been written to ok_tx file', { filePath }) - return true - } catch (writeError) { - this.logger.error('Error writing to ok_tx file:', writeError) - throw new Error('Error writing to ok_tx file') - } + await fs.writeFile(filePath, jsonString) + this.logger.info('JSON data has been written to ok_tx file', { filePath }) + return true } public async getLatestConfirmedTx(): Promise { const filePath = path.join(this.storagePath, INBOX_OK_FILE) - if (!this.fileExists(filePath)) { + if (!(await this.fileExists(filePath))) { return null } - try { - const data = await fs.readFile(filePath, 'utf-8') - if (!data) { - return null - } - const readJsonData = JSON.parse(data) - return { - batchIndex: readJsonData.batchIndex, - blockNumber: readJsonData.number, - txHash: readJsonData.hash, - } - } catch (readError) { - if (readError.code !== 'ENOENT') { - this.logger.error('Error reading ok_tx file', readError) - } + const data = await fs.readFile(filePath, 'utf-8') + if (!data) { + return null + } + const readJsonData = JSON.parse(data) + return { + batchIndex: readJsonData.batchIndex, + blockNumber: readJsonData.number, + txHash: readJsonData.hash, + } + } + + public async insertStep(jsonData: InboxSteps) { + const data = { + input: jsonData.input, + txHashes: jsonData.txHashes, + blobs: jsonData.blobs.map((blobArray) => + blobArray.map((blob) => { + if (typeof blob === 'string') { + return blob + } + return '0x' + Buffer.from(blob).toString('hex') + }) + ), + } + const jsonString = JSON.stringify(data, null, 2) + const filePath = path.join(this.storagePath, STEPS_FILE) + await fs.writeFile(filePath, jsonString) + } + + public async getStep(): Promise { + const filePath = path.join(this.storagePath, STEPS_FILE) + if (!(await this.fileExists(filePath))) { + return null } - return null + const data = await fs.readFile(filePath, 'utf-8') + return JSON.parse(data) } private async fileExists(filePath) { diff --git a/packages/batch-submitter/src/storage/pending-storage.ts b/packages/batch-submitter/src/storage/pending-storage.ts index aa1397ed87346..09d277d9c4ad6 100644 --- a/packages/batch-submitter/src/storage/pending-storage.ts +++ b/packages/batch-submitter/src/storage/pending-storage.ts @@ -90,7 +90,10 @@ export class PendingStorage { await fs.stat(filePath) return true } catch (error) { - return false + if (error.code === 'ENOENT') { + return false + } + throw error } } } diff --git a/packages/batch-submitter/src/utils/tx-submission.ts b/packages/batch-submitter/src/utils/tx-submission.ts index 20e6cdd20bc79..12aba1e0fcdab 100644 --- a/packages/batch-submitter/src/utils/tx-submission.ts +++ b/packages/batch-submitter/src/utils/tx-submission.ts @@ -30,6 +30,22 @@ export const setTxEIP1559Fees = async ( resubmissionTimeout: number ): Promise => { const feeData = await l1Provider.getFeeData() + + // Use 1Gwei as the tip fee for blob tx + const newMaxFeePerGas = + tx.type === 3 + ? feeData.maxFeePerGas * 2n + BigInt(1e9) + : feeData.maxFeePerGas * 2n + BigInt(1e7) + const newMaxPriorityFeePerGas = + tx.type === 3 && feeData.maxPriorityFeePerGas < BigInt(1e9) + ? BigInt(1e9) + : feeData.maxPriorityFeePerGas < BigInt(1e7) + ? BigInt(1e7) + : feeData.maxPriorityFeePerGas + + const newBlobBaseFeePerGas = + tx.type === 3 ? 2n * (await getBlobBaseFee(l1Provider)) : 0n + // check if pending tx exists and has not been confirmed yet, // also need to check if the resubmission timeout has passed, // will only bump the fees if the timeout has passed @@ -48,9 +64,6 @@ export const setTxEIP1559Fees = async ( const bumpedMaxPriorityFeePerGas = (toBigInt(oldTx.maxPriorityFeePerGas) * (100n + bumpThreshold)) / 100n - const newMaxFeePerGas = feeData.maxFeePerGas * 2n + BigInt(1e7) - const newMaxPriorityFeePerGas = feeData.maxPriorityFeePerGas + BigInt(1e7) - tx.maxFeePerGas = bumpedMaxFeePerGas > newMaxFeePerGas ? bumpedMaxFeePerGas @@ -61,7 +74,7 @@ export const setTxEIP1559Fees = async ( : newMaxPriorityFeePerGas if (tx.type === 3) { const bumpedMaxFeePerBlobGas = toBigInt(oldTx.maxFeePerBlobGas) * 2n - const newMaxFeePerBlobGas = (await getBlobBaseFee(l1Provider)) * 2n + const newMaxFeePerBlobGas = newBlobBaseFeePerGas tx.maxFeePerBlobGas = newMaxFeePerBlobGas > bumpedMaxFeePerBlobGas ? newMaxFeePerBlobGas @@ -70,10 +83,10 @@ export const setTxEIP1559Fees = async ( return true } - tx.maxFeePerGas = feeData.maxFeePerGas * 2n + BigInt(1e7) - tx.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas + BigInt(1e7) + tx.maxFeePerGas = newMaxFeePerGas + tx.maxPriorityFeePerGas = newMaxPriorityFeePerGas if (tx.type === 3) { - tx.maxFeePerBlobGas = (await getBlobBaseFee(l1Provider)) * 2n + tx.maxFeePerBlobGas = newBlobBaseFeePerGas } return false } diff --git a/yarn.lock b/yarn.lock index 9fc1efe0f5c2f..814debff3354f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -393,7 +393,7 @@ __metadata: "@types/chai": "npm:^4.2.18" "@types/lodash": "npm:^4.14.168" "@types/mocha": "npm:^8.2.2" - "@types/node": "npm:^15.12.2" + "@types/node": "npm:22" "@types/prettier": "npm:^2.2.3" "@types/rimraf": "npm:^3.0.0" "@types/sinon": "npm:^9.0.10" @@ -4439,6 +4439,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:22": + version: 22.19.1 + resolution: "@types/node@npm:22.19.1" + dependencies: + undici-types: "npm:~6.21.0" + checksum: 10c0/6edd93aea86da740cb7872626839cd6f4a67a049d3a3a6639cb592c620ec591408a30989ab7410008d1a0b2d4985ce50f1e488e79c033e4476d3bec6833b0a2f + languageName: node + linkType: hard + "@types/node@npm:22.7.5": version: 22.7.5 resolution: "@types/node@npm:22.7.5" @@ -4462,13 +4471,6 @@ __metadata: languageName: node linkType: hard -"@types/node@npm:^15.12.2": - version: 15.14.9 - resolution: "@types/node@npm:15.14.9" - checksum: 10c0/fe5b69cffd20f97c814d568c1d791b3c367f9efa6567a18d2c15cd73c5437f47bcff73a2e10bdfe59f90ce7df47e6cc3c6d431c76d2213bf6099e8ab5d16d355 - languageName: node - linkType: hard - "@types/node@npm:^8.0.0": version: 8.10.66 resolution: "@types/node@npm:8.10.66" @@ -21381,6 +21383,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.21.0": + version: 6.21.0 + resolution: "undici-types@npm:6.21.0" + checksum: 10c0/c01ed51829b10aa72fc3ce64b747f8e74ae9b60eafa19a7b46ef624403508a54c526ffab06a14a26b3120d055e1104d7abe7c9017e83ced038ea5cf52f8d5e04 + languageName: node + linkType: hard + "undici-types@npm:~7.16.0": version: 7.16.0 resolution: "undici-types@npm:7.16.0"