Restore per-request and handshake timeouts for NYM mixnet fetches - #731
Restore per-request and handshake timeouts for NYM mixnet fetches#731j0ntz wants to merge 1 commit into
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2e8fd21. Configure here.
2e8fd21 to
6ab87fe
Compare
Important context: this is a mitigation, not the fix for the QA reportA v1 baseline A/B (requested on the task after this PR opened) changes how this PR should be read. Please don't merge it thinking it resolves the staging report. I built the v1 baseline ( Tunnel setup
Per-request, both tunnels healthy, same moment
v1 was completing AVAX in ~2s repeatedly while v2 timed out on that same endpoint at the same instant. What that means
So v2 is a comprehensive regression, not merely the timeout mis-port this PR fixes. Why this PR is still worth landingAn unbounded per-request wait is a bug on its own terms, and this restores v1's proven 300000ms bound plus a sane handshake budget. It converts a permanent "Calculating Fee" spinner into a surfaced error. It just cannot make v2 deliver. The likely real fix is reverting #729 to 1.4.4 (neither version is npm-deprecated). Note that pinning |
|
Superseded by #732 (revert to mix-fetch 1.4.4). The multi-node A/B confirmed v2 is a comprehensive regression, not just the timeout mis-port this PR bounds. This PR's setup-timeout hardening is carried forward onto the v1 API in #732. Recommend closing this in favor of #732 once the revert is approved. |
mix-fetch's own requestTimeoutMs is meant to bound each request, but a request whose exit connection stalls has been observed hanging past it (David Coen's 2026-07-20 staging report: Coreum/Avalanche sends sat on 'Calculating Fee' indefinitely with NYM on, after the v1 revert). Wrap each mixFetch call in our own REQUEST_TIMEOUT_MS race so the promise rejects on our side and an engine fails over to its next server instead of awaiting forever. The 60s handshake bound is already in place. This re-expresses PR #731 on the v1 (1.4.4) mix-fetch stack that master reverted to; #731 was authored against v2.
6ab87fe to
4e8f2d4
Compare



CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Asana: https://app.asana.com/1/9976422036640/project/1215088146871429/task/1216608079766867
Restores an explicit per-request timeout on NYM mixnet fetches, re-expressed on the v1 (
@nymproject/mix-fetch@1.4.4) stack thatmasterreverted to in 2.47.1.Background
This PR was originally authored against the v2 mix-fetch upgrade (base 2.47.0). That upgrade was reverted to v1 in #732, so the branch has been reset onto the current v1
masterand the change re-expressed for the v1 API. The v1 revert already re-applied the 60s handshake bound (SETUP_TIMEOUT_MS, commita323e15f), so the remaining piece is the per-request side.What this changes
mixFetchOptions.mixFetchOverride.requestTimeoutMs(300000) asks mix-fetch to bound each request, but a request whose exit connection stalls has been observed hanging past it. David Coen's 2026-07-20 staging report (26071707, Android) showed Coreum/Avalanche sends sitting on "Calculating Fee" indefinitely with NYM on, after the v1 revert landed. This wraps eachmixFetchcall in our ownREQUEST_TIMEOUT_MSrace (fetchWithTimeout), so the promise rejects on our side once the budget elapses and an engine can fail over to its next server instead of awaiting the request forever. The wrapper keeps v1's 3-argIMixFetchFnsignature, so call sites are unchanged, and swallows the abandoned request's late rejection so it does not surface as an unhandled rejection in the worker.The value stays at 300000 to match the native
requestTimeoutMsit backstops. Reducing that budget (5 minutes still reads as a stuck spinner) is separate scope handled in #734 (task 1216729024836433), which also adds concurrency bounds and per-request logging; the two touch the same file and whichever lands second reconciles.Testing
tsc --noEmitclean;mochasuite green (162 passing).updot-linked into edge-react-gui, and force-rebuilt the iOS sim app (verified the installededge-core-js.bundle/edge-core.jscarries the wrapper).mixFetch initialized successfullythrough the wrappedinitMixFetchon every launch; drove a live Ethereum NYM toggle (Network privacy for ethereum: nym) and reverted it. Wallets sync normally through the wrapper with no spurious timeout and no regression.fuzzyTimeouttests. Screenshots attached.