4848 // TimeoutTxConfTarget defines the confirmation target for the loop in
4949 // timeout tx.
5050 TimeoutTxConfTarget = int32 (2 )
51+
52+ // ErrSwapFinalized is returned when a to be executed swap is already in
53+ // a final state.
54+ ErrSwapFinalized = errors .New ("swap is in a final state" )
5155)
5256
5357// loopInSwap contains all the in-memory state related to a pending loop in
@@ -70,6 +74,8 @@ type loopInSwap struct {
7074
7175 timeoutAddr btcutil.Address
7276
77+ abandonChan chan struct {}
78+
7379 wg sync.WaitGroup
7480}
7581
@@ -308,6 +314,8 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
308314 swap .log .Infof ("Server message: %v" , swapResp .serverMessage )
309315 }
310316
317+ swap .abandonChan = make (chan struct {}, 1 )
318+
311319 return & loopInInitResult {
312320 swap : swap ,
313321 serverMessage : swapResp .serverMessage ,
@@ -518,6 +526,11 @@ func (s *loopInSwap) execute(mainCtx context.Context,
518526 // error occurs.
519527 err = s .executeSwap (mainCtx )
520528
529+ // Stop the execution if the swap has been abandoned.
530+ if err != nil && s .state == loopdb .StateFailAbandoned {
531+ return err
532+ }
533+
521534 // Sanity check. If there is no error, the swap must be in a final
522535 // state.
523536 if err == nil && s .state .Type () == loopdb .StateTypePending {
@@ -553,6 +566,11 @@ func (s *loopInSwap) execute(mainCtx context.Context,
553566func (s * loopInSwap ) executeSwap (globalCtx context.Context ) error {
554567 var err error
555568
569+ // If the swap is already in a final state, we can return immediately.
570+ if s .state .IsFinal () {
571+ return ErrSwapFinalized
572+ }
573+
556574 // For loop in, the client takes the first step by publishing the
557575 // on-chain htlc. Only do this if we haven't already done so in a
558576 // previous run.
@@ -688,6 +706,11 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
688706 case notification := <- s .blockEpochChan :
689707 s .height = notification .(int32 )
690708
709+ // If the client requested the swap to be abandoned, we override
710+ // the status in the database.
711+ case <- s .abandonChan :
712+ return nil , s .setStateAbandoned (ctx )
713+
691714 // Cancel.
692715 case <- globalCtx .Done ():
693716 return nil , globalCtx .Err ()
@@ -840,6 +863,11 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
840863 htlcKeyRevealed := false
841864 for ! htlcSpend || ! invoiceFinalized {
842865 select {
866+ // If the client requested the swap to be abandoned, we override
867+ // the status in the database.
868+ case <- s .abandonChan :
869+ return s .setStateAbandoned (ctx )
870+
843871 // Spend notification error.
844872 case err := <- spendErr :
845873 return err
@@ -1062,6 +1090,31 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
10621090 return fee , nil
10631091}
10641092
1093+ // setStateAbandoned stores the abandoned state and announces it. It also
1094+ // cancels the swap invoice so the server can't settle it.
1095+ func (s * loopInSwap ) setStateAbandoned (ctx context.Context ) error {
1096+ s .log .Infof ("Abandoning swap %v..." , s .hash )
1097+
1098+ if ! s .state .IsPending () {
1099+ return fmt .Errorf ("cannot abandon swap in state %v" , s .state )
1100+ }
1101+
1102+ s .setState (loopdb .StateFailAbandoned )
1103+
1104+ err := s .persistAndAnnounceState (ctx )
1105+ if err != nil {
1106+ return err
1107+ }
1108+
1109+ // If the invoice is already settled or canceled, this is a nop.
1110+ _ = s .lnd .Invoices .CancelInvoice (ctx , s .hash )
1111+
1112+ return fmt .Errorf ("swap hash " +
1113+ "abandoned by client, " +
1114+ "swap ID: %v, %v" ,
1115+ s .hash , err )
1116+ }
1117+
10651118// persistAndAnnounceState updates the swap state on disk and sends out an
10661119// update notification.
10671120func (s * loopInSwap ) persistAndAnnounceState (ctx context.Context ) error {
0 commit comments