Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e660455
test: add randomized stress suite for real-world send/claim patterns
WiktorStarczewski Apr 15, 2026
b074ad5
docs: add E2E Stress Tests section to README
WiktorStarczewski Apr 15, 2026
f61907c
fix: rehydrate balances from SW sync snapshot before app mount
WiktorStarczewski Apr 15, 2026
d15764c
fix: do not auto-open the transaction-progress modal on page reload
WiktorStarczewski Apr 15, 2026
6523fa8
fix: do not auto-open the transaction-progress modal from Explore
WiktorStarczewski Apr 15, 2026
3cc42e4
fix: UI polish across send, activity, and forgot-password flows
WiktorStarczewski Apr 15, 2026
a7f6ef1
test: bump stress per-send timeout default to 90s
WiktorStarczewski Apr 15, 2026
bad7f37
fix: resume mobile/desktop tx loop on mount when queue non-empty
WiktorStarczewski Apr 16, 2026
cff28e7
fix: serialize wallet lock behind WASM mutex to prevent note loss
WiktorStarczewski Apr 16, 2026
c78d272
chore: upgrade @miden-sdk packages to 0.14.2
WiktorStarczewski Apr 16, 2026
3399e65
refactor: move WASM call serialization to SDK; use waitForIdle() for …
WiktorStarczewski Apr 16, 2026
d4b8c3c
feat: leave tx queued on locked-wallet sign failure; use SDK lastAuth…
WiktorStarczewski Apr 16, 2026
7857a91
feat: mark apply-after-submit failures as Completed; surface via SDK …
WiktorStarczewski Apr 16, 2026
b273aab
feat: private-note transport retry loop via SDK resendPrivateById
WiktorStarczewski Apr 16, 2026
4bff701
test: transport-failure perturbation in stress suite for #6 coverage
WiktorStarczewski Apr 16, 2026
743fb1b
refactor: unify MidenClient to single long-lived instance with keysto…
WiktorStarczewski Apr 16, 2026
1f385aa
fix: use defaultProver path now that SDK takes prover by reference
WiktorStarczewski Apr 17, 2026
d3040df
merge: resolve conflicts with main
WiktorStarczewski Apr 17, 2026
0fda749
docs: add changelog entries for SDK hardening + singleton unification
WiktorStarczewski Apr 17, 2026
20308af
fix: use published @miden-sdk/miden-sdk 0.14.2 instead of local file:…
WiktorStarczewski Apr 17, 2026
79bdedd
Revert "fix: use published @miden-sdk/miden-sdk 0.14.2 instead of loc…
WiktorStarczewski Apr 17, 2026
77c1dc2
fix: use published @miden-sdk/miden-sdk 0.14.2 and raise test coverage
WiktorStarczewski Apr 17, 2026
afbad78
test: raise branch coverage toward 95% threshold
WiktorStarczewski Apr 17, 2026
41e5a83
test: cover NewWalletRequest error path in main.test.ts
WiktorStarczewski Apr 17, 2026
39171ae
refactor: remove wallet-side AsyncMutex (withWasmClientLock is now a …
WiktorStarczewski Apr 17, 2026
dd0c668
fix: restore published @miden-sdk/miden-sdk 0.14.2 for CI
WiktorStarczewski Apr 17, 2026
22a0ac9
feat: show per-stage label in transaction progress modal
WiktorStarczewski Apr 19, 2026
a3a5702
chore: update translation files
github-actions[bot] Apr 19, 2026
665a2b6
refactor: drop wallet-side transport-retry; SDK outbox (miden-client#…
WiktorStarczewski May 29, 2026
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
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@

## 1.14.2 (TBD)

### Features

* [FEATURE][all] Unified `MidenClientSingleton` from two instances (dispose-recreate per-tx) to one long-lived singleton with a late-binding keystore bridge. Sends are ~2.5× faster (no per-tx client init overhead). New `keystore-bridge.ts` module and `keystore-wiring.ts` Effector event subscriptions manage vault key + sign callback lifecycle. (#189)
* [FEATURE][all] Typed sign-callback failure recovery via SDK `lastAuthError()`. When the wallet gets locked mid-transaction, the TransactionProcessor now leaves the tx Queued for retry after unlock instead of marking it Failed. (#189)
* [FEATURE][all] `ApplyTransactionAfterSubmitFailed` handling via SDK `errorCode` dispatch. Transactions that submit on-chain but fail to apply locally are marked Completed (not Failed). (#189)
* [FEATURE][e2e] Transport-failure perturbation (`STRESS_TRANSPORT_FAIL_PROB`) in the stress suite for end-to-end coverage of the SDK's durable relay outbox (miden-client#2127). (#189)

### Fixes

* [FIX][all] Gated page-side SDK WASM init. `fetchTokenMetadata` and `SendDetails` used to race the SDK's lazy wasm-bindgen load when constructing `Endpoint`/`RpcClient` directly on the page thread, hitting `Cannot read properties of undefined (reading '__wbindgen_malloc')` and blacklisting the token via `autoFetchMetadataFails` for the rest of the session. New `ensureSdkWasmReady()` helper actively triggers the SDK's `loadWasm()` via a Vite-aliased deep import and probes readiness, wired up before any page-side RPC construction. (#187)
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -261,10 +261,11 @@ All knobs are env vars — set on the command line to override. Full list in `pl
| `STRESS_LOCK_EVERY` | 15 | lock/unlock cycle frequency |
| `STRESS_RELOAD_EVERY` | 20 | page reload cycle frequency |
| `STRESS_CONCURRENT_PROB` | 0.15 | both wallets send at the same tick |
| `STRESS_TRANSPORT_FAIL_PROB` | 0 | probability of intercepting + failing the SendNote gRPC call on a private send, so the wallet's transport-retry loop is exercised end-to-end |
| `STRESS_SEED` | `Date.now()` | reproducibility |
| `STRESS_CONSERVATION_STRICT` | true | fail if final total != initial |

Set any of the `*_EVERY` knobs (or `STRESS_CONCURRENT_PROB`) to `0` to disable that perturbation entirely.
Set any of the `*_EVERY` knobs (or `STRESS_CONCURRENT_PROB` / `STRESS_TRANSPORT_FAIL_PROB`) to `0` to disable that perturbation entirely.

Example usage:

Expand Down
60 changes: 58 additions & 2 deletions playwright/e2e/stress/stress-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,16 @@ export interface StressOptions {
reloadEvery: number;
concurrentProb: number;
perTurnSendTimeoutMs: number;
/**
* Probability [0,1] that each private-note send gets its transport
* request intercepted and forced to fail (via Playwright `page.route`).
* Exercises the SDK's durable relay outbox (miden-client#2127): on
* failure the wallet marks the tx Completed (the on-chain commit is
* durable) and the SDK persists the relay payload, retrying it on the
* next sync. Final balance conservation should still hold. 0 disables
* (default).
*/
transportFailProb: number;
seed: number;
}

Expand All @@ -48,7 +58,13 @@ export interface StressResult {
requested: number;
completed: number;
failed: number;
perturbations: { locks: number; reloads: number; concurrent: number; idles: number };
perturbations: {
locks: number;
reloads: number;
concurrent: number;
idles: number;
transportFails: number;
};
perOp: StressOpRecord[];
}

Expand Down Expand Up @@ -102,7 +118,7 @@ export async function runStressDriver(
const wallets: Record<'A' | 'B', ChromeWalletPageApi> = { A: walletA, B: walletB };
const addrs: Record<'A' | 'B', string> = { A: addressA, B: addressB };
const perOp: StressOpRecord[] = [];
const perturbations = { locks: 0, reloads: 0, concurrent: 0, idles: 0 };
const perturbations = { locks: 0, reloads: 0, concurrent: 0, idles: 0, transportFails: 0 };
let completed = 0;
let failed = 0;
let idx = 0;
Expand Down Expand Up @@ -140,6 +156,42 @@ export async function runStressDriver(
data: { idx, sender: senderLabel, receiver: receiverLabel, isPrivate, amount, concurrent, phase: 'pre_send' },
});

// Transport-failure perturbation (private notes only — public notes
// don't hit the transport layer). Installs a one-shot route on the
// sender's page that aborts the next SendNote gRPC request, forcing
// the wallet into its transport-pending retry path. The retry loop
// then delivers the note a few seconds later; we verify the full
// pipeline works via the final conservation check.
let transportRouteCleanup: (() => Promise<void>) | undefined;
if (
isPrivate &&
opts.transportFailProb > 0 &&
!concurrent && // concurrent ops are messy; skip to keep the signal clean
rng() < opts.transportFailProb
) {
perturbations.transportFails++;
perturbation = perturbation ? `${perturbation}+transport_fail` : 'transport_fail';
timeline.emit({
category: 'stress_op',
severity: 'info',
message: `[stress] perturbation: block SendNote on ${senderLabel} for op#${idx}`,
});
const page = sender.page;
let armed = true;
const handler = async (route: import('@playwright/test').Route) => {
if (armed && /SendNote/i.test(route.request().url())) {
armed = false; // one-shot
await route.abort('failed').catch(() => {});
return;
}
await route.continue().catch(() => {});
};
await page.route('**/*transport*/**', handler);
transportRouteCleanup = async () => {
await page.unroute('**/*transport*/**', handler).catch(() => {});
};
}

try {
if (concurrent) {
// Both wallets fire a send at (roughly) the same time — stress the
Expand Down Expand Up @@ -184,6 +236,10 @@ export async function runStressDriver(
status = 'fail';
err = e instanceof Error ? e.message : String(e);
failed += 1;
} finally {
if (transportRouteCleanup) {
await transportRouteCleanup();
}
}
const sendMs = Date.now() - start;

Expand Down
5 changes: 5 additions & 0 deletions playwright/e2e/stress/stress.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ function parseOptions(): StressOptions {
reloadEvery: intEnv('STRESS_RELOAD_EVERY', 20),
concurrentProb: floatEnv('STRESS_CONCURRENT_PROB', 0.15),
perTurnSendTimeoutMs: intEnv('STRESS_SEND_TIMEOUT_MS', 90_000),
// Probability [0,1] of intercepting and failing the transport call on
// a private-note send, so the retry loop can be exercised end-to-end.
// Kept at 0 by default so the default stress run matches historical
// behavior; set to e.g. 0.1 to validate #6 hardening.
transportFailProb: floatEnv('STRESS_TRANSPORT_FAIL_PROB', 0),
seed: intEnv('STRESS_SEED', Date.now() >>> 0),
};
}
Expand Down
49 changes: 49 additions & 0 deletions public/_locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,55 @@
"message": "Dies kann je nach Netzwerkverkehr einen Moment dauern",
"englishSource": "This may take a moment depending on the level network traffic"
},
"transactionStageSyncing": {
"message": "Konto wird synchronisiert",
"englishSource": "Syncing account"
},
"transactionStageSyncingDescription": {
"message": "Rufen Sie Ihren aktuellen Kontostatus ab.",
"englishSource": "Fetching your latest account state."
},
"transactionStageSending": {
"message": "Transaktion wird gesendet",
"englishSource": "Sending transaction"
},
"transactionStageSendingDescription": {
"message": "Aufbau und Nachweis Ihrer Transaktion.",
"englishSource": "Building and proving your transaction."
},
"transactionStageClaiming": {
"message": "Anspruch auf note",
"englishSource": "Claiming note"
},
"transactionStageExecuting": {
"message": "Transaktion wird ausgeführt",
"englishSource": "Executing transaction"
},
"transactionStageConfirming": {
"message": "Bestätigung an der Kette",
"englishSource": "Confirming on chain"
},
"transactionStageConfirmingDescription": {
"message": "Warten auf Netzwerkbestätigung.",
"englishSource": "Waiting for network confirmation."
},
"transactionStageDelivering": {
"message": "Lieferung von note",
"englishSource": "Delivering note"
},
"transactionStageDeliveringDescription": {
"message": "note sicher an den Empfänger senden.",
"englishSource": "Securely sending the note to the recipient."
},
"transactionsRemainingInBatch": {
"message": "$count$ Transaktionen in Bearbeitung",
"englishSource": "$count$ transactions in progress",
"placeholders": {
"count": {
"content": "$1"
}
}
},
"status": {
"message": "Status",
"englishSource": "Status"
Expand Down
11 changes: 11 additions & 0 deletions public/_locales/en/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,17 @@
"fastTransaction": "Transaction Speed",
"generatingTransaction": "Generating Transaction",
"generatingTransactionDescription": "This may take a moment depending on the level network traffic",
"transactionStageSyncing": "Syncing account",
"transactionStageSyncingDescription": "Fetching your latest account state.",
"transactionStageSending": "Sending transaction",
"transactionStageSendingDescription": "Building and proving your transaction.",
"transactionStageClaiming": "Claiming note",
"transactionStageExecuting": "Executing transaction",
"transactionStageConfirming": "Confirming on chain",
"transactionStageConfirmingDescription": "Waiting for network confirmation.",
"transactionStageDelivering": "Delivering note",
"transactionStageDeliveringDescription": "Securely sending the note to the recipient.",
"transactionsRemainingInBatch": "$count$ transactions in progress",
"status": "Status",
"balancePending": "Balance Pending Sync",
"keepTabOpenForSync": "Keep this tab open for faster sync.",
Expand Down
49 changes: 49 additions & 0 deletions public/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,55 @@
"message": "This may take a moment depending on the level network traffic",
"englishSource": "This may take a moment depending on the level network traffic"
},
"transactionStageSyncing": {
"message": "Syncing account",
"englishSource": "Syncing account"
},
"transactionStageSyncingDescription": {
"message": "Fetching your latest account state.",
"englishSource": "Fetching your latest account state."
},
"transactionStageSending": {
"message": "Sending transaction",
"englishSource": "Sending transaction"
},
"transactionStageSendingDescription": {
"message": "Building and proving your transaction.",
"englishSource": "Building and proving your transaction."
},
"transactionStageClaiming": {
"message": "Claiming note",
"englishSource": "Claiming note"
},
"transactionStageExecuting": {
"message": "Executing transaction",
"englishSource": "Executing transaction"
},
"transactionStageConfirming": {
"message": "Confirming on chain",
"englishSource": "Confirming on chain"
},
"transactionStageConfirmingDescription": {
"message": "Waiting for network confirmation.",
"englishSource": "Waiting for network confirmation."
},
"transactionStageDelivering": {
"message": "Delivering note",
"englishSource": "Delivering note"
},
"transactionStageDeliveringDescription": {
"message": "Securely sending the note to the recipient.",
"englishSource": "Securely sending the note to the recipient."
},
"transactionsRemainingInBatch": {
"message": "$count$ transactions in progress",
"englishSource": "$count$ transactions in progress",
"placeholders": {
"count": {
"content": "$1"
}
}
},
"status": {
"message": "Status",
"englishSource": "Status"
Expand Down
49 changes: 49 additions & 0 deletions public/_locales/en_GB/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,55 @@
"message": "This may take a moment depending on the level network traffic",
"englishSource": "This may take a moment depending on the level network traffic"
},
"transactionStageSyncing": {
"message": "Syncing account",
"englishSource": "Syncing account"
},
"transactionStageSyncingDescription": {
"message": "Fetching your latest account state.",
"englishSource": "Fetching your latest account state."
},
"transactionStageSending": {
"message": "Sending transaction",
"englishSource": "Sending transaction"
},
"transactionStageSendingDescription": {
"message": "Building and proving your transaction.",
"englishSource": "Building and proving your transaction."
},
"transactionStageClaiming": {
"message": "Claiming note",
"englishSource": "Claiming note"
},
"transactionStageExecuting": {
"message": "Executing transaction",
"englishSource": "Executing transaction"
},
"transactionStageConfirming": {
"message": "Confirming on chain",
"englishSource": "Confirming on chain"
},
"transactionStageConfirmingDescription": {
"message": "Waiting for network confirmation.",
"englishSource": "Waiting for network confirmation."
},
"transactionStageDelivering": {
"message": "Delivering note",
"englishSource": "Delivering note"
},
"transactionStageDeliveringDescription": {
"message": "Securely sending the note to the recipient.",
"englishSource": "Securely sending the note to the recipient."
},
"transactionsRemainingInBatch": {
"message": "$count$ transactions in progress",
"englishSource": "$count$ transactions in progress",
"placeholders": {
"count": {
"content": "$1"
}
}
},
"status": {
"message": "Status",
"englishSource": "Status"
Expand Down
49 changes: 49 additions & 0 deletions public/_locales/es/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,55 @@
"message": "Esto puede tardar un momento dependiendo del nivel de tráfico de la red.",
"englishSource": "This may take a moment depending on the level network traffic"
},
"transactionStageSyncing": {
"message": "Sincronizando cuenta",
"englishSource": "Syncing account"
},
"transactionStageSyncingDescription": {
"message": "Obteniendo el último estado de su cuenta.",
"englishSource": "Fetching your latest account state."
},
"transactionStageSending": {
"message": "Transaccion de envio",
"englishSource": "Sending transaction"
},
"transactionStageSendingDescription": {
"message": "Construyendo y probando su transacción.",
"englishSource": "Building and proving your transaction."
},
"transactionStageClaiming": {
"message": "Reclamando note",
"englishSource": "Claiming note"
},
"transactionStageExecuting": {
"message": "Ejecutando transacción",
"englishSource": "Executing transaction"
},
"transactionStageConfirming": {
"message": "Confirmando en cadena",
"englishSource": "Confirming on chain"
},
"transactionStageConfirmingDescription": {
"message": "Esperando confirmación de red.",
"englishSource": "Waiting for network confirmation."
},
"transactionStageDelivering": {
"message": "Entregando note",
"englishSource": "Delivering note"
},
"transactionStageDeliveringDescription": {
"message": "Envío seguro del note al destinatario.",
"englishSource": "Securely sending the note to the recipient."
},
"transactionsRemainingInBatch": {
"message": "$count$ transacciones en curso",
"englishSource": "$count$ transactions in progress",
"placeholders": {
"count": {
"content": "$1"
}
}
},
"status": {
"message": "Estado",
"englishSource": "Status"
Expand Down
Loading