Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/release-notes/release-notes-next.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@
`loopd` failed with `exec format error` on ARM hosts.
[Issue #1211](https://github.com/lightninglabs/loop/issues/1211)

* Static Address Loop In timeout recovery now spends the exact HTLC output
confirmed on chain, including server-published backup fee variants.

* Static Address withdrawals now follow the transaction that actually replaces
an original withdrawal, reconcile partial conflicting spends, durably record
confirmed transactions before finalizing deposits, and wait for their
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE static_address_swaps
DROP COLUMN confirmed_htlc_output_value;

ALTER TABLE static_address_swaps
DROP COLUMN confirmed_htlc_output_index;

ALTER TABLE static_address_swaps
DROP COLUMN confirmed_htlc_tx_id;
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE static_address_swaps
ADD confirmed_htlc_tx_id TEXT;

ALTER TABLE static_address_swaps
ADD confirmed_htlc_output_index INTEGER;

ALTER TABLE static_address_swaps
ADD confirmed_htlc_output_value BIGINT;
3 changes: 3 additions & 0 deletions loopdb/sqlc/models.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion loopdb/sqlc/queries/static_address_loopin.sql
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,10 @@ INSERT INTO static_address_swaps (
UPDATE static_address_swaps
SET
htlc_tx_fee_rate_sat_kw = $2,
htlc_timeout_sweep_tx_id = $3
htlc_timeout_sweep_tx_id = $3,
confirmed_htlc_tx_id = $4,
confirmed_htlc_output_index = $5,
confirmed_htlc_output_value = $6
WHERE
swap_hash = $1;

Expand Down
39 changes: 32 additions & 7 deletions loopdb/sqlc/static_address_loopin.sql.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

70 changes: 69 additions & 1 deletion staticaddr/loopin/actions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package loopin

import (
"bytes"
"context"
"crypto/rand"
"errors"
Expand Down Expand Up @@ -1151,9 +1152,14 @@ func (f *FSM) MonitorInvoiceAndHtlcTxAction(ctx context.Context,
htlcConfirmed := false
for {
select {
case <-htlcConfChan:
case conf := <-htlcConfChan:
f.Infof("htlc tx confirmed")

err = f.recordConfirmedHtlc(ctx, conf, htlc.PkScript)
if err != nil {
return f.HandleError(err)
}

htlcConfirmed = true
if invoiceCanceledForNonPayment {
err = transitionDepositsToHtlcTimeout(
Expand Down Expand Up @@ -1194,6 +1200,10 @@ func (f *FSM) MonitorInvoiceAndHtlcTxAction(ctx context.Context,
// confirmation and re-register for the next
// confirmation.
htlcConfirmed = false
err = f.clearConfirmedHtlc(ctx)
if err != nil {
return f.HandleError(err)
}

htlcConfChan, htlcErrConfChan, err = registerHtlcConf()
if err != nil {
Expand Down Expand Up @@ -1371,6 +1381,64 @@ func (f *FSM) MonitorInvoiceAndHtlcTxAction(ctx context.Context,
}
}

func (f *FSM) recordConfirmedHtlc(ctx context.Context,
conf *chainntnfs.TxConfirmation, htlcPkScript []byte) error {

if conf == nil || conf.Tx == nil {
return errors.New("htlc confirmation missing transaction")
}
if f.cfg.Store == nil {
return errors.New("missing static address loop-in store")
}

tx := conf.Tx
txHash := tx.TxHash()
expectedInputs := f.loopIn.Outpoints()
if len(tx.TxIn) != len(expectedInputs) {
return fmt.Errorf("confirmed htlc tx %v has %d inputs, expected "+
"%d", txHash, len(tx.TxIn), len(expectedInputs))
}
for idx, expectedInput := range expectedInputs {
if tx.TxIn[idx].PreviousOutPoint != expectedInput {
return fmt.Errorf("confirmed htlc tx %v input %d spends %v, "+
"expected %v", txHash, idx,
tx.TxIn[idx].PreviousOutPoint, expectedInput)
}
}

for idx, txOut := range tx.TxOut {
if !bytes.Equal(txOut.PkScript, htlcPkScript) {
continue
}

f.loopIn.HtlcTxHash = &txHash
f.loopIn.HtlcOutputIndex = uint32(idx)
f.loopIn.HtlcOutputValue = btcutil.Amount(txOut.Value)

return f.cfg.Store.UpdateLoopIn(ctx, f.loopIn)
}

return fmt.Errorf("confirmed htlc tx %v missing expected htlc "+
"output", txHash)
}

func (f *FSM) clearConfirmedHtlc(ctx context.Context) error {
if f.loopIn.HtlcTxHash == nil && f.loopIn.HtlcOutputIndex == 0 &&
f.loopIn.HtlcOutputValue == 0 {

return nil
}
if f.cfg.Store == nil {
return errors.New("missing static address loop-in store")
}

f.loopIn.HtlcTxHash = nil
f.loopIn.HtlcOutputIndex = 0
f.loopIn.HtlcOutputValue = 0

return f.cfg.Store.UpdateLoopIn(ctx, f.loopIn)
}

// htlcTimeoutSweepRetryDelay is the delay between retries when publishing the
// htlc timeout sweep transaction fails.
const htlcTimeoutSweepRetryDelay = time.Hour
Expand Down
Loading
Loading