@@ -9,17 +9,16 @@ import (
9
9
10
10
"github.com/btcsuite/btcutil"
11
11
12
- "github.com/lightningnetwork/lnd/chainntnfs"
13
- "github.com/lightningnetwork/lnd/channeldb"
14
- "github.com/lightningnetwork/lnd/lnwire"
15
-
12
+ "github.com/btcsuite/btcd/chaincfg/chainhash"
16
13
"github.com/btcsuite/btcd/wire"
17
- "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
18
-
19
14
"github.com/lightninglabs/lndclient"
20
15
"github.com/lightninglabs/loop/loopdb"
21
16
"github.com/lightninglabs/loop/swap"
17
+ "github.com/lightningnetwork/lnd/chainntnfs"
18
+ "github.com/lightningnetwork/lnd/channeldb"
19
+ "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
22
20
"github.com/lightningnetwork/lnd/lntypes"
21
+ "github.com/lightningnetwork/lnd/lnwire"
23
22
)
24
23
25
24
var (
@@ -58,6 +57,9 @@ type loopInSwap struct {
58
57
59
58
htlcNP2WSH * swap.Htlc
60
59
60
+ // htlcTxHash is the confirmed htlc tx id.
61
+ htlcTxHash * chainhash.Hash
62
+
61
63
timeoutAddr btcutil.Address
62
64
}
63
65
@@ -209,6 +211,7 @@ func resumeLoopInSwap(reqContext context.Context, cfg *swapConfig,
209
211
} else {
210
212
swap .state = lastUpdate .State
211
213
swap .lastUpdateTime = lastUpdate .Time
214
+ swap .htlcTxHash = lastUpdate .HtlcTxHash
212
215
}
213
216
214
217
return swap , nil
@@ -394,20 +397,35 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
394
397
func (s * loopInSwap ) waitForHtlcConf (globalCtx context.Context ) (
395
398
* chainntnfs.TxConfirmation , error ) {
396
399
400
+ // Register for confirmation of the htlc. It is essential to specify not
401
+ // just the pk script, because an attacker may publish the same htlc
402
+ // with a lower value and we don't want to follow through with that tx.
403
+ // In the unlikely event that our call to SendOutputs crashes and we
404
+ // restart, htlcTxHash will be nil at this point. Then only register
405
+ // with PkScript and accept the risk that the call triggers on a
406
+ // different htlc outpoint.
407
+ s .log .Infof ("Register for htlc conf (hh=%v, txid=%v)" ,
408
+ s .InitiationHeight , s .htlcTxHash )
409
+
410
+ if s .htlcTxHash == nil {
411
+ s .log .Warnf ("No htlc tx hash available, registering with " +
412
+ "just the pkscript" )
413
+ }
414
+
397
415
ctx , cancel := context .WithCancel (globalCtx )
398
416
defer cancel ()
399
417
400
418
notifier := s .lnd .ChainNotifier
401
419
402
420
confChanP2WSH , confErrP2WSH , err := notifier .RegisterConfirmationsNtfn (
403
- ctx , nil , s .htlcP2WSH .PkScript , 1 , s .InitiationHeight ,
421
+ ctx , s . htlcTxHash , s .htlcP2WSH .PkScript , 1 , s .InitiationHeight ,
404
422
)
405
423
if err != nil {
406
424
return nil , err
407
425
}
408
426
409
427
confChanNP2WSH , confErrNP2WSH , err := notifier .RegisterConfirmationsNtfn (
410
- ctx , nil , s .htlcNP2WSH .PkScript , 1 , s .InitiationHeight ,
428
+ ctx , s . htlcTxHash , s .htlcNP2WSH .PkScript , 1 , s .InitiationHeight ,
411
429
)
412
430
if err != nil {
413
431
return nil , err
@@ -445,6 +463,17 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
445
463
}
446
464
}
447
465
466
+ // Store htlc tx hash for accounting purposes. Usually this call is a
467
+ // no-op because the htlc tx hash was already known. Exceptions are:
468
+ //
469
+ // - Old pending swaps that were initiated before we persisted the htlc
470
+ // tx hash directly after publish.
471
+ //
472
+ // - Swaps that experienced a crash during their call to SendOutputs. In
473
+ // that case, we weren't able to record the tx hash.
474
+ txHash := conf .Tx .TxHash ()
475
+ s .htlcTxHash = & txHash
476
+
448
477
return conf , nil
449
478
}
450
479
@@ -491,7 +520,20 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
491
520
if err != nil {
492
521
return false , fmt .Errorf ("send outputs: %v" , err )
493
522
}
494
- s .log .Infof ("Published on chain HTLC tx %v" , tx .TxHash ())
523
+ txHash := tx .TxHash ()
524
+ s .log .Infof ("Published on chain HTLC tx %v" , txHash )
525
+
526
+ // Persist the htlc hash so that after a restart we are still waiting
527
+ // for our own htlc. We don't need to announce to clients, because the
528
+ // state remains unchanged.
529
+ //
530
+ // TODO(joostjager): Store tx hash before calling SendOutputs. This is
531
+ // not yet possible with the current lnd api.
532
+ s .htlcTxHash = & txHash
533
+ s .lastUpdateTime = time .Now ()
534
+ if err := s .persistState (); err != nil {
535
+ return false , fmt .Errorf ("persist htlc tx: %v" , err )
536
+ }
495
537
496
538
return true , nil
497
539
@@ -507,7 +549,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
507
549
rpcCtx , cancel := context .WithCancel (ctx )
508
550
defer cancel ()
509
551
spendChan , spendErr , err := s .lnd .ChainNotifier .RegisterSpendNtfn (
510
- rpcCtx , nil , s .htlc .PkScript , s .InitiationHeight ,
552
+ rpcCtx , htlcOutpoint , s .htlc .PkScript , s .InitiationHeight ,
511
553
)
512
554
if err != nil {
513
555
return fmt .Errorf ("register spend ntfn: %v" , err )
@@ -714,8 +756,9 @@ func (s *loopInSwap) persistState() error {
714
756
return s .store .UpdateLoopIn (
715
757
s .hash , s .lastUpdateTime ,
716
758
loopdb.SwapStateData {
717
- State : s .state ,
718
- Cost : s .cost ,
759
+ State : s .state ,
760
+ Cost : s .cost ,
761
+ HtlcTxHash : s .htlcTxHash ,
719
762
},
720
763
)
721
764
}
0 commit comments