From e7d409410de77b153fe53d7f5243bb3f0f5df7bb Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Thu, 9 Sep 2021 14:32:46 +0200 Subject: [PATCH] itest: add itest for sidecar cancellation --- itest/self_chan_balance_test.go | 3 +- itest/sidecar_channels_test.go | 186 ++++++++++++++++-- itest/test_harness.go | 48 ++++- ...test_list_off.go => test_list_off_test.go} | 0 .../{test_list_on.go => test_list_on_test.go} | 4 + 5 files changed, 217 insertions(+), 24 deletions(-) rename itest/{test_list_off.go => test_list_off_test.go} (100%) rename itest/{test_list_on.go => test_list_on_test.go} (96%) diff --git a/itest/self_chan_balance_test.go b/itest/self_chan_balance_test.go index 23f51967c..edb73581b 100644 --- a/itest/self_chan_balance_test.go +++ b/itest/self_chan_balance_test.go @@ -26,8 +26,7 @@ func testSelfChanBalance(t *harnessTest) { t.lndHarness.SendCoins(ctx, t.t, 5_000_000, charlie) // Create an account over 2M sats that is valid for the next 1000 blocks - // for both traders. To test the message multi-plexing between token IDs - // and accounts, we add a secondary account to the second trader. + // for both traders. makerAccount := openAccountAndAssert( t, t.trader, &poolrpc.InitAccountRequest{ AccountValue: defaultAccountValue, diff --git a/itest/sidecar_channels_test.go b/itest/sidecar_channels_test.go index f4a364866..5b17ce9ec 100644 --- a/itest/sidecar_channels_test.go +++ b/itest/sidecar_channels_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "fmt" + "testing" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcutil" @@ -37,8 +38,7 @@ func sidecarChannelsHappyPath(ctx context.Context, t *harnessTest, auto bool) { defer shutdownAndAssert(t, dave, recipientTrader) // Create an account over 2M sats that is valid for the next 1000 blocks - // for both traders. To test the message multi-plexing between token IDs - // and accounts, we add a secondary account to the second trader. + // for both traders. makerAccount := openAccountAndAssert( t, t.trader, &poolrpc.InitAccountRequest{ AccountValue: defaultAccountValue, @@ -73,7 +73,7 @@ func sidecarChannelsHappyPath(ctx context.Context, t *harnessTest, auto bool) { // Create the sidecar channel ticket now, including the offer, order and // channel expectation. - firstSidecarBid := makeSidecar( + firstSidecarBid, _ := makeSidecar( t, providerTrader, recipientTrader, providerAccount.TraderKey, orderFixedRate, askAmt, selfChanBalance, auto, ) @@ -178,7 +178,7 @@ func sidecarChannelsHappyPath(ctx context.Context, t *harnessTest, auto bool) { // Create the sidecar channel ticket now, including the offer, order and // channel expectation. - secondSidecarBid := makeSidecar( + secondSidecarBid, _ := makeSidecar( t, providerTrader, recipientTrader, providerAccount.TraderKey, orderFixedRate, askAmt, 0, auto, ) @@ -262,8 +262,7 @@ func testSidecarChannelsRejectNewNodesOnly(t *harnessTest) { defer shutdownAndAssert(t, dave, recipientTrader) // Create an account over 2M sats that is valid for the next 1000 blocks - // for both traders. To test the message multi-plexing between token IDs - // and accounts, we add a secondary account to the second trader. + // for both traders. makerAccount := openAccountAndAssert( t, t.trader, &poolrpc.InitAccountRequest{ AccountValue: defaultAccountValue, @@ -307,7 +306,7 @@ func testSidecarChannelsRejectNewNodesOnly(t *harnessTest) { // Create the sidecar channel ticket now, including the offer, order and // channel expectation. - firstSidecarBid := makeSidecar( + firstSidecarBid, _ := makeSidecar( t, providerTrader, recipientTrader, providerAccount.TraderKey, orderFixedRate, askAmt, 0, false, ) @@ -368,8 +367,7 @@ func testSidecarChannelsRejectMinChanSize(t *harnessTest) { defer shutdownAndAssert(t, dave, recipientTrader) // Create an account over 2M sats that is valid for the next 1000 blocks - // for both traders. To test the message multi-plexing between token IDs - // and accounts, we add a secondary account to the second trader. + // for both traders. makerAccount := openAccountAndAssert( t, t.trader, &poolrpc.InitAccountRequest{ AccountValue: defaultAccountValue, @@ -424,13 +422,141 @@ func testSidecarChannelsRejectMinChanSize(t *harnessTest) { _, _ = executeBatch(t, 0) } +// testSidecarTicketCancellation makes sure sidecar tickets can be canceled in +// the various states they can be in during their lifecycle. +func testSidecarTicketCancellation(t *harnessTest) { + ctx := context.Background() + + // We need a third and fourth lnd node for the additional participants. + // Charlie is the sidecar channel provider (has an account, submits the + // bid order) and Dave is the sidecar channel recipient (has no account + // and only receives the channel). + charlie := t.lndHarness.NewNode(t.t, "charlie", nil) + providerTrader := setupTraderHarness( + t.t, t.lndHarness.BackendCfg, charlie, t.auctioneer, + ) + defer shutdownAndAssert(t, charlie, providerTrader) + t.lndHarness.SendCoins(ctx, t.t, 5_000_000, charlie) + + dave := t.lndHarness.NewNode(t.t, "dave", nil) + recipientTrader := setupTraderHarness( + t.t, t.lndHarness.BackendCfg, dave, t.auctioneer, + ) + defer shutdownAndAssert(t, dave, recipientTrader) + + // Create an account over 2M sats that is valid for the next 1000 blocks + // for both traders. + makerAccount := openAccountAndAssert( + t, t.trader, &poolrpc.InitAccountRequest{ + AccountValue: defaultAccountValue, + AccountExpiry: &poolrpc.InitAccountRequest_RelativeHeight{ + RelativeHeight: 1_000, + }, + }, + ) + providerAccount := openAccountAndAssert( + t, providerTrader, &poolrpc.InitAccountRequest{ + AccountValue: defaultAccountValue, + AccountExpiry: &poolrpc.InitAccountRequest_RelativeHeight{ + RelativeHeight: 1_000, + }, + }, + ) + + // Now that the accounts are confirmed, submit an ask order from our + // maker, selling 200 units (200k sats) of liquidity. + const ( + orderFixedRate = 100 + askAmt = 200_000 + selfChanBalance = 100_000 + ) + _, err := submitAskOrder( + t.trader, makerAccount.TraderKey, orderFixedRate, askAmt, + func(ask *poolrpc.SubmitOrderRequest_Ask) { + ask.Ask.Version = uint32(orderT.VersionSidecarChannel) + }, + ) + require.NoError(t.t, err) + + // Create an offer from the provider account. + offerResp, err := providerTrader.OfferSidecar( + ctx, &poolrpc.OfferSidecarRequest{ + AutoNegotiate: false, + Bid: &poolrpc.Bid{ + Details: &poolrpc.Order{ + TraderKey: providerAccount.TraderKey, + Amt: uint64(askAmt), + }, + SelfChanBalance: selfChanBalance, + LeaseDurationBlocks: defaultOrderDuration, + }, + }, + ) + require.NoError(t.t, err) + + offeredTicket, err := sidecar.DecodeString(offerResp.Ticket) + require.NoError(t.t, err) + + // The offer should now be listed and in the correct state. + assertSidecarState( + t.t, providerTrader, 1, offeredTicket.ID[:], + offeredTicket.State, + ) + + // Let's cancel the ticket. + _, err = providerTrader.CancelSidecar( + ctx, &poolrpc.CancelSidecarRequest{ + SidecarId: offeredTicket.ID[:], + }, + ) + require.NoError(t.t, err) + + // Make sure the state is properly reflected. + assertSidecarState( + t.t, providerTrader, 1, offeredTicket.ID[:], + sidecar.StateCanceled, + ) + + // Create a full sidecar channel ticket now, including the offer, order + // and channel expectation. Then we cancel it on the offer side and + // check that the order is canceled as well. + sidecarBid, ticketID := makeSidecar( + t, providerTrader, recipientTrader, providerAccount.TraderKey, + orderFixedRate, askAmt, 0, true, + ) + assertSidecarState( + t.t, providerTrader, 2, ticketID, sidecar.StateExpectingChannel, + ) + assertSidecarState( + t.t, recipientTrader, 1, ticketID, + sidecar.StateExpectingChannel, + ) + + // Let's cancel the ticket. + _, err = providerTrader.CancelSidecar( + ctx, &poolrpc.CancelSidecarRequest{ + SidecarId: ticketID, + }, + ) + require.NoError(t.t, err) + assertSidecarState( + t.t, providerTrader, 2, ticketID, sidecar.StateCanceled, + ) + + // The order should be canceled as well. + assertBidOrderState( + t, providerTrader, sidecarBid, + auctioneerrpc.OrderState_ORDER_CANCELED, + ) +} + // makeSidecar creates a sidecar ticket with an offer on the provider node, // registers the offer with the recipient node, creates a bid order for the // provider and finally adds the channel expectation to the recipient's node. func makeSidecar(t *harnessTest, providerTrader, recipientTrader *traderHarness, providerAccountKey []byte, orderFixedRate uint32, // nolint:unparam askAmt btcutil.Amount, selfChanBalance uint64, // nolint:unparam - auto bool) orderT.Nonce { // nolint:unparam + auto bool) (orderT.Nonce, []byte) { ctx := context.Background() @@ -479,6 +605,8 @@ func makeSidecar(t *harnessTest, providerTrader, recipientTrader *traderHarness, }, ) require.NoError(t.t, err) + registeredTicket, err := sidecar.DecodeString(registerResp.Ticket) + require.NoError(t.t, err) // If we're using automated negotiation, then the last two steps will // be done automatically, so we can exit here. @@ -488,18 +616,13 @@ func makeSidecar(t *harnessTest, providerTrader, recipientTrader *traderHarness, if auto { // If this is an auto negotiated sidecar ticket, then we'll // wait for the sidecar responder to connect as a new trader. - registeredTicket, err := sidecar.DecodeString( - registerResp.Ticket, - ) - require.NoError(t.t, err) - assertSidecarTraderSubscribed( t, registeredTicket.Recipient.MultiSigPubKey, ) var bidNonce orderT.Nonce copy(bidNonce[:], registeredTicket.Order.BidNonce[:]) - return bidNonce + return bidNonce, registeredTicket.ID[:] } // Step 3/4 is for the provider to submit a bid order referencing the @@ -534,7 +657,7 @@ func makeSidecar(t *harnessTest, providerTrader, recipientTrader *traderHarness, ) require.NoError(t.t, err) - return bidNonce + return bidNonce, registeredTicket.ID[:] } // assertSidecarLease makes sure that the leased sidecar channel can be found @@ -629,3 +752,32 @@ func remoteActiveBalanceCheck(balance int64) activeChanCheck { return nil } } + +// assertSidecarState asserts the number of sidecar tickets and the state of a +// single ticket within the list. +func assertSidecarState(t *testing.T, trader *traderHarness, numTickets int, + id []byte, state sidecar.State) { + + t.Helper() + + sidecars, err := trader.ListSidecars( + context.Background(), &poolrpc.ListSidecarsRequest{}, + ) + require.NoError(t, err) + require.Len(t, sidecars.Tickets, numTickets) + + found := false + for _, ticket := range sidecars.Tickets { + if !bytes.Equal(ticket.Id, id) { + continue + } + + found = true + require.Equal(t, state.String(), ticket.State) + break + } + + if !found { + require.Fail(t, "ticket with ID %x not found", id) + } +} diff --git a/itest/test_harness.go b/itest/test_harness.go index da69ceea9..32597470a 100644 --- a/itest/test_harness.go +++ b/itest/test_harness.go @@ -1347,8 +1347,9 @@ func assertAskOrderState(t *harnessTest, trader *traderHarness, var orderFound bool for _, order := range resp.Asks { - if !bytes.Equal(order.Details.OrderNonce, - orderNonce[:]) { + if !bytes.Equal( + order.Details.OrderNonce, orderNonce[:], + ) { continue } @@ -1360,6 +1361,7 @@ func assertAskOrderState(t *harnessTest, trader *traderHarness, } orderFound = true + break } if !orderFound { @@ -1368,9 +1370,45 @@ func assertAskOrderState(t *harnessTest, trader *traderHarness, return nil }, defaultWaitTimeout) - if err != nil { - t.Fatalf("order state doesn't match: %v", err) - } + require.NoError(t.t, err) +} + +func assertBidOrderState(t *harnessTest, trader *traderHarness, + orderNonce orderT.Nonce, expectedState auctioneerrpc.OrderState) { + + err := wait.NoError(func() error { + req := &poolrpc.ListOrdersRequest{} + resp, err := trader.ListOrders(context.Background(), req) + if err != nil { + return err + } + + var orderFound bool + for _, order := range resp.Bids { + if !bytes.Equal( + order.Details.OrderNonce, orderNonce[:], + ) { + continue + } + + if order.Details.State != expectedState { + return fmt.Errorf("order has state %v, "+ + " expected %v", + order.Details.State, + expectedState) + } + + orderFound = true + break + } + + if !orderFound { + return fmt.Errorf("order not found") + } + + return nil + }, defaultWaitTimeout) + require.NoError(t.t, err) } // assertChannelClosed asserts that the channel is properly cleaned up after diff --git a/itest/test_list_off.go b/itest/test_list_off_test.go similarity index 100% rename from itest/test_list_off.go rename to itest/test_list_off_test.go diff --git a/itest/test_list_on.go b/itest/test_list_on_test.go similarity index 96% rename from itest/test_list_on.go rename to itest/test_list_on_test.go index 8afbd635a..19b92a3cd 100644 --- a/itest/test_list_on.go +++ b/itest/test_list_on_test.go @@ -104,6 +104,10 @@ var testCases = []*testCase{ name: "sidecar channels reject min chan size", test: testSidecarChannelsRejectMinChanSize, }, + { + name: "sidecar channels cancellation", + test: testSidecarTicketCancellation, + }, { name: "hashmail server",