@aztec/end-to-end 0.0.0-test.1 → 0.0.1-commit.5476d83

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 (174) hide show
  1. package/dest/bench/client_flows/benchmark.d.ts +61 -0
  2. package/dest/bench/client_flows/benchmark.d.ts.map +1 -0
  3. package/dest/bench/client_flows/benchmark.js +261 -0
  4. package/dest/bench/client_flows/client_flows_benchmark.d.ts +80 -0
  5. package/dest/bench/client_flows/client_flows_benchmark.d.ts.map +1 -0
  6. package/dest/bench/client_flows/client_flows_benchmark.js +334 -0
  7. package/dest/bench/client_flows/config.d.ts +14 -0
  8. package/dest/bench/client_flows/config.d.ts.map +1 -0
  9. package/dest/bench/client_flows/config.js +106 -0
  10. package/dest/bench/client_flows/data_extractor.d.ts +2 -0
  11. package/dest/bench/client_flows/data_extractor.d.ts.map +1 -0
  12. package/dest/bench/client_flows/data_extractor.js +77 -0
  13. package/dest/bench/utils.d.ts +12 -38
  14. package/dest/bench/utils.d.ts.map +1 -1
  15. package/dest/bench/utils.js +26 -66
  16. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts +21 -13
  17. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.d.ts.map +1 -1
  18. package/dest/e2e_blacklist_token_contract/blacklist_token_contract_test.js +85 -57
  19. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts +19 -25
  20. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.d.ts.map +1 -1
  21. package/dest/e2e_cross_chain_messaging/cross_chain_messaging_test.js +50 -70
  22. package/dest/e2e_deploy_contract/deploy_test.d.ts +16 -8
  23. package/dest/e2e_deploy_contract/deploy_test.d.ts.map +1 -1
  24. package/dest/e2e_deploy_contract/deploy_test.js +13 -19
  25. package/dest/e2e_epochs/epochs_test.d.ts +59 -18
  26. package/dest/e2e_epochs/epochs_test.d.ts.map +1 -1
  27. package/dest/e2e_epochs/epochs_test.js +226 -44
  28. package/dest/e2e_fees/bridging_race.notest.d.ts +2 -0
  29. package/dest/e2e_fees/bridging_race.notest.d.ts.map +1 -0
  30. package/dest/e2e_fees/bridging_race.notest.js +63 -0
  31. package/dest/e2e_fees/fees_test.d.ts +21 -10
  32. package/dest/e2e_fees/fees_test.d.ts.map +1 -1
  33. package/dest/e2e_fees/fees_test.js +103 -109
  34. package/dest/e2e_l1_publisher/write_json.d.ts +10 -0
  35. package/dest/e2e_l1_publisher/write_json.d.ts.map +1 -0
  36. package/dest/e2e_l1_publisher/write_json.js +58 -0
  37. package/dest/e2e_multi_validator/utils.d.ts +12 -0
  38. package/dest/e2e_multi_validator/utils.d.ts.map +1 -0
  39. package/dest/e2e_multi_validator/utils.js +214 -0
  40. package/dest/e2e_nested_contract/nested_contract_test.d.ts +10 -7
  41. package/dest/e2e_nested_contract/nested_contract_test.d.ts.map +1 -1
  42. package/dest/e2e_nested_contract/nested_contract_test.js +24 -20
  43. package/dest/e2e_p2p/inactivity_slash_test.d.ts +31 -0
  44. package/dest/e2e_p2p/inactivity_slash_test.d.ts.map +1 -0
  45. package/dest/e2e_p2p/inactivity_slash_test.js +139 -0
  46. package/dest/e2e_p2p/p2p_network.d.ts +275 -23
  47. package/dest/e2e_p2p/p2p_network.d.ts.map +1 -1
  48. package/dest/e2e_p2p/p2p_network.js +184 -131
  49. package/dest/e2e_p2p/shared.d.ts +43 -7
  50. package/dest/e2e_p2p/shared.d.ts.map +1 -1
  51. package/dest/e2e_p2p/shared.js +164 -19
  52. package/dest/e2e_token_contract/token_contract_test.d.ts +12 -6
  53. package/dest/e2e_token_contract/token_contract_test.d.ts.map +1 -1
  54. package/dest/e2e_token_contract/token_contract_test.js +50 -26
  55. package/dest/fixtures/e2e_prover_test.d.ts +63 -0
  56. package/dest/fixtures/e2e_prover_test.d.ts.map +1 -0
  57. package/dest/{e2e_prover → fixtures}/e2e_prover_test.js +104 -105
  58. package/dest/fixtures/fixtures.d.ts +6 -7
  59. package/dest/fixtures/fixtures.d.ts.map +1 -1
  60. package/dest/fixtures/fixtures.js +4 -3
  61. package/dest/fixtures/get_acvm_config.d.ts +2 -2
  62. package/dest/fixtures/get_acvm_config.d.ts.map +1 -1
  63. package/dest/fixtures/get_acvm_config.js +2 -14
  64. package/dest/fixtures/get_bb_config.d.ts +2 -2
  65. package/dest/fixtures/get_bb_config.d.ts.map +1 -1
  66. package/dest/fixtures/get_bb_config.js +10 -17
  67. package/dest/fixtures/index.d.ts +1 -1
  68. package/dest/fixtures/l1_to_l2_messaging.d.ts +9 -6
  69. package/dest/fixtures/l1_to_l2_messaging.d.ts.map +1 -1
  70. package/dest/fixtures/l1_to_l2_messaging.js +44 -18
  71. package/dest/fixtures/logging.d.ts +1 -1
  72. package/dest/fixtures/setup_l1_contracts.d.ts +476 -5
  73. package/dest/fixtures/setup_l1_contracts.d.ts.map +1 -1
  74. package/dest/fixtures/setup_l1_contracts.js +4 -4
  75. package/dest/fixtures/setup_p2p_test.d.ts +15 -14
  76. package/dest/fixtures/setup_p2p_test.d.ts.map +1 -1
  77. package/dest/fixtures/setup_p2p_test.js +81 -21
  78. package/dest/fixtures/snapshot_manager.d.ts +17 -9
  79. package/dest/fixtures/snapshot_manager.d.ts.map +1 -1
  80. package/dest/fixtures/snapshot_manager.js +147 -121
  81. package/dest/fixtures/token_utils.d.ts +10 -4
  82. package/dest/fixtures/token_utils.d.ts.map +1 -1
  83. package/dest/fixtures/token_utils.js +28 -12
  84. package/dest/fixtures/utils.d.ts +524 -40
  85. package/dest/fixtures/utils.d.ts.map +1 -1
  86. package/dest/fixtures/utils.js +464 -369
  87. package/dest/fixtures/web3signer.d.ts +5 -0
  88. package/dest/fixtures/web3signer.d.ts.map +1 -0
  89. package/dest/fixtures/web3signer.js +53 -0
  90. package/dest/fixtures/with_telemetry_utils.d.ts +1 -1
  91. package/dest/index.d.ts +1 -1
  92. package/dest/quality_of_service/alert_checker.d.ts +2 -2
  93. package/dest/quality_of_service/alert_checker.d.ts.map +1 -1
  94. package/dest/shared/cross_chain_test_harness.d.ts +39 -34
  95. package/dest/shared/cross_chain_test_harness.d.ts.map +1 -1
  96. package/dest/shared/cross_chain_test_harness.js +104 -50
  97. package/dest/shared/gas_portal_test_harness.d.ts +29 -31
  98. package/dest/shared/gas_portal_test_harness.d.ts.map +1 -1
  99. package/dest/shared/gas_portal_test_harness.js +51 -30
  100. package/dest/shared/index.d.ts +1 -1
  101. package/dest/shared/jest_setup.d.ts +1 -1
  102. package/dest/shared/jest_setup.js +1 -1
  103. package/dest/shared/submit-transactions.d.ts +6 -4
  104. package/dest/shared/submit-transactions.d.ts.map +1 -1
  105. package/dest/shared/submit-transactions.js +8 -7
  106. package/dest/shared/uniswap_l1_l2.d.ts +14 -12
  107. package/dest/shared/uniswap_l1_l2.d.ts.map +1 -1
  108. package/dest/shared/uniswap_l1_l2.js +146 -116
  109. package/dest/simulators/index.d.ts +1 -1
  110. package/dest/simulators/lending_simulator.d.ts +7 -11
  111. package/dest/simulators/lending_simulator.d.ts.map +1 -1
  112. package/dest/simulators/lending_simulator.js +15 -16
  113. package/dest/simulators/token_simulator.d.ts +6 -3
  114. package/dest/simulators/token_simulator.d.ts.map +1 -1
  115. package/dest/simulators/token_simulator.js +16 -13
  116. package/dest/spartan/setup_test_wallets.d.ts +26 -11
  117. package/dest/spartan/setup_test_wallets.d.ts.map +1 -1
  118. package/dest/spartan/setup_test_wallets.js +201 -58
  119. package/dest/spartan/utils.d.ts +118 -313
  120. package/dest/spartan/utils.d.ts.map +1 -1
  121. package/dest/spartan/utils.js +472 -135
  122. package/package.json +65 -58
  123. package/src/bench/client_flows/benchmark.ts +341 -0
  124. package/src/bench/client_flows/client_flows_benchmark.ts +447 -0
  125. package/src/bench/client_flows/config.ts +61 -0
  126. package/src/bench/client_flows/data_extractor.ts +89 -0
  127. package/src/bench/utils.ts +22 -76
  128. package/src/e2e_blacklist_token_contract/blacklist_token_contract_test.ts +80 -77
  129. package/src/e2e_cross_chain_messaging/cross_chain_messaging_test.ts +65 -106
  130. package/src/e2e_deploy_contract/deploy_test.ts +24 -39
  131. package/src/e2e_epochs/epochs_test.ts +276 -55
  132. package/src/e2e_fees/bridging_race.notest.ts +80 -0
  133. package/src/e2e_fees/fees_test.ts +142 -138
  134. package/src/e2e_l1_publisher/write_json.ts +77 -0
  135. package/src/e2e_multi_validator/utils.ts +258 -0
  136. package/src/e2e_nested_contract/nested_contract_test.ts +29 -19
  137. package/src/e2e_p2p/inactivity_slash_test.ts +182 -0
  138. package/src/e2e_p2p/p2p_network.ts +279 -169
  139. package/src/e2e_p2p/shared.ts +247 -29
  140. package/src/e2e_token_contract/token_contract_test.ts +43 -39
  141. package/src/fixtures/dumps/epoch_proof_result.json +1 -1
  142. package/src/{e2e_prover → fixtures}/e2e_prover_test.ts +107 -152
  143. package/src/fixtures/fixtures.ts +4 -3
  144. package/src/fixtures/get_acvm_config.ts +3 -11
  145. package/src/fixtures/get_bb_config.ts +18 -13
  146. package/src/fixtures/l1_to_l2_messaging.ts +53 -23
  147. package/src/fixtures/setup_l1_contracts.ts +6 -7
  148. package/src/fixtures/setup_p2p_test.ts +126 -38
  149. package/src/fixtures/snapshot_manager.ts +187 -139
  150. package/src/fixtures/token_utils.ts +32 -15
  151. package/src/fixtures/utils.ts +580 -434
  152. package/src/fixtures/web3signer.ts +63 -0
  153. package/src/guides/up_quick_start.sh +7 -15
  154. package/src/quality_of_service/alert_checker.ts +1 -1
  155. package/src/shared/cross_chain_test_harness.ts +108 -79
  156. package/src/shared/gas_portal_test_harness.ts +59 -50
  157. package/src/shared/jest_setup.ts +1 -1
  158. package/src/shared/submit-transactions.ts +12 -8
  159. package/src/shared/uniswap_l1_l2.ts +181 -184
  160. package/src/simulators/lending_simulator.ts +14 -15
  161. package/src/simulators/token_simulator.ts +21 -13
  162. package/src/spartan/DEVELOP.md +121 -0
  163. package/src/spartan/setup_test_wallets.ts +251 -93
  164. package/src/spartan/utils.ts +536 -136
  165. package/dest/e2e_prover/e2e_prover_test.d.ts +0 -56
  166. package/dest/e2e_prover/e2e_prover_test.d.ts.map +0 -1
  167. package/dest/sample-dapp/connect.js +0 -12
  168. package/dest/sample-dapp/contracts.js +0 -10
  169. package/dest/sample-dapp/deploy.js +0 -35
  170. package/dest/sample-dapp/index.js +0 -98
  171. package/src/sample-dapp/connect.mjs +0 -16
  172. package/src/sample-dapp/contracts.mjs +0 -14
  173. package/src/sample-dapp/deploy.mjs +0 -40
  174. package/src/sample-dapp/index.mjs +0 -128
@@ -1,74 +1,81 @@
1
- import { createAztecNodeClient, createLogger, sleep } from '@aztec/aztec.js';
1
+ import { createLogger } from '@aztec/aztec.js/log';
2
+ import { promiseWithResolvers } from '@aztec/foundation/promise';
3
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
4
+ import { schemas } from '@aztec/foundation/schemas';
5
+ import { sleep } from '@aztec/foundation/sleep';
6
+ import { createAztecNodeAdminClient, createAztecNodeClient } from '@aztec/stdlib/interfaces/client';
2
7
  import { exec, execSync, spawn } from 'child_process';
3
8
  import path from 'path';
4
9
  import { promisify } from 'util';
10
+ import { createPublicClient, fallback, http } from 'viem';
5
11
  import { z } from 'zod';
6
- import { AlertChecker } from '../quality_of_service/alert_checker.js';
7
12
  const execAsync = promisify(exec);
8
13
  const logger = createLogger('e2e:k8s-utils');
9
- const ethereumHostsSchema = z.string().refine((str)=>str.split(',').every((url)=>{
10
- try {
11
- new URL(url.trim());
12
- return true;
13
- } catch {
14
- return false;
15
- }
16
- }), 'ETHEREUM_HOSTS must be a comma-separated list of valid URLs');
17
- const k8sLocalConfigSchema = z.object({
18
- ETHEREUM_SLOT_DURATION: z.coerce.number().min(1, 'ETHEREUM_SLOT_DURATION env variable must be set'),
19
- AZTEC_SLOT_DURATION: z.coerce.number().min(1, 'AZTEC_SLOT_DURATION env variable must be set'),
20
- AZTEC_EPOCH_DURATION: z.coerce.number().min(1, 'AZTEC_EPOCH_DURATION env variable must be set'),
21
- AZTEC_PROOF_SUBMISSION_WINDOW: z.coerce.number().min(1, 'AZTEC_PROOF_SUBMISSION_WINDOW env variable must be set'),
22
- INSTANCE_NAME: z.string().min(1, 'INSTANCE_NAME env variable must be set'),
23
- NAMESPACE: z.string().min(1, 'NAMESPACE env variable must be set'),
24
- CONTAINER_NODE_PORT: z.coerce.number().default(8080),
25
- CONTAINER_SEQUENCER_PORT: z.coerce.number().default(8080),
26
- CONTAINER_PROVER_NODE_PORT: z.coerce.number().default(8080),
27
- CONTAINER_PXE_PORT: z.coerce.number().default(8080),
28
- CONTAINER_ETHEREUM_PORT: z.coerce.number().default(8545),
29
- CONTAINER_METRICS_PORT: z.coerce.number().default(80),
30
- GRAFANA_PASSWORD: z.string().optional(),
31
- METRICS_API_PATH: z.string().default('/api/datasources/proxy/uid/spartan-metrics-prometheus/api/v1'),
32
- SPARTAN_DIR: z.string().min(1, 'SPARTAN_DIR env variable must be set'),
33
- ETHEREUM_HOSTS: ethereumHostsSchema.optional(),
34
- L1_ACCOUNT_MNEMONIC: z.string().default('test test test test test test test test test test test junk'),
35
- SEPOLIA_RUN: z.string().default('false'),
36
- K8S: z.literal('local')
37
- });
38
- const k8sGCloudConfigSchema = k8sLocalConfigSchema.extend({
39
- K8S: z.literal('gcloud'),
40
- CLUSTER_NAME: z.string().min(1, 'CLUSTER_NAME env variable must be set'),
41
- REGION: z.string().min(1, 'REGION env variable must be set')
42
- });
43
- const directConfigSchema = z.object({
44
- PXE_URL: z.string().url('PXE_URL must be a valid URL'),
45
- NODE_URL: z.string().url('NODE_URL must be a valid URL'),
46
- ETHEREUM_HOSTS: ethereumHostsSchema,
47
- K8S: z.literal('false')
14
+ const testConfigSchema = z.object({
15
+ NAMESPACE: z.string().default('scenario'),
16
+ REAL_VERIFIER: schemas.Boolean.optional().default(true),
17
+ CREATE_ETH_DEVNET: schemas.Boolean.optional().default(false),
18
+ L1_RPC_URLS_JSON: z.string().optional(),
19
+ L1_ACCOUNT_MNEMONIC: z.string().optional(),
20
+ AZTEC_SLOT_DURATION: z.coerce.number().optional().default(24),
21
+ AZTEC_PROOF_SUBMISSION_WINDOW: z.coerce.number().optional().default(5)
48
22
  });
49
- const envSchema = z.discriminatedUnion('K8S', [
50
- k8sLocalConfigSchema,
51
- k8sGCloudConfigSchema,
52
- directConfigSchema
53
- ]);
54
- export function isK8sConfig(config) {
55
- return config.K8S === 'local' || config.K8S === 'gcloud';
56
- }
57
- export function isGCloudConfig(config) {
58
- return config.K8S === 'gcloud';
59
- }
60
23
  export function setupEnvironment(env) {
61
- const config = envSchema.parse(env);
62
- if (isGCloudConfig(config)) {
63
- const command = `gcloud container clusters get-credentials ${config.CLUSTER_NAME} --region=${config.REGION}`;
64
- execSync(command);
65
- }
24
+ const config = testConfigSchema.parse(env);
25
+ logger.warn(`Loaded env config`, config);
66
26
  return config;
67
27
  }
28
+ /**
29
+ * @param path - The path to the script, relative to the project root
30
+ * @param args - The arguments to pass to the script
31
+ * @param logger - The logger to use
32
+ * @returns The exit code of the script
33
+ */ function runScript(path, args, logger, env) {
34
+ const childProcess = spawn(path, args, {
35
+ stdio: [
36
+ 'ignore',
37
+ 'pipe',
38
+ 'pipe'
39
+ ],
40
+ env: env ? {
41
+ ...process.env,
42
+ ...env
43
+ } : process.env
44
+ });
45
+ return new Promise((resolve, reject)=>{
46
+ childProcess.on('close', (code)=>resolve(code ?? 0));
47
+ childProcess.on('error', reject);
48
+ childProcess.stdout?.on('data', (data)=>{
49
+ logger.info(data.toString());
50
+ });
51
+ childProcess.stderr?.on('data', (data)=>{
52
+ logger.error(data.toString());
53
+ });
54
+ });
55
+ }
56
+ export function getAztecBin() {
57
+ return path.join(getGitProjectRoot(), 'yarn-project/aztec/dest/bin/index.js');
58
+ }
59
+ /**
60
+ * Runs the Aztec binary
61
+ * @param args - The arguments to pass to the Aztec binary
62
+ * @param logger - The logger to use
63
+ * @param env - Optional environment variables to set for the process
64
+ * @returns The exit code of the Aztec binary
65
+ */ export function runAztecBin(args, logger, env) {
66
+ return runScript('node', [
67
+ getAztecBin(),
68
+ ...args
69
+ ], logger, env);
70
+ }
71
+ export function runProjectScript(script, args, logger, env) {
72
+ const scriptPath = script.startsWith('/') ? script : path.join(getGitProjectRoot(), script);
73
+ return runScript(scriptPath, args, logger, env);
74
+ }
68
75
  export async function startPortForward({ resource, namespace, containerPort, hostPort }) {
69
76
  const hostPortAsString = hostPort ? hostPort.toString() : '';
70
- logger.info(`kubectl port-forward -n ${namespace} ${resource} ${hostPortAsString}:${containerPort}`);
71
- const process = spawn('kubectl', [
77
+ logger.debug(`kubectl port-forward -n ${namespace} ${resource} ${hostPortAsString}:${containerPort}`);
78
+ const process1 = spawn('kubectl', [
72
79
  'port-forward',
73
80
  '-n',
74
81
  namespace,
@@ -85,61 +92,107 @@ export async function startPortForward({ resource, namespace, containerPort, hos
85
92
  });
86
93
  let isResolved = false;
87
94
  const connected = new Promise((resolve)=>{
88
- process.stdout?.on('data', (data)=>{
95
+ process1.stdout?.on('data', (data)=>{
89
96
  const str = data.toString();
90
97
  if (!isResolved && str.includes('Forwarding from')) {
91
98
  isResolved = true;
92
- logger.info(str);
99
+ logger.debug(`Port forward for ${resource}: ${str}`);
93
100
  const port = str.search(/:\d+/);
94
101
  if (port === -1) {
95
102
  throw new Error('Port not found in port forward output');
96
103
  }
97
104
  const portNumber = parseInt(str.slice(port + 1));
98
- logger.info(`Port forward connected: ${portNumber}`);
99
- logger.info(`Port forward connected: ${portNumber}`);
105
+ logger.verbose(`Port forwarded for ${resource} at ${portNumber}:${containerPort}`);
100
106
  resolve(portNumber);
101
107
  } else {
102
108
  logger.silent(str);
103
109
  }
104
110
  });
105
- process.stderr?.on('data', (data)=>{
106
- logger.info(data.toString());
111
+ process1.stderr?.on('data', (data)=>{
112
+ logger.verbose(`Port forward for ${resource}: ${data.toString()}`);
107
113
  // It's a strange thing:
108
114
  // If we don't pipe stderr, then the port forwarding does not work.
109
115
  // Log to silent because this doesn't actually report errors,
110
116
  // just extremely verbose debug logs.
111
117
  logger.silent(data.toString());
112
118
  });
113
- process.on('close', ()=>{
119
+ process1.on('close', ()=>{
114
120
  if (!isResolved) {
115
121
  isResolved = true;
116
- logger.warn('Port forward closed before connection established');
122
+ logger.warn(`Port forward for ${resource} closed before connection established`);
117
123
  resolve(0);
118
124
  }
119
125
  });
120
- process.on('error', (error)=>{
121
- logger.error(`Port forward error: ${error}`);
126
+ process1.on('error', (error)=>{
127
+ logger.error(`Port forward for ${resource} error: ${error}`);
122
128
  resolve(0);
123
129
  });
124
- process.on('exit', (code)=>{
125
- logger.info(`Port forward exited with code ${code}`);
130
+ process1.on('exit', (code)=>{
131
+ logger.verbose(`Port forward for ${resource} exited with code ${code}`);
126
132
  resolve(0);
127
133
  });
128
134
  });
129
135
  const port = await connected;
130
136
  return {
131
- process,
137
+ process: process1,
132
138
  port
133
139
  };
134
140
  }
141
+ export function getExternalIP(namespace, serviceName) {
142
+ const { promise, resolve, reject } = promiseWithResolvers();
143
+ const process1 = spawn('kubectl', [
144
+ 'get',
145
+ 'service',
146
+ '-n',
147
+ namespace,
148
+ `${namespace}-${serviceName}`,
149
+ '--output',
150
+ "jsonpath='{.status.loadBalancer.ingress[0].ip}'"
151
+ ], {
152
+ stdio: 'pipe'
153
+ });
154
+ let ip = '';
155
+ process1.stdout.on('data', (data)=>{
156
+ ip += data;
157
+ });
158
+ process1.on('error', (err)=>{
159
+ reject(err);
160
+ });
161
+ process1.on('exit', ()=>{
162
+ // kubectl prints JSON. Remove the quotes
163
+ resolve(ip.replace(/"|'/g, ''));
164
+ });
165
+ return promise;
166
+ }
167
+ export function startPortForwardForRPC(namespace, resourceType = 'services', index = 0) {
168
+ return startPortForward({
169
+ resource: `${resourceType}/${namespace}-rpc-aztec-node-${index}`,
170
+ namespace,
171
+ containerPort: 8080
172
+ });
173
+ }
174
+ export function startPortForwardForEthereum(namespace) {
175
+ return startPortForward({
176
+ resource: `services/${namespace}-eth-execution`,
177
+ namespace,
178
+ containerPort: 8545
179
+ });
180
+ }
135
181
  export async function deleteResourceByName({ resource, namespace, name, force = false }) {
136
182
  const command = `kubectl delete ${resource} ${name} -n ${namespace} --ignore-not-found=true --wait=true ${force ? '--force' : ''}`;
137
183
  logger.info(`command: ${command}`);
138
184
  const { stdout } = await execAsync(command);
139
185
  return stdout;
140
186
  }
141
- export async function deleteResourceByLabel({ resource, namespace, label }) {
142
- const command = `kubectl delete ${resource} -l ${label} -n ${namespace} --ignore-not-found=true --wait=true`;
187
+ export async function deleteResourceByLabel({ resource, namespace, label, timeout = '5m', force = false }) {
188
+ // Check if the resource type exists before attempting to delete
189
+ try {
190
+ await execAsync(`kubectl api-resources --api-group="" --no-headers -o name | grep -q "^${resource}$" || kubectl api-resources --no-headers -o name | grep -q "^${resource}$"`);
191
+ } catch (error) {
192
+ logger.warn(`Resource type '${resource}' not found in cluster, skipping deletion ${error}`);
193
+ return '';
194
+ }
195
+ const command = `kubectl delete ${resource} -l ${label} -n ${namespace} --ignore-not-found=true --wait=true --timeout=${timeout} ${force ? '--force' : ''}`;
143
196
  logger.info(`command: ${command}`);
144
197
  const { stdout } = await execAsync(command);
145
198
  return stdout;
@@ -153,8 +206,12 @@ export async function waitForResourceByLabel({ resource, label, namespace, condi
153
206
  export function getChartDir(spartanDir, chartName) {
154
207
  return path.join(spartanDir.trim(), chartName);
155
208
  }
209
+ function shellQuote(value) {
210
+ // Single-quote safe shell escaping: ' -> '\''
211
+ return `'${value.replace(/'/g, "'\\''")}'`;
212
+ }
156
213
  function valuesToArgs(values) {
157
- return Object.entries(values).map(([key, value])=>`--set ${key}=${value}`).join(' ');
214
+ return Object.entries(values).map(([key, value])=>typeof value === 'number' || typeof value === 'boolean' ? `--set ${key}=${value}` : `--set-string ${key}=${shellQuote(String(value))}`).join(' ');
158
215
  }
159
216
  function createHelmCommand({ instanceName, helmChartDir, namespace, valuesFile, timeout, values, reuseValues = false }) {
160
217
  const valuesFileArgs = valuesFile ? `--values ${helmChartDir}/values/${valuesFile}` : '';
@@ -167,6 +224,33 @@ async function execHelmCommand(args) {
167
224
  const { stdout } = await execAsync(helmCommand);
168
225
  return stdout;
169
226
  }
227
+ export async function cleanHelm(instanceName, namespace, logger) {
228
+ // uninstall the helm chart if it exists
229
+ logger.info(`Uninstalling helm chart ${instanceName}`);
230
+ await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`);
231
+ // and delete the chaos-mesh resources created by this release
232
+ const deleteByLabel = async (resource)=>{
233
+ const args = {
234
+ resource,
235
+ namespace: namespace,
236
+ label: `app.kubernetes.io/instance=${instanceName}`
237
+ };
238
+ logger.info(`Deleting ${resource} resources for release ${instanceName}`);
239
+ await deleteResourceByLabel(args).catch((e)=>{
240
+ logger.error(`Error deleting ${resource}: ${e}`);
241
+ logger.info(`Force deleting ${resource}`);
242
+ return deleteResourceByLabel({
243
+ ...args,
244
+ force: true
245
+ });
246
+ });
247
+ };
248
+ await deleteByLabel('podchaos');
249
+ await deleteByLabel('networkchaos');
250
+ await deleteByLabel('podnetworkchaos');
251
+ await deleteByLabel('workflows');
252
+ await deleteByLabel('workflownodes');
253
+ }
170
254
  /**
171
255
  * Installs a Helm chart with the given parameters.
172
256
  * @param instanceName - The name of the Helm chart instance.
@@ -183,31 +267,14 @@ async function execHelmCommand(args) {
183
267
  * const stdout = await installChaosMeshChart({ instanceName: 'force-reorg', targetNamespace: 'smoke', valuesFile: 'prover-failure.yaml'});
184
268
  * console.log(stdout);
185
269
  * ```
186
- */ export async function installChaosMeshChart({ instanceName, targetNamespace, valuesFile, helmChartDir, chaosMeshNamespace = 'chaos-mesh', timeout = '5m', clean = true, values = {}, logger }) {
270
+ */ export async function installChaosMeshChart({ instanceName, targetNamespace, valuesFile, helmChartDir, timeout = '10m', clean = true, values = {}, logger }) {
187
271
  if (clean) {
188
- // uninstall the helm chart if it exists
189
- logger.info(`Uninstalling helm chart ${instanceName}`);
190
- await execAsync(`helm uninstall ${instanceName} --namespace ${chaosMeshNamespace} --wait --ignore-not-found`);
191
- // and delete the podchaos resource
192
- const deleteArgs = {
193
- resource: 'podchaos',
194
- namespace: chaosMeshNamespace,
195
- name: `${targetNamespace}-${instanceName}`
196
- };
197
- logger.info(`Deleting podchaos resource`);
198
- await deleteResourceByName(deleteArgs).catch((e)=>{
199
- logger.error(`Error deleting podchaos resource: ${e}`);
200
- logger.info(`Force deleting podchaos resource`);
201
- return deleteResourceByName({
202
- ...deleteArgs,
203
- force: true
204
- });
205
- });
272
+ await cleanHelm(instanceName, targetNamespace, logger);
206
273
  }
207
274
  return execHelmCommand({
208
275
  instanceName,
209
276
  helmChartDir,
210
- namespace: chaosMeshNamespace,
277
+ namespace: targetNamespace,
211
278
  valuesFile,
212
279
  timeout,
213
280
  values: {
@@ -260,13 +327,14 @@ export function applyBootNodeFailure({ namespace, spartanDir, durationSeconds, l
260
327
  logger
261
328
  });
262
329
  }
263
- export function applyValidatorKill({ namespace, spartanDir, logger }) {
330
+ export function applyValidatorKill({ namespace, spartanDir, logger, values }) {
264
331
  return installChaosMeshChart({
265
332
  instanceName: 'validator-kill',
266
333
  targetNamespace: namespace,
267
334
  valuesFile: 'validator-kill.yaml',
268
335
  helmChartDir: getChartDir(spartanDir, 'aztec-chaos-scenarios'),
269
- logger
336
+ logger,
337
+ values
270
338
  });
271
339
  }
272
340
  export function applyNetworkShaping({ valuesFile, namespace, spartanDir, logger }) {
@@ -298,16 +366,189 @@ export async function restartBot(namespace, logger) {
298
366
  await deleteResourceByLabel({
299
367
  resource: 'pods',
300
368
  namespace,
301
- label: 'app=bot'
369
+ label: 'app.kubernetes.io/name=bot'
302
370
  });
303
371
  await sleep(10 * 1000);
372
+ // Some bot images may take time to report Ready due to heavy boot-time proving.
373
+ // Waiting for PodReadyToStartContainers ensures the pod is scheduled and starting without blocking on full readiness.
304
374
  await waitForResourceByLabel({
305
375
  resource: 'pods',
306
376
  namespace,
307
- label: 'app=bot'
377
+ label: 'app.kubernetes.io/name=bot',
378
+ condition: 'PodReadyToStartContainers'
308
379
  });
309
380
  logger.info(`Bot restarted`);
310
381
  }
382
+ /**
383
+ * Installs or upgrades the transfer bot Helm release for the given namespace.
384
+ * Intended for test setup to enable L2 traffic generation only when needed.
385
+ */ export async function installTransferBot({ namespace, spartanDir, logger, replicas = 1, txIntervalSeconds = 10, followChain = 'PENDING', mnemonic = process.env.LABS_INFRA_MNEMONIC ?? 'test test test test test test test test test test test junk', mnemonicStartIndex, botPrivateKey = process.env.BOT_TRANSFERS_L2_PRIVATE_KEY ?? '0xcafe01', nodeUrl, timeout = '15m', reuseValues = true, aztecSlotDuration = Number(process.env.AZTEC_SLOT_DURATION ?? 12) }) {
386
+ const instanceName = `${namespace}-bot-transfers`;
387
+ const helmChartDir = getChartDir(spartanDir, 'aztec-bot');
388
+ const resolvedNodeUrl = nodeUrl ?? `http://${namespace}-rpc-aztec-node.${namespace}.svc.cluster.local:8080`;
389
+ logger.info(`Installing/upgrading transfer bot: replicas=${replicas}, followChain=${followChain}`);
390
+ const values = {
391
+ 'bot.replicaCount': replicas,
392
+ 'bot.txIntervalSeconds': txIntervalSeconds,
393
+ 'bot.followChain': followChain,
394
+ 'bot.botPrivateKey': botPrivateKey,
395
+ 'bot.nodeUrl': resolvedNodeUrl,
396
+ 'bot.mnemonic': mnemonic,
397
+ 'bot.feePaymentMethod': 'fee_juice',
398
+ 'aztec.slotDuration': aztecSlotDuration,
399
+ // Ensure bot can reach its own PXE started in-process (default rpc.port is 8080)
400
+ // Note: since aztec-bot depends on aztec-node with alias `bot`, env vars go under `bot.node.env`.
401
+ 'bot.node.env.BOT_PXE_URL': 'http://127.0.0.1:8080',
402
+ // Provide L1 execution RPC for bridging fee juice
403
+ 'bot.node.env.ETHEREUM_HOSTS': `http://${namespace}-eth-execution.${namespace}.svc.cluster.local:8545`,
404
+ // Provide L1 mnemonic for bridging (falls back to labs mnemonic)
405
+ 'bot.node.env.BOT_L1_MNEMONIC': mnemonic
406
+ };
407
+ // Ensure we derive a funded L1 key (index 0 is funded on anvil default mnemonic)
408
+ if (mnemonicStartIndex === undefined) {
409
+ values['bot.mnemonicStartIndex'] = 0;
410
+ }
411
+ // Also pass a funded private key directly if available
412
+ if (process.env.FUNDING_PRIVATE_KEY) {
413
+ values['bot.node.env.BOT_L1_PRIVATE_KEY'] = process.env.FUNDING_PRIVATE_KEY;
414
+ }
415
+ // Align bot image with the running network image: prefer env var, else detect from a validator pod
416
+ let repositoryFromEnv;
417
+ let tagFromEnv;
418
+ const aztecDockerImage = process.env.AZTEC_DOCKER_IMAGE;
419
+ if (aztecDockerImage && aztecDockerImage.includes(':')) {
420
+ const lastColon = aztecDockerImage.lastIndexOf(':');
421
+ repositoryFromEnv = aztecDockerImage.slice(0, lastColon);
422
+ tagFromEnv = aztecDockerImage.slice(lastColon + 1);
423
+ }
424
+ let repository = repositoryFromEnv;
425
+ let tag = tagFromEnv;
426
+ if (!repository || !tag) {
427
+ try {
428
+ const { stdout } = await execAsync(`kubectl get pods -l app.kubernetes.io/component=validator -n ${namespace} -o jsonpath='{.items[0].spec.containers[?(@.name=="aztec")].image}' | cat`);
429
+ const image = stdout.trim().replace(/^'|'$/g, '');
430
+ if (image && image.includes(':')) {
431
+ const lastColon = image.lastIndexOf(':');
432
+ repository = image.slice(0, lastColon);
433
+ tag = image.slice(lastColon + 1);
434
+ }
435
+ } catch (err) {
436
+ logger.warn(`Could not detect aztec image from validator pod: ${String(err)}`);
437
+ }
438
+ }
439
+ if (repository && tag) {
440
+ values['global.aztecImage.repository'] = repository;
441
+ values['global.aztecImage.tag'] = tag;
442
+ }
443
+ if (mnemonicStartIndex !== undefined) {
444
+ values['bot.mnemonicStartIndex'] = typeof mnemonicStartIndex === 'string' ? mnemonicStartIndex : Number(mnemonicStartIndex);
445
+ }
446
+ await execHelmCommand({
447
+ instanceName,
448
+ helmChartDir,
449
+ namespace,
450
+ valuesFile: undefined,
451
+ timeout,
452
+ values: values,
453
+ reuseValues
454
+ });
455
+ if (replicas > 0) {
456
+ await waitForResourceByLabel({
457
+ resource: 'pods',
458
+ namespace,
459
+ label: 'app.kubernetes.io/name=bot',
460
+ condition: 'PodReadyToStartContainers'
461
+ });
462
+ }
463
+ }
464
+ /**
465
+ * Uninstalls the transfer bot Helm release from the given namespace.
466
+ * Intended for test teardown to clean up bot resources.
467
+ */ export async function uninstallTransferBot(namespace, logger) {
468
+ const instanceName = `${namespace}-bot-transfers`;
469
+ logger.info(`Uninstalling transfer bot release ${instanceName}`);
470
+ await execAsync(`helm uninstall ${instanceName} --namespace ${namespace} --wait --ignore-not-found`);
471
+ // Ensure any leftover pods are removed
472
+ await deleteResourceByLabel({
473
+ resource: 'pods',
474
+ namespace,
475
+ label: 'app.kubernetes.io/name=bot'
476
+ }).catch(()=>undefined);
477
+ }
478
+ /**
479
+ * Enables or disables probabilistic transaction dropping on validators and waits for rollout.
480
+ * Wired to env vars P2P_DROP_TX and P2P_DROP_TX_CHANCE via Helm values.
481
+ */ export async function setValidatorTxDrop({ namespace, enabled, probability, logger }) {
482
+ const drop = enabled ? 'true' : 'false';
483
+ const prob = String(probability);
484
+ const selectors = [
485
+ 'app=validator',
486
+ 'app.kubernetes.io/component=validator'
487
+ ];
488
+ let updated = false;
489
+ for (const selector of selectors){
490
+ try {
491
+ const list = await execAsync(`kubectl get statefulset -l ${selector} -n ${namespace} --no-headers -o name | cat`);
492
+ const names = list.stdout.split('\n').map((s)=>s.trim()).filter(Boolean);
493
+ if (names.length === 0) {
494
+ continue;
495
+ }
496
+ const cmd = `kubectl set env statefulset -l ${selector} -n ${namespace} P2P_DROP_TX=${drop} P2P_DROP_TX_CHANCE=${prob}`;
497
+ logger.info(`command: ${cmd}`);
498
+ await execAsync(cmd);
499
+ updated = true;
500
+ } catch (e) {
501
+ logger.warn(`Failed to update validators with selector ${selector}: ${String(e)}`);
502
+ }
503
+ }
504
+ if (!updated) {
505
+ logger.warn(`No validator StatefulSets found in ${namespace}. Skipping tx drop toggle.`);
506
+ return;
507
+ }
508
+ // Restart validator pods to ensure env vars take effect and wait for readiness
509
+ await restartValidators(namespace, logger);
510
+ }
511
+ export async function restartValidators(namespace, logger) {
512
+ const selectors = [
513
+ 'app=validator',
514
+ 'app.kubernetes.io/component=validator'
515
+ ];
516
+ let any = false;
517
+ for (const selector of selectors){
518
+ try {
519
+ const { stdout } = await execAsync(`kubectl get pods -l ${selector} -n ${namespace} --no-headers -o name | cat`);
520
+ if (!stdout || stdout.trim().length === 0) {
521
+ continue;
522
+ }
523
+ any = true;
524
+ await deleteResourceByLabel({
525
+ resource: 'pods',
526
+ namespace,
527
+ label: selector
528
+ });
529
+ } catch (e) {
530
+ logger.warn(`Error restarting validator pods with selector ${selector}: ${String(e)}`);
531
+ }
532
+ }
533
+ if (!any) {
534
+ logger.warn(`No validator pods found to restart in ${namespace}.`);
535
+ return;
536
+ }
537
+ // Wait for either label to be Ready
538
+ for (const selector of selectors){
539
+ try {
540
+ await waitForResourceByLabel({
541
+ resource: 'pods',
542
+ namespace,
543
+ label: selector
544
+ });
545
+ return;
546
+ } catch {
547
+ // try next
548
+ }
549
+ }
550
+ logger.warn(`Validator pods did not report Ready; continuing.`);
551
+ }
311
552
  export async function enableValidatorDynamicBootNode(instanceName, namespace, spartanDir, logger) {
312
553
  logger.info(`Enabling validator dynamic boot node`);
313
554
  await execHelmCommand({
@@ -323,55 +564,107 @@ export async function enableValidatorDynamicBootNode(instanceName, namespace, sp
323
564
  });
324
565
  logger.info(`Validator dynamic boot node enabled`);
325
566
  }
326
- export async function runAlertCheck(config, alerts, logger) {
327
- if (isK8sConfig(config)) {
328
- const { process, port } = await startPortForward({
329
- resource: `svc/metrics-grafana`,
330
- namespace: 'metrics',
331
- containerPort: config.CONTAINER_METRICS_PORT
332
- });
333
- const alertChecker = new AlertChecker(logger, {
334
- grafanaEndpoint: `http://localhost:${port}${config.METRICS_API_PATH}`,
335
- grafanaCredentials: `admin:${config.GRAFANA_PASSWORD}`
336
- });
337
- await alertChecker.runAlertCheck(alerts);
338
- process.kill();
339
- } else {
340
- logger.info('Not running alert check in non-k8s environment');
341
- }
342
- }
343
- export async function updateSequencerConfig(url, config) {
344
- const node = createAztecNodeClient(url);
345
- await node.setConfig(config);
346
- }
347
567
  export async function getSequencers(namespace) {
348
- const command = `kubectl get pods -l app=validator -n ${namespace} -o jsonpath='{.items[*].metadata.name}'`;
568
+ const command = `kubectl get pods -l app.kubernetes.io/component=validator -n ${namespace} -o jsonpath='{.items[*].metadata.name}'`;
349
569
  const { stdout } = await execAsync(command);
350
- return stdout.split(' ');
570
+ const sequencers = stdout.split(' ');
571
+ logger.verbose(`Found sequencer pods ${sequencers.join(', ')}`);
572
+ return sequencers;
573
+ }
574
+ export function updateSequencersConfig(env, config) {
575
+ return withSequencersAdmin(env, async (client)=>{
576
+ await client.setConfig(config);
577
+ return client.getConfig();
578
+ });
351
579
  }
352
- export async function updateK8sSequencersConfig(args) {
353
- const { containerPort, namespace, config } = args;
580
+ export function getSequencersConfig(env) {
581
+ return withSequencersAdmin(env, (client)=>client.getConfig());
582
+ }
583
+ export async function withSequencersAdmin(env, fn) {
584
+ const adminContainerPort = 8880;
585
+ const namespace = env.NAMESPACE;
354
586
  const sequencers = await getSequencers(namespace);
587
+ const results = [];
355
588
  for (const sequencer of sequencers){
356
- const { process, port } = await startPortForward({
589
+ const { process: process1, port } = await startPortForward({
357
590
  resource: `pod/${sequencer}`,
358
591
  namespace,
359
- containerPort
592
+ containerPort: adminContainerPort
360
593
  });
361
594
  const url = `http://localhost:${port}`;
362
- await updateSequencerConfig(url, config);
363
- process.kill();
595
+ await retry(()=>fetch(`${url}/status`).then((res)=>res.status === 200), 'forward node admin port', makeBackoff([
596
+ 1,
597
+ 1,
598
+ 2,
599
+ 6
600
+ ]), logger, true);
601
+ const client = createAztecNodeAdminClient(url);
602
+ results.push(await fn(client));
603
+ process1.kill();
364
604
  }
605
+ return results;
365
606
  }
366
- export async function updateSequencersConfig(env, config) {
367
- if (isK8sConfig(env)) {
368
- await updateK8sSequencersConfig({
369
- containerPort: env.CONTAINER_NODE_PORT,
370
- namespace: env.NAMESPACE,
371
- config
607
+ /**
608
+ * Returns a public viem client to the eth execution node. If it was part of a local eth devnet,
609
+ * it first port-forwards the service and points to it. Otherwise, just uses the external RPC url.
610
+ */ export async function getPublicViemClient(env, /** If set, will push the new process into it */ processes) {
611
+ const { NAMESPACE, CREATE_ETH_DEVNET, L1_RPC_URLS_JSON } = env;
612
+ if (CREATE_ETH_DEVNET) {
613
+ logger.info(`Creating port forward to eth execution node`);
614
+ const { process: process1, port } = await startPortForward({
615
+ resource: `svc/${NAMESPACE}-eth-execution`,
616
+ namespace: NAMESPACE,
617
+ containerPort: 8545
618
+ });
619
+ const url = `http://127.0.0.1:${port}`;
620
+ const client = createPublicClient({
621
+ transport: fallback([
622
+ http(url)
623
+ ])
372
624
  });
625
+ if (processes) {
626
+ processes.push(process1);
627
+ }
628
+ return {
629
+ url,
630
+ client,
631
+ process: process1
632
+ };
373
633
  } else {
374
- await updateSequencerConfig(env.NODE_URL, config);
634
+ logger.info(`Connecting to the eth execution node at ${L1_RPC_URLS_JSON}`);
635
+ if (!L1_RPC_URLS_JSON) {
636
+ throw new Error(`L1_RPC_URLS_JSON is not defined`);
637
+ }
638
+ const client = createPublicClient({
639
+ transport: fallback([
640
+ http(L1_RPC_URLS_JSON)
641
+ ])
642
+ });
643
+ return {
644
+ url: L1_RPC_URLS_JSON,
645
+ client
646
+ };
647
+ }
648
+ }
649
+ /** Queries an Aztec node for the L1 deployment addresses */ export async function getL1DeploymentAddresses(env) {
650
+ let forwardProcess;
651
+ try {
652
+ const [sequencer] = await getSequencers(env.NAMESPACE);
653
+ const { process: process1, port } = await startPortForward({
654
+ resource: `pod/${sequencer}`,
655
+ namespace: env.NAMESPACE,
656
+ containerPort: 8080
657
+ });
658
+ forwardProcess = process1;
659
+ const url = `http://127.0.0.1:${port}`;
660
+ const node = createAztecNodeClient(url);
661
+ return await retry(()=>node.getNodeInfo().then((i)=>i.l1ContractAddresses), 'get node info', makeBackoff([
662
+ 1,
663
+ 3,
664
+ 6
665
+ ]), logger);
666
+ } finally{
667
+ forwardProcess?.kill();
375
668
  }
376
669
  }
377
670
  /**
@@ -443,3 +736,47 @@ export async function updateSequencersConfig(env, config) {
443
736
  label: 'app=pxe'
444
737
  });
445
738
  }
739
+ /**
740
+ * Returns the absolute path to the git repository root
741
+ */ export function getGitProjectRoot() {
742
+ try {
743
+ const rootDir = execSync('git rev-parse --show-toplevel', {
744
+ encoding: 'utf-8',
745
+ stdio: [
746
+ 'ignore',
747
+ 'pipe',
748
+ 'ignore'
749
+ ]
750
+ }).trim();
751
+ return rootDir;
752
+ } catch (error) {
753
+ throw new Error(`Failed to determine git project root: ${error}`);
754
+ }
755
+ }
756
+ /** Returns a client to the RPC of the given sequencer (defaults to first) */ export async function getNodeClient(env, index = 0) {
757
+ const namespace = env.NAMESPACE;
758
+ const containerPort = 8080;
759
+ const sequencers = await getSequencers(namespace);
760
+ const sequencer = sequencers[index];
761
+ if (!sequencer) {
762
+ throw new Error(`No sequencer found at index ${index} in namespace ${namespace}`);
763
+ }
764
+ const { process: process1, port } = await startPortForward({
765
+ resource: `pod/${sequencer}`,
766
+ namespace,
767
+ containerPort
768
+ });
769
+ const url = `http://localhost:${port}`;
770
+ await retry(()=>fetch(`${url}/status`).then((res)=>res.status === 200), 'forward port', makeBackoff([
771
+ 1,
772
+ 1,
773
+ 2,
774
+ 6
775
+ ]), logger, true);
776
+ const client = createAztecNodeClient(url);
777
+ return {
778
+ node: client,
779
+ port,
780
+ process: process1
781
+ };
782
+ }