Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ package may import from a higher layer.
| [`serverconn/mailboxpull`](serverconn/mailboxpull/) | Shared exponential-backoff retry primitives for mailbox pull loops (used by serverconn ingress and SDK swap consumers) |
| [`rpcauth`](rpcauth/) | Shared macaroon and TLS helpers securing gRPC/REST connections |
| [`metrics`](metrics/) | Prometheus instrumentation namespaced under `waved_`: event-driven counter actor pool plus a scrape-time `SystemCollector` for live gauges, and an opt-in `/metrics` HTTP server |
| [`internal/sqlbase`](internal/sqlbase/) | `walletdb`-compatible key/value backend over `database/sql` (js/wasm walletdb storage for `lwwallet` browser builds) |
| [`internal/sqlbase`](internal/sqlbase/) | `walletdb`-compatible key/value backend over `database/sql` (SQL walletdb storage for `lwwallet`) |
| [`internal/wasmhost`](internal/wasmhost/) | js/wasm host detection (browser vs Node) and the durable SQLite VFS name that follows from it; imported by `db`, `lwwallet`, and `cmd/wavewalletdk-wasm` |

### Layer 3: Application & Orchestration
Expand Down
81 changes: 45 additions & 36 deletions cmd/waved/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,42 +128,7 @@ func newRootCmd() *cobra.Command {

registerArkServerFlags(f, cfg)

// Wallet backend flags.
f.String(
"wallet.type", cfg.Wallet.Type,
"wallet backend type (lnd, lwwallet, btcwallet)",
)
f.String(
"wallet.esploraurl", cfg.Wallet.EsploraURL,
"esplora REST API URL (required for lwwallet)",
)
f.String(
"wallet.feeurl", cfg.Wallet.FeeURL,
"fee-estimate JSON endpoint URL (required for btcwallet)",
)
f.String(
"wallet.btcwallet_blockheaderssource",
cfg.Wallet.BtcwBlockSource,
"block header import source for btcwallet fast sync",
)
f.String(
"wallet.btcwallet_filterheaderssource",
cfg.Wallet.BtcwFilterSource,
"filter header import source for btcwallet fast sync",
)
f.Duration(
"wallet.pollinterval", cfg.Wallet.PollInterval,
"chain poll interval for lwwallet backend",
)
f.Uint32(
"wallet.recoverywindow", cfg.Wallet.RecoveryWindow,
"address recovery look-ahead window for lwwallet",
)
f.String(
"wallet.password_file", cfg.Wallet.PasswordFile, "path to "+
"file containing wallet password for auto-unlock "+
"at startup (lwwallet/btcwallet)",
)
registerWalletFlags(f, cfg)

registerBitcoindFlags(f)

Expand Down Expand Up @@ -362,6 +327,50 @@ func registerDaemonRPCFlags(f *pflag.FlagSet, cfg *waved.Config) {
)
}

// registerWalletFlags registers the flags of the daemon's wallet
// backends.
func registerWalletFlags(f *pflag.FlagSet, cfg *waved.Config) {
f.String(
"wallet.type", cfg.Wallet.Type,
"wallet backend type (lnd, lwwallet, btcwallet)",
)
f.String(
"wallet.esploraurl", cfg.Wallet.EsploraURL,
"esplora REST API URL (required for lwwallet)",
)
f.String(
"wallet.dbbackend", cfg.Wallet.DBBackend,
"wallet database backend for lwwallet (bolt, sqlite)",
)
f.String(
"wallet.feeurl", cfg.Wallet.FeeURL,
"fee-estimate JSON endpoint URL (required for btcwallet)",
)
f.String(
"wallet.btcwallet_blockheaderssource",
cfg.Wallet.BtcwBlockSource,
"block header import source for btcwallet fast sync",
)
f.String(
"wallet.btcwallet_filterheaderssource",
cfg.Wallet.BtcwFilterSource,
"filter header import source for btcwallet fast sync",
)
f.Duration(
"wallet.pollinterval", cfg.Wallet.PollInterval,
"chain poll interval for lwwallet backend",
)
f.Uint32(
"wallet.recoverywindow", cfg.Wallet.RecoveryWindow,
"address recovery look-ahead window for lwwallet",
)
f.String(
"wallet.password_file", cfg.Wallet.PasswordFile, "path to "+
"file containing wallet password for auto-unlock "+
"at startup (lwwallet/btcwallet)",
)
}

// registerArkServerFlags registers the daemon's outbound Ark operator flags.
func registerArkServerFlags(f *pflag.FlagSet, cfg *waved.Config) {
f.String(
Expand Down
1 change: 1 addition & 0 deletions docs/daemon_cli_guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,7 @@ waved \
| `--wallet.btcwallet_filterheaderssource` | | Filter header import source for btcwallet fast sync |
| `--wallet.pollinterval` | `30s` | Esplora poll interval (lwwallet only) |
| `--wallet.recoverywindow` | `100` | Address look-ahead window (lwwallet only) |
| `--wallet.dbbackend` | `bolt` | Wallet database backend (lwwallet only): `bolt` or `sqlite`; fixed once the wallet is created |
| `--wallet.password_file` | | Auto-unlock password file path (lwwallet/btcwallet) |
| `--lnd.host` | `localhost:10009` | lnd gRPC address |
| `--lnd.tlspath` | | Path to lnd TLS certificate |
Expand Down
16 changes: 7 additions & 9 deletions internal/sqlbase/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

## Purpose

A `walletdb`-compatible key/value backend implemented over `database/sql`,
built only for `js && wasm` (every file carries that build tag). It emulates
`btcwallet/walletdb` buckets/cursors on top of a relational table so
`lwwallet` can run the wallet stack in the browser (SQLite via
`go-wasmsqlite`), where the native BoltDB/`kvdb` backends are unavailable.
A `walletdb`-compatible key/value backend implemented over `database/sql`.
It emulates `btcwallet/walletdb` buckets/cursors on top of a relational
table so `lwwallet` can run the wallet stack on any `database/sql` driver
instead of the BoltDB/`kvdb` backends: `go-wasmsqlite` (OPFS) in the
browser, where BoltDB is unavailable at all, and `modernc.org/sqlite` on
native builds.

## Key Types

Expand All @@ -29,13 +30,10 @@ built only for `js && wasm` (every file carries that build tag). It emulates

- **Depends on**: `btcwallet/walletdb` (interface being implemented),
`lnd/sqldb` (shared SQL error classification).
- **Depended on by**: `lwwallet` (wasm builds only, via `internal/sqlbase`).
- **Depended on by**: `lwwallet` (all platforms).

## Invariants

- Every file is `//go:build js && wasm`; this package does not build (and
cannot be exercised) on native `GOOS`/`GOARCH` — use
`GOOS=js GOARCH=wasm go build ./internal/sqlbase` or `go doc` to inspect it.
- `DefaultNumTxRetries = 50`: `Update`/`View` retry on transaction errors that
permit repetition, calling the caller's `reset` before each retry.
- `WithTxLevelLock` serializes all read-write transactions through a single
Expand Down
16 changes: 7 additions & 9 deletions internal/sqlbase/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@

## Purpose

A `walletdb`-compatible key/value backend implemented over `database/sql`,
built only for `js && wasm` (every file carries that build tag). It emulates
`btcwallet/walletdb` buckets/cursors on top of a relational table so
`lwwallet` can run the wallet stack in the browser (SQLite via
`go-wasmsqlite`), where the native BoltDB/`kvdb` backends are unavailable.
A `walletdb`-compatible key/value backend implemented over `database/sql`.
It emulates `btcwallet/walletdb` buckets/cursors on top of a relational
table so `lwwallet` can run the wallet stack on any `database/sql` driver
instead of the BoltDB/`kvdb` backends: `go-wasmsqlite` (OPFS) in the
browser, where BoltDB is unavailable at all, and `modernc.org/sqlite` on
native builds.

## Key Types

Expand All @@ -29,13 +30,10 @@ built only for `js && wasm` (every file carries that build tag). It emulates

- **Depends on**: `btcwallet/walletdb` (interface being implemented),
`lnd/sqldb` (shared SQL error classification).
- **Depended on by**: `lwwallet` (wasm builds only, via `internal/sqlbase`).
- **Depended on by**: `lwwallet` (all platforms).

## Invariants

- Every file is `//go:build js && wasm`; this package does not build (and
cannot be exercised) on native `GOOS`/`GOARCH` — use
`GOOS=js GOARCH=wasm go build ./internal/sqlbase` or `go doc` to inspect it.
- `DefaultNumTxRetries = 50`: `Update`/`View` retry on transaction errors that
permit repetition, calling the caller's `reset` before each retry.
- `WithTxLevelLock` serializes all read-write transactions through a single
Expand Down
14 changes: 6 additions & 8 deletions internal/sqlbase/db.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build js && wasm

package sqlbase

import (
Expand Down Expand Up @@ -77,6 +75,8 @@ type db struct {
//
// TODO: This is an anti-pattern that is in place until the kvdb
// interface supports a context.
//
//nolint:containedctx
ctx context.Context

// db is the underlying database connection instance.
Expand Down Expand Up @@ -163,11 +163,6 @@ func (db *db) getTimeoutCtx() (context.Context, func()) {
return context.WithTimeout(db.ctx, db.cfg.Timeout)
}

// getPrefixedTableName returns a table name for this prefix (namespace).
func (db *db) getPrefixedTableName(table string) string {
return fmt.Sprintf("%s_%s", db.prefix, table)
}

// catchPanic executes the specified function. If a panic occurs, it is returned
// as an error value.
func catchPanic(f func() error) (err error) {
Expand Down Expand Up @@ -208,9 +203,11 @@ func catchPanic(f func() error) (err error) {
// expect retries of the f closure (depending on the database backend used), the
// reset function will be called before each retry respectively.
func (db *db) View(f func(tx walletdb.ReadTx) error, reset func()) error {
// walletdb.ReadWriteTx embeds walletdb.ReadTx, so the read-write
// transaction satisfies the read-only callback as-is.
return db.executeTransaction(
func(tx walletdb.ReadWriteTx) error {
return f(tx.(walletdb.ReadTx))
return f(tx)
},
reset, true,
)
Expand Down Expand Up @@ -245,6 +242,7 @@ func (db *db) executeTransaction(f func(tx walletdb.ReadWriteTx) error,
}

reset()

return catchPanic(func() error { return f(kvTx) })
}

Expand Down
2 changes: 0 additions & 2 deletions internal/sqlbase/db_conn_set.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build js && wasm

package sqlbase

import (
Expand Down
2 changes: 0 additions & 2 deletions internal/sqlbase/log.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build js && wasm

package sqlbase

import "github.com/btcsuite/btclog/v2"
Expand Down
26 changes: 17 additions & 9 deletions internal/sqlbase/readwrite_bucket.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build js && wasm

package sqlbase

import (
Expand Down Expand Up @@ -84,7 +82,7 @@ func (b *readWriteBucket) Get(key []byte) []byte {
err := row.Scan(&value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil

case err != nil:
Expand Down Expand Up @@ -137,7 +135,7 @@ func (b *readWriteBucket) NestedReadWriteBucket(
err := row.Scan(&id)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil

case err != nil:
Expand Down Expand Up @@ -173,7 +171,7 @@ func (b *readWriteBucket) CreateBucket(key []byte) (walletdb.ReadWriteBucket,
err := row.Scan(&id, &value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
case err == nil && value == nil:
return nil, walletdb.ErrBucketExists

Expand Down Expand Up @@ -229,7 +227,7 @@ func (b *readWriteBucket) CreateBucketIfNotExists(key []byte) (
switch {
// Bucket does not yet exist, so create it now. Postgres will generate a
// bucket id for the new bucket.
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
row, cancel := b.tx.QueryRow(
"INSERT INTO "+b.table+" (parent_id, key) "+
"VALUES($1, $2) RETURNING id",
Expand Down Expand Up @@ -365,7 +363,7 @@ func (b *readWriteBucket) Delete(key []byte) error {
err := row.Scan(&dummy)
switch {
// No bucket exists, proceed to deletion of the key.
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
case err != nil:
return err

Expand Down Expand Up @@ -447,7 +445,7 @@ func (b *readWriteBucket) Sequence() uint64 {
err := row.Scan(&seq)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return 0

case err != nil:
Expand All @@ -473,6 +471,13 @@ func (b *readWriteBucket) ForAll(cb func(k, v []byte) error) error {
}
defer cancel()

// cancel only releases the query's timeout context; the result set
// holds a connection from the pool until it is closed explicitly,
// which rows.Next() only does for us if it is driven to completion.
defer func() {
_ = rows.Close()
}()

for rows.Next() {
var key, value []byte

Expand All @@ -487,5 +492,8 @@ func (b *readWriteBucket) ForAll(cb func(k, v []byte) error) error {
}
}

return nil
// A row error terminates the loop above just like a fully consumed
// result set does, so without this check a truncated bucket walk is
// indistinguishable from a complete one.
return rows.Err()
}
15 changes: 7 additions & 8 deletions internal/sqlbase/readwrite_cursor.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
//go:build js && wasm

package sqlbase

import (
"database/sql"
"errors"

"github.com/btcsuite/btcwallet/walletdb"
)
Expand Down Expand Up @@ -39,7 +38,7 @@ func (c *readWriteCursor) First() ([]byte, []byte) {
err := row.Scan(&key, &value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil, nil

case err != nil:
Expand Down Expand Up @@ -69,7 +68,7 @@ func (c *readWriteCursor) Last() ([]byte, []byte) {
err := row.Scan(&key, &value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil, nil

case err != nil:
Expand Down Expand Up @@ -100,7 +99,7 @@ func (c *readWriteCursor) Next() ([]byte, []byte) {
err := row.Scan(&key, &value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil, nil

case err != nil:
Expand Down Expand Up @@ -131,7 +130,7 @@ func (c *readWriteCursor) Prev() ([]byte, []byte) {
err := row.Scan(&key, &value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil, nil

case err != nil:
Expand Down Expand Up @@ -169,7 +168,7 @@ func (c *readWriteCursor) Seek(seek []byte) ([]byte, []byte) {
err := row.Scan(&key, &value)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil, nil

case err != nil:
Expand Down Expand Up @@ -199,7 +198,7 @@ func (c *readWriteCursor) Delete() error {
err := row.Scan(&key)

switch {
case err == sql.ErrNoRows:
case errors.Is(err, sql.ErrNoRows):
return nil

case err != nil:
Expand Down
2 changes: 0 additions & 2 deletions internal/sqlbase/readwrite_tx.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//go:build js && wasm

package sqlbase

import (
Expand Down
Loading
Loading