Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e92cc1a
fix(sequencer): do not run fallback actions within build window (#24504)
spalladino Jul 5, 2026
c1b0d79
fix(proposal-handler): ensure we default to pushing blocks to archive…
spalladino Jul 5, 2026
e0859db
fix(p2p): bump global reqresp limits (#24507)
spalladino Jul 5, 2026
62ec0b4
fix(p2p): reject proposal when expected proposer is undefined (#24509)
spalladino Jul 5, 2026
54a08fd
fix(archiver): tolerate re-included already-stored checkpoints (#24513)
spalladino Jul 5, 2026
c545f94
fix(p2p): canonicalize yParity attestation signatures before L1 bundl…
spalladino Jul 5, 2026
111790c
test(e2e): sync anvil clock before sequencer start (#24474)
spalladino Jul 5, 2026
9f23247
test(e2e): consolidate automine token suites (#24489)
spalladino Jul 5, 2026
70ab422
test(e2e): consolidate automine contracts and effects suites (#24490)
spalladino Jul 5, 2026
863d4cc
test(e2e): consolidate cross-chain bridge and messaging suites (#24491)
spalladino Jul 5, 2026
7a1fe62
test(e2e): merge single-node fee, proving, and sequencer config suite…
spalladino Jul 5, 2026
0829dba
test(e2e): name shared timing presets and collapse proof-submission c…
spalladino Jul 5, 2026
842cb8d
test(e2e): unify slashing timing profiles and merge duplicate suites …
spalladino Jul 5, 2026
78594d7
test(e2e): dedupe multi-node block-production and governance test set…
spalladino Jul 5, 2026
a5b3c78
test(e2e): extract p2p gossip scenario skeleton and add category READ…
spalladino Jul 5, 2026
181ceed
test(e2e): split node-killing HA test into its own composed suite fil…
spalladino Jul 5, 2026
7e7d8d0
feat(sequencer): make sequencer pausable (#24475)
spalladino Jul 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions .test_patterns.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,19 +126,19 @@ tests:
- regex: "src/single-node/cross-chain/l1_to_l2"
owners:
- *palla
- regex: "src/single-node/cross-chain/token_bridge_private"
- regex: "src/single-node/cross-chain/token_bridge"
error_regex: "✕ Claim secret is enough to consume the message"
owners:
- *lasse
- regex: "src/single-node/cross-chain/token_bridge_public"
- regex: "src/single-node/cross-chain/token_bridge"
error_regex: "✕ Someone else can mint funds to me on my behalf"
owners:
- *lasse
- regex: "src/single-node/cross-chain/token_bridge_public"
- regex: "src/single-node/cross-chain/token_bridge"
error_regex: "✕ Publicly deposit funds"
owners:
- *lasse
- regex: "src/single-node/cross-chain/token_bridge_failure_cases"
- regex: "src/single-node/cross-chain/token_bridge"
error_regex: "✕ Can't claim funds"
owners:
- *lasse
Expand Down Expand Up @@ -397,6 +397,10 @@ tests:
owners:
- *spyros

- regex: "yarn-project/end-to-end/scripts/run_test.sh ha src/composed/ha/e2e_ha_distribute_work.test.ts"
owners:
- *spyros

# http://ci.aztec-labs.com/98d59d04f85223f8
# Build-cache flake: module not found during Jest startup
- regex: "src/single-node/sequencer/gov_proposal.parallel.test.ts"
Expand All @@ -418,7 +422,7 @@ tests:
# http://ci.aztec-labs.com/930ddc9ede87059f
# Pipelining race: slasher doesn't record the duplicate proposal offense before
# the test's wait timeout — same family as #23501.
- regex: "src/multi-node/slashing/duplicate_proposal.test.ts.*duplicate proposals"
- regex: "src/multi-node/slashing/equivocation_offenses.test.ts.*duplicate proposals"
error_regex: "TimeoutError: Timeout awaiting duplicate proposal offense"
owners:
- *palla
2 changes: 1 addition & 1 deletion docs/docs-developers/docs/aztec-js/how_to_pay_fees.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ You can derive the Sponsored FPC address from its deployment parameters, registe

Here's a simpler example from the test suite:

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

### Private fee payment

Expand Down
57 changes: 57 additions & 0 deletions yarn-project/archiver/src/modules/data_store_updater.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,63 @@ describe('ArchiverDataStoreUpdater', () => {
expect(await store.contractClasses.getContractClass(contractClassId)).toBeUndefined();
expect(await store.contractInstances.getContractInstance(instanceAddress, timestamp)).toBeUndefined();
});

it('accepts a re-included already-stored checkpoint carrying contract data (A-1350)', async () => {
const block = await L2Block.random(BlockNumber(1), {
checkpointNumber: CheckpointNumber(1),
indexWithinCheckpoint: IndexWithinCheckpoint(0),
});
block.body.txEffects[0].contractClassLogs = [contractClassLog];
block.body.txEffects[0].privateLogs = [PrivateLog.fromBuffer(getSampleContractInstancePublishedEventPayload())];

const checkpoint = makeCheckpoint([block]);
await updater.addCheckpoints([makePublishedCheckpoint(checkpoint, 10)]);
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();

// Simulate an L1 reorg that re-includes the same checkpoint at a later L1 block.
await expect(updater.addCheckpoints([makePublishedCheckpoint(checkpoint, 999)])).resolves.toBeDefined();

// L1 metadata must reflect the re-inclusion and contract data must still be present.
const stored = await store.blocks.getCheckpointData(CheckpointNumber(1));
expect(stored?.l1.blockNumber).toBe(999n);
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();
const timestamp = block.header.globalVariables.timestamp + 1n;
expect(await store.contractInstances.getContractInstance(instanceAddress, timestamp)).toBeDefined();
});

it('extracts only the newly-inserted suffix when a re-included checkpoint is batched with a new one (A-1350)', async () => {
// Checkpoint 1 (block 1) carries the contract class log; checkpoint 2 (block 2) carries the
// contract instance log. Ingest checkpoint 1, then re-present it (at a new L1 block) batched with
// the brand-new checkpoint 2. Only checkpoint 2's block is new, so its instance must be extracted
// while re-extracting checkpoint 1's already-stored class is skipped rather than throwing.
const block1 = await L2Block.random(BlockNumber(1), {
checkpointNumber: CheckpointNumber(1),
indexWithinCheckpoint: IndexWithinCheckpoint(0),
});
block1.body.txEffects[0].contractClassLogs = [contractClassLog];

const checkpoint1 = makeCheckpoint([block1]);
await updater.addCheckpoints([makePublishedCheckpoint(checkpoint1, 10)]);
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();

const block2 = await L2Block.random(BlockNumber(2), {
checkpointNumber: CheckpointNumber(2),
indexWithinCheckpoint: IndexWithinCheckpoint(0),
lastArchive: block1.archive,
});
block2.body.txEffects[0].privateLogs = [PrivateLog.fromBuffer(getSampleContractInstancePublishedEventPayload())];
const checkpoint2 = makeCheckpoint([block2], CheckpointNumber(2));

// Re-present checkpoint 1 at a new L1 block, batched with the new checkpoint 2.
await expect(
updater.addCheckpoints([makePublishedCheckpoint(checkpoint1, 999), makePublishedCheckpoint(checkpoint2, 20)]),
).resolves.toBeDefined();

// Checkpoint 1's class stays stored (not re-extracted), and checkpoint 2's instance was extracted.
expect(await store.contractClasses.getContractClass(contractClassId)).toBeDefined();
const timestamp = block2.header.globalVariables.timestamp + 1n;
expect(await store.contractInstances.getContractInstance(instanceAddress, timestamp)).toBeDefined();
});
});

describe('logs handling', () => {
Expand Down
7 changes: 4 additions & 3 deletions yarn-project/archiver/src/modules/data_store_updater.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,11 @@ export class ArchiverDataStoreUpdater {
// Before adding checkpoints, check for conflicts with local blocks if any
const { prunedBlocks, lastAlreadyInsertedBlockNumber } = await this.pruneMismatchingLocalBlocks(checkpoints);

await this.stores.blocks.addCheckpoints(checkpoints);
const insertedCheckpoints = await this.stores.blocks.addCheckpoints(checkpoints);

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

Expand Down
20 changes: 11 additions & 9 deletions yarn-project/archiver/src/store/block_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ describe('BlockStore', () => {

describe('addCheckpoints', () => {
it('returns success when adding checkpoints', async () => {
await expect(blockStore.addCheckpoints(publishedCheckpoints)).resolves.toBe(true);
await expect(blockStore.addCheckpoints(publishedCheckpoints)).resolves.toEqual(publishedCheckpoints);
});

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

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

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

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

await expect(blockStore.addCheckpoints([publishedCheckpoint])).resolves.toBe(true);
await expect(blockStore.addCheckpoints([publishedCheckpoint])).resolves.toEqual([publishedCheckpoint]);
});

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

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

await expect(blockStore.addCheckpoints(checkpoints)).resolves.toBe(true);
await expect(blockStore.addCheckpoints(checkpoints)).resolves.toEqual(checkpoints);

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

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

// Verify block exists and is consistent
const storedBlock = await blockStore.getBlock({ number: BlockNumber(2) });
Expand Down
16 changes: 11 additions & 5 deletions yarn-project/archiver/src/store/block_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,12 +306,18 @@ export class BlockStore {

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

return await this.db.transactionAsync(async () => {
Expand All @@ -324,7 +330,7 @@ export class BlockStore {
if (!opts.force && firstCheckpointNumber <= previousCheckpointNumber) {
checkpoints = await this.skipOrUpdateAlreadyStoredCheckpoints(checkpoints, previousCheckpointNumber);
if (checkpoints.length === 0) {
return true;
return [];
}
// Re-check sequentiality after skipping
const newFirstNumber = checkpoints[0].checkpoint.number;
Expand Down Expand Up @@ -387,7 +393,7 @@ export class BlockStore {
}

await this.advanceSynchedL1BlockNumber(checkpoints[checkpoints.length - 1].l1.blockNumber);
return true;
return checkpoints;
});
}

Expand Down
12 changes: 11 additions & 1 deletion yarn-project/archiver/src/store/contract_class_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,22 @@ describe('ContractClassStore', () => {
await expect(contractClassStore.getContractClass(contractClass.id)).resolves.toBeUndefined();
});

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

it('treats re-adding the same contract class at the same block as a no-op (A-1350)', async () => {
// An L1 reorg can re-present an already-stored checkpoint, replaying this class at the same block.
const originalCommitment = await computePublicBytecodeCommitment(contractClass.packedBytecode);
await expect(
contractClassStore.addContractClasses([await withCommitment(contractClass)], BlockNumber(blockNum)),
).resolves.not.toThrow();
await expect(contractClassStore.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
await expect(contractClassStore.getBytecodeCommitment(contractClass.id)).resolves.toEqual(originalCommitment);
});

it('returns contract class if deleted at a later block number', async () => {
await contractClassStore.deleteContractClasses([contractClass], BlockNumber(blockNum + 1));
await expect(contractClassStore.getContractClass(contractClass.id)).resolves.toMatchObject(contractClass);
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/archiver/src/store/contract_class_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,19 @@ export class ContractClassStore {
): Promise<void> {
await this.db.transactionAsync(async () => {
const key = contractClass.id.toString();
if (await this.#contractClasses.hasAsync(key)) {
const existing = await this.#contractClasses.getAsync(key);
if (existing !== undefined) {
// Protocol contracts are preloaded at block 0, so a later on-chain (re-)publish of a bundled
// protocol class id is valid and must be a no-op. Keep the existing block-0 entry untouched
// (do not bump its block number) so it survives reorgs of the publishing block.
if (isProtocolContractClass(contractClass.id)) {
return;
}
// An L1 reorg can re-present an already-stored checkpoint, replaying this class at the same
// block; treat that as a no-op. A duplicate at a different block still signals double-processing.
if (deserializeContractClassPublic(existing).l2BlockNumber === blockNumber) {
return;
}
throw new Error(`Contract class ${key} already exists, cannot add again at block ${blockNumber}`);
}
await this.#contractClasses.set(
Expand Down
15 changes: 14 additions & 1 deletion yarn-project/archiver/src/store/contract_instance_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,24 @@ describe('ContractInstanceStore', () => {
).resolves.toBeUndefined();
});

it('throws when adding the same contract instance twice', async () => {
it('throws when adding the same contract instance again at a different block', async () => {
await expect(contractInstanceStore.addContractInstances([contractInstance], BlockNumber(2))).rejects.toThrow(
/already exists/,
);
});

it('treats re-adding the same contract instance at the same block as a no-op (A-1350)', async () => {
// An L1 reorg can re-present an already-stored checkpoint, replaying this instance at the same block.
await expect(
contractInstanceStore.addContractInstances([contractInstance], BlockNumber(blockNum)),
).resolves.not.toThrow();
await expect(
contractInstanceStore.getContractInstance(contractInstance.address, timestamp),
).resolves.toMatchObject(contractInstance);
await expect(
contractInstanceStore.getContractInstanceDeploymentBlockNumber(contractInstance.address),
).resolves.toEqual(blockNum);
});
});

describe('protocol contract instances (A-1257)', () => {
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/archiver/src/store/contract_instance_store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,14 @@ export class ContractInstanceStore {
if (isProtocolContract(contractInstance.address)) {
return;
}
const existingBlockNumber = await this.#contractInstancePublishedAt.getAsync(key);
// An L1 reorg can re-present an already-stored checkpoint, replaying this instance at the same
// block; treat that as a no-op. A duplicate at a different block still signals double-processing.
if (existingBlockNumber === blockNumber) {
return;
}
throw new Error(
`Contract instance at ${key} already exists (deployed at block ${await this.#contractInstancePublishedAt.getAsync(key)}), cannot add again at block ${blockNumber}`,
`Contract instance at ${key} already exists (deployed at block ${existingBlockNumber}), cannot add again at block ${blockNumber}`,
);
}
await this.#contractInstances.set(key, new SerializableContractInstance(contractInstance).toBuffer());
Expand Down
21 changes: 19 additions & 2 deletions yarn-project/end-to-end/bootstrap.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,21 @@ function test_cmds {
multi-node/governance/add_rollup)
test_prefix="$prefix:TIMEOUT=20m"
;;
single-node/cross-chain/l1_to_l2.parallel)
test_prefix="$prefix:TIMEOUT=20m"
single-node/cross-chain/l1_to_l2)
# The duplicate-message scenario runs over private and public scope serially in one container.
test_prefix="$prefix:TIMEOUT=25m"
;;
single-node/cross-chain/l1_to_l2_inbox_drift)
# The inbox-drift scenario runs over private and public scope serially in one container.
test_prefix="$prefix:TIMEOUT=25m"
;;
single-node/cross-chain/l2_to_l1)
# Six message-shape / reorg its run serially in one container (was .parallel, one per it).
test_prefix="$prefix:TIMEOUT=25m"
;;
single-node/cross-chain/token_bridge)
# Private + public round trips and the failure cases share one node and run serially.
test_prefix="$prefix:TIMEOUT=25m"
;;
single-node/block-building/block_building)
# Block-building covers the full multi-tx / reorg surface and dumps AVM circuit inputs
Expand Down Expand Up @@ -106,6 +119,10 @@ function test_cmds {

# compose-based tests (use running local network)
tests=(
# integration_proof_verification and e2e_persistence are excluded and run nowhere: the former's committed
# epoch-proof fixture is stale (the proof no longer verifies), and the latter's beforeAll no longer
# completes on the current branch (the single-node sequencer stalls in checkpoint proposal). See each
# file's header comment. Both stay excluded until fixed/regenerated.
src/composed/!(integration_proof_verification|e2e_persistence).test.ts
src/guides/*.test.ts
)
Expand Down
Loading
Loading