Skip to content

Commit 7bfeae4

Browse files
committed
fix: Prove block that consumes an L1toL2 msg added on the same L1 block
1 parent 5256c9f commit 7bfeae4

File tree

2 files changed

+107
-1
lines changed

2 files changed

+107
-1
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import { Fr, type Logger, generateClaimSecret, retryUntil } from '@aztec/aztec.js';
2+
import { EthAddress } from '@aztec/foundation/eth-address';
3+
import { TestContract } from '@aztec/noir-test-contracts.js/Test';
4+
5+
import { jest } from '@jest/globals';
6+
7+
import { sendL1ToL2Message } from '../fixtures/l1_to_l2_messaging.js';
8+
import type { EndToEndContext } from '../fixtures/utils.js';
9+
import { EpochsTestContext } from './epochs_test.js';
10+
11+
jest.setTimeout(1000 * 60 * 10);
12+
13+
// Proves an epoch that contains txs with public function calls that consume L1 to L2 messages
14+
// Regression for this issue https://aztecprotocol.slack.com/archives/C085C1942HG/p1754400213976059
15+
describe('e2e_epochs/epochs_proof_public_cross_chain', () => {
16+
let context: EndToEndContext;
17+
let logger: Logger;
18+
19+
let test: EpochsTestContext;
20+
21+
beforeEach(async () => {
22+
test = await EpochsTestContext.setup({ numberOfAccounts: 1, minTxsPerBlock: 1, disableAnvilTestWatcher: true });
23+
({ context, logger } = test);
24+
});
25+
26+
afterEach(async () => {
27+
jest.restoreAllMocks();
28+
await test.teardown();
29+
});
30+
31+
it('submits proof with a tx with public l1-to-l2 message claim', async () => {
32+
// Deploy a contract that consumes L1 to L2 messages
33+
await context.aztecNodeAdmin!.setConfig({ minTxsPerBlock: 0 });
34+
logger.warn(`Deploying test contract`);
35+
const testContract = await TestContract.deploy(context.wallet).send({ from: context.accounts[0] }).deployed();
36+
logger.warn(`Test contract deployed at ${testContract.address}`);
37+
38+
// Send an l1 to l2 message to be consumed from the contract
39+
const [secret, secretHash] = await generateClaimSecret();
40+
const message = { recipient: testContract.address, content: Fr.random(), secretHash };
41+
logger.warn(`Sending L1 to L2 message ${message.content.toString()} to be consumed by ${testContract.address}`);
42+
const { msgHash, globalLeafIndex } = await sendL1ToL2Message(message, context.deployL1ContractsValues);
43+
44+
logger.warn(`Waiting for message ${msgHash} with index ${globalLeafIndex} to be synced`);
45+
await retryUntil(
46+
() => context.aztecNode.isL1ToL2MessageSynced(msgHash),
47+
'message sync',
48+
test.L2_SLOT_DURATION_IN_S * 4,
49+
);
50+
51+
// And we consume the message using the test contract. It's important that we don't wait for the membership witness
52+
// to be available, since we want to test the scenario where the message becomes available on the same block the tx lands.
53+
logger.warn(`Consuming message ${message.content.toString()} from the contract at ${testContract.address}`);
54+
const txReceipt = await testContract.methods
55+
.consume_message_from_arbitrary_sender_public(
56+
message.content,
57+
secret,
58+
EthAddress.fromString(context.deployL1ContractsValues.l1Client.account.address),
59+
globalLeafIndex.toBigInt(),
60+
)
61+
.send({ from: context.accounts[0] })
62+
.wait();
63+
expect(txReceipt.blockNumber).toBeGreaterThan(0);
64+
65+
// Wait until a proof lands for the transaction
66+
logger.warn(`Waiting for proof for tx ${txReceipt.txHash} mined at ${txReceipt.blockNumber!}`);
67+
await retryUntil(
68+
async () => {
69+
const provenBlockNumber = await context.aztecNode.getProvenBlockNumber();
70+
logger.info(`Proven block number is ${provenBlockNumber}`);
71+
return provenBlockNumber >= txReceipt.blockNumber!;
72+
},
73+
'Proof has been submitted',
74+
test.L2_SLOT_DURATION_IN_S * test.epochDuration * 3,
75+
);
76+
77+
const provenBlockNumber = await context.aztecNode.getProvenBlockNumber();
78+
expect(provenBlockNumber).toBeGreaterThanOrEqual(txReceipt.blockNumber!);
79+
80+
logger.info(`Test succeeded`);
81+
});
82+
});

yarn-project/prover-node/src/job/epoch-proving-job.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { BatchedBlob, Blob } from '@aztec/blob-lib';
2+
import { NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP } from '@aztec/constants';
23
import { asyncPool } from '@aztec/foundation/async-pool';
4+
import { padArrayEnd } from '@aztec/foundation/collection';
5+
import { Fr } from '@aztec/foundation/fields';
36
import { createLogger } from '@aztec/foundation/log';
47
import { RunningPromise, promiseWithResolvers } from '@aztec/foundation/promise';
58
import { Timer } from '@aztec/foundation/timer';
@@ -11,6 +14,7 @@ import {
1114
EpochProvingJobTerminalState,
1215
type ForkMerkleTreeOperations,
1316
} from '@aztec/stdlib/interfaces/server';
17+
import { MerkleTreeId } from '@aztec/stdlib/trees';
1418
import type { ProcessedTx, Tx } from '@aztec/stdlib/tx';
1519
import { Attributes, type Traceable, type Tracer, trackSpan } from '@aztec/telemetry-client';
1620

@@ -151,7 +155,7 @@ export class EpochProvingJob implements Traceable {
151155
await this.prover.startNewBlock(globalVariables, l1ToL2Messages, previousHeader);
152156

153157
// Process public fns
154-
const db = await this.dbProvider.fork(block.number - 1);
158+
const db = await this.createFork(block.number - 1, l1ToL2Messages);
155159
const publicProcessor = this.publicProcessorFactory.create(db, globalVariables, true);
156160
const processed = await this.processTxs(publicProcessor, txs);
157161
await this.prover.addTxs(processed);
@@ -214,6 +218,26 @@ export class EpochProvingJob implements Traceable {
214218
}
215219
}
216220

221+
/**
222+
* Create a new db fork for tx processing, inserting all L1 to L2.
223+
* REFACTOR: The prover already spawns a db fork of its own for each block, so we may be able to do away with just one fork.
224+
*/
225+
private async createFork(blockNumber: number, l1ToL2Messages: Fr[]) {
226+
const db = await this.dbProvider.fork(blockNumber);
227+
const l1ToL2MessagesPadded = padArrayEnd(
228+
l1ToL2Messages,
229+
Fr.ZERO,
230+
NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP,
231+
'Too many L1 to L2 messages',
232+
);
233+
this.log.verbose(`Creating fork at ${blockNumber} with ${l1ToL2Messages.length} L1 to L2 messages`, {
234+
blockNumber,
235+
l1ToL2Messages: l1ToL2MessagesPadded.map(m => m.toString()),
236+
});
237+
await db.appendLeaves(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, l1ToL2MessagesPadded);
238+
return db;
239+
}
240+
217241
private progressState(state: EpochProvingJobState) {
218242
this.checkState();
219243
this.state = state;

0 commit comments

Comments
 (0)