Repository navigation
fix(stellar): run account reads under the fetch policy and report stale balances as unavailable - #525
Closed
rvelaz wants to merge 2 commits into
Conversation
…le balances as unavailable - loadOnChainAccount (Horizon) and getAccount (Soroban RPC) run under createFetchPolicy from #462: retries with backoff on transient failures and a circuit breaker. A confirmed missing account is a non-retryable 404; an empty-body 404 or a bare 'Failed to fetch' is a network failure. The original SDK error is rethrown so callers keep classifying it. The snap bundle provides the performance shim the policy needs. - A balance is stale when the last successful sync is older than 90 s and the syncs since then have failed. getAccountBalances then throws AccountBalanceStaleException (expected state, untracked) and requests an on-demand sync through the existing 30 s dedupe. The failure-streak start is persisted per account only on transitions; the error clears on the next successful sync.
This was referenced Oct 26, 2026
…ests-staleness # Conflicts: # packages/stellar-wallet-snap/src/handlers/keyring/keyring.test.ts # packages/stellar-wallet-snap/src/handlers/keyring/keyring.ts # packages/stellar-wallet-snap/src/services/on-chain-account/OnChainAccountService.ts
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/stellar-wallet-snapExplanation
Bug
When stellar syncs start failing after a successful one, XLM keeps showing the last amount as if it were current. Horizon and Soroban reads also have no bounded retry, so one transient failure fails the whole sync.
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/stellar-wallet-snap. To see the wallet-side "Balance unavailable" + Retry, add core PR 10 and extension PR 12.stellar-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: about 90 s after the last successful sync (plus the next poll), XLM shows "Balance unavailable" + Retry, then recovers within seconds of the heal. There is no Sentry event for the stale state.
Actual (before this change): XLM keeps the old amount. In fixes-v5 A-v5 it was still shown 3.5 min after its requests started failing.
Without a browser:
SynchronizeService.test.ts›sync health for stale-balance detection (v6)›marks the balance stale after ~90 s of failed syncs and clears it on the next success (real service), andNetworkService.test.ts›read retries›treats an empty-body 404 as a network failure, not a missing account.Summary
Two changes that make XLM fail in a visible way instead of going quietly stale:
loadAccountand Soroban RPCgetAccountnow run under the sharedcreateFetchPolicy(feat(snap-networks-utils): add shared createFetchPolicy in networks utils #462): retry with backoff on transient failures, plus a circuit breaker. A confirmed missing account is never retried. An empty-body 404 counts as a transport failure.getAccountBalancesthrowsAccountBalanceStaleException(expected state, untracked) and asks for a sync. The wallet then shows "Balance unavailable" + Retry instead of an old amount that looks current. The error clears on the next successful sync.Why
Chrome's HTTP/2 connection coalescing across
*.infura.iocan route the stellar snap's requests over a connection that belongs to another Infura host. The replies are empty 404s without CORS headers, so the snap seesFailed to fetch. Before this PR:getAccountBalanceskept returning it, and the wallet showed the old amount with no sign that it was stale. In A-v5, BTC/XLM still showed balances 3.5 min after every request started failing.What changed
services/network/NetworkService.ts:#read()runs each account read under a per-scope fetch policy (stellar_horizon,stellar_rpc) and rethrows the original SDK error, viaunwrapFetchPolicyError, so the existing classification (AccountNotActivatedException,NetworkServiceException) is unchanged.services/network/utils.ts:toStellarFetchPolicyErrormaps errors to policy decisions:snap.config.ts+package.json(webpackdevDependency) +yarn.lock:ProvidePluginsupplies@metamask/snap-networks-utils/performance-shim, because the policy usesperformance.now()and snaps don't have it.OnChainAccountRepositorystoressyncFailingSinceper keyring account, written only on transitions.OnChainAccountService.recordSyncOutcome/isBalanceStalekeeps the last success in memory perid:scope.SynchronizeServicerecords the outcome of each account sync.AccountBalanceStaleExceptionand requests an on-demand sync through PR 06's 30 s dedupe.utils/errors.tskeeps it untracked.Validation
Checked locally: the test files changed in this PR were run with the parent'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 06)
KeyringHandler › getAccountBalances › stale balance after failing syncs (v6) ›throws the untracked AccountBalanceStaleException and schedules a sync when the balance is stale,answers from the snapshot when the balance is not stale,also fails for a confirmed-unfunded account whose syncs keep failingSynchronizeService › sync health for stale-balance detection (v6) ›4 tests, includingmarks the balance stale after ~90 s of failed syncs and clears it on the next success (real service)OnChainAccountService › recordSyncOutcome / isBalanceStale (v6) ›3;OnChainAccountRepository › markSyncFailing / ...3NetworkService › read retries ›retries a transient Horizon failure on the primary endpoint,treats an empty-body 404 as a network failure, not a missing accounttoStellarFetchPolicyError(4) andunwrapFetchPolicyError(2)errors › shouldTrackError › does not track the stale-balance state, even re-created or wrapped (v6)Full run on this branch
jest --runInBandinpackages/stellar-wallet-snap: 93 suites, 1499 tests pass.tsc --noEmit(package): clean.pathimport 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.AccountBalanceStaleExceptionsnap_trackError(control)Risks
Changelog (
packages/stellar-wallet-snap/CHANGELOG.md, Unreleased)@metamask/snap-networks-utils(#525)References
Dependencies
@metamask/snap-networks-utilswithcreateFetchPolicy/executeWithFetchPolicy/performance-shim, which is already onmain(feat(snap-networks-utils): add shared createFetchPolicy in networks utils #462).snap.manifest.jsonshasum (yarn workspace @metamask/stellar-wallet-snap build).Stale non-EVM balances series (Chrome HTTP/2 coalescing across
*.infura.io):Checklist