Batch transactions prove on the main thread and freeze the UI
- Dominant language
- TypeScript
- Stars
- 1
- Forks
- 21
- Avg merge
- 12h 14m
- Merged PRs (30d)
- 41
Description
## Summary
`transactions.batch()` / `transactions.submitBatch()` execute and prove **every** transaction in the batch, plus the batch proof itself, on the **main thread**. A batch of N transactions blocks the UI for the full duration of N proofs.
This is the opposite of what you'd expect: the API whose entire purpose is doing more work in one go is the one that does not offload, while a plain single `submit()` does.
## Why it happens
`WebClient` routes calls two ways. Methods named in `MethodName` (`js/constants.js`) get a hand-written wrapper that posts to the Web Worker. Everything else falls through the `Proxy` in `createClientProxy` (`js/index.js`) to the **main-thread** WASM instance, wrapped only in `_serializeWasmCall` — which is a promise-chain mutex, not an offload.
`submitNewTransactionBatch` is not in `MethodName`, so it takes the second path. The whole batch runs inside one Rust call:
```rust
// crates/web-client/src/new_transactions.rs
/// Executes a batch of transactions against the specified account, proves them individually
/// and as a batch, submits the batch to the network, and atomically applies the per-tx
/// updates to the local store.
```
So the asymmetry is:
| Call | Execute | Prove | Thread |
|---|---|---|---|
| `transactions.submit` | worker | worker | only the network POST is on main |
| `transactions.batch` / `submitBatch` | main | main | **everything, N times, plus the batch proof** |
`transactions.executeProgram` and `submitProven` are also main-thread, but those are comparatively cheap: `submitProven` is a network call, and `executeProgram` is a VM run with no proving. Batching is the one where the cost scales with N and is dominated by proving.
## Impact
- A multi-second-per-transaction proof freezes the tab for `N x proof_time`. No spinner animates, no input is handled.
- Proving in the worker is also **parallelized** — `initThreadPool(numThreads)` is called inside the worker's WASM instance on both the `INIT` and `INIT_MOCK` paths, and rayon's pool is per-instance. Main-thread WASM has no pool, so batch proving is single-threaded on top of being blocking. Fixing the routing should recover both.
- It pushes users toward `Promise.all` of single submits, which is exactly what the batch API exists to prevent, since that can partially succeed while a batch is atomic.
## Proposed fix
Mirror the existing `submitNewTransaction` treatment:
1. Add `SUBMIT_NEW_TRANSACTION_BATCH` (and a `_MOCK` variant) to `MethodName` in `js/constants.js`.
2. Add worker handlers in `js/workers/web-client-methods-worker.js`. The batch binding takes an account id plus `Vec` and returns a `u32` block number, so the payload is simpler than the single-submit handlers — no `TransactionResult` round trip.
3. Add an explicit `submitNewTransactionBatch` wrapper on `WebClient` in `js/index.js`, keeping the existing no-worker fallback path.
4. Add a `MockWebClient` override that round-trips the serialized mock chain and note-transport node, exactly as `submitNewTransaction` does — otherwise the worker's mock client would not see main-thread state.
The resource layer needs no change: `submitBatch` already calls `this.#inner.submitNewTransactionBatch(...)` with serialized requests, and defining the method explicitly on `WebClient` takes it off the `Proxy` fallback automatically.
Adding an explicit wrapper also satisfies `check:method-classification`, which already accepts explicit wrapper methods as a valid classification.
## Notes
Batching currently has no integration test — only a mocked unit test in `js/__tests__/resources/transactions.test.js` and one live-network Playwright test (`test/batch.browser.test.ts`) that calls the raw binding. Worth covering the worker path as part of this.
Contributor guide
Research direction
The issue is in the routing logic between main thread and Web Worker. Start by examining js/constants.js for MethodName, js/index.js for createClientProxy and the WebClient class, and js/workers/web-client-methods-worker.js for existing handlers. The fix involves adding a new method name, a worker handler, and an explicit wrapper on WebClient. Verify the change by running the existing unit test in js/__tests__/resources/transactions.test.js and the Playwright test test/batch.browser.test.ts. Done when batch transactions are proven in the worker without blocking the UI.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust, typescript, wasm
- Domain
- backend, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100