Skip to content

Commit dae4079

Browse files
committed
Merge branch 'spy/deployment-config' of https://github.com/AztecProtocol/aztec-packages into spy/deployment-config
2 parents f7127da + 5bd39aa commit dae4079

12 files changed

Lines changed: 326 additions & 64 deletions

File tree

yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { EthAddress } from '@aztec/aztec.js/addresses';
55
import { waitForProven } from '@aztec/aztec.js/contracts';
66
import { generateClaimSecret } from '@aztec/aztec.js/ethereum';
77
import { Fr } from '@aztec/aztec.js/fields';
8+
import { waitForL1ToL2MessageReady } from '@aztec/aztec.js/messaging';
89
import { RollupCheatCodes } from '@aztec/aztec/testing';
910
import { FeeAssetHandlerContract, RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
1011
import { deployRollupForUpgrade } from '@aztec/ethereum/deploy-aztec-l1-contracts';
@@ -13,6 +14,7 @@ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses'
1314
import { L1TxUtils, createL1TxUtils } from '@aztec/ethereum/l1-tx-utils';
1415
import type { ExtendedViemWalletClient } from '@aztec/ethereum/types';
1516
import { CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
17+
import { retryUntil } from '@aztec/foundation/retry';
1618
import { sleep } from '@aztec/foundation/sleep';
1719
import {
1820
GovernanceAbi,
@@ -42,7 +44,6 @@ import { shouldCollectMetrics } from '../fixtures/fixtures.js';
4244
import { sendL1ToL2Message } from '../fixtures/l1_to_l2_messaging.js';
4345
import { ATTESTER_PRIVATE_KEYS_START_INDEX, createNodes, createProverNode } from '../fixtures/setup_p2p_test.js';
4446
import { setupSharedBlobStorage } from '../fixtures/utils.js';
45-
import { waitForL1ToL2MessageSeen } from '../shared/wait_for_l1_to_l2_message.js';
4647
import { TestWallet } from '../test-wallet/test_wallet.js';
4748
import { P2PNetworkTest, SHORTENED_BLOCK_TIME_CONFIG_NO_PRUNES } from './p2p_network.js';
4849

@@ -53,7 +54,7 @@ const BOOT_NODE_UDP_PORT = 4500;
5354
const DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), 'add-rollup-old-'));
5455
const DATA_DIR_NEW = fs.mkdtempSync(path.join(os.tmpdir(), 'add-rollup-new-'));
5556

56-
jest.setTimeout(1000 * 60 * 10);
57+
jest.setTimeout(1000 * 60 * 20);
5758

5859
/**
5960
* This test emulates the addition of a new rollup to the registry and tests that cross-chain messages work.
@@ -80,6 +81,14 @@ describe('e2e_p2p_add_rollup', () => {
8081
...SHORTENED_BLOCK_TIME_CONFIG_NO_PRUNES,
8182
listenAddress: '127.0.0.1',
8283
governanceProposerRoundSize: 10,
84+
enableProposerPipelining: true,
85+
// Allow validators to build empty checkpoints under pipelining so the chain keeps
86+
// advancing while we wait for L1->L2 messages to land in the next checkpoint's inbox tree.
87+
minTxsPerBlock: 0,
88+
// Pipelining starts cycle for checkpoint N+1 during slot N, but the inbox tree for
89+
// checkpoint N is only sealed when checkpoint N is published. inboxLag: 2 sources
90+
// L1->L2 messages from checkpoint N-1 (already sealed), avoiding L1ToL2MessagesNotReadyError.
91+
inboxLag: 2,
8392
},
8493
startProverNode: false, // Start one later using p2p.
8594
});
@@ -307,7 +316,10 @@ describe('e2e_p2p_add_rollup', () => {
307316
});
308317

309318
const makeMessageConsumable = async (msgHash: Fr) => {
310-
await waitForL1ToL2MessageSeen(node, msgHash, { timeoutSeconds: 10 });
319+
// Wait until the message is ready to be consumed (the rollup has reached the message's checkpoint).
320+
// Using waitForL1ToL2MessageReady rather than isL1ToL2MessageSynced because with `inboxLag > 0`
321+
// a synced message is not yet present in the latest checkpoint's inbox tree.
322+
await waitForL1ToL2MessageReady(node, msgHash, { timeoutSeconds: 120 });
311323

312324
const { receipt } = await testContract.methods
313325
.create_l2_to_l1_message_arbitrary_recipient_private(contentOutFromRollup, ethRecipient)
@@ -578,6 +590,21 @@ describe('e2e_p2p_add_rollup', () => {
578590
// The new rollup should have no checkpoints
579591
expect(await newRollup.getCheckpointNumber()).toBe(CheckpointNumber(0));
580592

593+
// Wait for the new rollup to publish its first checkpoint AND for `nodes[0]` to have synced
594+
// it locally, before the second bridging step. The bridge wallet uses
595+
// `syncChainTip: 'checkpointed'`, which falls back to the genesis block when no checkpoint
596+
// exists. After warping ~500 epochs forward, txs anchored at genesis would expire before
597+
// being included. We poll the node's local view (not just the L1 rollup contract) so the PXE
598+
// and the assertion observe the same chain state.
599+
t.logger.info(`Waiting for new rollup to publish its first checkpoint`);
600+
await retryUntil(
601+
async () => Number(await nodes[0].getCheckpointNumber('checkpointed')) > 0,
602+
'newRollup first checkpoint synced by node',
603+
300,
604+
2,
605+
);
606+
t.logger.info(`New rollup published its first checkpoint`);
607+
581608
// Bridge into and out of the new rollup to ensure that it works.
582609
await bridging(
583610
nodes[0],

yarn-project/end-to-end/src/e2e_p2p/data_withholding_slash.test.ts

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,8 +157,36 @@ describe('e2e_p2p_data_withholding_slash', () => {
157157
t.logger.warn('L2 txs mined');
158158

159159
t.logger.warn('Stopping nodes');
160+
// removeInitialNode sends a dummy L1 tx and awaits its receipt to sync the
161+
// dateProvider, so it must run while L1 mining is still active.
160162
await t.removeInitialNode();
161-
// Now stop the nodes,
163+
164+
// Pause L1 block production while we tear down and recreate validators. With
165+
// `aztecProofSubmissionEpochs=0`, epoch 8 becomes prunable as soon as epoch 9 begins
166+
// (~32s after slot 17). The stop/wipe/recreate cycle takes longer than that, so L1
167+
// would otherwise race past the prune deadline before the recreated nodes come up.
168+
// When that happens, the recreated archivers detect the prune during their initial
169+
// sync (`handleEpochPrune` emits `L2PruneUnproven`), but the `EpochPruneWatcher`
170+
// listener is only attached after `archiver.waitForInitialSync()` resolves
171+
// (see `aztec-node/server.ts`), so the event is dropped and `DATA_WITHHOLDING` is
172+
// never emitted. By freezing L1 here, the recreated archivers ingest checkpoint 1
173+
// cleanly during initial sync, the watcher starts and attaches its listener, and
174+
// then we resume L1 below so the prune fires while the listener is live.
175+
const ethCheatCodes = t.ctx.cheatCodes.eth;
176+
await ethCheatCodes.setAutomine(false);
177+
await ethCheatCodes.setIntervalMining(0);
178+
179+
// Fail fast if we paused too late — i.e. if L1 already crossed into epoch 9 before
180+
// we got here. In that case the recreated nodes would still see the prune during
181+
// initial sync and the test would flake exactly the same way.
182+
const epochAtPause = await rollup.getCurrentEpoch();
183+
expect(Number(epochAtPause)).toBeLessThan(9);
184+
185+
// Now stop the validator nodes. With L1 paused, any in-flight L1 submissions from
186+
// the validator sequencers would hang `sequencer.stop()` (it awaits pending L1
187+
// submissions). Since `minTxsPerBlock=1` and no txs are queued for slot 18+, the
188+
// sequencers don't submit further L1 transactions after the slot-17 checkpoint
189+
// (already published before `waitForTx` returned), so this is safe.
162190
await t.stopNodes(nodes);
163191
// And remove the data directories (which forms the crux of the "attack")
164192
for (let i = 0; i < NUM_VALIDATORS; i++) {
@@ -186,6 +214,16 @@ describe('e2e_p2p_data_withholding_slash', () => {
186214
// Wait for P2P mesh to be fully formed before proceeding
187215
await t.waitForP2PMeshConnectivity(nodes, NUM_VALIDATORS);
188216

217+
// Resume L1 block production. Warp L1 forward to current wall-clock time so the
218+
// epoch-8 deadline is crossed immediately on the next L1 block, then re-enable
219+
// interval mining. By now each recreated archiver has block 1 stored locally and
220+
// its `EpochPruneWatcher` listener is attached, so the next sync iteration emits
221+
// `L2PruneUnproven` for epoch 8 to a live listener → `DATA_WITHHOLDING`.
222+
const resumeTimestamp = Math.floor(t.ctx.dateProvider.now() / 1000);
223+
await ethCheatCodes.setNextBlockTimestamp(resumeTimestamp);
224+
await ethCheatCodes.mine();
225+
await ethCheatCodes.setIntervalMining(t.ctx.aztecNodeConfig.ethereumSlotDuration);
226+
189227
const offenses = await awaitOffenseDetected({
190228
epochDuration: t.ctx.aztecNodeConfig.aztecEpochDuration,
191229
logger: t.logger,

yarn-project/prover-client/src/light/lightweight_checkpoint_builder.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,14 @@ export class LightweightCheckpointBuilder {
290290
totalManaUsed,
291291
});
292292

293+
this.logger.debug(`Completed checkpoint ${this.checkpointNumber}`, {
294+
checkpointNumber: this.checkpointNumber,
295+
headerHash: header.hash().toString(),
296+
checkpointOutHash: checkpointOutHash.toString(),
297+
numPreviousCheckpointOutHashes: this.previousCheckpointOutHashes.length,
298+
...header.toInspect(),
299+
});
300+
293301
return new Checkpoint(newArchive, header, blocks, this.checkpointNumber, this.feeAssetPriceModifier);
294302
}
295303

yarn-project/prover-client/src/orchestrator/orchestrator.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -896,7 +896,11 @@ export class ProvingOrchestrator implements EpochProver {
896896
},
897897
),
898898
async result => {
899-
this.logger.debug(`Completed ${rollupType} proof for block ${provingState.blockNumber}`);
899+
this.logger.debug(`Completed ${rollupType} proof for block ${provingState.blockNumber}`, {
900+
blockNumber: provingState.blockNumber,
901+
checkpointIndex: provingState.parentCheckpoint.index,
902+
...result.inputs.toInspect(),
903+
});
900904

901905
const leafLocation = provingState.setBlockRootRollupProof(result);
902906
const checkpointProvingState = provingState.parentCheckpoint;
@@ -1015,6 +1019,11 @@ export class ProvingOrchestrator implements EpochProver {
10151019
signal => this.prover.getBlockMergeRollupProof(inputs, signal, provingState.epochNumber),
10161020
),
10171021
async result => {
1022+
this.logger.debug(`Completed block merge rollup proof for checkpoint ${provingState.index}`, {
1023+
checkpointIndex: provingState.index,
1024+
mergeLocation: location,
1025+
...result.inputs.toInspect(),
1026+
});
10181027
provingState.setBlockMergeRollupProof(location, result);
10191028
await this.checkAndEnqueueNextBlockMergeRollup(provingState, location);
10201029
},
@@ -1067,7 +1076,10 @@ export class ProvingOrchestrator implements EpochProver {
10671076
return;
10681077
}
10691078

1070-
this.logger.debug(`Completed ${rollupType} proof for checkpoint ${provingState.index}.`);
1079+
this.logger.debug(`Completed ${rollupType} proof for checkpoint ${provingState.index}`, {
1080+
checkpointIndex: provingState.index,
1081+
...result.inputs.toInspect(),
1082+
});
10711083

10721084
const leafLocation = provingState.setCheckpointRootRollupProof(result);
10731085
const epochProvingState = provingState.parentEpoch;

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,11 +191,12 @@ export class EpochProvingJob implements Traceable {
191191
const previousHeader = previousBlockHeaders[checkpointIndex];
192192
const l1ToL2Messages = this.getL1ToL2Messages(checkpoint);
193193

194-
this.log.verbose(`Starting processing checkpoint ${checkpoint.number}`, {
194+
this.log.debug(`Starting processing checkpoint ${checkpoint.number}`, {
195195
number: checkpoint.number,
196196
checkpointHash: checkpoint.hash().toString(),
197-
lastArchive: checkpoint.header.lastArchiveRoot,
198-
previousHeader: previousHeader.hash(),
197+
headerHash: checkpoint.header.hash().toString(),
198+
numL1ToL2Messages: l1ToL2Messages.length,
199+
previousBlockNumber: previousHeader.globalVariables.blockNumber,
199200
uuid: this.uuid,
200201
});
201202

yarn-project/prover-node/src/prover-node-publisher.ts

Lines changed: 105 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,14 @@ export class ProverNodePublisher {
203203
const argsPublicInputs = [...publicInputs.toFields()];
204204

205205
if (!areArraysEqual(rollupPublicInputs, argsPublicInputs, (a, b) => a.equals(b))) {
206-
const fmt = (inputs: Fr[] | readonly string[]) => inputs.map(x => x.toString()).join(', ');
207-
throw new Error(
208-
`Root rollup public inputs mismatch:\nRollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
209-
);
206+
throw await reportPublicInputsMismatch({
207+
rollupPublicInputs,
208+
argsPublicInputs,
209+
fromCheckpoint,
210+
toCheckpoint,
211+
rollupContract: this.rollupContract,
212+
log: this.log,
213+
});
210214
}
211215
}
212216

@@ -372,3 +376,100 @@ export class ProverNodePublisher {
372376
};
373377
}
374378
}
379+
380+
/**
381+
* Decodes a `Root rollup public inputs mismatch`, fetches the on-chain CheckpointLog for any
382+
* mismatching `checkpointHeaderHashes[i]`, emits a structured error log, and returns a thrown-ready
383+
* Error with a human-readable summary.
384+
*
385+
* Layout of `RootRollupPublicInputs.toFields()`:
386+
* [0] previousArchiveRoot
387+
* [1] endArchiveRoot
388+
* [2] outHash
389+
* [3 .. 3+N-1] checkpointHeaderHashes[i] for i in 0..N-1 (N = MAX_CHECKPOINTS_PER_EPOCH)
390+
* [3+N .. 3+3N-1] fees[i] = (recipient, value) for i in 0..N-1
391+
* [3+3N .. 3+3N+4] EpochConstantData (chainId, version, vkTreeRoot, protocolContractsHash, proverId)
392+
* [3+3N+5 ..] blobPublicInputs (FinalBlobAccumulator)
393+
*/
394+
async function reportPublicInputsMismatch(input: {
395+
rollupPublicInputs: readonly Fr[];
396+
argsPublicInputs: readonly Fr[];
397+
fromCheckpoint: CheckpointNumber;
398+
toCheckpoint: CheckpointNumber;
399+
rollupContract: RollupContract;
400+
log: Logger;
401+
}): Promise<Error> {
402+
const { rollupPublicInputs, argsPublicInputs, fromCheckpoint, toCheckpoint, rollupContract, log } = input;
403+
const N = MAX_CHECKPOINTS_PER_EPOCH;
404+
const constantsStart = 3 + 3 * N;
405+
const blobStart = constantsStart + 5;
406+
const constantLabels = ['chainId', 'version', 'vkTreeRoot', 'protocolContractsHash', 'proverId'];
407+
408+
const diffs: { index: number; label: string; rollup: Fr; computed: Fr; checkpointIndex?: number }[] = [];
409+
const len = Math.max(rollupPublicInputs.length, argsPublicInputs.length);
410+
for (let i = 0; i < len; i++) {
411+
const a = rollupPublicInputs[i] ?? Fr.ZERO;
412+
const b = argsPublicInputs[i] ?? Fr.ZERO;
413+
if (a.equals(b)) {
414+
continue;
415+
}
416+
let label: string;
417+
let checkpointIndex: number | undefined;
418+
if (i === 0) {
419+
label = 'previousArchiveRoot';
420+
} else if (i === 1) {
421+
label = 'endArchiveRoot';
422+
} else if (i === 2) {
423+
label = 'outHash';
424+
} else if (i < 3 + N) {
425+
checkpointIndex = i - 3;
426+
label = `checkpointHeaderHashes[${checkpointIndex}]`;
427+
} else if (i < 3 + 3 * N) {
428+
const feePairIndex = i - (3 + N);
429+
const feeIndex = Math.floor(feePairIndex / 2);
430+
const sub = feePairIndex % 2 === 0 ? 'recipient' : 'value';
431+
label = `fees[${feeIndex}].${sub}`;
432+
} else if (i < blobStart) {
433+
label = `constants.${constantLabels[i - constantsStart]}`;
434+
} else {
435+
label = `blobPublicInputs[${i - blobStart}]`;
436+
}
437+
diffs.push({ index: i, label, rollup: a, computed: b, checkpointIndex });
438+
}
439+
440+
// For each mismatching checkpointHeaderHash, fetch the L1 CheckpointLog so the operator can
441+
// see what was published on-chain alongside the prover's recomputed hash.
442+
const onChainCheckpoints = await Promise.all(
443+
diffs
444+
.filter(d => d.checkpointIndex !== undefined)
445+
.map(async d => {
446+
const checkpointNumber = CheckpointNumber(fromCheckpoint + d.checkpointIndex!);
447+
try {
448+
const cp = await rollupContract.getCheckpoint(checkpointNumber);
449+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, headerHash: cp.headerHash.toString() };
450+
} catch (err) {
451+
return { checkpointIndex: d.checkpointIndex!, checkpointNumber, error: (err as Error).message };
452+
}
453+
}),
454+
);
455+
456+
log.error(`Root rollup public inputs mismatch`, undefined, {
457+
fromCheckpoint,
458+
toCheckpoint,
459+
numDiffs: diffs.length,
460+
diffs: diffs.map(d => ({
461+
index: d.index,
462+
label: d.label,
463+
rollup: d.rollup.toString(),
464+
computed: d.computed.toString(),
465+
})),
466+
onChainCheckpoints,
467+
});
468+
469+
const fmt = (inputs: readonly Fr[]) => inputs.map(x => x.toString()).join(', ');
470+
const summary = diffs.map(d => `[${d.index} ${d.label}] L1=${d.rollup} prover=${d.computed}`).join('\n');
471+
return new Error(
472+
`Root rollup public inputs mismatch (${diffs.length} fields differ):\n${summary}\n` +
473+
`Rollup: ${fmt(rollupPublicInputs)}\nComputed:${fmt(argsPublicInputs)}`,
474+
);
475+
}

0 commit comments

Comments
 (0)