@aztec/end-to-end 0.0.1-commit.bf2612ae → 0.0.1-commit.c2595eba

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 (39) hide show
  1. package/dest/e2e_epochs/epochs_test.d.ts +7 -1
  2. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  3. package/dest/e2e_epochs/epochs_test.js +28 -9
  4. package/dest/e2e_l1_publisher/write_json.d.ts +3 -2
  5. package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
  6. package/dest/e2e_l1_publisher/write_json.js +1 -7
  7. package/dest/e2e_p2p/shared.d.ts +1 -1
  8. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  9. package/dest/e2e_p2p/shared.js +2 -2
  10. package/dest/fixtures/e2e_prover_test.js +1 -1
  11. package/dest/fixtures/setup.d.ts +3 -3
  12. package/dest/fixtures/setup.d.ts.map +1 -1
  13. package/dest/fixtures/setup.js +20 -15
  14. package/dest/fixtures/setup_p2p_test.d.ts +4 -5
  15. package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
  16. package/dest/fixtures/setup_p2p_test.js +24 -19
  17. package/dest/spartan/tx_metrics.d.ts +35 -1
  18. package/dest/spartan/tx_metrics.d.ts.map +1 -1
  19. package/dest/spartan/tx_metrics.js +150 -0
  20. package/dest/spartan/utils/index.d.ts +3 -3
  21. package/dest/spartan/utils/index.d.ts.map +1 -1
  22. package/dest/spartan/utils/index.js +2 -2
  23. package/dest/spartan/utils/k8s.d.ts +29 -1
  24. package/dest/spartan/utils/k8s.d.ts.map +1 -1
  25. package/dest/spartan/utils/k8s.js +118 -0
  26. package/dest/spartan/utils/nodes.d.ts +11 -1
  27. package/dest/spartan/utils/nodes.d.ts.map +1 -1
  28. package/dest/spartan/utils/nodes.js +192 -27
  29. package/package.json +39 -39
  30. package/src/e2e_epochs/epochs_test.ts +31 -10
  31. package/src/e2e_l1_publisher/write_json.ts +1 -6
  32. package/src/e2e_p2p/shared.ts +10 -2
  33. package/src/fixtures/e2e_prover_test.ts +1 -1
  34. package/src/fixtures/setup.ts +13 -13
  35. package/src/fixtures/setup_p2p_test.ts +15 -20
  36. package/src/spartan/tx_metrics.ts +126 -0
  37. package/src/spartan/utils/index.ts +2 -0
  38. package/src/spartan/utils/k8s.ts +152 -0
  39. package/src/spartan/utils/nodes.ts +236 -24
@@ -1,8 +1,9 @@
1
1
  import { createLogger } from '@aztec/aztec.js/log';
2
+ import { createAztecNodeClient } from '@aztec/aztec.js/node';
2
3
  import type { RollupCheatCodes } from '@aztec/aztec/testing';
3
4
  import type { CheckpointNumber } from '@aztec/foundation/branded-types';
4
5
  import type { Logger } from '@aztec/foundation/log';
5
- import { makeBackoff, retry } from '@aztec/foundation/retry';
6
+ import { makeBackoff, retry, retryUntil } from '@aztec/foundation/retry';
6
7
  import { sleep } from '@aztec/foundation/sleep';
7
8
  import {
8
9
  type AztecNodeAdmin,
@@ -15,7 +16,13 @@ import { promisify } from 'util';
15
16
 
16
17
  import type { TestConfig } from './config.js';
17
18
  import { execHelmCommand } from './helm.js';
18
- import { deleteResourceByLabel, getChartDir, startPortForward, waitForResourceByLabel } from './k8s.js';
19
+ import {
20
+ deleteResourceByLabel,
21
+ getChartDir,
22
+ startPortForward,
23
+ waitForResourceByLabel,
24
+ waitForStatefulSetsReady,
25
+ } from './k8s.js';
19
26
 
20
27
  const execAsync = promisify(exec);
21
28
 
@@ -42,6 +49,63 @@ export async function awaitCheckpointNumber(
42
49
  }
43
50
  }
44
51
 
52
+ /**
53
+ * Waits until the proven block number increases.
54
+ *
55
+ * @param rpcUrl - URL of an Aztec RPC node to query
56
+ * @param log - Logger instance
57
+ * @param timeoutSeconds - Maximum time to wait
58
+ * @param pollIntervalSeconds - How often to check
59
+ */
60
+ export async function waitForProvenToAdvance(
61
+ rpcUrl: string,
62
+ log: Logger,
63
+ timeoutSeconds: number = 300,
64
+ pollIntervalSeconds: number = 12, // slot duration
65
+ ): Promise<void> {
66
+ const node = createAztecNodeClient(rpcUrl);
67
+
68
+ log.info('Waiting for proven block to advance (indicating epoch proof just submitted)...');
69
+
70
+ // Get current proven block number
71
+ let initialProvenBlock: number;
72
+ try {
73
+ const tips = await node.getL2Tips();
74
+ initialProvenBlock = Number(tips.proven.block.number);
75
+ log.info(`Current proven block: ${initialProvenBlock}. Waiting for it to increase...`);
76
+ } catch (err) {
77
+ log.warn(`Error getting initial tips: ${err}. Will poll until successful.`);
78
+ initialProvenBlock = 0;
79
+ }
80
+
81
+ await retryUntil(
82
+ async () => {
83
+ try {
84
+ const tips = await node.getL2Tips();
85
+ const currentProvenBlock = Number(tips.proven.block.number);
86
+ const proposedBlock = Number(tips.proposed.number);
87
+
88
+ log.verbose(
89
+ `Chain state: proposed=${proposedBlock}, proven=${currentProvenBlock} (waiting for > ${initialProvenBlock})`,
90
+ );
91
+
92
+ if (currentProvenBlock > initialProvenBlock) {
93
+ log.info(`Proven block advanced from ${initialProvenBlock} to ${currentProvenBlock}.`);
94
+ return true;
95
+ }
96
+
97
+ return false;
98
+ } catch (err) {
99
+ log.verbose(`Error checking tips: ${err}`);
100
+ return false;
101
+ }
102
+ },
103
+ 'proven block to advance',
104
+ timeoutSeconds,
105
+ pollIntervalSeconds,
106
+ );
107
+ }
108
+
45
109
  export async function getSequencers(namespace: string) {
46
110
  const selectors = [
47
111
  'app.kubernetes.io/name=validator',
@@ -127,6 +191,66 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
127
191
  return results;
128
192
  }
129
193
 
194
+ async function getAztecImageForMigrations(namespace: string): Promise<string> {
195
+ const aztecDockerImage = process.env.AZTEC_DOCKER_IMAGE;
196
+ if (aztecDockerImage) {
197
+ return aztecDockerImage;
198
+ }
199
+
200
+ const { stdout } = await execAsync(
201
+ `kubectl get pods -l app.kubernetes.io/name=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`,
202
+ );
203
+ const image = stdout.trim().replace(/^'|'$/g, '');
204
+ if (!image) {
205
+ throw new Error(`Could not detect aztec image from validator pod in namespace ${namespace}`);
206
+ }
207
+ return image;
208
+ }
209
+
210
+ async function getHaDbConnectionUrl(namespace: string): Promise<string> {
211
+ const secretName = `${namespace}-validator-ha-db-postgres`;
212
+ const { stdout } = await execAsync(`kubectl get secret ${secretName} -n ${namespace} -o json`);
213
+ const secret = JSON.parse(stdout);
214
+ const data = secret?.data ?? {};
215
+ const decode = (value?: string) => (value ? Buffer.from(value, 'base64').toString('utf8') : '');
216
+ const user = decode(data.POSTGRES_USER);
217
+ const password = decode(data.POSTGRES_PASSWORD);
218
+ const database = decode(data.POSTGRES_DB);
219
+ if (!user || !password || !database) {
220
+ throw new Error(`Missing HA DB credentials in secret ${secretName}`);
221
+ }
222
+ const host = `${namespace}-validator-ha-db-postgres.${namespace}.svc.cluster.local`;
223
+ return `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:5432/${database}`;
224
+ }
225
+
226
+ export async function initHADb(namespace: string): Promise<void> {
227
+ const databaseUrl = await getHaDbConnectionUrl(namespace);
228
+ const image = await getAztecImageForMigrations(namespace);
229
+ const jobName = `${namespace}-validator-ha-db-migrate`;
230
+ await execAsync(`kubectl delete pod ${jobName} -n ${namespace} --ignore-not-found=true`).catch(() => undefined);
231
+
232
+ const migrateCmd = [
233
+ `kubectl run ${jobName} -n ${namespace}`,
234
+ '--rm -i',
235
+ '--restart=Never',
236
+ `--image=${image}`,
237
+ `--env=DATABASE_URL=${databaseUrl}`,
238
+ '--command -- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js migrate-ha-db up',
239
+ ].join(' ');
240
+ const migrateCmdForLog = migrateCmd.replace(/--env=DATABASE_URL=\S+/, '--env=DATABASE_URL=<redacted>');
241
+
242
+ await retry(
243
+ async () => {
244
+ logger.info(`command: ${migrateCmdForLog}`);
245
+ await execAsync(migrateCmd);
246
+ },
247
+ 'run HA DB migrations',
248
+ makeBackoff([1, 2, 4, 8, 16]),
249
+ logger,
250
+ true,
251
+ );
252
+ }
253
+
130
254
  /**
131
255
  * Enables or disables probabilistic transaction dropping on validators and waits for rollout.
132
256
  * Wired to env vars P2P_DROP_TX and P2P_DROP_TX_CHANCE via Helm values.
@@ -239,25 +363,46 @@ export async function enableValidatorDynamicBootNode(
239
363
  */
240
364
  export async function rollAztecPods(namespace: string, clearState: boolean = false) {
241
365
  // Pod components use 'validator', but StatefulSets and PVCs use 'sequencer-node' for validators
242
- const podComponents = ['p2p-bootstrap', 'prover-node', 'prover-broker', 'prover-agent', 'sequencer-node', 'rpc'];
243
- const pvcComponents = ['p2p-bootstrap', 'prover-node', 'prover-broker', 'sequencer-node', 'rpc'];
366
+ const podComponents = [
367
+ 'p2p-bootstrap',
368
+ 'prover-node',
369
+ 'prover-broker',
370
+ 'prover-agent',
371
+ 'sequencer-node',
372
+ 'rpc',
373
+ 'validator-ha-db',
374
+ ];
375
+ const pvcComponents = ['p2p-bootstrap', 'prover-node', 'prover-broker', 'sequencer-node', 'rpc', 'validator-ha-db'];
244
376
  // StatefulSet components that need to be scaled down before PVC deletion
245
377
  // Note: validators use 'sequencer-node' as component label, not 'validator'
246
- const statefulSetComponents = ['p2p-bootstrap', 'prover-node', 'prover-broker', 'sequencer-node', 'rpc'];
378
+ const statefulSetComponents = [
379
+ 'p2p-bootstrap',
380
+ 'prover-node',
381
+ 'prover-broker',
382
+ 'sequencer-node',
383
+ 'rpc',
384
+ 'validator-ha-db',
385
+ ];
247
386
 
248
387
  if (clearState) {
249
388
  // To delete PVCs, we must first scale down StatefulSets so pods release the volumes
250
389
  // Otherwise PVC deletion will hang waiting for pods to terminate
251
390
 
252
- // First, save original replica counts
391
+ // Save original replica counts for all StatefulSets
253
392
  const originalReplicas: Map<string, number> = new Map();
254
393
  for (const component of statefulSetComponents) {
255
394
  try {
256
- const getCmd = `kubectl get statefulset -l app.kubernetes.io/component=${component} -n ${namespace} -o jsonpath='{.items[0].spec.replicas}'`;
395
+ // Get all StatefulSets that match the component label
396
+ const getCmd = `kubectl get statefulset -l app.kubernetes.io/component=${component} -n ${namespace} -o json`;
257
397
  const { stdout } = await execAsync(getCmd);
258
- const replicas = parseInt(stdout.replace(/'/g, '').trim(), 10);
259
- if (!isNaN(replicas) && replicas > 0) {
260
- originalReplicas.set(component, replicas);
398
+ const result = JSON.parse(stdout);
399
+ for (const sts of result.items || []) {
400
+ const name = sts.metadata.name;
401
+ const replicas = sts.spec.replicas ?? 1;
402
+ if (replicas > 0) {
403
+ originalReplicas.set(name, replicas);
404
+ logger.debug(`Saved replica count for StatefulSet ${name}: ${replicas}`);
405
+ }
261
406
  }
262
407
  } catch {
263
408
  // Component might not exist, continue
@@ -276,27 +421,81 @@ export async function rollAztecPods(namespace: string, clearState: boolean = fal
276
421
  }
277
422
  }
278
423
 
279
- // Wait for pods to terminate
280
- await sleep(15 * 1000);
424
+ // Wait for all pods to fully terminate before deleting PVCs.
425
+ // terminationGracePeriodSeconds default is 30s.
426
+ logger.info('Waiting for pods to fully terminate before deleting PVCs...');
427
+ for (const component of statefulSetComponents) {
428
+ try {
429
+ // Wait for all pods with this component label to be deleted
430
+ const waitCmd = `kubectl wait pods -l app.kubernetes.io/component=${component} --for=delete -n ${namespace} --timeout=2m`;
431
+ logger.info(`command: ${waitCmd}`);
432
+ await execAsync(waitCmd);
433
+ } catch (e) {
434
+ logger.verbose(`Wait for pod deletion ${component} skipped: ${e}`);
435
+ }
436
+ }
437
+ // Extra buffer to ensure PVC protection finalizers are cleared
438
+ await sleep(5 * 1000);
281
439
 
282
440
  // Now delete PVCs (they should no longer be in use)
283
441
  for (const component of pvcComponents) {
284
- await deleteResourceByLabel({
285
- resource: 'persistentvolumeclaims',
286
- namespace: namespace,
287
- label: `app.kubernetes.io/component=${component}`,
288
- });
442
+ try {
443
+ await deleteResourceByLabel({
444
+ resource: 'persistentvolumeclaims',
445
+ namespace: namespace,
446
+ label: `app.kubernetes.io/component=${component}`,
447
+ });
448
+ } catch (e) {
449
+ logger.warn(`Failed to delete PVCs for ${component}: ${e}`);
450
+ }
289
451
  }
290
452
 
291
- // Scale StatefulSets back up to original replica counts
292
- for (const component of statefulSetComponents) {
293
- const replicas = originalReplicas.get(component) ?? 1;
453
+ // Verify PVCs are deleted
454
+ for (const component of pvcComponents) {
294
455
  try {
295
- const scaleCmd = `kubectl scale statefulset -l app.kubernetes.io/component=${component} -n ${namespace} --replicas=${replicas} --timeout=2m`;
456
+ const waitCmd = `kubectl wait pvc -l app.kubernetes.io/component=${component} --for=delete -n ${namespace} --timeout=2m`;
457
+ logger.info(`command: ${waitCmd}`);
458
+ await execAsync(waitCmd);
459
+ } catch (e) {
460
+ logger.verbose(`Wait for PVC deletion ${component} skipped: ${e}`);
461
+ }
462
+ }
463
+
464
+ const haDbStatefulSets = [...originalReplicas.entries()].filter(([name]) => name.includes('validator-ha-db'));
465
+ const otherStatefulSets = [...originalReplicas.entries()].filter(([name]) => !name.includes('validator-ha-db'));
466
+
467
+ // Bring up HA DB first so we can run migrations before validators start
468
+ for (const [stsName, replicas] of haDbStatefulSets) {
469
+ try {
470
+ const scaleCmd = `kubectl scale statefulset ${stsName} -n ${namespace} --replicas=${replicas} --timeout=2m`;
296
471
  logger.info(`command: ${scaleCmd}`);
297
472
  await execAsync(scaleCmd);
298
473
  } catch (e) {
299
- logger.verbose(`Scale up ${component} skipped: ${e}`);
474
+ logger.verbose(`Scale up ${stsName} skipped: ${e}`);
475
+ }
476
+ }
477
+
478
+ if (haDbStatefulSets.length > 0) {
479
+ try {
480
+ await waitForStatefulSetsReady({
481
+ namespace,
482
+ label: 'app.kubernetes.io/component=validator-ha-db',
483
+ timeoutSeconds: 600,
484
+ });
485
+ await initHADb(namespace);
486
+ } catch (e) {
487
+ logger.warn(`HA DB migration step skipped or failed: ${e}`);
488
+ }
489
+ }
490
+
491
+ // Scale remaining StatefulSets back up to original replica counts (by name, not label)
492
+ for (const [stsName, replicas] of otherStatefulSets) {
493
+ try {
494
+ const scaleCmd = `kubectl scale statefulset ${stsName} -n ${namespace} --replicas=${replicas} --timeout=2m`;
495
+ logger.info(`command: ${scaleCmd}`);
496
+ await execAsync(scaleCmd);
497
+ } catch (e) {
498
+ logger.verbose(`Scale up ${stsName} skipped: ${e}`);
300
499
  }
301
500
  }
302
501
  } else {
@@ -312,8 +511,21 @@ export async function rollAztecPods(namespace: string, clearState: boolean = fal
312
511
 
313
512
  await sleep(10 * 1000);
314
513
 
315
- // Wait for pods to come back
316
- for (const component of podComponents) {
514
+ // Wait for StatefulSets to have all replicas ready.
515
+ for (const component of statefulSetComponents) {
516
+ try {
517
+ await waitForStatefulSetsReady({
518
+ namespace,
519
+ label: `app.kubernetes.io/component=${component}`,
520
+ timeoutSeconds: 600, // 10 minutes
521
+ });
522
+ } catch (e) {
523
+ logger.warn(`StatefulSet component ${component} may not be fully ready: ${e}`);
524
+ }
525
+ }
526
+
527
+ const nonStatefulSetComponents = podComponents.filter(c => !statefulSetComponents.includes(c));
528
+ for (const component of nonStatefulSetComponents) {
317
529
  await waitForResourceByLabel({
318
530
  resource: 'pods',
319
531
  namespace: namespace,