From cae72b5848f088a118e64d828783cd43a9e3efd4 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 7 Jun 2021 10:56:34 +0200 Subject: [PATCH 1/3] loopin/test: move invoice updates to loopin test context --- loopin_test.go | 37 +++++++++---------------------------- loopin_testcontext_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 28 deletions(-) diff --git a/loopin_test.go b/loopin_test.go index 8189558..93b6454 100644 --- a/loopin_test.go +++ b/loopin_test.go @@ -4,7 +4,6 @@ import ( "context" "testing" - "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/swap" "github.com/lightninglabs/loop/test" @@ -89,17 +88,11 @@ func TestLoopInSuccess(t *testing.T) { <-ctx.lnd.RegisterSpendChannel // Client starts listening for swap invoice updates. - subscription := <-ctx.lnd.SingleInvoiceSubcribeChannel - if subscription.Hash != ctx.server.swapHash { - t.Fatal("client subscribing to wrong invoice") - } + ctx.assertSubscribeInvoice(ctx.server.swapHash) // Server has already paid invoice before spending the htlc. Signal // settled. - subscription.Update <- lndclient.InvoiceUpdate{ - State: channeldb.ContractSettled, - AmtPaid: 49000, - } + ctx.updateInvoiceState(49000, channeldb.ContractSettled) // Swap is expected to move to the state InvoiceSettled ctx.assertState(loopdb.StateInvoiceSettled) @@ -257,10 +250,7 @@ func testLoopInTimeout(t *testing.T, <-ctx.lnd.RegisterSpendChannel // Client starts listening for swap invoice updates. - subscription := <-ctx.lnd.SingleInvoiceSubcribeChannel - if subscription.Hash != ctx.server.swapHash { - t.Fatal("client subscribing to wrong invoice") - } + ctx.assertSubscribeInvoice(ctx.server.swapHash) // Let htlc expire. ctx.blockEpochChan <- s.LoopInContract.CltvExpiry @@ -294,10 +284,8 @@ func testLoopInTimeout(t *testing.T, // safely cancel the swap invoice. <-ctx.lnd.FailInvoiceChannel - // Signal the the invoice was canceled. - subscription.Update <- lndclient.InvoiceUpdate{ - State: channeldb.ContractCanceled, - } + // Signal that the invoice was canceled. + ctx.updateInvoiceState(0, channeldb.ContractCanceled) ctx.assertState(loopdb.StateFailTimeout) state := ctx.store.assertLoopInState(loopdb.StateFailTimeout) @@ -501,18 +489,12 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool, <-ctx.lnd.RegisterSpendChannel // Client starts listening for swap invoice updates. - subscription := <-ctx.lnd.SingleInvoiceSubcribeChannel - if subscription.Hash != testPreimage.Hash() { - t.Fatal("client subscribing to wrong invoice") - } + ctx.assertSubscribeInvoice(testPreimage.Hash()) // Server has already paid invoice before spending the htlc. Signal // settled. - invoiceUpdate := lndclient.InvoiceUpdate{ - State: channeldb.ContractSettled, - AmtPaid: 49000, - } - subscription.Update <- invoiceUpdate + amtPaid := btcutil.Amount(49000) + ctx.updateInvoiceState(amtPaid, channeldb.ContractSettled) // Swap is expected to move to the state InvoiceSettled ctx.assertState(loopdb.StateInvoiceSettled) @@ -537,7 +519,6 @@ func testLoopInResume(t *testing.T, state loopdb.SwapState, expired bool, // We expect our server fee to reflect as the difference between htlc // value and invoice amount paid. We use our original on-chain cost, set // earlier in the test, because we expect this value to be unchanged. - cost.Server = btcutil.Amount(htlcTx.TxOut[0].Value) - - invoiceUpdate.AmtPaid + cost.Server = btcutil.Amount(htlcTx.TxOut[0].Value) - amtPaid require.Equal(t, cost, finalState.Cost) } diff --git a/loopin_testcontext_test.go b/loopin_testcontext_test.go index 1af542d..7f0c0a8 100644 --- a/loopin_testcontext_test.go +++ b/loopin_testcontext_test.go @@ -4,9 +4,14 @@ import ( "testing" "time" + "github.com/btcsuite/btcutil" + "github.com/lightninglabs/lndclient" "github.com/lightninglabs/loop/loopdb" "github.com/lightninglabs/loop/sweep" "github.com/lightninglabs/loop/test" + "github.com/lightningnetwork/lnd/channeldb" + "github.com/lightningnetwork/lnd/lntypes" + "github.com/stretchr/testify/require" ) type loopInTestContext struct { @@ -18,6 +23,8 @@ type loopInTestContext struct { cfg *executeConfig statusChan chan SwapInfo blockEpochChan chan interface{} + + swapInvoiceSubscription *test.SingleInvoiceSubscription } func newLoopInTestContext(t *testing.T) *loopInTestContext { @@ -61,3 +68,20 @@ func (c *loopInTestContext) assertState(expectedState loopdb.SwapState) { state.State) } } + +// assertSubscribeInvoice asserts that the client subscribes to invoice updates +// for our swap invoice. +func (c *loopInTestContext) assertSubscribeInvoice(hash lntypes.Hash) { + c.swapInvoiceSubscription = <-c.lnd.SingleInvoiceSubcribeChannel + require.Equal(c.t, hash, c.swapInvoiceSubscription.Hash) +} + +// updateInvoiceState mocks an update to our swap invoice state. +func (c *loopInTestContext) updateInvoiceState(amount btcutil.Amount, + state channeldb.ContractState) { + + c.swapInvoiceSubscription.Update <- lndclient.InvoiceUpdate{ + AmtPaid: amount, + State: state, + } +} From 7dca93fd88efe16d09de23e9ee3f1d295431d6c3 Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 7 Jun 2021 10:56:35 +0200 Subject: [PATCH 2/3] go.mod: bump to lndclient version that handles EOF --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 0354f4a..d7da53d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway v1.14.3 github.com/jessevdk/go-flags v1.4.0 github.com/lightninglabs/aperture v0.1.6-beta - github.com/lightninglabs/lndclient v0.11.1-6 + github.com/lightninglabs/lndclient v0.11.1-9 github.com/lightninglabs/protobuf-hex-display v1.4.3-hex-display github.com/lightningnetwork/lnd v0.13.0-beta.rc2 github.com/lightningnetwork/lnd/cert v1.0.3 diff --git a/go.sum b/go.sum index de84452..d92cce7 100644 --- a/go.sum +++ b/go.sum @@ -230,8 +230,8 @@ github.com/lightninglabs/aperture v0.1.6-beta/go.mod h1:9xl4mx778ZAzrB87nLHMqk+X github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc= github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/lndclient v0.11.0-4/go.mod h1:8/cTKNwgL87NX123gmlv3Xh6p1a7pvzu+40Un3PhHiI= -github.com/lightninglabs/lndclient v0.11.1-6 h1:2L+0GIjAShSWsxEsRQ/ZbK+62SF4DBiCpnWBx8HSwyA= -github.com/lightninglabs/lndclient v0.11.1-6/go.mod h1:qVFcrIXxsagpu3RC0SSSdVEs3QVxuv5YiHUYwDauUnc= +github.com/lightninglabs/lndclient v0.11.1-9 h1:KTrCkOnqqP1gCsou0D7k7TOOC7HLboKDS35YREN7a3s= +github.com/lightninglabs/lndclient v0.11.1-9/go.mod h1:qVFcrIXxsagpu3RC0SSSdVEs3QVxuv5YiHUYwDauUnc= github.com/lightninglabs/neutrino v0.11.0/go.mod h1:CuhF0iuzg9Sp2HO6ZgXgayviFTn1QHdSTJlMncK80wg= github.com/lightninglabs/neutrino v0.11.1-0.20200316235139-bffc52e8f200/go.mod h1:MlZmoKa7CJP3eR1s5yB7Rm5aSyadpKkxqAwLQmog7N0= github.com/lightninglabs/neutrino v0.12.1 h1:9umzk5kKNc/l3bAyak8ClSRP1qSulnjc6kppLYDnuqk= From 0e72c2bf92dca3d8cf94278292405f613c933a4a Mon Sep 17 00:00:00 2001 From: carla Date: Mon, 7 Jun 2021 10:56:36 +0200 Subject: [PATCH 3/3] loopin: handle EOF case for SubscribeSingleInvoice From lnd 0.13.0, the SubscribeSingleInvoice rpc will return an EOF once it has served a final state to the stream. This is handled in our lndclient wrapper by closing the channels that we send updates/ errors on. When we are exclusively consuming updates from these streams, we don't need to handle this case because we will receive our final update and exit. However, in the case where we continue to listen on the update channels after consuming the final update, we need to handle this EOF/closed channels case. This is done by setting the channels to nil after they're closed so that we no longer select on them but can continue waiting for our other cases to complete. We have similar handling in loopout's waitForHtlcSpendConfirmed. --- loopin.go | 22 ++++++++++++++++++++-- loopin_testcontext_test.go | 9 +++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/loopin.go b/loopin.go index e2bbe6c..7a76593 100644 --- a/loopin.go +++ b/loopin.go @@ -779,12 +779,30 @@ func (s *loopInSwap) waitForSwapComplete(ctx context.Context, htlcSpend = true // Swap invoice ntfn error. - case err := <-swapInvoiceErr: + case err, ok := <-swapInvoiceErr: + // If the channel has been closed, the server has + // finished sending updates, so we set the channel to + // nil because we don't want to constantly select this + // case. + if !ok { + swapInvoiceErr = nil + continue + } + return err // An update to the swap invoice occurred. Check the new state // and update the swap state accordingly. - case update := <-swapInvoiceChan: + case update, ok := <-swapInvoiceChan: + // If the channel has been closed, the server has + // finished sending updates, so we set the channel to + // nil because we don't want to constantly select this + // case. + if !ok { + swapInvoiceChan = nil + continue + } + s.log.Infof("Received swap invoice update: %v", update.State) diff --git a/loopin_testcontext_test.go b/loopin_testcontext_test.go index 7f0c0a8..e029c15 100644 --- a/loopin_testcontext_test.go +++ b/loopin_testcontext_test.go @@ -84,4 +84,13 @@ func (c *loopInTestContext) updateInvoiceState(amount btcutil.Amount, AmtPaid: amount, State: state, } + + // If we're in a final state, close our update channels as lndclient + // would. + if state == channeldb.ContractCanceled || + state == channeldb.ContractSettled { + + close(c.swapInvoiceSubscription.Update) + close(c.swapInvoiceSubscription.Err) + } }