loopd: abandon loop-ins

This commit is contained in:
Slyghtning 2023-11-13 14:50:10 +01:00
parent 0fbf253391
commit 5e91c446b8
No known key found for this signature in database
GPG key ID: F82D456EA023C9BF
6 changed files with 161 additions and 3 deletions

View file

@ -48,6 +48,10 @@ var (
// TimeoutTxConfTarget defines the confirmation target for the loop in
// timeout tx.
TimeoutTxConfTarget = int32(2)
// ErrSwapFinalized is returned when a to be executed swap is already in
// a final state.
ErrSwapFinalized = errors.New("swap is in a final state")
)
// loopInSwap contains all the in-memory state related to a pending loop in
@ -70,6 +74,8 @@ type loopInSwap struct {
timeoutAddr btcutil.Address
abandonChan chan struct{}
wg sync.WaitGroup
}
@ -308,6 +314,8 @@ func newLoopInSwap(globalCtx context.Context, cfg *swapConfig,
swap.log.Infof("Server message: %v", swapResp.serverMessage)
}
swap.abandonChan = make(chan struct{}, 1)
return &loopInInitResult{
swap: swap,
serverMessage: swapResp.serverMessage,
@ -518,6 +526,11 @@ func (s *loopInSwap) execute(mainCtx context.Context,
// error occurs.
err = s.executeSwap(mainCtx)
// Stop the execution if the swap has been abandoned.
if err != nil && s.state == loopdb.StateFailAbandoned {
return err
}
// Sanity check. If there is no error, the swap must be in a final
// state.
if err == nil && s.state.Type() == loopdb.StateTypePending {
@ -553,6 +566,11 @@ func (s *loopInSwap) execute(mainCtx context.Context,
func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
var err error
// If the swap is already in a final state, we can return immediately.
if s.state.IsFinal() {
return ErrSwapFinalized
}
// For loop in, the client takes the first step by publishing the
// on-chain htlc. Only do this if we haven't already done so in a
// previous run.
@ -688,6 +706,11 @@ func (s *loopInSwap) waitForHtlcConf(globalCtx context.Context) (
case notification := <-s.blockEpochChan:
s.height = notification.(int32)
// If the client requested the swap to be abandoned, we override
// the status in the database.
case <-s.abandonChan:
return nil, s.setStateAbandoned(ctx)
// Cancel.
case <-globalCtx.Done():
return nil, globalCtx.Err()
@ -840,6 +863,11 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context,
htlcKeyRevealed := false
for !htlcSpend || !invoiceFinalized {
select {
// If the client requested the swap to be abandoned, we override
// the status in the database.
case <-s.abandonChan:
return s.setStateAbandoned(ctx)
// Spend notification error.
case err := <-spendErr:
return err
@ -1062,6 +1090,31 @@ func (s *loopInSwap) publishTimeoutTx(ctx context.Context,
return fee, nil
}
// setStateAbandoned stores the abandoned state and announces it. It also
// cancels the swap invoice so the server can't settle it.
func (s *loopInSwap) setStateAbandoned(ctx context.Context) error {
s.log.Infof("Abandoning swap %v...", s.hash)
if !s.state.IsPending() {
return fmt.Errorf("cannot abandon swap in state %v", s.state)
}
s.setState(loopdb.StateFailAbandoned)
err := s.persistAndAnnounceState(ctx)
if err != nil {
return err
}
// If the invoice is already settled or canceled, this is a nop.
_ = s.lnd.Invoices.CancelInvoice(ctx, s.hash)
return fmt.Errorf("swap hash "+
"abandoned by client, "+
"swap ID: %v, %v",
s.hash, err)
}
// persistAndAnnounceState updates the swap state on disk and sends out an
// update notification.
func (s *loopInSwap) persistAndAnnounceState(ctx context.Context) error {