@@ -252,7 +252,7 @@ func (s *loopInSwap) execute(mainCtx context.Context,
252252 // permanently and losing funds.
253253 if err != nil {
254254 s .log .Errorf ("Swap error: %v" , err )
255- s .state = loopdb .StateFailTemporary
255+ s .setState ( loopdb .StateFailTemporary )
256256
257257 // If we cannot send out this update, there is nothing we can do.
258258 _ = s .sendUpdate (mainCtx )
@@ -282,7 +282,7 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
282282 // If an external htlc was indicated, we can move to the
283283 // HtlcPublished state directly and wait for
284284 // confirmation.
285- s .state = loopdb .StateHtlcPublished
285+ s .setState ( loopdb .StateHtlcPublished )
286286 err = s .persistState (globalCtx )
287287 if err != nil {
288288 return err
@@ -320,41 +320,7 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
320320 // invoice, receive the preimage and sweep the htlc. We are waiting for
321321 // this to happen and simultaneously watch the htlc expiry height. When
322322 // the htlc expires, we will publish a timeout tx to reclaim the funds.
323- spend , err := s .waitForHtlcSpend (globalCtx , htlcOutpoint )
324- if err != nil {
325- return err
326- }
327-
328- // Determine the htlc input of the spending tx and inspect the witness
329- // to findout whether a success or a timeout tx spend the htlc.
330- htlcInput := spend .SpendingTx .TxIn [spend .SpenderInputIndex ]
331-
332- if s .htlc .IsSuccessWitness (htlcInput .Witness ) {
333- s .state = loopdb .StateSuccess
334-
335- // Server swept the htlc. The htlc value can be added to the
336- // server cost balance.
337- s .cost .Server += htlcValue
338- } else {
339- s .state = loopdb .StateFailTimeout
340-
341- // Now that the timeout tx confirmed, we can safely cancel the
342- // swap invoice. We still need to query the final invoice state.
343- // This is not a hodl invoice, so it may be that the invoice was
344- // already settled. This means that the server didn't succeed in
345- // sweeping the htlc after paying the invoice.
346- err := s .lnd .Invoices .CancelInvoice (globalCtx , s .hash )
347- if err != nil && err != channeldb .ErrInvoiceAlreadySettled {
348- return err
349- }
350-
351- // TODO: Add miner fee of timeout tx to swap cost balance.
352- }
353-
354- // Wait for a final state of the swap invoice. It should either be
355- // settled because the server successfully paid it or canceled because
356- // we canceled after our timeout tx confirmed.
357- err = s .waitForSwapInvoiceResult (globalCtx )
323+ err = s .waitForSwapComplete (globalCtx , htlcOutpoint , htlcValue )
358324 if err != nil {
359325 return err
360326 }
@@ -411,7 +377,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
411377
412378 // Verify whether it still makes sense to publish the htlc.
413379 if blocksRemaining < MinLoopInPublishDelta {
414- s .state = loopdb .StateFailTimeout
380+ s .setState ( loopdb .StateFailTimeout )
415381 return false , s .persistState (ctx )
416382 }
417383
@@ -425,7 +391,7 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
425391
426392 // Transition to state HtlcPublished before calling SendOutputs to
427393 // prevent us from ever paying multiple times after a crash.
428- s .state = loopdb .StateHtlcPublished
394+ s .setState ( loopdb .StateHtlcPublished )
429395 err = s .persistState (ctx )
430396 if err != nil {
431397 return false , err
@@ -448,10 +414,11 @@ func (s *loopInSwap) publishOnChainHtlc(ctx context.Context) (bool, error) {
448414
449415}
450416
451- // waitForHtlcSpend waits until a spending tx of the htlc gets confirmed and
452- // returns the spend details.
453- func (s * loopInSwap ) waitForHtlcSpend (ctx context.Context ,
454- htlc * wire.OutPoint ) (* chainntnfs.SpendDetail , error ) {
417+ // waitForSwapComplete waits until a spending tx of the htlc gets confirmed and
418+ // the swap invoice is either settled or canceled. If the htlc times out, the
419+ // timeout tx will be published.
420+ func (s * loopInSwap ) waitForSwapComplete (ctx context.Context ,
421+ htlc * wire.OutPoint , htlcValue btcutil.Amount ) error {
455422
456423 // Register the htlc spend notification.
457424 rpcCtx , cancel := context .WithCancel (ctx )
@@ -460,58 +427,61 @@ func (s *loopInSwap) waitForHtlcSpend(ctx context.Context,
460427 rpcCtx , nil , s .htlc .ScriptHash , s .InitiationHeight ,
461428 )
462429 if err != nil {
463- return nil , fmt .Errorf ("register spend ntfn: %v" , err )
430+ return fmt .Errorf ("register spend ntfn: %v" , err )
464431 }
465432
466- for {
433+ // Register for swap invoice updates.
434+ rpcCtx , cancel = context .WithCancel (ctx )
435+ defer cancel ()
436+ s .log .Infof ("Subscribing to swap invoice %v" , s .hash )
437+ swapInvoiceChan , swapInvoiceErr , err := s .lnd .Invoices .SubscribeSingleInvoice (
438+ rpcCtx , s .hash ,
439+ )
440+ if err != nil {
441+ return fmt .Errorf ("subscribe to swap invoice: %v" , err )
442+ }
443+
444+ htlcSpend := false
445+ invoiceFinalized := false
446+ for ! htlcSpend || ! invoiceFinalized {
467447 select {
468448 // Spend notification error.
469449 case err := <- spendErr :
470- return nil , err
450+ return err
471451
452+ // Receive block epochs and start publishing the timeout tx
453+ // whenever possible.
472454 case notification := <- s .blockEpochChan :
473455 s .height = notification .(int32 )
474456
475457 if s .height >= s .LoopInContract .CltvExpiry {
476458 err := s .publishTimeoutTx (ctx , htlc )
477459 if err != nil {
478- return nil , err
460+ return err
479461 }
480462 }
481463
482- // Htlc spend, break loop.
464+ // The htlc spend is confirmed. Inspect the spending tx to
465+ // determine the final swap state.
483466 case spendDetails := <- spendChan :
484467 s .log .Infof ("Htlc spend by tx: %v" ,
485468 spendDetails .SpenderTxHash )
486469
487- return spendDetails , nil
488-
489- case <- ctx .Done ():
490- return nil , ctx .Err ()
491- }
492- }
493- }
494-
495- // waitForSwapPaid waits until our swap invoice gets paid by the server.
496- func (s * loopInSwap ) waitForSwapInvoiceResult (ctx context.Context ) error {
497- // Wait for swap invoice to be paid.
498- rpcCtx , cancel := context .WithCancel (ctx )
499- defer cancel ()
500- s .log .Infof ("Subscribing to swap invoice %v" , s .hash )
501- swapInvoiceChan , swapInvoiceErr , err := s .lnd .Invoices .SubscribeSingleInvoice (
502- rpcCtx , s .hash ,
503- )
504- if err != nil {
505- return err
506- }
470+ err := s .processHtlcSpend (
471+ ctx , spendDetails , htlcValue ,
472+ )
473+ if err != nil {
474+ return err
475+ }
507476
508- for {
509- select {
477+ htlcSpend = true
510478
511479 // Swap invoice ntfn error.
512480 case err := <- swapInvoiceErr :
513481 return err
514482
483+ // An update to the swap invoice occured. Check the new state
484+ // and update the swap state accordingly.
515485 case update := <- swapInvoiceChan :
516486 s .log .Infof ("Received swap invoice update: %v" ,
517487 update .State )
@@ -521,18 +491,67 @@ func (s *loopInSwap) waitForSwapInvoiceResult(ctx context.Context) error {
521491 // Swap invoice was paid, so update server cost balance.
522492 case channeldb .ContractSettled :
523493 s .cost .Server -= update .AmtPaid
524- return nil
494+
495+ // If invoice settlement and htlc spend happen
496+ // in the expected order, move the swap to an
497+ // intermediate state that indicates that the
498+ // swap is complete from the user point of view,
499+ // but still incomplete with regards to
500+ // accounting data.
501+ if s .state == loopdb .StateHtlcPublished {
502+ s .setState (loopdb .StateInvoiceSettled )
503+ err := s .persistState (ctx )
504+ if err != nil {
505+ return err
506+ }
507+ }
508+
509+ invoiceFinalized = true
525510
526511 // Canceled invoice has no effect on server cost
527512 // balance.
528513 case channeldb .ContractCanceled :
529- return nil
514+ invoiceFinalized = true
530515 }
531516
532517 case <- ctx .Done ():
533518 return ctx .Err ()
534519 }
535520 }
521+
522+ return nil
523+ }
524+
525+ func (s * loopInSwap ) processHtlcSpend (ctx context.Context ,
526+ spend * chainntnfs.SpendDetail , htlcValue btcutil.Amount ) error {
527+
528+ // Determine the htlc input of the spending tx and inspect the witness
529+ // to findout whether a success or a timeout tx spend the htlc.
530+ htlcInput := spend .SpendingTx .TxIn [spend .SpenderInputIndex ]
531+
532+ if s .htlc .IsSuccessWitness (htlcInput .Witness ) {
533+ s .setState (loopdb .StateSuccess )
534+
535+ // Server swept the htlc. The htlc value can be added to the
536+ // server cost balance.
537+ s .cost .Server += htlcValue
538+ } else {
539+ s .setState (loopdb .StateFailTimeout )
540+
541+ // Now that the timeout tx confirmed, we can safely cancel the
542+ // swap invoice. We still need to query the final invoice state.
543+ // This is not a hodl invoice, so it may be that the invoice was
544+ // already settled. This means that the server didn't succeed in
545+ // sweeping the htlc after paying the invoice.
546+ err := s .lnd .Invoices .CancelInvoice (ctx , s .hash )
547+ if err != nil && err != channeldb .ErrInvoiceAlreadySettled {
548+ return err
549+ }
550+
551+ // TODO: Add miner fee of timeout tx to swap cost balance.
552+ }
553+
554+ return nil
536555}
537556
538557// publishTimeoutTx publishes a timeout tx after the on-chain htlc has expired.
@@ -582,16 +601,18 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
582601
583602// persistState updates the swap state and sends out an update notification.
584603func (s * loopInSwap ) persistState (ctx context.Context ) error {
585- updateTime := time .Now ()
586-
587- s .lastUpdateTime = updateTime
588-
589604 // Update state in store.
590- err := s .store .UpdateLoopIn (s .hash , updateTime , s .state )
605+ err := s .store .UpdateLoopIn (s .hash , s . lastUpdateTime , s .state )
591606 if err != nil {
592607 return err
593608 }
594609
595610 // Send out swap update
596611 return s .sendUpdate (ctx )
597612}
613+
614+ // setState updates the swap state and last update timestamp.
615+ func (s * loopInSwap ) setState (state loopdb.SwapState ) {
616+ s .lastUpdateTime = time .Now ()
617+ s .state = state
618+ }
0 commit comments