Skip to content

Latest commit

 

History

History
175 lines (128 loc) · 7.03 KB

DATABASE.md

File metadata and controls

175 lines (128 loc) · 7.03 KB

Storage Backends

Storage Backends

Overview · File · SQLite · MySQL · Migration · Encryption · Which One?

NUTbits supports three storage backends. Choose based on your use case.

At a Glance

file sqlite mysql
Best for Personal use, simple setups LNbits, concurrent payments A shared/remote database
Concurrent safety Write mutex (serialized) Full ACID transactions Full ACID transactions
Multiple instances Not supported Not supported Not supported (see below)
Dependencies None better-sqlite3 mysql2
Setup Zero config Zero config Requires MySQL server
Portable Single .enc file Single .db file Remote database
Encryption Whole-file AES-256-GCM Per-column AES-256-GCM Per-column AES-256-GCM

File Backend (default)

The original approach. Entire state is encrypted and written to a single .enc file.

# .env
NUTBITS_STATE_BACKEND=file
NUTBITS_STATE_FILE=./nutbits_state.enc
NUTBITS_STATE_PASSPHRASE=your-passphrase

How it works: All state lives in memory. On every mutation, the entire state is encrypted and written atomically (write to .tmp, then rename). A write mutex prevents concurrent writes from clobbering each other.

Limitation: Two concurrent payments can still read stale proof data between async await points. The mutex serializes writes but not reads. For single-user setups this rarely matters. For LNbits with concurrent users, use SQLite or MySQL.

See STATE.md for encryption details and manual decryption.

SQLite Backend (recommended)

Atomic per-proof operations via SQLite transactions. Best balance of simplicity and safety.

# Install driver
npm install better-sqlite3

# .env
NUTBITS_STATE_BACKEND=sqlite
NUTBITS_STATE_PASSPHRASE=your-passphrase
# Optional: custom path (default: nutbits_state.db)
# NUTBITS_SQLITE_PATH=./nutbits.db

How it works: Each proof, connection, and transaction is a row in the database. Sensitive fields (private keys, proof secrets, invoices) are encrypted per-column with AES-256-GCM. Proof operations use SQLite transactions for atomicity; two concurrent payments cannot select the same proofs.

Schema:

proofs        - one row per ecash proof (proof_id, mint_url, amount, proof_enc)
connections   - one row per NWC connection (app_pubkey, data_enc, balance)
              includes per-connection: permissions, spending limits, service fee rates
transactions  - one row per payment (payment_hash, app_pubkey, data_enc)
              includes: fees_paid (routing), service_fee (operator), settled_at
daily_spend   - per-connection daily spend tracking (spend_key, sats)
reservations  - funds committed to a payment in flight (id, app_pubkey, gross_msat,
              net_msat, created_at, held, reason) - owned by ledger.js, removed on
              settle/release/resolve, restored as holds at boot. Not encrypted:
              no secrets, same as balance and daily_spend.
mints         - configured mints (url, last_healthy)
config        - key-value settings (active_mint_url, encryption_salt, schema_version)

Existing databases upgrade themselves on first start. CREATE TABLE IF NOT EXISTS runs on every open, so new tables simply appear; the schema_version row in config then moves forward in its own transaction. Nothing is rewritten and nothing is lost.

MySQL Backend

For a shared or remote database - typically because you already run MySQL, or want the wallet state to live somewhere other than the NUTbits host.

One instance at a time, on any backend.

MySQL gives you a remote database, not a clustered NUTbits. Two NUTbits processes against one database will spend the same ecash twice: proof selection and the allocation ledger are both serialized by in-process locks, so neither instance can see the other's in-flight work. Both read the same proofs, both melt them, and one payment fails while its proofs are restored as though unspent.

Since v0.8.1 a second instance on the same host is refused at boot via a PID lock beside the state file. That guard cannot see another machine, so pointing two hosts at one MySQL database is still your responsibility to avoid. Payment reservations are now persisted, which was one prerequisite, but the remaining one is not met: the locks in lib/mutex.js serialize one event loop, not a cluster, so two processes still cannot see each other's in-flight work. Making it safe needs a lock held in the database itself.

# Install driver
npm install mysql2

# .env
NUTBITS_STATE_BACKEND=mysql
NUTBITS_STATE_PASSPHRASE=your-passphrase
NUTBITS_MYSQL_URL=mysql://user:password@localhost:3306/nutbits

How it works: Same schema and encryption as SQLite, adapted for MySQL. Uses connection pooling and InnoDB transactions. Tables are auto-created on first run.

Create the database first:

CREATE DATABASE nutbits CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'nutbits'@'%' IDENTIFIED BY 'your-db-password';
GRANT ALL PRIVILEGES ON nutbits.* TO 'nutbits'@'%';

Migration

From file to SQLite/MySQL

Automatic. On first run with a new backend, NUTbits detects the existing .enc file and migrates all data:

  1. Decrypts the .enc file
  2. Inserts all proofs, connections, and transactions into the database
  3. Renames the .enc file to .enc.migrated (backup)
[MIGRATION] Migrated .enc state to SQLite. Old file renamed to nutbits_state.enc.migrated

No manual steps needed. Just change NUTBITS_STATE_BACKEND and restart.

From SQLite to MySQL (or vice versa)

Not automatic. Export/import manually:

  1. Stop NUTbits
  2. Use the old backend to read state
  3. Use the new backend to write state
  4. Update .env and restart

Encryption Details

All backends encrypt sensitive data with the same algorithm:

  • Key derivation: scrypt (passphrase + random salt = 32-byte AES key)
  • Encryption: AES-256-GCM (authenticated encryption)
  • Per-value IV: Each encrypted field gets a fresh 12-byte random IV
  • Format: [IV (12 bytes)][Auth Tag (16 bytes)][Ciphertext]

The encryption salt is stored:

  • File backend: Random per-save (embedded in the .enc file header)
  • SQLite/MySQL: Generated once at database creation, stored in the config table

The NUTBITS_STATE_PASSPHRASE is the only secret. Without it, the data is unreadable.

Which Backend Should I Use?

Personal use, single user          → file
LNbits funding source              → sqlite
State must live off-host           → mysql

Multiple NUTbits instances         → not supported on any backend

Related

  • STATE.md - encrypted state file format and manual decryption
  • BACKUP.md - backup, recovery, and seed recovery
  • INSTALL.md - getting NUTbits up and running