Skip to content

Commit

Permalink
unit: abandon swap
Browse files Browse the repository at this point in the history
  • Loading branch information
hieblmi committed Nov 24, 2023
1 parent bd5f781 commit 06f3f35
Show file tree
Hide file tree
Showing 3 changed files with 207 additions and 0 deletions.
202 changes: 202 additions & 0 deletions loopin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -574,3 +574,205 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool,
cost.Server = btcutil.Amount(htlcTx.TxOut[0].Value) - amtPaid
require.Equal(t, cost, finalState.Cost)
}

// TestAbandonPublishedHtlcState advances a loop-in swap to StateHtlcPublished,
// then abandons it and ensures that executing the same swap would not progress.
func TestAbandonPublishedHtlcState(t *testing.T) {
defer test.Guard(t)()

ctx := newLoopInTestContext(t)

height := int32(600)

cfg, err, inSwap := startNewLoopIn(t, ctx, height)
require.NoError(t, err)

advanceToPublishedHtlc(t, ctx)

// The client requests to abandon the published htlc state.
inSwap.abandonChan <- struct{}{}

// Ensure that the swap is now in the StateFailAbandoned state.
ctx.assertState(loopdb.StateFailAbandoned)

// Ensure that the swap is also in the StateFailAbandoned state in the
// database.
ctx.store.assertLoopInState(loopdb.StateFailAbandoned)

// Ensure that the swap was abandoned and the execution stopped.
err = <-ctx.errChan
require.Error(t, err)
require.Contains(t, err.Error(), "swap hash abandoned by client")

// We re-instantiate the swap and ensure that it does not progress.
pendSwap := &loopdb.LoopIn{
Contract: &inSwap.LoopInContract,
Loop: loopdb.Loop{
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: inSwap.state,
},
},
},
Hash: testPreimage.Hash(),
},
}
resumedSwap, err := resumeLoopInSwap(
context.Background(), cfg, pendSwap,
)
require.NoError(t, err)

// Execute the abandoned swap.
go func() {
err := resumedSwap.execute(
context.Background(), ctx.cfg, height,
)
if err != nil {
log.Error(err)
}
ctx.errChan <- err
}()

// Ensure that the swap is still in the StateFailAbandoned state.
swapInfo := <-ctx.statusChan
require.Equal(t, loopdb.StateFailAbandoned, swapInfo.State)

// Ensure that the execution flagged the abandoned swap as finalized.
err = <-ctx.errChan
require.Error(t, err)
require.Equal(t, ErrSwapFinalized, err)
}

// TestAbandonSettledInvoiceState advances a loop-in swap to
// StateInvoiceSettled, then abandons it and ensures that executing the same
// swap would not progress.
func TestAbandonSettledInvoiceState(t *testing.T) {
defer test.Guard(t)()

ctx := newLoopInTestContext(t)

height := int32(600)

cfg, err, inSwap := startNewLoopIn(t, ctx, height)
require.NoError(t, err)

advanceToPublishedHtlc(t, ctx)

// Client starts listening for swap invoice updates.
ctx.assertSubscribeInvoice(ctx.server.swapHash)

// Server has already paid invoice before spending the htlc. Signal
// settled.
ctx.updateInvoiceState(49000, invpkg.ContractSettled)

// Swap is expected to move to the state InvoiceSettled
ctx.assertState(loopdb.StateInvoiceSettled)
ctx.store.assertLoopInState(loopdb.StateInvoiceSettled)

// The client requests to abandon the published htlc state.
inSwap.abandonChan <- struct{}{}

// Ensure that the swap is now in the StateFailAbandoned state.
ctx.assertState(loopdb.StateFailAbandoned)

// Ensure that the swap is also in the StateFailAbandoned state in the
// database.
ctx.store.assertLoopInState(loopdb.StateFailAbandoned)

// Ensure that the swap was abandoned and the execution stopped.
err = <-ctx.errChan
require.Error(t, err)
require.Contains(t, err.Error(), "swap hash abandoned by client")

// We re-instantiate the swap and ensure that it does not progress.
pendSwap := &loopdb.LoopIn{
Contract: &inSwap.LoopInContract,
Loop: loopdb.Loop{
Events: []*loopdb.LoopEvent{
{
SwapStateData: loopdb.SwapStateData{
State: inSwap.state,
},
},
},
Hash: testPreimage.Hash(),
},
}
resumedSwap, err := resumeLoopInSwap(context.Background(), cfg, pendSwap)
require.NoError(t, err)

// Execute the abandoned swap.
go func() {
err := resumedSwap.execute(
context.Background(), ctx.cfg, height,
)
if err != nil {
log.Error(err)
}
ctx.errChan <- err
}()

// Ensure that the swap is still in the StateFailAbandoned state.
swapInfo := <-ctx.statusChan
require.Equal(t, loopdb.StateFailAbandoned, swapInfo.State)

// Ensure that the execution flagged the abandoned swap as finalized.
err = <-ctx.errChan
require.Error(t, err)
require.Equal(t, ErrSwapFinalized, err)
}

func advanceToPublishedHtlc(t *testing.T, ctx *loopInTestContext) SwapInfo {
swapInfo := <-ctx.statusChan
require.Equal(t, loopdb.StateInitiated, swapInfo.State)

ctx.assertState(loopdb.StateHtlcPublished)
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)

// Expect htlc to be published.
htlcTx := <-ctx.lnd.SendOutputsChannel

// Expect the same state to be written again with the htlc tx hash
// and on chain fee.
ctx.store.assertLoopInState(loopdb.StateHtlcPublished)

// Expect register for htlc conf (only one, since the htlc is p2tr).
<-ctx.lnd.RegisterConfChannel

// Confirm htlc.
ctx.lnd.ConfChannel <- &chainntnfs.TxConfirmation{
Tx: &htlcTx,
}

// Client starts listening for spend of htlc.
<-ctx.lnd.RegisterSpendChannel
return swapInfo
}

func startNewLoopIn(t *testing.T, ctx *loopInTestContext, height int32) (
*swapConfig, error, *loopInSwap) {

cfg := newSwapConfig(&ctx.lnd.LndServices, ctx.store, ctx.server)

req := &testLoopInRequest

initResult, err := newLoopInSwap(
context.Background(), cfg,
height, req,
)
require.NoError(t, err)

inSwap := initResult.swap

ctx.store.assertLoopInStored()

go func() {
err := inSwap.execute(context.Background(), ctx.cfg, height)
if err != nil {
log.Error(err)
}
ctx.errChan <- err
}()
return cfg, err, inSwap
}
3 changes: 3 additions & 0 deletions loopin_testcontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ type loopInTestContext struct {
sweeper *sweep.Sweeper
cfg *executeConfig
statusChan chan SwapInfo
errChan chan error
blockEpochChan chan interface{}

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

blockEpochChan := make(chan interface{})
statusChan := make(chan SwapInfo)
errChan := make(chan error)

expiryChan := make(chan time.Time)
timerFactory := func(expiry time.Duration) <-chan time.Time {
Expand All @@ -57,6 +59,7 @@ func newLoopInTestContext(t *testing.T) *loopInTestContext {
sweeper: &sweeper,
cfg: &cfg,
statusChan: statusChan,
errChan: errChan,
blockEpochChan: blockEpochChan,
}
}
Expand Down
2 changes: 2 additions & 0 deletions testcontext_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func createClientTestContext(t *testing.T,
})

statusChan := make(chan SwapInfo)
abandonChans := make(map[lntypes.Hash]chan struct{})

ctx := &testContext{
Context: test.NewContext(
Expand All @@ -128,6 +129,7 @@ func createClientTestContext(t *testing.T,
err := swapClient.Run(
runCtx,
statusChan,
abandonChans,
)
log.Errorf("client run: %v", err)
ctx.runErr <- err
Expand Down

0 comments on commit 06f3f35

Please sign in to comment.