@@ -9,17 +9,16 @@ import (
99
1010 "github.com/btcsuite/btcutil"
1111
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"
1613 "github.com/btcsuite/btcd/wire"
17- "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
18-
1914 "github.com/lightninglabs/lndclient"
2015 "github.com/lightninglabs/loop/loopdb"
2116 "github.com/lightninglabs/loop/swap"
17+ "github.com/lightningnetwork/lnd/chainntnfs"
18+ "github.com/lightningnetwork/lnd/channeldb"
19+ "github.com/lightningnetwork/lnd/lnrpc/invoicesrpc"
2220 "github.com/lightningnetwork/lnd/lntypes"
21+ "github.com/lightningnetwork/lnd/lnwire"
2322)
2423
2524var (
@@ -58,6 +57,9 @@ type loopInSwap struct {
5857
5958 htlcNP2WSH * swap.Htlc
6059
60+ // htlcTxHash is the confirmed htlc tx id.
61+ htlcTxHash * chainhash.Hash
62+
6163 timeoutAddr btcutil.Address
6264}
6365
@@ -209,6 +211,7 @@ func resumeLoopInSwap(reqContext context.Context, cfg *swapConfig,
209211 } else {
210212 swap .state = lastUpdate .State
211213 swap .lastUpdateTime = lastUpdate .Time
214+ swap .htlcTxHash = lastUpdate .HtlcTxHash
212215 }
213216
214217 return swap , nil
@@ -333,7 +336,7 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
333336 // HtlcPublished state directly and wait for
334337 // confirmation.
335338 s .setState (loopdb .StateHtlcPublished )
336- err = s .persistState (globalCtx )
339+ err = s .persistAndAnnounceState (globalCtx )
337340 if err != nil {
338341 return err
339342 }
@@ -363,6 +366,13 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
363366 return err
364367 }
365368
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+
366376 // TODO: Add miner fee of htlc tx to swap cost balance.
367377
368378 // 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 {
376386 }
377387
378388 // Persist swap outcome.
379- if err := s .persistState (globalCtx ); err != nil {
389+ if err := s .persistAndAnnounceState (globalCtx ); err != nil {
380390 return err
381391 }
382392
@@ -387,39 +397,53 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
387397func (s * loopInSwap ) waitForHtlcConf (globalCtx context.Context ) (
388398 * chainntnfs.TxConfirmation , error ) {
389399
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+
390415 ctx , cancel := context .WithCancel (globalCtx )
391416 defer cancel ()
392417
393418 notifier := s .lnd .ChainNotifier
394419
395420 confChanP2WSH , confErrP2WSH , err := notifier .RegisterConfirmationsNtfn (
396- ctx , nil , s .htlcP2WSH .PkScript , 1 , s .InitiationHeight ,
421+ ctx , s . htlcTxHash , s .htlcP2WSH .PkScript , 1 , s .InitiationHeight ,
397422 )
398423 if err != nil {
399424 return nil , err
400425 }
401426
402427 confChanNP2WSH , confErrNP2WSH , err := notifier .RegisterConfirmationsNtfn (
403- ctx , nil , s .htlcNP2WSH .PkScript , 1 , s .InitiationHeight ,
428+ ctx , s . htlcTxHash , s .htlcNP2WSH .PkScript , 1 , s .InitiationHeight ,
404429 )
405430 if err != nil {
406431 return nil , err
407432 }
408433
409- for {
434+ var conf * chainntnfs.TxConfirmation
435+ for conf == nil {
410436 select {
411437
412438 // P2WSH htlc confirmed.
413- case conf : = <- confChanP2WSH :
439+ case conf = <- confChanP2WSH :
414440 s .htlc = s .htlcP2WSH
415441 s .log .Infof ("P2WSH htlc confirmed" )
416- return conf , nil
417442
418443 // NP2WSH htlc confirmed.
419- case conf : = <- confChanNP2WSH :
444+ case conf = <- confChanNP2WSH :
420445 s .htlc = s .htlcNP2WSH
421446 s .log .Infof ("NP2WSH htlc confirmed" )
422- return conf , nil
423447
424448 // Conf ntfn error.
425449 case err := <- confErrP2WSH :
@@ -438,6 +462,19 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
438462 return nil , globalCtx .Err ()
439463 }
440464 }
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
441478}
442479
443480// publishOnChainHtlc checks whether there are still enough blocks left and if
@@ -451,7 +488,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
451488 // Verify whether it still makes sense to publish the htlc.
452489 if blocksRemaining < MinLoopInPublishDelta {
453490 s .setState (loopdb .StateFailTimeout )
454- return false , s .persistState (ctx )
491+ return false , s .persistAndAnnounceState (ctx )
455492 }
456493
457494 // Get fee estimate from lnd.
@@ -465,7 +502,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
465502 // Transition to state HtlcPublished before calling SendOutputs to
466503 // prevent us from ever paying multiple times after a crash.
467504 s .setState (loopdb .StateHtlcPublished )
468- err = s .persistState (ctx )
505+ err = s .persistAndAnnounceState (ctx )
469506 if err != nil {
470507 return false , err
471508 }
@@ -483,7 +520,20 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
483520 if err != nil {
484521 return false , fmt .Errorf ("send outputs: %v" , err )
485522 }
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+ }
487537
488538 return true , nil
489539
@@ -499,7 +549,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
499549 rpcCtx , cancel := context .WithCancel (ctx )
500550 defer cancel ()
501551 spendChan , spendErr , err := s .lnd .ChainNotifier .RegisterSpendNtfn (
502- rpcCtx , nil , s .htlc .PkScript , s .InitiationHeight ,
552+ rpcCtx , htlcOutpoint , s .htlc .PkScript , s .InitiationHeight ,
503553 )
504554 if err != nil {
505555 return fmt .Errorf ("register spend ntfn: %v" , err )
@@ -589,7 +639,7 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
589639 // accounting data.
590640 if s .state == loopdb .StateHtlcPublished {
591641 s .setState (loopdb .StateInvoiceSettled )
592- err := s .persistState (ctx )
642+ err := s .persistAndAnnounceState (ctx )
593643 if err != nil {
594644 return err
595645 }
@@ -689,24 +739,30 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
689739 return nil
690740}
691741
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 {
694745 // 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 {
703747 return err
704748 }
705749
706750 // Send out swap update
707751 return s .sendUpdate (ctx )
708752}
709753
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+
710766// setState updates the swap state and last update timestamp.
711767func (s * loopInSwap ) setState (state loopdb.SwapState ) {
712768 s .lastUpdateTime = time .Now ()
0 commit comments