Skip to content

[v0.1.x-branch] Backport #1131: mobile: prevent wallet stalls on external I/O - #1202

Merged
bhandras merged 6 commits into
v0.1.x-branchfrom
backport-1131-to-v0.1.x-branch
Aug 26, 2026
Merged

[v0.1.x-branch] Backport #1131: mobile: prevent wallet stalls on external I/O#1202
bhandras merged 6 commits into
v0.1.x-branchfrom
backport-1131-to-v0.1.x-branch

Conversation

@github-actions

Copy link
Copy Markdown

Backport of #1131


The problem

Mobile hosts call the embedded Wavelength wallet through gomobile, which cannot carry a caller context.Context. If iOS or Android suspends the process while an external connection is using an old network path, wallet reads and Lightning invoice creation can wait for the embedded daemon's full lifetime instead of returning control to the app.

Wallet recovery can cause a second stall. btcwallet v0.18.0 keeps its database writer open while the chain backend filters recovery blocks. The lightweight backend may perform remote I/O during that call, so unrelated wallet operations can wait behind a slow recovery request.

Read calls are safe to repeat. Invoice creation is different: a deadline or lifecycle cancellation may race durable receive-session creation, so cancellation does not prove that no invoice exists.

The fix

  • Give repeatable mobile reads a 10-second deadline. This covers GetInfo, Balance, List, ExitStatus, ExitSummary, GetExitPlan, Status, and the scalar convenience helpers.
  • Give mobile Receive an optional TimeoutSeconds field. Older hosts receive a 20-second default. Values above five minutes are rejected.
  • Mark deadline and lifecycle cancellation of Receive as an uncertain outcome with a stable reconcile-before-retry error prefix.
  • Return the authoritative local satoshi balance when optional remote credit enrichment exceeds its own two-second deadline, and log that the enrichment was skipped.
  • Update btcwallet to the merged recovery implementation from btcsuite/btcwallet#1318. It performs chain-backend filtering without an open wallet transaction, then atomically persists discoveries and the batch sync marker.
  • Document that the host must stop and restart the embedded wallet after a real background/foreground transition so external transports are re-dialled.
  • Release the Postgres test-fixture slot after container startup and store migration. This keeps the resource-sensitive initialization boundary at four concurrent fixtures without holding slots through each test lifetime.

Safety rule

The host may retry timed-out reads. It must not blindly retry a canceled Receive; it must first reconcile the authoritative Activity view and recover any matching invoice.

Recovery keeps address-index operations serialized and commits each completed batch atomically, while unrelated database writers remain available during remote filter I/O.

What does not change

  • The Go SDK request type and server RPC schema do not change.
  • Existing mobile hosts may omit TimeoutSeconds.
  • A healthy credit endpoint still enriches the local balance with credit fields.
  • The mobile binding still cannot observe the application lifecycle. The host owns background/foreground recovery.

Tests

  • make lint-changed-local
  • make tidy-module-check
  • make unit-swapruntime
  • go test -tags='mobile wavewalletrpc swapruntime' ./sdk/wavewalletdk/mobile
  • go test -tags='wavewalletrpc swapruntime' ./swapwallet -run '^TestServiceBalance' -count=1
  • go test -tags=test_postgres ./waved -count=1 -timeout=30m
  • go test -tags=test_postgres ./db -count=1 -timeout=30m
  • make mobile-ios

The tests cover default and explicit mobile deadlines, invalid deadline values, repeatable read deadlines, uncertain Receive cancellation, healthy credit enrichment, credit timeout fallback and logging, and the Postgres fixture-slot lifecycle. The full wallet-runtime suite and iOS xcframework build pass against the updated btcwallet dependency.

Exact-head CI passes all 20 required checks, including Postgres unit and system tests, race tests, SQLite tests, lint, static checks, and every cross-build.

Compatibility and rollout

TimeoutSeconds is additive at the mobile JSON boundary. Older Wavelength bindings ignore the extra field. Updated bindings apply the bounded default even when an older host omits it.

The btcwallet fix is pinned to v0.18.1-0.20260826052527-33c252f3b4d6, the Go pseudo-version for merged commit 33c252f3b4d6. No btcwallet v0.18.1 tag exists yet.

After this PR merges, publish a Wavelength release containing both changes. wavelength-mobile#6 can then consume and validate the release xcframework.

Mobile hosts cannot pass caller contexts through gomobile.
They may retain stale transports across OS suspension. Bound repeatable
reads and invoice creation without retrying an uncertain receive.

Keep the local satoshi balance available when optional remote credit
enrichment stalls.

(cherry picked from commit b854311)
Mobile wallet recovery asks the chain backend to filter remote blocks.
The v0.18.0 wallet holds its database writer while that backend call is
in flight, which can stall unrelated wallet operations on a slow path.

Update to the merged btcwallet recovery change. It builds requests under
a short read transaction, performs filter I/O without an open
transaction, and atomically persists each batch while serializing
address derivation.

This keeps recovery progress atomic without blocking unrelated database
writers during remote filter calls. The iOS binding build and the full
wallet-runtime unit suite pass against the new version.

(cherry picked from commit a4575fe)
Apply the binding-owned read deadline to every repeatable mobile read.
Return a stable uncertain-outcome prefix when Receive reaches its own
deadline. Document that receive timeouts over five minutes are rejected.

(cherry picked from commit 038df79)
Mark Receive cancellation by either its request deadline or wallet Stop
with the same stable uncertain-outcome prefix.

This tells mobile hosts to reconcile Activity before retrying after
foreground timeouts and lifecycle cancellation.

(cherry picked from commit a41cc55)
Log the error when optional credit enrichment is omitted from an
otherwise valid local balance response.

This lets operators distinguish a degraded remote read from a genuine
zero-credit wallet without failing the local refresh.

(cherry picked from commit 502fa87)
Release the Postgres fixture semaphore after Docker startup and store
migrations finish instead of retaining it until test cleanup.

This preserves bounded initialization while preventing parallel tests
from exhausting every slot and blocking the remaining test barrier.

(cherry picked from commit 7da20dd)
@bhandras
bhandras merged commit e7bbe32 into v0.1.x-branch Aug 26, 2026
22 of 24 checks passed
@bhandras
bhandras deleted the backport-1131-to-v0.1.x-branch branch August 26, 2026 12:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant