diff --git a/spartan/aztec-network/templates/faucet.yaml b/spartan/aztec-network/templates/faucet.yaml index 5053999d0a6..018c85f96ab 100644 --- a/spartan/aztec-network/templates/faucet.yaml +++ b/spartan/aztec-network/templates/faucet.yaml @@ -1,3 +1,4 @@ +{{- if not .Values.ethereum.externalHost }} apiVersion: apps/v1 kind: Deployment metadata: @@ -127,3 +128,4 @@ spec: - protocol: TCP port: {{ .Values.faucet.apiServerPort }} targetPort: {{ .Values.faucet.apiServerPort }} +{{ end }} diff --git a/spartan/aztec-network/templates/validator.yaml b/spartan/aztec-network/templates/validator.yaml index 02331567b02..bf21e4a0fff 100644 --- a/spartan/aztec-network/templates/validator.yaml +++ b/spartan/aztec-network/templates/validator.yaml @@ -208,6 +208,10 @@ spec: value: {{ .Values.validator.viemPollingInterval | quote }} - name: SEQ_VIEM_POLLING_INTERVAL_MS value: {{ .Values.validator.viemPollingInterval | quote }} + - name: L1_FIXED_PRIORITY_FEE_PER_GAS + value: {{ .Values.validator.l1FixedPriorityFeePerGas | quote }} + - name: L1_GAS_LIMIT_BUFFER_PERCENTAGE + value: {{ .Values.validator.l1GasLimitBufferPercentage | quote }} - name: DATA_DIRECTORY value: "{{ .Values.validator.dataDir }}" - name: DATA_STORE_MAP_SIZE_KB diff --git a/spartan/aztec-network/values.yaml b/spartan/aztec-network/values.yaml index 9f7ee290d30..efd30dbbd66 100644 --- a/spartan/aztec-network/values.yaml +++ b/spartan/aztec-network/values.yaml @@ -127,6 +127,8 @@ validator: viemPollingInterval: 1000 storageSize: "1Gi" dataDir: "/data" + l1FixedPriorityFeePerGas: "" + l1GasLimitBufferPercentage: "" proverNode: proverPublisherPrivateKey: "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" diff --git a/spartan/aztec-network/values/sepolia-48-validators-with-metrics.yaml b/spartan/aztec-network/values/sepolia-48-validators-with-metrics.yaml index 9e944e701fb..25781c330f2 100644 --- a/spartan/aztec-network/values/sepolia-48-validators-with-metrics.yaml +++ b/spartan/aztec-network/values/sepolia-48-validators-with-metrics.yaml @@ -12,6 +12,8 @@ ethereum: deployL1ContractsPrivateKey: validator: + l1FixedPriorityFeePerGas: 1 + l1GasLimitBufferPercentage: 5 replicas: 48 validatorKeys: validatorAddresses: @@ -73,4 +75,4 @@ proverNode: proverPublisherPrivateKey: bot: - txIntervalSeconds: 20 \ No newline at end of file + txIntervalSeconds: 5 \ No newline at end of file diff --git a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts index e7a8f8fd56b..ba092589a22 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/p2p_network.ts @@ -148,7 +148,7 @@ export class P2PNetworkTest { this.logger.info('Syncing mock system time'); const { dateProvider, deployL1ContractsValues } = this.ctx!; // Send a tx and only update the time after the tx is mined, as eth time is not continuous - const receipt = await this.gasUtils!.sendAndMonitorTransaction({ + const { receipt } = await this.gasUtils!.sendAndMonitorTransaction({ to: this.baseAccount.address, data: '0x', value: 1n, @@ -300,7 +300,7 @@ export class P2PNetworkTest { this.ctx.deployL1ContractsValues.walletClient, this.logger, { - gasLimitBufferPercentage: 20n, + gasLimitBufferPercentage: 20, maxGwei: 500n, minGwei: 1n, maxAttempts: 3, diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 4b20b83798f..35ff655a03e 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -762,7 +762,7 @@ export async function deployL1Contract( } else { // Regular deployment path const deployData = encodeDeployData({ abi, bytecode, args }); - const receipt = await l1TxUtils.sendAndMonitorTransaction({ + const { receipt } = await l1TxUtils.sendAndMonitorTransaction({ to: null, data: deployData, }); diff --git a/yarn-project/ethereum/src/l1_tx_utils.test.ts b/yarn-project/ethereum/src/l1_tx_utils.test.ts index 1927563052a..61a4de07fb6 100644 --- a/yarn-project/ethereum/src/l1_tx_utils.test.ts +++ b/yarn-project/ethereum/src/l1_tx_utils.test.ts @@ -1,3 +1,4 @@ +import { Blob } from '@aztec/foundation/blob'; import { EthAddress } from '@aztec/foundation/eth-address'; import { createLogger } from '@aztec/foundation/log'; import { sleep } from '@aztec/foundation/sleep'; @@ -19,6 +20,7 @@ import { foundry } from 'viem/chains'; import { EthCheatCodes } from './eth_cheat_codes.js'; import { L1TxUtils, defaultL1TxUtilsConfig } from './l1_tx_utils.js'; import { startAnvil } from './test/start_anvil.js'; +import { formatViemError } from './utils.js'; const MNEMONIC = 'test test test test test test test test test test test junk'; const WEI_CONST = 1_000_000_000n; @@ -71,7 +73,7 @@ describe('GasUtils', () => { await cheatCodes.evmMine(); gasUtils = new L1TxUtils(publicClient, walletClient, logger, { - gasLimitBufferPercentage: 20n, + gasLimitBufferPercentage: 20, maxGwei: 500n, minGwei: 1n, maxAttempts: 3, @@ -92,7 +94,7 @@ describe('GasUtils', () => { }, 5_000); it('sends and monitors a simple transaction', async () => { - const receipt = await gasUtils.sendAndMonitorTransaction({ + const { receipt } = await gasUtils.sendAndMonitorTransaction({ to: '0x1234567890123456789012345678901234567890', data: '0x', value: 0n, @@ -106,8 +108,9 @@ describe('GasUtils', () => { await cheatCodes.setAutomine(false); await cheatCodes.setIntervalMining(0); - // Ensure initial base fee is low - await cheatCodes.setNextBlockBaseFeePerGas(initialBaseFee); + // Add blob data + const blobData = new Uint8Array(131072).fill(1); + const kzg = Blob.getViemKzgInstance(); const request = { to: '0x1234567890123456789012345678901234567890' as `0x${string}`, @@ -119,12 +122,16 @@ describe('GasUtils', () => { const originalMaxFeePerGas = WEI_CONST * 10n; const originalMaxPriorityFeePerGas = WEI_CONST; + const originalMaxFeePerBlobGas = WEI_CONST * 10n; const txHash = await walletClient.sendTransaction({ ...request, gas: estimatedGas, maxFeePerGas: originalMaxFeePerGas, maxPriorityFeePerGas: originalMaxPriorityFeePerGas, + blobs: [blobData], + kzg, + maxFeePerBlobGas: originalMaxFeePerBlobGas, }); const rawTx = await cheatCodes.getRawTransaction(txHash); @@ -142,11 +149,12 @@ describe('GasUtils', () => { params: [rawTx], }); - // keeping auto-mining disabled to simulate a stuck transaction - // The monitor should detect the stall and create a replacement tx - // Monitor should detect stall and replace with higher gas price - const monitorFn = gasUtils.monitorTransaction(request, txHash, { gasLimit: estimatedGas }); + const monitorFn = gasUtils.monitorTransaction(request, txHash, { gasLimit: estimatedGas }, undefined, { + blobs: [blobData], + kzg, + maxFeePerBlobGas: WEI_CONST * 20n, + }); await sleep(2000); // re-enable mining @@ -156,11 +164,12 @@ describe('GasUtils', () => { // Verify that a replacement transaction was created expect(receipt.transactionHash).not.toBe(txHash); - // Get details of replacement tx to verify higher gas price + // Get details of replacement tx to verify higher gas prices const replacementTx = await publicClient.getTransaction({ hash: receipt.transactionHash }); expect(replacementTx.maxFeePerGas!).toBeGreaterThan(originalMaxFeePerGas); expect(replacementTx.maxPriorityFeePerGas!).toBeGreaterThan(originalMaxPriorityFeePerGas); + expect(replacementTx.maxFeePerBlobGas!).toBeGreaterThan(originalMaxFeePerBlobGas); }, 20_000); it('respects max gas price limits during spikes', async () => { @@ -173,11 +182,14 @@ describe('GasUtils', () => { // Mine a new block to make the base fee change take effect await cheatCodes.evmMine(); - const receipt = await gasUtils.sendAndMonitorTransaction({ - to: '0x1234567890123456789012345678901234567890', - data: '0x', - value: 0n, - }); + const { receipt } = await gasUtils.sendAndMonitorTransaction( + { + to: '0x1234567890123456789012345678901234567890', + data: '0x', + value: 0n, + }, + { maxGwei }, + ); expect(receipt.effectiveGasPrice).toBeLessThanOrEqual(maxGwei * WEI_CONST); }, 60_000); @@ -189,7 +201,7 @@ describe('GasUtils', () => { // First deploy without any buffer const baselineGasUtils = new L1TxUtils(publicClient, walletClient, logger, { - gasLimitBufferPercentage: 0n, + gasLimitBufferPercentage: 0, maxGwei: 500n, minGwei: 10n, // Increased minimum gas price maxAttempts: 5, @@ -197,7 +209,7 @@ describe('GasUtils', () => { stallTimeMs: 1000, }); - const baselineTx = await baselineGasUtils.sendAndMonitorTransaction({ + const { receipt: baselineTx } = await baselineGasUtils.sendAndMonitorTransaction({ to: EthAddress.ZERO.toString(), data: SIMPLE_CONTRACT_BYTECODE, }); @@ -209,7 +221,7 @@ describe('GasUtils', () => { // Now deploy with 20% buffer const bufferedGasUtils = new L1TxUtils(publicClient, walletClient, logger, { - gasLimitBufferPercentage: 20n, + gasLimitBufferPercentage: 20, maxGwei: 500n, minGwei: 1n, maxAttempts: 3, @@ -217,7 +229,7 @@ describe('GasUtils', () => { stallTimeMs: 1000, }); - const bufferedTx = await bufferedGasUtils.sendAndMonitorTransaction({ + const { receipt: bufferedTx } = await bufferedGasUtils.sendAndMonitorTransaction({ to: EthAddress.ZERO.toString(), data: SIMPLE_CONTRACT_BYTECODE, }); @@ -256,7 +268,7 @@ describe('GasUtils', () => { const initialGasPrice = await gasUtils['getGasPrice'](); // Get retry gas price for 2nd attempt - const retryGasPrice = await gasUtils['getGasPrice'](undefined, 1, initialGasPrice); + const retryGasPrice = await gasUtils['getGasPrice'](undefined, false, 1, initialGasPrice); // With default config, retry should bump fees by 50% const expectedPriorityFee = (initialGasPrice.maxPriorityFeePerGas * 150n) / 100n; @@ -269,13 +281,13 @@ describe('GasUtils', () => { it('respects minimum gas price bump for replacements', async () => { const gasUtils = new L1TxUtils(publicClient, walletClient, logger, { ...defaultL1TxUtilsConfig, - priorityFeeRetryBumpPercentage: 5n, // Set lower than minimum 10% + priorityFeeRetryBumpPercentage: 5, // Set lower than minimum 10% }); const initialGasPrice = await gasUtils['getGasPrice'](); // Get retry gas price with attempt = 1 - const retryGasPrice = await gasUtils['getGasPrice'](undefined, 1, initialGasPrice); + const retryGasPrice = await gasUtils['getGasPrice'](undefined, false, 1, initialGasPrice); // Should use 10% minimum bump even though config specified 5% const expectedPriorityFee = (initialGasPrice.maxPriorityFeePerGas * 110n) / 100n; @@ -300,6 +312,100 @@ describe('GasUtils', () => { expect(bufferedEstimate).toBe(expectedEstimate); }); + it('correctly handles transactions with blobs', async () => { + // Create a sample blob + const blobData = new Uint8Array(131072).fill(1); // 128KB blob + const kzg = Blob.getViemKzgInstance(); + + const { receipt } = await gasUtils.sendAndMonitorTransaction( + { + to: '0x1234567890123456789012345678901234567890', + data: '0x', + value: 0n, + }, + undefined, + { + blobs: [blobData], + kzg, + maxFeePerBlobGas: 10000000000n, // 10 gwei + }, + ); + + expect(receipt.status).toBe('success'); + expect(receipt.blobGasUsed).toBeDefined(); + expect(receipt.blobGasPrice).toBeDefined(); + }, 20_000); + + it('estimates gas correctly for blob transactions', async () => { + // Create a sample blob + const blobData = new Uint8Array(131072).fill(1); // 128KB blob + const kzg = Blob.getViemKzgInstance(); + + const request = { + to: '0x1234567890123456789012345678901234567890' as `0x${string}`, + data: '0x' as `0x${string}`, + value: 0n, + }; + + // Estimate gas without blobs first + const baseEstimate = await gasUtils.estimateGas(walletClient.account!, request); + + // Estimate gas with blobs + const blobEstimate = await gasUtils.estimateGas(walletClient.account!, request, undefined, { + blobs: [blobData], + kzg, + maxFeePerBlobGas: 10000000000n, + }); + // Blob transactions should require more gas + expect(blobEstimate).toBeGreaterThan(baseEstimate); + }, 20_000); + + it('formats anvil errors correctly', async () => { + // Set base fee extremely high to trigger error + const extremelyHighBaseFee = WEI_CONST * 1_000_000n; // 1M gwei + await cheatCodes.setNextBlockBaseFeePerGas(extremelyHighBaseFee); + await cheatCodes.evmMine(); + + try { + await gasUtils.sendAndMonitorTransaction({ + to: '0x1234567890123456789012345678901234567890', + data: '0x', + value: 0n, + }); + fail('Should have thrown'); + } catch (err: any) { + const formattedError = formatViemError(err); + const parsed = JSON.parse(formattedError); + + // Check the error chain structure + expect(parsed.errorChain).toBeDefined(); + expect(Array.isArray(parsed.errorChain)).toBe(true); + expect(parsed.errorChain.length).toBeGreaterThan(0); + + // Check that we have the key error information + const firstError = parsed.errorChain[0]; + expect(firstError.name).toBe('TransactionExecutionError'); + expect(firstError.message).toContain('fee cap'); + + // Verify no duplicate messages in error chain + const messages = parsed.errorChain.map((e: any) => e.message); + expect(new Set(messages).size).toBe(messages.length - 1); // contains duplicate message + + // Check request body formatting if present + if (firstError.requestBody) { + const body = JSON.parse(firstError.requestBody); + expect(body.method).toBe('eth_sendRawTransaction'); + expect(body.params).toBeDefined(); + expect(Array.isArray(body.params)).toBe(true); + // Check params are truncated if too long + body.params.forEach((param: string) => { + if (param.length > 50) { + expect(param).toContain('...'); + } + }); + } + } + }, 10_000); it('stops trying after timeout', async () => { await cheatCodes.setAutomine(false); await cheatCodes.setIntervalMining(0); diff --git a/yarn-project/ethereum/src/l1_tx_utils.ts b/yarn-project/ethereum/src/l1_tx_utils.ts index b88c4ce0deb..45336efa94d 100644 --- a/yarn-project/ethereum/src/l1_tx_utils.ts +++ b/yarn-project/ethereum/src/l1_tx_utils.ts @@ -22,6 +22,8 @@ import { formatGwei, } from 'viem'; +import { formatViemError } from './utils.js'; + // 1_000_000_000 Gwei = 1 ETH // 1_000_000_000 Wei = 1 Gwei // 1_000_000_000_000_000_000 Wei = 1 ETH @@ -30,7 +32,11 @@ const WEI_CONST = 1_000_000_000n; // setting a minimum bump percentage to 10% due to geth's implementation // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/legacypool/list.go#L298 -const MIN_REPLACEMENT_BUMP_PERCENTAGE = 10n; +const MIN_REPLACEMENT_BUMP_PERCENTAGE = 10; + +// setting a minimum bump percentage to 100% due to geth's implementation +// https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/blobpool/config.go#L34 +const MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE = 100; // Avg ethereum block time is ~12s const BLOCK_TIME_MS = 12_000; @@ -39,7 +45,7 @@ export interface L1TxUtilsConfig { /** * How much to increase calculated gas limit. */ - gasLimitBufferPercentage?: bigint; + gasLimitBufferPercentage?: number; /** * Maximum gas price in gwei */ @@ -48,14 +54,22 @@ export interface L1TxUtilsConfig { * Minimum gas price in gwei */ minGwei?: bigint; + /** + * Maximum blob fee per gas in gwei + */ + maxBlobGwei?: bigint; /** * Priority fee bump percentage */ - priorityFeeBumpPercentage?: bigint; + priorityFeeBumpPercentage?: number; /** * How much to increase priority fee by each attempt (percentage) */ - priorityFeeRetryBumpPercentage?: bigint; + priorityFeeRetryBumpPercentage?: number; + /** + * Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage config + */ + fixedPriorityFeePerGas?: number; /** * Maximum number of speed-up attempts */ @@ -83,7 +97,7 @@ export const l1TxUtilsConfigMappings: ConfigMappingsType = { gasLimitBufferPercentage: { description: 'How much to increase gas price by each attempt (percentage)', env: 'L1_GAS_LIMIT_BUFFER_PERCENTAGE', - ...bigintConfigHelper(20n), + ...numberConfigHelper(10), }, minGwei: { description: 'Minimum gas price in gwei', @@ -95,15 +109,25 @@ export const l1TxUtilsConfigMappings: ConfigMappingsType = { env: 'L1_GAS_PRICE_MAX', ...bigintConfigHelper(100n), }, + maxBlobGwei: { + description: 'Maximum blob fee per gas in gwei', + env: 'L1_BLOB_FEE_PER_GAS_MAX', + ...bigintConfigHelper(1_500n), + }, priorityFeeBumpPercentage: { description: 'How much to increase priority fee by each attempt (percentage)', env: 'L1_PRIORITY_FEE_BUMP_PERCENTAGE', - ...bigintConfigHelper(20n), + ...numberConfigHelper(20), }, priorityFeeRetryBumpPercentage: { description: 'How much to increase priority fee by each retry attempt (percentage)', env: 'L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE', - ...bigintConfigHelper(50n), + ...numberConfigHelper(50), + }, + fixedPriorityFeePerGas: { + description: 'Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage', + env: 'L1_FIXED_PRIORITY_FEE_PER_GAS', + ...numberConfigHelper(0), }, maxAttempts: { description: 'Maximum number of speed-up attempts', @@ -118,7 +142,7 @@ export const l1TxUtilsConfigMappings: ConfigMappingsType = { stallTimeMs: { description: 'How long before considering tx stalled', env: 'L1_TX_MONITOR_STALL_TIME_MS', - ...numberConfigHelper(30_000), + ...numberConfigHelper(45_000), }, txTimeoutMs: { description: 'How long to wait for a tx to be mined before giving up. Set to 0 to disable.', @@ -143,12 +167,13 @@ export interface L1TxRequest { export interface L1BlobInputs { blobs: Uint8Array[]; kzg: any; - maxFeePerBlobGas: bigint; + maxFeePerBlobGas?: bigint; } -interface GasPrice { +export interface GasPrice { maxFeePerGas: bigint; maxPriorityFeePerGas: bigint; + maxFeePerBlobGas?: bigint; } export class L1TxUtils { @@ -175,40 +200,55 @@ export class L1TxUtils { public async sendTransaction( request: L1TxRequest, _gasConfig?: Partial & { fixedGas?: bigint; txTimeoutAt?: Date }, - _blobInputs?: L1BlobInputs, + blobInputs?: L1BlobInputs, ): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> { - const gasConfig = { ...this.config, ..._gasConfig }; - const account = this.walletClient.account; - let gasLimit: bigint; + try { + const gasConfig = { ...this.config, ..._gasConfig }; + const account = this.walletClient.account; + let gasLimit: bigint; + + if (gasConfig.fixedGas) { + gasLimit = gasConfig.fixedGas; + } else { + gasLimit = await this.estimateGas(account, request); + } - if (gasConfig.fixedGas) { - gasLimit = gasConfig.fixedGas; - } else { - gasLimit = await this.estimateGas(account, request); - } + const gasPrice = await this.getGasPrice(gasConfig, !!blobInputs); - const gasPrice = await this.getGasPrice(gasConfig); + if (gasConfig.txTimeoutAt && Date.now() > gasConfig.txTimeoutAt.getTime()) { + throw new Error('Transaction timed out before sending'); + } - if (gasConfig.txTimeoutAt && Date.now() > gasConfig.txTimeoutAt.getTime()) { - throw new Error('Transaction timed out before sending'); + let txHash: Hex; + if (blobInputs) { + txHash = await this.walletClient.sendTransaction({ + ...request, + ...blobInputs, + gas: gasLimit, + maxFeePerGas: gasPrice.maxFeePerGas, + maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas, + maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!, + }); + } else { + txHash = await this.walletClient.sendTransaction({ + ...request, + gas: gasLimit, + maxFeePerGas: gasPrice.maxFeePerGas, + maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas, + }); + } + this.logger?.verbose(`Sent L1 transaction ${txHash}`, { + gasLimit, + maxFeePerGas: formatGwei(gasPrice.maxFeePerGas), + maxPriorityFeePerGas: formatGwei(gasPrice.maxPriorityFeePerGas), + ...(gasPrice.maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(gasPrice.maxFeePerBlobGas) }), + }); + + return { txHash, gasLimit, gasPrice }; + } catch (err: any) { + this.logger?.error(`Failed to send transaction: ${formatViemError(err)}`); + throw err; } - - const blobInputs = _blobInputs || {}; - const txHash = await this.walletClient.sendTransaction({ - ...request, - ...blobInputs, - gas: gasLimit, - maxFeePerGas: gasPrice.maxFeePerGas, - maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas, - }); - - this.logger?.verbose(`Sent L1 transaction ${txHash}`, { - gasLimit, - maxFeePerGas: formatGwei(gasPrice.maxFeePerGas), - maxPriorityFeePerGas: formatGwei(gasPrice.maxPriorityFeePerGas), - }); - - return { txHash, gasLimit, gasPrice }; } /** @@ -306,15 +346,25 @@ export class L1TxUtils { attempts++; const newGasPrice = await this.getGasPrice( gasConfig, + !!blobInputs, attempts, tx.maxFeePerGas && tx.maxPriorityFeePerGas - ? { maxFeePerGas: tx.maxFeePerGas, maxPriorityFeePerGas: tx.maxPriorityFeePerGas } + ? { + maxFeePerGas: tx.maxFeePerGas, + maxPriorityFeePerGas: tx.maxPriorityFeePerGas, + maxFeePerBlobGas: tx.maxFeePerBlobGas, + } : undefined, ); this.logger?.debug( `L1 transaction ${currentTxHash} appears stuck. Attempting speed-up ${attempts}/${gasConfig.maxAttempts} ` + `with new priority fee ${formatGwei(newGasPrice.maxPriorityFeePerGas)} gwei`, + { + maxFeePerGas: formatGwei(newGasPrice.maxFeePerGas), + maxPriorityFeePerGas: formatGwei(newGasPrice.maxPriorityFeePerGas), + ...(newGasPrice.maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(newGasPrice.maxFeePerBlobGas) }), + }, ); currentTxHash = await this.walletClient.sendTransaction({ @@ -331,7 +381,7 @@ export class L1TxUtils { } await sleep(gasConfig.checkIntervalMs!); } catch (err: any) { - this.logger?.warn(`Error monitoring tx ${currentTxHash}:`, err); + this.logger?.warn(`Error monitoring tx ${currentTxHash}:`, formatViemError(err)); if (err.message?.includes('reverted')) { throw err; } @@ -353,9 +403,10 @@ export class L1TxUtils { request: L1TxRequest, gasConfig?: Partial & { fixedGas?: bigint; txTimeoutAt?: Date }, blobInputs?: L1BlobInputs, - ): Promise { - const { txHash, gasLimit } = await this.sendTransaction(request, gasConfig, blobInputs); - return this.monitorTransaction(request, txHash, { gasLimit }, gasConfig, blobInputs); + ): Promise<{ receipt: TransactionReceipt; gasPrice: GasPrice }> { + const { txHash, gasLimit, gasPrice } = await this.sendTransaction(request, gasConfig, blobInputs); + const receipt = await this.monitorTransaction(request, txHash, { gasLimit }, gasConfig, blobInputs); + return { receipt, gasPrice }; } /** @@ -363,6 +414,7 @@ export class L1TxUtils { */ private async getGasPrice( _gasConfig?: L1TxUtilsConfig, + isBlobTx: boolean = false, attempt: number = 0, previousGasPrice?: typeof attempt extends 0 ? never : GasPrice, ): Promise { @@ -370,26 +422,54 @@ export class L1TxUtils { const block = await this.publicClient.getBlock({ blockTag: 'latest' }); const baseFee = block.baseFeePerGas ?? 0n; - // Get initial priority fee from the network - let priorityFee = await this.publicClient.estimateMaxPriorityFeePerGas(); + // Get blob base fee if available + let blobBaseFee = 0n; + try { + const blobBaseFeeHex = await this.publicClient.request({ method: 'eth_blobBaseFee' }); + blobBaseFee = BigInt(blobBaseFeeHex); + this.logger?.debug('Blob base fee:', { blobBaseFee: formatGwei(blobBaseFee) }); + } catch { + this.logger?.warn('Failed to get blob base fee', attempt); + } + + let priorityFee: bigint; + if (gasConfig.fixedPriorityFeePerGas) { + this.logger?.debug('Using fixed priority fee per gas', { + fixedPriorityFeePerGas: gasConfig, + }); + // try to maintain precision up to 1000000 wei + priorityFee = BigInt(gasConfig.fixedPriorityFeePerGas * 1_000_000) * (WEI_CONST / 1_000_000n); + } else { + // Get initial priority fee from the network + priorityFee = await this.publicClient.estimateMaxPriorityFeePerGas(); + } let maxFeePerGas = baseFee; + let maxFeePerBlobGas = blobBaseFee; + // Bump base fee so it's valid for next blocks if it stalls const numBlocks = Math.ceil(gasConfig.stallTimeMs! / BLOCK_TIME_MS); for (let i = 0; i < numBlocks; i++) { // each block can go up 12.5% from previous baseFee maxFeePerGas = (maxFeePerGas * (1_000n + 125n)) / 1_000n; + // same for blob gas fee + maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n)) / 1_000n; } if (attempt > 0) { const configBump = gasConfig.priorityFeeRetryBumpPercentage ?? defaultL1TxUtilsConfig.priorityFeeRetryBumpPercentage!; - const bumpPercentage = - configBump > MIN_REPLACEMENT_BUMP_PERCENTAGE ? configBump : MIN_REPLACEMENT_BUMP_PERCENTAGE; + + // if this is a blob tx, we have to use the blob bump percentage + const minBumpPercentage = isBlobTx ? MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE : MIN_REPLACEMENT_BUMP_PERCENTAGE; + + const bumpPercentage = configBump > minBumpPercentage ? configBump : minBumpPercentage; // Calculate minimum required fees based on previous attempt - const minPriorityFee = (previousGasPrice!.maxPriorityFeePerGas * (100n + bumpPercentage)) / 100n; - const minMaxFee = (previousGasPrice!.maxFeePerGas * (100n + bumpPercentage)) / 100n; + // multiply by 100 & divide by 100 to maintain some precision + const minPriorityFee = + (previousGasPrice!.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n; + const minMaxFee = (previousGasPrice!.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n; // Add priority fee to maxFeePerGas maxFeePerGas += priorityFee; @@ -398,8 +478,11 @@ export class L1TxUtils { priorityFee = priorityFee > minPriorityFee ? priorityFee : minPriorityFee; maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee; } else { - // first attempt, just bump priority fee - priorityFee = (priorityFee * (100n + (gasConfig.priorityFeeBumpPercentage || 0n))) / 100n; + // first attempt, just bump priority fee, unless it's a fixed config + // multiply by 100 & divide by 100 to maintain some precision + if (!gasConfig.fixedPriorityFeePerGas) { + priorityFee = (priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00))) / 100_00n; + } maxFeePerGas += priorityFee; } @@ -407,17 +490,41 @@ export class L1TxUtils { const maxGweiInWei = gasConfig.maxGwei! * WEI_CONST; maxFeePerGas = maxFeePerGas > maxGweiInWei ? maxGweiInWei : maxFeePerGas; + // Ensure we don't exceed maxBlobGwei + if (maxFeePerBlobGas) { + const maxBlobGweiInWei = gasConfig.maxBlobGwei! * WEI_CONST; + maxFeePerBlobGas = maxFeePerBlobGas > maxBlobGweiInWei ? maxBlobGweiInWei : maxFeePerBlobGas; + } + // Ensure priority fee doesn't exceed max fee const maxPriorityFeePerGas = priorityFee > maxFeePerGas ? maxFeePerGas : priorityFee; + if (attempt > 0 && previousGasPrice?.maxFeePerBlobGas) { + const bumpPercentage = + gasConfig.priorityFeeRetryBumpPercentage! > MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE + ? gasConfig.priorityFeeRetryBumpPercentage! + : MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE; + + // calculate min blob fee based on previous attempt + const minBlobFee = (previousGasPrice.maxFeePerBlobGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n; + + // use max between current network values and min required values + maxFeePerBlobGas = maxFeePerBlobGas > minBlobFee ? maxFeePerBlobGas : minBlobFee; + } + this.logger?.debug(`Computed gas price`, { attempt, baseFee: formatGwei(baseFee), maxFeePerGas: formatGwei(maxFeePerGas), maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas), + ...(maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(maxFeePerBlobGas) }), }); - return { maxFeePerGas, maxPriorityFeePerGas }; + return { + maxFeePerGas, + maxPriorityFeePerGas, + ...(maxFeePerBlobGas && { maxFeePerBlobGas: maxFeePerBlobGas }), + }; } /** @@ -435,14 +542,22 @@ export class L1TxUtils { // Strangely, the only way to get gas and send blobs is prepareTransactionRequest(). // See: https://github.com/wevm/viem/issues/2075 if (_blobInputs) { - initialEstimate = (await this.walletClient.prepareTransactionRequest({ account, ...request, ..._blobInputs })) - .gas; + const gasPrice = await this.getGasPrice(gasConfig, true, 0); + initialEstimate = ( + await this.walletClient.prepareTransactionRequest({ + account, + ...request, + ..._blobInputs, + maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!, + }) + )?.gas; } else { initialEstimate = await this.publicClient.estimateGas({ account, ...request }); } // Add buffer based on either fixed amount or percentage - const withBuffer = initialEstimate + (initialEstimate * (gasConfig.gasLimitBufferPercentage ?? 0n)) / 100n; + const withBuffer = + initialEstimate + (initialEstimate * BigInt((gasConfig.gasLimitBufferPercentage || 0) * 1_00)) / 100_00n; return withBuffer; } diff --git a/yarn-project/ethereum/src/utils.ts b/yarn-project/ethereum/src/utils.ts index fbd8d45d55d..68ba0278ce1 100644 --- a/yarn-project/ethereum/src/utils.ts +++ b/yarn-project/ethereum/src/utils.ts @@ -3,7 +3,9 @@ import { type Logger } from '@aztec/foundation/log'; import { type Abi, + BaseError, type ContractEventName, + ContractFunctionRevertedError, type DecodeEventLogReturnType, type Hex, type Log, @@ -64,3 +66,110 @@ function tryExtractEvent< } } } + +export function prettyLogViemErrorMsg(err: any) { + if (err instanceof BaseError) { + const revertError = err.walk(err => err instanceof ContractFunctionRevertedError); + if (revertError instanceof ContractFunctionRevertedError) { + const errorName = revertError.data?.errorName ?? ''; + const args = + revertError.metaMessages && revertError.metaMessages?.length > 1 ? revertError.metaMessages[1].trimStart() : ''; + return `${errorName}${args}`; + } + } + return err?.message ?? err; +} + +export function formatViemError(error: any): string { + const truncateHex = (hex: string, length = 100) => { + if (!hex || typeof hex !== 'string') { + return hex; + } + if (!hex.startsWith('0x')) { + return hex; + } + if (hex.length <= length * 2) { + return hex; + } + return `${hex.slice(0, length)}...${hex.slice(-length)}`; + }; + + const formatRequestBody = (body: string) => { + try { + const parsed = JSON.parse(body); + + // Recursively process all parameters that might contain hex strings + const processParams = (obj: any): any => { + if (Array.isArray(obj)) { + return obj.map(item => processParams(item)); + } + if (typeof obj === 'object' && obj !== null) { + const result: any = {}; + for (const [key, value] of Object.entries(obj)) { + result[key] = processParams(value); + } + return result; + } + if (typeof obj === 'string') { + if (obj.startsWith('0x')) { + return truncateHex(obj); + } + } + return obj; + }; + + // Process the entire request body + const processed = processParams(parsed); + return JSON.stringify(processed, null, 2); + } catch { + return body; + } + }; + + const truncateHexStringsInText = (text: string): string => { + const hexRegex = /0x[a-fA-F0-9]{10,}/g; + return text.replace(hexRegex, hex => truncateHex(hex)); + }; + + const extractAndFormatRequestBody = (message: string): string => { + // First handle Request body JSON + const requestBodyRegex = /Request body: ({[\s\S]*?})\n/g; + let result = message.replace(requestBodyRegex, (match, body) => { + return `Request body: ${formatRequestBody(body)}\n`; + }); + + // Then handle Arguments section + const argsRegex = /((?:Request |Estimate Gas )?Arguments:[\s\S]*?(?=\n\n|$))/g; + result = result.replace(argsRegex, section => { + const lines = section.split('\n'); + const processedLines = lines.map(line => { + // Check if line contains a colon followed by content + const colonIndex = line.indexOf(':'); + if (colonIndex !== -1) { + const [prefix, content] = [line.slice(0, colonIndex + 1), line.slice(colonIndex + 1).trim()]; + // If content contains a hex string, truncate it + if (content.includes('0x')) { + const hexMatches = content.match(/0x[a-fA-F0-9]+/g) || []; + let processedContent = content; + hexMatches.forEach(hex => { + processedContent = processedContent.replace(hex, truncateHex(hex)); + }); + return `${prefix} ${processedContent}`; + } + } + return line; + }); + return processedLines.join('\n'); + }); + + // Finally, catch any remaining hex strings in the message + result = truncateHexStringsInText(result); + + return result; + }; + + return JSON.stringify({ error: extractAndFormatRequestBody(error?.message || String(error)) }, null, 2).replace( + /\\n/g, + '\n', + ); +} diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index 868dc8191e5..dd323960758 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -190,8 +190,10 @@ export type EnvVar = | 'L1_GAS_LIMIT_BUFFER_FIXED' | 'L1_GAS_PRICE_MIN' | 'L1_GAS_PRICE_MAX' + | 'L1_BLOB_FEE_PER_GAS_MAX' | 'L1_PRIORITY_FEE_BUMP_PERCENTAGE' | 'L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE' + | 'L1_FIXED_PRIORITY_FEE_PER_GAS' | 'L1_TX_MONITOR_MAX_ATTEMPTS' | 'L1_TX_MONITOR_CHECK_INTERVAL_MS' | 'L1_TX_MONITOR_STALL_TIME_MS' diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts index a000d6f80f5..b638f3a5fb0 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.test.ts @@ -2,6 +2,7 @@ import { HttpBlobSinkClient } from '@aztec/blob-sink/client'; import { L2Block } from '@aztec/circuit-types'; import { EthAddress } from '@aztec/circuits.js'; import { + type GasPrice, type L1ContractsConfig, type L1TxRequest, type L1TxUtilsConfig, @@ -41,7 +42,7 @@ interface MockL1TxUtils { sendAndMonitorTransaction: ( request: L1TxRequest, _gasConfig?: Partial, - ) => Promise; + ) => Promise<{ receipt: TransactionReceipt; gasPrice: GasPrice }>; } interface MockRollupContractWrite { @@ -149,7 +150,10 @@ describe('L1Publisher', () => { rollupContractRead.getCurrentSlot.mockResolvedValue(l2Block.header.globalVariables.slotNumber.toBigInt()); publicClient.getBlock.mockResolvedValue({ timestamp: 12n }); publicClient.estimateGas.mockResolvedValue(GAS_GUESS); - l1TxUtils.sendAndMonitorTransaction.mockResolvedValue(proposeTxReceipt); + l1TxUtils.sendAndMonitorTransaction.mockResolvedValue({ + receipt: proposeTxReceipt, + gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n }, + }); (l1TxUtils as any).estimateGas.mockResolvedValue(GAS_GUESS); }); @@ -235,7 +239,7 @@ describe('L1Publisher', () => { data: encodeFunctionData({ abi: rollupContract.abi, functionName: 'propose', args }), }, { fixedGas: GAS_GUESS + L1Publisher.PROPOSE_GAS_GUESS }, - { blobs: expectedBlobs.map(b => b.dataWithZeros), kzg, maxFeePerBlobGas: 10000000000n }, + { blobs: expectedBlobs.map(b => b.dataWithZeros), kzg }, ); expect(sendToBlobSinkSpy).toHaveBeenCalledTimes(1); @@ -247,7 +251,9 @@ describe('L1Publisher', () => { it('does not retry if sending a propose tx fails', async () => { rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); - l1TxUtils.sendAndMonitorTransaction.mockRejectedValueOnce(new Error()).mockResolvedValueOnce(proposeTxReceipt); + l1TxUtils.sendAndMonitorTransaction + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce({ receipt: proposeTxReceipt, gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n } }); const result = await publisher.proposeL2Block(l2Block); @@ -265,7 +271,9 @@ describe('L1Publisher', () => { it('does not retry if sending a publish and propose tx fails', async () => { rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); - l1TxUtils.sendAndMonitorTransaction.mockRejectedValueOnce(new Error()).mockResolvedValueOnce(proposeTxReceipt); + l1TxUtils.sendAndMonitorTransaction + .mockRejectedValueOnce(new Error()) + .mockResolvedValueOnce({ receipt: proposeTxReceipt, gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n } }); const result = await publisher.proposeL2Block(l2Block); @@ -274,7 +282,10 @@ describe('L1Publisher', () => { it('returns false if publish and propose tx reverts', async () => { rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); - l1TxUtils.sendAndMonitorTransaction.mockResolvedValueOnce({ ...proposeTxReceipt, status: 'reverted' }); + l1TxUtils.sendAndMonitorTransaction.mockResolvedValueOnce({ + receipt: { ...proposeTxReceipt, status: 'reverted' }, + gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n }, + }); const result = await publisher.proposeL2Block(l2Block); @@ -284,7 +295,10 @@ describe('L1Publisher', () => { it('returns false if propose tx reverts', async () => { rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); - l1TxUtils.sendAndMonitorTransaction.mockResolvedValueOnce({ ...proposeTxReceipt, status: 'reverted' }); + l1TxUtils.sendAndMonitorTransaction.mockResolvedValueOnce({ + receipt: { ...proposeTxReceipt, status: 'reverted' }, + gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n }, + }); const result = await publisher.proposeL2Block(l2Block); @@ -294,7 +308,11 @@ describe('L1Publisher', () => { it('returns false if sending publish and progress tx is interrupted', async () => { rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); l1TxUtils.sendAndMonitorTransaction.mockImplementationOnce( - () => sleep(10, proposeTxReceipt) as Promise, + () => + sleep(10, { receipt: proposeTxReceipt, gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n } }) as Promise<{ + receipt: TransactionReceipt; + gasPrice: GasPrice; + }>, ); const resultPromise = publisher.proposeL2Block(l2Block); publisher.interrupt(); @@ -307,7 +325,11 @@ describe('L1Publisher', () => { it('returns false if sending propose tx is interrupted', async () => { rollupContractRead.archive.mockResolvedValue(l2Block.header.lastArchive.root.toString() as `0x${string}`); l1TxUtils.sendAndMonitorTransaction.mockImplementationOnce( - () => sleep(10, proposeTxReceipt) as Promise, + () => + sleep(10, { receipt: proposeTxReceipt, gasPrice: { maxFeePerGas: 1n, maxPriorityFeePerGas: 1n } }) as Promise<{ + receipt: TransactionReceipt; + gasPrice: GasPrice; + }>, ); const resultPromise = publisher.proposeL2Block(l2Block); diff --git a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts index f298bf6d6ed..03296ab61b4 100644 --- a/yarn-project/sequencer-client/src/publisher/l1-publisher.ts +++ b/yarn-project/sequencer-client/src/publisher/l1-publisher.ts @@ -17,7 +17,14 @@ import { type Proof, } from '@aztec/circuits.js'; import { type FeeRecipient, type RootRollupPublicInputs } from '@aztec/circuits.js/rollup'; -import { type EthereumChain, type L1ContractsConfig, L1TxUtils, createEthereumChain } from '@aztec/ethereum'; +import { + type EthereumChain, + type GasPrice, + type L1ContractsConfig, + L1TxUtils, + createEthereumChain, + formatViemError, +} from '@aztec/ethereum'; import { makeTuple } from '@aztec/foundation/array'; import { toHex } from '@aztec/foundation/bigint-buffer'; import { Blob } from '@aztec/foundation/blob'; @@ -65,7 +72,6 @@ import { privateKeyToAccount } from 'viem/accounts'; import { type PublisherConfig, type TxSenderConfig } from './config.js'; import { L1PublisherMetrics } from './l1-publisher-metrics.js'; -import { prettyLogViemErrorMsg } from './utils.js'; /** * Stats for a sent transaction. @@ -119,6 +125,14 @@ type L1ProcessArgs = { attestations?: Signature[]; }; +type L1ProcessReturnType = { + receipt: TransactionReceipt | undefined; + args: any; + functionName: string; + data: Hex; + gasPrice: GasPrice; +}; + /** Arguments to the submitEpochProof method of the rollup contract */ export type L1SubmitEpochProofArgs = { epochSize: number; @@ -532,7 +546,7 @@ export class L1Publisher { account: this.account, }); } catch (err) { - const msg = prettyLogViemErrorMsg(err); + const msg = formatViemError(err); logger.error(`Failed to vote`, msg); this.myLastVotes[voteType] = cachedMyLastVote; return false; @@ -611,7 +625,7 @@ export class L1Publisher { return false; } - const { receipt, args, functionName, data } = result; + const { receipt, args, functionName, data, gasPrice } = result; // Tx was mined successfully if (receipt.status === 'success') { @@ -650,7 +664,7 @@ export class L1Publisher { { blobs: proposeTxArgs.blobs.map(b => b.dataWithZeros), kzg, - maxFeePerBlobGas: 10000000000n, + maxFeePerBlobGas: gasPrice.maxFeePerBlobGas ?? 10000000000n, }, ); this.log.error(`Rollup process tx reverted. ${errorMsg}`, undefined, { @@ -664,11 +678,10 @@ export class L1Publisher { /** Calls claimEpochProofRight in the Rollup contract to submit a chosen prover quote for the previous epoch. */ public async claimEpochProofRight(proofQuote: EpochProofQuote) { const timer = new Timer(); - - let receipt; + let result; try { this.log.debug(`Submitting claimEpochProofRight transaction`); - receipt = await this.l1TxUtils.sendAndMonitorTransaction({ + result = await this.l1TxUtils.sendAndMonitorTransaction({ to: this.rollupContract.address, data: encodeFunctionData({ abi: RollupAbi, @@ -677,12 +690,14 @@ export class L1Publisher { }), }); } catch (err) { - this.log.error(`Failed to claim epoch proof right: ${prettyLogViemErrorMsg(err)}`, err, { + this.log.error(`Failed to claim epoch proof right: ${formatViemError(err)}`, err, { proofQuote: proofQuote.toInspect(), }); return false; } + const { receipt } = result; + if (receipt.status === 'success') { const tx = await this.getTransactionStats(receipt.transactionHash); const stats: L1PublishStats = { @@ -922,7 +937,7 @@ export class L1Publisher { this.log.info(`SubmitEpochProof proofSize=${args.proof.withoutPublicInputs().length} bytes`); - const txReceipt = await this.l1TxUtils.sendAndMonitorTransaction({ + const { receipt } = await this.l1TxUtils.sendAndMonitorTransaction({ to: this.rollupContract.address, data: encodeFunctionData({ abi: this.rollupContract.abi, @@ -931,7 +946,7 @@ export class L1Publisher { }), }); - return txReceipt.transactionHash; + return receipt.transactionHash; } catch (err) { this.log.error(`Rollup submit epoch proof failed`, err); return undefined; @@ -954,7 +969,7 @@ export class L1Publisher { { blobs: encodedData.blobs.map(b => b.dataWithZeros), kzg, - maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS + // maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS }, ); @@ -1022,7 +1037,7 @@ export class L1Publisher { private async sendProposeTx( encodedData: L1ProcessArgs, opts: { txTimeoutAt?: Date } = {}, - ): Promise<{ receipt: TransactionReceipt | undefined; args: any; functionName: string; data: Hex } | undefined> { + ): Promise { if (this.interrupted) { return undefined; } @@ -1034,7 +1049,7 @@ export class L1Publisher { functionName: 'propose', args, }); - const receipt = await this.l1TxUtils.sendAndMonitorTransaction( + const result = await this.l1TxUtils.sendAndMonitorTransaction( { to: this.rollupContract.address, data, @@ -1046,17 +1061,18 @@ export class L1Publisher { { blobs: encodedData.blobs.map(b => b.dataWithZeros), kzg, - maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS + // maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS }, ); return { - receipt, + receipt: result.receipt, + gasPrice: result.gasPrice, args, functionName: 'propose', data, }; } catch (err) { - this.log.error(`Rollup publish failed: ${prettyLogViemErrorMsg(err)}`, err); + this.log.error(`Rollup publish failed: ${formatViemError(err)}`, err); return undefined; } } @@ -1065,7 +1081,7 @@ export class L1Publisher { encodedData: L1ProcessArgs, quote: EpochProofQuote, opts: { txTimeoutAt?: Date } = {}, - ): Promise<{ receipt: TransactionReceipt | undefined; args: any; functionName: string; data: Hex } | undefined> { + ): Promise { if (this.interrupted) { return undefined; } @@ -1077,7 +1093,7 @@ export class L1Publisher { functionName: 'proposeAndClaim', args: [...args, quote.toViemArgs()], }); - const receipt = await this.l1TxUtils.sendAndMonitorTransaction( + const result = await this.l1TxUtils.sendAndMonitorTransaction( { to: this.rollupContract.address, data, @@ -1089,18 +1105,19 @@ export class L1Publisher { { blobs: encodedData.blobs.map(b => b.dataWithZeros), kzg, - maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS + // maxFeePerBlobGas: 10000000000n, //This is 10 gwei, taken from DEFAULT_MAX_FEE_PER_GAS }, ); return { - receipt, + receipt: result.receipt, + gasPrice: result.gasPrice, args: [...args, quote.toViemArgs()], functionName: 'proposeAndClaim', data, }; } catch (err) { - this.log.error(`Rollup publish failed: ${prettyLogViemErrorMsg(err)}`, err); + this.log.error(`Rollup publish failed: ${formatViemError(err)}`, err); return undefined; } } diff --git a/yarn-project/sequencer-client/src/publisher/utils.ts b/yarn-project/sequencer-client/src/publisher/utils.ts deleted file mode 100644 index c8a570896f4..00000000000 --- a/yarn-project/sequencer-client/src/publisher/utils.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { BaseError, ContractFunctionRevertedError } from 'viem'; - -export function prettyLogViemErrorMsg(err: any) { - if (err instanceof BaseError) { - const revertError = err.walk(err => err instanceof ContractFunctionRevertedError); - if (revertError instanceof ContractFunctionRevertedError) { - const errorName = revertError.data?.errorName ?? ''; - const args = - revertError.metaMessages && revertError.metaMessages?.length > 1 ? revertError.metaMessages[1].trimStart() : ''; - return `${errorName}${args}`; - } - } - return err?.message ?? err; -} diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index cbff50f2b1f..8991a6e3f01 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -21,6 +21,7 @@ import { type GlobalVariables, StateReference, } from '@aztec/circuits.js'; +import { prettyLogViemErrorMsg } from '@aztec/ethereum'; import { AztecAddress } from '@aztec/foundation/aztec-address'; import { omit } from '@aztec/foundation/collection'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -37,7 +38,6 @@ import { type ValidatorClient } from '@aztec/validator-client'; import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js'; import { type L1Publisher, VoteType } from '../publisher/l1-publisher.js'; -import { prettyLogViemErrorMsg } from '../publisher/utils.js'; import { type SlasherClient } from '../slasher/slasher_client.js'; import { createValidatorsForBlockBuilding } from '../tx_validator/tx_validator_factory.js'; import { getDefaultAllowedSetupFunctions } from './allowed.js';