From 8378e4fe6b32898fc524291eb46fbdb88ce2eb58 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Thu, 16 Jul 2026 11:54:09 +0200 Subject: [PATCH 1/3] probe: add probe targets replay artifact --- test/helpers/probe.ts | 44 ++++++++++++++++++++++++++++----- test/specs/mainnet/probe.e2e.ts | 15 ++++++++--- 2 files changed, 50 insertions(+), 9 deletions(-) diff --git a/test/helpers/probe.ts b/test/helpers/probe.ts index b0bbf8c..c14f124 100644 --- a/test/helpers/probe.ts +++ b/test/helpers/probe.ts @@ -110,6 +110,16 @@ export type ProbeOrder = 'desc' | 'random' | 'config'; export type ProbeQueueEntry = { target: ProbeTarget; amountMsat: number }; +export type ProbeArtifactState = { + results: ProbeResult[]; + readiness?: ProbeReadiness | null; + replayProbes?: ProbeQueueEntry[]; +}; + +export type ProbeArtifactWriteOptions = { + writeStepSummary?: boolean; +}; + export function resolveProbeOrder(): ProbeOrder { const raw = process.env.PROBE_ORDER?.toLowerCase(); if (!raw) return 'config'; @@ -143,6 +153,23 @@ export function buildProbeQueue(targets: ProbeTarget[], order: ProbeOrder): Prob return queue; } +export function formatProbeReplayTargetsJson(probes: ProbeQueueEntry[], space?: number): string { + const replayTargets = probes.map(({ target, amountMsat }) => { + const baseTarget: Partial = { ...target }; + delete baseTarget.amountMsat; + delete baseTarget.amountsMsat; + + return Object.fromEntries( + Object.entries({ + ...baseTarget, + amountMsat, + }).filter(([, value]) => value !== undefined) + ); + }); + + return JSON.stringify(replayTargets, null, space); +} + function shuffleInPlace(items: T[]): T[] { for (let i = items.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); @@ -260,9 +287,7 @@ function parseDevResultSuccess(payload: Record): boolean { if ('success' in payload) return payload.success === true; const type = payload.type; - return ( - typeof type === 'string' && (type === 'Success' || type.endsWith('.ProbeSuccess')) - ); + return typeof type === 'string' && (type === 'Success' || type.endsWith('.ProbeSuccess')); } export function parseProbeCommandResult(raw: string): ProbeCommandResult | null { @@ -545,10 +570,10 @@ export async function waitForProbeReadiness({ } export function writeProbeArtifacts( - results: ProbeResult[], - readiness?: ProbeReadiness | null, - options: { writeStepSummary?: boolean } = {} + artifactState: ProbeArtifactState, + options: ProbeArtifactWriteOptions = {} ): void { + const { results, readiness, replayProbes } = artifactState; const artifactsDir = resolveArtifactsDir(); fs.mkdirSync(artifactsDir, { recursive: true }); @@ -559,6 +584,13 @@ export function writeProbeArtifacts( fs.writeFileSync(jsonPath, `${JSON.stringify(results, null, 2)}\n`); fs.writeFileSync(reportPath, report); + if (replayProbes && replayProbes.length > 0) { + fs.writeFileSync( + path.join(artifactsDir, 'probe-targets-replay.json'), + `${formatProbeReplayTargetsJson(replayProbes, 2)}\n` + ); + } + if (readiness) { const readinessPath = path.join(artifactsDir, 'probe-readiness.json'); fs.writeFileSync(readinessPath, `${JSON.stringify(readiness, null, 2)}\n`); diff --git a/test/specs/mainnet/probe.e2e.ts b/test/specs/mainnet/probe.e2e.ts index 5c3aecc..e82b433 100644 --- a/test/specs/mainnet/probe.e2e.ts +++ b/test/specs/mainnet/probe.e2e.ts @@ -16,6 +16,7 @@ import { summarizeProbeCommandFailure, waitForProbeReadiness, writeProbeArtifacts, + type ProbeQueueEntry, type ProbeReadiness, type ProbeResult, type ProbeTarget, @@ -217,6 +218,7 @@ describe('@probe_mainnet - Lightning probe smoke', () => { ciIt('@probe_mainnet_1 - Can probe configured mainnet LNURL targets', async () => { const results: ProbeResult[] = []; let readiness: ProbeReadiness | null = null; + let probes: ProbeQueueEntry[] = []; try { console.info('→ [Probe] Restoring probe wallet...'); @@ -238,7 +240,7 @@ describe('@probe_mainnet - Lightning probe smoke', () => { }); const probeOrder = resolveProbeOrder(); - const probes = buildProbeQueue(targets, probeOrder); + probes = buildProbeQueue(targets, probeOrder); const probeDelayMs = resolveProbeDelayMs(); const probeRetries = resolveProbeRetries(); console.info(`→ [Probe] Probe amount profile configured: ${resolveProbeAmountProfile()}`); @@ -249,11 +251,18 @@ describe('@probe_mainnet - Lightning probe smoke', () => { .map((it) => `${it.target.name}:${it.amountMsat / 1000}`) .join(', ')}` ); + writeProbeArtifacts( + { results, readiness, replayProbes: probes }, + { writeStepSummary: false } + ); for (const [index, { target, amountMsat }] of probes.entries()) { const result = await runProbe(target, amountMsat); results.push(result); - writeProbeArtifacts(results, readiness, { writeStepSummary: false }); + writeProbeArtifacts( + { results, readiness, replayProbes: probes }, + { writeStepSummary: false } + ); console.info( `→ [Probe] ${result.targetName} ${result.amountSats} sats (${result.probeMode}): ${ result.success ? '✅ success' : `❌ failed (${result.error ?? 'unknown'})` @@ -266,7 +275,7 @@ describe('@probe_mainnet - Lightning probe smoke', () => { } } } finally { - writeProbeArtifacts(results, readiness); + writeProbeArtifacts({ results, readiness, replayProbes: probes }); } const failedRequired = results.filter((it) => it.required && !it.success); From 5055ebc5e110b745db07ab549f2a330d2b3d2191 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Thu, 16 Jul 2026 13:16:41 +0200 Subject: [PATCH 2/3] Rename probe targets replay helpers --- test/helpers/probe.ts | 12 ++++++------ test/specs/mainnet/probe.e2e.ts | 9 +++------ 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/test/helpers/probe.ts b/test/helpers/probe.ts index c14f124..1068feb 100644 --- a/test/helpers/probe.ts +++ b/test/helpers/probe.ts @@ -113,7 +113,7 @@ export type ProbeQueueEntry = { target: ProbeTarget; amountMsat: number }; export type ProbeArtifactState = { results: ProbeResult[]; readiness?: ProbeReadiness | null; - replayProbes?: ProbeQueueEntry[]; + replayQueue?: ProbeQueueEntry[]; }; export type ProbeArtifactWriteOptions = { @@ -153,8 +153,8 @@ export function buildProbeQueue(targets: ProbeTarget[], order: ProbeOrder): Prob return queue; } -export function formatProbeReplayTargetsJson(probes: ProbeQueueEntry[], space?: number): string { - const replayTargets = probes.map(({ target, amountMsat }) => { +export function formatProbeTargetsReplayJson(queue: ProbeQueueEntry[], space?: number): string { + const replayTargets = queue.map(({ target, amountMsat }) => { const baseTarget: Partial = { ...target }; delete baseTarget.amountMsat; delete baseTarget.amountsMsat; @@ -573,7 +573,7 @@ export function writeProbeArtifacts( artifactState: ProbeArtifactState, options: ProbeArtifactWriteOptions = {} ): void { - const { results, readiness, replayProbes } = artifactState; + const { results, readiness, replayQueue } = artifactState; const artifactsDir = resolveArtifactsDir(); fs.mkdirSync(artifactsDir, { recursive: true }); @@ -584,10 +584,10 @@ export function writeProbeArtifacts( fs.writeFileSync(jsonPath, `${JSON.stringify(results, null, 2)}\n`); fs.writeFileSync(reportPath, report); - if (replayProbes && replayProbes.length > 0) { + if (replayQueue && replayQueue.length > 0) { fs.writeFileSync( path.join(artifactsDir, 'probe-targets-replay.json'), - `${formatProbeReplayTargetsJson(replayProbes, 2)}\n` + `${formatProbeTargetsReplayJson(replayQueue, 2)}\n` ); } diff --git a/test/specs/mainnet/probe.e2e.ts b/test/specs/mainnet/probe.e2e.ts index e82b433..70dd802 100644 --- a/test/specs/mainnet/probe.e2e.ts +++ b/test/specs/mainnet/probe.e2e.ts @@ -251,16 +251,13 @@ describe('@probe_mainnet - Lightning probe smoke', () => { .map((it) => `${it.target.name}:${it.amountMsat / 1000}`) .join(', ')}` ); - writeProbeArtifacts( - { results, readiness, replayProbes: probes }, - { writeStepSummary: false } - ); + writeProbeArtifacts({ results, readiness, replayQueue: probes }, { writeStepSummary: false }); for (const [index, { target, amountMsat }] of probes.entries()) { const result = await runProbe(target, amountMsat); results.push(result); writeProbeArtifacts( - { results, readiness, replayProbes: probes }, + { results, readiness, replayQueue: probes }, { writeStepSummary: false } ); console.info( @@ -275,7 +272,7 @@ describe('@probe_mainnet - Lightning probe smoke', () => { } } } finally { - writeProbeArtifacts({ results, readiness, replayProbes: probes }); + writeProbeArtifacts({ results, readiness, replayQueue: probes }); } const failedRequired = results.filter((it) => it.required && !it.success); From fe209cd8827822476671b0fa3497bf99c5d24ba1 Mon Sep 17 00:00:00 2001 From: Philipp Walter Date: Fri, 17 Jul 2026 11:44:34 +0200 Subject: [PATCH 3/3] docs: update probe readme --- docs/mainnet-probe.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/mainnet-probe.md b/docs/mainnet-probe.md index 7be1d98..cc0bdfe 100644 --- a/docs/mainnet-probe.md +++ b/docs/mainnet-probe.md @@ -68,6 +68,7 @@ Written to `artifacts/` (or `artifacts/attempt-N/` when `ATTEMPT` is set): - `probe-results.json` — per-probe results (target, amount, success, retries, duration, error) - `probe-report.md` — markdown summary table (also appended to `GITHUB_STEP_SUMMARY` on CI) - `probe-readiness.json` — node readiness snapshot at probe start +- `probe-targets-replay.json` — configured targets expanded into the exact target+amount order used by this run. Use it with `PROBE_ORDER=config` to replay a random run. ## Running locally @@ -96,6 +97,15 @@ APPIUM_NEW_COMMAND_TIMEOUT=1200 \ ./ci_run_android.sh --spec ./test/specs/mainnet/probe.e2e.ts --mochaOpts.grep "@probe_mainnet" ``` +Replay a previous random probe run from its artifact: + +```bash +export PROBE_TARGETS_JSON="$(jq -c . artifacts/probe-targets-replay.json)" +export PROBE_ORDER=config +``` + +Then run the suite with the same wallet/app build and other probe settings used by the original run. + Notes: - The wallet derived from `PROBE_SEED` must already have an open, usable channel with outbound liquidity covering the largest configured probe amount.