Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions docs/mainnet-probe.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
Expand Down
44 changes: 38 additions & 6 deletions test/helpers/probe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
replayQueue?: ProbeQueueEntry[];
};

export type ProbeArtifactWriteOptions = {
writeStepSummary?: boolean;
};

export function resolveProbeOrder(): ProbeOrder {
const raw = process.env.PROBE_ORDER?.toLowerCase();
if (!raw) return 'config';
Expand Down Expand Up @@ -143,6 +153,23 @@ export function buildProbeQueue(targets: ProbeTarget[], order: ProbeOrder): Prob
return queue;
}

export function formatProbeTargetsReplayJson(queue: ProbeQueueEntry[], space?: number): string {
const replayTargets = queue.map(({ target, amountMsat }) => {
const baseTarget: Partial<ProbeTarget> = { ...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<T>(items: T[]): T[] {
for (let i = items.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
Expand Down Expand Up @@ -260,9 +287,7 @@ function parseDevResultSuccess(payload: Record<string, unknown>): 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 {
Expand Down Expand Up @@ -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, replayQueue } = artifactState;
const artifactsDir = resolveArtifactsDir();
fs.mkdirSync(artifactsDir, { recursive: true });

Expand All @@ -559,6 +584,13 @@ export function writeProbeArtifacts(
fs.writeFileSync(jsonPath, `${JSON.stringify(results, null, 2)}\n`);
fs.writeFileSync(reportPath, report);

if (replayQueue && replayQueue.length > 0) {
fs.writeFileSync(
path.join(artifactsDir, 'probe-targets-replay.json'),
`${formatProbeTargetsReplayJson(replayQueue, 2)}\n`
);
}

if (readiness) {
const readinessPath = path.join(artifactsDir, 'probe-readiness.json');
fs.writeFileSync(readinessPath, `${JSON.stringify(readiness, null, 2)}\n`);
Expand Down
12 changes: 9 additions & 3 deletions test/specs/mainnet/probe.e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
summarizeProbeCommandFailure,
waitForProbeReadiness,
writeProbeArtifacts,
type ProbeQueueEntry,
type ProbeReadiness,
type ProbeResult,
type ProbeTarget,
Expand Down Expand Up @@ -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...');
Expand All @@ -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()}`);
Expand All @@ -249,11 +251,15 @@ describe('@probe_mainnet - Lightning probe smoke', () => {
.map((it) => `${it.target.name}:${it.amountMsat / 1000}`)
.join(', ')}`
);
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, { writeStepSummary: false });
writeProbeArtifacts(
{ results, readiness, replayQueue: probes },
{ writeStepSummary: false }
);
console.info(
`→ [Probe] ${result.targetName} ${result.amountSats} sats (${result.probeMode}): ${
result.success ? '✅ success' : `❌ failed (${result.error ?? 'unknown'})`
Expand All @@ -266,7 +272,7 @@ describe('@probe_mainnet - Lightning probe smoke', () => {
}
}
} finally {
writeProbeArtifacts(results, readiness);
writeProbeArtifacts({ results, readiness, replayQueue: probes });
}

const failedRequired = results.filter((it) => it.required && !it.success);
Expand Down