From 3896f1027d769724887520defd7716f70d9755d2 Mon Sep 17 00:00:00 2001 From: Gorka Date: Thu, 30 Jul 2026 16:43:03 -0300 Subject: [PATCH] testnet: de-list the ephemeral e2e councils after each suite run. Both suites leave their per-run council listed on council-platform, so every run adds a junk council to the public network dashboard. Each suite now sweeps all councils owned by its admin key at the end of a passed run; the sweep only covers crashed runs too when MASTER_SECRET is set, since random keys are unrecoverable. --- lifecycle/testnet-verify.ts | 18 +++++++++++++++ testnet/cleanup-councils.ts | 45 +++++++++++++++++++++++++++++++++++++ testnet/main.ts | 18 +++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 testnet/cleanup-councils.ts diff --git a/lifecycle/testnet-verify.ts b/lifecycle/testnet-verify.ts index bf1cd45..f1ee676 100644 --- a/lifecycle/testnet-verify.ts +++ b/lifecycle/testnet-verify.ts @@ -57,6 +57,7 @@ import { withdraw } from "../lib/client/withdraw.ts"; import { registerEntity } from "../lib/client/register-entity.ts"; import { sdkTracer, withE2ESpan, writeTraceIds } from "../lib/client/tracer.ts"; import { exerciseCouncilSpans } from "../lib/exercise-cp-spans.ts"; +import { cleanupOwnedCouncils } from "../testnet/cleanup-councils.ts"; // ─── Events-capture contract ────────────────────────────────────────── /** @@ -698,6 +699,23 @@ export async function main() { await writeTraceIds(); + // ── Cleanup — de-list verify councils ───────────────────────────── + // Loud but non-fatal: a failed cleanup must not turn a passed lifecycle + // flow into a failed suite, but it must show in the logs. + console.log("\n[cleanup] De-list verify councils"); + try { + await cleanupOwnedCouncils( + COUNCIL_URL, + await walletAuth(COUNCIL_URL, "/api/v1/admin/auth", admin), + ); + } catch (err) { + console.error( + ` WARNING: council cleanup failed, this run's council stays listed on the network dashboard: ${ + (err as Error).message + }`, + ); + } + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); console.log( `\n=== Testnet Lifecycle Verification PASSED in ${elapsed}s ===\n`, diff --git a/testnet/cleanup-councils.ts b/testnet/cleanup-councils.ts new file mode 100644 index 0000000..2148d46 --- /dev/null +++ b/testnet/cleanup-councils.ts @@ -0,0 +1,45 @@ +/** + * Post-run council cleanup shared by the testnet suites. + * + * testnet/main.ts and lifecycle/testnet-verify.ts each create an ephemeral + * listed council per run; without a sweep those accumulate on council-platform + * and pollute the public network dashboard (counters, directory, live feed). + * + * The sweep deletes every council OWNED by the suite's admin key rather than + * just this run's id: with MASTER_SECRET-derived keys that also collects + * leftovers from earlier runs that died before their own cleanup. With random + * keys (MASTER_SECRET unset) only this run's council is owned — and a run + * that dies mid-flow leaks a council no key can ever delete again, so + * deployed-testnet invocations should always set MASTER_SECRET. + */ +export async function cleanupOwnedCouncils( + councilUrl: string, + adminJwt: string, +): Promise { + const listRes = await fetch(`${councilUrl}/api/v1/council/list`, { + headers: { Authorization: `Bearer ${adminJwt}` }, + }); + if (!listRes.ok) { + throw new Error( + `Council list failed: ${listRes.status} ${await listRes.text()}`, + ); + } + const { data } = await listRes.json() as { + data?: { councilId: string; name: string }[]; + }; + for (const council of data ?? []) { + const delRes = await fetch( + `${councilUrl}/api/v1/council/metadata?councilId=${ + encodeURIComponent(council.councilId) + }`, + { method: "DELETE", headers: { Authorization: `Bearer ${adminJwt}` } }, + ); + if (!delRes.ok) { + throw new Error( + `De-list ${council.councilId} failed: ${delRes.status} ${await delRes + .text()}`, + ); + } + console.log(` De-listed "${council.name}" (${council.councilId})`); + } +} diff --git a/testnet/main.ts b/testnet/main.ts index bcbede1..34b4173 100644 --- a/testnet/main.ts +++ b/testnet/main.ts @@ -39,6 +39,7 @@ import { withdraw } from "../lib/client/withdraw.ts"; import { registerEntity } from "../lib/client/register-entity.ts"; import { sdkTracer, withE2ESpan, writeTraceIds } from "../lib/client/tracer.ts"; import { exerciseCouncilSpans } from "../lib/exercise-cp-spans.ts"; +import { cleanupOwnedCouncils } from "./cleanup-councils.ts"; // ─── Events-capture contract ──────────────────────────────────────── /** @@ -664,6 +665,23 @@ export async function main() { console.log("\n[12/12] Finalize"); await writeTraceIds(); + // ── Cleanup — de-list e2e councils ──────────────────────────────── + // Loud but non-fatal: a failed cleanup must not turn a passed payment + // flow into a failed suite, but it must show in the logs. + console.log("\n[cleanup] De-list e2e councils"); + try { + await cleanupOwnedCouncils( + COUNCIL_URL, + await walletAuth(COUNCIL_URL, "/api/v1/admin/auth", admin), + ); + } catch (err) { + console.error( + ` WARNING: council cleanup failed, this run's council stays listed on the network dashboard: ${ + (err as Error).message + }`, + ); + } + const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); console.log(`\n✅ Testnet E2E passed in ${elapsed}s`); console.log(`\n Council ID: ${channelAuthId}`);