Skip to content

Commit f397aa5

Browse files
authored
feat: merge-train/spartan-v5 (#24524)
BEGIN_COMMIT_OVERRIDE fix(sequencer): do not run fallback actions within build window (#24504) fix(proposal-handler): ensure we default to pushing blocks to archiver (#24506) fix(p2p): bump global reqresp limits (#24507) fix(p2p): reject proposal when expected proposer is undefined (#24509) fix(archiver): tolerate re-included already-stored checkpoints (#24513) fix(p2p): canonicalize yParity attestation signatures before L1 bundle (#24515) test(e2e): sync anvil clock before sequencer start (#24474) test(e2e): consolidate automine token suites (#24489) test(e2e): consolidate automine contracts and effects suites (#24490) test(e2e): consolidate cross-chain bridge and messaging suites (#24491) test(e2e): merge single-node fee, proving, and sequencer config suites (#24492) test(e2e): name shared timing presets and collapse proof-submission constants (#24494) test(e2e): unify slashing timing profiles and merge duplicate suites (#24495) test(e2e): dedupe multi-node block-production and governance test setups (#24498) test(e2e): extract p2p gossip scenario skeleton and add category README (#24500) test(e2e): split node-killing HA test into its own composed suite file (#24503) feat(sequencer): make sequencer pausable (#24475) END_COMMIT_OVERRIDE
2 parents aba2a2e + 7e7d8d0 commit f397aa5

152 files changed

Lines changed: 6190 additions & 6695 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.test_patterns.yml

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,19 @@ tests:
126126
- regex: "src/single-node/cross-chain/l1_to_l2"
127127
owners:
128128
- *palla
129-
- regex: "src/single-node/cross-chain/token_bridge_private"
129+
- regex: "src/single-node/cross-chain/token_bridge"
130130
error_regex: "✕ Claim secret is enough to consume the message"
131131
owners:
132132
- *lasse
133-
- regex: "src/single-node/cross-chain/token_bridge_public"
133+
- regex: "src/single-node/cross-chain/token_bridge"
134134
error_regex: "✕ Someone else can mint funds to me on my behalf"
135135
owners:
136136
- *lasse
137-
- regex: "src/single-node/cross-chain/token_bridge_public"
137+
- regex: "src/single-node/cross-chain/token_bridge"
138138
error_regex: "✕ Publicly deposit funds"
139139
owners:
140140
- *lasse
141-
- regex: "src/single-node/cross-chain/token_bridge_failure_cases"
141+
- regex: "src/single-node/cross-chain/token_bridge"
142142
error_regex: "✕ Can't claim funds"
143143
owners:
144144
- *lasse
@@ -397,6 +397,10 @@ tests:
397397
owners:
398398
- *spyros
399399

400+
- regex: "yarn-project/end-to-end/scripts/run_test.sh ha src/composed/ha/e2e_ha_distribute_work.test.ts"
401+
owners:
402+
- *spyros
403+
400404
# http://ci.aztec-labs.com/98d59d04f85223f8
401405
# Build-cache flake: module not found during Jest startup
402406
- regex: "src/single-node/sequencer/gov_proposal.parallel.test.ts"
@@ -418,7 +422,7 @@ tests:
418422
# http://ci.aztec-labs.com/930ddc9ede87059f
419423
# Pipelining race: slasher doesn't record the duplicate proposal offense before
420424
# the test's wait timeout — same family as #23501.
421-
- regex: "src/multi-node/slashing/duplicate_proposal.test.ts.*duplicate proposals"
425+
- regex: "src/multi-node/slashing/equivocation_offenses.test.ts.*duplicate proposals"
422426
error_regex: "TimeoutError: Timeout awaiting duplicate proposal offense"
423427
owners:
424428
- *palla

docs/docs-developers/docs/aztec-js/how_to_pay_fees.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ You can derive the Sponsored FPC address from its deployment parameters, registe
101101

102102
Here's a simpler example from the test suite:
103103

104-
#include_code sponsored_fpc_simple yarn-project/end-to-end/src/single-node/fees/sponsored_payments.test.ts typescript
104+
#include_code sponsored_fpc_simple yarn-project/end-to-end/src/single-node/fees/private_payments.parallel.test.ts typescript
105105

106106
### Private fee payment
107107

yarn-project/archiver/src/modules/data_store_updater.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,63 @@ describe('ArchiverDataStoreUpdater', () => {
341341
expect(await store.contractClasses.getContractClass(contractClassId)).toBeUndefined();
342342
expect(await store.contractInstances.getContractInstance(instanceAddress, timestamp)).toBeUndefined();
343343
});
344+
345+
it('accepts a re-included already-stored checkpoint carrying contract data (A-1350)', async () => {
346+
const block = await L2Block.random(BlockNumber(1), {
347+
checkpointNumber: CheckpointNumber(1),
348+
indexWithinCheckpoint: IndexWithinCheckpoint(0),
349+
});
350+
block.body.txEffects[0].contractClassLogs = [contractClassLog];
351+
block.body.txEffects[0].privateLogs = [PrivateLog.fromBuffer(getSampleContractInstancePublishedEventPayload())];
352+
353+
const checkpoint = makeCheckpoint([block]);
354+
await updater.addCheckpoints([makePublishedCheckpoint(checkpoint, 10)]);
355+
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();
356+
357+
// Simulate an L1 reorg that re-includes the same checkpoint at a later L1 block.
358+
await expect(updater.addCheckpoints([makePublishedCheckpoint(checkpoint, 999)])).resolves.toBeDefined();
359+
360+
// L1 metadata must reflect the re-inclusion and contract data must still be present.
361+
const stored = await store.blocks.getCheckpointData(CheckpointNumber(1));
362+
expect(stored?.l1.blockNumber).toBe(999n);
363+
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();
364+
const timestamp = block.header.globalVariables.timestamp + 1n;
365+
expect(await store.contractInstances.getContractInstance(instanceAddress, timestamp)).toBeDefined();
366+
});
367+
368+
it('extracts only the newly-inserted suffix when a re-included checkpoint is batched with a new one (A-1350)', async () => {
369+
// Checkpoint 1 (block 1) carries the contract class log; checkpoint 2 (block 2) carries the
370+
// contract instance log. Ingest checkpoint 1, then re-present it (at a new L1 block) batched with
371+
// the brand-new checkpoint 2. Only checkpoint 2's block is new, so its instance must be extracted
372+
// while re-extracting checkpoint 1's already-stored class is skipped rather than throwing.
373+
const block1 = await L2Block.random(BlockNumber(1), {
374+
checkpointNumber: CheckpointNumber(1),
375+
indexWithinCheckpoint: IndexWithinCheckpoint(0),
376+
});
377+
block1.body.txEffects[0].contractClassLogs = [contractClassLog];
378+
379+
const checkpoint1 = makeCheckpoint([block1]);
380+
await updater.addCheckpoints([makePublishedCheckpoint(checkpoint1, 10)]);
381+
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();
382+
383+
const block2 = await L2Block.random(BlockNumber(2), {
384+
checkpointNumber: CheckpointNumber(2),
385+
indexWithinCheckpoint: IndexWithinCheckpoint(0),
386+
lastArchive: block1.archive,
387+
});
388+
block2.body.txEffects[0].privateLogs = [PrivateLog.fromBuffer(getSampleContractInstancePublishedEventPayload())];
389+
const checkpoint2 = makeCheckpoint([block2], CheckpointNumber(2));
390+
391+
// Re-present checkpoint 1 at a new L1 block, batched with the new checkpoint 2.
392+
await expect(
393+
updater.addCheckpoints([makePublishedCheckpoint(checkpoint1, 999), makePublishedCheckpoint(checkpoint2, 20)]),
394+
).resolves.toBeDefined();
395+
396+
// Checkpoint 1's class stays stored (not re-extracted), and checkpoint 2's instance was extracted.
397+
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();
398+
const timestamp = block2.header.globalVariables.timestamp + 1n;
399+
expect(await store.contractInstances.getContractInstance(instanceAddress, timestamp)).toBeDefined();
400+
});
344401
});
345402

346403
describe('logs handling', () => {

yarn-project/archiver/src/modules/data_store_updater.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,10 +123,11 @@ export class ArchiverDataStoreUpdater {
123123
// Before adding checkpoints, check for conflicts with local blocks if any
124124
const { prunedBlocks, lastAlreadyInsertedBlockNumber } = await this.pruneMismatchingLocalBlocks(checkpoints);
125125

126-
await this.stores.blocks.addCheckpoints(checkpoints);
126+
const insertedCheckpoints = await this.stores.blocks.addCheckpoints(checkpoints);
127127

128-
// Filter out blocks that were already inserted via addProposedBlock() to avoid duplicating logs/contract data
129-
const newBlocks = checkpoints
128+
// Skip blocks already inserted via addProposedBlock() and blocks of already-stored checkpoints
129+
// re-included by an L1 reorg: their logs/contract data were extracted when first inserted.
130+
const newBlocks = insertedCheckpoints
130131
.flatMap((ch: PublishedCheckpoint) => ch.checkpoint.blocks)
131132
.filter(b => lastAlreadyInsertedBlockNumber === undefined || b.number > lastAlreadyInsertedBlockNumber);
132133

yarn-project/archiver/src/store/block_store.test.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ describe('BlockStore', () => {
9898

9999
describe('addCheckpoints', () => {
100100
it('returns success when adding checkpoints', async () => {
101-
await expect(blockStore.addCheckpoints(publishedCheckpoints)).resolves.toBe(true);
101+
await expect(blockStore.addCheckpoints(publishedCheckpoints)).resolves.toEqual(publishedCheckpoints);
102102
});
103103

104104
it('accepts duplicate checkpoints with matching archives and updates L1 info', async () => {
@@ -118,8 +118,10 @@ describe('BlockStore', () => {
118118
makeL1PublishedData(999),
119119
first3[2].attestations,
120120
);
121-
// Also add checkpoint 4 (the next one) in the same batch
122-
await blockStore.addCheckpoints([cp3WithNewL1, publishedCheckpoints[3]]);
121+
// Also add checkpoint 4 (the next one) in the same batch; only checkpoint 4 is newly inserted.
122+
await expect(blockStore.addCheckpoints([cp3WithNewL1, publishedCheckpoints[3]])).resolves.toEqual([
123+
publishedCheckpoints[3],
124+
]);
123125

124126
// Checkpoint 3's L1 info should be updated
125127
const afterData = await blockStore.getCheckpointData(CheckpointNumber(3));
@@ -135,8 +137,8 @@ describe('BlockStore', () => {
135137
const first3 = publishedCheckpoints.slice(0, 3);
136138
await blockStore.addCheckpoints(first3);
137139

138-
// Re-add the same 3 checkpoints — should succeed without error
139-
await expect(blockStore.addCheckpoints(first3)).resolves.toBe(true);
140+
// Re-add the same 3 checkpoints — should succeed without inserting anything new
141+
await expect(blockStore.addCheckpoints(first3)).resolves.toEqual([]);
140142
});
141143

142144
it('throws on duplicate checkpoints with mismatching archives', async () => {
@@ -284,7 +286,7 @@ describe('BlockStore', () => {
284286
);
285287
const publishedCheckpoint = makePublishedCheckpoint(checkpoint, 10);
286288

287-
await expect(blockStore.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
289+
await expect(blockStore.addCheckpoints([publishedCheckpoint])).resolves.toEqual([publishedCheckpoint]);
288290
});
289291

290292
it('throws on duplicate checkpoint with different content', async () => {
@@ -314,7 +316,7 @@ describe('BlockStore', () => {
314316
);
315317
const publishedCheckpoint2 = makePublishedCheckpoint(checkpoint2, 10);
316318

317-
await expect(blockStore.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
319+
await expect(blockStore.addCheckpoints([publishedCheckpoint])).resolves.toEqual([publishedCheckpoint]);
318320
await expect(blockStore.addCheckpoints([publishedCheckpoint2])).rejects.toThrow(
319321
'already exists in store but with a different archive',
320322
);
@@ -352,7 +354,7 @@ describe('BlockStore', () => {
352354
blocksPerCheckpoint: 2,
353355
});
354356

355-
await expect(blockStore.addCheckpoints(checkpoints)).resolves.toBe(true);
357+
await expect(blockStore.addCheckpoints(checkpoints)).resolves.toEqual(checkpoints);
356358

357359
// Verify blocks have correct checkpoint assignments
358360
const block1 = await blockStore.getBlock({ number: BlockNumber(1) });
@@ -2081,7 +2083,7 @@ describe('BlockStore', () => {
20812083
const publishedCheckpoint2 = makePublishedCheckpoint(checkpoint2, 10);
20822084

20832085
// This should NOT throw - addCheckpoints uses .set() which is idempotent
2084-
await expect(blockStore.addCheckpoints([publishedCheckpoint2])).resolves.toBe(true);
2086+
await expect(blockStore.addCheckpoints([publishedCheckpoint2])).resolves.toEqual([publishedCheckpoint2]);
20852087

20862088
// Verify block exists and is consistent
20872089
const storedBlock = await blockStore.getBlock({ number: BlockNumber(2) });

yarn-project/archiver/src/store/block_store.ts

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -306,12 +306,18 @@ export class BlockStore {
306306

307307
/**
308308
* Append new checkpoints to the store's list.
309+
* Checkpoints at the start of the batch that are already stored (e.g. re-included by an L1 reorg)
310+
* are accepted if their archive root matches: their L1 metadata is updated but they are not
311+
* re-inserted, and they are excluded from the returned array.
309312
* @param checkpoints - The L2 checkpoints to be added to the store.
310-
* @returns True if the operation is successful.
313+
* @returns The checkpoints that were actually inserted (excluding already-stored ones).
311314
*/
312-
async addCheckpoints(checkpoints: PublishedCheckpoint[], opts: { force?: boolean } = {}): Promise<boolean> {
315+
async addCheckpoints(
316+
checkpoints: PublishedCheckpoint[],
317+
opts: { force?: boolean } = {},
318+
): Promise<PublishedCheckpoint[]> {
313319
if (checkpoints.length === 0) {
314-
return true;
320+
return [];
315321
}
316322

317323
return await this.db.transactionAsync(async () => {
@@ -324,7 +330,7 @@ export class BlockStore {
324330
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
325331
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
326332
if (checkpoints.length === 0) {
327-
return true;
333+
return [];
328334
}
329335
// Re-check sequentiality after skipping
330336
const newFirstNumber = checkpoints[0].checkpoint.number;
@@ -387,7 +393,7 @@ export class BlockStore {
387393
}
388394

389395
await this.advanceSynchedL1BlockNumber(checkpoints[checkpoints.length - 1].l1.blockNumber);
390-
return true;
396+
return checkpoints;
391397
});
392398
}
393399

yarn-project/archiver/src/store/contract_class_store.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,22 @@ describe('ContractClassStore', () => {
4444
await expect(contractClassStore.getContractClass(contractClass.id)).resolves.toBeUndefined();
4545
});
4646

47-
it('throws if the same contract class is added again', async () => {
47+
it('throws if the same contract class is added again at a different block', async () => {
4848
await expect(
4949
contractClassStore.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum + 1)),
5050
).rejects.toThrow(/already exists/);
5151
});
5252

53+
it('treats re-adding the same contract class at the same block as a no-op (A-1350)', async () => {
54+
// An L1 reorg can re-present an already-stored checkpoint, replaying this class at the same block.
55+
const originalCommitment = await computePublicBytecodeCommitment(contractClass.packedBytecode);
56+
await expect(
57+
contractClassStore.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum)),
58+
).resolves.not.toThrow();
59+
await expect(contractClassStore.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
60+
await expect(contractClassStore.getBytecodeCommitment(contractClass.id)).resolves.toEqual(originalCommitment);
61+
});
62+
5363
it('returns contract class if deleted at a later block number', async () => {
5464
await contractClassStore.deleteContractClasses([contractClass], BlockNumber(blockNum + 1));
5565
await expect(contractClassStore.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);

yarn-project/archiver/src/store/contract_class_store.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,19 @@ export class ContractClassStore {
5050
): Promise<void> {
5151
await this.db.transactionAsync(async () => {
5252
const key = contractClass.id.toString();
53-
if (await this.#contractClasses.hasAsync(key)) {
53+
const existing = await this.#contractClasses.getAsync(key);
54+
if (existing !== undefined) {
5455
// Protocol contracts are preloaded at block 0, so a later on-chain (re-)publish of a bundled
5556
// protocol class id is valid and must be a no-op. Keep the existing block-0 entry untouched
5657
// (do not bump its block number) so it survives reorgs of the publishing block.
5758
if (isProtocolContractClass(contractClass.id)) {
5859
return;
5960
}
61+
// An L1 reorg can re-present an already-stored checkpoint, replaying this class at the same
62+
// block; treat that as a no-op. A duplicate at a different block still signals double-processing.
63+
if (deserializeContractClassPublic(existing).l2BlockNumber === blockNumber) {
64+
return;
65+
}
6066
throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
6167
}
6268
await this.#contractClasses.set(

yarn-project/archiver/src/store/contract_instance_store.test.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,24 @@ describe('ContractInstanceStore', () => {
4949
).resolves.toBeUndefined();
5050
});
5151

52-
it('throws when adding the same contract instance twice', async () => {
52+
it('throws when adding the same contract instance again at a different block', async () => {
5353
await expect(contractInstanceStore.addContractInstances([contractInstance], BlockNumber(2))).rejects.toThrow(
5454
/already exists/,
5555
);
5656
});
57+
58+
it('treats re-adding the same contract instance at the same block as a no-op (A-1350)', async () => {
59+
// An L1 reorg can re-present an already-stored checkpoint, replaying this instance at the same block.
60+
await expect(
61+
contractInstanceStore.addContractInstances([contractInstance], BlockNumber(blockNum)),
62+
).resolves.not.toThrow();
63+
await expect(
64+
contractInstanceStore.getContractInstance(contractInstance.address, timestamp),
65+
).resolves.toMatchObject(contractInstance);
66+
await expect(
67+
contractInstanceStore.getContractInstanceDeploymentBlockNumber(contractInstance.address),
68+
).resolves.toEqual(blockNum);
69+
});
5770
});
5871

5972
describe('protocol contract instances (A-1257)', () => {

yarn-project/archiver/src/store/contract_instance_store.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,14 @@ export class ContractInstanceStore {
7878
if (isProtocolContract(contractInstance.address)) {
7979
return;
8080
}
81+
const existingBlockNumber = await this.#contractInstancePublishedAt.getAsync(key);
82+
// An L1 reorg can re-present an already-stored checkpoint, replaying this instance at the same
83+
// block; treat that as a no-op. A duplicate at a different block still signals double-processing.
84+
if (existingBlockNumber === blockNumber) {
85+
return;
86+
}
8187
throw new Error(
82-
`Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`,
88+
`Contract instance at ${key} already exists (deployed at block ${existingBlockNumber}), cannot add again at block ${blockNumber}`,
8389
);
8490
}
8591
await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer());

0 commit comments

Comments
 (0)