Skip to content

Commit ce6f826

Browse files
committed
unit: abandon swap
1 parent 378d817 commit ce6f826

File tree

3 files changed

+206
-4
lines changed

3 files changed

+206
-4
lines changed

loopin_test.go

Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,3 +574,205 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
574574
cost.Server = btcutil.Amount(htlcTx.TxOut[0].Value) - amtPaid
575575
require.Equal(t, cost, finalState.Cost)
576576
}
577+
578+
// TestAbandonPublishedHtlcState advances a loop-in swap to StateHtlcPublished,
579+
// then abandons it and ensures that executing the same swap would not progress.
580+
func TestAbandonPublishedHtlcState(t *testing.T) {
581+
defer test.Guard(t)()
582+
583+
ctx := newLoopInTestContext(t)
584+
585+
height := int32(600)
586+
587+
cfg, err, inSwap := startNewLoopIn(t, ctx, height)
588+
require.NoError(t, err)
589+
590+
advanceToPublishedHtlc(t, ctx)
591+
592+
// The client requests to abandon the published htlc state.
593+
inSwap.abandonChan <- struct{}{}
594+
595+
// Ensure that the swap is now in the StateFailAbandoned state.
596+
ctx.assertState(loopdb.StateFailAbandoned)
597+
598+
// Ensure that the swap is also in the StateFailAbandoned state in the
599+
// database.
600+
ctx.store.assertLoopInState(loopdb.StateFailAbandoned)
601+
602+
// Ensure that the swap was abandoned and the execution stopped.
603+
err = <-ctx.errChan
604+
require.Error(t, err)
605+
require.Contains(t, err.Error(), "swap hash abandoned by client")
606+
607+
// We re-instantiate the swap and ensure that it does not progress.
608+
pendSwap := &loopdb.LoopIn{
609+
Contract: &inSwap.LoopInContract,
610+
Loop: loopdb.Loop{
611+
Events: []*loopdb.LoopEvent{
612+
{
613+
SwapStateData: loopdb.SwapStateData{
614+
State: inSwap.state,
615+
},
616+
},
617+
},
618+
Hash: testPreimage.Hash(),
619+
},
620+
}
621+
resumedSwap, err := resumeLoopInSwap(
622+
context.Background(), cfg, pendSwap,
623+
)
624+
require.NoError(t, err)
625+
626+
// Execute the abandoned swap.
627+
go func() {
628+
err := resumedSwap.execute(
629+
context.Background(), ctx.cfg, height,
630+
)
631+
if err != nil {
632+
log.Error(err)
633+
}
634+
ctx.errChan <- err
635+
}()
636+
637+
// Ensure that the swap is still in the StateFailAbandoned state.
638+
swapInfo := <-ctx.statusChan
639+
require.Equal(t, loopdb.StateFailAbandoned, swapInfo.State)
640+
641+
// Ensure that the execution flagged the abandoned swap as finalized.
642+
err = <-ctx.errChan
643+
require.Error(t, err)
644+
require.Equal(t, ErrSwapFinalized, err)
645+
}
646+
647+
// TestAbandonSettledInvoiceState advances a loop-in swap to
648+
// StateInvoiceSettled, then abandons it and ensures that executing the same
649+
// swap would not progress.
650+
func TestAbandonSettledInvoiceState(t *testing.T) {
651+
defer test.Guard(t)()
652+
653+
ctx := newLoopInTestContext(t)
654+
655+
height := int32(600)
656+
657+
cfg, err, inSwap := startNewLoopIn(t, ctx, height)
658+
require.NoError(t, err)
659+
660+
advanceToPublishedHtlc(t, ctx)
661+
662+
// Client starts listening for swap invoice updates.
663+
ctx.assertSubscribeInvoice(ctx.server.swapHash)
664+
665+
// Server has already paid invoice before spending the htlc. Signal
666+
// settled.
667+
ctx.updateInvoiceState(49000, invpkg.ContractSettled)
668+
669+
// Swap is expected to move to the state InvoiceSettled
670+
ctx.assertState(loopdb.StateInvoiceSettled)
671+
ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)
672+
673+
// The client requests to abandon the published htlc state.
674+
inSwap.abandonChan <- struct{}{}
675+
676+
// Ensure that the swap is now in the StateFailAbandoned state.
677+
ctx.assertState(loopdb.StateFailAbandoned)
678+
679+
// Ensure that the swap is also in the StateFailAbandoned state in the
680+
// database.
681+
ctx.store.assertLoopInState(loopdb.StateFailAbandoned)
682+
683+
// Ensure that the swap was abandoned and the execution stopped.
684+
err = <-ctx.errChan
685+
require.Error(t, err)
686+
require.Contains(t, err.Error(), "swap hash abandoned by client")
687+
688+
// We re-instantiate the swap and ensure that it does not progress.
689+
pendSwap := &loopdb.LoopIn{
690+
Contract: &inSwap.LoopInContract,
691+
Loop: loopdb.Loop{
692+
Events: []*loopdb.LoopEvent{
693+
{
694+
SwapStateData: loopdb.SwapStateData{
695+
State: inSwap.state,
696+
},
697+
},
698+
},
699+
Hash: testPreimage.Hash(),
700+
},
701+
}
702+
resumedSwap, err := resumeLoopInSwap(context.Background(), cfg, pendSwap)
703+
require.NoError(t, err)
704+
705+
// Execute the abandoned swap.
706+
go func() {
707+
err := resumedSwap.execute(
708+
context.Background(), ctx.cfg, height,
709+
)
710+
if err != nil {
711+
log.Error(err)
712+
}
713+
ctx.errChan <- err
714+
}()
715+
716+
// Ensure that the swap is still in the StateFailAbandoned state.
717+
swapInfo := <-ctx.statusChan
718+
require.Equal(t, loopdb.StateFailAbandoned, swapInfo.State)
719+
720+
// Ensure that the execution flagged the abandoned swap as finalized.
721+
err = <-ctx.errChan
722+
require.Error(t, err)
723+
require.Equal(t, ErrSwapFinalized, err)
724+
}
725+
726+
func advanceToPublishedHtlc(t *testing.T, ctx *loopInTestContext) SwapInfo {
727+
swapInfo := <-ctx.statusChan
728+
require.Equal(t, loopdb.StateInitiated, swapInfo.State)
729+
730+
ctx.assertState(loopdb.StateHtlcPublished)
731+
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
732+
733+
// Expect htlc to be published.
734+
htlcTx := <-ctx.lnd.SendOutputsChannel
735+
736+
// Expect the same state to be written again with the htlc tx hash
737+
// and on chain fee.
738+
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)
739+
740+
// Expect register for htlc conf (only one, since the htlc is p2tr).
741+
<-ctx.lnd.RegisterConfChannel
742+
743+
// Confirm htlc.
744+
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
745+
Tx: &htlcTx,
746+
}
747+
748+
// Client starts listening for spend of htlc.
749+
<-ctx.lnd.RegisterSpendChannel
750+
return swapInfo
751+
}
752+
753+
func startNewLoopIn(t *testing.T, ctx *loopInTestContext, height int32) (
754+
*swapConfig, error, *loopInSwap) {
755+
756+
cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)
757+
758+
req := &testLoopInRequest
759+
760+
initResult, err := newLoopInSwap(
761+
context.Background(), cfg,
762+
height, req,
763+
)
764+
require.NoError(t, err)
765+
766+
inSwap := initResult.swap
767+
768+
ctx.store.assertLoopInStored()
769+
770+
go func() {
771+
err := inSwap.execute(context.Background(), ctx.cfg, height)
772+
if err != nil {
773+
log.Error(err)
774+
}
775+
ctx.errChan <- err
776+
}()
777+
return cfg, err, inSwap
778+
}

loopin_testcontext_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type loopInTestContext struct {
2222
sweeper *sweep.Sweeper
2323
cfg *executeConfig
2424
statusChan chan SwapInfo
25+
errChan chan error
2526
blockEpochChan chan interface{}
2627

2728
swapInvoiceSubscription *test.SingleInvoiceSubscription
@@ -35,6 +36,7 @@ func newLoopInTestContext(t *testing.T) *loopInTestContext {
3536

3637
blockEpochChan := make(chan interface{})
3738
statusChan := make(chan SwapInfo)
39+
errChan := make(chan error)
3840

3941
expiryChan := make(chan time.Time)
4042
timerFactory := func(expiry time.Duration) <-chan time.Time {
@@ -57,6 +59,7 @@ func newLoopInTestContext(t *testing.T) *loopInTestContext {
5759
sweeper: &sweeper,
5860
cfg: &cfg,
5961
statusChan: statusChan,
62+
errChan: errChan,
6063
blockEpochChan: blockEpochChan,
6164
}
6265
}

testcontext_test.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -125,10 +125,7 @@ func createClientTestContext(t *testing.T,
125125
ctx.stop = stop
126126

127127
go func() {
128-
err := swapClient.Run(
129-
runCtx,
130-
statusChan,
131-
)
128+
err := swapClient.Run(runCtx, statusChan)
132129
log.Errorf("client run: %v", err)
133130
ctx.runErr <- err
134131
}()

0 commit comments

Comments
 (0)