From 4ca122c3ff4ad5d14d0fa0f2125e932b014492f5 Mon Sep 17 00:00:00 2001 From: Santiago Palladino Date: Wed, 17 Sep 2025 16:54:58 -0300 Subject: [PATCH] fix: Do not assemble a block larger than blobs allowance The sequencer publisher enforces a max size for a block, depending on the size it takes up in blobs. If the block exceeds that size, it's rejected by the publisher. This PR adds a check during block building to ensure that we don't go past that limit. --- .../src/sequencer/sequencer.ts | 3 ++- .../public_processor/public_processor.test.ts | 22 +++++++++++++++++++ .../public_processor/public_processor.ts | 21 +++++++++++++++++- .../stdlib/src/interfaces/block-builder.ts | 1 + 4 files changed, 45 insertions(+), 2 deletions(-) diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 2055b77a66e5..3c162dc684f1 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,5 +1,5 @@ import type { L2Block } from '@aztec/aztec.js'; -import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; +import { BLOBS_PER_BLOCK, FIELDS_PER_BLOB, INITIAL_L2_BLOCK_NUM } from '@aztec/constants'; import type { EpochCache } from '@aztec/epoch-cache'; import { FormattedViemError, NoCommitteeError, type RollupContract } from '@aztec/ethereum'; import { omit, pick } from '@aztec/foundation/collection'; @@ -581,6 +581,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter { expect(failed).toEqual([]); }); + it('does not exceed max blob fields limit', async function () { + // Create 3 private-only transactions + const txs = await Promise.all(Array.from([1, 2, 3], seed => mockPrivateOnlyTx({ seed }))); + + // First, let's process one transaction to see how many blob fields it actually has + const [testProcessed] = await processor.process([txs[0]]); + const actualBlobFields = testProcessed[0].txEffect.toBlobFields().length; + + // Set the limit to allow only 2 transactions + // If each tx has `actualBlobFields` fields, we set limit to allow 2 but not 3 + const maxBlobFields = actualBlobFields * 2; + + // Process all 3 transactions with the blob field limit + const [processed, failed] = await processor.process(txs, { maxBlobFields }); + + // Should only process 2 transactions due to blob field limit + expect(processed.length).toBe(2); + expect(processed[0].hash).toEqual(txs[0].getTxHash()); + expect(processed[1].hash).toEqual(txs[1].getTxHash()); + expect(failed).toEqual([]); + }); + it('does not send a transaction to the prover if pre validation fails', async function () { const tx = await mockPrivateOnlyTx(); diff --git a/yarn-project/simulator/src/public/public_processor/public_processor.ts b/yarn-project/simulator/src/public/public_processor/public_processor.ts index 70b208020e24..50ba42d65ebf 100644 --- a/yarn-project/simulator/src/public/public_processor/public_processor.ts +++ b/yarn-project/simulator/src/public/public_processor/public_processor.ts @@ -154,7 +154,7 @@ export class PublicProcessor implements Traceable { limits: PublicProcessorLimits = {}, validator: PublicProcessorValidator = {}, ): Promise<[ProcessedTx[], FailedTx[], Tx[], NestedProcessReturnValues[]]> { - const { maxTransactions, maxBlockSize, deadline, maxBlockGas } = limits; + const { maxTransactions, maxBlockSize, deadline, maxBlockGas, maxBlobFields } = limits; const { preprocessValidator, nullifierCache } = validator; const result: ProcessedTx[] = []; const usedTxs: Tx[] = []; @@ -165,6 +165,7 @@ export class PublicProcessor implements Traceable { let returns: NestedProcessReturnValues[] = []; let totalPublicGas = new Gas(0, 0); let totalBlockGas = new Gas(0, 0); + let totalBlobFields = 0; for await (const origTx of txs) { // Only process up to the max tx limit @@ -252,6 +253,23 @@ export class PublicProcessor implements Traceable { continue; } + // If the actual blob fields of this tx would exceed the limit, skip it + const txBlobFields = processedTx.txEffect.toBlobFields().length; + if (maxBlobFields !== undefined && totalBlobFields + txBlobFields > maxBlobFields) { + this.log.debug( + `Skipping processed tx ${txHash} with ${txBlobFields} blob fields due to max blob fields limit.`, + { + txHash, + txBlobFields, + totalBlobFields, + maxBlobFields, + }, + ); + // Need to revert the checkpoint here and don't go any further + await checkpoint.revert(); + continue; + } + // FIXME(fcarreiro): it's ugly to have to notify the validator of nullifiers. // I'd rather pass the validators the processedTx as well and let them deal with it. nullifierCache?.addNullifiers(processedTx.txEffect.nullifiers.map(n => n.toBuffer())); @@ -262,6 +280,7 @@ export class PublicProcessor implements Traceable { totalPublicGas = totalPublicGas.add(processedTx.gasUsed.publicGas); totalBlockGas = totalBlockGas.add(processedTx.gasUsed.totalGas); totalSizeInBytes += txSize; + totalBlobFields += txBlobFields; } catch (err: any) { if (err?.name === 'PublicProcessorTimeoutError') { this.log.warn(`Stopping tx processing due to timeout.`); diff --git a/yarn-project/stdlib/src/interfaces/block-builder.ts b/yarn-project/stdlib/src/interfaces/block-builder.ts index 2e8cde98a35f..53f62ee7169e 100644 --- a/yarn-project/stdlib/src/interfaces/block-builder.ts +++ b/yarn-project/stdlib/src/interfaces/block-builder.ts @@ -43,6 +43,7 @@ export interface PublicProcessorLimits { maxTransactions?: number; maxBlockSize?: number; maxBlockGas?: Gas; + maxBlobFields?: number; deadline?: Date; }