@aztec/end-to-end 4.0.0-nightly.20250907 → 4.0.0-nightly.20260108

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.
Files changed (165) hide show
  1. package/dest/bench/client_flows/benchmark.d.ts +4 -3
  2. package/dest/bench/client_flows/benchmark.d.ts.map +1 -1
  3. package/dest/bench/client_flows/benchmark.js +2 -2
  4. package/dest/bench/client_flows/client_flows_benchmark.d.ts +26 -15
  5. package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
  6. package/dest/bench/client_flows/client_flows_benchmark.js +111 -90
  7. package/dest/bench/client_flows/config.d.ts +1 -1
  8. package/dest/bench/client_flows/data_extractor.d.ts +1 -1
  9. package/dest/bench/client_flows/data_extractor.js +10 -30
  10. package/dest/bench/utils.d.ts +3 -12
  11. package/dest/bench/utils.d.ts.map +1 -1
  12. package/dest/bench/utils.js +17 -37
  13. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +8 -8
  14. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
  15. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +42 -42
  16. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +13 -10
  17. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  18. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +35 -35
  19. package/dest/e2e_deploy_contract/deploy_test.d.ts +12 -6
  20. package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
  21. package/dest/e2e_deploy_contract/deploy_test.js +9 -18
  22. package/dest/e2e_epochs/epochs_test.d.ts +20 -12
  23. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  24. package/dest/e2e_epochs/epochs_test.js +36 -27
  25. package/dest/e2e_fees/bridging_race.notest.d.ts +1 -1
  26. package/dest/e2e_fees/bridging_race.notest.js +14 -11
  27. package/dest/e2e_fees/fees_test.d.ts +13 -9
  28. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  29. package/dest/e2e_fees/fees_test.js +39 -40
  30. package/dest/e2e_l1_publisher/write_json.d.ts +4 -2
  31. package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
  32. package/dest/e2e_l1_publisher/write_json.js +9 -8
  33. package/dest/e2e_multi_validator/utils.d.ts +2 -2
  34. package/dest/e2e_multi_validator/utils.d.ts.map +1 -1
  35. package/dest/e2e_multi_validator/utils.js +4 -10
  36. package/dest/e2e_nested_contract/nested_contract_test.d.ts +7 -4
  37. package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
  38. package/dest/e2e_nested_contract/nested_contract_test.js +11 -12
  39. package/dest/e2e_p2p/inactivity_slash_test.d.ts +31 -0
  40. package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -0
  41. package/dest/e2e_p2p/inactivity_slash_test.js +136 -0
  42. package/dest/e2e_p2p/p2p_network.d.ts +238 -18
  43. package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
  44. package/dest/e2e_p2p/p2p_network.js +50 -25
  45. package/dest/e2e_p2p/shared.d.ts +16 -17
  46. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  47. package/dest/e2e_p2p/shared.js +57 -56
  48. package/dest/e2e_token_contract/token_contract_test.d.ts +6 -5
  49. package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
  50. package/dest/e2e_token_contract/token_contract_test.js +14 -17
  51. package/dest/fixtures/e2e_prover_test.d.ts +13 -11
  52. package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
  53. package/dest/fixtures/e2e_prover_test.js +57 -66
  54. package/dest/fixtures/fixtures.d.ts +2 -3
  55. package/dest/fixtures/fixtures.d.ts.map +1 -1
  56. package/dest/fixtures/fixtures.js +2 -3
  57. package/dest/fixtures/get_acvm_config.d.ts +2 -2
  58. package/dest/fixtures/get_acvm_config.d.ts.map +1 -1
  59. package/dest/fixtures/get_acvm_config.js +1 -1
  60. package/dest/fixtures/get_bb_config.d.ts +2 -2
  61. package/dest/fixtures/get_bb_config.d.ts.map +1 -1
  62. package/dest/fixtures/get_bb_config.js +2 -2
  63. package/dest/fixtures/index.d.ts +1 -1
  64. package/dest/fixtures/l1_to_l2_messaging.d.ts +4 -3
  65. package/dest/fixtures/l1_to_l2_messaging.d.ts.map +1 -1
  66. package/dest/fixtures/l1_to_l2_messaging.js +2 -2
  67. package/dest/fixtures/logging.d.ts +1 -1
  68. package/dest/fixtures/setup_p2p_test.d.ts +12 -11
  69. package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
  70. package/dest/fixtures/setup_p2p_test.js +50 -24
  71. package/dest/fixtures/snapshot_manager.d.ts +16 -15
  72. package/dest/fixtures/snapshot_manager.d.ts.map +1 -1
  73. package/dest/fixtures/snapshot_manager.js +84 -88
  74. package/dest/fixtures/token_utils.d.ts +10 -5
  75. package/dest/fixtures/token_utils.d.ts.map +1 -1
  76. package/dest/fixtures/token_utils.js +17 -18
  77. package/dest/fixtures/utils.d.ts +44 -47
  78. package/dest/fixtures/utils.d.ts.map +1 -1
  79. package/dest/fixtures/utils.js +128 -185
  80. package/dest/fixtures/web3signer.d.ts +5 -0
  81. package/dest/fixtures/web3signer.d.ts.map +1 -0
  82. package/dest/fixtures/web3signer.js +53 -0
  83. package/dest/fixtures/with_telemetry_utils.d.ts +2 -2
  84. package/dest/fixtures/with_telemetry_utils.d.ts.map +1 -1
  85. package/dest/fixtures/with_telemetry_utils.js +2 -2
  86. package/dest/index.d.ts +1 -1
  87. package/dest/quality_of_service/alert_checker.d.ts +2 -2
  88. package/dest/quality_of_service/alert_checker.d.ts.map +1 -1
  89. package/dest/shared/cross_chain_test_harness.d.ts +20 -23
  90. package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
  91. package/dest/shared/cross_chain_test_harness.js +14 -16
  92. package/dest/shared/gas_portal_test_harness.d.ts +10 -17
  93. package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
  94. package/dest/shared/gas_portal_test_harness.js +11 -8
  95. package/dest/shared/index.d.ts +1 -1
  96. package/dest/shared/jest_setup.d.ts +1 -1
  97. package/dest/shared/jest_setup.js +1 -1
  98. package/dest/shared/submit-transactions.d.ts +6 -4
  99. package/dest/shared/submit-transactions.d.ts.map +1 -1
  100. package/dest/shared/submit-transactions.js +8 -7
  101. package/dest/shared/uniswap_l1_l2.d.ts +13 -9
  102. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  103. package/dest/shared/uniswap_l1_l2.js +44 -58
  104. package/dest/simulators/index.d.ts +1 -1
  105. package/dest/simulators/lending_simulator.d.ts +4 -7
  106. package/dest/simulators/lending_simulator.d.ts.map +1 -1
  107. package/dest/simulators/lending_simulator.js +8 -5
  108. package/dest/simulators/token_simulator.d.ts +4 -2
  109. package/dest/simulators/token_simulator.d.ts.map +1 -1
  110. package/dest/simulators/token_simulator.js +2 -2
  111. package/dest/spartan/setup_test_wallets.d.ts +22 -14
  112. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  113. package/dest/spartan/setup_test_wallets.js +144 -86
  114. package/dest/spartan/tx_metrics.d.ts +39 -0
  115. package/dest/spartan/tx_metrics.d.ts.map +1 -0
  116. package/dest/spartan/tx_metrics.js +95 -0
  117. package/dest/spartan/utils.d.ts +101 -16
  118. package/dest/spartan/utils.d.ts.map +1 -1
  119. package/dest/spartan/utils.js +414 -52
  120. package/package.json +43 -40
  121. package/src/bench/client_flows/benchmark.ts +8 -8
  122. package/src/bench/client_flows/client_flows_benchmark.ts +143 -115
  123. package/src/bench/client_flows/data_extractor.ts +9 -31
  124. package/src/bench/utils.ts +15 -39
  125. package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +46 -63
  126. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +46 -55
  127. package/src/e2e_deploy_contract/deploy_test.ts +18 -36
  128. package/src/e2e_epochs/epochs_test.ts +59 -42
  129. package/src/e2e_fees/bridging_race.notest.ts +16 -11
  130. package/src/e2e_fees/fees_test.ts +48 -52
  131. package/src/e2e_l1_publisher/write_json.ts +12 -9
  132. package/src/e2e_multi_validator/utils.ts +5 -11
  133. package/src/e2e_nested_contract/nested_contract_test.ts +15 -13
  134. package/src/e2e_p2p/inactivity_slash_test.ts +179 -0
  135. package/src/e2e_p2p/p2p_network.ts +125 -89
  136. package/src/e2e_p2p/shared.ts +69 -60
  137. package/src/e2e_token_contract/token_contract_test.ts +17 -17
  138. package/src/fixtures/e2e_prover_test.ts +65 -105
  139. package/src/fixtures/fixtures.ts +2 -5
  140. package/src/fixtures/get_acvm_config.ts +2 -2
  141. package/src/fixtures/get_bb_config.ts +3 -2
  142. package/src/fixtures/l1_to_l2_messaging.ts +4 -2
  143. package/src/fixtures/setup_p2p_test.ts +79 -32
  144. package/src/fixtures/snapshot_manager.ts +120 -131
  145. package/src/fixtures/token_utils.ts +16 -24
  146. package/src/fixtures/utils.ts +175 -269
  147. package/src/fixtures/web3signer.ts +63 -0
  148. package/src/fixtures/with_telemetry_utils.ts +2 -2
  149. package/src/guides/up_quick_start.sh +3 -11
  150. package/src/quality_of_service/alert_checker.ts +1 -1
  151. package/src/shared/cross_chain_test_harness.ts +23 -31
  152. package/src/shared/gas_portal_test_harness.ts +14 -21
  153. package/src/shared/jest_setup.ts +1 -1
  154. package/src/shared/submit-transactions.ts +12 -8
  155. package/src/shared/uniswap_l1_l2.ts +80 -88
  156. package/src/simulators/lending_simulator.ts +9 -6
  157. package/src/simulators/token_simulator.ts +5 -2
  158. package/src/spartan/DEVELOP.md +15 -3
  159. package/src/spartan/setup_test_wallets.ts +171 -127
  160. package/src/spartan/tx_metrics.ts +130 -0
  161. package/src/spartan/utils.ts +543 -45
  162. package/dest/fixtures/setup_l1_contracts.d.ts +0 -6
  163. package/dest/fixtures/setup_l1_contracts.d.ts.map +0 -1
  164. package/dest/fixtures/setup_l1_contracts.js +0 -17
  165. package/src/fixtures/setup_l1_contracts.ts +0 -26
@@ -1,9 +1,13 @@
1
- import { createLogger, sleep } from '@aztec/aztec.js';
1
+ import { createLogger } from '@aztec/aztec.js/log';
2
2
  import type { RollupCheatCodes } from '@aztec/aztec/testing';
3
- import type { L1ContractAddresses, ViemPublicClient } from '@aztec/ethereum';
3
+ import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
4
+ import type { ViemPublicClient } from '@aztec/ethereum/types';
5
+ import type { CheckpointNumber } from '@aztec/foundation/branded-types';
4
6
  import type { Logger } from '@aztec/foundation/log';
7
+ import { promiseWithResolvers } from '@aztec/foundation/promise';
5
8
  import { makeBackoff, retry } from '@aztec/foundation/retry';
6
9
  import { schemas } from '@aztec/foundation/schemas';
10
+ import { sleep } from '@aztec/foundation/sleep';
7
11
  import {
8
12
  type AztecNodeAdmin,
9
13
  type AztecNodeAdminConfig,
@@ -26,6 +30,10 @@ const testConfigSchema = z.object({
26
30
  REAL_VERIFIER: schemas.Boolean.optional().default(true),
27
31
  CREATE_ETH_DEVNET: schemas.Boolean.optional().default(false),
28
32
  L1_RPC_URLS_JSON: z.string().optional(),
33
+ L1_ACCOUNT_MNEMONIC: z.string().optional(),
34
+ AZTEC_SLOT_DURATION: z.coerce.number().optional().default(24),
35
+ AZTEC_EPOCH_DURATION: z.coerce.number().optional().default(32),
36
+ AZTEC_PROOF_SUBMISSION_WINDOW: z.coerce.number().optional().default(5),
29
37
  });
30
38
 
31
39
  export type TestConfig = z.infer<typeof testConfigSchema>;
@@ -156,9 +164,42 @@ export async function startPortForward({
156
164
  return { process, port };
157
165
  }
158
166
 
159
- export function startPortForwardForRPC(namespace: string) {
167
+ export function getExternalIP(namespace: string, serviceName: string): Promise<string> {
168
+ const { promise, resolve, reject } = promiseWithResolvers<string>();
169
+ const process = spawn(
170
+ 'kubectl',
171
+ [
172
+ 'get',
173
+ 'service',
174
+ '-n',
175
+ namespace,
176
+ `${namespace}-${serviceName}`,
177
+ '--output',
178
+ "jsonpath='{.status.loadBalancer.ingress[0].ip}'",
179
+ ],
180
+ {
181
+ stdio: 'pipe',
182
+ },
183
+ );
184
+
185
+ let ip = '';
186
+ process.stdout.on('data', data => {
187
+ ip += data;
188
+ });
189
+ process.on('error', err => {
190
+ reject(err);
191
+ });
192
+ process.on('exit', () => {
193
+ // kubectl prints JSON. Remove the quotes
194
+ resolve(ip.replace(/"|'/g, ''));
195
+ });
196
+
197
+ return promise;
198
+ }
199
+
200
+ export function startPortForwardForRPC(namespace: string, index = 0) {
160
201
  return startPortForward({
161
- resource: `services/${namespace}-rpc-aztec-node`,
202
+ resource: `pod/${namespace}-rpc-aztec-node-${index}`,
162
203
  namespace,
163
204
  containerPort: 8080,
164
205
  });
@@ -204,6 +245,16 @@ export async function deleteResourceByLabel({
204
245
  timeout?: string;
205
246
  force?: boolean;
206
247
  }) {
248
+ try {
249
+ // Match both plain and group-qualified names (e.g., "podchaos" or "podchaos.chaos-mesh.org")
250
+ const escaped = resource.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&');
251
+ const regex = `(^|\\.)${escaped}(\\.|$)`;
252
+ await execAsync(`kubectl api-resources --no-headers -o name | grep -Eq '${regex}'`);
253
+ } catch (error) {
254
+ logger.warn(`Resource type '${resource}' not found in cluster, skipping deletion ${error}`);
255
+ return '';
256
+ }
257
+
207
258
  const command = `kubectl delete ${resource} -l ${label} -n ${namespace} --ignore-not-found=true --wait=true --timeout=${timeout} ${
208
259
  force ? '--force' : ''
209
260
  }`;
@@ -231,13 +282,74 @@ export async function waitForResourceByLabel({
231
282
  return stdout;
232
283
  }
233
284
 
285
+ export async function waitForResourceByName({
286
+ resource,
287
+ name,
288
+ namespace,
289
+ condition = 'Ready',
290
+ timeout = '10m',
291
+ }: {
292
+ resource: string;
293
+ name: string;
294
+ namespace: string;
295
+ condition?: string;
296
+ timeout?: string;
297
+ }) {
298
+ const command = `kubectl wait ${resource}/${name} --for=condition=${condition} -n ${namespace} --timeout=${timeout}`;
299
+ logger.info(`command: ${command}`);
300
+ const { stdout } = await execAsync(command);
301
+ return stdout;
302
+ }
303
+
304
+ export async function waitForResourcesByName({
305
+ resource,
306
+ names,
307
+ namespace,
308
+ condition = 'Ready',
309
+ timeout = '10m',
310
+ }: {
311
+ resource: string;
312
+ names: string[];
313
+ namespace: string;
314
+ condition?: string;
315
+ timeout?: string;
316
+ }) {
317
+ if (!names.length) {
318
+ throw new Error(`No ${resource} names provided to waitForResourcesByName`);
319
+ }
320
+
321
+ // Wait all in parallel; if any fails, surface which one.
322
+ await Promise.all(
323
+ names.map(async name => {
324
+ try {
325
+ await waitForResourceByName({ resource, name, namespace, condition, timeout });
326
+ } catch (err) {
327
+ throw new Error(
328
+ `Failed waiting for ${resource}/${name} condition=${condition} timeout=${timeout} namespace=${namespace}: ${String(
329
+ err,
330
+ )}`,
331
+ );
332
+ }
333
+ }),
334
+ );
335
+ }
336
+
234
337
  export function getChartDir(spartanDir: string, chartName: string) {
235
338
  return path.join(spartanDir.trim(), chartName);
236
339
  }
237
340
 
238
- function valuesToArgs(values: Record<string, string | number>) {
341
+ function shellQuote(value: string) {
342
+ // Single-quote safe shell escaping: ' -> '\''
343
+ return `'${value.replace(/'/g, "'\\''")}'`;
344
+ }
345
+
346
+ function valuesToArgs(values: Record<string, string | number | boolean>) {
239
347
  return Object.entries(values)
240
- .map(([key, value]) => `--set ${key}=${value}`)
348
+ .map(([key, value]) =>
349
+ typeof value === 'number' || typeof value === 'boolean'
350
+ ? `--set ${key}=${value}`
351
+ : `--set-string ${key}=${shellQuote(String(value))}`,
352
+ )
241
353
  .join(' ');
242
354
  }
243
355
 
@@ -255,7 +367,7 @@ function createHelmCommand({
255
367
  namespace: string;
256
368
  valuesFile: string | undefined;
257
369
  timeout: string;
258
- values: Record<string, string | number>;
370
+ values: Record<string, string | number | boolean>;
259
371
  reuseValues?: boolean;
260
372
  }) {
261
373
  const valuesFileArgs = valuesFile ? `--values ${helmChartDir}/values/${valuesFile}` : '';
@@ -272,6 +384,61 @@ async function execHelmCommand(args: Parameters<typeof createHelmCommand>[0]) {
272
384
  return stdout;
273
385
  }
274
386
 
387
+ async function getHelmReleaseStatus(instanceName: string, namespace: string): Promise<string | undefined> {
388
+ try {
389
+ const { stdout } = await execAsync(
390
+ `helm list --namespace ${namespace} --all --filter '^${instanceName}$' --output json | cat`,
391
+ );
392
+ const parsed = JSON.parse(stdout) as Array<{ name?: string; status?: string }>;
393
+ const row = parsed.find(r => r.name === instanceName);
394
+ return row?.status;
395
+ } catch {
396
+ return undefined;
397
+ }
398
+ }
399
+
400
+ async function forceDeleteHelmReleaseRecord(instanceName: string, namespace: string, logger: Logger) {
401
+ const labelSelector = `owner=helm,name=${instanceName}`;
402
+ const cmd = `kubectl delete secret -n ${namespace} -l ${labelSelector} --ignore-not-found=true`;
403
+ logger.warn(`Force deleting Helm release record: ${cmd}`);
404
+ await execAsync(cmd).catch(() => undefined);
405
+ }
406
+
407
+ async function hasDeployedHelmRelease(instanceName: string, namespace: string): Promise<boolean> {
408
+ try {
409
+ const status = await getHelmReleaseStatus(instanceName, namespace);
410
+ return status?.toLowerCase() === 'deployed';
411
+ } catch {
412
+ return false;
413
+ }
414
+ }
415
+
416
+ export async function uninstallChaosMesh(instanceName: string, namespace: string, logger: Logger) {
417
+ // uninstall the helm chart if it exists
418
+ logger.info(`Uninstalling helm chart ${instanceName}`);
419
+ await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`);
420
+ // and delete the chaos-mesh resources created by this release
421
+ const deleteByLabel = async (resource: string) => {
422
+ const args = {
423
+ resource,
424
+ namespace: namespace,
425
+ label: `app.kubernetes.io/instance=${instanceName}`,
426
+ } as const;
427
+ logger.info(`Deleting ${resource} resources for release ${instanceName}`);
428
+ await deleteResourceByLabel(args).catch(e => {
429
+ logger.error(`Error deleting ${resource}: ${e}`);
430
+ logger.info(`Force deleting ${resource}`);
431
+ return deleteResourceByLabel({ ...args, force: true });
432
+ });
433
+ };
434
+
435
+ await deleteByLabel('podchaos');
436
+ await deleteByLabel('networkchaos');
437
+ await deleteByLabel('podnetworkchaos');
438
+ await deleteByLabel('workflows');
439
+ await deleteByLabel('workflownodes');
440
+ }
441
+
275
442
  /**
276
443
  * Installs a Helm chart with the given parameters.
277
444
  * @param instanceName - The name of the Helm chart instance.
@@ -294,8 +461,7 @@ export async function installChaosMeshChart({
294
461
  targetNamespace,
295
462
  valuesFile,
296
463
  helmChartDir,
297
- chaosMeshNamespace = 'chaos-mesh',
298
- timeout = '5m',
464
+ timeout = '10m',
299
465
  clean = true,
300
466
  values = {},
301
467
  logger,
@@ -311,27 +477,13 @@ export async function installChaosMeshChart({
311
477
  logger: Logger;
312
478
  }) {
313
479
  if (clean) {
314
- // uninstall the helm chart if it exists
315
- logger.info(`Uninstalling helm chart ${instanceName}`);
316
- await execAsync(`helm uninstall ${instanceName} --namespace ${chaosMeshNamespace} --wait --ignore-not-found`);
317
- // and delete the podchaos resource
318
- const deleteArgs = {
319
- resource: 'podchaos',
320
- namespace: chaosMeshNamespace,
321
- label: `app.kubernetes.io/instance=${instanceName}`,
322
- };
323
- logger.info(`Deleting podchaos resource`);
324
- await deleteResourceByLabel(deleteArgs).catch(e => {
325
- logger.error(`Error deleting podchaos resource: ${e}`);
326
- logger.info(`Force deleting podchaos resource`);
327
- return deleteResourceByLabel({ ...deleteArgs, force: true });
328
- });
480
+ await uninstallChaosMesh(instanceName, targetNamespace, logger);
329
481
  }
330
482
 
331
483
  return execHelmCommand({
332
484
  instanceName,
333
485
  helmChartDir,
334
- namespace: chaosMeshNamespace,
486
+ namespace: targetNamespace,
335
487
  valuesFile,
336
488
  timeout,
337
489
  values: { ...values, 'global.targetNamespace': targetNamespace },
@@ -361,22 +513,49 @@ export function applyProverFailure({
361
513
  });
362
514
  }
363
515
 
516
+ export function applyValidatorFailure({
517
+ namespace,
518
+ spartanDir,
519
+ logger,
520
+ values,
521
+ instanceName,
522
+ }: {
523
+ namespace: string;
524
+ spartanDir: string;
525
+ logger: Logger;
526
+ values?: Record<string, string | number>;
527
+ instanceName?: string;
528
+ }) {
529
+ return installChaosMeshChart({
530
+ instanceName: instanceName ?? 'validator-failure',
531
+ targetNamespace: namespace,
532
+ valuesFile: 'validator-failure.yaml',
533
+ helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
534
+ values,
535
+ logger,
536
+ });
537
+ }
538
+
364
539
  export function applyProverKill({
365
540
  namespace,
366
541
  spartanDir,
367
542
  logger,
543
+ values,
368
544
  }: {
369
545
  namespace: string;
370
546
  spartanDir: string;
371
547
  logger: Logger;
548
+ values?: Record<string, string | number>;
372
549
  }) {
373
550
  return installChaosMeshChart({
374
551
  instanceName: 'prover-kill',
375
552
  targetNamespace: namespace,
376
553
  valuesFile: 'prover-kill.yaml',
377
554
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
555
+ chaosMeshNamespace: namespace,
378
556
  clean: true,
379
557
  logger,
558
+ values,
380
559
  });
381
560
  }
382
561
 
@@ -384,10 +563,12 @@ export function applyProverBrokerKill({
384
563
  namespace,
385
564
  spartanDir,
386
565
  logger,
566
+ values,
387
567
  }: {
388
568
  namespace: string;
389
569
  spartanDir: string;
390
570
  logger: Logger;
571
+ values?: Record<string, string | number>;
391
572
  }) {
392
573
  return installChaosMeshChart({
393
574
  instanceName: 'prover-broker-kill',
@@ -396,66 +577,79 @@ export function applyProverBrokerKill({
396
577
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
397
578
  clean: true,
398
579
  logger,
580
+ values,
399
581
  });
400
582
  }
401
583
 
402
584
  export function applyBootNodeFailure({
585
+ instanceName = 'boot-node-failure',
403
586
  namespace,
404
587
  spartanDir,
405
588
  durationSeconds,
406
589
  logger,
590
+ values,
407
591
  }: {
592
+ instanceName?: string;
408
593
  namespace: string;
409
594
  spartanDir: string;
410
595
  durationSeconds: number;
411
596
  logger: Logger;
597
+ values?: Record<string, string | number>;
412
598
  }) {
413
599
  return installChaosMeshChart({
414
- instanceName: 'boot-node-failure',
600
+ instanceName,
415
601
  targetNamespace: namespace,
416
602
  valuesFile: 'boot-node-failure.yaml',
417
603
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
418
604
  values: {
419
605
  'bootNodeFailure.duration': `${durationSeconds}s`,
606
+ ...(values ?? {}),
420
607
  },
421
608
  logger,
422
609
  });
423
610
  }
424
611
 
425
612
  export function applyValidatorKill({
613
+ instanceName = 'validator-kill',
426
614
  namespace,
427
615
  spartanDir,
428
616
  logger,
429
617
  values,
618
+ clean = true,
430
619
  }: {
620
+ instanceName?: string;
431
621
  namespace: string;
432
622
  spartanDir: string;
433
623
  logger: Logger;
434
624
  values?: Record<string, string | number>;
625
+ clean?: boolean;
435
626
  }) {
436
627
  return installChaosMeshChart({
437
- instanceName: 'validator-kill',
628
+ instanceName: instanceName ?? 'validator-kill',
438
629
  targetNamespace: namespace,
439
630
  valuesFile: 'validator-kill.yaml',
440
631
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
632
+ clean,
441
633
  logger,
442
634
  values,
443
635
  });
444
636
  }
445
637
 
446
638
  export function applyNetworkShaping({
639
+ instanceName = 'network-shaping',
447
640
  valuesFile,
448
641
  namespace,
449
642
  spartanDir,
450
643
  logger,
451
644
  }: {
645
+ instanceName?: string;
452
646
  valuesFile: string;
453
647
  namespace: string;
454
648
  spartanDir: string;
455
649
  logger: Logger;
456
650
  }) {
457
651
  return installChaosMeshChart({
458
- instanceName: 'network-shaping',
652
+ instanceName,
459
653
  targetNamespace: namespace,
460
654
  valuesFile,
461
655
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
@@ -463,35 +657,283 @@ export function applyNetworkShaping({
463
657
  });
464
658
  }
465
659
 
466
- export async function awaitL2BlockNumber(
660
+ export async function awaitCheckpointNumber(
467
661
  rollupCheatCodes: RollupCheatCodes,
468
- blockNumber: bigint,
662
+ checkpointNumber: CheckpointNumber,
469
663
  timeoutSeconds: number,
470
664
  logger: Logger,
471
665
  ) {
472
- logger.info(`Waiting for L2 Block ${blockNumber}`);
666
+ logger.info(`Waiting for checkpoint ${checkpointNumber}`);
473
667
  let tips = await rollupCheatCodes.getTips();
474
668
  const endTime = Date.now() + timeoutSeconds * 1000;
475
- while (tips.pending < blockNumber && Date.now() < endTime) {
476
- logger.info(`At L2 Block ${tips.pending}`);
669
+ while (tips.pending < checkpointNumber && Date.now() < endTime) {
670
+ logger.info(`At checkpoint ${tips.pending}`);
477
671
  await sleep(1000);
478
672
  tips = await rollupCheatCodes.getTips();
479
673
  }
480
- if (tips.pending < blockNumber) {
481
- throw new Error(`Timeout waiting for L2 Block ${blockNumber}, only reached ${tips.pending}`);
674
+ if (tips.pending < checkpointNumber) {
675
+ throw new Error(`Timeout waiting for checkpoint ${checkpointNumber}, only reached ${tips.pending}`);
482
676
  } else {
483
- logger.info(`Reached L2 Block ${tips.pending}`);
677
+ logger.info(`Reached checkpoint ${tips.pending}`);
484
678
  }
485
679
  }
486
680
 
487
681
  export async function restartBot(namespace: string, logger: Logger) {
488
682
  logger.info(`Restarting bot`);
489
- await deleteResourceByLabel({ resource: 'pods', namespace, label: 'app=bot' });
683
+ await deleteResourceByLabel({ resource: 'pods', namespace, label: 'app.kubernetes.io/name=bot' });
490
684
  await sleep(10 * 1000);
491
- await waitForResourceByLabel({ resource: 'pods', namespace, label: 'app=bot' });
685
+ // Some bot images may take time to report Ready due to heavy boot-time proving.
686
+ // Waiting for PodReadyToStartContainers ensures the pod is scheduled and starting without blocking on full readiness.
687
+ await waitForResourceByLabel({
688
+ resource: 'pods',
689
+ namespace,
690
+ label: 'app.kubernetes.io/name=bot',
691
+ condition: 'PodReadyToStartContainers',
692
+ });
492
693
  logger.info(`Bot restarted`);
493
694
  }
494
695
 
696
+ /**
697
+ * Installs or upgrades the transfer bot Helm release for the given namespace.
698
+ * Intended for test setup to enable L2 traffic generation only when needed.
699
+ */
700
+ export async function installTransferBot({
701
+ namespace,
702
+ spartanDir,
703
+ logger,
704
+ replicas = 1,
705
+ txIntervalSeconds = 10,
706
+ followChain = 'PENDING',
707
+ mnemonic = process.env.LABS_INFRA_MNEMONIC ?? 'test test test test test test test test test test test junk',
708
+ mnemonicStartIndex,
709
+ botPrivateKey = process.env.BOT_TRANSFERS_L2_PRIVATE_KEY ?? '0xcafe01',
710
+ nodeUrl,
711
+ timeout = '15m',
712
+ reuseValues = true,
713
+ aztecSlotDuration = Number(process.env.AZTEC_SLOT_DURATION ?? 12),
714
+ }: {
715
+ namespace: string;
716
+ spartanDir: string;
717
+ logger: Logger;
718
+ replicas?: number;
719
+ txIntervalSeconds?: number;
720
+ followChain?: string;
721
+ mnemonic?: string;
722
+ mnemonicStartIndex?: number | string;
723
+ botPrivateKey?: string;
724
+ nodeUrl?: string;
725
+ timeout?: string;
726
+ reuseValues?: boolean;
727
+ aztecSlotDuration?: number;
728
+ }) {
729
+ const instanceName = `${namespace}-bot-transfers`;
730
+ const helmChartDir = getChartDir(spartanDir, 'aztec-bot');
731
+ const resolvedNodeUrl = nodeUrl ?? `http://${namespace}-rpc-aztec-node.${namespace}.svc.cluster.local:8080`;
732
+
733
+ logger.info(`Installing/upgrading transfer bot: replicas=${replicas}, followChain=${followChain}`);
734
+
735
+ const values: Record<string, string | number | boolean> = {
736
+ 'bot.replicaCount': replicas,
737
+ 'bot.txIntervalSeconds': txIntervalSeconds,
738
+ 'bot.followChain': followChain,
739
+ 'bot.botPrivateKey': botPrivateKey,
740
+ 'bot.nodeUrl': resolvedNodeUrl,
741
+ 'bot.mnemonic': mnemonic,
742
+ 'bot.feePaymentMethod': 'fee_juice',
743
+ 'aztec.slotDuration': aztecSlotDuration,
744
+ // Ensure bot can reach its own PXE started in-process (default rpc.port is 8080)
745
+ // Note: since aztec-bot depends on aztec-node with alias `bot`, env vars go under `bot.node.env`.
746
+ 'bot.node.env.BOT_PXE_URL': 'http://127.0.0.1:8080',
747
+ // Provide L1 execution RPC for bridging fee juice
748
+ 'bot.node.env.ETHEREUM_HOSTS': `http://${namespace}-eth-execution.${namespace}.svc.cluster.local:8545`,
749
+ // Provide L1 mnemonic for bridging (falls back to labs mnemonic)
750
+ 'bot.node.env.BOT_L1_MNEMONIC': mnemonic,
751
+
752
+ // The bot does not need Kubernetes API access. Disable RBAC + ServiceAccount creation so the chart
753
+ // can be installed by users without cluster-scoped RBAC permissions.
754
+ 'bot.rbac.create': false,
755
+ 'bot.serviceAccount.create': false,
756
+ 'bot.serviceAccount.name': 'default',
757
+ };
758
+ // Ensure we derive a funded L1 key (index 0 is funded on anvil default mnemonic)
759
+ if (mnemonicStartIndex === undefined) {
760
+ values['bot.mnemonicStartIndex'] = 0;
761
+ }
762
+ // Also pass a funded private key directly if available
763
+ if (process.env.FUNDING_PRIVATE_KEY) {
764
+ values['bot.node.env.BOT_L1_PRIVATE_KEY'] = process.env.FUNDING_PRIVATE_KEY;
765
+ }
766
+ // Align bot image with the running network image: prefer env var, else detect from a validator pod
767
+ let repositoryFromEnv: string | undefined;
768
+ let tagFromEnv: string | undefined;
769
+ const aztecDockerImage = process.env.AZTEC_DOCKER_IMAGE;
770
+ if (aztecDockerImage && aztecDockerImage.includes(':')) {
771
+ const lastColon = aztecDockerImage.lastIndexOf(':');
772
+ repositoryFromEnv = aztecDockerImage.slice(0, lastColon);
773
+ tagFromEnv = aztecDockerImage.slice(lastColon + 1);
774
+ }
775
+
776
+ let repository = repositoryFromEnv;
777
+ let tag = tagFromEnv;
778
+ if (!repository || !tag) {
779
+ try {
780
+ const { stdout } = await execAsync(
781
+ `kubectl get pods -l app.kubernetes.io/name=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`,
782
+ );
783
+ const image = stdout.trim().replace(/^'|'$/g, '');
784
+ if (image && image.includes(':')) {
785
+ const lastColon = image.lastIndexOf(':');
786
+ repository = image.slice(0, lastColon);
787
+ tag = image.slice(lastColon + 1);
788
+ }
789
+ } catch (err) {
790
+ logger.warn(`Could not detect aztec image from validator pod: ${String(err)}`);
791
+ }
792
+ }
793
+ if (repository && tag) {
794
+ values['global.aztecImage.repository'] = repository;
795
+ values['global.aztecImage.tag'] = tag;
796
+ }
797
+ if (mnemonicStartIndex !== undefined) {
798
+ values['bot.mnemonicStartIndex'] =
799
+ typeof mnemonicStartIndex === 'string' ? mnemonicStartIndex : Number(mnemonicStartIndex);
800
+ }
801
+
802
+ // If a previous install attempt left the release in a non-deployed state (e.g. FAILED),
803
+ // `helm upgrade --install` can error with "has no deployed releases".
804
+ // In that case, clear the release record and do a clean install.
805
+ const existingStatus = await getHelmReleaseStatus(instanceName, namespace);
806
+ if (existingStatus && existingStatus.toLowerCase() !== 'deployed') {
807
+ logger.warn(`Transfer bot release ${instanceName} is in status '${existingStatus}'. Reinstalling cleanly.`);
808
+ await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`).catch(
809
+ () => undefined,
810
+ );
811
+ // If helm left the release in `uninstalling`, force-delete the record so we can reinstall.
812
+ const afterUninstallStatus = await getHelmReleaseStatus(instanceName, namespace);
813
+ if (afterUninstallStatus?.toLowerCase() === 'uninstalling') {
814
+ await forceDeleteHelmReleaseRecord(instanceName, namespace, logger);
815
+ }
816
+ }
817
+
818
+ // `--reuse-values` fails if the release has never successfully deployed (e.g. first install, or a previous failed install).
819
+ // Only reuse values when we have a deployed release to reuse from.
820
+ const effectiveReuseValues = reuseValues && (await hasDeployedHelmRelease(instanceName, namespace));
821
+
822
+ await execHelmCommand({
823
+ instanceName,
824
+ helmChartDir,
825
+ namespace,
826
+ valuesFile: undefined,
827
+ timeout,
828
+ values: values as unknown as Record<string, string | number | boolean>,
829
+ reuseValues: effectiveReuseValues,
830
+ });
831
+
832
+ if (replicas > 0) {
833
+ await waitForResourceByLabel({
834
+ resource: 'pods',
835
+ namespace,
836
+ label: 'app.kubernetes.io/name=bot',
837
+ condition: 'PodReadyToStartContainers',
838
+ });
839
+ }
840
+ }
841
+
842
+ /**
843
+ * Uninstalls the transfer bot Helm release from the given namespace.
844
+ * Intended for test teardown to clean up bot resources.
845
+ */
846
+ export async function uninstallTransferBot(namespace: string, logger: Logger) {
847
+ const instanceName = `${namespace}-bot-transfers`;
848
+ logger.info(`Uninstalling transfer bot release ${instanceName}`);
849
+ await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`);
850
+ // Ensure any leftover pods are removed
851
+ await deleteResourceByLabel({ resource: 'pods', namespace, label: 'app.kubernetes.io/name=bot' }).catch(
852
+ () => undefined,
853
+ );
854
+ }
855
+
856
+ /**
857
+ * Enables or disables probabilistic transaction dropping on validators and waits for rollout.
858
+ * Wired to env vars P2P_DROP_TX and P2P_DROP_TX_CHANCE via Helm values.
859
+ */
860
+ export async function setValidatorTxDrop({
861
+ namespace,
862
+ enabled,
863
+ probability,
864
+ logger,
865
+ }: {
866
+ namespace: string;
867
+ enabled: boolean;
868
+ probability: number;
869
+ logger: Logger;
870
+ }) {
871
+ const drop = enabled ? 'true' : 'false';
872
+ const prob = String(probability);
873
+
874
+ const selectors = ['app.kubernetes.io/name=validator', 'app.kubernetes.io/component=validator', 'app=validator'];
875
+ let updated = false;
876
+ for (const selector of selectors) {
877
+ try {
878
+ const list = await execAsync(`kubectl get statefulset -l ${selector} -n ${namespace} --no-headers -o name | cat`);
879
+ const names = list.stdout
880
+ .split('\n')
881
+ .map(s => s.trim())
882
+ .filter(Boolean);
883
+ if (names.length === 0) {
884
+ continue;
885
+ }
886
+ const cmd = `kubectl set env statefulset -l ${selector} -n ${namespace} P2P_DROP_TX=${drop} P2P_DROP_TX_CHANCE=${prob}`;
887
+ logger.info(`command: ${cmd}`);
888
+ await execAsync(cmd);
889
+ updated = true;
890
+ } catch (e) {
891
+ logger.warn(`Failed to update validators with selector ${selector}: ${String(e)}`);
892
+ }
893
+ }
894
+
895
+ if (!updated) {
896
+ logger.warn(`No validator StatefulSets found in ${namespace}. Skipping tx drop toggle.`);
897
+ return;
898
+ }
899
+
900
+ // Restart validator pods to ensure env vars take effect and wait for readiness
901
+ await restartValidators(namespace, logger);
902
+ }
903
+
904
+ export async function restartValidators(namespace: string, logger: Logger) {
905
+ const selectors = ['app.kubernetes.io/name=validator', 'app.kubernetes.io/component=validator', 'app=validator'];
906
+ let any = false;
907
+ for (const selector of selectors) {
908
+ try {
909
+ const { stdout } = await execAsync(`kubectl get pods -l ${selector} -n ${namespace} --no-headers -o name | cat`);
910
+ if (!stdout || stdout.trim().length === 0) {
911
+ continue;
912
+ }
913
+ any = true;
914
+ await deleteResourceByLabel({ resource: 'pods', namespace, label: selector });
915
+ } catch (e) {
916
+ logger.warn(`Error restarting validator pods with selector ${selector}: ${String(e)}`);
917
+ }
918
+ }
919
+
920
+ if (!any) {
921
+ logger.warn(`No validator pods found to restart in ${namespace}.`);
922
+ return;
923
+ }
924
+
925
+ // Wait for either label to be Ready
926
+ for (const selector of selectors) {
927
+ try {
928
+ await waitForResourceByLabel({ resource: 'pods', namespace, label: selector });
929
+ return;
930
+ } catch {
931
+ // try next
932
+ }
933
+ }
934
+ logger.warn(`Validator pods did not report Ready; continuing.`);
935
+ }
936
+
495
937
  export async function enableValidatorDynamicBootNode(
496
938
  instanceName: string,
497
939
  namespace: string,
@@ -515,11 +957,33 @@ export async function enableValidatorDynamicBootNode(
515
957
  }
516
958
 
517
959
  export async function getSequencers(namespace: string) {
518
- const command = `kubectl get pods -l app.kubernetes.io/component=validator -n ${namespace} -o jsonpath='{.items[*].metadata.name}'`;
519
- const { stdout } = await execAsync(command);
520
- const sequencers = stdout.split(' ');
521
- logger.verbose(`Found sequencer pods ${sequencers.join(', ')}`);
522
- return sequencers;
960
+ const selectors = [
961
+ 'app.kubernetes.io/name=validator',
962
+ 'app.kubernetes.io/component=validator',
963
+ 'app.kubernetes.io/component=sequencer-node',
964
+ 'app=validator',
965
+ ];
966
+ for (const selector of selectors) {
967
+ try {
968
+ const command = `kubectl get pods -l ${selector} -n ${namespace} -o jsonpath='{.items[*].metadata.name}'`;
969
+ const { stdout } = await execAsync(command);
970
+ const sequencers = stdout
971
+ .split(' ')
972
+ .map(s => s.trim())
973
+ .filter(Boolean);
974
+ if (sequencers.length > 0) {
975
+ logger.verbose(`Found sequencer pods ${sequencers.join(', ')} (selector=${selector})`);
976
+ return sequencers;
977
+ }
978
+ } catch {
979
+ // try next selector
980
+ }
981
+ }
982
+
983
+ // Fail fast instead of returning [''] which leads to attempts to port-forward `pod/`.
984
+ throw new Error(
985
+ `No sequencer/validator pods found in namespace ${namespace}. Tried selectors: ${selectors.join(', ')}`,
986
+ );
523
987
  }
524
988
 
525
989
  export function updateSequencersConfig(env: TestConfig, config: Partial<AztecNodeAdminConfig>) {
@@ -580,7 +1044,7 @@ export async function getPublicViemClient(
580
1044
  containerPort: 8545,
581
1045
  });
582
1046
  const url = `http://127.0.0.1:${port}`;
583
- const client: ViemPublicClient = createPublicClient({ transport: fallback([http(url)]) });
1047
+ const client: ViemPublicClient = createPublicClient({ transport: fallback([http(url, { batch: false })]) });
584
1048
  if (processes) {
585
1049
  processes.push(process);
586
1050
  }
@@ -590,7 +1054,9 @@ export async function getPublicViemClient(
590
1054
  if (!L1_RPC_URLS_JSON) {
591
1055
  throw new Error(`L1_RPC_URLS_JSON is not defined`);
592
1056
  }
593
- const client: ViemPublicClient = createPublicClient({ transport: fallback([http(L1_RPC_URLS_JSON)]) });
1057
+ const client: ViemPublicClient = createPublicClient({
1058
+ transport: fallback([http(L1_RPC_URLS_JSON, { batch: false })]),
1059
+ });
594
1060
  return { url: L1_RPC_URLS_JSON, client };
595
1061
  }
596
1062
  }
@@ -658,3 +1124,35 @@ export function getGitProjectRoot(): string {
658
1124
  throw new Error(`Failed to determine git project root: ${error}`);
659
1125
  }
660
1126
  }
1127
+
1128
+ /** Returns a client to the RPC of the given sequencer (defaults to first) */
1129
+ export async function getNodeClient(
1130
+ env: TestConfig,
1131
+ index: number = 0,
1132
+ ): Promise<{ node: ReturnType<typeof createAztecNodeClient>; port: number; process: ChildProcess }> {
1133
+ const namespace = env.NAMESPACE;
1134
+ const containerPort = 8080;
1135
+ const sequencers = await getSequencers(namespace);
1136
+ const sequencer = sequencers[index];
1137
+ if (!sequencer) {
1138
+ throw new Error(`No sequencer found at index ${index} in namespace ${namespace}`);
1139
+ }
1140
+
1141
+ const { process, port } = await startPortForward({
1142
+ resource: `pod/${sequencer}`,
1143
+ namespace,
1144
+ containerPort,
1145
+ });
1146
+
1147
+ const url = `http://localhost:${port}`;
1148
+ await retry(
1149
+ () => fetch(`${url}/status`).then(res => res.status === 200),
1150
+ 'forward port',
1151
+ makeBackoff([1, 1, 2, 6]),
1152
+ logger,
1153
+ true,
1154
+ );
1155
+
1156
+ const client = createAztecNodeClient(url);
1157
+ return { node: client, port, process };
1158
+ }