@aztec/end-to-end 0.0.1-commit.e61ad554 → 0.0.1-commit.ec5f612

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 (172) hide show
  1. package/dest/bench/client_flows/client_flows_benchmark.d.ts +2 -2
  2. package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -1
  3. package/dest/bench/client_flows/client_flows_benchmark.js +3 -3
  4. package/dest/bench/client_flows/config.d.ts +2 -2
  5. package/dest/bench/client_flows/config.d.ts.map +1 -1
  6. package/dest/bench/client_flows/config.js +18 -0
  7. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +4 -2
  8. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
  9. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +10 -0
  10. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +2 -2
  11. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  12. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +3 -2
  13. package/dest/e2e_deploy_contract/deploy_test.d.ts +2 -2
  14. package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
  15. package/dest/e2e_epochs/epochs_test.d.ts +15 -7
  16. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  17. package/dest/e2e_epochs/epochs_test.js +85 -41
  18. package/dest/e2e_fees/fees_test.d.ts +2 -2
  19. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  20. package/dest/e2e_fees/fees_test.js +6 -6
  21. package/dest/e2e_l1_publisher/write_json.d.ts +3 -2
  22. package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -1
  23. package/dest/e2e_l1_publisher/write_json.js +1 -7
  24. package/dest/e2e_p2p/p2p_network.d.ts +2 -2
  25. package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
  26. package/dest/e2e_p2p/p2p_network.js +16 -2
  27. package/dest/e2e_p2p/reqresp/utils.d.ts +22 -0
  28. package/dest/e2e_p2p/reqresp/utils.d.ts.map +1 -0
  29. package/dest/e2e_p2p/reqresp/utils.js +177 -0
  30. package/dest/e2e_p2p/shared.d.ts +2 -2
  31. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  32. package/dest/e2e_p2p/shared.js +7 -3
  33. package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.d.ts +2 -0
  34. package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.d.ts.map +1 -0
  35. package/dest/e2e_storage_proof/fixtures/storage_proof_fetcher.js +184 -0
  36. package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.d.ts +18 -0
  37. package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.d.ts.map +1 -0
  38. package/dest/e2e_storage_proof/fixtures/storage_proof_fixture.js +120 -0
  39. package/dest/e2e_token_contract/token_contract_test.d.ts +4 -2
  40. package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
  41. package/dest/e2e_token_contract/token_contract_test.js +10 -0
  42. package/dest/fixtures/authwit_proxy.d.ts +15 -0
  43. package/dest/fixtures/authwit_proxy.d.ts.map +1 -0
  44. package/dest/fixtures/authwit_proxy.js +30 -0
  45. package/dest/fixtures/e2e_prover_test.d.ts +4 -4
  46. package/dest/fixtures/e2e_prover_test.d.ts.map +1 -1
  47. package/dest/fixtures/e2e_prover_test.js +28 -35
  48. package/dest/fixtures/elu_monitor.d.ts +21 -0
  49. package/dest/fixtures/elu_monitor.d.ts.map +1 -0
  50. package/dest/fixtures/elu_monitor.js +102 -0
  51. package/dest/fixtures/fixtures.d.ts +5 -1
  52. package/dest/fixtures/fixtures.d.ts.map +1 -1
  53. package/dest/fixtures/fixtures.js +6 -0
  54. package/dest/fixtures/ha_setup.d.ts +71 -0
  55. package/dest/fixtures/ha_setup.d.ts.map +1 -0
  56. package/dest/fixtures/ha_setup.js +116 -0
  57. package/dest/fixtures/index.d.ts +2 -1
  58. package/dest/fixtures/index.d.ts.map +1 -1
  59. package/dest/fixtures/index.js +1 -0
  60. package/dest/fixtures/setup.d.ts +40 -24
  61. package/dest/fixtures/setup.d.ts.map +1 -1
  62. package/dest/fixtures/setup.js +79 -164
  63. package/dest/fixtures/setup_p2p_test.d.ts +16 -9
  64. package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
  65. package/dest/fixtures/setup_p2p_test.js +40 -29
  66. package/dest/fixtures/token_utils.d.ts +2 -2
  67. package/dest/fixtures/token_utils.d.ts.map +1 -1
  68. package/dest/fixtures/token_utils.js +3 -2
  69. package/dest/shared/index.d.ts +2 -1
  70. package/dest/shared/index.d.ts.map +1 -1
  71. package/dest/shared/index.js +1 -0
  72. package/dest/shared/jest_setup.js +10 -1
  73. package/dest/shared/mock_state_view.d.ts +86 -0
  74. package/dest/shared/mock_state_view.d.ts.map +1 -0
  75. package/dest/shared/mock_state_view.js +186 -0
  76. package/dest/shared/submit-transactions.d.ts +2 -2
  77. package/dest/shared/submit-transactions.d.ts.map +1 -1
  78. package/dest/shared/uniswap_l1_l2.d.ts +1 -1
  79. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  80. package/dest/shared/uniswap_l1_l2.js +8 -6
  81. package/dest/simulators/token_simulator.d.ts +1 -1
  82. package/dest/simulators/token_simulator.d.ts.map +1 -1
  83. package/dest/simulators/token_simulator.js +2 -23
  84. package/dest/spartan/setup_test_wallets.d.ts +9 -2
  85. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  86. package/dest/spartan/setup_test_wallets.js +31 -1
  87. package/dest/spartan/tx_metrics.d.ts +35 -1
  88. package/dest/spartan/tx_metrics.d.ts.map +1 -1
  89. package/dest/spartan/tx_metrics.js +150 -0
  90. package/dest/spartan/utils/bot.d.ts +3 -2
  91. package/dest/spartan/utils/bot.d.ts.map +1 -1
  92. package/dest/spartan/utils/bot.js +2 -1
  93. package/dest/spartan/utils/config.d.ts +7 -1
  94. package/dest/spartan/utils/config.d.ts.map +1 -1
  95. package/dest/spartan/utils/config.js +3 -1
  96. package/dest/spartan/utils/index.d.ts +5 -4
  97. package/dest/spartan/utils/index.d.ts.map +1 -1
  98. package/dest/spartan/utils/index.js +4 -2
  99. package/dest/spartan/utils/k8s.d.ts +31 -1
  100. package/dest/spartan/utils/k8s.d.ts.map +1 -1
  101. package/dest/spartan/utils/k8s.js +124 -0
  102. package/dest/spartan/utils/nodes.d.ts +14 -5
  103. package/dest/spartan/utils/nodes.d.ts.map +1 -1
  104. package/dest/spartan/utils/nodes.js +204 -33
  105. package/dest/spartan/utils/scripts.d.ts +18 -4
  106. package/dest/spartan/utils/scripts.d.ts.map +1 -1
  107. package/dest/spartan/utils/scripts.js +19 -4
  108. package/dest/test-wallet/test_wallet.d.ts +83 -0
  109. package/dest/test-wallet/test_wallet.d.ts.map +1 -0
  110. package/dest/test-wallet/test_wallet.js +214 -0
  111. package/dest/test-wallet/utils.d.ts +41 -0
  112. package/dest/test-wallet/utils.d.ts.map +1 -0
  113. package/dest/test-wallet/utils.js +71 -0
  114. package/dest/test-wallet/wallet_worker_script.d.ts +2 -0
  115. package/dest/test-wallet/wallet_worker_script.d.ts.map +1 -0
  116. package/dest/test-wallet/wallet_worker_script.js +48 -0
  117. package/dest/test-wallet/worker_wallet.d.ts +52 -0
  118. package/dest/test-wallet/worker_wallet.d.ts.map +1 -0
  119. package/dest/test-wallet/worker_wallet.js +151 -0
  120. package/dest/test-wallet/worker_wallet_schema.d.ts +274 -0
  121. package/dest/test-wallet/worker_wallet_schema.d.ts.map +1 -0
  122. package/dest/test-wallet/worker_wallet_schema.js +10 -0
  123. package/package.json +43 -39
  124. package/src/bench/client_flows/client_flows_benchmark.ts +8 -12
  125. package/src/bench/client_flows/config.ts +9 -1
  126. package/src/bench/utils.ts +1 -1
  127. package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +11 -2
  128. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +7 -8
  129. package/src/e2e_deploy_contract/deploy_test.ts +3 -3
  130. package/src/e2e_epochs/epochs_test.ts +103 -72
  131. package/src/e2e_fees/bridging_race.notest.ts +1 -1
  132. package/src/e2e_fees/fees_test.ts +12 -27
  133. package/src/e2e_l1_publisher/write_json.ts +1 -6
  134. package/src/e2e_nested_contract/nested_contract_test.ts +1 -1
  135. package/src/e2e_p2p/inactivity_slash_test.ts +4 -4
  136. package/src/e2e_p2p/p2p_network.ts +29 -7
  137. package/src/e2e_p2p/reqresp/utils.ts +235 -0
  138. package/src/e2e_p2p/shared.ts +13 -3
  139. package/src/e2e_storage_proof/fixtures/storage_proof.json +915 -0
  140. package/src/e2e_storage_proof/fixtures/storage_proof_fetcher.ts +190 -0
  141. package/src/e2e_storage_proof/fixtures/storage_proof_fixture.ts +173 -0
  142. package/src/e2e_token_contract/token_contract_test.ts +11 -2
  143. package/src/fixtures/authwit_proxy.ts +50 -0
  144. package/src/fixtures/dumps/epoch_proof_result.json +1 -1
  145. package/src/fixtures/e2e_prover_test.ts +29 -39
  146. package/src/fixtures/elu_monitor.ts +126 -0
  147. package/src/fixtures/fixtures.ts +10 -0
  148. package/src/fixtures/ha_setup.ts +186 -0
  149. package/src/fixtures/index.ts +1 -0
  150. package/src/fixtures/setup.ts +103 -225
  151. package/src/fixtures/setup_p2p_test.ts +40 -44
  152. package/src/fixtures/token_utils.ts +2 -1
  153. package/src/guides/up_quick_start.sh +3 -3
  154. package/src/shared/index.ts +1 -0
  155. package/src/shared/jest_setup.ts +16 -1
  156. package/src/shared/mock_state_view.ts +188 -0
  157. package/src/shared/submit-transactions.ts +6 -2
  158. package/src/shared/uniswap_l1_l2.ts +9 -11
  159. package/src/simulators/token_simulator.ts +1 -29
  160. package/src/spartan/setup_test_wallets.ts +58 -4
  161. package/src/spartan/tx_metrics.ts +126 -0
  162. package/src/spartan/utils/bot.ts +4 -1
  163. package/src/spartan/utils/config.ts +2 -0
  164. package/src/spartan/utils/index.ts +7 -1
  165. package/src/spartan/utils/k8s.ts +160 -0
  166. package/src/spartan/utils/nodes.ts +251 -31
  167. package/src/spartan/utils/scripts.ts +43 -7
  168. package/src/test-wallet/test_wallet.ts +306 -0
  169. package/src/test-wallet/utils.ts +112 -0
  170. package/src/test-wallet/wallet_worker_script.ts +60 -0
  171. package/src/test-wallet/worker_wallet.ts +213 -0
  172. package/src/test-wallet/worker_wallet_schema.ts +13 -0
@@ -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,14 @@ 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
+ waitForResourceByName,
25
+ waitForStatefulSetsReady,
26
+ } from './k8s.js';
19
27
 
20
28
  const execAsync = promisify(exec);
21
29
 
@@ -42,6 +50,63 @@ export async function awaitCheckpointNumber(
42
50
  }
43
51
  }
44
52
 
53
+ /**
54
+ * Waits until the proven block number increases.
55
+ *
56
+ * @param rpcUrl - URL of an Aztec RPC node to query
57
+ * @param log - Logger instance
58
+ * @param timeoutSeconds - Maximum time to wait
59
+ * @param pollIntervalSeconds - How often to check
60
+ */
61
+ export async function waitForProvenToAdvance(
62
+ rpcUrl: string,
63
+ log: Logger,
64
+ timeoutSeconds: number = 300,
65
+ pollIntervalSeconds: number = 12, // slot duration
66
+ ): Promise<void> {
67
+ const node = createAztecNodeClient(rpcUrl);
68
+
69
+ log.info('Waiting for proven block to advance (indicating epoch proof just submitted)...');
70
+
71
+ // Get current proven block number
72
+ let initialProvenBlock: number;
73
+ try {
74
+ const tips = await node.getL2Tips();
75
+ initialProvenBlock = Number(tips.proven.block.number);
76
+ log.info(`Current proven block: ${initialProvenBlock}. Waiting for it to increase...`);
77
+ } catch (err) {
78
+ log.warn(`Error getting initial tips: ${err}. Will poll until successful.`);
79
+ initialProvenBlock = 0;
80
+ }
81
+
82
+ await retryUntil(
83
+ async () => {
84
+ try {
85
+ const tips = await node.getL2Tips();
86
+ const currentProvenBlock = Number(tips.proven.block.number);
87
+ const proposedBlock = Number(tips.proposed.number);
88
+
89
+ log.verbose(
90
+ `Chain state: proposed=${proposedBlock}, proven=${currentProvenBlock} (waiting for > ${initialProvenBlock})`,
91
+ );
92
+
93
+ if (currentProvenBlock > initialProvenBlock) {
94
+ log.info(`Proven block advanced from ${initialProvenBlock} to ${currentProvenBlock}.`);
95
+ return true;
96
+ }
97
+
98
+ return false;
99
+ } catch (err) {
100
+ log.verbose(`Error checking tips: ${err}`);
101
+ return false;
102
+ }
103
+ },
104
+ 'proven block to advance',
105
+ timeoutSeconds,
106
+ pollIntervalSeconds,
107
+ );
108
+ }
109
+
45
110
  export async function getSequencers(namespace: string) {
46
111
  const selectors = [
47
112
  'app.kubernetes.io/name=validator',
@@ -90,6 +155,8 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
90
155
  const results = [];
91
156
 
92
157
  for (const sequencer of sequencers) {
158
+ // Ensure pod is Ready before attempting port-forward.
159
+ await waitForResourceByName({ resource: 'pods', name: sequencer, namespace });
93
160
  // Wrap port-forward + fetch in a retry to handle flaky port-forwards
94
161
  const result = await retry(
95
162
  async () => {
@@ -106,7 +173,7 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
106
173
  if (statusRes.status !== 200) {
107
174
  throw new Error(`Admin endpoint returned status ${statusRes.status}`);
108
175
  }
109
- const client = createAztecNodeAdminClient(url);
176
+ const client = createAztecNodeAdminClient(url, {}, undefined, env.AZTEC_ADMIN_API_KEY);
110
177
  return { result: await fn(client), process };
111
178
  } catch (err) {
112
179
  // Kill the port-forward before retrying
@@ -127,22 +194,79 @@ export async function withSequencersAdmin<T>(env: TestConfig, fn: (node: AztecNo
127
194
  return results;
128
195
  }
129
196
 
197
+ async function getAztecImageForMigrations(namespace: string): Promise<string> {
198
+ const aztecDockerImage = process.env.AZTEC_DOCKER_IMAGE;
199
+ if (aztecDockerImage) {
200
+ return aztecDockerImage;
201
+ }
202
+
203
+ const { stdout } = await execAsync(
204
+ `kubectl get pods -l app.kubernetes.io/name=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`,
205
+ );
206
+ const image = stdout.trim().replace(/^'|'$/g, '');
207
+ if (!image) {
208
+ throw new Error(`Could not detect aztec image from validator pod in namespace ${namespace}`);
209
+ }
210
+ return image;
211
+ }
212
+
213
+ async function getHaDbConnectionUrl(namespace: string): Promise<string> {
214
+ const secretName = `${namespace}-validator-ha-db-postgres`;
215
+ const { stdout } = await execAsync(`kubectl get secret ${secretName} -n ${namespace} -o json`);
216
+ const secret = JSON.parse(stdout);
217
+ const data = secret?.data ?? {};
218
+ const decode = (value?: string) => (value ? Buffer.from(value, 'base64').toString('utf8') : '');
219
+ const user = decode(data.POSTGRES_USER);
220
+ const password = decode(data.POSTGRES_PASSWORD);
221
+ const database = decode(data.POSTGRES_DB);
222
+ if (!user || !password || !database) {
223
+ throw new Error(`Missing HA DB credentials in secret ${secretName}`);
224
+ }
225
+ const host = `${namespace}-validator-ha-db-postgres.${namespace}.svc.cluster.local`;
226
+ return `postgresql://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:5432/${database}`;
227
+ }
228
+
229
+ export async function initHADb(namespace: string): Promise<void> {
230
+ const databaseUrl = await getHaDbConnectionUrl(namespace);
231
+ const image = await getAztecImageForMigrations(namespace);
232
+ const jobName = `${namespace}-validator-ha-db-migrate`;
233
+ await execAsync(`kubectl delete pod ${jobName} -n ${namespace} --ignore-not-found=true`).catch(() => undefined);
234
+
235
+ const migrateCmd = [
236
+ `kubectl run ${jobName} -n ${namespace}`,
237
+ '--rm -i',
238
+ '--restart=Never',
239
+ `--image=${image}`,
240
+ `--env=DATABASE_URL=${databaseUrl}`,
241
+ '--command -- node --no-warnings /usr/src/yarn-project/aztec/dest/bin/index.js migrate-ha-db up',
242
+ ].join(' ');
243
+ const migrateCmdForLog = migrateCmd.replace(/--env=DATABASE_URL=\S+/, '--env=DATABASE_URL=<redacted>');
244
+
245
+ await retry(
246
+ async () => {
247
+ logger.info(`command: ${migrateCmdForLog}`);
248
+ await execAsync(migrateCmd);
249
+ },
250
+ 'run HA DB migrations',
251
+ makeBackoff([1, 2, 4, 8, 16]),
252
+ logger,
253
+ true,
254
+ );
255
+ }
256
+
130
257
  /**
131
- * Enables or disables probabilistic transaction dropping on validators and waits for rollout.
132
- * Wired to env vars P2P_DROP_TX and P2P_DROP_TX_CHANCE via Helm values.
258
+ * Sets probabilistic transaction dropping on validators and waits for rollout.
259
+ * Use probability=0 to disable. Wired to env var P2P_DROP_TX_CHANCE via Helm values.
133
260
  */
134
261
  export async function setValidatorTxDrop({
135
262
  namespace,
136
- enabled,
137
263
  probability,
138
264
  logger: log,
139
265
  }: {
140
266
  namespace: string;
141
- enabled: boolean;
142
267
  probability: number;
143
268
  logger: Logger;
144
269
  }) {
145
- const drop = enabled ? 'true' : 'false';
146
270
  const prob = String(probability);
147
271
 
148
272
  const selectors = ['app.kubernetes.io/name=validator', 'app.kubernetes.io/component=validator', 'app=validator'];
@@ -157,7 +281,7 @@ export async function setValidatorTxDrop({
157
281
  if (names.length === 0) {
158
282
  continue;
159
283
  }
160
- const cmd = `kubectl set env statefulset -l ${selector} -n ${namespace} P2P_DROP_TX=${drop} P2P_DROP_TX_CHANCE=${prob}`;
284
+ const cmd = `kubectl set env statefulset -l ${selector} -n ${namespace} P2P_DROP_TX_CHANCE=${prob}`;
161
285
  log.info(`command: ${cmd}`);
162
286
  await execAsync(cmd);
163
287
  updated = true;
@@ -239,25 +363,54 @@ 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
+ // RPC nodes have nodeType='rpc-node' in Helm values, so their component label is 'rpc-node' (not 'rpc')
367
+ const podComponents = [
368
+ 'p2p-bootstrap',
369
+ 'prover-node',
370
+ 'prover-broker',
371
+ 'prover-agent',
372
+ 'sequencer-node',
373
+ 'rpc-node',
374
+ 'validator-ha-db',
375
+ ];
376
+ const pvcComponents = [
377
+ 'p2p-bootstrap',
378
+ 'prover-node',
379
+ 'prover-broker',
380
+ 'sequencer-node',
381
+ 'rpc-node',
382
+ 'validator-ha-db',
383
+ ];
244
384
  // StatefulSet components that need to be scaled down before PVC deletion
245
385
  // Note: validators use 'sequencer-node' as component label, not 'validator'
246
- const statefulSetComponents = ['p2p-bootstrap', 'prover-node', 'prover-broker', 'sequencer-node', 'rpc'];
386
+ const statefulSetComponents = [
387
+ 'p2p-bootstrap',
388
+ 'prover-node',
389
+ 'prover-broker',
390
+ 'sequencer-node',
391
+ 'rpc-node',
392
+ 'validator-ha-db',
393
+ ];
247
394
 
248
395
  if (clearState) {
249
396
  // To delete PVCs, we must first scale down StatefulSets so pods release the volumes
250
397
  // Otherwise PVC deletion will hang waiting for pods to terminate
251
398
 
252
- // First, save original replica counts
399
+ // Save original replica counts for all StatefulSets
253
400
  const originalReplicas: Map<string, number> = new Map();
254
401
  for (const component of statefulSetComponents) {
255
402
  try {
256
- const getCmd = `kubectl get statefulset -l app.kubernetes.io/component=${component} -n ${namespace} -o jsonpath='{.items[0].spec.replicas}'`;
403
+ // Get all StatefulSets that match the component label
404
+ const getCmd = `kubectl get statefulset -l app.kubernetes.io/component=${component} -n ${namespace} -o json`;
257
405
  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);
406
+ const result = JSON.parse(stdout);
407
+ for (const sts of result.items || []) {
408
+ const name = sts.metadata.name;
409
+ const replicas = sts.spec.replicas ?? 1;
410
+ if (replicas > 0) {
411
+ originalReplicas.set(name, replicas);
412
+ logger.debug(`Saved replica count for StatefulSet ${name}: ${replicas}`);
413
+ }
261
414
  }
262
415
  } catch {
263
416
  // Component might not exist, continue
@@ -276,27 +429,81 @@ export async function rollAztecPods(namespace: string, clearState: boolean = fal
276
429
  }
277
430
  }
278
431
 
279
- // Wait for pods to terminate
280
- await sleep(15 * 1000);
432
+ // Wait for all pods to fully terminate before deleting PVCs.
433
+ // terminationGracePeriodSeconds default is 30s.
434
+ logger.info('Waiting for pods to fully terminate before deleting PVCs...');
435
+ for (const component of statefulSetComponents) {
436
+ try {
437
+ // Wait for all pods with this component label to be deleted
438
+ const waitCmd = `kubectl wait pods -l app.kubernetes.io/component=${component} --for=delete -n ${namespace} --timeout=2m`;
439
+ logger.info(`command: ${waitCmd}`);
440
+ await execAsync(waitCmd);
441
+ } catch (e) {
442
+ logger.verbose(`Wait for pod deletion ${component} skipped: ${e}`);
443
+ }
444
+ }
445
+ // Extra buffer to ensure PVC protection finalizers are cleared
446
+ await sleep(5 * 1000);
281
447
 
282
448
  // Now delete PVCs (they should no longer be in use)
283
449
  for (const component of pvcComponents) {
284
- await deleteResourceByLabel({
285
- resource: 'persistentvolumeclaims',
286
- namespace: namespace,
287
- label: `app.kubernetes.io/component=${component}`,
288
- });
450
+ try {
451
+ await deleteResourceByLabel({
452
+ resource: 'persistentvolumeclaims',
453
+ namespace: namespace,
454
+ label: `app.kubernetes.io/component=${component}`,
455
+ });
456
+ } catch (e) {
457
+ logger.warn(`Failed to delete PVCs for ${component}: ${e}`);
458
+ }
289
459
  }
290
460
 
291
- // Scale StatefulSets back up to original replica counts
292
- for (const component of statefulSetComponents) {
293
- const replicas = originalReplicas.get(component) ?? 1;
461
+ // Verify PVCs are deleted
462
+ for (const component of pvcComponents) {
294
463
  try {
295
- const scaleCmd = `kubectl scale statefulset -l app.kubernetes.io/component=${component} -n ${namespace} --replicas=${replicas} --timeout=2m`;
464
+ const waitCmd = `kubectl wait pvc -l app.kubernetes.io/component=${component} --for=delete -n ${namespace} --timeout=2m`;
465
+ logger.info(`command: ${waitCmd}`);
466
+ await execAsync(waitCmd);
467
+ } catch (e) {
468
+ logger.verbose(`Wait for PVC deletion ${component} skipped: ${e}`);
469
+ }
470
+ }
471
+
472
+ const haDbStatefulSets = [...originalReplicas.entries()].filter(([name]) => name.includes('validator-ha-db'));
473
+ const otherStatefulSets = [...originalReplicas.entries()].filter(([name]) => !name.includes('validator-ha-db'));
474
+
475
+ // Bring up HA DB first so we can run migrations before validators start
476
+ for (const [stsName, replicas] of haDbStatefulSets) {
477
+ try {
478
+ const scaleCmd = `kubectl scale statefulset ${stsName} -n ${namespace} --replicas=${replicas} --timeout=2m`;
296
479
  logger.info(`command: ${scaleCmd}`);
297
480
  await execAsync(scaleCmd);
298
481
  } catch (e) {
299
- logger.verbose(`Scale up ${component} skipped: ${e}`);
482
+ logger.verbose(`Scale up ${stsName} skipped: ${e}`);
483
+ }
484
+ }
485
+
486
+ if (haDbStatefulSets.length > 0) {
487
+ try {
488
+ await waitForStatefulSetsReady({
489
+ namespace,
490
+ label: 'app.kubernetes.io/component=validator-ha-db',
491
+ timeoutSeconds: 600,
492
+ });
493
+ await initHADb(namespace);
494
+ } catch (e) {
495
+ logger.warn(`HA DB migration step skipped or failed: ${e}`);
496
+ }
497
+ }
498
+
499
+ // Scale remaining StatefulSets back up to original replica counts (by name, not label)
500
+ for (const [stsName, replicas] of otherStatefulSets) {
501
+ try {
502
+ const scaleCmd = `kubectl scale statefulset ${stsName} -n ${namespace} --replicas=${replicas} --timeout=2m`;
503
+ logger.info(`command: ${scaleCmd}`);
504
+ await execAsync(scaleCmd);
505
+ } catch (e) {
506
+ logger.verbose(`Scale up ${stsName} skipped: ${e}`);
300
507
  }
301
508
  }
302
509
  } else {
@@ -312,8 +519,21 @@ export async function rollAztecPods(namespace: string, clearState: boolean = fal
312
519
 
313
520
  await sleep(10 * 1000);
314
521
 
315
- // Wait for pods to come back
316
- for (const component of podComponents) {
522
+ // Wait for StatefulSets to have all replicas ready.
523
+ for (const component of statefulSetComponents) {
524
+ try {
525
+ await waitForStatefulSetsReady({
526
+ namespace,
527
+ label: `app.kubernetes.io/component=${component}`,
528
+ timeoutSeconds: 600, // 10 minutes
529
+ });
530
+ } catch (e) {
531
+ logger.warn(`StatefulSet component ${component} may not be fully ready: ${e}`);
532
+ }
533
+ }
534
+
535
+ const nonStatefulSetComponents = podComponents.filter(c => !statefulSetComponents.includes(c));
536
+ for (const component of nonStatefulSetComponents) {
317
537
  await waitForResourceByLabel({
318
538
  resource: 'pods',
319
539
  namespace: namespace,
@@ -3,24 +3,47 @@ import type { Logger } from '@aztec/foundation/log';
3
3
  import { execSync, spawn } from 'child_process';
4
4
  import path from 'path';
5
5
 
6
+ /** Result from running a script */
7
+ export type ScriptResult = {
8
+ exitCode: number;
9
+ stdout: string;
10
+ stderr: string;
11
+ };
12
+
6
13
  /**
7
14
  * @param scriptPath - The path to the script, relative to the project root
8
15
  * @param args - The arguments to pass to the script
9
16
  * @param logger - The logger to use
10
- * @returns The exit code of the script
17
+ * @returns The exit code, stdout, and stderr of the script
11
18
  */
12
- function runScript(scriptPath: string, args: string[], logger: Logger, env?: Record<string, string>) {
19
+ function runScript(
20
+ scriptPath: string,
21
+ args: string[],
22
+ logger: Logger,
23
+ env?: Record<string, string>,
24
+ ): Promise<ScriptResult> {
13
25
  const childProcess = spawn(scriptPath, args, {
14
26
  stdio: ['ignore', 'pipe', 'pipe'],
15
27
  env: env ? { ...process.env, ...env } : process.env,
16
28
  });
17
- return new Promise<number>((resolve, reject) => {
18
- childProcess.on('close', (code: number | null) => resolve(code ?? 0));
29
+ const stdoutChunks: Buffer[] = [];
30
+ const stderrChunks: Buffer[] = [];
31
+
32
+ return new Promise<ScriptResult>((resolve, reject) => {
33
+ childProcess.on('close', (code: number | null) =>
34
+ resolve({
35
+ exitCode: code ?? 0,
36
+ stdout: Buffer.concat(stdoutChunks).toString(),
37
+ stderr: Buffer.concat(stderrChunks).toString(),
38
+ }),
39
+ );
19
40
  childProcess.on('error', reject);
20
41
  childProcess.stdout?.on('data', (data: Buffer) => {
42
+ stdoutChunks.push(data);
21
43
  logger.info(data.toString());
22
44
  });
23
45
  childProcess.stderr?.on('data', (data: Buffer) => {
46
+ stderrChunks.push(data);
24
47
  logger.error(data.toString());
25
48
  });
26
49
  });
@@ -51,13 +74,26 @@ export function getAztecBin() {
51
74
  * @param args - The arguments to pass to the Aztec binary
52
75
  * @param logger - The logger to use
53
76
  * @param env - Optional environment variables to set for the process
54
- * @returns The exit code of the Aztec binary
77
+ * @returns The exit code, stdout, and stderr of the Aztec binary
55
78
  */
56
- export function runAztecBin(args: string[], logger: Logger, env?: Record<string, string>) {
79
+ export function runAztecBin(args: string[], logger: Logger, env?: Record<string, string>): Promise<ScriptResult> {
57
80
  return runScript('node', [getAztecBin(), ...args], logger, env);
58
81
  }
59
82
 
60
- export function runProjectScript(script: string, args: string[], logger: Logger, env?: Record<string, string>) {
83
+ /**
84
+ * Runs a script from the project root
85
+ * @param script - The path to the script, relative to the project root
86
+ * @param args - The arguments to pass to the script
87
+ * @param logger - The logger to use
88
+ * @param env - Optional environment variables to set for the process
89
+ * @returns The exit code, stdout, and stderr of the script
90
+ */
91
+ export function runProjectScript(
92
+ script: string,
93
+ args: string[],
94
+ logger: Logger,
95
+ env?: Record<string, string>,
96
+ ): Promise<ScriptResult> {
61
97
  const scriptPath = script.startsWith('/') ? script : path.join(getGitProjectRoot(), script);
62
98
  return runScript(scriptPath, args, logger, env);
63
99
  }