Skip to content

Commit 1df6f4d

Browse files
authored
Merge pull request #676 from hieblmi/sweep-timeout-on-invalid-amt
loopin: sweep incorrect amount on timeout
2 parents 6a62cd0 + 855fa8d commit 1df6f4d

7 files changed

+189
-136
lines changed

loopd/swapclient_server.go

+3
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,9 @@ func (s *swapClientServer) marshallSwap(loopSwap *loop.SwapInfo) (
306306
case loopdb.StateFailInsufficientConfirmedBalance:
307307
failureReason = clientrpc.FailureReason_FAILURE_REASON_INSUFFICIENT_CONFIRMED_BALANCE
308308

309+
case loopdb.StateFailIncorrectHtlcAmtSwept:
310+
failureReason = clientrpc.FailureReason_FAILURE_REASON_INCORRECT_HTLC_AMT_SWEPT
311+
309312
default:
310313
return nil, fmt.Errorf("unknown swap state: %v", loopSwap.State)
311314
}

loopdb/swapstate.go

+10-5
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ const (
7272
// StateFailInsufficientConfirmedBalance indicates that the swap wasn't
7373
// published due to insufficient confirmed balance.
7474
StateFailInsufficientConfirmedBalance SwapState = 12
75+
76+
// StateFailIncorrectHtlcAmtSwept indicates that the amount of an
77+
// externally published loop in htlc that didn't match the swap amount
78+
// has been swept back to the user after the htlc timeout period.
79+
StateFailIncorrectHtlcAmtSwept SwapState = 13
7580
)
7681

7782
// SwapStateType defines the types of swap states that exist. Every swap state
@@ -92,10 +97,7 @@ const (
9297

9398
// Type returns the type of the SwapState it is called on.
9499
func (s SwapState) Type() SwapStateType {
95-
if s == StateInitiated || s == StateHtlcPublished ||
96-
s == StatePreimageRevealed || s == StateFailTemporary ||
97-
s == StateInvoiceSettled {
98-
100+
if s.IsPending() {
99101
return StateTypePending
100102
}
101103

@@ -110,7 +112,7 @@ func (s SwapState) Type() SwapStateType {
110112
func (s SwapState) IsPending() bool {
111113
return s == StateInitiated || s == StateHtlcPublished ||
112114
s == StatePreimageRevealed || s == StateFailTemporary ||
113-
s == StateInvoiceSettled
115+
s == StateInvoiceSettled || s == StateFailIncorrectHtlcAmt
114116
}
115117

116118
// IsFinal returns true if the swap is in a final state.
@@ -160,6 +162,9 @@ func (s SwapState) String() string {
160162
case StateFailInsufficientConfirmedBalance:
161163
return "InsufficientConfirmedBalance"
162164

165+
case StateFailIncorrectHtlcAmtSwept:
166+
return "StateFailIncorrectHtlcAmtSwept"
167+
163168
default:
164169
return "Unknown"
165170
}

loopin.go

+21-4
Original file line numberDiff line numberDiff line change
@@ -623,10 +623,18 @@ func (s *loopInSwap) executeSwap(globalCtx context.Context) error {
623623
}
624624

625625
// Verify that the confirmed (external) htlc value matches the swap
626-
// amount. Otherwise, fail the swap immediately.
627-
if htlcValue != s.LoopInContract.AmountRequested {
626+
// amount. If the amounts mismatch we update the swap state to indicate
627+
// this, but end processing the swap. Instead, we continue to wait for
628+
// the htlc to expire and publish a timeout tx to reclaim the funds. We
629+
// skip this part if the swap was recovered from this state.
630+
if s.state != loopdb.StateFailIncorrectHtlcAmt &&
631+
htlcValue != s.LoopInContract.AmountRequested {
632+
628633
s.setState(loopdb.StateFailIncorrectHtlcAmt)
629-
return s.persistAndAnnounceState(globalCtx)
634+
err = s.persistAndAnnounceState(globalCtx)
635+
if err != nil {
636+
log.Errorf("Error persisting state: %v", err)
637+
}
630638
}
631639

632640
// The server is expected to see the htlc on-chain and know that it can
@@ -1032,7 +1040,16 @@ func (s *loopInSwap) processHtlcSpend(ctx context.Context,
10321040
// We needed another on chain tx to sweep the timeout clause,
10331041
// which we now include in our costs.
10341042
s.cost.Onchain += sweepFee
1035-
s.setState(loopdb.StateFailTimeout)
1043+
1044+
// If the swap is in state StateFailIncorrectHtlcAmt we know
1045+
// that the deposited htlc amount wasn't equal to the contract
1046+
// amount. We can finalize the swap by setting an appropriate
1047+
// state.
1048+
if s.state == loopdb.StateFailIncorrectHtlcAmt {
1049+
s.setState(loopdb.StateFailIncorrectHtlcAmtSwept)
1050+
} else {
1051+
s.setState(loopdb.StateFailTimeout)
1052+
}
10361053

10371054
// Now that the timeout tx confirmed, we can safely cancel the
10381055
// swap invoice. We still need to query the final invoice state.

loopin_test.go

+22-9
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,19 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
277277
Tx: &htlcTx,
278278
}
279279

280-
// Assert that the swap is failed in case of an invalid amount.
281-
invalidAmt := externalValue != 0 && externalValue != int64(req.Amount)
282-
if invalidAmt {
283-
ctx.assertState(loopdb.StateFailIncorrectHtlcAmt)
284-
ctx.store.AssertLoopInState(loopdb.StateFailIncorrectHtlcAmt)
280+
isInvalidAmt := externalValue != 0 && externalValue != int64(req.Amount)
281+
handleHtlcExpiry(
282+
t, ctx, inSwap, htlcTx, cost, errChan, isInvalidAmt,
283+
)
284+
}
285285

286-
require.NoError(t, <-errChan)
287-
return
286+
func handleHtlcExpiry(t *testing.T, ctx *loopInTestContext, inSwap *loopInSwap,
287+
htlcTx wire.MsgTx, cost loopdb.SwapCost, errChan chan error,
288+
isInvalidAmount bool) {
289+
290+
if isInvalidAmount {
291+
ctx.store.AssertLoopInState(loopdb.StateFailIncorrectHtlcAmt)
292+
ctx.assertState(loopdb.StateFailIncorrectHtlcAmt)
288293
}
289294

290295
// Client starts listening for spend of htlc.
@@ -329,8 +334,16 @@ func testLoopInTimeout(t *testing.T, externalValue int64) {
329334
// Signal that the invoice was canceled.
330335
ctx.updateInvoiceState(0, invpkg.ContractCanceled)
331336

332-
ctx.assertState(loopdb.StateFailTimeout)
333-
state := ctx.store.AssertLoopInState(loopdb.StateFailTimeout)
337+
var state loopdb.SwapStateData
338+
if isInvalidAmount {
339+
state = ctx.store.AssertLoopInState(
340+
loopdb.StateFailIncorrectHtlcAmtSwept,
341+
)
342+
ctx.assertState(loopdb.StateFailIncorrectHtlcAmtSwept)
343+
} else {
344+
ctx.assertState(loopdb.StateFailTimeout)
345+
state = ctx.store.AssertLoopInState(loopdb.StateFailTimeout)
346+
}
334347
require.Equal(t, cost, state.Cost)
335348

336349
require.NoError(t, <-errChan)

0 commit comments

Comments
 (0)