Skip to content

Commit 9db8bd5

Browse files
committed
loopout: do not reveal preimage if time to safe reveal has passed
1 parent 8d4404a commit 9db8bd5

File tree

2 files changed

+55
-17
lines changed

2 files changed

+55
-17
lines changed

loopout.go

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,12 @@ func (s *loopOutSwap) executeSwap(globalCtx context.Context) error {
438438
return err
439439
}
440440

441+
// If spend details are nil, we resolved the swap without waiting for
442+
// its spend, so we can exit.
443+
if spendDetails == nil {
444+
return nil
445+
}
446+
441447
// Inspect witness stack to see if it is a success transaction. We
442448
// don't just try to match with the hash of our sweep tx, because it
443449
// may be swept by a different (fee) sweep tx from a previous run.
@@ -854,6 +860,14 @@ func (s *loopOutSwap) waitForHtlcSpendConfirmed(globalCtx context.Context,
854860
return nil, err
855861
}
856862

863+
// If the result of our spend func was that the swap
864+
// has reached a final state, then we return nil spend
865+
// details, because there is no further action required
866+
// for this swap.
867+
if s.state.Type() != loopdb.StateTypePending {
868+
return nil, nil
869+
}
870+
857871
// If our off chain payment is not yet complete, we
858872
// try to push our preimage to the server.
859873
if !paymentComplete {
@@ -889,7 +903,9 @@ func (s *loopOutSwap) pushPreimage(ctx context.Context) {
889903

890904
// sweep tries to sweep the given htlc to a destination address. It takes into
891905
// account the max miner fee and marks the preimage as revealed when it
892-
// published the tx.
906+
// published the tx. If the preimage has not yet been revealed, and the time
907+
// during which we can safely reveal it has passed, the swap will be marked
908+
// as failed, and the function will return.
893909
//
894910
// TODO: Use lnd sweeper?
895911
func (s *loopOutSwap) sweep(ctx context.Context,
@@ -900,16 +916,36 @@ func (s *loopOutSwap) sweep(ctx context.Context,
900916
return s.htlc.GenSuccessWitness(sig, s.Preimage)
901917
}
902918

919+
remainingBlocks := s.CltvExpiry - s.height
920+
blocksToLastReveal := remainingBlocks - MinLoopOutPreimageRevealDelta
921+
preimageRevealed := s.state == loopdb.StatePreimageRevealed
922+
923+
// If we have not revealed our preimage, and we don't have time left
924+
// to sweep the swap, we abandon the swap because we can no longer
925+
// sweep the success path (without potentially having to compete with
926+
// the server's timeout sweep), and we have not had any coins pulled
927+
// off-chain.
928+
if blocksToLastReveal <= 0 && !preimageRevealed {
929+
s.log.Infof("Preimage can no longer be safely revealed: "+
930+
"expires at: %v, current height: %v", s.CltvExpiry,
931+
s.height)
932+
933+
s.state = loopdb.StateFailTimeout
934+
return nil
935+
}
936+
903937
// Calculate the transaction fee based on the confirmation target
904938
// required to sweep the HTLC before the timeout. We'll use the
905939
// confirmation target provided by the client unless we've come too
906940
// close to the expiration height, in which case we'll use the default
907941
// if it is better than what the client provided.
908942
confTarget := s.SweepConfTarget
909-
if s.CltvExpiry-s.height <= DefaultSweepConfTargetDelta &&
943+
if remainingBlocks <= DefaultSweepConfTargetDelta &&
910944
confTarget > DefaultSweepConfTarget {
945+
911946
confTarget = DefaultSweepConfTarget
912947
}
948+
913949
fee, err := s.sweeper.GetSweepFee(
914950
ctx, s.htlc.AddSuccessToEstimator, s.DestAddr, confTarget,
915951
)
@@ -922,7 +958,7 @@ func (s *loopOutSwap) sweep(ctx context.Context,
922958
s.log.Warnf("Required fee %v exceeds max miner fee of %v",
923959
fee, s.MaxMinerFee)
924960

925-
if s.state == loopdb.StatePreimageRevealed {
961+
if preimageRevealed {
926962
// The currently required fee exceeds the max, but we
927963
// already revealed the preimage. The best we can do now
928964
// is to republish with the max fee.

loopout_test.go

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -583,8 +583,8 @@ func TestPreimagePush(t *testing.T) {
583583
}
584584

585585
// TestExpiryBeforeReveal tests the case where the on-chain HTLC expires before
586-
// we have revealed our preimage. This test demonstrates that the client will
587-
// erroneously reveal the preimage even though we're nearing our timeout.
586+
// we have revealed our preimage, demonstrating that we do not reveal our
587+
// preimage once we've reached our expiry height.
588588
func TestExpiryBeforeReveal(t *testing.T) {
589589
defer test.Guard(t)()
590590

@@ -622,9 +622,8 @@ func TestExpiryBeforeReveal(t *testing.T) {
622622
}
623623

624624
errChan := make(chan error)
625-
cancelCtx, cancel := context.WithCancel(context.Background())
626625
go func() {
627-
err := swap.execute(cancelCtx, &executeConfig{
626+
err := swap.execute(context.Background(), &executeConfig{
628627
statusChan: statusChan,
629628
blockEpochChan: blockEpochChan,
630629
timerFactory: timerFactory,
@@ -653,7 +652,8 @@ func TestExpiryBeforeReveal(t *testing.T) {
653652
ctx.AssertRegisterConf(false, defaultConfirmations)
654653

655654
// Advance the block height to get the HTLC confirmed.
656-
blockEpochChan <- ctx.Lnd.Height + 1
655+
height := ctx.Lnd.Height + 1
656+
blockEpochChan <- height
657657

658658
htlcTx := wire.NewMsgTx(2)
659659
htlcTx.AddTxOut(&wire.TxOut{
@@ -681,17 +681,19 @@ func TestExpiryBeforeReveal(t *testing.T) {
681681

682682
// Advance the block height to the point where we would do timeout
683683
// instead of pushing the preimage.
684-
blockEpochChan <- lnd.Height + testReq.Expiry
684+
blockEpochChan <- testReq.Expiry + height
685685

686-
// Tick our expiry chan again, this time we expect the swap to
687-
// publish our sweep timeout, despite expiry having passed, and the
688-
// potential for a race with the server.
686+
// Tick our expiry channel again to trigger another sweep attempt.
689687
expiryChan <- testTime
690688

691-
// Expect a signing request for the HTLC success transaction.
692-
<-ctx.Lnd.SignOutputRawChannel
689+
// We should see our swap marked as failed.
690+
cfg.store.(*storeMock).assertLoopOutState(
691+
loopdb.StateFailTimeout,
692+
)
693+
status := <-statusChan
694+
require.Equal(
695+
t, status.State, loopdb.StateFailTimeout,
696+
)
693697

694-
// We just cancel the swap now rather than testing to completion.
695-
cancel()
696-
require.Equal(t, context.Canceled, <-errChan)
698+
require.Nil(t, <-errChan)
697699
}

0 commit comments

Comments
 (0)