@aztec/end-to-end 0.0.1-commit.5daedc8 → 0.0.1-commit.6230efd

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 (98) hide show
  1. package/dest/bench/client_flows/client_flows_benchmark.d.ts +3 -3
  2. package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
  3. package/dest/bench/client_flows/client_flows_benchmark.js +5 -3
  4. package/dest/bench/utils.d.ts +2 -2
  5. package/dest/bench/utils.d.ts.map +1 -1
  6. package/dest/bench/utils.js +10 -6
  7. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +5 -4
  8. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  9. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +2 -1
  10. package/dest/e2e_epochs/epochs_test.d.ts +11 -9
  11. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  12. package/dest/e2e_epochs/epochs_test.js +13 -11
  13. package/dest/e2e_fees/bridging_race.notest.js +1 -1
  14. package/dest/e2e_fees/fees_test.d.ts +8 -4
  15. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  16. package/dest/e2e_fees/fees_test.js +4 -1
  17. package/dest/e2e_multi_validator/utils.js +1 -1
  18. package/dest/e2e_p2p/inactivity_slash_test.d.ts +2 -2
  19. package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -1
  20. package/dest/e2e_p2p/inactivity_slash_test.js +3 -6
  21. package/dest/e2e_p2p/p2p_network.d.ts +7 -6
  22. package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
  23. package/dest/e2e_p2p/p2p_network.js +10 -8
  24. package/dest/e2e_p2p/shared.d.ts +2 -2
  25. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  26. package/dest/fixtures/e2e_prover_test.d.ts +3 -5
  27. package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
  28. package/dest/fixtures/e2e_prover_test.js +6 -9
  29. package/dest/fixtures/fixtures.d.ts +2 -3
  30. package/dest/fixtures/fixtures.d.ts.map +1 -1
  31. package/dest/fixtures/fixtures.js +2 -3
  32. package/dest/fixtures/get_acvm_config.js +1 -1
  33. package/dest/fixtures/l1_to_l2_messaging.d.ts +4 -3
  34. package/dest/fixtures/l1_to_l2_messaging.d.ts.map +1 -1
  35. package/dest/fixtures/l1_to_l2_messaging.js +2 -2
  36. package/dest/fixtures/setup_p2p_test.js +3 -3
  37. package/dest/fixtures/snapshot_manager.d.ts +6 -8
  38. package/dest/fixtures/snapshot_manager.d.ts.map +1 -1
  39. package/dest/fixtures/snapshot_manager.js +34 -46
  40. package/dest/fixtures/utils.d.ts +20 -463
  41. package/dest/fixtures/utils.d.ts.map +1 -1
  42. package/dest/fixtures/utils.js +50 -78
  43. package/dest/fixtures/web3signer.js +1 -1
  44. package/dest/fixtures/with_telemetry_utils.d.ts +2 -2
  45. package/dest/fixtures/with_telemetry_utils.d.ts.map +1 -1
  46. package/dest/fixtures/with_telemetry_utils.js +2 -2
  47. package/dest/shared/cross_chain_test_harness.d.ts +5 -3
  48. package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
  49. package/dest/shared/cross_chain_test_harness.js +1 -1
  50. package/dest/shared/gas_portal_test_harness.d.ts +2 -2
  51. package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
  52. package/dest/shared/uniswap_l1_l2.d.ts +4 -3
  53. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  54. package/dest/shared/uniswap_l1_l2.js +4 -2
  55. package/dest/simulators/lending_simulator.d.ts +2 -2
  56. package/dest/simulators/lending_simulator.d.ts.map +1 -1
  57. package/dest/simulators/lending_simulator.js +1 -1
  58. package/dest/spartan/setup_test_wallets.d.ts +1 -1
  59. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  60. package/dest/spartan/setup_test_wallets.js +2 -1
  61. package/dest/spartan/tx_metrics.d.ts +39 -0
  62. package/dest/spartan/tx_metrics.d.ts.map +1 -0
  63. package/dest/spartan/tx_metrics.js +95 -0
  64. package/dest/spartan/utils.d.ts +43 -10
  65. package/dest/spartan/utils.d.ts.map +1 -1
  66. package/dest/spartan/utils.js +148 -38
  67. package/package.json +40 -40
  68. package/src/bench/client_flows/client_flows_benchmark.ts +7 -4
  69. package/src/bench/utils.ts +11 -7
  70. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +13 -9
  71. package/src/e2e_epochs/epochs_test.ts +33 -20
  72. package/src/e2e_fees/bridging_race.notest.ts +1 -1
  73. package/src/e2e_fees/fees_test.ts +10 -4
  74. package/src/e2e_multi_validator/utils.ts +1 -1
  75. package/src/e2e_p2p/inactivity_slash_test.ts +4 -7
  76. package/src/e2e_p2p/p2p_network.ts +9 -16
  77. package/src/e2e_p2p/shared.ts +5 -1
  78. package/src/fixtures/e2e_prover_test.ts +8 -10
  79. package/src/fixtures/fixtures.ts +2 -5
  80. package/src/fixtures/get_acvm_config.ts +1 -1
  81. package/src/fixtures/l1_to_l2_messaging.ts +4 -2
  82. package/src/fixtures/setup_p2p_test.ts +3 -3
  83. package/src/fixtures/snapshot_manager.ts +51 -65
  84. package/src/fixtures/utils.ts +79 -138
  85. package/src/fixtures/web3signer.ts +1 -1
  86. package/src/fixtures/with_telemetry_utils.ts +2 -2
  87. package/src/shared/cross_chain_test_harness.ts +5 -2
  88. package/src/shared/gas_portal_test_harness.ts +1 -1
  89. package/src/shared/uniswap_l1_l2.ts +8 -10
  90. package/src/simulators/lending_simulator.ts +2 -2
  91. package/src/spartan/DEVELOP.md +7 -0
  92. package/src/spartan/setup_test_wallets.ts +2 -1
  93. package/src/spartan/tx_metrics.ts +130 -0
  94. package/src/spartan/utils.ts +207 -31
  95. package/dest/fixtures/setup_l1_contracts.d.ts +0 -477
  96. package/dest/fixtures/setup_l1_contracts.d.ts.map +0 -1
  97. package/dest/fixtures/setup_l1_contracts.js +0 -17
  98. package/src/fixtures/setup_l1_contracts.ts +0 -26
@@ -0,0 +1,130 @@
1
+ import type { AztecNode } from '@aztec/aztec.js/node';
2
+ import type { L2Block } from '@aztec/stdlib/block';
3
+ import { Tx, type TxReceipt, TxStatus } from '@aztec/stdlib/tx';
4
+
5
+ import { createHistogram } from 'perf_hooks';
6
+
7
+ export type TxInclusionData = {
8
+ txHash: string;
9
+ sentAt: number;
10
+ minedAt: number;
11
+ attestedAt: number;
12
+ blocknumber: number;
13
+ priorityFee: number;
14
+ totalFee: number;
15
+ positionInBlock: number;
16
+ group: string;
17
+ };
18
+
19
+ export class TxInclusionMetrics {
20
+ private data = new Map<string, TxInclusionData>();
21
+ private groups = new Set<string>();
22
+ private blocks = new Map<number, Promise<L2Block>>();
23
+
24
+ constructor(private aztecNode: AztecNode) {}
25
+
26
+ recordSentTx(tx: Tx, group: string): void {
27
+ const txHash = tx.getTxHash().toString();
28
+ const priorityFees = tx.getGasSettings().maxPriorityFeesPerGas;
29
+
30
+ this.data.set(txHash, {
31
+ txHash,
32
+ sentAt: Math.trunc(Date.now() / 1000),
33
+ minedAt: -1,
34
+ attestedAt: -1,
35
+ blocknumber: -1,
36
+ priorityFee: Number(priorityFees.feePerDaGas + priorityFees.feePerL2Gas),
37
+ totalFee: -1,
38
+ positionInBlock: -1,
39
+ group,
40
+ });
41
+ this.groups.add(group);
42
+ }
43
+
44
+ async recordMinedTx(txReceipt: TxReceipt): Promise<void> {
45
+ const { status, txHash, blockNumber } = txReceipt;
46
+ if (status !== TxStatus.SUCCESS || !blockNumber) {
47
+ return;
48
+ }
49
+
50
+ if (!this.blocks.has(blockNumber)) {
51
+ this.blocks.set(blockNumber, this.aztecNode.getBlock(blockNumber) as Promise<L2Block>);
52
+ }
53
+
54
+ const block = await this.blocks.get(blockNumber)!;
55
+ const data = this.data.get(txHash.toString())!;
56
+ data.blocknumber = blockNumber;
57
+ data.minedAt = Number(block.header.globalVariables.timestamp);
58
+ data.attestedAt = -1;
59
+ data.totalFee = Number(txReceipt.transactionFee ?? 0n);
60
+ data.positionInBlock = block.body.txEffects.findIndex(txEffect => txEffect.txHash.equals(txHash));
61
+ }
62
+
63
+ public inclusionTimeInSeconds(group: string): {
64
+ count: number;
65
+ group: string;
66
+ min: number;
67
+ mean: number;
68
+ max: number;
69
+ median: number;
70
+ p99: number;
71
+ } {
72
+ const histogram = createHistogram({});
73
+ for (const tx of this.data.values()) {
74
+ if (!tx.blocknumber || tx.group !== group) {
75
+ continue;
76
+ }
77
+
78
+ histogram.record(tx.minedAt - tx.sentAt);
79
+ }
80
+
81
+ if (histogram.count === 0) {
82
+ return {
83
+ group,
84
+ count: 0,
85
+ mean: 0,
86
+ max: 0,
87
+ median: 0,
88
+ min: 0,
89
+ p99: 0,
90
+ };
91
+ }
92
+
93
+ return {
94
+ group,
95
+ count: histogram.count,
96
+ mean: histogram.mean,
97
+ max: histogram.max,
98
+ median: histogram.percentile(50),
99
+ min: histogram.min,
100
+ p99: histogram.percentile(99),
101
+ };
102
+ }
103
+
104
+ toGithubActionBenchmarkJSON(): Array<{ name: string; unit: string; value: number; range?: number; extra?: string }> {
105
+ const data: Array<{ name: string; unit: string; value: number; range?: number; extra?: string }> = [];
106
+ for (const group of this.groups) {
107
+ const stats = this.inclusionTimeInSeconds(group);
108
+
109
+ data.push(
110
+ {
111
+ name: `${group}/avg_inclusion`,
112
+ unit: 's',
113
+ value: stats.mean,
114
+ },
115
+ {
116
+ name: `${group}/median_inclusion`,
117
+ unit: 's',
118
+ value: stats.median,
119
+ },
120
+ {
121
+ name: `${group}/p99_inclusion`,
122
+ unit: 's',
123
+ value: stats.p99,
124
+ },
125
+ );
126
+ }
127
+
128
+ return data;
129
+ }
130
+ }
@@ -1,6 +1,8 @@
1
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';
5
7
  import { promiseWithResolvers } from '@aztec/foundation/promise';
6
8
  import { makeBackoff, retry } from '@aztec/foundation/retry';
@@ -30,6 +32,7 @@ const testConfigSchema = z.object({
30
32
  L1_RPC_URLS_JSON: z.string().optional(),
31
33
  L1_ACCOUNT_MNEMONIC: z.string().optional(),
32
34
  AZTEC_SLOT_DURATION: z.coerce.number().optional().default(24),
35
+ AZTEC_EPOCH_DURATION: z.coerce.number().optional().default(32),
33
36
  AZTEC_PROOF_SUBMISSION_WINDOW: z.coerce.number().optional().default(5),
34
37
  });
35
38
 
@@ -194,9 +197,9 @@ export function getExternalIP(namespace: string, serviceName: string): Promise<s
194
197
  return promise;
195
198
  }
196
199
 
197
- export function startPortForwardForRPC(namespace: string, resourceType = 'services', index = 0) {
200
+ export function startPortForwardForRPC(namespace: string, index = 0) {
198
201
  return startPortForward({
199
- resource: `${resourceType}/${namespace}-rpc-aztec-node-${index}`,
202
+ resource: `pod/${namespace}-rpc-aztec-node-${index}`,
200
203
  namespace,
201
204
  containerPort: 8080,
202
205
  });
@@ -242,11 +245,11 @@ export async function deleteResourceByLabel({
242
245
  timeout?: string;
243
246
  force?: boolean;
244
247
  }) {
245
- // Check if the resource type exists before attempting to delete
246
248
  try {
247
- await execAsync(
248
- `kubectl api-resources --api-group="" --no-headers -o name | grep -q "^${resource}$" || kubectl api-resources --no-headers -o name | grep -q "^${resource}$"`,
249
- );
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}'`);
250
253
  } catch (error) {
251
254
  logger.warn(`Resource type '${resource}' not found in cluster, skipping deletion ${error}`);
252
255
  return '';
@@ -279,6 +282,58 @@ export async function waitForResourceByLabel({
279
282
  return stdout;
280
283
  }
281
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
+
282
337
  export function getChartDir(spartanDir: string, chartName: string) {
283
338
  return path.join(spartanDir.trim(), chartName);
284
339
  }
@@ -329,7 +384,36 @@ async function execHelmCommand(args: Parameters<typeof createHelmCommand>[0]) {
329
384
  return stdout;
330
385
  }
331
386
 
332
- export async function cleanHelm(instanceName: string, namespace: string, logger: Logger) {
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) {
333
417
  // uninstall the helm chart if it exists
334
418
  logger.info(`Uninstalling helm chart ${instanceName}`);
335
419
  await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`);
@@ -393,7 +477,7 @@ export async function installChaosMeshChart({
393
477
  logger: Logger;
394
478
  }) {
395
479
  if (clean) {
396
- await cleanHelm(instanceName, targetNamespace, logger);
480
+ await uninstallChaosMesh(instanceName, targetNamespace, logger);
397
481
  }
398
482
 
399
483
  return execHelmCommand({
@@ -429,22 +513,49 @@ export function applyProverFailure({
429
513
  });
430
514
  }
431
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
+
432
539
  export function applyProverKill({
433
540
  namespace,
434
541
  spartanDir,
435
542
  logger,
543
+ values,
436
544
  }: {
437
545
  namespace: string;
438
546
  spartanDir: string;
439
547
  logger: Logger;
548
+ values?: Record<string, string | number>;
440
549
  }) {
441
550
  return installChaosMeshChart({
442
551
  instanceName: 'prover-kill',
443
552
  targetNamespace: namespace,
444
553
  valuesFile: 'prover-kill.yaml',
445
554
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
555
+ chaosMeshNamespace: namespace,
446
556
  clean: true,
447
557
  logger,
558
+ values,
448
559
  });
449
560
  }
450
561
 
@@ -452,10 +563,12 @@ export function applyProverBrokerKill({
452
563
  namespace,
453
564
  spartanDir,
454
565
  logger,
566
+ values,
455
567
  }: {
456
568
  namespace: string;
457
569
  spartanDir: string;
458
570
  logger: Logger;
571
+ values?: Record<string, string | number>;
459
572
  }) {
460
573
  return installChaosMeshChart({
461
574
  instanceName: 'prover-broker-kill',
@@ -464,66 +577,79 @@ export function applyProverBrokerKill({
464
577
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
465
578
  clean: true,
466
579
  logger,
580
+ values,
467
581
  });
468
582
  }
469
583
 
470
584
  export function applyBootNodeFailure({
585
+ instanceName = 'boot-node-failure',
471
586
  namespace,
472
587
  spartanDir,
473
588
  durationSeconds,
474
589
  logger,
590
+ values,
475
591
  }: {
592
+ instanceName?: string;
476
593
  namespace: string;
477
594
  spartanDir: string;
478
595
  durationSeconds: number;
479
596
  logger: Logger;
597
+ values?: Record<string, string | number>;
480
598
  }) {
481
599
  return installChaosMeshChart({
482
- instanceName: 'boot-node-failure',
600
+ instanceName,
483
601
  targetNamespace: namespace,
484
602
  valuesFile: 'boot-node-failure.yaml',
485
603
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
486
604
  values: {
487
605
  'bootNodeFailure.duration': `${durationSeconds}s`,
606
+ ...(values ?? {}),
488
607
  },
489
608
  logger,
490
609
  });
491
610
  }
492
611
 
493
612
  export function applyValidatorKill({
613
+ instanceName = 'validator-kill',
494
614
  namespace,
495
615
  spartanDir,
496
616
  logger,
497
617
  values,
618
+ clean = true,
498
619
  }: {
620
+ instanceName?: string;
499
621
  namespace: string;
500
622
  spartanDir: string;
501
623
  logger: Logger;
502
624
  values?: Record<string, string | number>;
625
+ clean?: boolean;
503
626
  }) {
504
627
  return installChaosMeshChart({
505
- instanceName: 'validator-kill',
628
+ instanceName: instanceName ?? 'validator-kill',
506
629
  targetNamespace: namespace,
507
630
  valuesFile: 'validator-kill.yaml',
508
631
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
632
+ clean,
509
633
  logger,
510
634
  values,
511
635
  });
512
636
  }
513
637
 
514
638
  export function applyNetworkShaping({
639
+ instanceName = 'network-shaping',
515
640
  valuesFile,
516
641
  namespace,
517
642
  spartanDir,
518
643
  logger,
519
644
  }: {
645
+ instanceName?: string;
520
646
  valuesFile: string;
521
647
  namespace: string;
522
648
  spartanDir: string;
523
649
  logger: Logger;
524
650
  }) {
525
651
  return installChaosMeshChart({
526
- instanceName: 'network-shaping',
652
+ instanceName,
527
653
  targetNamespace: namespace,
528
654
  valuesFile,
529
655
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
@@ -531,24 +657,24 @@ export function applyNetworkShaping({
531
657
  });
532
658
  }
533
659
 
534
- export async function awaitL2BlockNumber(
660
+ export async function awaitCheckpointNumber(
535
661
  rollupCheatCodes: RollupCheatCodes,
536
- blockNumber: bigint,
662
+ checkpointNumber: CheckpointNumber,
537
663
  timeoutSeconds: number,
538
664
  logger: Logger,
539
665
  ) {
540
- logger.info(`Waiting for L2 Block ${blockNumber}`);
666
+ logger.info(`Waiting for checkpoint ${checkpointNumber}`);
541
667
  let tips = await rollupCheatCodes.getTips();
542
668
  const endTime = Date.now() + timeoutSeconds * 1000;
543
- while (tips.pending < blockNumber && Date.now() < endTime) {
544
- logger.info(`At L2 Block ${tips.pending}`);
669
+ while (tips.pending < checkpointNumber && Date.now() < endTime) {
670
+ logger.info(`At checkpoint ${tips.pending}`);
545
671
  await sleep(1000);
546
672
  tips = await rollupCheatCodes.getTips();
547
673
  }
548
- if (tips.pending < blockNumber) {
549
- 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}`);
550
676
  } else {
551
- logger.info(`Reached L2 Block ${tips.pending}`);
677
+ logger.info(`Reached checkpoint ${tips.pending}`);
552
678
  }
553
679
  }
554
680
 
@@ -622,6 +748,12 @@ export async function installTransferBot({
622
748
  'bot.node.env.ETHEREUM_HOSTS': `http://${namespace}-eth-execution.${namespace}.svc.cluster.local:8545`,
623
749
  // Provide L1 mnemonic for bridging (falls back to labs mnemonic)
624
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',
625
757
  };
626
758
  // Ensure we derive a funded L1 key (index 0 is funded on anvil default mnemonic)
627
759
  if (mnemonicStartIndex === undefined) {
@@ -646,7 +778,7 @@ export async function installTransferBot({
646
778
  if (!repository || !tag) {
647
779
  try {
648
780
  const { stdout } = await execAsync(
649
- `kubectl get pods -l app.kubernetes.io/component=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`,
781
+ `kubectl get pods -l app.kubernetes.io/name=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`,
650
782
  );
651
783
  const image = stdout.trim().replace(/^'|'$/g, '');
652
784
  if (image && image.includes(':')) {
@@ -667,6 +799,26 @@ export async function installTransferBot({
667
799
  typeof mnemonicStartIndex === 'string' ? mnemonicStartIndex : Number(mnemonicStartIndex);
668
800
  }
669
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
+
670
822
  await execHelmCommand({
671
823
  instanceName,
672
824
  helmChartDir,
@@ -674,7 +826,7 @@ export async function installTransferBot({
674
826
  valuesFile: undefined,
675
827
  timeout,
676
828
  values: values as unknown as Record<string, string | number | boolean>,
677
- reuseValues,
829
+ reuseValues: effectiveReuseValues,
678
830
  });
679
831
 
680
832
  if (replicas > 0) {
@@ -719,7 +871,7 @@ export async function setValidatorTxDrop({
719
871
  const drop = enabled ? 'true' : 'false';
720
872
  const prob = String(probability);
721
873
 
722
- const selectors = ['app=validator', 'app.kubernetes.io/component=validator'];
874
+ const selectors = ['app.kubernetes.io/name=validator', 'app.kubernetes.io/component=validator', 'app=validator'];
723
875
  let updated = false;
724
876
  for (const selector of selectors) {
725
877
  try {
@@ -750,7 +902,7 @@ export async function setValidatorTxDrop({
750
902
  }
751
903
 
752
904
  export async function restartValidators(namespace: string, logger: Logger) {
753
- const selectors = ['app=validator', 'app.kubernetes.io/component=validator'];
905
+ const selectors = ['app.kubernetes.io/name=validator', 'app.kubernetes.io/component=validator', 'app=validator'];
754
906
  let any = false;
755
907
  for (const selector of selectors) {
756
908
  try {
@@ -805,11 +957,33 @@ export async function enableValidatorDynamicBootNode(
805
957
  }
806
958
 
807
959
  export async function getSequencers(namespace: string) {
808
- const command = `kubectl get pods -l app.kubernetes.io/component=validator -n ${namespace} -o jsonpath='{.items[*].metadata.name}'`;
809
- const { stdout } = await execAsync(command);
810
- const sequencers = stdout.split(' ');
811
- logger.verbose(`Found sequencer pods ${sequencers.join(', ')}`);
812
- 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
+ );
813
987
  }
814
988
 
815
989
  export function updateSequencersConfig(env: TestConfig, config: Partial<AztecNodeAdminConfig>) {
@@ -870,7 +1044,7 @@ export async function getPublicViemClient(
870
1044
  containerPort: 8545,
871
1045
  });
872
1046
  const url = `http://127.0.0.1:${port}`;
873
- const client: ViemPublicClient = createPublicClient({ transport: fallback([http(url)]) });
1047
+ const client: ViemPublicClient = createPublicClient({ transport: fallback([http(url, { batch: false })]) });
874
1048
  if (processes) {
875
1049
  processes.push(process);
876
1050
  }
@@ -880,7 +1054,9 @@ export async function getPublicViemClient(
880
1054
  if (!L1_RPC_URLS_JSON) {
881
1055
  throw new Error(`L1_RPC_URLS_JSON is not defined`);
882
1056
  }
883
- 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
+ });
884
1060
  return { url: L1_RPC_URLS_JSON, client };
885
1061
  }
886
1062
  }