Skip to content

v0.9.0

Latest

Choose a tag to compare

@DoktorShift DoktorShift released this 26 Aug 21:49
60448fa

NUTbits v0.9.0

This release gives NUTbits a real payment ledger. Balances are now committed before a payment instead of deducted after it, unknown outcomes are resolved against the mint instead of guessed, and every interface shows what you can actually spend.

Homepage

NUTbits Landingpage

The Allocation Ledger

NUTbits holds one pool of ecash. A dedicated connection's balance is a claim on part of that pool, so the claim is only worth what enforces it. One component now does that enforcing.

  • Funds are committed before a payment and settled against the actual spend afterwards
  • Concurrent payments can no longer both pass the same balance check and both go out
  • dedicated_balance_msat has exactly one writer, so the rule cannot drift back apart
  • Withdrawing cannot take sats already committed to a payment in flight
  • Revoking a connection with a payment in flight no longer double spends

Three Payment Outcomes, Not Two

A Lightning payment can succeed, definitely fail, or leave you with no answer. Only the first two are safe to act on.

  • An unclear melt is resolved by asking the mint about the quote (NUT-05)
  • PAID records the spend, UNPAID hands the ecash back, and no answer keeps the sats committed
  • Proofs are no longer restored after a melt the mint may already have spent
  • A payment awaiting reconciliation is reported by nutbits status, not buried in a log
  • GET /api/v1/ledger lists them; POST /api/v1/ledger/holds/:id/resolve settles or returns one
  • Mint calls are bounded by NUTBITS_MINT_TIMEOUT_MS, so one unresponsive mint no longer blocks every payment to it for the life of the process

Spendable vs Total

The pool total was never what you could send. Now the difference is visible everywhere instead of being discovered on a rejection.

  • total is every sat held; spendable is total minus allocations minus payments in flight
  • CLI, TUI and GUI all gate on spendable, so none of them offers a payment the wallet will refuse
  • nutbits withdraw shows the exact withdrawable figure per connection and names the locked remainder
  • Wallets with no dedicated connections see exactly the output they saw before

Fund Safety Fixes

The private report that started this release came from Muhammed Shekho (@SYR-ROOT): two concurrent pay_invoice events on one dedicated connection could both pass the balance check and both pay. Auditing around it turned up more of the same kind.

  • POST /api/v1/pay compared against the raw pool with no allocations subtracted, so CLI, TUI and GUI payments could spend the funds backing every dedicated connection. No race required
  • FileStore.getProofs handed out its live internal array, so a payment in flight could delete newly minted proofs as if they had been spent. Fund loss on the default backend
  • NUT-13 counters were not reserved atomically; two operations could derive identical blinded messages, which the mint refuses and the ecash behind is lost
  • Switching storage backends dropped the NUT-13 counters entirely, re-deriving already spent secrets
  • A new connection was live on relays before dedicated was set, briefly granting shared wallet access
  • An unauthenticated /connect racing an authenticated create could be handed the wrong connection
  • Concurrent incoming payments to one connection lost a credit
  • Two events paying the same invoice let the loser overwrite the winner's record, so a successful payment reported itself as failed
  • MySQL lost updates in updateConnection and updateTx

Reliability

  • A second NUTbits process on the same state backend is refused at boot. Previously it silently took over the socket and both kept running, which double spends
  • Reservations survive a crash and come back held, so an interrupted payment is reported rather than forgotten
  • Configuration is validated before first use. NUTBITS_FEE_RESERVE_PCT=abc used to become NaN, and NaN - needed < 0 is false, so the fee reserve silently stopped applying; the same NaN turned the spending caps into "no limit"
  • A failed payment returns an HTTP error instead of 200 OK with success: false, which every client rendered as "Payment sent!"
  • --version no longer reports a stale number

Tests

NUTbits had no test suite. It has one now.

  • npm test, 128 tests, no mint or relay required
  • The reported race is one of them
  • One shared mutex primitive replaces four hand written copies

Documentation

  • HOW-IT-WORKS.md explains how balances are protected and why the wallet reports two figures
  • DATABASE.md and STATE.md advertised MySQL for "multiple NUTbits instances". That was never safe, and they now say so
  • AGENTS.md carries the ledger contract, since it is the file contributors read first
  • Every em dash removed from the repository

Upgrading

Existing wallets load without migration. Two things to expect:

  1. If you have funded dedicated connections, nutbits pay can now send less than before. That is the fix, not a regression: those sats were never yours to spend. Run nutbits balance to see the split
  2. If you run more than one NUTbits process against one state file or database, the second will now refuse to start. It was never safe; nothing had stopped it

MySQL users: the reservation table is created on first start. The code path is syntax checked and mirrors SQLite statement for statement, but it has not been exercised against a live MySQL server. Take a backup first.

Breaking Changes

  • POST /api/v1/pay no longer spends into dedicated allocations, and applies the fee reserve as the NWC path always did
  • A failed payment returns a 5xx instead of 200 with success: false
  • GET /api/v1/status reports balance_sats across all mints, matching /api/v1/balance. It was active mint only, so spendable_sats could read higher than the balance
  • A second instance on the same state backend is refused at boot
  • createNWCconnection takes a profile object and returns { app_pubkey, nwc_string }