@@ -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
@@ -333,7 +336,7 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
333
336
// HtlcPublished state directly and wait for
334
337
// confirmation.
335
338
s .setState (loopdb .StateHtlcPublished )
336
- err = s .persistState (globalCtx )
339
+ err = s .persistAndAnnounceState (globalCtx )
337
340
if err != nil {
338
341
return err
339
342
}
@@ -363,6 +366,13 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
363
366
return err
364
367
}
365
368
369
+ // Verify that the confirmed (external) htlc value matches the swap
370
+ // amount. Otherwise fail the swap immediately.
371
+ if htlcValue != s .LoopInContract .AmountRequested {
372
+ s .setState (loopdb .StateFailIncorrectHtlcAmt )
373
+ return s .persistAndAnnounceState (globalCtx )
374
+ }
375
+
366
376
// TODO: Add miner fee of htlc tx to swap cost balance.
367
377
368
378
// The server is expected to see the htlc on-chain and knowing that it
@@ -376,7 +386,7 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
376
386
}
377
387
378
388
// Persist swap outcome.
379
- if err := s .persistState (globalCtx ); err != nil {
389
+ if err := s .persistAndAnnounceState (globalCtx ); err != nil {
380
390
return err
381
391
}
382
392
@@ -387,39 +397,53 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
387
397
func (s * loopInSwap ) waitForHtlcConf (globalCtx context.Context ) (
388
398
* chainntnfs.TxConfirmation , error ) {
389
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
+
390
415
ctx , cancel := context .WithCancel (globalCtx )
391
416
defer cancel ()
392
417
393
418
notifier := s .lnd .ChainNotifier
394
419
395
420
confChanP2WSH , confErrP2WSH , err := notifier .RegisterConfirmationsNtfn (
396
- ctx , nil , s .htlcP2WSH .PkScript , 1 , s .InitiationHeight ,
421
+ ctx , s . htlcTxHash , s .htlcP2WSH .PkScript , 1 , s .InitiationHeight ,
397
422
)
398
423
if err != nil {
399
424
return nil , err
400
425
}
401
426
402
427
confChanNP2WSH , confErrNP2WSH , err := notifier .RegisterConfirmationsNtfn (
403
- ctx , nil , s .htlcNP2WSH .PkScript , 1 , s .InitiationHeight ,
428
+ ctx , s . htlcTxHash , s .htlcNP2WSH .PkScript , 1 , s .InitiationHeight ,
404
429
)
405
430
if err != nil {
406
431
return nil , err
407
432
}
408
433
409
- for {
434
+ var conf * chainntnfs.TxConfirmation
435
+ for conf == nil {
410
436
select {
411
437
412
438
// P2WSH htlc confirmed.
413
- case conf : = <- confChanP2WSH :
439
+ case conf = <- confChanP2WSH :
414
440
s .htlc = s .htlcP2WSH
415
441
s .log .Infof ("P2WSH htlc confirmed" )
416
- return conf , nil
417
442
418
443
// NP2WSH htlc confirmed.
419
- case conf : = <- confChanNP2WSH :
444
+ case conf = <- confChanNP2WSH :
420
445
s .htlc = s .htlcNP2WSH
421
446
s .log .Infof ("NP2WSH htlc confirmed" )
422
- return conf , nil
423
447
424
448
// Conf ntfn error.
425
449
case err := <- confErrP2WSH :
@@ -438,6 +462,19 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
438
462
return nil , globalCtx .Err ()
439
463
}
440
464
}
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
+
477
+ return conf , nil
441
478
}
442
479
443
480
// publishOnChainHtlc checks whether there are still enough blocks left and if
@@ -451,7 +488,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
451
488
// Verify whether it still makes sense to publish the htlc.
452
489
if blocksRemaining < MinLoopInPublishDelta {
453
490
s .setState (loopdb .StateFailTimeout )
454
- return false , s .persistState (ctx )
491
+ return false , s .persistAndAnnounceState (ctx )
455
492
}
456
493
457
494
// Get fee estimate from lnd.
@@ -465,7 +502,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
465
502
// Transition to state HtlcPublished before calling SendOutputs to
466
503
// prevent us from ever paying multiple times after a crash.
467
504
s .setState (loopdb .StateHtlcPublished )
468
- err = s .persistState (ctx )
505
+ err = s .persistAndAnnounceState (ctx )
469
506
if err != nil {
470
507
return false , err
471
508
}
@@ -483,7 +520,20 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
483
520
if err != nil {
484
521
return false , fmt .Errorf ("send outputs: %v" , err )
485
522
}
486
- 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
+ }
487
537
488
538
return true , nil
489
539
@@ -499,7 +549,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
499
549
rpcCtx , cancel := context .WithCancel (ctx )
500
550
defer cancel ()
501
551
spendChan , spendErr , err := s .lnd .ChainNotifier .RegisterSpendNtfn (
502
- rpcCtx , nil , s .htlc .PkScript , s .InitiationHeight ,
552
+ rpcCtx , htlcOutpoint , s .htlc .PkScript , s .InitiationHeight ,
503
553
)
504
554
if err != nil {
505
555
return fmt .Errorf ("register spend ntfn: %v" , err )
@@ -589,7 +639,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
589
639
// accounting data.
590
640
if s .state == loopdb .StateHtlcPublished {
591
641
s .setState (loopdb .StateInvoiceSettled )
592
- err := s .persistState (ctx )
642
+ err := s .persistAndAnnounceState (ctx )
593
643
if err != nil {
594
644
return err
595
645
}
@@ -689,24 +739,30 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
689
739
return nil
690
740
}
691
741
692
- // persistState updates the swap state and sends out an update notification.
693
- func (s * loopInSwap ) persistState (ctx context.Context ) error {
742
+ // persistAndAnnounceState updates the swap state on disk and sends out an
743
+ // update notification.
744
+ func (s * loopInSwap ) persistAndAnnounceState (ctx context.Context ) error {
694
745
// Update state in store.
695
- err := s .store .UpdateLoopIn (
696
- s .hash , s .lastUpdateTime ,
697
- loopdb.SwapStateData {
698
- State : s .state ,
699
- Cost : s .cost ,
700
- },
701
- )
702
- if err != nil {
746
+ if err := s .persistState (); err != nil {
703
747
return err
704
748
}
705
749
706
750
// Send out swap update
707
751
return s .sendUpdate (ctx )
708
752
}
709
753
754
+ // persistState updates the swap state on disk.
755
+ func (s * loopInSwap ) persistState () error {
756
+ return s .store .UpdateLoopIn (
757
+ s .hash , s .lastUpdateTime ,
758
+ loopdb.SwapStateData {
759
+ State : s .state ,
760
+ Cost : s .cost ,
761
+ HtlcTxHash : s .htlcTxHash ,
762
+ },
763
+ )
764
+ }
765
+
710
766
// setState updates the swap state and last update timestamp.
711
767
func (s * loopInSwap ) setState (state loopdb.SwapState ) {
712
768
s .lastUpdateTime = time .Now ()
0 commit comments