@aztec/end-to-end 5.0.0-rc.1 → 5.0.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dest/bench/client_flows/client_flows_benchmark.d.ts +4 -4
- package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
- package/dest/bench/client_flows/client_flows_benchmark.js +9 -6
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +3 -2
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
- package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +8 -6
- package/dest/e2e_epochs/epochs_test.d.ts +1 -1
- package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
- package/dest/e2e_epochs/epochs_test.js +2 -2
- package/dest/e2e_fees/bridging_race.notest.js +10 -0
- package/dest/e2e_fees/fees_test.d.ts +1 -1
- package/dest/e2e_fees/fees_test.d.ts.map +1 -1
- package/dest/e2e_fees/fees_test.js +4 -2
- package/dest/e2e_l1_publisher/write_json.d.ts +1 -1
- package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
- package/dest/e2e_l1_publisher/write_json.js +2 -1
- package/dest/e2e_p2p/p2p_network.d.ts +2 -2
- package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
- package/dest/e2e_p2p/p2p_network.js +10 -8
- package/dest/e2e_p2p/shared.d.ts +26 -1
- package/dest/e2e_p2p/shared.d.ts.map +1 -1
- package/dest/e2e_p2p/shared.js +40 -0
- package/dest/fixtures/e2e_prover_test.d.ts +1 -1
- package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
- package/dest/fixtures/e2e_prover_test.js +3 -3
- package/dest/fixtures/fixtures.d.ts +2 -1
- package/dest/fixtures/fixtures.d.ts.map +1 -1
- package/dest/fixtures/fixtures.js +6 -0
- package/dest/fixtures/setup.d.ts +4 -2
- package/dest/fixtures/setup.d.ts.map +1 -1
- package/dest/fixtures/setup.js +34 -6
- package/dest/fixtures/setup_p2p_test.d.ts +1 -1
- package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
- package/dest/fixtures/setup_p2p_test.js +3 -3
- package/dest/fixtures/token_utils.d.ts +22 -4
- package/dest/fixtures/token_utils.d.ts.map +1 -1
- package/dest/fixtures/token_utils.js +21 -1
- package/dest/shared/timing_env.js +254 -0
- package/dest/simulators/lending_simulator.js +1 -1
- package/dest/spartan/setup_test_wallets.d.ts +1 -1
- package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
- package/dest/spartan/setup_test_wallets.js +5 -1
- package/package.json +41 -41
- package/src/bench/client_flows/client_flows_benchmark.ts +22 -8
- package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +14 -5
- package/src/e2e_epochs/epochs_test.ts +2 -2
- package/src/e2e_fees/bridging_race.notest.ts +10 -0
- package/src/e2e_fees/fees_test.ts +10 -2
- package/src/e2e_l1_publisher/write_json.ts +1 -0
- package/src/e2e_p2p/p2p_network.ts +10 -7
- package/src/e2e_p2p/shared.ts +59 -0
- package/src/fixtures/e2e_prover_test.ts +4 -3
- package/src/fixtures/fixtures.ts +8 -0
- package/src/fixtures/setup.ts +40 -10
- package/src/fixtures/setup_p2p_test.ts +3 -3
- package/src/fixtures/token_utils.ts +39 -4
- package/src/guides/up_quick_start.sh +5 -0
- package/src/shared/timing_env.mjs +259 -0
- package/src/simulators/lending_simulator.ts +1 -1
- package/src/spartan/setup_test_wallets.ts +4 -1
|
@@ -431,6 +431,7 @@ export class P2PNetworkTest {
|
|
|
431
431
|
timeoutSeconds = 30,
|
|
432
432
|
checkIntervalSeconds = 0.1,
|
|
433
433
|
topics: TopicType[] = [TopicType.tx],
|
|
434
|
+
minMeshPeerCount = 1,
|
|
434
435
|
) {
|
|
435
436
|
const nodeCount = expectedNodeCount ?? nodes.length;
|
|
436
437
|
const minPeerCount = nodeCount - 1;
|
|
@@ -457,11 +458,13 @@ export class P2PNetworkTest {
|
|
|
457
458
|
|
|
458
459
|
this.logger.warn('All nodes connected to P2P mesh');
|
|
459
460
|
|
|
460
|
-
// Wait for GossipSub mesh to form for all specified topics.
|
|
461
|
-
//
|
|
462
|
-
//
|
|
461
|
+
// Wait for the GossipSub mesh to form for all specified topics. By default we only require at
|
|
462
|
+
// least 1 mesh peer per node, since GossipSub stops grafting once it reaches Dlo peers and won't
|
|
463
|
+
// fill the mesh to every available peer. Callers that need a proposal to reach the whole
|
|
464
|
+
// committee within a slot (e.g. quorum-from-genesis tests) raise `minMeshPeerCount` so the mesh
|
|
465
|
+
// is fully formed — a single mesh peer can leave some committee members unreached at first.
|
|
463
466
|
for (const topic of topics) {
|
|
464
|
-
this.logger.warn(`Waiting for GossipSub mesh
|
|
467
|
+
this.logger.warn(`Waiting for GossipSub mesh (>= ${minMeshPeerCount} peers per node) for ${topic} topic...`);
|
|
465
468
|
await Promise.all(
|
|
466
469
|
nodes.map(async (node, index) => {
|
|
467
470
|
const p2p = node.getP2P();
|
|
@@ -469,15 +472,15 @@ export class P2PNetworkTest {
|
|
|
469
472
|
async () => {
|
|
470
473
|
const meshPeers = await p2p.getGossipMeshPeerCount(topic);
|
|
471
474
|
this.logger.debug(`Node ${index} has ${meshPeers} gossip mesh peers for ${topic} topic`);
|
|
472
|
-
return meshPeers >=
|
|
475
|
+
return meshPeers >= minMeshPeerCount ? true : undefined;
|
|
473
476
|
},
|
|
474
|
-
`Node ${index} to have gossip mesh peers for ${topic} topic`,
|
|
477
|
+
`Node ${index} to have >= ${minMeshPeerCount} gossip mesh peers for ${topic} topic`,
|
|
475
478
|
timeoutSeconds,
|
|
476
479
|
checkIntervalSeconds,
|
|
477
480
|
);
|
|
478
481
|
}),
|
|
479
482
|
);
|
|
480
|
-
this.logger.warn(`All nodes have gossip mesh peers for ${topic} topic`);
|
|
483
|
+
this.logger.warn(`All nodes have >= ${minMeshPeerCount} gossip mesh peers for ${topic} topic`);
|
|
481
484
|
}
|
|
482
485
|
}
|
|
483
486
|
|
package/src/e2e_p2p/shared.ts
CHANGED
|
@@ -147,6 +147,65 @@ export async function awaitCommitteeExists({
|
|
|
147
147
|
return committee!.map(c => c.toString() as `0x${string}`);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
/**
|
|
151
|
+
* Scans L2 slots forward from `minLeadSlots` ahead of the current slot, returning the first slot in
|
|
152
|
+
* which `targetProposer` is the proposer.
|
|
153
|
+
*
|
|
154
|
+
* Scanning starts at `currentSlot + minLeadSlots` and only ever moves forward, so every returned slot
|
|
155
|
+
* is at least `minLeadSlots` ahead — a caller can safely warp to `targetSlot - minLeadSlots` for a
|
|
156
|
+
* settle buffer without risking a backwards warp. Stepping by a single slot examines both epoch
|
|
157
|
+
* parities, which matters because the per-slot proposer is a different RANDAO-shuffled committee
|
|
158
|
+
* member: searching only a fixed offset within each epoch can leave a 1-of-N target unexamined when
|
|
159
|
+
* the epoch is short. A candidate in an epoch whose committee isn't sampled yet makes the proposer
|
|
160
|
+
* lookup revert with EpochNotStable; this warps one epoch forward and continues, keeping the
|
|
161
|
+
* candidate at least `minLeadSlots` ahead of the new current slot. Throws after `maxSlotsToScan`.
|
|
162
|
+
*
|
|
163
|
+
* Unlike {@link advanceToEpochBeforeProposer}, this does not stop an epoch early — callers that want
|
|
164
|
+
* to warp close to the target (rather than stage sequencers an epoch ahead) use this and warp the
|
|
165
|
+
* final `minLeadSlots` in themselves.
|
|
166
|
+
*/
|
|
167
|
+
export async function findUpcomingProposerSlot({
|
|
168
|
+
epochCache,
|
|
169
|
+
cheatCodes,
|
|
170
|
+
targetProposer,
|
|
171
|
+
logger,
|
|
172
|
+
minLeadSlots,
|
|
173
|
+
maxSlotsToScan = 100,
|
|
174
|
+
}: {
|
|
175
|
+
epochCache: EpochCacheInterface;
|
|
176
|
+
cheatCodes: RollupCheatCodes;
|
|
177
|
+
targetProposer: EthAddress;
|
|
178
|
+
logger: Logger;
|
|
179
|
+
minLeadSlots: number;
|
|
180
|
+
maxSlotsToScan?: number;
|
|
181
|
+
}): Promise<SlotNumber> {
|
|
182
|
+
let candidate = Number(await cheatCodes.getSlot()) + minLeadSlots;
|
|
183
|
+
|
|
184
|
+
for (let scanned = 0; scanned < maxSlotsToScan; scanned++) {
|
|
185
|
+
let proposer: EthAddress | undefined;
|
|
186
|
+
try {
|
|
187
|
+
proposer = await epochCache.getProposerAttesterAddressInSlot(SlotNumber(candidate));
|
|
188
|
+
} catch (err) {
|
|
189
|
+
if (!(err instanceof Error) || !err.message.includes('EpochNotStable')) {
|
|
190
|
+
throw err;
|
|
191
|
+
}
|
|
192
|
+
await cheatCodes.advanceToNextEpoch();
|
|
193
|
+
const newCurrentSlot = Number(await cheatCodes.getSlot());
|
|
194
|
+
// Keep the lead after the warp: never return a slot we could no longer warp ahead of.
|
|
195
|
+
candidate = Math.max(candidate, newCurrentSlot + minLeadSlots);
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (proposer && proposer.equals(targetProposer)) {
|
|
200
|
+
logger.warn(`Found target proposer ${targetProposer} at slot ${candidate}`);
|
|
201
|
+
return SlotNumber(candidate);
|
|
202
|
+
}
|
|
203
|
+
candidate++;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
throw new Error(`Target proposer ${targetProposer} not found within ${maxSlotsToScan} slots`);
|
|
207
|
+
}
|
|
208
|
+
|
|
150
209
|
/**
|
|
151
210
|
* Advance epochs until we find one where the target proposer is selected for a slot at least
|
|
152
211
|
* `warmupSlots` into the epoch, then stop one epoch before it. This leaves time for the caller to
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing';
|
|
2
|
-
import { AztecNodeService } from '@aztec/aztec-node';
|
|
2
|
+
import { AztecNodeService, createAztecNodeService } from '@aztec/aztec-node';
|
|
3
3
|
import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses';
|
|
4
4
|
import { type Logger, createLogger } from '@aztec/aztec.js/log';
|
|
5
5
|
import type { AztecNode } from '@aztec/aztec.js/node';
|
|
@@ -177,6 +177,7 @@ export class FullProverTest {
|
|
|
177
177
|
this.logger.verbose(`Main setup completed, initializing full prover PXE, Node, and Prover Node`);
|
|
178
178
|
const { wallet: provenWallet, teardown: provenTeardown } = await setupPXEAndGetWallet(
|
|
179
179
|
this.aztecNode,
|
|
180
|
+
this.context.aztecNode,
|
|
180
181
|
{ proverEnabled: this.realProofs },
|
|
181
182
|
undefined,
|
|
182
183
|
'pxe-proven',
|
|
@@ -225,7 +226,7 @@ export class FullProverTest {
|
|
|
225
226
|
this.context.genesis!.genesisTimestamp,
|
|
226
227
|
);
|
|
227
228
|
|
|
228
|
-
const proverNodeConfig: Parameters<typeof
|
|
229
|
+
const proverNodeConfig: Parameters<typeof createAztecNodeService>[0] = {
|
|
229
230
|
...config,
|
|
230
231
|
enableProverNode: true,
|
|
231
232
|
disableValidator: true,
|
|
@@ -250,7 +251,7 @@ export class FullProverTest {
|
|
|
250
251
|
validatorPrivateKeys: new SecretValue([]),
|
|
251
252
|
};
|
|
252
253
|
|
|
253
|
-
this.proverAztecNode = await
|
|
254
|
+
this.proverAztecNode = await createAztecNodeService(
|
|
254
255
|
proverNodeConfig,
|
|
255
256
|
{ dateProvider: this.context.dateProvider, p2pClientDeps: { rpcTxProviders: [this.aztecNode] } },
|
|
256
257
|
{ genesis },
|
package/src/fixtures/fixtures.ts
CHANGED
|
@@ -109,6 +109,14 @@ export const TEST_PEER_CHECK_INTERVAL_MS = 1000;
|
|
|
109
109
|
export const TEST_MAX_PENDING_TX_POOL_COUNT = 10_000; // Number of max pending TXs ~ 1.56GB
|
|
110
110
|
|
|
111
111
|
export const MNEMONIC = 'test test test test test test test test test test test junk';
|
|
112
|
+
|
|
113
|
+
// Mnemonic account index for tests that issue direct L1 writes (e.g. bridging fee juice) while a
|
|
114
|
+
// sequencer is running. The deployer/sequencer publisher uses index 0, the prover index 2, and
|
|
115
|
+
// validators index 3+. Test-side viem writes and the publisher's l1-tx-utils track nonces
|
|
116
|
+
// independently, so sharing an account causes "replacement transaction underpriced" races; index 1
|
|
117
|
+
// is otherwise unused, so issuing those writes from it keeps them off the publisher's nonce stream.
|
|
118
|
+
export const L1_DIRECT_WRITE_ACCOUNT_INDEX = 1;
|
|
119
|
+
|
|
112
120
|
export const privateKey = Buffer.from('ac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', 'hex');
|
|
113
121
|
export const privateKey2 = Buffer.from('59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', 'hex');
|
|
114
122
|
|
package/src/fixtures/setup.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type InitialAccountData, generateSchnorrAccounts } from '@aztec/accounts/testing';
|
|
2
|
-
import { type AztecNodeConfig, AztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
|
|
2
|
+
import { type AztecNodeConfig, AztecNodeService, createAztecNodeService, getConfigEnvVars } from '@aztec/aztec-node';
|
|
3
3
|
import { AztecAddress, EthAddress } from '@aztec/aztec.js/addresses';
|
|
4
4
|
import type { ContractMethod } from '@aztec/aztec.js/contracts';
|
|
5
5
|
import { publishContractClass, publishInstance } from '@aztec/aztec.js/deployment';
|
|
@@ -109,6 +109,8 @@ export async function setupSharedBlobStorage(config: { dataDirectory?: string }
|
|
|
109
109
|
/**
|
|
110
110
|
* Sets up Private eXecution Environment (PXE) and returns the corresponding test wallet.
|
|
111
111
|
* @param aztecNode - An instance of Aztec Node.
|
|
112
|
+
* @param nodeDebug - The node's debug API, used to register public function signatures for named traces; pass
|
|
113
|
+
* `undefined` when the node does not expose it.
|
|
112
114
|
* @param opts - Partial configuration for the PXE.
|
|
113
115
|
* @param logger - The logger to be used.
|
|
114
116
|
* @param actor - Actor label to include in log output (e.g., 'pxe-test').
|
|
@@ -116,6 +118,7 @@ export async function setupSharedBlobStorage(config: { dataDirectory?: string }
|
|
|
116
118
|
*/
|
|
117
119
|
export async function setupPXEAndGetWallet(
|
|
118
120
|
aztecNode: AztecNode,
|
|
121
|
+
nodeDebug: AztecNodeDebug | undefined,
|
|
119
122
|
opts: Partial<PXEConfig> = {},
|
|
120
123
|
logger = getLogger(),
|
|
121
124
|
actor?: string,
|
|
@@ -136,7 +139,10 @@ export async function setupPXEAndGetWallet(
|
|
|
136
139
|
|
|
137
140
|
const teardown = configuredDataDirectory ? () => Promise.resolve() : () => tryRmDir(PXEConfig.dataDirectory!);
|
|
138
141
|
|
|
139
|
-
const wallet = await TestWallet.create(aztecNode, PXEConfig, {
|
|
142
|
+
const wallet = await TestWallet.create(aztecNode, PXEConfig, {
|
|
143
|
+
loggerActorLabel: actor,
|
|
144
|
+
nodeDebug,
|
|
145
|
+
});
|
|
140
146
|
|
|
141
147
|
return {
|
|
142
148
|
wallet,
|
|
@@ -294,6 +300,17 @@ function assertContractArtifactsVersion() {
|
|
|
294
300
|
}
|
|
295
301
|
}
|
|
296
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Records a function-level timing span into the shared collector installed by the e2e timing
|
|
305
|
+
* environment. No-op unless TEST_TIMING_FILE is set (the env only installs the collector then). The
|
|
306
|
+
* span is tagged with the name of the currently running test so the env can attribute it to the
|
|
307
|
+
* right line; `null` during beforeAll/afterAll lands it on the suite-scoped line.
|
|
308
|
+
*/
|
|
309
|
+
function recordFnSpan(kind: 'setup' | 'teardown', ms: number) {
|
|
310
|
+
const collector = (globalThis as { __e2eTimings?: { current: string | null; fnSpans: unknown[] } }).__e2eTimings;
|
|
311
|
+
collector?.fnSpans.push({ name: collector.current, kind, ms });
|
|
312
|
+
}
|
|
313
|
+
|
|
297
314
|
/**
|
|
298
315
|
* Sets up the environment for the end-to-end tests.
|
|
299
316
|
* @param numberOfAccounts - The number of new accounts to be created once the PXE is initiated.
|
|
@@ -305,6 +322,20 @@ export async function setup(
|
|
|
305
322
|
opts: SetupOptions = {},
|
|
306
323
|
pxeOpts: Partial<PXEConfig> = {},
|
|
307
324
|
chain: Chain = foundry,
|
|
325
|
+
): Promise<EndToEndContext> {
|
|
326
|
+
const setupStart = performance.now();
|
|
327
|
+
try {
|
|
328
|
+
return await setupInner(numberOfAccounts, opts, pxeOpts, chain);
|
|
329
|
+
} finally {
|
|
330
|
+
recordFnSpan('setup', performance.now() - setupStart);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async function setupInner(
|
|
335
|
+
numberOfAccounts: number,
|
|
336
|
+
opts: SetupOptions,
|
|
337
|
+
pxeOpts: Partial<PXEConfig>,
|
|
338
|
+
chain: Chain,
|
|
308
339
|
): Promise<EndToEndContext> {
|
|
309
340
|
assertContractArtifactsVersion();
|
|
310
341
|
let anvil: Anvil | undefined;
|
|
@@ -556,7 +587,7 @@ export async function setup(
|
|
|
556
587
|
: config;
|
|
557
588
|
|
|
558
589
|
const aztecNodeService = await withLoggerBindings({ actor: 'node-0' }, () =>
|
|
559
|
-
|
|
590
|
+
createAztecNodeService(
|
|
560
591
|
initialNodeConfig,
|
|
561
592
|
{ dateProvider, telemetry: telemetryClient, p2pClientDeps },
|
|
562
593
|
{ genesis, dontStartSequencer: opts.skipInitialSequencer },
|
|
@@ -598,6 +629,8 @@ export async function setup(
|
|
|
598
629
|
pxeConfig.proverEnabled = !!pxeOpts.proverEnabled;
|
|
599
630
|
const wallet = await TestWallet.create(aztecNodeService, pxeConfig, {
|
|
600
631
|
loggerActorLabel: 'pxe-0',
|
|
632
|
+
// In-process node implements the debug API, so register public function signatures for named traces.
|
|
633
|
+
nodeDebug: aztecNodeService,
|
|
601
634
|
...opts.pxeCreationOptions,
|
|
602
635
|
});
|
|
603
636
|
|
|
@@ -605,12 +638,7 @@ export async function setup(
|
|
|
605
638
|
wallet.setMinFeePadding(opts.walletMinFeePadding);
|
|
606
639
|
}
|
|
607
640
|
|
|
608
|
-
const cheatCodes = await CheatCodes.create(
|
|
609
|
-
config.l1RpcUrls,
|
|
610
|
-
aztecNodeService,
|
|
611
|
-
dateProvider,
|
|
612
|
-
aztecNodeService.getAutomineSequencer(),
|
|
613
|
-
);
|
|
641
|
+
const cheatCodes = await CheatCodes.create(config.l1RpcUrls, aztecNodeService, dateProvider);
|
|
614
642
|
|
|
615
643
|
if (
|
|
616
644
|
(opts.aztecTargetCommitteeSize && opts.aztecTargetCommitteeSize > 0) ||
|
|
@@ -664,6 +692,7 @@ export async function setup(
|
|
|
664
692
|
}
|
|
665
693
|
|
|
666
694
|
const teardown = async () => {
|
|
695
|
+
const teardownStart = performance.now();
|
|
667
696
|
try {
|
|
668
697
|
await tryStop(wallet, logger);
|
|
669
698
|
await tryStop(aztecNodeService, logger);
|
|
@@ -688,6 +717,7 @@ export async function setup(
|
|
|
688
717
|
} catch (err) {
|
|
689
718
|
logger.error(`Error during telemetry client stop`, err);
|
|
690
719
|
}
|
|
720
|
+
recordFnSpan('teardown', performance.now() - teardownStart);
|
|
691
721
|
}
|
|
692
722
|
};
|
|
693
723
|
|
|
@@ -807,7 +837,7 @@ export function createAndSyncProverNode(
|
|
|
807
837
|
options: { genesis?: GenesisData; dontStart?: boolean },
|
|
808
838
|
): Promise<{ proverNode: AztecNodeService }> {
|
|
809
839
|
return withLoggerBindings({ actor: 'prover-0' }, async () => {
|
|
810
|
-
const proverNode = await
|
|
840
|
+
const proverNode = await createAztecNodeService(
|
|
811
841
|
{
|
|
812
842
|
...baseConfig,
|
|
813
843
|
...configOverrides,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Test fixtures and utilities to set up and run a test using multiple validators
|
|
3
3
|
*/
|
|
4
|
-
import { type AztecNodeConfig, AztecNodeService } from '@aztec/aztec-node';
|
|
4
|
+
import { type AztecNodeConfig, AztecNodeService, createAztecNodeService } from '@aztec/aztec-node';
|
|
5
5
|
import { range } from '@aztec/foundation/array';
|
|
6
6
|
import { SecretValue } from '@aztec/foundation/config';
|
|
7
7
|
import { withLoggerBindings } from '@aztec/foundation/log/server';
|
|
@@ -109,7 +109,7 @@ export async function createNode(
|
|
|
109
109
|
return await withLoggerBindings({ actor: `validator-${actorIndex}` }, async () => {
|
|
110
110
|
const validatorConfig = await createValidatorConfig(config, bootstrapNode, tcpPort, addressIndex, dataDirectory);
|
|
111
111
|
const telemetry = await getEndToEndTestTelemetryClient(metricsPort);
|
|
112
|
-
return await
|
|
112
|
+
return await createAztecNodeService(
|
|
113
113
|
validatorConfig,
|
|
114
114
|
{ telemetry, dateProvider },
|
|
115
115
|
{ genesis, dontStartSequencer: config.dontStartSequencer },
|
|
@@ -137,7 +137,7 @@ export async function createNonValidatorNode(
|
|
|
137
137
|
sequencerPublisherPrivateKeys: [],
|
|
138
138
|
};
|
|
139
139
|
const telemetry = await getEndToEndTestTelemetryClient(metricsPort);
|
|
140
|
-
return await
|
|
140
|
+
return await createAztecNodeService(config, { telemetry, dateProvider }, { genesis });
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
143
|
|
|
@@ -3,6 +3,15 @@ import { BatchCall } from '@aztec/aztec.js/contracts';
|
|
|
3
3
|
import type { Logger } from '@aztec/aztec.js/log';
|
|
4
4
|
import type { Wallet } from '@aztec/aztec.js/wallet';
|
|
5
5
|
import { TokenContract } from '@aztec/noir-contracts.js/Token';
|
|
6
|
+
import { TestTokenContract } from '@aztec/noir-test-contracts.js/TestToken';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Either token flavour. Canonical `TokenContract` uses constrained message delivery (production / docs
|
|
10
|
+
* source of truth); `TestTokenContract` is its codegen'd unconstrained-delivery sibling, used by tests
|
|
11
|
+
* where a token is just a unit-of-account vehicle. The two share an identical ABI, so helpers that only
|
|
12
|
+
* read balances or mint accept either.
|
|
13
|
+
*/
|
|
14
|
+
export type AnyTokenContract = TokenContract | TestTokenContract;
|
|
6
15
|
|
|
7
16
|
export async function deployToken(wallet: Wallet, admin: AztecAddress, initialAdminBalance: bigint, logger: Logger) {
|
|
8
17
|
logger.info(`Deploying Token contract...`);
|
|
@@ -19,8 +28,34 @@ export async function deployToken(wallet: Wallet, admin: AztecAddress, initialAd
|
|
|
19
28
|
return { contract, instance };
|
|
20
29
|
}
|
|
21
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Deploys the unconstrained-delivery `TestTokenContract`. Use this in tests where the token is a
|
|
33
|
+
* unit-of-account vehicle rather than the subject, so they don't pay constrained delivery's first-send
|
|
34
|
+
* handshake cost (which distorts step/log counts that benches assert on). Use {@link deployToken} when a
|
|
35
|
+
* test exercises canonical Token semantics or note discovery.
|
|
36
|
+
*/
|
|
37
|
+
export async function deployTestToken(
|
|
38
|
+
wallet: Wallet,
|
|
39
|
+
admin: AztecAddress,
|
|
40
|
+
initialAdminBalance: bigint,
|
|
41
|
+
logger: Logger,
|
|
42
|
+
) {
|
|
43
|
+
logger.info(`Deploying TestToken contract...`);
|
|
44
|
+
const { contract, instance } = await TestTokenContract.deploy(wallet, admin, 'TokenName', 'TokenSymbol', 18).send({
|
|
45
|
+
from: admin,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
if (initialAdminBalance > 0n) {
|
|
49
|
+
await mintTokensToPrivate(contract, admin, admin, initialAdminBalance);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
logger.info('L2 contract deployed');
|
|
53
|
+
|
|
54
|
+
return { contract, instance };
|
|
55
|
+
}
|
|
56
|
+
|
|
22
57
|
export async function mintTokensToPrivate(
|
|
23
|
-
token:
|
|
58
|
+
token: AnyTokenContract,
|
|
24
59
|
minter: AztecAddress,
|
|
25
60
|
recipient: AztecAddress,
|
|
26
61
|
amount: bigint,
|
|
@@ -31,13 +66,13 @@ export async function mintTokensToPrivate(
|
|
|
31
66
|
|
|
32
67
|
export async function expectTokenBalance(
|
|
33
68
|
wallet: Wallet,
|
|
34
|
-
token:
|
|
69
|
+
token: AnyTokenContract,
|
|
35
70
|
owner: AztecAddress,
|
|
36
71
|
expectedBalance: bigint,
|
|
37
72
|
logger: Logger,
|
|
38
73
|
) {
|
|
39
74
|
// Then check the balance
|
|
40
|
-
const contractWithWallet =
|
|
75
|
+
const contractWithWallet = token.withWallet(wallet);
|
|
41
76
|
const { result: balance } = await contractWithWallet.methods.balance_of_private(owner).simulate({ from: owner });
|
|
42
77
|
logger.info(`Account ${owner} balance: ${balance}`);
|
|
43
78
|
expect(balance).toBe(expectedBalance);
|
|
@@ -47,7 +82,7 @@ export async function mintNotes(
|
|
|
47
82
|
wallet: Wallet,
|
|
48
83
|
minter: AztecAddress,
|
|
49
84
|
recipient: AztecAddress,
|
|
50
|
-
asset:
|
|
85
|
+
asset: AnyTokenContract,
|
|
51
86
|
noteAmounts: bigint[],
|
|
52
87
|
): Promise<bigint> {
|
|
53
88
|
// We can only mint 5 notes at a time, since that's the maximum number of calls our entrypoints allow
|
|
@@ -38,6 +38,11 @@ fi
|
|
|
38
38
|
|
|
39
39
|
TRANSFER_AMOUNT=42
|
|
40
40
|
|
|
41
|
+
# Alice's bridged fee-juice claim is only consumable inboxLag (2) checkpoints after the L1->L2 message
|
|
42
|
+
# is inserted. The token deploy and mint above produced two blocks; force one more here (a public mint
|
|
43
|
+
# from test0, which leaves the alice/bob private balances untouched) so the claim is available below.
|
|
44
|
+
aztec-wallet send mint_to_public -ca last --args accounts:test0 1 -f test0
|
|
45
|
+
|
|
41
46
|
aztec-wallet send transfer_in_private -ca last --args accounts:alice accounts:bob $TRANSFER_AMOUNT 0 -f alice --payment method=fee_juice,claim
|
|
42
47
|
|
|
43
48
|
# Test end result
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { appendFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { basename } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import CustomEnvironment from '../../../foundation/src/jest/env.mjs';
|
|
5
|
+
|
|
6
|
+
// Per-test e2e timing environment. Gated entirely on the TEST_TIMING_FILE env var: when unset, this
|
|
7
|
+
// behaves exactly like the base CustomEnvironment (it only delegates). When set, it records, per test
|
|
8
|
+
// worker process, the time spent in jest before/after hooks and the test body, and merges in the
|
|
9
|
+
// function-level time captured by setup.ts/teardown.ts via a collector shared on `this.global`.
|
|
10
|
+
//
|
|
11
|
+
// Output is one JSONL file per worker process (the env runs once per worker). Each line carries a
|
|
12
|
+
// `type` discriminator and is one of:
|
|
13
|
+
// - `type: 'test'` (`name` set): beforeEach hooks, the it() body, afterEach hooks for one test; or
|
|
14
|
+
// - `type: 'suite'` (`name: null`): the suite-scoped beforeAll/afterAll hooks for the whole file.
|
|
15
|
+
export default class TimingEnvironment extends CustomEnvironment {
|
|
16
|
+
constructor(config, context) {
|
|
17
|
+
super(config, context);
|
|
18
|
+
|
|
19
|
+
this.timingFile = process.env.TEST_TIMING_FILE;
|
|
20
|
+
if (!this.timingFile) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
this.suite = basename(context?.testPath ?? config?.testPath ?? 'unknown').replace(/\.test\.[cm]?[jt]s$/, '');
|
|
25
|
+
this.meta = {
|
|
26
|
+
commit: process.env.COMMIT_HASH ?? null,
|
|
27
|
+
branch: process.env.TARGET_BRANCH ?? process.env.REF_NAME ?? null,
|
|
28
|
+
runId: process.env.RUN_ID ?? null,
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Shared collector. setup.ts (running in the sandbox realm) reads `globalThis.__e2eTimings`; this
|
|
32
|
+
// env (host realm) reads `this.global.__e2eTimings` — they are the same object. `current` is the
|
|
33
|
+
// full name of the test currently running (null during beforeAll/afterAll), used to tag fn spans.
|
|
34
|
+
this.collector = { current: null, fnSpans: [] };
|
|
35
|
+
this.global.__e2eTimings = this.collector;
|
|
36
|
+
|
|
37
|
+
// Records to flush as JSONL on teardown. One per test plus one suite-scoped record.
|
|
38
|
+
this.records = [];
|
|
39
|
+
this.recordsByName = new Map();
|
|
40
|
+
// The suite-scoped record (beforeAll/afterAll); bodyMs/totalMs are computed at flush time.
|
|
41
|
+
this.suiteRecord = { name: null, status: 'passed', beforeHooksMs: 0, afterHooksMs: 0 };
|
|
42
|
+
// Start times keyed by hook type (beforeAll/beforeEach/afterAll/afterEach).
|
|
43
|
+
this.hookStarts = {};
|
|
44
|
+
this.testStarts = new Map();
|
|
45
|
+
// Guards against double-flushing (we flush on both the teardown event and the teardown method).
|
|
46
|
+
this.flushed = false;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Flush on the teardown method too: the jest lifecycle always calls this, whereas the 'teardown'
|
|
50
|
+
// event is not reliably delivered when a test run is interrupted. Whichever fires first wins.
|
|
51
|
+
async teardown() {
|
|
52
|
+
this.finalizeAndFlush();
|
|
53
|
+
await super.teardown();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async handleTestEvent(event, state) {
|
|
57
|
+
// Run the base env first so its unhandledRejection patching for after-hooks stays intact.
|
|
58
|
+
await super.handleTestEvent(event, state);
|
|
59
|
+
|
|
60
|
+
if (!this.timingFile) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
switch (event.name) {
|
|
66
|
+
case 'hook_start': {
|
|
67
|
+
this.hookStarts[event.hook.type] = now;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'hook_success':
|
|
71
|
+
case 'hook_failure': {
|
|
72
|
+
const start = this.hookStarts[event.hook.type];
|
|
73
|
+
if (start === undefined) {
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
delete this.hookStarts[event.hook.type];
|
|
77
|
+
const ms = now - start;
|
|
78
|
+
if (event.hook.type === 'beforeEach') {
|
|
79
|
+
this.addToCurrent('beforeHooksMs', ms);
|
|
80
|
+
} else if (event.hook.type === 'afterEach') {
|
|
81
|
+
this.addToCurrent('afterHooksMs', ms);
|
|
82
|
+
} else if (event.hook.type === 'beforeAll') {
|
|
83
|
+
this.suiteRecord.beforeHooksMs += ms;
|
|
84
|
+
} else if (event.hook.type === 'afterAll') {
|
|
85
|
+
this.suiteRecord.afterHooksMs += ms;
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
case 'test_start': {
|
|
90
|
+
const name = this.fullTestName(event.test);
|
|
91
|
+
const record = {
|
|
92
|
+
type: 'test',
|
|
93
|
+
name,
|
|
94
|
+
status: 'passed',
|
|
95
|
+
setupFnMs: 0,
|
|
96
|
+
beforeHooksMs: 0,
|
|
97
|
+
bodyMs: 0,
|
|
98
|
+
teardownFnMs: 0,
|
|
99
|
+
afterHooksMs: 0,
|
|
100
|
+
totalMs: 0,
|
|
101
|
+
startedAt: new Date(now).toISOString(),
|
|
102
|
+
};
|
|
103
|
+
this.records.push(record);
|
|
104
|
+
this.recordsByName.set(name, record);
|
|
105
|
+
this.testStarts.set(name, now);
|
|
106
|
+
this.collector.current = name;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'test_fn_start': {
|
|
110
|
+
this.testStarts.set(`fn:${this.fullTestName(event.test)}`, now);
|
|
111
|
+
break;
|
|
112
|
+
}
|
|
113
|
+
case 'test_fn_success':
|
|
114
|
+
case 'test_fn_failure': {
|
|
115
|
+
const name = this.fullTestName(event.test);
|
|
116
|
+
const start = this.testStarts.get(`fn:${name}`);
|
|
117
|
+
const record = this.recordsByName.get(name);
|
|
118
|
+
if (record && start !== undefined) {
|
|
119
|
+
record.bodyMs = now - start;
|
|
120
|
+
}
|
|
121
|
+
if (record && event.name === 'test_fn_failure') {
|
|
122
|
+
record.status = 'failed';
|
|
123
|
+
}
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
case 'test_done': {
|
|
127
|
+
const name = this.fullTestName(event.test);
|
|
128
|
+
const record = this.recordsByName.get(name);
|
|
129
|
+
const start = this.testStarts.get(name);
|
|
130
|
+
if (record && start !== undefined) {
|
|
131
|
+
record.totalMs = now - start;
|
|
132
|
+
}
|
|
133
|
+
if (record && event.test?.errors?.length) {
|
|
134
|
+
record.status = 'failed';
|
|
135
|
+
}
|
|
136
|
+
this.mergeFnSpans(name, record);
|
|
137
|
+
this.collector.current = null;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'teardown': {
|
|
141
|
+
this.finalizeAndFlush();
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
default:
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The record for the test currently running, if any. */
|
|
150
|
+
currentRecord() {
|
|
151
|
+
return this.collector.current ? this.recordsByName.get(this.collector.current) : undefined;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Adds `ms` to a hook bucket on the current per-test record, or the suite record during beforeAll/afterAll. */
|
|
155
|
+
addToCurrent(field, ms) {
|
|
156
|
+
const record = this.currentRecord();
|
|
157
|
+
if (record) {
|
|
158
|
+
record[field] += ms;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/** Builds the full test name by walking up the describe blocks, matching jest's currentTestName. */
|
|
163
|
+
fullTestName(test) {
|
|
164
|
+
if (!test) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
const parts = [];
|
|
168
|
+
let node = test;
|
|
169
|
+
while (node && node.name && node.name !== 'ROOT_DESCRIBE_BLOCK') {
|
|
170
|
+
parts.unshift(node.name);
|
|
171
|
+
node = node.parent;
|
|
172
|
+
}
|
|
173
|
+
return parts.join(' ');
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** Moves fn spans tagged with `name` into the matching record's setup/teardown buckets. */
|
|
177
|
+
mergeFnSpans(name, record) {
|
|
178
|
+
if (!record) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const remaining = [];
|
|
182
|
+
for (const span of this.collector.fnSpans) {
|
|
183
|
+
if (span.name === name) {
|
|
184
|
+
if (span.kind === 'setup') {
|
|
185
|
+
record.setupFnMs += span.ms;
|
|
186
|
+
} else if (span.kind === 'teardown') {
|
|
187
|
+
record.teardownFnMs += span.ms;
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
remaining.push(span);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
this.collector.fnSpans = remaining;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Finalizes the suite-scoped record (untagged fn spans + beforeAll/afterAll) and writes all JSONL. */
|
|
197
|
+
finalizeAndFlush() {
|
|
198
|
+
if (this.flushed || !this.timingFile) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.flushed = true;
|
|
202
|
+
const suite = {
|
|
203
|
+
setupFnMs: 0,
|
|
204
|
+
beforeHooksMs: this.suiteRecord.beforeHooksMs,
|
|
205
|
+
teardownFnMs: 0,
|
|
206
|
+
afterHooksMs: this.suiteRecord.afterHooksMs,
|
|
207
|
+
};
|
|
208
|
+
for (const span of this.collector.fnSpans) {
|
|
209
|
+
if (span.name === null || span.name === undefined) {
|
|
210
|
+
if (span.kind === 'setup') {
|
|
211
|
+
suite.setupFnMs += span.ms;
|
|
212
|
+
} else if (span.kind === 'teardown') {
|
|
213
|
+
suite.teardownFnMs += span.ms;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
this.collector.fnSpans = [];
|
|
218
|
+
|
|
219
|
+
const lines = [];
|
|
220
|
+
for (const record of this.records) {
|
|
221
|
+
lines.push(this.toLine({ ...record }));
|
|
222
|
+
}
|
|
223
|
+
lines.push(
|
|
224
|
+
this.toLine({
|
|
225
|
+
type: 'suite',
|
|
226
|
+
name: null,
|
|
227
|
+
status: 'passed',
|
|
228
|
+
setupFnMs: suite.setupFnMs,
|
|
229
|
+
beforeHooksMs: suite.beforeHooksMs,
|
|
230
|
+
teardownFnMs: suite.teardownFnMs,
|
|
231
|
+
afterHooksMs: suite.afterHooksMs,
|
|
232
|
+
totalMs: suite.beforeHooksMs + suite.afterHooksMs,
|
|
233
|
+
}),
|
|
234
|
+
);
|
|
235
|
+
|
|
236
|
+
const payload = lines.join('\n') + '\n';
|
|
237
|
+
try {
|
|
238
|
+
// One file per worker process, but a worker may run several suites: append so we keep them all.
|
|
239
|
+
appendFileSync(this.timingFile, payload);
|
|
240
|
+
} catch {
|
|
241
|
+
try {
|
|
242
|
+
writeFileSync(this.timingFile, payload);
|
|
243
|
+
} catch {
|
|
244
|
+
// Timing is best-effort; never fail a test because we couldn't write the file.
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Flattens metadata onto a record and serializes to a single JSON line. */
|
|
250
|
+
toLine(record) {
|
|
251
|
+
const obj = { suite: this.suite, ...record, ...this.meta };
|
|
252
|
+
for (const key of Object.keys(obj)) {
|
|
253
|
+
if (key.endsWith('Ms') && typeof obj[key] === 'number') {
|
|
254
|
+
obj[key] = Math.round(obj[key]);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return JSON.stringify(obj);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -208,7 +208,7 @@ export class LendingSimulator {
|
|
|
208
208
|
expect(interestAccumulator).toEqual(this.accumulator);
|
|
209
209
|
expect(asset['last_updated_ts']).toEqual(BigInt(this.time));
|
|
210
210
|
|
|
211
|
-
for (const key of [this.account.address, AztecAddress.
|
|
211
|
+
for (const key of [this.account.address, AztecAddress.fromFieldUnsafe(await this.account.key())]) {
|
|
212
212
|
const { result: privatePos } = await this.lendingContract.methods
|
|
213
213
|
.get_position(key)
|
|
214
214
|
.simulate({ from: this.account.address });
|