From 8d21bfa63ea4b4b3b5b5445e162605798198c2bd Mon Sep 17 00:00:00 2001 From: ericlee Date: Thu, 4 Dec 2025 19:59:35 +0800 Subject: [PATCH 01/15] batch: update submission timestamp logging and state roots info --- .../src/batch-submitter/batch-submitter.ts | 9 +++++++-- .../src/batch-submitter/state-batch-submitter.ts | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts index 766abdb1ea45e..8634654580ab1 100755 --- a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts @@ -275,7 +275,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 +307,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..7221043b9b57c 100755 --- a/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/state-batch-submitter.ts @@ -502,7 +502,7 @@ export class StateBatchSubmitter extends BatchSubmitter { } this.logger.info('Generated state commitment batch', { - stateRoots, // list of stateRoots + stateRootsCount: stateRoots.length, }) return { stateRoots, From a2846fdf1bad26205b453588413f4cd67c219856 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 09:31:04 +0800 Subject: [PATCH 02/15] batch: add inbox steps storage --- packages/batch-submitter/package.json | 2 +- .../tx-batch-submitter-inbox.ts | 263 +++++++++++------- .../src/batch-submitter/tx-batch-submitter.ts | 19 -- packages/batch-submitter/src/da/types.ts | 9 + .../src/storage/inbox-storage.ts | 91 +++--- .../src/storage/pending-storage.ts | 5 +- .../src/utils/tx-submission.ts | 13 +- yarn.lock | 25 +- 8 files changed, 253 insertions(+), 174 deletions(-) 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/tx-batch-submitter-inbox.ts b/packages/batch-submitter/src/batch-submitter/tx-batch-submitter-inbox.ts index 1eb189df2ffdb..e1b746db06e21 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 + } } } } @@ -125,18 +142,32 @@ export class TransactionBatchSubmitterInbox { // 2. it is large enough // 3. enough time has passed since last submission if (!wasBatchTruncated && !shouldSubmitBatch(batchSizeInBytes)) { + this.logger.info('Skipping batch submission to inbox', { + meta: batchParams.inputMeta, + useBlob, + batchSizeInBytes, + wasBatchTruncated, + }) return } metrics.numTxPerBatch.observe(endBlock - startBlock) - const l1tipHeight = await signer.provider.getBlockNumber() - this.logger.debug('Submitting batch to inbox.', { - calldata: batchParams, - l1tipHeight, + this.logger.debug('Submitting batch to inbox', { + meta: batchParams.inputMeta, + useBlob, + batchSizeInBytes, + wasBatchTruncated, }) + // const params = + // steps == null || steps.txHashes.length === steps.blobs.length + 1 + 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 +185,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 +204,62 @@ export class TransactionBatchSubmitterInbox { ) => Promise ) => Promise ): Promise { + if (batchParams.txHashes.length === batchParams.blobs.length + 1) { + throw new Error('Batch already submitted') + } + // 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' + remove0x(batchParams.input), // use remove0x for compatibility + 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 + const sendBlobTx = batchParams.blobs && batchParams.blobs.length > 0 if (sendBlobTx) { let mpcClient: MpcClient + 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 + const blobTxData = batchParams.blobs + this.logger.info('Submitting blob txs for inbox batch', { + count: blobTxData.length, + }) + // 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 + for (const [txIndex, blobs] of blobTxData.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() + 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,11 +267,18 @@ 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 } + this.logger.info('submitting blob tx', { + count: blobs.length, + from: signerAddress, + nonce: blobTx.nonce, + step: `${txIndex}/${blobTxData.length}`, + }) + // mpc model can use ynatm let submitTx: () => Promise if (mpcUrl) { @@ -277,25 +319,19 @@ export class TransactionBatchSubmitterInbox { } } else { submitTx = async (): Promise => { - try { - 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, - }) - - return blobTransactionSubmitter.submitTransaction(blobTx, hooks) - } catch (err) { - this.logger.error('blob tx submission failed', { err }) - throw new Error('Blob tx submission failed') - } + 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, + }) + return blobTransactionSubmitter.submitTransaction(blobTx, hooks) } } @@ -323,18 +359,19 @@ export class TransactionBatchSubmitterInbox { throw new Error('Blob tx submission failed') } + batchParams.txHashes.push(blobTxReceipt.hash) + await this.inboxStorage.insertStep(batchParams) // append tx hashes to the tx data to the end - tx.data += remove0x(blobTxReceipt.hash) + inboxTx.data += remove0x(blobTxReceipt.hash) } } + // 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 }) + this.logger.info('submitter inbox meta with mpc', { + blobTxs: batchParams.txHashes.join(','), + }) const mpcClient = new MpcClient(mpcUrl, this.logger) const mpcInfo = await mpcClient.getLatestMpc() @@ -343,38 +380,30 @@ export class TransactionBatchSubmitterInbox { } 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 // mpc model can use ynatm const submitSignedTransaction = (): Promise => { return transactionSubmitter.submitSignedTransaction( - tx, + inboxTx, async () => { const replaced = await setTxEIP1559Fees( - tx, + inboxTx, await this.pendingStorage.getPendingTx(mpcAddress), this.l1Provider, this.resubmissionTimeout ) this.logger.info('MPC tx fees updated', { - maxFeePerGas: tx.maxFeePerGas.toString(), - maxPriorityFeePerGas: tx.maxPriorityFeePerGas.toString(), + maxFeePerGas: inboxTx.maxFeePerGas.toString(), + maxPriorityFeePerGas: inboxTx.maxPriorityFeePerGas.toString(), replaced, }) - - const signedTx = await mpcClient.signTx( - tx, - mpcInfo.mpc_id, - mpcSignTimeout - ) - return signedTx + return mpcClient.signTx(inboxTx, mpcInfo.mpc_id, mpcSignTimeout) }, hooks ) @@ -383,50 +412,69 @@ 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, + 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(), + maxFeePerGas: inboxTx.maxFeePerGas.toString(), + maxPriorityFeePerGas: inboxTx.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 +708,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/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..a3d6cfd86900d 100644 --- a/packages/batch-submitter/src/utils/tx-submission.ts +++ b/packages/batch-submitter/src/utils/tx-submission.ts @@ -48,8 +48,17 @@ 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) + // 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 tx.maxFeePerGas = bumpedMaxFeePerGas > newMaxFeePerGas 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" From ee30c87fa91c7e46e8ab29ce6823613e49fcea72 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 09:39:49 +0800 Subject: [PATCH 03/15] batch: update logs --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) 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 e1b746db06e21..2f45ea27c8f65 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 @@ -158,9 +158,6 @@ export class TransactionBatchSubmitterInbox { wasBatchTruncated, }) - // const params = - // steps == null || steps.txHashes.length === steps.blobs.length + 1 - return this.submitSequencerBatch( nextBatchIndex, { @@ -242,7 +239,7 @@ export class TransactionBatchSubmitterInbox { // if using blob, we need to submit the blob txs before the inbox tx const blobTxData = batchParams.blobs this.logger.info('Submitting blob txs for inbox batch', { - count: blobTxData.length, + txes: blobTxData.length, }) // submit the blob txs in order, to simplify the process, @@ -273,7 +270,7 @@ export class TransactionBatchSubmitterInbox { } this.logger.info('submitting blob tx', { - count: blobs.length, + blobs: blobs.length, from: signerAddress, nonce: blobTx.nonce, step: `${txIndex}/${blobTxData.length}`, From ab26703c063c20d65ebe959df953b5cc51fa9260 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 09:46:10 +0800 Subject: [PATCH 04/15] batch: update logs --- .../tx-batch-submitter-inbox.ts | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) 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 2f45ea27c8f65..1c8ee3a1ea8fa 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 @@ -151,9 +151,10 @@ export class TransactionBatchSubmitterInbox { return } metrics.numTxPerBatch.observe(endBlock - startBlock) - this.logger.debug('Submitting batch to inbox', { + this.logger.info('Submitting batch to inbox', { meta: batchParams.inputMeta, useBlob, + blobTxes: batchParams.blobTxData.length, batchSizeInBytes, wasBatchTruncated, }) @@ -215,7 +216,7 @@ export class TransactionBatchSubmitterInbox { value: 0n, } - // use blob txs if batch params contains blob tx data + // if using blob, we need to submit the blob txs before the inbox tx const sendBlobTx = batchParams.blobs && batchParams.blobs.length > 0 if (sendBlobTx) { let mpcClient: MpcClient @@ -236,19 +237,13 @@ export class TransactionBatchSubmitterInbox { signerAddress = await blobSigner.getAddress() } - // if using blob, we need to submit the blob txs before the inbox tx - const blobTxData = batchParams.blobs - this.logger.info('Submitting blob txs for inbox batch', { - txes: blobTxData.length, - }) - - // submit the blob txs in order, to simplify the process, - // use serialized operations for now - for (const [txIndex, blobs] of blobTxData.entries()) { + // 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') } + // skip already submitted blob tx if (batchParams.txHashes[txIndex]) { this.logger.info('Blob tx already submitted, skipping', { txIndex, @@ -273,7 +268,7 @@ export class TransactionBatchSubmitterInbox { blobs: blobs.length, from: signerAddress, nonce: blobTx.nonce, - step: `${txIndex}/${blobTxData.length}`, + step: `${txIndex}/${batchParams.blobs.length}`, }) // mpc model can use ynatm From 81f9e7bea75fd02719a356af8f16c6fc066695e3 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 09:52:34 +0800 Subject: [PATCH 05/15] batch: fix steps' input data --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) 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 1c8ee3a1ea8fa..83f234a5f3a4b 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 @@ -208,16 +208,17 @@ export class TransactionBatchSubmitterInbox { // MPC enabled: prepare nonce, gasPrice const { chainId } = await signer.provider.getNetwork() + const sendBlobTx = batchParams.blobs && batchParams.blobs.length > 0 + const inboxTx: TransactionRequest = { type: 2, chainId, to: this.inboxAddress, - data: '0x' + remove0x(batchParams.input), // use remove0x for compatibility + data: sendBlobTx ? '0x' : '0x' + remove0x(batchParams.input), value: 0n, } // if using blob, we need to submit the blob txs before the inbox tx - const sendBlobTx = batchParams.blobs && batchParams.blobs.length > 0 if (sendBlobTx) { let mpcClient: MpcClient let signerAddress: string @@ -351,11 +352,13 @@ export class TransactionBatchSubmitterInbox { throw new Error('Blob tx submission failed') } + batchParams.input += remove0x(blobTxReceipt.hash) batchParams.txHashes.push(blobTxReceipt.hash) await this.inboxStorage.insertStep(batchParams) // append tx hashes to the tx data to the end - inboxTx.data += remove0x(blobTxReceipt.hash) } + + inboxTx.data += remove0x(batchParams.input) } // Build and send inbox transaction From 80aca002f6c92478f5df5386daa808cfd9a23e4c Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 09:54:37 +0800 Subject: [PATCH 06/15] batch: update check log --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) 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 83f234a5f3a4b..8366d0a785c5a 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 @@ -202,14 +202,9 @@ export class TransactionBatchSubmitterInbox { ) => Promise ) => Promise ): Promise { - if (batchParams.txHashes.length === batchParams.blobs.length + 1) { - throw new Error('Batch already submitted') - } - // MPC enabled: prepare nonce, gasPrice const { chainId } = await signer.provider.getNetwork() const sendBlobTx = batchParams.blobs && batchParams.blobs.length > 0 - const inboxTx: TransactionRequest = { type: 2, chainId, @@ -220,6 +215,10 @@ export class TransactionBatchSubmitterInbox { // if using blob, we need to submit the blob txs before the inbox tx if (sendBlobTx) { + if (batchParams.txHashes.length === batchParams.blobs.length + 1) { + throw new Error('Batch already submitted') + } + let mpcClient: MpcClient let signerAddress: string let mpcId: string From 02689466a045101b2ae5e6b7cf1d2e52511660a4 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 09:56:27 +0800 Subject: [PATCH 07/15] batch: fix step index log --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 8366d0a785c5a..78e7931bc0387 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 @@ -268,7 +268,7 @@ export class TransactionBatchSubmitterInbox { blobs: blobs.length, from: signerAddress, nonce: blobTx.nonce, - step: `${txIndex}/${batchParams.blobs.length}`, + step: `${txIndex + 1}/${batchParams.blobs.length}`, }) // mpc model can use ynatm From 46a6a6ea4c41d1ba66a326402fd64f8c58bd96c3 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 10:04:20 +0800 Subject: [PATCH 08/15] batch: update input data handling for blob transactions --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) 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 78e7931bc0387..ac5e643f0dec4 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 @@ -209,7 +209,8 @@ export class TransactionBatchSubmitterInbox { type: 2, chainId, to: this.inboxAddress, - data: sendBlobTx ? '0x' : '0x' + remove0x(batchParams.input), + // if it's for blob, set input data later + data: sendBlobTx ? undefined : '0x' + remove0x(batchParams.input), value: 0n, } @@ -351,13 +352,15 @@ export class TransactionBatchSubmitterInbox { throw new Error('Blob tx submission failed') } - batchParams.input += remove0x(blobTxReceipt.hash) batchParams.txHashes.push(blobTxReceipt.hash) await this.inboxStorage.insertStep(batchParams) - // append tx hashes to the tx data to the end } - inboxTx.data += remove0x(batchParams.input) + // set inbox tx data + inboxTx.data = + '0x' + + remove0x(batchParams.input) + + batchParams.txHashes.reduce((acc, hash) => acc + remove0x(hash), '') } // Build and send inbox transaction From ba84f1bc83d3670471156714a1047db6e5f817c9 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 10:12:27 +0800 Subject: [PATCH 09/15] batch: update inbox transaction data handling for blob transactions --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) 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 ac5e643f0dec4..af50b32bef38a 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 @@ -209,8 +209,7 @@ export class TransactionBatchSubmitterInbox { type: 2, chainId, to: this.inboxAddress, - // if it's for blob, set input data later - data: sendBlobTx ? undefined : '0x' + remove0x(batchParams.input), + data: '0x' + batchParams.input, value: 0n, } @@ -356,11 +355,11 @@ export class TransactionBatchSubmitterInbox { await this.inboxStorage.insertStep(batchParams) } - // set inbox tx data - inboxTx.data = - '0x' + - remove0x(batchParams.input) + - batchParams.txHashes.reduce((acc, hash) => acc + remove0x(hash), '') + // append all blob tx hashes to inbox tx data + inboxTx.data += batchParams.txHashes.reduce( + (acc, hash) => acc + remove0x(hash), + '' + ) } // Build and send inbox transaction From c1dfdd999d242e17e9fec302b8470edb0afa744c Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 12:17:50 +0800 Subject: [PATCH 10/15] batch: refactor MPC client initialization and blob transaction handling --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) 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 af50b32bef38a..64cc51abf305b 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 @@ -202,9 +202,7 @@ export class TransactionBatchSubmitterInbox { ) => Promise ) => Promise ): Promise { - // MPC enabled: prepare nonce, gasPrice const { chainId } = await signer.provider.getNetwork() - const sendBlobTx = batchParams.blobs && batchParams.blobs.length > 0 const inboxTx: TransactionRequest = { type: 2, chainId, @@ -213,17 +211,17 @@ export class TransactionBatchSubmitterInbox { value: 0n, } + const mpcClient = new MpcClient(mpcUrl, this.logger) + // if using blob, we need to submit the blob txs before the inbox tx - if (sendBlobTx) { + if (batchParams.blobs && batchParams.blobs.length > 0) { if (batchParams.txHashes.length === batchParams.blobs.length + 1) { throw new Error('Batch already submitted') } - let mpcClient: MpcClient 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 @@ -368,7 +366,6 @@ export class TransactionBatchSubmitterInbox { this.logger.info('submitter inbox meta with mpc', { blobTxs: batchParams.txHashes.join(','), }) - const mpcClient = new MpcClient(mpcUrl, this.logger) const mpcInfo = await mpcClient.getLatestMpc() if (!mpcInfo || !mpcInfo.mpc_address) { From 795f834fe59ce6f5f3adcf79b04d1a936a50c124 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 16:00:52 +0800 Subject: [PATCH 11/15] batch: update setTxEIP1559Fees --- .../src/batch-submitter/batch-submitter.ts | 7 + .../batch-submitter/state-batch-submitter.ts | 39 ++---- .../tx-batch-submitter-inbox.ts | 122 +++++++----------- 3 files changed, 64 insertions(+), 104 deletions(-) diff --git a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts index 8634654580ab1..0ed43047d8469 100755 --- a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts @@ -231,6 +231,13 @@ export abstract class BatchSubmitter { this.logger.info(`Submitting ${txName} transaction`, { txType: tx.type, gasLimit: tx.gasLimit ? toNumber(tx.gasLimit) : 0, + maxFeePerGas: tx.maxFeePerGas ? toNumber(tx.maxFeePerGas) : 0, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas + ? toNumber(tx.maxPriorityFeePerGas) + : 0, + maxFeePerBlobGas: tx.maxFeePerBlobGas + ? toNumber(tx.maxFeePerBlobGas) + : 0, nonce: toNumber(tx.nonce), contractAddr: tx.to, }) 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 7221043b9b57c..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') ) } 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 64cc51abf305b..95d83fe550bba 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 @@ -262,68 +262,45 @@ export class TransactionBatchSubmitterInbox { 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 => { - 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, - }) - return blobTransactionSubmitter.submitTransaction(blobTx, hooks) - } - } + const submitTx: () => Promise = mpcUrl + ? () => + transactionSubmitter.submitSignedTransaction( + blobTx, + 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 + ) + : () => blobTransactionSubmitter.submitTransaction(blobTx, hooks) const blobTxReceipt = await submitAndLogTx( submitTx, @@ -360,13 +337,14 @@ export class TransactionBatchSubmitterInbox { ) } + 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) { - this.logger.info('submitter inbox meta with mpc', { - blobTxs: batchParams.txHashes.join(','), - }) - const mpcInfo = await mpcClient.getLatestMpc() if (!mpcInfo || !mpcInfo.mpc_address) { throw new Error('MPC info get failed') @@ -379,25 +357,18 @@ export class TransactionBatchSubmitterInbox { from: mpcAddress, data: inboxTx.data, }) + await setTxEIP1559Fees( + inboxTx, + await this.pendingStorage.getPendingTx(mpcAddress), + this.l1Provider, + this.resubmissionTimeout + ) // mpc model can use ynatm const submitSignedTransaction = (): Promise => { return transactionSubmitter.submitSignedTransaction( inboxTx, - async () => { - const replaced = await setTxEIP1559Fees( - inboxTx, - await this.pendingStorage.getPendingTx(mpcAddress), - this.l1Provider, - this.resubmissionTimeout - ) - this.logger.info('MPC tx fees updated', { - maxFeePerGas: inboxTx.maxFeePerGas.toString(), - maxPriorityFeePerGas: inboxTx.maxPriorityFeePerGas.toString(), - replaced, - }) - return mpcClient.signTx(inboxTx, mpcInfo.mpc_id, mpcSignTimeout) - }, + () => mpcClient.signTx(inboxTx, mpcInfo.mpc_id, mpcSignTimeout), hooks ) } @@ -425,17 +396,12 @@ export class TransactionBatchSubmitterInbox { from: await signer.getAddress(), data: inboxTx.data, }) - const replaced = await setTxEIP1559Fees( + await setTxEIP1559Fees( inboxTx, await this.pendingStorage.getPendingTx(await signer.getAddress()), this.l1Provider, this.resubmissionTimeout ) - this.logger.info('Tx fees updated', { - maxFeePerGas: inboxTx.maxFeePerGas.toString(), - maxPriorityFeePerGas: inboxTx.maxPriorityFeePerGas.toString(), - replaced, - }) } const submitTransaction = (): Promise => { From c625509d714202675d02caa90f2647f139e712ad Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 16:02:43 +0800 Subject: [PATCH 12/15] batch: remove logging for skipped batch submission to inbox --- .../src/batch-submitter/tx-batch-submitter-inbox.ts | 6 ------ 1 file changed, 6 deletions(-) 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 95d83fe550bba..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 @@ -142,12 +142,6 @@ export class TransactionBatchSubmitterInbox { // 2. it is large enough // 3. enough time has passed since last submission if (!wasBatchTruncated && !shouldSubmitBatch(batchSizeInBytes)) { - this.logger.info('Skipping batch submission to inbox', { - meta: batchParams.inputMeta, - useBlob, - batchSizeInBytes, - wasBatchTruncated, - }) return } metrics.numTxPerBatch.observe(endBlock - startBlock) From 86d1431cb248c5c15cbcdc9dde0fbdd6ae9a7d5c Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 16:23:53 +0800 Subject: [PATCH 13/15] batch: optimize gas fee calculation for blob transactions in setTxEIP1559Fees --- .../src/utils/tx-submission.ts | 36 ++++++++++--------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/packages/batch-submitter/src/utils/tx-submission.ts b/packages/batch-submitter/src/utils/tx-submission.ts index a3d6cfd86900d..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,18 +64,6 @@ export const setTxEIP1559Fees = async ( const bumpedMaxPriorityFeePerGas = (toBigInt(oldTx.maxPriorityFeePerGas) * (100n + bumpThreshold)) / 100n - // 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 - tx.maxFeePerGas = bumpedMaxFeePerGas > newMaxFeePerGas ? bumpedMaxFeePerGas @@ -70,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 @@ -79,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 } From 088a9b295a445b553fa7f02451b2818234e4ab21 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 16:34:00 +0800 Subject: [PATCH 14/15] batch: update transaction logging to include gas price before gas limit --- .../batch-submitter/src/batch-submitter/batch-submitter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts index 0ed43047d8469..358f9793535c1 100755 --- a/packages/batch-submitter/src/batch-submitter/batch-submitter.ts +++ b/packages/batch-submitter/src/batch-submitter/batch-submitter.ts @@ -230,7 +230,7 @@ export abstract class BatchSubmitter { beforeSendTransaction: async (tx: ethers.TransactionRequest) => { this.logger.info(`Submitting ${txName} transaction`, { txType: tx.type, - gasLimit: tx.gasLimit ? toNumber(tx.gasLimit) : 0, + gasPrice: tx.gasPrice ? toNumber(tx.gasPrice) : 0, maxFeePerGas: tx.maxFeePerGas ? toNumber(tx.maxFeePerGas) : 0, maxPriorityFeePerGas: tx.maxPriorityFeePerGas ? toNumber(tx.maxPriorityFeePerGas) @@ -238,6 +238,7 @@ export abstract class BatchSubmitter { maxFeePerBlobGas: tx.maxFeePerBlobGas ? toNumber(tx.maxFeePerBlobGas) : 0, + gasLimit: tx.gasLimit ? toNumber(tx.gasLimit) : 0, nonce: toNumber(tx.nonce), contractAddr: tx.to, }) From 946e0c4cd4e8c06933fbd3125c989ca70030dd53 Mon Sep 17 00:00:00 2001 From: ericlee Date: Fri, 5 Dec 2025 20:14:01 +0800 Subject: [PATCH 15/15] batch: increase maximum number of blobs per transaction from 5 to 7 --- packages/batch-submitter/src/da/consts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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')