staticaddr: dyn-conf-tracker#1141
Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request significantly improves the reliability and responsiveness of static-address deposits. By allowing immediate visibility of mempool deposits while enforcing strict confirmation requirements for high-risk operations like channel opens and withdrawals, the system balances user experience with security. The changes also include robust handling of deposit replacement, durable persistence of risk decisions for recovery, and comprehensive regression testing to ensure stable behavior across the deposit lifecycle. Highlights
New Features🧠 You can now enable Memory (public preview) to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces support for unconfirmed deposits in static address loop-ins, enabling faster swap initiation while managing confirmation risk. It includes client-side warnings for low-confirmation outputs, server-side persistence of risk decisions, and logic to handle deposits that vanish from the wallet view due to mempool replacement or reorgs. The notification manager was also enhanced to support asynchronous risk acceptance/rejection events. Feedback was provided regarding the duplication of coin selection logic between the CLI warning path and the loop-in manager, recommending a refactor into a shared utility for better maintainability.
7bee626 to
ec42d48
Compare
f8f474e to
d424169
Compare
d424169 to
d630c2e
Compare
49241e1 to
f6caba3
Compare
f6caba3 to
0669702
Compare
|
@hieblmi, remember to re-request review from reviewers when ready |
8304783 to
da5253e
Compare
|
@claude please review |
Code Review — PR #1141: staticaddr: dyn-conf-trackerThis PR is a substantial addition: it surfaces mempool deposits immediately (vs. waiting for MinConfs), adds RBF/replacement tracking, persist-and-replay for the server's confirmation-risk decision, notification manager queueing, and regression coverage across all the new flows. The overall design is sound and the test coverage is good. A few correctness issues and edge cases worth addressing before merge: Bug (High) — Payment deadline reset to full duration on legacy MinConfs fallbackFile: The new // Legacy path
startPaymentDeadline(
"legacy confirmation fallback",
time.Time{}, // <-- zero time → full timeout granted from NOW
)This means a swap initiated 55 seconds ago with a 60-second payment window grants the server a fresh 60-second deadline from the moment deposits confirm, rather than the remaining 5 seconds. The server could use this discrepancy to delay payment well beyond the negotiated window. The fix is to pass Bug (Medium) —
|
da5253e to
80a5b44
Compare
|
@claude please re-review |
Code Review — PR #1141: staticaddr: dyn-conf-trackerThis PR surfaces static-address deposits from the mempool (without waiting for 6 confirmations), adds RBF/replacement tracking, introduces confirmation-risk notifications from the server, hardens the loop-in lifecycle, and persists risk decisions across restarts. It's a substantial, well-structured change. The findings below are ranked most-severe first. Findings1. When the payment deadline fires, case <-htlcConfChan:
htlcConfirmed = true
if invoiceCanceledForNonPayment { // ← always false after deadlineChan fires
transitionDepositsToHtlcTimeout(...) // ← skipped
}The consequence: if the server publishes the HTLC on-chain after the deadline has already expired, deposits are left in Fix: add 2. Legacy MinConfs fallback grants the server the full payment timeout instead of the remaining time startPaymentDeadline(
"legacy confirmation fallback",
time.Time{}, // ← zero value
)Inside timeout := f.loopIn.PaymentTimeoutDuration()
if !startedAt.IsZero() {
timeout -= time.Since(startedAt) // ← never executed for zero value
...
}When deposits confirm 30 minutes after swap initiation with a 60-minute 3. context.AfterFunc(ctx, func() {
m.removeSubscriber(NotificationTypeStaticLoopInRiskAccepted, sub)
m.Lock()
delete(m.staticLoopInRiskAccepted, swapHash) // ← unconditional
m.Unlock()
close(notifChan)
})Problematic sequence:
Consider not deleting the cache entry in AfterFunc, or only deleting it after verifying the notification was actually delivered to the subscriber. 4. Old code added any confirmed UTXO that had no DB record to ignoreUnknownOutpoints := true // was false
deposits, err := s.depositManager.DepositsForOutpoints(...)
for _, d := range deposits {
if d == nil { continue } // unknown outpoints silently skipped
if d.IsInState(deposit.Deposited) {
isUnspent[d.OutPoint] = struct{}{}
}
}A deposit that arrived between two reconcile ticks (up to 5. The function manually reproduces the sort/filter logic from 6. After if !deadlineStarted && legacyMinConfsReached(...) {
startPaymentDeadline(...)
}7. When the decision has not yet been persisted, the function calls 8. for _, sub := range m.subscribers[notifType] {
if !hasSwapHash || sub.swapHash == nil ||
*sub.swapHash != swapHash {
continue
}
notifySubscriber(sub)
}Risk notifications are swap-scoped and there is at most one subscriber per in-flight swap. With N concurrent loop-ins, every incoming risk notification scans all N entries. The existing Overall the approach is sound and the test coverage (reconcile, replacement, notification delivery, risk-decision persistence) is thorough. The two highest-priority fixes are the missing |
|
On the two priority items:
|
fc506ac to
a56abf3
Compare
24375c8 to
46fbeb4
Compare
Document the lock-order invariant between Manager.mu and individual deposit locks. Later changes need both locks in the same path, so make the rule explicit before the locking surface grows.
Move active-deposit block notification fan-out into a helper. This keeps the event loop small and gives later startup replay logic a single path for notifying recovered deposit FSMs.
Guard reconcileDeposits with a dedicated mutex. Polling and block-driven reconciliation can overlap, so serialize the path before it updates confirmation data and active FSM state.
Reject nil deposits and final-state deposits before sending FSM events. This keeps callers from transitioning stale or completed deposits and uses the no-lock state helper while deposits are already locked.
Add a duration helper that falls back to the default payment timeout. Recovered legacy swaps can have a zero persisted timeout, so later deadline logic can use this without treating zero as immediate expiry.
A block notification can queue OnExpiry before a deposit reaches a final state. If the final transition wins that race first, the stale expiry event must not overwrite the terminal outcome. Keep LoopedIn and Withdrawn as self-loops on OnExpiry, matching the other final states. Add a focused FSM test that sends OnExpiry directly to each final state and verifies the state is preserved.
Document deposit lock ownership for mutable confirmation state and route production reads through deposit accessors. Keep store persistence on no-lock helpers while callers hold the deposit lock, preserving the existing transition behavior without leaving direct field reads in user-facing paths.
A shutdown while publishing or monitoring the HTLC timeout sweep should not transition the loop-in to Failed. Return NoOp on context cancellation in those actions so the persisted state remains a recovery point. Add focused tests for shutdown during publication retry and confirmation monitoring.
After the client gives the server HTLC signatures, shutdown must not drive the monitor state through the generic error path. That path cancels the invoice and attempts to unlock deposits even though the server can still publish the HTLC. Return NoOp for monitor-state cancellation races and cover shutdown with a regression test that asserts no invoice cancellation or deposit unlock occurs.
86d1de1 to
7849084
Compare
Allow golangci-lint more time for the larger static-address test suite. The dynamic-confirmation stack adds several focused tests, and the default timeout is tight on slower CI workers.
Retain static-address deposits as soon as lnd reports the UTXO, even when the output is still unconfirmed. Store the first confirmation height once the output confirms. Replay the startup block to recovered deposit FSMs so expiry handling can run immediately after restart. Derive confirmation heights from a stable wallet view because lnd reports confirmation counts.
Build list and summary responses from tracked deposit records instead of raw wallet UTXOs so RPC clients see the manager availability state. Split unconfirmed value from confirmed deposited value in summaries. Keep withdrawal and channel-open flows on confirmed inputs by rejecting unconfirmed selected deposits in those paths.
Treat unconfirmed static-address deposits as swappable because their CSV timeout has not started yet. Keep confirmed deposits ahead of unconfirmed ones during automatic selection, then sort by value and remaining lifetime within each confirmation group. Share the expiry calculation with the dynamic-programming selector so unconfirmed deposits do not look like the earliest-expiring candidates.
Treat lnd wallet view as the source of spendable static-address outputs while keeping historical deposit records in the DB. Reconcile active FSMs against the current wallet view and reactivate known deposits when their outpoints are visible again. Refresh deposits before selection, withdrawal, loop-in, and channel-open paths, and filter list and summary responses through the live active set so stale Deposited records are not exposed as available funds.
Check the originally selected deposit outpoints before signing a static loop-in HTLC transaction. If any selected outpoint is no longer available, cancel the swap invoice and fail the signing action instead of producing signatures for stale inputs. Wire the lnd-backed checker through loopd and make invoice-monitoring handle closed subscription channels without spinning.
Subscribe to static loop-in confirmation-risk notifications before starting the payment deadline. Start that deadline only after server acceptance or the legacy confirmation fallback, and cancel the swap invoice when the server rejects the risk wait. Refresh selected deposits before the legacy fallback so recovered monitors use current confirmation heights.
7849084 to
9c23aab
Compare
Store server confirmation-risk decisions with static loop-in swaps and recover accepted payment-deadline timers after restart. Wire notification persistence through loopd so recovered swaps do not lose pending risk state. Deduplicate notification fanout cache entries by swap hash.
Warn before dispatching a static loop-in that selects deposits below the conservative six-confirmation threshold. Mirror automatic coin selection before prompting so the warning reflects both manual and auto-selected deposits. Cover manual and auto-selected warning paths in CLI tests.
Refresh static loop-in replay sessions for the low-confirmation warning and payment-timeout prompts. Add replay coverage for the warning prompt and update fee and payment-timeout variants for the new interaction sequence.
9c23aab to
5193b2b
Compare
This PR surfaces static-address deposits as soon as they appear in the wallet, including mempool deposits, instead of waiting for the old confirmation readiness
threshold.
It also updates the static-address flows around those low-confirmation deposits: