Repository navigation
Conversation
…meouts Scans, syncs, REST lookups and fee estimates run under createFetchPolicy from @metamask/snap-networks-utils (bounded retries with backoff on transient failures, circuit breaker); a broadcast is never retried. A whole sync is capped at 60 s and a full scan at 120 s, so a request stalled by a broken connection can no longer keep the coalesced sync stuck. performance is provided to the bundle through a ProvidePlugin shim.
This was referenced Oct 26, 2026
fix(stellar): run account reads under the fetch policy and report stale balances as unavailable
#525
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@metamask/bitcoin-wallet-snapExplanation
Bug
Esplora reads in the bitcoin snap have no bounded retry and no overall timeout. A transient failure fails the whole scan or sync at once, and a request that never settles can keep the account's sync guard busy, so its balance stops refreshing. This PR is groundwork. On its own it changes no visible number; it makes the scan logic in PR 03 dependable.
How to reproduce
release/13.54.0(acf000d) withyarn build:dev. For the "after" case, also build this branch and copy itsdist/bundle.js,snap.manifest.jsonandpreinstalled-snap.jsonintonode_modules/@metamask/bitcoin-wallet-snap. To see the wallet-side "Balance unavailable" + Retry, add core PR 10 and extension PR 12.bitcoin-mainnet.infura.iocan reuse the HTTP/2 connection already open to another*.infura.iohost (in our runs the Solana snap usually opened it first). The reply is an empty 404 without CORS headers, so the snap seesFailed to fetch. We reproduced this on a test machine where every*.infura.ioname resolved to the same IP. It depends on the network path and may not reproduce on every network. In our runs we moved the shared connection between hosts on purpose: we dropped the test browser's sockets while briefly blocking the other Infura hosts, which "heals" one chain and breaks the others.--disable-http2give no coalescing, and all four balances are correct.Expected: transient failures are retried with backoff, and a stalled scan or sync fails after its timeout so the next one can run.
Actual (before this change): there is no retry beyond the WASM client's own, and no operation timeout.
Without a browser:
EsploraClientAdapter.test.ts›operation timeouts›fails a full scan whose requests never settle after FULL_SCAN_TIMEOUT_MSreproduces a stalled request with fake timers.Summary
Every Esplora read (full scan, sync, REST sender lookups, fee estimates) now runs under the shared
createFetchPolicyfrom@metamask/snap-networks-utils(#462): bounded retries with backoff on transient failures, and a circuit breaker. A broadcast is never retried. A whole sync is capped at 60 s and a full scan at 120 s.Why
In Chrome, HTTP/2 connection coalescing across
*.infura.iocan send the snap's Esplora request over a connection opened for another Infura host. The provider router answers with an empty 404 and no CORS headers, so the request fails withFailed to fetch. Before this PR:EsploraClientsets no timeout. A request stalled by a suspended or broken connection could stay pending forever. The cron coalesces syncs, so every later cron run joined the stuck sync and the account never refreshed.What changed
infra/EsploraClientAdapter.ts:scanPolicywithmaxRetries: 1because the WASM client already retries each request, andrestPolicy);#read()runs every read under its policy;withOperationTimeout()rejects with aTimeoutError, which the policies treat as transient.SYNC_TIMEOUT_MS = 60_000,FULL_SCAN_TIMEOUT_MS = 120_000. A timed-out result is discarded, never applied to the wallet;broadcastgoes straight to the client: no retry, which could broadcast twice.src/index.ts: passes the logger to the adapter (policy warnings).snap.config.ts,package.json,yarn.lock: aProvidePluginmapsperformanceto@metamask/snap-networks-utils/performance-shim. cockatiel times calls withperformance.now(), which snaps don't have. Adds thewebpack: 5.107.2devDependency for the plugin import.withOperationTimeoutand sync/full-scan timeouts with fake timers (a stalled scan settles after 120 s, not 60 s, and applies nothing); REST sender lookups retried under the policy (timeouts, transient vs non-retryable non-2xx, cache eviction).Validation
Checked locally: the test files changed in this PR were run with the parent branch's source (tests from this branch, every other file from the parent), then on this branch.
Unit tests that fail before and pass after (parent: PR 01)
EsploraClientAdapter › operation timeouts ›fails a full scan whose requests never settle after FULL_SCAN_TIMEOUT_MS,fails a sync whose request never settles after SYNC_TIMEOUT_MS, without applying anything, plus the 2withOperationTimeouttestsEsploraClientAdapter › read retries ›retries a scan that fails with a network error once,fails once the retry also fails with a network errorEsploraClientAdapter › getTransactionSenders ›retries a timed-out request under the fetch policy, then rejects and evicts the cache,retries a transient non-2xx response and returns the recovered resultFull run on this branch
jest --runInBand --testPathIgnorePatterns integration-testinpackages/bitcoin-wallet-snap: 26 suites, 679 tests pass.tsc --noEmit(package): clean.process/__dirnamehits insnap.config.ts.Manual A/B
A = Chrome with default HTTP/2 (reproduces the coalescing); B = Chrome with
--disable-http2(control, no coalescing). Every run used the same test wallet and a localyarn build:devof extension 13.54.0 (acf000d), with the snaps and assets-controller wired into node_modules by hand. "Unfixed 13.54.0" is that build with no fixes. The fixes-vN builds are cumulative: each one has every fix up to that pass, so an "after" number shows this PR together with the others in that build. Run names (A-v6, B-v7, ...) are our local test passes on those builds; times are CEST.No A/B number isolates this PR. Its retries and timeouts were in every build from fixes-v3 onwards, together with PR 03. Check that it causes no regression on the control:
snap_trackErrorRisks
maxRetries), and the breaker stops hammering a dead endpoint.webpackdevDependency is only used bysnap.config.tsat build time.Changelog (
packages/bitcoin-wallet-snap/CHANGELOG.md, Unreleased → Fixed)References
Dependencies
performance-shimfrom@metamask/snap-networks-utils(feat(snap-networks-utils): add shared createFetchPolicy in networks utils #462, already on main).snap.manifest.jsonshasum.Stale non-EVM balances series (Chrome HTTP/2 coalescing across
*.infura.io):Checklist