Skip to content

Fail closed when the wasm wallet database can't persist to OPFS#1046

Open
jamaljsr wants to merge 2 commits into
mainfrom
agent/wasm-sqlite-require-persistent
Open

Fail closed when the wasm wallet database can't persist to OPFS#1046
jamaljsr wants to merge 2 commits into
mainfrom
agent/wasm-sqlite-require-persistent

Conversation

@jamaljsr

Copy link
Copy Markdown
Member

Summary

A second browser tab can't acquire the wallet database's exclusive OPFS handles, and the wasm SQLite worker used to treat that open failure as a cue to fall back to an in-memory database. The daemon then booted against a throwaway store and died much later inside migrations with an unrelated-looking SQLITE_CANTOPEN, while any writes that had already succeeded were doomed to vanish on page close. That's the raw trace behind lightninglabs/wavelength-sdk#48.

This makes the open fail closed instead: require_persistent on the DSN turns a missing persistent OPFS VFS into a real locked-database error at open time, so there's no silent in-memory downgrade and no confusing failure later in migrations.

Changes

  • Set require_persistent=true on both wasm SQLite DSNs: the generic db store (db/sqlite_open_wasm.go) and btcwallet's OPFS-backed walletdb (lwwallet/walletdb_wasm.go).
  • Widen the open-retry predicate to SQLITE_BUSY and database is locked alongside the existing SQLITE_CANTOPEN, since contention now surfaces as an open failure rather than an in-memory fallback. The helpers are renamed isWASM*RetryableOpen to match.
  • The retry budget still absorbs the brief handle-release race right after a reload. A lock holder that never goes away exhausts the retries and returns the locked error to the caller, where the SDK maps it to a typed wallet_locked.

Testing

  • Built the daemon to wasm and exercised the two-tab flow in the browser against a regtest stack: a second tab surfaces a locked-database error at open time instead of booting on an in-memory store, and a single tab (including a reload after the previous runtime releases its handles) still opens normally.

Related

jamaljsr added 2 commits July 22, 2026 16:07
A second browser tab cannot acquire the wallet database's exclusive
OPFS handles, and the SQLite worker used to degrade that open failure
to an in-memory database. The daemon then booted against a throwaway
store and died much later inside migrations with an unrelated-looking
SQLITE_CANTOPEN, while any writes that had succeeded were doomed to
vanish on page close (issue wavelength-sdk#48).

Set require_persistent on the DSN so the open fails closed with the
real locked-database error instead. Widen the open retry predicate to
SQLITE_BUSY, which is how lock contention now surfaces: the retries
still absorb the brief handle-release race after a reload, and a lock
holder that never goes away exhausts them and returns the locked error
to the caller, where the SDK maps it to a typed wallet_locked failure.
Mirror the db store's fail-closed behavior for btcwallet's OPFS-backed
walletdb: never fall back to an in-memory database when the persistent
OPFS VFS cannot be opened, and treat SQLITE_BUSY as retryable alongside
SQLITE_CANTOPEN, since lock contention now surfaces as an open failure
rather than a silent in-memory downgrade.
@jamaljsr jamaljsr self-assigned this Jul 23, 2026
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@jamaljsr

Copy link
Copy Markdown
Member Author

Complemented by the SDK-side fast-path guard in lightninglabs/wavelength-sdk#56, which detects the second tab via a Web Lock before it reaches this daemon path.

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