Commit graph

2557 commits

Author SHA1 Message Date
Slyghtning
fb6e6101e8
instantout/reservation: adapt hardening tests to protocol FSM
The security tests use the pre-feature helper names. Update them for
the client-requested reservation manager API after combining both PR
stacks.
2026-08-11 15:03:14 +02:00
Slyghtning
08a06a1914
loopdb: renumber reservation prepay migration
Current master already uses migration 15 for static address withdrawals.
Move the reservation prepay migration to the next available version so
database initialization does not reject the duplicate version.
2026-08-11 15:02:45 +02:00
Slyghtning
10a50ef452
instantout/reservation: lock initial currentHeight write in Run
Run wrote m.currentHeight = height without holding the lock, while
later writes (newBlockChan case) and reads in
RequestReservationFromServer take m.Lock. Daemon startup serializes
'wait for initChan' before serving RPC, so in practice the race
window is short, but the race detector flags it -- and on the
nautilus side a similar pattern is the most plausible cause of the
unit-race CI failure on the buy-reservations head commit. Symmetric
fix here keeps the synchronisation rule uniform.
2026-08-11 15:02:44 +02:00
Slyghtning
29c92a10ea
instantout: detach reservation unlock cleanup context
handleErrorAndUnlockReservations is called specifically from error paths
and from the new OnRecover cleanup. In practice the caller's ctx is
almost always already canceled by the time we get here (caller timeout,
daemon shutdown, ctx.Done() arm in PollPaymentAcceptedAction, etc.). The
existing implementation derived its 30s timeout context from that
canceled parent, so:

  - The for-loop calling UnlockReservation immediately hit ctx.Err() ==
    context.Canceled on every reservation. Locks were never released on
    disk.
  - The goroutine sending CancelInstantSwap to the server captured the
    same already-canceled ctx, then further wrapped it in WithTimeout
    (still canceled). The server never heard about the cancel.

Both code paths were no-ops in exactly the scenario they were written
for. Switch to context.Background() with a fresh 30s timeout so the
cleanup actually runs. The goroutine also gets its own background
context (the previous code captured the parent's already-done ctx via
closure, then re-wrapped it).
2026-08-11 15:02:44 +02:00
Slyghtning
10512bfa19
instantout: unlock reservations on OnRecover from in-flight states
SendPaymentAndPollAccepted and BuildHtlc both run after
PollPaymentAcceptedAction has called LockReservation on every
reservation backing the swap. Their OnRecover transitions pointed
directly to Failed, whose action is fsm.NoOpAction -- so on daemon
restart while in either state, the FSM moved to Failed without ever
unlocking the reservations. The local store kept them in the Locked
state until on-chain expiry (typically tens of hours later), making
them unusable for any subsequent swap. For users who pay for
reservations (PR #883's invoice-requested flow) that is a direct
material loss.

Add an intermediate UnlockReservationsOnRecover state whose action
calls handleErrorAndUnlockReservations and then routes to Failed via
the normal OnError edge. SendPaymentAndPollAccepted.OnRecover and
BuildHtlc.OnRecover now point at this state instead of Failed
directly.

Init.OnRecover -> Failed is left alone because at that point the
InstantOut row has not yet been persisted and no reservation locks
have been taken; there is nothing to clean up. Post-PushPreimage
states (PushPreimage.OnRecover -> PushPreimage, etc.) are also left
alone since they self-loop on recovery rather than terminate.

The cleanup helper itself still derives its context from the caller's
context (see existing handleErrorAndUnlockReservations); fixing that
context-cancel hazard is a separate change.
2026-08-11 15:02:44 +02:00
Slyghtning
17df6e86e6
instantout/reservation: extend RPC state wait timeout
RequestReservationFromServer blocked for defaultWaitForStateTime (15s)
waiting for the FSM to reach SendPrepaymentPayment. Reaching that state
requires, in order:

  - Wallet.DeriveNextKey (local lnd round-trip)
  - server's RequestReservation gRPC (network + server's own lnd invoice
    creation, including hold-invoice persistence)
  - LightningClient.DecodePaymentRequest
  - Store.CreateReservation

15 seconds was achievable on a fast LAN with idle servers, but under
even modest load (server-side hold-invoice creation can routinely take
several seconds in the wild) the timer expired and the RPC returned an
error to the caller. The FSM kept running in the background and the
wallet would still pay the prepay LN invoice -- so the user got an
error, but their funds still moved. The next call to the same
reservation_id would then fail mysteriously because the server-side
state was already advanced.

Bump to 60s. The right longer-term fix is to plumb the caller's gRPC
context into the FSM SendEvent so cancellation actually aborts the
in-flight server call instead of orphaning it; that's a larger refactor
and is left as a follow-up.
2026-08-11 15:02:44 +02:00
Slyghtning
b988ea8788
loopdb: lowercase protocol_version column in migration 14
Migration 000014_reservation_protocol_version used 'protocol_Version'
(capital V) in the ADD COLUMN / DROP COLUMN statements. Postgres folds
unquoted identifiers to lowercase and SQLite is case-insensitive on
identifier comparison, so the running schema column is
'protocol_version' either way -- but the sqlc-generated Go
(loopdb/sqlc/reservations.sql.go) also uses the lowercase form, so the
file as written was both unusual and inconsistent with its own generated
SQL.

Use 'protocol_version' everywhere. No data migration is required; the
column on disk is unchanged. Pure cosmetic / portability fix.
2026-08-11 15:02:43 +02:00
Slyghtning
402b431c80
instantout/reservation: persist client reservations as Init
InitFromClientRequestAction wrote the new reservation row via
Store.CreateReservation while reservation.State was still the zero value
(fsm.EmptyState) returned by NewReservation. The
GetClientInitiatedReservationStates() state map has no OnRecover
transition on EmptyState. If the daemon crashed (or the context was
cancelled) any time after CreateReservation returned, the row was
permanently stuck: on restart RecoverReservations rebuilt the FSM at
state "", SendEvent(OnRecover) returned "event not allowed", and the
goroutine just logged and gave up. The HD key index was wasted; the
server-side reservation was left orphan.

Set reservation.State = Init before persisting. The Init state already
has OnRecover: Failed, so a crashed-mid-Init reservation now recovers
cleanly into Failed on the next start. updateReservation's existing
skip-list keeps the immediately-following SendPrepaymentPayment
transition working as before (it skips writes while in Init).

A follow-up should also notify the server to cancel orphaned
reservations from Failed.OnRecover; that requires plumbing a cancel-RPC
into the client-initiated state map.
2026-08-11 15:02:43 +02:00
Slyghtning
3c495a2a5c
cmd/loop: require explicit 'y' confirmation on reservation new
The reservation new command printed the prepay cost and asked the user
to confirm with 'y/n'. The implementation read the answer with
fmt.Scanln(&answer) and treated only the literal 'n' as a 'no'. The
return value was discarded, so:

  - On EOF / closed stdin (CI pipelines, automated wrappers, terminal
    disconnect) Scanln returned an error and answer remained the empty
    string, which is not 'n', so the command proceeded and paid the
    LN prepayment with no user confirmation.
  - The case-sensitive 'n' check also accepted 'N', 'no', 'yes', 'Y',
    or any other string as a 'yes'.

Match the convention used by the rest of the loop CLI: only continue
when the user typed exactly 'y' (or 'Y'), and treat any read error as
'no'.
2026-08-11 15:02:42 +02:00
Slyghtning
800dae0750
instantout/reservation: honor context when queueing request
RequestReservationFromServer dispatched the OnClientInitialized event to
the manager's Run loop via a bare 'm.reqChan <- ...' send. reqChan is an
unbuffered channel; if Run had already returned (e.g. because the block
epoch subscription errored, or the manager is shutting down) the send
would block forever, holding the gRPC handler goroutine and the caller's
connection open until something external killed it.

Wrap the send in a select that also watches the caller's context. A
cancelled caller context now returns ctx.Err() instead of hanging.

Note: this still does not detect "Run exited cleanly while reqChan was
empty" -- doing that requires exposing Run's runCtx (or a quit channel)
on the Manager struct. That refactor is left for a follow-up; the
caller-side cancel path above is enough to keep RPC handlers from
leaking when their grpc deadline fires.
2026-08-11 15:02:42 +02:00
Slyghtning
eb58fcaa03
instantout/reservation: avoid uint32 underflow in expiry-bounds check
InitFromClientRequestAction validates that the server-returned
absolute expiry is within +/- expiryDelta of expectedExpiry =
relativeExpiry + heightHint. Both sides were uint32, so when
expectedExpiry < expiryDelta (low regtest heights, fresh
deployments, anything with heightHint = 0 like the existing test
fixtures) expectedExpiry - expiryDelta wrapped to ~2^32. The lower
bound check then trivially admitted any reasonable response, and the
client would accept e.g. Expiry = 0 from the server, immediately past
the reservation's own deadline -- meaning the server can sweep via
the expiry script path while the client still believes it owns the
reservation slot.

Promote the comparison to int64 so the arithmetic is sign-honest.
This is the smallest patch that closes the underflow; a follow-up
should also add an absolute floor (e.g. Expiry >= heightHint +
minSafeExpiry) so the server cannot return a near-deadline reservation
even within the delta.
2026-08-11 15:02:42 +02:00
Slyghtning
508b9dfdc1
multi: require Loop Out permission for Instant Out
The InstantOut RPC accepts a caller-controlled dest_addr that becomes
the output of the cooperative sweepless sweep (and of the htlc success
sweep on the fallback path), so it is a fund-moving operation equivalent
to LoopOut. Until now it required only swap:execute, while LoopOut
requires both swap:execute and loop:out. A macaroon scoped to
swap:execute -- intended for, say, an autoloop scheduler or a quote
poller -- could therefore drain reservation balances to an attacker
address. ReservationRequest is analogous on the inbound side: it
triggers an outgoing LN prepayment, so it also belongs behind loop:out.

We also harden the address handling in instantout.Manager.NewInstantOut
to match validateLoopOutRequest:

  - sweepAddr.IsForNet(m.cfg.Network) is now enforced. btcutil
    .DecodeAddress is more permissive than IsForNet for some formats
    (notably anything that happens to share a network prefix); without
    the explicit network check cross-chain copy-paste mistakes parse
    silently and then sign over an unspendable output.
  - The address must be one of the formats Loop normally accepts: P2TR /
    P2WSH / P2WPKH / P2SH / P2PKH. Anything else (e.g. a future address
    type that the user's wallet would otherwise interpret differently)
    is rejected up front rather than failing later in the signing path.

InstantOutQuote and ReservationQuote stay on swap:read since they are
read-only.
2026-08-11 15:02:42 +02:00
Slyghtning
e4c4e11627
loopd: nil-guard reservation/instant-out RPC handlers
When loopd is started without --experimental the swap client server's
reservationManager and instantOutManager are nil. ListReservations
already returns codes.Unimplemented in that case; the rest of the
instant-out / reservation RPC family didn't, and would dereference a
nil pointer.

Affected handlers (all of which now return the same Unimplemented
status):

  - ReservationRequest (new in PR #883)
  - ReservationQuote   (new in PR #883)
  - InstantOut
  - InstantOutQuote
  - ListInstantOuts

Without this fix an authenticated caller can crash the daemon by
invoking any of these RPCs against a non-experimental loopd. With
default localhost binding the attack surface is small, but loop is
also commonly fronted by lit / LSP wrappers that expose RPCs to other
internal services, so a single packet is enough for a remote DoS.
2026-08-11 14:57:32 +02:00
sputn1ck
56be61c31d
cmd: add new reservation clis 2026-08-11 14:57:32 +02:00
sputn1ck
b48fd9aa7c
looprpc_server: add reservation calls 2026-08-11 14:57:32 +02:00
sputn1ck
04a874e0e1
looprpc: add client calls 2026-08-11 14:57:32 +02:00
sputn1ck
5ca0ccd486
loopd: update reservation cfg 2026-08-11 14:56:59 +02:00
sputn1ck
327d4b42d0
loopdb: store reservation prepay invoice 2026-08-11 14:56:59 +02:00
sputn1ck
4f121287d6
reservation: add client requested reservations to manager 2026-08-11 14:56:59 +02:00
sputn1ck
23e533eebc
reservations: add client requested fsm 2026-08-11 14:56:26 +02:00
sputn1ck
af017413a3
swapserverrpc: add buying reservations 2026-08-11 14:56:26 +02:00
sputn1ck
96572f39ba
reservation: add protocol version 2026-08-11 14:56:25 +02:00
Slyghtning
4f07354ff5
docs: document instant out reliability improvements
Record the reservation and Instant Out validation, recovery, fee-limit,
and lifecycle updates in the next release notes.
2026-08-11 12:16:08 +02:00
Slyghtning
63a0bccf3d
instantout: enforce the accepted swap fee
Carry the accepted quote into each request, persist it, and reject
invoices above that limit while retaining millisatoshi precision.
2026-08-11 12:16:08 +02:00
Slyghtning
cfbf74161b
instantout: recheck reservation timing during recovery
Compare reservation expiry with the current height during recovery and
use the HTLC path when the remaining window is too short.
2026-08-11 12:16:07 +02:00
Slyghtning
adc181ea83
instantout: close unfinished MuSig2 sessions
Clean up abandoned signing sessions on error paths while leaving
completed sessions to lnd.
2026-08-11 12:16:07 +02:00
Slyghtning
e0412ac775
instantout: verify finalized MuSig2 witnesses
Run script validation for every combined signature before accepting a
finalized transaction, surfacing invalid witnesses immediately.
2026-08-11 12:16:07 +02:00
Slyghtning
35a2df080f
instantout: validate MuSig2 response dimensions
Check nonce, signature, session, and transaction input counts before
indexing signing vectors, returning clear errors for incomplete data.
2026-08-11 12:16:06 +02:00
Slyghtning
a491bcc505
reservation: validate confirmed output amounts
Compare each confirmed transaction output with the expected reservation
amount before advancing the state machine.
2026-08-11 12:16:06 +02:00
Slyghtning
94015a5a9a
reservation: bound and prune active state machines
Limit active reservation state machines, remove terminal entries from
memory, and count recovered entries toward the same bound.
2026-08-11 12:16:06 +02:00
Slyghtning
e11c0bdfaf reservation: reject duplicate reservation entries
Check active and persisted reservations before creating a new state
machine, preserving the existing reservation when a duplicate arrives.
2026-08-11 11:32:52 +02:00
Slyghtning
055a80cf4e reservation: isolate asynchronous initialization errors
Use a goroutine-local result for event dispatch so observer errors
remain independent and initialization outcomes stay deterministic.
2026-08-11 11:31:55 +02:00
Slyghtning
a4973caa08 reservation: keep processing after notification errors
Log individual reservation initialization failures and continue
consuming later notifications instead of stopping the manager.
2026-08-11 11:31:08 +02:00
Slyghtning
6825e35594 looprpc: align instant out permissions with loop out
Apply the loop:out permission to Instant Out and reservation RPCs so
their authorization requirements match the rest of the Loop Out API.
2026-08-11 11:30:10 +02:00
Slyghtning
1b81a06b42
Merge pull request #1192 from hieblmi/no-change-label
Some checks failed
CI / Commit Message (push) Has been cancelled
CI / RPC compilation check (push) Has been cancelled
CI / SQL compilation check (push) Has been cancelled
CI / go mod check (push) Has been cancelled
CI / build and lint code (push) Has been cancelled
CI / verify that auto-generated documentation is up-to-date (push) Has been cancelled
CI / run unit-test sqlite3 race (push) Has been cancelled
CI / run unit-test postgres race (push) Has been cancelled
CI / run LiT itests (push) Has been cancelled
CI / run LiT unit tests (push) Has been cancelled
github: allow no-changelog to skip release notes
2026-08-06 11:06:38 +02:00
Slyghtning
57fb007f95
docs: document no-changelog release note opt-out 2026-08-04 11:02:33 +02:00
Slyghtning
6f0d817db4
github: allow no-changelog to skip release notes 2026-08-04 11:02:22 +02:00
Slyghtning
f5f76a61cc
Merge pull request #1191 from hieblmi/docs-gate
Some checks failed
CI / Release Notes (push) Has been cancelled
CI / Commit Message (push) Has been cancelled
CI / RPC compilation check (push) Has been cancelled
CI / SQL compilation check (push) Has been cancelled
CI / go mod check (push) Has been cancelled
CI / build and lint code (push) Has been cancelled
CI / verify that auto-generated documentation is up-to-date (push) Has been cancelled
CI / run unit-test sqlite3 race (push) Has been cancelled
CI / run unit-test postgres race (push) Has been cancelled
CI / run LiT itests (push) Has been cancelled
CI / run LiT unit tests (push) Has been cancelled
github: require release notes for pull requests
2026-08-03 11:48:15 +02:00
Slyghtning
4016f4a8f2
docs: instruct agents to add release notes 2026-08-03 11:18:14 +02:00
Slyghtning
414760c607
github: require release notes for pull requests 2026-08-03 11:17:54 +02:00
Slyghtning
a8e3e9c853
Merge pull request #1189 from hieblmi/tapd-client-fixes
assets: harden tapd client lifecycle and RFQ handling
2026-08-03 11:10:36 +02:00
Slyghtning
b686d97445
docs: note tapd client hardening
Document the RFQ validation, cache responsiveness, and tapd connection
lifecycle fixes in the rolling release notes.
2026-08-03 10:05:06 +02:00
Slyghtning
9e97992e18
assets: validate accepted RFQ asset rates
Validate the bid rate before returning an accepted asset sell quote.
This prevents malformed rates from reaching downstream quote arithmetic,
where nil or non-positive values can panic. Cover valid and malformed
responses with table-driven tests.
2026-08-03 10:05:05 +02:00
Slyghtning
cfbade1239
assets: reject malformed RFQ asset rates
Validate the rate pointer and decimal coefficient before converting
asset units. Return errors for nil, malformed, non-positive, and
oversized-scale rates instead of allowing nil dereferences or
division-by-zero panics. Add regression tests for each case.
2026-08-03 10:05:05 +02:00
Slyghtning
dc39d63d8d
assets: avoid locking cache during RPC
Restrict the asset-name cache mutex to map access so a slow
QueryAssetStats call cannot block cached readers. Use an RWMutex for
independent cache reads and add a concurrent regression test.
2026-08-03 10:05:05 +02:00
Slyghtning
c7d5e466cd
assets: validate RFQ timeout conversion
Convert the configured duration once during client creation. Round
positive fractional durations up to the whole seconds accepted by tapd.
Reject zero, negative, and overflowing values, and cover the conversion
boundaries with unit tests.
2026-08-03 10:05:05 +02:00
Slyghtning
514c3f06ad
loopd: close tapd client connections
Close the TapdClient when daemon initialization fails, during normal
shutdown, and after the view command completes. This prevents gRPC
transport resources from leaking across embedded daemon lifecycles and
error paths.
2026-08-03 10:05:04 +02:00
Slyghtning
8f831e85ae
Merge pull request #1188 from lightninglabs/gateway-v0.6.0
Some checks failed
CI / Commit Message (push) Has been cancelled
CI / RPC compilation check (push) Has been cancelled
CI / SQL compilation check (push) Has been cancelled
CI / go mod check (push) Has been cancelled
CI / build and lint code (push) Has been cancelled
CI / verify that auto-generated documentation is up-to-date (push) Has been cancelled
CI / run unit-test sqlite3 race (push) Has been cancelled
CI / run unit-test postgres race (push) Has been cancelled
CI / run LiT itests (push) Has been cancelled
CI / run LiT unit tests (push) Has been cancelled
github: pin gateway v0.6.0 (Opus 5, /gateway check)
2026-07-28 09:07:41 +02:00
saubyk
2dda482660
github: pin gateway v0.6.0 (Opus 5, /gateway check)
Bumps both pins together: the gateway-action SHA and the runtime_ref it
resolves. runtime_ref is pinned explicitly rather than left to the
action's default, so bumping only the action would leave the job on the
v0.5.0 runtime.

v0.6.0 adds no trigger and no input, so the rest of the shim is
unchanged.

Also corrects a copy-paste artifact in the adjacent comment, which said
runtime upgrades go through an lnd PR.
2026-07-27 20:46:33 -07:00
Slyghtning
dede47e727
Merge pull request #1187 from hieblmi/review-gateway
github: add gateway code-review workflow
2026-07-27 21:53:55 +02:00