Skip to content

Insert L1 to L2 messages in the tree before public execution in public rollup circuits #16675

@spalladino

Description

@spalladino

We have a lingering issue with L1 to L2 messages: if a tx consumes an L1 to L2 message on the same block it is seen by the archiver, then that block is not provable. This is because of a mismatch between how the sequencer and the prover node work.

The sequencer uses the block_builder, which when starting a new block, first inserts all L1 to L2 messages into the tree to make them available, so when txs are processed by the public_processor, they can access the messages.

await blockFactory.startNewBlock(newGlobalVariables, l1ToL2Messages);
const [publicProcessorDuration, [processedTxs, failedTxs, usedTxs]] = await elapsed(() =>
processor.process(pendingTxs, opts, validator),
);
// All real transactions have been added, set the block as full and pad if needed
await blockFactory.addTxs(processedTxs);
const block = await blockFactory.setBlockCompleted();

However, the prover node doesn't. It forks world-state immediately before the block it wants to prove, and then calls the public_processor without first adding the L1 to L2 messages.

// Process public fns
const db = await this.dbProvider.fork(block.number - 1);
const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, true);
const processed = await this.processTxs(publicProcessor, txs);
await this.prover.addTxs(processed);
await db.close();

So, in #16375 we changed it so that we do add the L1 to L2 messages before we start processing. But then we started getting this error:

[17:19:39.610] ERROR: prover-client:broker-circuit-prover-facade Job errored with 'Circuit execution failed: last_l1_to_l2 in constants does not match the value in the previous block header' id=1:BLOCK_ROOT_ROLLUP:fa05417b0e9ee6e429e870e21962107691067177f857f0b33fb3869c7399bb2a type=SINGLE_TX_BLOCK_ROOT_ROLLUP

This error comes from a mismatch between the previous_rollup_data[0].base_or_merge_rollup_public_inputs.constants.last_l1_to_l2 and data.previous_block_header.state.l1_to_l2_message_tree from the SingleTxBlockRootRollupInputs (if I managed to follow the data flow correctly).

// Validate last_l1_to_l2.
assert_eq(
constants.last_l1_to_l2,
previous_block_header.state.l1_to_l2_message_tree,
"last_l1_to_l2 in constants does not match the value in the previous block header",
);

Now, the constants.last_l1_to_l2 in the public base rollup gets populated from the AVM start_tree_snapshots in the public base rollup:

last_l1_to_l2: avm_data.start_tree_snapshots.l1_to_l2_message_tree,

So, if I understand correctly, this means that circuits are expecting that the L1 to L2 message tree does not change between the end of the previous block and the start of AVM execution, which would contradict what the sequencer is doing by inserting messages before tx execution starts.

So my question is: what is the expected behaviour? Should we be injecting the L1 to L2 messages for a given block into their tree before we start tx public execution, or at the end?

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions