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