Commit graph

152 commits

Author SHA1 Message Date
Slyghtning
325545143c
staticaddr/loopin: persist risk decisions
Store the server's static loop-in confirmation-risk decision and the time it
was received. This lets recovered swaps reconstruct whether payment waiting had
already started and how much of the payment timeout remains.

Wire notification handling to persist accepted and rejected decisions before
caching and forwarding them. If the swap row is not present yet, the
notification is still cached so the per-swap waiter can replay and store the
decision later.

Recover accepted decisions by starting the payment deadline from the persisted
decision time, and recover rejected decisions by canceling the invoice and
failing the swap instead of waiting forever.
2026-06-01 10:46:52 +02:00
Slyghtning
7b29d9215e
staticaddr/loopin: list failed swaps by state
GetStaticAddressLoopInSwapsByStates passes a comma-separated
state list into a SQL LIKE membership check. The query wraps the
input with commas before matching latest update states as
comma-delimited tokens.

Wrapping that list in braces meant the first and last states
were not bounded by commas, so boundary entries in a state set
could be missed. In particular, Failed is the last final
static-address loop-in state, which made final-state queries skip
failed swaps.

Drop the braces from the serialized state list and extend the
SQL store test with a failed swap so the final-state boundary is
covered.
2026-06-01 10:46:52 +02:00
Slyghtning
8417c57be3
staticaddr/loopin: handle risk rejection notification
Add client handling for the server's static loop-in risk-rejected notification.
If the server aborts confirmation-risk waiting before payment, the client fails
the local swap instead of waiting for a payment deadline that will never start.

Cache rejected notifications by swap hash using the same replay path as accepted
notifications, and clear the opposite cached state when a final risk decision is
received. This keeps reconnect and subscription-order races from stranding the
client in the risk wait.
2026-06-01 10:46:52 +02:00
Slyghtning
9818ad8985
staticaddr/loopin: wait for risk acceptance notification
Wait for the server's static loop-in risk-accepted notification before starting
the client payment deadline. The server may intentionally hold the swap at the
confirmation-risk gate after HTLC signing, and the client deadline should not
run while that server-side wait is still in progress.

Cache risk-accepted notifications by swap hash inside the local notification
manager and replay them to the per-swap subscriber. This covers both reconnects
and the internal race where the global notification stream receives the server
event before the static loop-in FSM registers its waiter.
2026-06-01 10:46:52 +02:00
Slyghtning
858066dd0c
staticaddr: cancel loop-ins when deposit inputs vanish
Keep replacement UTXOs as fresh deposits while preserving the original deposit
record and selected outpoint snapshot for pending swaps.

Before signing a static loop-in HTLC, check each original selected outpoint with
GetTxOut(..., includeMempool=true). Cancel the pending invoice only when that
check reports an original outpoint unavailable; lookup errors fail the action
without canceling so transient chain backend errors do not incorrectly abandon
the swap.

Keep recovered loop-ins using their stored outpoint snapshot and cover
replacement discovery and cancellation in tests.
2026-06-01 10:46:51 +02:00
Slyghtning
6d70f0fe47
staticaddr/deposit: async finalization cleanup
FinalizeDepositAction only needs to tell the manager to remove the FSM from its
active set, but the old synchronous send was still tied to the caller context
and could race with request cancellation or a busy manager loop.

Send the cleanup notification asynchronously and tie it to the FSM lifetime
instead. Withdrawal completion no longer blocks while deposit locks are held
just because the original request context was canceled.
2026-06-01 10:46:51 +02:00
Slyghtning
5a7a953810
staticaddr/loopin: cancel orphan invoice when init fails early
If InitHtlcAction creates the private swap invoice but fails before the loop-in
is stored, the retry path otherwise leaves behind a live orphan invoice.

Cancel that invoice on the early error path with a detached, timeout-limited
context, and reuse the same helper when tearing down the monitor path. This
keeps failed initialization attempts from leaving invoices that no local swap
can complete.
2026-06-01 10:46:51 +02:00
Slyghtning
30a14ecc82
staticaddr: apply confirmation policy by flow
Allow static loop-ins to select unconfirmed deposits because their CSV timeout
has not started yet, while still preferring confirmed outputs during automatic
selection.

Keep confirmed-input requirements for channel opens and withdrawals now that
Deposited includes mempool outputs. Filter unconfirmed deposits out of automatic
selection for those flows and fail manual requests that reference them, so the
client does not build PSBTs or withdrawal attempts with unusable inputs.

Treat deposit.MinConfs as the legacy readiness threshold rather than the single
source of truth for all flows. Loop-in readiness is now governed by server
confirmation-risk policy, while withdrawals and channel opens keep their
confirmed-input checks.
2026-06-01 10:46:50 +02:00
Slyghtning
3b29ef3629
staticaddr/deposit: replay startup block to recovered deposits
The deposit manager consumes one block epoch before recovered deposit FSMs are
started. That left already-expired recovered deposits idle until another block
arrived.

Remember the startup height and deliver it to active deposit FSMs after
recovery and reconciliation have finished. Move the block notification fanout
into a helper so startup replay and normal block handling use the same path.

Add coverage that starts the manager with a recovered deposit at its expiry
height and verifies the expiry sweep is signed and published immediately.
2026-06-01 10:45:58 +02:00
Slyghtning
3df71f9a19
staticaddr: track unconfirmed deposits
Surface static-address deposits as soon as they appear in the wallet instead
of waiting for the old six-confirmation readiness threshold.

Reconcile the wallet view on startup, on each block, and on the polling ticker
so mempool deposits are created immediately. Backfill the first confirmation
height once those outputs confirm, protect unconfirmed deposits from expiry,
and mark vanished unconfirmed outpoints as Replaced so RBFed-away deposits stop
showing up in RPCs.

Expose the new state through static-address RPCs by deriving availability and
summary totals from stored deposit state, reporting sensible expiry data for
unconfirmed outputs, and hiding Replaced records from normal listings.
2026-06-01 10:45:58 +02:00
Slyghtning
db9bd06629
staticaddr: reject malformed MuSig2 signing data
Server-supplied nonces and partial signatures are consumed by the static address loop-in and withdrawal MuSig2 signing paths. Reject nil signing info, wrong nonce lengths, and wrong partial signature lengths before registering nonces or combining signatures, so malformed responses cannot be silently zero-padded into signing attempts.

Add withdrawal coverage for nil and malformed server signing data.
2026-05-29 11:53:32 +02:00
Slyghtning
1fa919711f
staticaddr: fix timeout comment typo 2026-05-27 11:49:49 +02:00
Boris Nagaev
8d6df2bf20
staticaddr: use dp autoloop selector
Replace the recursive full-deposit autoloop selector with a bounded-memory
DP implementation in staticaddr/loopin/autoloop_dp.go. The new selector
keeps the existing no-change semantics, first finds the best reachable
total, then applies the 25 percent band rule so earlier-expiring deposits
can win inside that near-optimal range.

The DP table is capped at 128 MiB and keeps exact satoshi sums alongside
compressed bucket weights, so planning stays memory-bounded without
allowing oversized candidates. The compressed weighting now rounds down
with a minimum of one bucket, which avoids rejecting valid sums after
multiple per-deposit rounding steps while leaving the exact-sum check
as the real safety boundary.
2026-05-22 02:20:36 -05:00
Boris Nagaev
7cf0a87c2b
liquidity: count static loop-ins
Teach the liquidity manager to include persisted static loop-ins
in budget accounting, in-flight limits, and peer traffic backoff.
This adds the static fee model used for conservative accounting
and passes storage errors through the relevant planner helpers.

The daemon wiring now exposes static loop-ins to liquidity so the
manager can see the same ongoing swaps that the static-address
subsystem persists, while easy autoloop keeps working with the new
fallible traffic lookup path.
2026-05-22 02:20:36 -05:00
Boris Nagaev
94fc04a71a
staticaddr: add autoloop loop-in prep
Add the static-address helper that prepares full-deposit autoloop loop-ins
without dispatching them. The helper selects no-change deposit sets, records
explicit outpoints, and quotes the exact selected amount before the planner
tries to dispatch anything.

The tests cover the full-deposit selector, the quoted request construction,
and excluded outpoint handling so later liquidity work can rely on a stable
preparation surface.
2026-05-22 02:20:36 -05:00
Boris Nagaev
48a7bdc7d0
staticaddr/loopin: makeDeposit gets confheight arg
Test-only change. This is needed to reuse it in another test.
2026-05-22 02:20:36 -05:00
Boris Nagaev
562416e99b
loopd: validate static loop-in labels at rpc
Move static loop-in label validation to the rpc boundary and
remove the same check from the internal manager path.

This keeps external requests aligned with the existing swap rpc
surface while allowing internal autoloop callers to keep using
reserved labels for automated swaps. The tests cover both sides of
that contract: rpc requests still reject reserved labels, and the
manager path accepts them.
2026-05-22 02:20:36 -05:00
Slyghtning
72a38c535b
Merge pull request #1133 from 0xfandom/refactor/1056-move-parameters-to-script
staticaddr: move address.Parameters to script package
2026-05-20 22:02:57 +02:00
Slyghtning
6aa6979e77
staticaddr: validate server address parameters 2026-05-11 08:59:03 +02:00
0xfandom
8b89f77529 staticaddr: move address.Parameters to script package
The Parameters struct describes the keys, expiry and pkScript that
define the static address script, so its natural home is the script
package. Moving it there lets staticutil drop its dependency on the
address package and lets callers reuse a single type alongside
script.StaticAddress and script.NewStaticAddress.

No behavior change.

Closes #1056
2026-05-01 15:54:37 +05:30
Boris Nagaev
51bb2b6ac5
staticaddr/loopin: add route hint regression test 2026-04-17 21:38:36 -05:00
Boris Nagaev
aa699b16e6
staticaddr: deflake deposit manager test
The sqlite and postgres race jobs were both hanging in
deposit.TestManager. The test was observing the manager through
implementation details that were not safe to share with the manager
itself:

- it replaced the manager's internal finalizedDepositChan and then
  waited on the same channel the manager consumes
- it reused package-level block and confirmation channels across runs
- it treated confirmationHeight+expiry as the last pre-expiry block
  even though the production IsExpired check uses >=
- it relied on scheduler timing when asserting that no sign request
  had happened yet

Make the test assert on stable effects instead of internal channel
ownership:

- create per-test notifier channels in the test context
- run the manager from a cancellable t.Context-derived context and
  assert clean shutdown
- send the actual last pre-expiry height, then the expiry height
- wait for the expiry sign and publish steps with bounded timeouts
- verify finalization by waiting for the manager to remove the
  deposit from activeDeposits instead of racing its private
  finalization channel

This keeps the test aligned with the production expiry semantics
and removes the race that only showed up reliably under -race.
2026-04-14 04:10:33 -05:00
Slyghtning
650640c854
Revert "staticaddr: support dynamic deposit confirmation requirements"
This reverts commit 66a17f47e6.
2026-03-13 11:18:42 +01:00
Slyghtning
798d86a433
Merge pull request #1091 from hieblmi/fix-clock
Inject clocks into swap lifecycle and update swap tests
2026-03-13 11:15:21 +01:00
Slyghtning
541974f715
Merge pull request #1085 from starius/pgx-err-no-rows
staticaddr: remove redundant pgx.ErrNoRows checks
2026-03-12 09:26:45 +01:00
Slyghtning
66a17f47e6
staticaddr: support dynamic deposit confirmation requirements
Reduce MinConfs from 6 to 3 to allow faster swap attempts while the
server enforces risk-based confirmation requirements. Update
SelectDeposits to prioritize more-confirmed deposits first, increasing
the likelihood of server acceptance. Add client-side logging of
insufficient confirmation details from server error responses.
2026-03-11 20:58:35 +01:00
Slyghtning
a10c741a26
lint: fix linter issues 2026-03-09 14:39:45 +01:00
Slyghtning
3e526faf82
openchannel: cancel shims with a live context
In manager.go, deferred shim cleanup was calling
FundingStateStep with the original request ctx.
If the user had already canceled that context,
the cleanup RPC would run with a canceled context
and could fail to remove the pending shim. I changed
that cleanup path to use context.WithoutCancel(ctx)
so the cancellation RPC still has a live context.
2026-03-09 14:39:45 +01:00
Slyghtning
0b295123e7
staticaddr: remove dead code across managers
Remove unused errChan fields from the loopin, openchannel, and withdraw
managers. These channels were declared and initialized but never read
from or written to.

Remove the unused activeLoopIns map from the loopin manager. The map
was only written to but never read, making it dead code.

Remove the stale withdraw.Store interface whose method signatures no
longer match the concrete SqlStore API used by the manager.

Remove unused config fields from openchannel.Config (Server,
AddressManager, ChainNotifier, Signer) and deposit.ManagerConfig
(AddressClient, SwapClient, ChainParams) along with their daemon
wiring. Also remove the now-orphaned openchannel.AddressManager
interface.

Remove the unused GetStaticAddress and Close methods from
address.SqlStore and the GetStaticAddress method from the address.Store
interface, as the codebase only uses GetAllStaticAddresses.
2026-03-09 14:39:44 +01:00
Slyghtning
f80aee4dae
staticaddr: avoid startup SendEvent data races 2026-03-09 14:39:44 +01:00
Slyghtning
a9977dd5f4
godoc: address copy-paste errors accross the codebase 2026-03-09 14:39:44 +01:00
Slyghtning
c1036ff705
withdraw: handle confirmation registration failures 2026-03-09 14:39:44 +01:00
Slyghtning
d39e247a74
withdraw: use %v instead of %w in log.Errorf
The %w formatting verb is only meaningful for fmt.Errorf where it
enables error wrapping. In log.Errorf it prints as %%!w(error=...),
producing garbled log output. Use %v and add the missing colon
separator.
2026-03-09 08:37:50 +01:00
Slyghtning
33891ae6ae
withdraw: check error from RegisterSpendNtfn before using channels
handleWithdrawal did not check the error returned by
RegisterSpendNtfn before spawning a goroutine to read from the
notification channels. If registration failed, spentChan would be nil
and the goroutine would block forever on a nil channel read.

Add the missing error check so the function returns early on
registration failure.
2026-03-09 08:37:50 +01:00
Slyghtning
4a6ccd3d29
loopin: fix data race on activeLoopIns map during recovery
recoverLoopIns wrote to the activeLoopIns map from inside a goroutine
without any synchronization. The map is shared state accessed from
the manager's main Run loop, creating a data race.

Move the map write before the goroutine spawn so it happens
synchronously during recovery. Also fix the stale log message that
said "OnStart" instead of "OnRecover".
2026-03-09 08:37:49 +01:00
Slyghtning
aadf6e2daa
loopin: exit htlc timeout sweep retry loop on context cancellation
SweepHtlcTimeoutAction used a select with a default case containing a
blocking time.After. When the context was canceled, it logged the error
but continued the retry loop instead of returning. The default case
also meant that ctx.Done was only checked when it was already signaled,
while an hour-long sleep blocked without listening for cancellation.

Replace the default+time.After pattern with a proper select on both
ctx.Done and time.After so the function exits promptly on shutdown.
2026-03-09 08:37:49 +01:00
Slyghtning
63d8f5560e
loopin: enforce HTLC output index invariant in sweep tx construction
createHtlcTx always places the HTLC output at index 0 and the change
output (if any) at index 1. Previously, createHtlcSweepTx attempted to
dynamically find the HTLC index but then unconditionally read
TxOut[0].Value, ignoring the computed index.

Replace the dynamic search with a const htlcInputIndex=0 and a fail-fast
check that errors if the layout invariant is ever violated. Add a test
that verifies the sweep value is derived from the HTLC output, not the
change output.
2026-03-09 08:37:48 +01:00
Boris Nagaev
25056c6547
staticaddr: remove redundant pgx.ErrNoRows checks
pgx/v5 defines ErrNoRows as a wrapped sql.ErrNoRows, so this code covers both
of them: errors.Is(err, sql.ErrNoRows).
2026-03-04 18:33:40 -05:00
Slyghtning
6cb54edf06
openchannel: optimize open channel request creation and err handling 2026-02-26 20:57:28 +01:00
Slyghtning
cd377f35f8
openchannel: recover deposits at runtime after PSBT finalize failure
If the PSBT finalize step succeeds but the stream fails before
ChanPending, deposits would remain stuck in OpeningChannel until the
next daemon restart. Run the recovery logic immediately so deposits are
resolved without requiring a restart.

Also, add tests for the following edge cases requested in review:

- Reorg: channel tx reorged, UTXOs reappear as unspent, deposits
  return to Deposited state.
- Daemon restart during channel opening: deposits in OpeningChannel
  recovered based on UTXO status (spent → ChannelPublished, unspent →
  Deposited).
- Mempool eviction: tx evicted, UTXOs unspent, deposits return to
  Deposited.
- Mempool rejection: tx never accepted, same recovery as eviction.
- Stream errors: lnd stream fails before PSBT finalize, error returned
  without errPsbtFinalized so deposits can be safely rolled back.
- PSBT finalize then stream abort: finalize succeeds but stream dies
  before ChanPending, error wrapped with errPsbtFinalized so caller
  triggers recovery instead of blind rollback.
- Duplicate outpoints: already covered by TestOpenChannelDuplicateOutpoints.
2026-02-26 20:57:27 +01:00
Slyghtning
e710ea5e8f
openchannel: reject duplicate outpoints in open channel request
Duplicate outpoints in the request lead to fee miscalculation and an
invalid PSBT with the same input listed twice. Validate early and return
a clear error message.
2026-02-26 20:57:27 +01:00
Slyghtning
dbac12ed78
deposit: add OnExpiry self-transitions to OpeningChannel and ChannelPublished
Both OpeningChannel and ChannelPublished lacked OnExpiry transitions.
handleBlockNotification fires OnExpiry on every new block once the
deposit is expired, regardless of the current state. Since both states
use NoOpAction or FinalizeDepositAction which release the FSM mutex
briefly, an OnExpiry SendEvent can sneak in. Add self-transitions so
the event is safely absorbed.
2026-02-26 20:57:27 +01:00
Slyghtning
52d884cec1
openchannel: return error on hash creation failure
Return an error instead of just logging when chainhash.NewHash fails in
the ChanPending handler. The hash variable would be nil and crash on the
subsequent String() call.
2026-02-26 20:57:27 +01:00
Slyghtning
eaf7883bcf
openchannel: fix shimPending data race
Protect the shimPending variable with a sync.Mutex since it is accessed
from multiple goroutines: the main loop goroutine and the server error
handling goroutine. Without synchronization this is a data race.
2026-02-26 20:57:27 +01:00
Slyghtning
b74dab8af4
loopd: instantiate static address open channel manager 2026-02-26 20:57:27 +01:00
Slyghtning
63097ee220
staticaddr: close quit channel only once 2026-02-26 20:57:26 +01:00
Slyghtning
dfc75f2e1a
staticaddr: open channel manager 2026-02-26 20:57:26 +01:00
Slyghtning
d12663ea7c
staticaddr: channel open states for deposit and fsm 2026-02-26 20:57:26 +01:00
Slyghtning
81012de948
staticaddr: replace block-based deposit detection with polling
Block-based deposit fetching from the internal lnd wallet was
susceptible to wallet syncing issues. Replace it with interval-based
polling. Reconciliation errors are now logged instead of being fatal,
improving resilience during transient failures.
2026-02-26 20:57:26 +01:00
Slyghtning
f1c138abf5
deposit: remove dead code in reconcileDeposits
Remove unreachable error check after filterNewDeposits which does not
return an error. The err variable was already handled from the
ListUnspent call above and could never be non-nil at this point.
2026-02-26 20:57:26 +01:00