diff --git a/lntest/itest/lnd_routing_test.go b/lntest/itest/lnd_routing_test.go new file mode 100644 index 00000000000..7458338b47c --- /dev/null +++ b/lntest/itest/lnd_routing_test.go @@ -0,0 +1,2285 @@ +package itest + +import ( + "bytes" + "context" + "encoding/hex" + "fmt" + "strings" + "testing" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd/chainreg" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lnrpc/routerrpc" + "github.com/lightningnetwork/lnd/lntest" + "github.com/lightningnetwork/lnd/lntest/wait" + "github.com/lightningnetwork/lnd/lnwire" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" +) + +type singleHopSendToRouteCase struct { + name string + + // streaming tests streaming SendToRoute if true, otherwise tests + // synchronous SenToRoute. + streaming bool + + // routerrpc submits the request to the routerrpc subserver if true, + // otherwise submits to the main rpc server. + routerrpc bool +} + +var singleHopSendToRouteCases = []singleHopSendToRouteCase{ + { + name: "regular main sync", + }, + { + name: "regular main stream", + streaming: true, + }, + { + name: "regular routerrpc sync", + routerrpc: true, + }, + { + name: "mpp main sync", + }, + { + name: "mpp main stream", + streaming: true, + }, + { + name: "mpp routerrpc sync", + routerrpc: true, + }, +} + +// testSingleHopSendToRoute tests that payments are properly processed through a +// provided route with a single hop. We'll create the following network +// topology: +// Carol --100k--> Dave +// We'll query the daemon for routes from Carol to Dave and then send payments +// by feeding the route back into the various SendToRoute RPC methods. Here we +// test all three SendToRoute endpoints, forcing each to perform both a regular +// payment and an MPP payment. +func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { + for _, test := range singleHopSendToRouteCases { + test := test + + t.t.Run(test.name, func(t1 *testing.T) { + ht := newHarnessTest(t1, t.lndHarness) + ht.RunTestCase(&testCase{ + name: test.name, + test: func(_ *lntest.NetworkHarness, tt *harnessTest) { + testSingleHopSendToRouteCase(net, tt, test) + }, + }) + }) + } +} + +func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, + test singleHopSendToRouteCase) { + + const chanAmt = btcutil.Amount(100000) + const paymentAmtSat = 1000 + const numPayments = 5 + const amountPaid = int64(numPayments * paymentAmtSat) + + ctxb := context.Background() + var networkChans []*lnrpc.ChannelPoint + + // Create Carol and Dave, then establish a channel between them. Carol + // is the sole funder of the channel with 100k satoshis. The network + // topology should look like: + // Carol -> 100k -> Dave + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + dave, err := net.NewNode("Dave", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, dave) + + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + + // Open a channel with 100k satoshis between Carol and Dave with Carol + // being the sole funder of the channel. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointCarol) + + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{carol, dave} + for _, chanPoint := range networkChans { + for _, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", node.Name(), + node.NodeID, point, err) + } + } + } + + // Create invoices for Dave, which expect a payment from Carol. + payReqs, rHashes, _, err := createPayReqs( + dave, paymentAmtSat, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + // Reconstruct payment addresses. + var payAddrs [][]byte + for _, payReq := range payReqs { + ctx, _ := context.WithTimeout( + context.Background(), defaultTimeout, + ) + resp, err := dave.DecodePayReq( + ctx, + &lnrpc.PayReqString{PayReq: payReq}, + ) + if err != nil { + t.Fatalf("decode pay req: %v", err) + } + payAddrs = append(payAddrs, resp.PaymentAddr) + } + + // Assert Carol and Dave are synced to the chain before proceeding, to + // ensure the queried route will have a valid final CLTV once the HTLC + // reaches Dave. + _, minerHeight, err := net.Miner.Client.GetBestBlock() + if err != nil { + t.Fatalf("unable to get best height: %v", err) + } + ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) + defer cancel() + require.NoError(t.t, waitForNodeBlockHeight(ctxt, carol, minerHeight)) + require.NoError(t.t, waitForNodeBlockHeight(ctxt, dave, minerHeight)) + + // Query for routes to pay from Carol to Dave using the default CLTV + // config. + routesReq := &lnrpc.QueryRoutesRequest{ + PubKey: dave.PubKeyStr, + Amt: paymentAmtSat, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routes, err := carol.QueryRoutes(ctxt, routesReq) + if err != nil { + t.Fatalf("unable to get route from %s: %v", + carol.Name(), err) + } + + // There should only be one route to try, so take the first item. + r := routes.Routes[0] + + // Construct a closure that will set MPP fields on the route, which + // allows us to test MPP payments. + setMPPFields := func(i int) { + hop := r.Hops[len(r.Hops)-1] + hop.TlvPayload = true + hop.MppRecord = &lnrpc.MPPRecord{ + PaymentAddr: payAddrs[i], + TotalAmtMsat: paymentAmtSat * 1000, + } + } + + // Construct closures for each of the payment types covered: + // - main rpc server sync + // - main rpc server streaming + // - routerrpc server sync + sendToRouteSync := func() { + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: r, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.SendToRouteSync( + ctxt, sendReq, + ) + if err != nil { + t.Fatalf("unable to send to route for "+ + "%s: %v", carol.Name(), err) + } + if resp.PaymentError != "" { + t.Fatalf("received payment error from %s: %v", + carol.Name(), resp.PaymentError) + } + } + } + sendToRouteStream := func() { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + alicePayStream, err := carol.SendToRoute(ctxt) // nolint:staticcheck + if err != nil { + t.Fatalf("unable to create payment stream for "+ + "carol: %v", err) + } + + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: routes.Routes[0], + } + err := alicePayStream.Send(sendReq) + + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + resp, err := alicePayStream.Recv() + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + if resp.PaymentError != "" { + t.Fatalf("received payment error: %v", + resp.PaymentError) + } + } + } + sendToRouteRouterRPC := func() { + for i, rHash := range rHashes { + setMPPFields(i) + + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: r, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.RouterClient.SendToRouteV2( + ctxt, sendReq, + ) + if err != nil { + t.Fatalf("unable to send to route for "+ + "%s: %v", carol.Name(), err) + } + if resp.Failure != nil { + t.Fatalf("received payment error from %s: %v", + carol.Name(), resp.Failure) + } + } + } + + // Using Carol as the node as the source, send the payments + // synchronously via the the routerrpc's SendToRoute, or via the main RPC + // server's SendToRoute streaming or sync calls. + switch { + case !test.routerrpc && test.streaming: + sendToRouteStream() + case !test.routerrpc && !test.streaming: + sendToRouteSync() + case test.routerrpc && !test.streaming: + sendToRouteRouterRPC() + default: + t.Fatalf("routerrpc does not support streaming send_to_route") + } + + // Verify that the payment's from Carol's PoV have the correct payment + // hash and amount. + ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) + paymentsResp, err := carol.ListPayments( + ctxt, &lnrpc.ListPaymentsRequest{}, + ) + if err != nil { + t.Fatalf("error when obtaining %s payments: %v", + carol.Name(), err) + } + if len(paymentsResp.Payments) != numPayments { + t.Fatalf("incorrect number of payments, got %v, want %v", + len(paymentsResp.Payments), numPayments) + } + + for i, p := range paymentsResp.Payments { + // Assert that the payment hashes for each payment match up. + rHashHex := hex.EncodeToString(rHashes[i]) + if p.PaymentHash != rHashHex { + t.Fatalf("incorrect payment hash for payment %d, "+ + "want: %s got: %s", + i, rHashHex, p.PaymentHash) + } + + // Assert that each payment has no invoice since the payment was + // completed using SendToRoute. + if p.PaymentRequest != "" { + t.Fatalf("incorrect payment request for payment: %d, "+ + "want: \"\", got: %s", + i, p.PaymentRequest) + } + + // Assert the payment amount is correct. + if p.ValueSat != paymentAmtSat { + t.Fatalf("incorrect payment amt for payment %d, "+ + "want: %d, got: %d", + i, paymentAmtSat, p.ValueSat) + } + + // Assert exactly one htlc was made. + if len(p.Htlcs) != 1 { + t.Fatalf("expected 1 htlc for payment %d, got: %d", + i, len(p.Htlcs)) + } + + // Assert the htlc's route is populated. + htlc := p.Htlcs[0] + if htlc.Route == nil { + t.Fatalf("expected route for payment %d", i) + } + + // Assert the hop has exactly one hop. + if len(htlc.Route.Hops) != 1 { + t.Fatalf("expected 1 hop for payment %d, got: %d", + i, len(htlc.Route.Hops)) + } + + // If this is an MPP test, assert the MPP record's fields are + // properly populated. Otherwise the hop should not have an MPP + // record. + hop := htlc.Route.Hops[0] + if hop.MppRecord == nil { + t.Fatalf("expected mpp record for mpp payment") + } + + if hop.MppRecord.TotalAmtMsat != paymentAmtSat*1000 { + t.Fatalf("incorrect mpp total msat for payment %d "+ + "want: %d, got: %d", + i, paymentAmtSat*1000, + hop.MppRecord.TotalAmtMsat) + } + + expAddr := payAddrs[i] + if !bytes.Equal(hop.MppRecord.PaymentAddr, expAddr) { + t.Fatalf("incorrect mpp payment addr for payment %d "+ + "want: %x, got: %x", + i, expAddr, hop.MppRecord.PaymentAddr) + } + } + + // Verify that the invoices's from Dave's PoV have the correct payment + // hash and amount. + ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) + invoicesResp, err := dave.ListInvoices( + ctxt, &lnrpc.ListInvoiceRequest{}, + ) + if err != nil { + t.Fatalf("error when obtaining %s payments: %v", + dave.Name(), err) + } + if len(invoicesResp.Invoices) != numPayments { + t.Fatalf("incorrect number of invoices, got %v, want %v", + len(invoicesResp.Invoices), numPayments) + } + + for i, inv := range invoicesResp.Invoices { + // Assert that the payment hashes match up. + if !bytes.Equal(inv.RHash, rHashes[i]) { + t.Fatalf("incorrect payment hash for invoice %d, "+ + "want: %x got: %x", + i, rHashes[i], inv.RHash) + } + + // Assert that the amount paid to the invoice is correct. + if inv.AmtPaidSat != paymentAmtSat { + t.Fatalf("incorrect payment amt for invoice %d, "+ + "want: %d, got %d", + i, paymentAmtSat, inv.AmtPaidSat) + } + } + + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Dave, the sink within the + // payment flow generated above. The order of asserts corresponds to + // increasing of time is needed to embed the HTLC in commitment + // transaction, in channel Carol->Dave, order is Dave and then Carol. + assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, + carolFundPoint, amountPaid, int64(0)) + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// testMultiHopSendToRoute tests that payments are properly processed +// through a provided route. We'll create the following network topology: +// Alice --100k--> Bob --100k--> Carol +// We'll query the daemon for routes from Alice to Carol and then +// send payments through the routes. +func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + var networkChans []*lnrpc.ChannelPoint + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointAlice) + + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // Create Carol and establish a channel from Bob. Bob is the sole funder + // of the channel with 100k satoshis. The network topology should look like: + // Alice -> Bob -> Carol + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, net.Bob) + if err != nil { + t.Fatalf("unable to send coins to bob: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointBob) + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + bobFundPoint := wire.OutPoint{ + Hash: *bobChanTXID, + Index: chanPointBob.OutputIndex, + } + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol} + nodeNames := []string{"Alice", "Bob", "Carol"} + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, point, err) + } + } + } + + // Create 5 invoices for Carol, which expect a payment from Alice for 1k + // satoshis with a different preimage each time. + const ( + numPayments = 5 + paymentAmt = 1000 + ) + _, rHashes, invoices, err := createPayReqs( + carol, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + // Construct a route from Alice to Carol for each of the invoices + // created above. We set FinalCltvDelta to 40 since by default + // QueryRoutes returns the last hop with a final cltv delta of 9 where + // as the default in htlcswitch is 40. + routesReq := &lnrpc.QueryRoutesRequest{ + PubKey: carol.PubKeyStr, + Amt: paymentAmt, + FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routes, err := net.Alice.QueryRoutes(ctxt, routesReq) + if err != nil { + t.Fatalf("unable to get route: %v", err) + } + + // We'll wait for all parties to recognize the new channels within the + // network. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("bob didn't advertise his channel in time: %v", err) + } + + time.Sleep(time.Millisecond * 50) + + // Using Alice as the source, pay to the 5 invoices from Carol created + // above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + + for i, rHash := range rHashes { + // Manually set the MPP payload a new for each payment since + // the payment addr will change with each invoice, although we + // can re-use the route itself. + route := *routes.Routes[0] + route.Hops[len(route.Hops)-1].TlvPayload = true + route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{ + PaymentAddr: invoices[i].PaymentAddr, + TotalAmtMsat: int64( + lnwire.NewMSatFromSatoshis(paymentAmt), + ), + } + + sendReq := &routerrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: &route, + } + resp, err := net.Alice.RouterClient.SendToRouteV2(ctxt, sendReq) + if err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + if resp.Failure != nil { + t.Fatalf("received payment error: %v", resp.Failure) + } + } + + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when + // creating the seed nodes in the network. + const baseFee = 1 + + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Carol, the sink within the + // payment flow generated above. The order of asserts corresponds to + // increasing of time is needed to embed the HTLC in commitment + // transaction, in channel Alice->Bob->Carol, order is Carol, Bob, + // Alice. + const amountPaid = int64(5000) + assertAmountPaid(t, "Bob(local) => Carol(remote)", carol, + bobFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Bob(local) => Carol(remote)", net.Bob, + bobFundPoint, amountPaid, int64(0)) + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, + aliceFundPoint, int64(0), amountPaid+(baseFee*numPayments)) + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, + aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0)) + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointBob, false) +} + +// testSendToRouteErrorPropagation tests propagation of errors that occur +// while processing a multi-hop payment through an unknown route. +func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("alice didn't advertise her channel: %v", err) + } + + // Create a new nodes (Carol and Charlie), load her with some funds, + // then establish a connection between Carol and Charlie with a channel + // that has identical capacity to the one created above.Then we will + // get route via queryroutes call which will be fake route for Alice -> + // Bob graph. + // + // The network topology should now look like: Alice -> Bob; Carol -> Charlie. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + + charlie, err := net.NewNode("Charlie", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, charlie) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, charlie) + if err != nil { + t.Fatalf("unable to send coins to charlie: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, charlie); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, charlie, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't advertise her channel: %v", err) + } + + // Query routes from Carol to Charlie which will be an invalid route + // for Alice -> Bob. + fakeReq := &lnrpc.QueryRoutesRequest{ + PubKey: charlie.PubKeyStr, + Amt: int64(1), + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq) + if err != nil { + t.Fatalf("unable get fake route: %v", err) + } + + // Create 1 invoices for Bob, which expect a payment from Alice for 1k + // satoshis + const paymentAmt = 1000 + + invoice := &lnrpc.Invoice{ + Memo: "testing", + Value: paymentAmt, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := net.Bob.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to add invoice: %v", err) + } + + rHash := resp.RHash + + // Using Alice as the source, pay to the 5 invoices from Bob created above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + alicePayStream, err := net.Alice.SendToRoute(ctxt) // nolint:staticcheck + if err != nil { + t.Fatalf("unable to create payment stream for alice: %v", err) + } + + sendReq := &lnrpc.SendToRouteRequest{ + PaymentHash: rHash, + Route: fakeRoute.Routes[0], + } + + if err := alicePayStream.Send(sendReq); err != nil { + t.Fatalf("unable to send payment: %v", err) + } + + // At this place we should get an rpc error with notification + // that edge is not found on hop(0) + if _, err := alicePayStream.Recv(); err != nil && strings.Contains(err.Error(), + "edge not found") { + + } else if err != nil { + t.Fatalf("payment stream has been closed but fake route has consumed: %v", err) + } + + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// testPrivateChannels tests that a private channel can be used for +// routing by the two endpoints of the channel, but is not known by +// the rest of the nodes in the graph. +func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + var networkChans []*lnrpc.ChannelPoint + + // We create the following topology: + // + // Dave --100k--> Alice --200k--> Bob + // ^ ^ + // | | + // 100k 100k + // | | + // +---- Carol ----+ + // + // where the 100k channel between Carol and Alice is private. + + // Open a channel with 200k satoshis between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt * 2, + }, + ) + networkChans = append(networkChans, chanPointAlice) + + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // Create Dave, and a channel to Alice of 100k. + dave, err := net.NewNode("Dave", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to connect dave to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, dave) + if err != nil { + t.Fatalf("unable to send coins to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointDave := openChannelAndAssert( + ctxt, t, net, dave, net.Alice, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + daveFundPoint := wire.OutPoint{ + Hash: *daveChanTXID, + Index: chanPointDave.OutputIndex, + } + + // Next, we'll create Carol and establish a channel from her to + // Dave of 100k. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointCarol) + + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Wait for all nodes to have seen all these channels, as they + // are all public. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, point, err) + } + } + } + // Now create a _private_ channel directly between Carol and + // Alice of 100k. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanOpenUpdate := openChannelStream( + ctxt, t, net, carol, net.Alice, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + if err != nil { + t.Fatalf("unable to open channel: %v", err) + } + + // One block is enough to make the channel ready for use, since the + // nodes have defaultNumConfs=1 set. + block := mineBlocks(t, net, 1, 1)[0] + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanPointPrivate, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) + if err != nil { + t.Fatalf("error while waiting for channel open: %v", err) + } + fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPointPrivate) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + assertTxInBlock(t, block, fundingTxID) + + // The channel should be listed in the peer information returned by + // both peers. + privateFundPoint := wire.OutPoint{ + Hash: *fundingTxID, + Index: chanPointPrivate.OutputIndex, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.AssertChannelExists(ctxt, carol, &privateFundPoint) + if err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.AssertChannelExists(ctxt, net.Alice, &privateFundPoint) + if err != nil { + t.Fatalf("unable to assert channel existence: %v", err) + } + + // The channel should be available for payments between Carol and Alice. + // We check this by sending payments from Carol to Bob, that + // collectively would deplete at least one of Carol's channels. + + // Create 2 invoices for Bob, each of 70k satoshis. Since each of + // Carol's channels is of size 100k, these payments cannot succeed + // by only using one of the channels. + const numPayments = 2 + const paymentAmt = 70000 + payReqs, _, _, err := createPayReqs( + net.Bob, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + time.Sleep(time.Millisecond * 50) + + // Let Carol pay the invoices. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, carol, carol.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) + } + + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when + // creating the seed nodes in the network. + const baseFee = 1 + + // Bob should have received 140k satoshis from Alice. + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, + aliceFundPoint, int64(0), 2*paymentAmt) + + // Alice sent 140k to Bob. + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, + aliceFundPoint, 2*paymentAmt, int64(0)) + + // Alice received 70k + fee from Dave. + assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, + daveFundPoint, int64(0), paymentAmt+baseFee) + + // Dave sent 70k+fee to Alice. + assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, + daveFundPoint, paymentAmt+baseFee, int64(0)) + + // Dave received 70k+fee of two hops from Carol. + assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, + carolFundPoint, int64(0), paymentAmt+baseFee*2) + + // Carol sent 70k+fee of two hops to Dave. + assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, + carolFundPoint, paymentAmt+baseFee*2, int64(0)) + + // Alice received 70k+fee from Carol. + assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", + net.Alice, privateFundPoint, int64(0), paymentAmt+baseFee) + + // Carol sent 70k+fee to Alice. + assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", + carol, privateFundPoint, paymentAmt+baseFee, int64(0)) + + // Alice should also be able to route payments using this channel, + // so send two payments of 60k back to Carol. + const paymentAmt60k = 60000 + payReqs, _, _, err = createPayReqs( + carol, paymentAmt60k, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + time.Sleep(time.Millisecond * 50) + + // Let Bob pay the invoices. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments: %v", err) + } + + // Finally, we make sure Dave and Bob does not know about the + // private channel between Carol and Alice. We first mine + // plenty of blocks, such that the channel would have been + // announced in case it was public. + mineBlocks(t, net, 10, 0) + + // We create a helper method to check how many edges each of the + // nodes know about. Carol and Alice should know about 4, while + // Bob and Dave should only know about 3, since one channel is + // private. + numChannels := func(node *lntest.HarnessNode, includeUnannounced bool) int { + req := &lnrpc.ChannelGraphRequest{ + IncludeUnannounced: includeUnannounced, + } + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err := node.DescribeGraph(ctxt, req) + if err != nil { + t.Fatalf("unable go describegraph: %v", err) + } + return len(chanGraph.Edges) + } + + var predErr error + err = wait.Predicate(func() bool { + aliceChans := numChannels(net.Alice, true) + if aliceChans != 4 { + predErr = fmt.Errorf("expected Alice to know 4 edges, "+ + "had %v", aliceChans) + return false + } + alicePubChans := numChannels(net.Alice, false) + if alicePubChans != 3 { + predErr = fmt.Errorf("expected Alice to know 3 public edges, "+ + "had %v", alicePubChans) + return false + } + bobChans := numChannels(net.Bob, true) + if bobChans != 3 { + predErr = fmt.Errorf("expected Bob to know 3 edges, "+ + "had %v", bobChans) + return false + } + carolChans := numChannels(carol, true) + if carolChans != 4 { + predErr = fmt.Errorf("expected Carol to know 4 edges, "+ + "had %v", carolChans) + return false + } + carolPubChans := numChannels(carol, false) + if carolPubChans != 3 { + predErr = fmt.Errorf("expected Carol to know 3 public edges, "+ + "had %v", carolPubChans) + return false + } + daveChans := numChannels(dave, true) + if daveChans != 3 { + predErr = fmt.Errorf("expected Dave to know 3 edges, "+ + "had %v", daveChans) + return false + } + return true + }, defaultTimeout) + if err != nil { + t.Fatalf("%v", predErr) + } + + // Close all channels. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointPrivate, false) +} + +// testUpdateChannelPolicyForPrivateChannel tests when a private channel +// updates its channel edge policy, we will use the updated policy to send our +// payment. +// The topology is created as: Alice -> Bob -> Carol, where Alice -> Bob is +// public and Bob -> Carol is private. After an invoice is created by Carol, +// Bob will update the base fee via UpdateChannelPolicy, we will test that +// Alice will not fail the payment and send it using the updated channel +// policy. +func testUpdateChannelPolicyForPrivateChannel(net *lntest.NetworkHarness, + t *harnessTest) { + + ctxb := context.Background() + defer ctxb.Done() + + // We'll create the following topology first, + // Alice <--public:100k--> Bob <--private:100k--> Carol + const chanAmt = btcutil.Amount(100000) + + // Open a channel with 100k satoshis between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + defer closeChannelAndAssert( + ctxt, t, net, net.Alice, chanPointAliceBob, false, + ) + + // Get Alice's funding point. + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAliceBob) + require.NoError(t.t, err, "unable to get txid") + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAliceBob.OutputIndex, + } + + // Create a new node Carol. + carol, err := net.NewNode("Carol", nil) + require.NoError(t.t, err, "unable to create node carol") + defer shutdownAndAssert(net, t, carol) + + // Connect Carol to Bob. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + require.NoError(t.t, + net.ConnectNodes(ctxt, carol, net.Bob), + "unable to connect carol to bob", + ) + + // Open a channel with 100k satoshis between Bob and Carol. + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBobCarol := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + defer closeChannelAndAssert( + ctxt, t, net, net.Bob, chanPointBobCarol, false, + ) + + // Get Bob's funding point. + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBobCarol) + require.NoError(t.t, err, "unable to get txid") + bobFundPoint := wire.OutPoint{ + Hash: *bobChanTXID, + Index: chanPointBobCarol.OutputIndex, + } + + // We should have the following topology now, + // Alice <--public:100k--> Bob <--private:100k--> Carol + // + // Now we will create an invoice for Carol. + const paymentAmt = 20000 + invoice := &lnrpc.Invoice{ + Memo: "routing hints", + Value: paymentAmt, + Private: true, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := carol.AddInvoice(ctxt, invoice) + require.NoError(t.t, err, "unable to create invoice for carol") + + // Bob now updates the channel edge policy for the private channel. + const ( + baseFeeMSat = 33000 + ) + timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta) + updateFeeReq := &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: baseFeeMSat, + TimeLockDelta: timeLockDelta, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPointBobCarol, + }, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Bob.UpdateChannelPolicy(ctxt, updateFeeReq) + require.NoError(t.t, err, "unable to update chan policy") + + // Alice pays the invoices. She will use the updated baseFeeMSat in the + // payment + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + payReqs := []string{resp.PaymentRequest} + require.NoError(t.t, + completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ), "unable to send payment", + ) + + // Check that Alice did make the payment with two HTLCs, one failed and + // one succeeded. + ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) + paymentsResp, err := net.Alice.ListPayments( + ctxt, &lnrpc.ListPaymentsRequest{}, + ) + require.NoError(t.t, err, "failed to obtain payments for Alice") + require.Equal(t.t, 1, len(paymentsResp.Payments), "expected 1 payment") + + htlcs := paymentsResp.Payments[0].Htlcs + require.Equal(t.t, 2, len(htlcs), "expected to have 2 HTLCs") + require.Equal( + t.t, lnrpc.HTLCAttempt_FAILED, htlcs[0].Status, + "the first HTLC attempt should fail", + ) + require.Equal( + t.t, lnrpc.HTLCAttempt_SUCCEEDED, htlcs[1].Status, + "the second HTLC attempt should succeed", + ) + + // Carol should have received 20k satoshis from Bob. + assertAmountPaid(t, "Carol(remote) [<=private] Bob(local)", + carol, bobFundPoint, 0, paymentAmt) + + // Bob should have sent 20k satoshis to Carol. + assertAmountPaid(t, "Bob(local) [private=>] Carol(remote)", + net.Bob, bobFundPoint, paymentAmt, 0) + + // Calcuate the amount in satoshis. + amtExpected := int64(paymentAmt + baseFeeMSat/1000) + + // Bob should have received 20k satoshis + fee from Alice. + assertAmountPaid(t, "Bob(remote) <= Alice(local)", + net.Bob, aliceFundPoint, 0, amtExpected) + + // Alice should have sent 20k satoshis + fee to Bob. + assertAmountPaid(t, "Alice(local) => Bob(remote)", + net.Alice, aliceFundPoint, amtExpected, 0) +} + +// testInvoiceRoutingHints tests that the routing hints for an invoice are +// created properly. +func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + + // Throughout this test, we'll be opening a channel between Alice and + // several other parties. + // + // First, we'll create a private channel between Alice and Bob. This + // will be the only channel that will be considered as a routing hint + // throughout this test. We'll include a push amount since we currently + // require channels to have enough remote balance to cover the invoice's + // payment. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + Private: true, + }, + ) + + // Then, we'll create Carol's node and open a public channel between her + // and Alice. This channel will not be considered as a routing hint due + // to it being public. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create carol's node: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Alice, carol); err != nil { + t.Fatalf("unable to connect alice to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + }, + ) + + // We'll also create a public channel between Bob and Carol to ensure + // that Bob gets selected as the only routing hint. We do this as + // we should only include routing hints for nodes that are publicly + // advertised, otherwise we'd end up leaking information about nodes + // that wish to stay unadvertised. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { + t.Fatalf("unable to connect alice to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBobCarol := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + }, + ) + + // Then, we'll create Dave's node and open a private channel between him + // and Alice. We will not include a push amount in order to not consider + // this channel as a routing hint as it will not have enough remote + // balance for the invoice's amount. + dave, err := net.NewNode("Dave", nil) + if err != nil { + t.Fatalf("unable to create dave's node: %v", err) + } + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Alice, dave); err != nil { + t.Fatalf("unable to connect alice to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointDave := openChannelAndAssert( + ctxt, t, net, net.Alice, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + + // Finally, we'll create Eve's node and open a private channel between + // her and Alice. This time though, we'll take Eve's node down after the + // channel has been created to avoid populating routing hints for + // inactive channels. + eve, err := net.NewNode("Eve", nil) + if err != nil { + t.Fatalf("unable to create eve's node: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Alice, eve); err != nil { + t.Fatalf("unable to connect alice to eve: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointEve := openChannelAndAssert( + ctxt, t, net, net.Alice, eve, + lntest.OpenChannelParams{ + Amt: chanAmt, + PushAmt: chanAmt / 2, + Private: true, + }, + ) + + // Make sure all the channels have been opened. + chanNames := []string{ + "alice-bob", "alice-carol", "bob-carol", "alice-dave", + "alice-eve", + } + aliceChans := []*lnrpc.ChannelPoint{ + chanPointBob, chanPointCarol, chanPointBobCarol, chanPointDave, + chanPointEve, + } + for i, chanPoint := range aliceChans { + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("timed out waiting for channel open %s: %v", + chanNames[i], err) + } + } + + // Now that the channels are open, we'll take down Eve's node. + shutdownAndAssert(net, t, eve) + + // Create an invoice for Alice that will populate the routing hints. + invoice := &lnrpc.Invoice{ + Memo: "routing hints", + Value: int64(chanAmt / 4), + Private: true, + } + + // Due to the way the channels were set up above, the channel between + // Alice and Bob should be the only channel used as a routing hint. + var predErr error + var decoded *lnrpc.PayReq + err = wait.Predicate(func() bool { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := net.Alice.AddInvoice(ctxt, invoice) + if err != nil { + predErr = fmt.Errorf("unable to add invoice: %v", err) + return false + } + + // We'll decode the invoice's payment request to determine which + // channels were used as routing hints. + payReq := &lnrpc.PayReqString{ + PayReq: resp.PaymentRequest, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + decoded, err = net.Alice.DecodePayReq(ctxt, payReq) + if err != nil { + predErr = fmt.Errorf("unable to decode payment "+ + "request: %v", err) + return false + } + + if len(decoded.RouteHints) != 1 { + predErr = fmt.Errorf("expected one route hint, got %d", + len(decoded.RouteHints)) + return false + } + return true + }, defaultTimeout) + if err != nil { + t.Fatalf(predErr.Error()) + } + + hops := decoded.RouteHints[0].HopHints + if len(hops) != 1 { + t.Fatalf("expected one hop in route hint, got %d", len(hops)) + } + chanID := hops[0].ChanId + + // We'll need the short channel ID of the channel between Alice and Bob + // to make sure the routing hint is for this channel. + listReq := &lnrpc.ListChannelsRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + listResp, err := net.Alice.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to retrieve alice's channels: %v", err) + } + + var aliceBobChanID uint64 + for _, channel := range listResp.Channels { + if channel.RemotePubkey == net.Bob.PubKeyStr { + aliceBobChanID = channel.ChanId + } + } + + if aliceBobChanID == 0 { + t.Fatalf("channel between alice and bob not found") + } + + if chanID != aliceBobChanID { + t.Fatalf("expected channel ID %d, got %d", aliceBobChanID, + chanID) + } + + // Now that we've confirmed the routing hints were added correctly, we + // can close all the channels and shut down all the nodes created. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointDave, false) + + // The channel between Alice and Eve should be force closed since Eve + // is offline. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointEve, true) + + // Cleanup by mining the force close and sweep transaction. + cleanupForceClose(t, net, net.Alice, chanPointEve) +} + +// testMultiHopOverPrivateChannels tests that private channels can be used as +// intermediate hops in a route for payments. +func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // We'll test that multi-hop payments over private channels work as + // intended. To do so, we'll create the following topology: + // private public private + // Alice <--100k--> Bob <--100k--> Carol <--100k--> Dave + const chanAmt = btcutil.Amount(100000) + + // First, we'll open a private channel between Alice and Bob with Alice + // being the funder. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("alice didn't see the channel alice <-> bob before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + if err != nil { + t.Fatalf("bob didn't see the channel alice <-> bob before "+ + "timeout: %v", err) + } + + // Retrieve Alice's funding outpoint. + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // Next, we'll create Carol's node and open a public channel between + // her and Bob with Bob being the funder. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create carol's node: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { + t.Fatalf("unable to connect bob to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("bob didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("carol didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("alice didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + + // Retrieve Bob's funding outpoint. + bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + bobFundPoint := wire.OutPoint{ + Hash: *bobChanTXID, + Index: chanPointBob.OutputIndex, + } + + // Next, we'll create Dave's node and open a private channel between him + // and Carol with Carol being the funder. + dave, err := net.NewNode("Dave", nil) + if err != nil { + t.Fatalf("unable to create dave's node: %v", err) + } + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + Private: true, + }, + ) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't see the channel carol <-> dave before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = dave.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("dave didn't see the channel carol <-> dave before "+ + "timeout: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = dave.WaitForNetworkChannelOpen(ctxt, chanPointBob) + if err != nil { + t.Fatalf("dave didn't see the channel bob <-> carol before "+ + "timeout: %v", err) + } + + // Retrieve Carol's funding point. + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Now that all the channels are set up according to the topology from + // above, we can proceed to test payments. We'll create an invoice for + // Dave of 20k satoshis and pay it with Alice. Since there is no public + // route from Alice to Dave, we'll need to use the private channel + // between Carol and Dave as a routing hint encoded in the invoice. + const paymentAmt = 20000 + + // Create the invoice for Dave. + invoice := &lnrpc.Invoice{ + Memo: "two hopz!", + Value: paymentAmt, + Private: true, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + resp, err := dave.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to add invoice for dave: %v", err) + } + + // Let Alice pay the invoice. + payReqs := []string{resp.PaymentRequest} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ) + if err != nil { + t.Fatalf("unable to send payments from alice to dave: %v", err) + } + + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when opening + // the channels. + const baseFee = 1 + + // Dave should have received 20k satoshis from Carol. + assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", + dave, carolFundPoint, 0, paymentAmt) + + // Carol should have sent 20k satoshis to Dave. + assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", + carol, carolFundPoint, paymentAmt, 0) + + // Carol should have received 20k satoshis + fee for one hop from Bob. + assertAmountPaid(t, "Bob(local) => Carol(remote)", + carol, bobFundPoint, 0, paymentAmt+baseFee) + + // Bob should have sent 20k satoshis + fee for one hop to Carol. + assertAmountPaid(t, "Bob(local) => Carol(remote)", + net.Bob, bobFundPoint, paymentAmt+baseFee, 0) + + // Bob should have received 20k satoshis + fee for two hops from Alice. + assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Bob, + aliceFundPoint, 0, paymentAmt+baseFee*2) + + // Alice should have sent 20k satoshis + fee for two hops to Bob. + assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Alice, + aliceFundPoint, paymentAmt+baseFee*2, 0) + + // At this point, the payment was successful. We can now close all the + // channels and shutdown the nodes created throughout this test. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// computeFee calculates the payment fee as specified in BOLT07 +func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { + return baseFee + amt*feeRate/1000000 +} + +// testQueryRoutes checks the response of queryroutes. +// We'll create the following network topology: +// Alice --> Bob --> Carol --> Dave +// and query the daemon for routes from Alice to Dave. +func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + const chanAmt = btcutil.Amount(100000) + var networkChans []*lnrpc.ChannelPoint + + // Open a channel between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAlice := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointAlice) + + // Create Carol and establish a channel from Bob. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { + t.Fatalf("unable to connect carol to bob: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, net.Bob) + if err != nil { + t.Fatalf("unable to send coins to bob: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBob := openChannelAndAssert( + ctxt, t, net, net.Bob, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointBob) + + // Create Dave and establish a channel from Carol. + dave, err := net.NewNode("Dave", nil) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, carol); err != nil { + t.Fatalf("unable to connect dave to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarol := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + networkChans = append(networkChans, chanPointCarol) + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + point := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d): timeout waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, point, err) + } + } + } + + // Query for routes to pay from Alice to Dave. + const paymentAmt = 1000 + routesReq := &lnrpc.QueryRoutesRequest{ + PubKey: dave.PubKeyStr, + Amt: paymentAmt, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq) + if err != nil { + t.Fatalf("unable to get route: %v", err) + } + + const mSat = 1000 + feePerHopMSat := computeFee(1000, 1, paymentAmt*mSat) + + for i, route := range routesRes.Routes { + expectedTotalFeesMSat := + lnwire.MilliSatoshi(len(route.Hops)-1) * feePerHopMSat + expectedTotalAmtMSat := (paymentAmt * mSat) + expectedTotalFeesMSat + + if route.TotalFees != route.TotalFeesMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v: total fees %v (msat) does not "+ + "round down to %v (sat)", + i, route.TotalFeesMsat, route.TotalFees) // nolint:staticcheck + } + if route.TotalFeesMsat != int64(expectedTotalFeesMSat) { + t.Fatalf("route %v: total fees in msat expected %v got %v", + i, expectedTotalFeesMSat, route.TotalFeesMsat) + } + + if route.TotalAmt != route.TotalAmtMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v: total amt %v (msat) does not "+ + "round down to %v (sat)", + i, route.TotalAmtMsat, route.TotalAmt) // nolint:staticcheck + } + if route.TotalAmtMsat != int64(expectedTotalAmtMSat) { + t.Fatalf("route %v: total amt in msat expected %v got %v", + i, expectedTotalAmtMSat, route.TotalAmtMsat) + } + + // For all hops except the last, we check that fee equals feePerHop + // and amount to forward deducts feePerHop on each hop. + expectedAmtToForwardMSat := expectedTotalAmtMSat + for j, hop := range route.Hops[:len(route.Hops)-1] { + expectedAmtToForwardMSat -= feePerHopMSat + + if hop.Fee != hop.FeeMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v hop %v: fee %v (msat) does not "+ + "round down to %v (sat)", + i, j, hop.FeeMsat, hop.Fee) // nolint:staticcheck + } + if hop.FeeMsat != int64(feePerHopMSat) { + t.Fatalf("route %v hop %v: fee in msat expected %v got %v", + i, j, feePerHopMSat, hop.FeeMsat) + } + + if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ + "round down to %v (sat)", + i, j, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck + } + if hop.AmtToForwardMsat != int64(expectedAmtToForwardMSat) { + t.Fatalf("route %v hop %v: amt to forward in msat "+ + "expected %v got %v", + i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat) + } + } + // Last hop should have zero fee and amount to forward should equal + // payment amount. + hop := route.Hops[len(route.Hops)-1] + + if hop.Fee != 0 || hop.FeeMsat != 0 { // nolint:staticcheck + t.Fatalf("route %v hop %v: fee expected 0 got %v (sat) %v (msat)", + i, len(route.Hops)-1, hop.Fee, hop.FeeMsat) // nolint:staticcheck + } + + if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck + t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ + "round down to %v (sat)", + i, len(route.Hops)-1, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck + } + if hop.AmtToForwardMsat != paymentAmt*mSat { + t.Fatalf("route %v hop %v: amt to forward in msat "+ + "expected %v got %v", + i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat) + } + } + + // While we're here, we test updating mission control's config values + // and assert that they are correctly updated and check that our mission + // control import function updates appropriately. + testMissionControlCfg(t.t, net.Alice) + testMissionControlImport( + t.t, net.Alice, net.Bob.PubKey[:], carol.PubKey[:], + ) + + // We clean up the test case by closing channels that were created for + // the duration of the tests. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +} + +// testMissionControlCfg tests getting and setting of a node's mission control +// config, resetting to the original values after testing so that no other +// tests are affected. +func testMissionControlCfg(t *testing.T, node *lntest.HarnessNode) { + ctxb := context.Background() + startCfg, err := node.RouterClient.GetMissionControlConfig( + ctxb, &routerrpc.GetMissionControlConfigRequest{}, + ) + require.NoError(t, err) + + cfg := &routerrpc.MissionControlConfig{ + HalfLifeSeconds: 8000, + HopProbability: 0.8, + Weight: 0.3, + MaximumPaymentResults: 30, + MinimumFailureRelaxInterval: 60, + } + + _, err = node.RouterClient.SetMissionControlConfig( + ctxb, &routerrpc.SetMissionControlConfigRequest{ + Config: cfg, + }, + ) + require.NoError(t, err) + + resp, err := node.RouterClient.GetMissionControlConfig( + ctxb, &routerrpc.GetMissionControlConfigRequest{}, + ) + require.NoError(t, err) + require.True(t, proto.Equal(cfg, resp.Config)) + + _, err = node.RouterClient.SetMissionControlConfig( + ctxb, &routerrpc.SetMissionControlConfigRequest{ + Config: startCfg.Config, + }, + ) + require.NoError(t, err) +} + +// testMissionControlImport tests import of mission control results from an +// external source. +func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, + fromNode, toNode []byte) { + + ctxb := context.Background() + + // Reset mission control so that our query will return the default + // probability for our first request. + _, err := node.RouterClient.ResetMissionControl( + ctxb, &routerrpc.ResetMissionControlRequest{}, + ) + require.NoError(t, err, "could not reset mission control") + + // Get our baseline probability for a 10 msat hop between our target + // nodes. + var amount int64 = 10 + probReq := &routerrpc.QueryProbabilityRequest{ + FromNode: fromNode, + ToNode: toNode, + AmtMsat: amount, + } + + importHistory := &routerrpc.PairData{ + FailTime: time.Now().Unix(), + FailAmtMsat: amount, + } + + // Assert that our history is not already equal to the value we want to + // set. This should not happen because we have just cleared our state. + resp1, err := node.RouterClient.QueryProbability(ctxb, probReq) + require.NoError(t, err, "query probability failed") + require.Zero(t, resp1.History.FailTime) + require.Zero(t, resp1.History.FailAmtMsat) + + // Now, we import a single entry which tracks a failure of the amount + // we want to query between our nodes. + req := &routerrpc.XImportMissionControlRequest{ + Pairs: []*routerrpc.PairHistory{ + { + NodeFrom: fromNode, + NodeTo: toNode, + History: importHistory, + }, + }, + } + + _, err = node.RouterClient.XImportMissionControl(ctxb, req) + require.NoError(t, err, "could not import config") + + resp2, err := node.RouterClient.QueryProbability(ctxb, probReq) + require.NoError(t, err, "query probability failed") + require.Equal(t, importHistory.FailTime, resp2.History.FailTime) + require.Equal(t, importHistory.FailAmtMsat, resp2.History.FailAmtMsat) + + // Finally, check that we will fail if inconsistent sat/msat values are + // set. + importHistory.FailAmtSat = amount * 2 + _, err = node.RouterClient.XImportMissionControl(ctxb, req) + require.Error(t, err, "mismatched import amounts succeeded") +} + +// testRouteFeeCutoff tests that we are able to prevent querying routes and +// sending payments that incur a fee higher than the fee limit. +func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { + ctxb := context.Background() + + // For this test, we'll create the following topology: + // + // --- Bob --- + // / \ + // Alice ---- ---- Dave + // \ / + // -- Carol -- + // + // Alice will attempt to send payments to Dave that should not incur a + // fee greater than the fee limit expressed as a percentage of the + // amount and as a fixed amount of satoshis. + const chanAmt = btcutil.Amount(100000) + + // Open a channel between Alice and Bob. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceBob := openChannelAndAssert( + ctxt, t, net, net.Alice, net.Bob, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Create Carol's node and open a channel between her and Alice with + // Alice being the funder. + carol, err := net.NewNode("Carol", nil) + if err != nil { + t.Fatalf("unable to create carol's node: %v", err) + } + defer shutdownAndAssert(net, t, carol) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { + t.Fatalf("unable to connect carol to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointAliceCarol := openChannelAndAssert( + ctxt, t, net, net.Alice, carol, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Create Dave's node and open a channel between him and Bob with Bob + // being the funder. + dave, err := net.NewNode("Dave", nil) + if err != nil { + t.Fatalf("unable to create dave's node: %v", err) + } + defer shutdownAndAssert(net, t, dave) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, net.Bob); err != nil { + t.Fatalf("unable to connect dave to bob: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointBobDave := openChannelAndAssert( + ctxt, t, net, net.Bob, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Open a channel between Carol and Dave. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, carol, dave); err != nil { + t.Fatalf("unable to connect carol to dave: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) + chanPointCarolDave := openChannelAndAssert( + ctxt, t, net, carol, dave, + lntest.OpenChannelParams{ + Amt: chanAmt, + }, + ) + + // Now that all the channels were set up, we'll wait for all the nodes + // to have seen all the channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"alice", "bob", "carol", "dave"} + networkChans := []*lnrpc.ChannelPoint{ + chanPointAliceBob, chanPointAliceCarol, chanPointBobDave, + chanPointCarolDave, + } + for _, chanPoint := range networkChans { + for i, node := range nodes { + txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + outpoint := wire.OutPoint{ + Hash: *txid, + Index: chanPoint.OutputIndex, + } + + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { + t.Fatalf("%s(%d) timed out waiting for "+ + "channel(%s) open: %v", nodeNames[i], + node.NodeID, outpoint, err) + } + } + } + + // The payments should only be successful across the route: + // Alice -> Bob -> Dave + // Therefore, we'll update the fee policy on Carol's side for the + // channel between her and Dave to invalidate the route: + // Alice -> Carol -> Dave + baseFee := int64(10000) + feeRate := int64(5) + timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta) + maxHtlc := calculateMaxHtlc(chanAmt) + + expectedPolicy := &lnrpc.RoutingPolicy{ + FeeBaseMsat: baseFee, + FeeRateMilliMsat: testFeeBase * feeRate, + TimeLockDelta: timeLockDelta, + MinHtlc: 1000, // default value + MaxHtlcMsat: maxHtlc, + } + + updateFeeReq := &lnrpc.PolicyUpdateRequest{ + BaseFeeMsat: baseFee, + FeeRate: float64(feeRate), + TimeLockDelta: timeLockDelta, + MaxHtlcMsat: maxHtlc, + Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ + ChanPoint: chanPointCarolDave, + }, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if _, err := carol.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil { + t.Fatalf("unable to update chan policy: %v", err) + } + + // Wait for Alice to receive the channel update from Carol. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceSub := subscribeGraphNotifications(ctxt, t, net.Alice) + defer close(aliceSub.quit) + + waitForChannelUpdate( + t, aliceSub, + []expectedChanUpdate{ + {carol.PubKeyStr, expectedPolicy, chanPointCarolDave}, + }, + ) + + // We'll also need the channel IDs for Bob's channels in order to + // confirm the route of the payments. + listReq := &lnrpc.ListChannelsRequest{} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + listResp, err := net.Bob.ListChannels(ctxt, listReq) + if err != nil { + t.Fatalf("unable to retrieve bob's channels: %v", err) + } + + var aliceBobChanID, bobDaveChanID uint64 + for _, channel := range listResp.Channels { + switch channel.RemotePubkey { + case net.Alice.PubKeyStr: + aliceBobChanID = channel.ChanId + case dave.PubKeyStr: + bobDaveChanID = channel.ChanId + } + } + + if aliceBobChanID == 0 { + t.Fatalf("channel between alice and bob not found") + } + if bobDaveChanID == 0 { + t.Fatalf("channel between bob and dave not found") + } + hopChanIDs := []uint64{aliceBobChanID, bobDaveChanID} + + // checkRoute is a helper closure to ensure the route contains the + // correct intermediate hops. + checkRoute := func(route *lnrpc.Route) { + if len(route.Hops) != 2 { + t.Fatalf("expected two hops, got %d", len(route.Hops)) + } + + for i, hop := range route.Hops { + if hop.ChanId != hopChanIDs[i] { + t.Fatalf("expected chan id %d, got %d", + hopChanIDs[i], hop.ChanId) + } + } + } + + // We'll be attempting to send two payments from Alice to Dave. One will + // have a fee cutoff expressed as a percentage of the amount and the + // other will have it expressed as a fixed amount of satoshis. + const paymentAmt = 100 + carolFee := computeFee(lnwire.MilliSatoshi(baseFee), 1, paymentAmt) + + // testFeeCutoff is a helper closure that will ensure the different + // types of fee limits work as intended when querying routes and sending + // payments. + testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) { + queryRoutesReq := &lnrpc.QueryRoutesRequest{ + PubKey: dave.PubKeyStr, + Amt: paymentAmt, + FeeLimit: feeLimit, + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq) + if err != nil { + t.Fatalf("unable to get routes: %v", err) + } + + checkRoute(routesResp.Routes[0]) + + invoice := &lnrpc.Invoice{Value: paymentAmt} + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + invoiceResp, err := dave.AddInvoice(ctxt, invoice) + if err != nil { + t.Fatalf("unable to create invoice: %v", err) + } + + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoiceResp.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + } + switch limit := feeLimit.Limit.(type) { + case *lnrpc.FeeLimit_Fixed: + sendReq.FeeLimitMsat = 1000 * limit.Fixed + case *lnrpc.FeeLimit_Percent: + sendReq.FeeLimitMsat = 1000 * paymentAmt * limit.Percent / 100 + } + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + result := sendAndAssertSuccess(ctxt, t, net.Alice, sendReq) + + checkRoute(result.Htlcs[0].Route) + } + + // We'll start off using percentages first. Since the fee along the + // route using Carol as an intermediate hop is 10% of the payment's + // amount, we'll use a lower percentage in order to invalid that route. + feeLimitPercent := &lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Percent{ + Percent: baseFee/1000 - 1, + }, + } + testFeeCutoff(feeLimitPercent) + + // Now we'll test using fixed fee limit amounts. Since we computed the + // fee for the route using Carol as an intermediate hop earlier, we can + // use a smaller value in order to invalidate that route. + feeLimitFixed := &lnrpc.FeeLimit{ + Limit: &lnrpc.FeeLimit_Fixed{ + Fixed: int64(carolFee.ToSatoshis()) - 1, + }, + } + testFeeCutoff(feeLimitFixed) + + // Once we're done, close the channels and shut down the nodes created + // throughout this test. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceBob, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceCarol, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobDave, false) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, carol, chanPointCarolDave, false) +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index fcf21ea0787..c7016e98caa 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -232,7 +232,7 @@ func closeChannelAndAssertType(ctx context.Context, t *harnessTest, // updates before initiating the channel closure. var graphSub *graphSubscription if expectDisable { - sub := subscribeGraphNotifications(t, ctx, node) + sub := subscribeGraphNotifications(ctx, t, node) graphSub = &sub defer close(graphSub.quit) } @@ -1576,9 +1576,9 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // Launch notification clients for all nodes, such that we can // get notified when they discover new channels and updates in the // graph. - aliceSub := subscribeGraphNotifications(t, ctxb, net.Alice) + aliceSub := subscribeGraphNotifications(ctxb, t, net.Alice) defer close(aliceSub.quit) - bobSub := subscribeGraphNotifications(t, ctxb, net.Bob) + bobSub := subscribeGraphNotifications(ctxb, t, net.Bob) defer close(bobSub.quit) chanAmt := funding.MaxBtcFundingAmount @@ -1654,7 +1654,7 @@ func testUpdateChannelPolicy(net *lntest.NetworkHarness, t *harnessTest) { // Clean up carol's node when the test finishes. defer shutdownAndAssert(net, t, carol) - carolSub := subscribeGraphNotifications(t, ctxb, carol) + carolSub := subscribeGraphNotifications(ctxb, t, carol) defer close(carolSub.quit) graphSubs = append(graphSubs, carolSub) @@ -4940,7 +4940,7 @@ func testUpdateChanStatus(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to connect bob to carol: %v", err) } - carolSub := subscribeGraphNotifications(t, ctxb, carol) + carolSub := subscribeGraphNotifications(ctxb, t, carol) defer close(carolSub.quit) // sendReq sends an UpdateChanStatus request to the given node. @@ -5399,7 +5399,7 @@ func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, // Wait for listener node to receive the channel update from node. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - graphSub := subscribeGraphNotifications(t, ctxt, listenerNode) + graphSub := subscribeGraphNotifications(ctxt, t, listenerNode) defer close(graphSub.quit) waitForChannelUpdate( @@ -5410,1851 +5410,325 @@ func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, ) } -type singleHopSendToRouteCase struct { - name string - - // streaming tests streaming SendToRoute if true, otherwise tests - // synchronous SenToRoute. - streaming bool - - // routerrpc submits the request to the routerrpc subserver if true, - // otherwise submits to the main rpc server. - routerrpc bool -} - -var singleHopSendToRouteCases = []singleHopSendToRouteCase{ - { - name: "regular main sync", - }, - { - name: "regular main stream", - streaming: true, - }, - { - name: "regular routerrpc sync", - routerrpc: true, - }, - { - name: "mpp main sync", - }, - { - name: "mpp main stream", - streaming: true, - }, - { - name: "mpp routerrpc sync", - routerrpc: true, - }, -} - -// testSingleHopSendToRoute tests that payments are properly processed through a -// provided route with a single hop. We'll create the following network -// topology: -// Carol --100k--> Dave -// We'll query the daemon for routes from Carol to Dave and then send payments -// by feeding the route back into the various SendToRoute RPC methods. Here we -// test all three SendToRoute endpoints, forcing each to perform both a regular -// payment and an MPP payment. -func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { - for _, test := range singleHopSendToRouteCases { - test := test - - t.t.Run(test.name, func(t1 *testing.T) { - ht := newHarnessTest(t1, t.lndHarness) - ht.RunTestCase(&testCase{ - name: test.name, - test: func(_ *lntest.NetworkHarness, tt *harnessTest) { - testSingleHopSendToRouteCase(net, tt, test) - }, - }) - }) - } -} - -func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, - test singleHopSendToRouteCase) { - - const chanAmt = btcutil.Amount(100000) - const paymentAmtSat = 1000 - const numPayments = 5 - const amountPaid = int64(numPayments * paymentAmtSat) - +// testUnannouncedChannels checks unannounced channels are not returned by +// describeGraph RPC request unless explicitly asked for. +func testUnannouncedChannels(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - var networkChans []*lnrpc.ChannelPoint - - // Create Carol and Dave, then establish a channel between them. Carol - // is the sole funder of the channel with 100k satoshis. The network - // topology should look like: - // Carol -> 100k -> Dave - carol, err := net.NewNode("Carol", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, carol) - - dave, err := net.NewNode("Dave", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, dave) - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) - if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) - } + amount := funding.MaxBtcFundingAmount - // Open a channel with 100k satoshis between Carol and Dave with Carol - // being the sole funder of the channel. - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, + // Open a channel between Alice and Bob, ensuring the + // channel has been opened properly. + ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + chanOpenUpdate := openChannelStream( + ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: amount, }, ) - networkChans = append(networkChans, chanPointCarol) - - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, - } - - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{carol, dave} - for _, chanPoint := range networkChans { - for _, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", node.Name(), - node.NodeID, point, err) - } - } - } + // Mine 2 blocks, and check that the channel is opened but not yet + // announced to the network. + mineBlocks(t, net, 2, 1) - // Create invoices for Dave, which expect a payment from Carol. - payReqs, rHashes, _, err := createPayReqs( - dave, paymentAmtSat, numPayments, - ) + // One block is enough to make the channel ready for use, since the + // nodes have defaultNumConfs=1 set. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + fundingChanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) + t.Fatalf("error while waiting for channel open: %v", err) } - // Reconstruct payment addresses. - var payAddrs [][]byte - for _, payReq := range payReqs { - ctx, _ := context.WithTimeout( - context.Background(), defaultTimeout, - ) - resp, err := dave.DecodePayReq( - ctx, - &lnrpc.PayReqString{PayReq: payReq}, - ) - if err != nil { - t.Fatalf("decode pay req: %v", err) - } - payAddrs = append(payAddrs, resp.PaymentAddr) + // Alice should have 1 edge in her graph. + req := &lnrpc.ChannelGraphRequest{ + IncludeUnannounced: true, } - - // Assert Carol and Dave are synced to the chain before proceeding, to - // ensure the queried route will have a valid final CLTV once the HTLC - // reaches Dave. - _, minerHeight, err := net.Miner.Client.GetBestBlock() + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err := net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("unable to get best height: %v", err) + t.Fatalf("unable to query alice's graph: %v", err) } - ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout) - defer cancel() - require.NoError(t.t, waitForNodeBlockHeight(ctxt, carol, minerHeight)) - require.NoError(t.t, waitForNodeBlockHeight(ctxt, dave, minerHeight)) - // Query for routes to pay from Carol to Dave using the default CLTV - // config. - routesReq := &lnrpc.QueryRoutesRequest{ - PubKey: dave.PubKeyStr, - Amt: paymentAmtSat, + numEdges := len(chanGraph.Edges) + if numEdges != 1 { + t.Fatalf("expected to find 1 edge in the graph, found %d", numEdges) } + + // Channels should not be announced yet, hence Alice should have no + // announced edges in her graph. + req.IncludeUnannounced = false ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routes, err := carol.QueryRoutes(ctxt, routesReq) + chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("unable to get route from %s: %v", - carol.Name(), err) + t.Fatalf("unable to query alice's graph: %v", err) } - // There should only be one route to try, so take the first item. - r := routes.Routes[0] - - // Construct a closure that will set MPP fields on the route, which - // allows us to test MPP payments. - setMPPFields := func(i int) { - hop := r.Hops[len(r.Hops)-1] - hop.TlvPayload = true - hop.MppRecord = &lnrpc.MPPRecord{ - PaymentAddr: payAddrs[i], - TotalAmtMsat: paymentAmtSat * 1000, - } + numEdges = len(chanGraph.Edges) + if numEdges != 0 { + t.Fatalf("expected to find 0 announced edges in the graph, found %d", + numEdges) } - // Construct closures for each of the payment types covered: - // - main rpc server sync - // - main rpc server streaming - // - routerrpc server sync - sendToRouteSync := func() { - for i, rHash := range rHashes { - setMPPFields(i) + // Mine 4 more blocks, and check that the channel is now announced. + mineBlocks(t, net, 4, 0) - sendReq := &lnrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: r, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.SendToRouteSync( - ctxt, sendReq, - ) - if err != nil { - t.Fatalf("unable to send to route for "+ - "%s: %v", carol.Name(), err) - } - if resp.PaymentError != "" { - t.Fatalf("received payment error from %s: %v", - carol.Name(), resp.PaymentError) - } - } - } - sendToRouteStream := func() { + // Give the network a chance to learn that auth proof is confirmed. + var predErr error + err = wait.Predicate(func() bool { + // The channel should now be announced. Check that Alice has 1 + // announced edge. + req.IncludeUnannounced = false ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - alicePayStream, err := carol.SendToRoute(ctxt) // nolint:staticcheck + chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("unable to create payment stream for "+ - "carol: %v", err) - } - - for i, rHash := range rHashes { - setMPPFields(i) - - sendReq := &lnrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: routes.Routes[0], - } - err := alicePayStream.Send(sendReq) - - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - - resp, err := alicePayStream.Recv() - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - if resp.PaymentError != "" { - t.Fatalf("received payment error: %v", - resp.PaymentError) - } + predErr = fmt.Errorf("unable to query alice's graph: %v", err) + return false } - } - sendToRouteRouterRPC := func() { - for i, rHash := range rHashes { - setMPPFields(i) - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: r, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.RouterClient.SendToRouteV2( - ctxt, sendReq, - ) - if err != nil { - t.Fatalf("unable to send to route for "+ - "%s: %v", carol.Name(), err) - } - if resp.Failure != nil { - t.Fatalf("received payment error from %s: %v", - carol.Name(), resp.Failure) - } + numEdges = len(chanGraph.Edges) + if numEdges != 1 { + predErr = fmt.Errorf("expected to find 1 announced edge in "+ + "the graph, found %d", numEdges) + return false } - } - - // Using Carol as the node as the source, send the payments - // synchronously via the the routerrpc's SendToRoute, or via the main RPC - // server's SendToRoute streaming or sync calls. - switch { - case !test.routerrpc && test.streaming: - sendToRouteStream() - case !test.routerrpc && !test.streaming: - sendToRouteSync() - case test.routerrpc && !test.streaming: - sendToRouteRouterRPC() - default: - t.Fatalf("routerrpc does not support streaming send_to_route") - } - - // Verify that the payment's from Carol's PoV have the correct payment - // hash and amount. - ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) - paymentsResp, err := carol.ListPayments( - ctxt, &lnrpc.ListPaymentsRequest{}, - ) + return true + }, defaultTimeout) if err != nil { - t.Fatalf("error when obtaining %s payments: %v", - carol.Name(), err) - } - if len(paymentsResp.Payments) != numPayments { - t.Fatalf("incorrect number of payments, got %v, want %v", - len(paymentsResp.Payments), numPayments) - } - - for i, p := range paymentsResp.Payments { - // Assert that the payment hashes for each payment match up. - rHashHex := hex.EncodeToString(rHashes[i]) - if p.PaymentHash != rHashHex { - t.Fatalf("incorrect payment hash for payment %d, "+ - "want: %s got: %s", - i, rHashHex, p.PaymentHash) - } - - // Assert that each payment has no invoice since the payment was - // completed using SendToRoute. - if p.PaymentRequest != "" { - t.Fatalf("incorrect payment request for payment: %d, "+ - "want: \"\", got: %s", - i, p.PaymentRequest) - } - - // Assert the payment amount is correct. - if p.ValueSat != paymentAmtSat { - t.Fatalf("incorrect payment amt for payment %d, "+ - "want: %d, got: %d", - i, paymentAmtSat, p.ValueSat) - } - - // Assert exactly one htlc was made. - if len(p.Htlcs) != 1 { - t.Fatalf("expected 1 htlc for payment %d, got: %d", - i, len(p.Htlcs)) - } - - // Assert the htlc's route is populated. - htlc := p.Htlcs[0] - if htlc.Route == nil { - t.Fatalf("expected route for payment %d", i) - } - - // Assert the hop has exactly one hop. - if len(htlc.Route.Hops) != 1 { - t.Fatalf("expected 1 hop for payment %d, got: %d", - i, len(htlc.Route.Hops)) - } - - // If this is an MPP test, assert the MPP record's fields are - // properly populated. Otherwise the hop should not have an MPP - // record. - hop := htlc.Route.Hops[0] - if hop.MppRecord == nil { - t.Fatalf("expected mpp record for mpp payment") - } - - if hop.MppRecord.TotalAmtMsat != paymentAmtSat*1000 { - t.Fatalf("incorrect mpp total msat for payment %d "+ - "want: %d, got: %d", - i, paymentAmtSat*1000, - hop.MppRecord.TotalAmtMsat) - } - - expAddr := payAddrs[i] - if !bytes.Equal(hop.MppRecord.PaymentAddr, expAddr) { - t.Fatalf("incorrect mpp payment addr for payment %d "+ - "want: %x, got: %x", - i, expAddr, hop.MppRecord.PaymentAddr) - } + t.Fatalf("%v", predErr) } - // Verify that the invoices's from Dave's PoV have the correct payment - // hash and amount. - ctxt, _ = context.WithTimeout(ctxt, defaultTimeout) - invoicesResp, err := dave.ListInvoices( - ctxt, &lnrpc.ListInvoiceRequest{}, - ) + // The channel should now be announced. Check that Alice has 1 announced + // edge. + req.IncludeUnannounced = false + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + chanGraph, err = net.Alice.DescribeGraph(ctxt, req) if err != nil { - t.Fatalf("error when obtaining %s payments: %v", - dave.Name(), err) - } - if len(invoicesResp.Invoices) != numPayments { - t.Fatalf("incorrect number of invoices, got %v, want %v", - len(invoicesResp.Invoices), numPayments) + t.Fatalf("unable to query alice's graph: %v", err) } - for i, inv := range invoicesResp.Invoices { - // Assert that the payment hashes match up. - if !bytes.Equal(inv.RHash, rHashes[i]) { - t.Fatalf("incorrect payment hash for invoice %d, "+ - "want: %x got: %x", - i, rHashes[i], inv.RHash) - } - - // Assert that the amount paid to the invoice is correct. - if inv.AmtPaidSat != paymentAmtSat { - t.Fatalf("incorrect payment amt for invoice %d, "+ - "want: %d, got %d", - i, paymentAmtSat, inv.AmtPaidSat) - } + numEdges = len(chanGraph.Edges) + if numEdges != 1 { + t.Fatalf("expected to find 1 announced edge in the graph, found %d", + numEdges) } - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Dave, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment - // transaction, in channel Carol->Dave, order is Dave and then Carol. - assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, - carolFundPoint, amountPaid, int64(0)) - + // Close the channel used during the test. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) + closeChannelAndAssert(ctxt, t, net, net.Alice, fundingChanPoint, false) } -// testMultiHopSendToRoute tests that payments are properly processed -// through a provided route. We'll create the following network topology: -// Alice --100k--> Bob --100k--> Carol -// We'll query the daemon for routes from Alice to Carol and then -// send payments through the routes. -func testMultiHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { +func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint + const chanAmt = btcutil.Amount(500000) - // Open a channel with 100k satoshis between Alice and Bob with Alice + // Open a channel with 500k satoshis between Alice and Bob with Alice // being the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( + chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ Amt: chanAmt, }, ) - networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, + // Next create a new invoice for Bob requesting 1k satoshis. + // TODO(roasbeef): make global list of invoices for each node to re-use + // and avoid collisions + const paymentAmt = 1000 + invoice := &lnrpc.Invoice{ + Memo: "testing", + RPreimage: makeFakePayHash(t), + Value: paymentAmt, } - - // Create Carol and establish a channel from Bob. Bob is the sole funder - // of the channel with 100k satoshis. The network topology should look like: - // Alice -> Bob -> Carol - carol, err := net.NewNode("Carol", nil) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + invoiceResp, err := net.Bob.AddInvoice(ctxt, invoice) if err != nil { - t.Fatalf("unable to create new nodes: %v", err) + t.Fatalf("unable to add invoice: %v", err) } - defer shutdownAndAssert(net, t, carol) + lastAddIndex := invoiceResp.AddIndex - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, net.Bob) - if err != nil { - t.Fatalf("unable to send coins to bob: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointBob) - bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) + // Create a new invoice subscription client for Bob, the notification + // should be dispatched shortly below. + req := &lnrpc.InvoiceSubscription{} + ctx, cancelInvoiceSubscription := context.WithCancel(ctxb) + bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctx, req) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - bobFundPoint := wire.OutPoint{ - Hash: *bobChanTXID, - Index: chanPointBob.OutputIndex, + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol} - nodeNames := []string{"Alice", "Bob", "Carol"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } + var settleIndex uint64 + quit := make(chan struct{}) + updateSent := make(chan struct{}) + go func() { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + select { + case <-quit: + // Received cancellation + return + default: } - } - - // Create 5 invoices for Carol, which expect a payment from Alice for 1k - // satoshis with a different preimage each time. - const ( - numPayments = 5 - paymentAmt = 1000 - ) - _, rHashes, invoices, err := createPayReqs( - carol, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Construct a route from Alice to Carol for each of the invoices - // created above. We set FinalCltvDelta to 40 since by default - // QueryRoutes returns the last hop with a final cltv delta of 9 where - // as the default in htlcswitch is 40. - routesReq := &lnrpc.QueryRoutesRequest{ - PubKey: carol.PubKeyStr, - Amt: paymentAmt, - FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routes, err := net.Alice.QueryRoutes(ctxt, routesReq) - if err != nil { - t.Fatalf("unable to get route: %v", err) - } - - // We'll wait for all parties to recognize the new channels within the - // network. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("bob didn't advertise his channel in time: %v", err) - } - - time.Sleep(time.Millisecond * 50) - - // Using Alice as the source, pay to the 5 invoices from Carol created - // above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - for i, rHash := range rHashes { - // Manually set the MPP payload a new for each payment since - // the payment addr will change with each invoice, although we - // can re-use the route itself. - route := *routes.Routes[0] - route.Hops[len(route.Hops)-1].TlvPayload = true - route.Hops[len(route.Hops)-1].MppRecord = &lnrpc.MPPRecord{ - PaymentAddr: invoices[i].PaymentAddr, - TotalAmtMsat: int64( - lnwire.NewMSatFromSatoshis(paymentAmt), - ), + if err != nil { + t.Fatalf("unable to recv invoice update: %v", err) } - sendReq := &routerrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: &route, + // The invoice update should exactly match the invoice created + // above, but should now be settled and have SettleDate + if !invoiceUpdate.Settled { // nolint:staticcheck + t.Fatalf("invoice not settled but should be") } - resp, err := net.Alice.RouterClient.SendToRouteV2(ctxt, sendReq) - if err != nil { - t.Fatalf("unable to send payment: %v", err) + if invoiceUpdate.SettleDate == 0 { + t.Fatalf("invoice should have non zero settle date, but doesn't") } - if resp.Failure != nil { - t.Fatalf("received payment error: %v", resp.Failure) + if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) { + t.Fatalf("payment preimages don't match: expected %v, got %v", + invoice.RPreimage, invoiceUpdate.RPreimage) } - } - - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when - // creating the seed nodes in the network. - const baseFee = 1 - - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Carol, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment - // transaction, in channel Alice->Bob->Carol, order is Carol, Bob, - // Alice. - const amountPaid = int64(5000) - assertAmountPaid(t, "Bob(local) => Carol(remote)", carol, - bobFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Bob(local) => Carol(remote)", net.Bob, - bobFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointBob, false) -} -// testSendToRouteErrorPropagation tests propagation of errors that occur -// while processing a multi-hop payment through an unknown route. -func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() + if invoiceUpdate.SettleIndex == 0 { + t.Fatalf("invoice should have settle index") + } - const chanAmt = btcutil.Amount(100000) + settleIndex = invoiceUpdate.SettleIndex - // Open a channel with 100k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) + close(updateSent) + }() + // Wait for the channel to be recognized by both Alice and Bob before + // continuing the rest of the test. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { - t.Fatalf("alice didn't advertise her channel: %v", err) + // TODO(roasbeef): will need to make num blocks to advertise a + // node param + close(quit) + t.Fatalf("channel not seen by alice before timeout: %v", err) } - // Create a new nodes (Carol and Charlie), load her with some funds, - // then establish a connection between Carol and Charlie with a channel - // that has identical capacity to the one created above.Then we will - // get route via queryroutes call which will be fake route for Alice -> - // Bob graph. - // - // The network topology should now look like: Alice -> Bob; Carol -> Charlie. - carol, err := net.NewNode("Carol", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) + // With the assertion above set up, send a payment from Alice to Bob + // which should finalize and settle the invoice. + sendReq := &routerrpc.SendPaymentRequest{ + PaymentRequest: invoiceResp.PaymentRequest, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, } - defer shutdownAndAssert(net, t, carol) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + stream, err := net.Alice.RouterClient.SendPaymentV2(ctxt, sendReq) if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) + close(quit) + t.Fatalf("unable to send payment: %v", err) } - - charlie, err := net.NewNode("Charlie", nil) + result, err := getPaymentResult(stream) if err != nil { - t.Fatalf("unable to create new nodes: %v", err) + close(quit) + t.Fatalf("cannot get payment result: %v", err) } - defer shutdownAndAssert(net, t, charlie) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, charlie) - if err != nil { - t.Fatalf("unable to send coins to charlie: %v", err) + if result.Status != lnrpc.Payment_SUCCEEDED { + close(quit) + t.Fatalf("error when attempting recv: %v", result.Status) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, charlie); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) + select { + case <-time.After(time.Second * 10): + close(quit) + t.Fatalf("update not sent after 10 seconds") + case <-updateSent: // Fall through on success } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, charlie, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, + // With the base case working, we'll now cancel Bob's current + // subscription in order to exercise the backlog fill behavior. + cancelInvoiceSubscription() + + // We'll now add 3 more invoices to Bob's invoice registry. + const numInvoices = 3 + payReqs, _, newInvoices, err := createPayReqs( + net.Bob, paymentAmt, numInvoices, ) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) if err != nil { - t.Fatalf("carol didn't advertise her channel: %v", err) + t.Fatalf("unable to create pay reqs: %v", err) } - // Query routes from Carol to Charlie which will be an invalid route - // for Alice -> Bob. - fakeReq := &lnrpc.QueryRoutesRequest{ - PubKey: charlie.PubKeyStr, - Amt: int64(1), + // Now that the set of invoices has been added, we'll re-register for + // streaming invoice notifications for Bob, this time specifying the + // add invoice of the last prior invoice. + req = &lnrpc.InvoiceSubscription{ + AddIndex: lastAddIndex, } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fakeRoute, err := carol.QueryRoutes(ctxt, fakeReq) + ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) + bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) if err != nil { - t.Fatalf("unable get fake route: %v", err) + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - // Create 1 invoices for Bob, which expect a payment from Alice for 1k - // satoshis - const paymentAmt = 1000 + // Since we specified a value of the prior add index above, we should + // now immediately get the invoices we just added as we should get the + // backlog of notifications. + for i := 0; i < numInvoices; i++ { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + if err != nil { + t.Fatalf("unable to receive subscription") + } - invoice := &lnrpc.Invoice{ - Memo: "testing", - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Bob.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) + // We should now get the ith invoice we added, as they should + // be returned in order. + if invoiceUpdate.Settled { // nolint:staticcheck + t.Fatalf("should have only received add events") + } + originalInvoice := newInvoices[i] + rHash := sha256.Sum256(originalInvoice.RPreimage) + if !bytes.Equal(invoiceUpdate.RHash, rHash[:]) { + t.Fatalf("invoices have mismatched payment hashes: "+ + "expected %x, got %x", rHash[:], + invoiceUpdate.RHash) + } } - rHash := resp.RHash + cancelInvoiceSubscription() - // Using Alice as the source, pay to the 5 invoices from Bob created above. + // We'll now have Bob settle out the remainder of these invoices so we + // can test that all settled invoices are properly notified. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - alicePayStream, err := net.Alice.SendToRoute(ctxt) + err = completePaymentRequests( + ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, + ) if err != nil { - t.Fatalf("unable to create payment stream for alice: %v", err) + t.Fatalf("unable to send payment: %v", err) } - sendReq := &lnrpc.SendToRouteRequest{ - PaymentHash: rHash, - Route: fakeRoute.Routes[0], + // With the set of invoices paid, we'll now cancel the old + // subscription, and create a new one for Bob, this time using the + // settle index to obtain the backlog of settled invoices. + req = &lnrpc.InvoiceSubscription{ + SettleIndex: settleIndex, } - - if err := alicePayStream.Send(sendReq); err != nil { - t.Fatalf("unable to send payment: %v", err) + ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) + bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) + if err != nil { + t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) } - // At this place we should get an rpc error with notification - // that edge is not found on hop(0) - if _, err := alicePayStream.Recv(); err != nil && strings.Contains(err.Error(), - "edge not found") { + defer cancelInvoiceSubscription() - } else if err != nil { - t.Fatalf("payment stream has been closed but fake route has consumed: %v", err) + // As we specified the index of the past settle index, we should now + // receive notifications for the three HTLCs that we just settled. As + // the order that the HTLCs will be settled in is partially randomized, + // we'll use a map to assert that the proper set has been settled. + settledInvoices := make(map[[32]byte]struct{}) + for _, invoice := range newInvoices { + rHash := sha256.Sum256(invoice.RPreimage) + settledInvoices[rHash] = struct{}{} } - - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) -} - -// testUnannouncedChannels checks unannounced channels are not returned by -// describeGraph RPC request unless explicitly asked for. -func testUnannouncedChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - amount := funding.MaxBtcFundingAmount - - // Open a channel between Alice and Bob, ensuring the - // channel has been opened properly. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanOpenUpdate := openChannelStream( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: amount, - }, - ) - - // Mine 2 blocks, and check that the channel is opened but not yet - // announced to the network. - mineBlocks(t, net, 2, 1) - - // One block is enough to make the channel ready for use, since the - // nodes have defaultNumConfs=1 set. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fundingChanPoint, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) - if err != nil { - t.Fatalf("error while waiting for channel open: %v", err) - } - - // Alice should have 1 edge in her graph. - req := &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: true, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err := net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query alice's graph: %v", err) - } - - numEdges := len(chanGraph.Edges) - if numEdges != 1 { - t.Fatalf("expected to find 1 edge in the graph, found %d", numEdges) - } - - // Channels should not be announced yet, hence Alice should have no - // announced edges in her graph. - req.IncludeUnannounced = false - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query alice's graph: %v", err) - } - - numEdges = len(chanGraph.Edges) - if numEdges != 0 { - t.Fatalf("expected to find 0 announced edges in the graph, found %d", - numEdges) - } - - // Mine 4 more blocks, and check that the channel is now announced. - mineBlocks(t, net, 4, 0) - - // Give the network a chance to learn that auth proof is confirmed. - var predErr error - err = wait.Predicate(func() bool { - // The channel should now be announced. Check that Alice has 1 - // announced edge. - req.IncludeUnannounced = false - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - predErr = fmt.Errorf("unable to query alice's graph: %v", err) - return false - } - - numEdges = len(chanGraph.Edges) - if numEdges != 1 { - predErr = fmt.Errorf("expected to find 1 announced edge in "+ - "the graph, found %d", numEdges) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } - - // The channel should now be announced. Check that Alice has 1 announced - // edge. - req.IncludeUnannounced = false - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err = net.Alice.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable to query alice's graph: %v", err) - } - - numEdges = len(chanGraph.Edges) - if numEdges != 1 { - t.Fatalf("expected to find 1 announced edge in the graph, found %d", - numEdges) - } - - // Close the channel used during the test. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, fundingChanPoint, false) -} - -// testPrivateChannels tests that a private channel can be used for -// routing by the two endpoints of the channel, but is not known by -// the rest of the nodes in the graph. -func testPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint - - // We create the following topology: - // - // Dave --100k--> Alice --200k--> Bob - // ^ ^ - // | | - // 100k 100k - // | | - // +---- Carol ----+ - // - // where the 100k channel between Carol and Alice is private. - - // Open a channel with 200k satoshis between Alice and Bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt * 2, - }, - ) - networkChans = append(networkChans, chanPointAlice) - - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, - } - - // Create Dave, and a channel to Alice of 100k. - dave, err := net.NewNode("Dave", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, dave) - if err != nil { - t.Fatalf("unable to send coins to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointDave := openChannelAndAssert( - ctxt, t, net, dave, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - daveFundPoint := wire.OutPoint{ - Hash: *daveChanTXID, - Index: chanPointDave.OutputIndex, - } - - // Next, we'll create Carol and establish a channel from her to - // Dave of 100k. - carol, err := net.NewNode("Carol", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) - if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointCarol) - - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, - } - - // Wait for all nodes to have seen all these channels, as they - // are all public. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } - } - } - // Now create a _private_ channel directly between Carol and - // Alice of 100k. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanOpenUpdate := openChannelStream( - ctxt, t, net, carol, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - if err != nil { - t.Fatalf("unable to open channel: %v", err) - } - - // One block is enough to make the channel ready for use, since the - // nodes have defaultNumConfs=1 set. - block := mineBlocks(t, net, 1, 1)[0] - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - chanPointPrivate, err := net.WaitForChannelOpen(ctxt, chanOpenUpdate) - if err != nil { - t.Fatalf("error while waiting for channel open: %v", err) - } - fundingTxID, err := lnrpc.GetChanPointFundingTxid(chanPointPrivate) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - assertTxInBlock(t, block, fundingTxID) - - // The channel should be listed in the peer information returned by - // both peers. - privateFundPoint := wire.OutPoint{ - Hash: *fundingTxID, - Index: chanPointPrivate.OutputIndex, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.AssertChannelExists(ctxt, carol, &privateFundPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.AssertChannelExists(ctxt, net.Alice, &privateFundPoint) - if err != nil { - t.Fatalf("unable to assert channel existence: %v", err) - } - - // The channel should be available for payments between Carol and Alice. - // We check this by sending payments from Carol to Bob, that - // collectively would deplete at least one of Carol's channels. - - // Create 2 invoices for Bob, each of 70k satoshis. Since each of - // Carol's channels is of size 100k, these payments cannot succeed - // by only using one of the channels. - const numPayments = 2 - const paymentAmt = 70000 - payReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - time.Sleep(time.Millisecond * 50) - - // Let Carol pay the invoices. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, carol, carol.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when - // creating the seed nodes in the network. - const baseFee = 1 - - // Bob should have received 140k satoshis from Alice. - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), 2*paymentAmt) - - // Alice sent 140k to Bob. - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, 2*paymentAmt, int64(0)) - - // Alice received 70k + fee from Dave. - assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, - daveFundPoint, int64(0), paymentAmt+baseFee) - - // Dave sent 70k+fee to Alice. - assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, - daveFundPoint, paymentAmt+baseFee, int64(0)) - - // Dave received 70k+fee of two hops from Carol. - assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, - carolFundPoint, int64(0), paymentAmt+baseFee*2) - - // Carol sent 70k+fee of two hops to Dave. - assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, - carolFundPoint, paymentAmt+baseFee*2, int64(0)) - - // Alice received 70k+fee from Carol. - assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", - net.Alice, privateFundPoint, int64(0), paymentAmt+baseFee) - - // Carol sent 70k+fee to Alice. - assertAmountPaid(t, "Carol(local) [private=>] Alice(remote)", - carol, privateFundPoint, paymentAmt+baseFee, int64(0)) - - // Alice should also be able to route payments using this channel, - // so send two payments of 60k back to Carol. - const paymentAmt60k = 60000 - payReqs, _, _, err = createPayReqs( - carol, paymentAmt60k, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - time.Sleep(time.Millisecond * 50) - - // Let Bob pay the invoices. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } - - // Finally, we make sure Dave and Bob does not know about the - // private channel between Carol and Alice. We first mine - // plenty of blocks, such that the channel would have been - // announced in case it was public. - mineBlocks(t, net, 10, 0) - - // We create a helper method to check how many edges each of the - // nodes know about. Carol and Alice should know about 4, while - // Bob and Dave should only know about 3, since one channel is - // private. - numChannels := func(node *lntest.HarnessNode, includeUnannounced bool) int { - req := &lnrpc.ChannelGraphRequest{ - IncludeUnannounced: includeUnannounced, - } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - chanGraph, err := node.DescribeGraph(ctxt, req) - if err != nil { - t.Fatalf("unable go describegraph: %v", err) - } - return len(chanGraph.Edges) - } - - var predErr error - err = wait.Predicate(func() bool { - aliceChans := numChannels(net.Alice, true) - if aliceChans != 4 { - predErr = fmt.Errorf("expected Alice to know 4 edges, "+ - "had %v", aliceChans) - return false - } - alicePubChans := numChannels(net.Alice, false) - if alicePubChans != 3 { - predErr = fmt.Errorf("expected Alice to know 3 public edges, "+ - "had %v", alicePubChans) - return false - } - bobChans := numChannels(net.Bob, true) - if bobChans != 3 { - predErr = fmt.Errorf("expected Bob to know 3 edges, "+ - "had %v", bobChans) - return false - } - carolChans := numChannels(carol, true) - if carolChans != 4 { - predErr = fmt.Errorf("expected Carol to know 4 edges, "+ - "had %v", carolChans) - return false - } - carolPubChans := numChannels(carol, false) - if carolPubChans != 3 { - predErr = fmt.Errorf("expected Carol to know 3 public edges, "+ - "had %v", carolPubChans) - return false - } - daveChans := numChannels(dave, true) - if daveChans != 3 { - predErr = fmt.Errorf("expected Dave to know 3 edges, "+ - "had %v", daveChans) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("%v", predErr) - } - - // Close all channels. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointPrivate, false) -} - -// testInvoiceRoutingHints tests that the routing hints for an invoice are -// created properly. -func testInvoiceRoutingHints(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(100000) - - // Throughout this test, we'll be opening a channel between Alice and - // several other parties. - // - // First, we'll create a private channel between Alice and Bob. This - // will be the only channel that will be considered as a routing hint - // throughout this test. We'll include a push amount since we currently - // require channels to have enough remote balance to cover the invoice's - // payment. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - Private: true, - }, - ) - - // Then, we'll create Carol's node and open a public channel between her - // and Alice. This channel will not be considered as a routing hint due - // to it being public. - carol, err := net.NewNode("Carol", nil) - if err != nil { - t.Fatalf("unable to create carol's node: %v", err) - } - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Alice, carol); err != nil { - t.Fatalf("unable to connect alice to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - }, - ) - - // We'll also create a public channel between Bob and Carol to ensure - // that Bob gets selected as the only routing hint. We do this as - // we should only include routing hints for nodes that are publicly - // advertised, otherwise we'd end up leaking information about nodes - // that wish to stay unadvertised. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { - t.Fatalf("unable to connect alice to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBobCarol := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - }, - ) - - // Then, we'll create Dave's node and open a private channel between him - // and Alice. We will not include a push amount in order to not consider - // this channel as a routing hint as it will not have enough remote - // balance for the invoice's amount. - dave, err := net.NewNode("Dave", nil) - if err != nil { - t.Fatalf("unable to create dave's node: %v", err) - } - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Alice, dave); err != nil { - t.Fatalf("unable to connect alice to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointDave := openChannelAndAssert( - ctxt, t, net, net.Alice, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - - // Finally, we'll create Eve's node and open a private channel between - // her and Alice. This time though, we'll take Eve's node down after the - // channel has been created to avoid populating routing hints for - // inactive channels. - eve, err := net.NewNode("Eve", nil) - if err != nil { - t.Fatalf("unable to create eve's node: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Alice, eve); err != nil { - t.Fatalf("unable to connect alice to eve: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointEve := openChannelAndAssert( - ctxt, t, net, net.Alice, eve, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: chanAmt / 2, - Private: true, - }, - ) - - // Make sure all the channels have been opened. - chanNames := []string{ - "alice-bob", "alice-carol", "bob-carol", "alice-dave", - "alice-eve", - } - aliceChans := []*lnrpc.ChannelPoint{ - chanPointBob, chanPointCarol, chanPointBobCarol, chanPointDave, - chanPointEve, - } - for i, chanPoint := range aliceChans { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("timed out waiting for channel open %s: %v", - chanNames[i], err) - } - } - - // Now that the channels are open, we'll take down Eve's node. - shutdownAndAssert(net, t, eve) - - // Create an invoice for Alice that will populate the routing hints. - invoice := &lnrpc.Invoice{ - Memo: "routing hints", - Value: int64(chanAmt / 4), - Private: true, - } - - // Due to the way the channels were set up above, the channel between - // Alice and Bob should be the only channel used as a routing hint. - var predErr error - var decoded *lnrpc.PayReq - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := net.Alice.AddInvoice(ctxt, invoice) - if err != nil { - predErr = fmt.Errorf("unable to add invoice: %v", err) - return false - } - - // We'll decode the invoice's payment request to determine which - // channels were used as routing hints. - payReq := &lnrpc.PayReqString{ - PayReq: resp.PaymentRequest, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - decoded, err = net.Alice.DecodePayReq(ctxt, payReq) - if err != nil { - predErr = fmt.Errorf("unable to decode payment "+ - "request: %v", err) - return false - } - - if len(decoded.RouteHints) != 1 { - predErr = fmt.Errorf("expected one route hint, got %d", - len(decoded.RouteHints)) - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf(predErr.Error()) - } - - hops := decoded.RouteHints[0].HopHints - if len(hops) != 1 { - t.Fatalf("expected one hop in route hint, got %d", len(hops)) - } - chanID := hops[0].ChanId - - // We'll need the short channel ID of the channel between Alice and Bob - // to make sure the routing hint is for this channel. - listReq := &lnrpc.ListChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - listResp, err := net.Alice.ListChannels(ctxt, listReq) - if err != nil { - t.Fatalf("unable to retrieve alice's channels: %v", err) - } - - var aliceBobChanID uint64 - for _, channel := range listResp.Channels { - if channel.RemotePubkey == net.Bob.PubKeyStr { - aliceBobChanID = channel.ChanId - } - } - - if aliceBobChanID == 0 { - t.Fatalf("channel between alice and bob not found") - } - - if chanID != aliceBobChanID { - t.Fatalf("expected channel ID %d, got %d", aliceBobChanID, - chanID) - } - - // Now that we've confirmed the routing hints were added correctly, we - // can close all the channels and shut down all the nodes created. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointBob, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointCarol, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobCarol, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointDave, false) - - // The channel between Alice and Eve should be force closed since Eve - // is offline. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointEve, true) - - // Cleanup by mining the force close and sweep transaction. - cleanupForceClose(t, net, net.Alice, chanPointEve) -} - -// testMultiHopOverPrivateChannels tests that private channels can be used as -// intermediate hops in a route for payments. -func testMultiHopOverPrivateChannels(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - // We'll test that multi-hop payments over private channels work as - // intended. To do so, we'll create the following topology: - // private public private - // Alice <--100k--> Bob <--100k--> Carol <--100k--> Dave - const chanAmt = btcutil.Amount(100000) - - // First, we'll open a private channel between Alice and Bob with Alice - // being the funder. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err := net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointAlice) - if err != nil { - t.Fatalf("alice didn't see the channel alice <-> bob before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice) - if err != nil { - t.Fatalf("bob didn't see the channel alice <-> bob before "+ - "timeout: %v", err) - } - - // Retrieve Alice's funding outpoint. - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, - } - - // Next, we'll create Carol's node and open a public channel between - // her and Bob with Bob being the funder. - carol, err := net.NewNode("Carol", nil) - if err != nil { - t.Fatalf("unable to create carol's node: %v", err) - } - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, net.Bob, carol); err != nil { - t.Fatalf("unable to connect bob to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("bob didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("carol didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("alice didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - - // Retrieve Bob's funding outpoint. - bobChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointBob) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - bobFundPoint := wire.OutPoint{ - Hash: *bobChanTXID, - Index: chanPointBob.OutputIndex, - } - - // Next, we'll create Dave's node and open a private channel between him - // and Carol with Carol being the funder. - dave, err := net.NewNode("Dave", nil) - if err != nil { - t.Fatalf("unable to create dave's node: %v", err) - } - defer shutdownAndAssert(net, t, dave) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) - if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - Private: true, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("carol didn't see the channel carol <-> dave before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("dave didn't see the channel carol <-> dave before "+ - "timeout: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointBob) - if err != nil { - t.Fatalf("dave didn't see the channel bob <-> carol before "+ - "timeout: %v", err) - } - - // Retrieve Carol's funding point. - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, - } - - // Now that all the channels are set up according to the topology from - // above, we can proceed to test payments. We'll create an invoice for - // Dave of 20k satoshis and pay it with Alice. Since there is no public - // route from Alice to Dave, we'll need to use the private channel - // between Carol and Dave as a routing hint encoded in the invoice. - const paymentAmt = 20000 - - // Create the invoice for Dave. - invoice := &lnrpc.Invoice{ - Memo: "two hopz!", - Value: paymentAmt, - Private: true, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := dave.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice for dave: %v", err) - } - - // Let Alice pay the invoice. - payReqs := []string{resp.PaymentRequest} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments from alice to dave: %v", err) - } - - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when opening - // the channels. - const baseFee = 1 - - // Dave should have received 20k satoshis from Carol. - assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", - dave, carolFundPoint, 0, paymentAmt) - - // Carol should have sent 20k satoshis to Dave. - assertAmountPaid(t, "Carol(local) [private=>] Dave(remote)", - carol, carolFundPoint, paymentAmt, 0) - - // Carol should have received 20k satoshis + fee for one hop from Bob. - assertAmountPaid(t, "Bob(local) => Carol(remote)", - carol, bobFundPoint, 0, paymentAmt+baseFee) - - // Bob should have sent 20k satoshis + fee for one hop to Carol. - assertAmountPaid(t, "Bob(local) => Carol(remote)", - net.Bob, bobFundPoint, paymentAmt+baseFee, 0) - - // Bob should have received 20k satoshis + fee for two hops from Alice. - assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Bob, - aliceFundPoint, 0, paymentAmt+baseFee*2) - - // Alice should have sent 20k satoshis + fee for two hops to Bob. - assertAmountPaid(t, "Alice(local) [private=>] Bob(remote)", net.Alice, - aliceFundPoint, paymentAmt+baseFee*2, 0) - - // At this point, the payment was successful. We can now close all the - // channels and shutdown the nodes created throughout this test. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) -} - -func testInvoiceSubscriptions(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(500000) - - // Open a channel with 500k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - - // Next create a new invoice for Bob requesting 1k satoshis. - // TODO(roasbeef): make global list of invoices for each node to re-use - // and avoid collisions - const paymentAmt = 1000 - invoice := &lnrpc.Invoice{ - Memo: "testing", - RPreimage: makeFakePayHash(t), - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err := net.Bob.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) - } - lastAddIndex := invoiceResp.AddIndex - - // Create a new invoice subscription client for Bob, the notification - // should be dispatched shortly below. - req := &lnrpc.InvoiceSubscription{} - ctx, cancelInvoiceSubscription := context.WithCancel(ctxb) - bobInvoiceSubscription, err := net.Bob.SubscribeInvoices(ctx, req) - if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) - } - - var settleIndex uint64 - quit := make(chan struct{}) - updateSent := make(chan struct{}) - go func() { - invoiceUpdate, err := bobInvoiceSubscription.Recv() - select { - case <-quit: - // Received cancellation - return - default: - } - - if err != nil { - t.Fatalf("unable to recv invoice update: %v", err) - } - - // The invoice update should exactly match the invoice created - // above, but should now be settled and have SettleDate - if !invoiceUpdate.Settled { - t.Fatalf("invoice not settled but should be") - } - if invoiceUpdate.SettleDate == 0 { - t.Fatalf("invoice should have non zero settle date, but doesn't") - } - - if !bytes.Equal(invoiceUpdate.RPreimage, invoice.RPreimage) { - t.Fatalf("payment preimages don't match: expected %v, got %v", - invoice.RPreimage, invoiceUpdate.RPreimage) - } - - if invoiceUpdate.SettleIndex == 0 { - t.Fatalf("invoice should have settle index") - } - - settleIndex = invoiceUpdate.SettleIndex - - close(updateSent) - }() - - // Wait for the channel to be recognized by both Alice and Bob before - // continuing the rest of the test. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - // TODO(roasbeef): will need to make num blocks to advertise a - // node param - close(quit) - t.Fatalf("channel not seen by alice before timeout: %v", err) - } - - // With the assertion above set up, send a payment from Alice to Bob - // which should finalize and settle the invoice. - sendReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - stream, err := net.Alice.RouterClient.SendPaymentV2(ctxt, sendReq) - if err != nil { - close(quit) - t.Fatalf("unable to send payment: %v", err) - } - result, err := getPaymentResult(stream) - if err != nil { - close(quit) - t.Fatalf("cannot get payment result: %v", err) - } - if result.Status != lnrpc.Payment_SUCCEEDED { - close(quit) - t.Fatalf("error when attempting recv: %v", result.Status) - } - - select { - case <-time.After(time.Second * 10): - close(quit) - t.Fatalf("update not sent after 10 seconds") - case <-updateSent: // Fall through on success - } - - // With the base case working, we'll now cancel Bob's current - // subscription in order to exercise the backlog fill behavior. - cancelInvoiceSubscription() - - // We'll now add 3 more invoices to Bob's invoice registry. - const numInvoices = 3 - payReqs, _, newInvoices, err := createPayReqs( - net.Bob, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Now that the set of invoices has been added, we'll re-register for - // streaming invoice notifications for Bob, this time specifying the - // add invoice of the last prior invoice. - req = &lnrpc.InvoiceSubscription{ - AddIndex: lastAddIndex, - } - ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) - bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) - if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) - } - - // Since we specified a value of the prior add index above, we should - // now immediately get the invoices we just added as we should get the - // backlog of notifications. - for i := 0; i < numInvoices; i++ { - invoiceUpdate, err := bobInvoiceSubscription.Recv() - if err != nil { - t.Fatalf("unable to receive subscription") - } - - // We should now get the ith invoice we added, as they should - // be returned in order. - if invoiceUpdate.Settled { - t.Fatalf("should have only received add events") - } - originalInvoice := newInvoices[i] - rHash := sha256.Sum256(originalInvoice.RPreimage[:]) - if !bytes.Equal(invoiceUpdate.RHash, rHash[:]) { - t.Fatalf("invoices have mismatched payment hashes: "+ - "expected %x, got %x", rHash[:], - invoiceUpdate.RHash) - } - } - - cancelInvoiceSubscription() - - // We'll now have Bob settle out the remainder of these invoices so we - // can test that all settled invoices are properly notified. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Alice, net.Alice.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payment: %v", err) - } - - // With the set of invoices paid, we'll now cancel the old - // subscription, and create a new one for Bob, this time using the - // settle index to obtain the backlog of settled invoices. - req = &lnrpc.InvoiceSubscription{ - SettleIndex: settleIndex, - } - ctx, cancelInvoiceSubscription = context.WithCancel(ctxb) - bobInvoiceSubscription, err = net.Bob.SubscribeInvoices(ctx, req) - if err != nil { - t.Fatalf("unable to subscribe to bob's invoice updates: %v", err) - } - - defer cancelInvoiceSubscription() - - // As we specified the index of the past settle index, we should now - // receive notifications for the three HTLCs that we just settled. As - // the order that the HTLCs will be settled in is partially randomized, - // we'll use a map to assert that the proper set has been settled. - settledInvoices := make(map[[32]byte]struct{}) - for _, invoice := range newInvoices { - rHash := sha256.Sum256(invoice.RPreimage[:]) - settledInvoices[rHash] = struct{}{} - } - for i := 0; i < numInvoices; i++ { - invoiceUpdate, err := bobInvoiceSubscription.Recv() - if err != nil { - t.Fatalf("unable to receive subscription") - } + for i := 0; i < numInvoices; i++ { + invoiceUpdate, err := bobInvoiceSubscription.Recv() + if err != nil { + t.Fatalf("unable to receive subscription") + } // We should now get the ith invoice we added, as they should // be returned in order. - if !invoiceUpdate.Settled { + if !invoiceUpdate.Settled { // nolint:staticcheck t.Fatalf("should have only received settle events") } @@ -8127,11 +6601,9 @@ func testGarbageCollectLinkNodes(net *lntest.NetworkHarness, t *harnessTest) { } predErr = checkNumForceClosedChannels(pendingChanResp, 0) - if predErr != nil { - return false - } - return true + return predErr == nil + }, defaultTimeout) if err != nil { t.Fatalf("channels not marked as fully resolved: %v", predErr) @@ -8554,7 +7026,7 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - carolChan, err = getChanInfo(ctxt, carol) + _, err = getChanInfo(ctxt, carol) if err != nil { t.Fatalf("unable to get carol chan info: %v", err) } @@ -8586,13 +7058,14 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness // feel the wrath of Dave's retribution. var ( closeUpdates lnrpc.Lightning_CloseChannelClient - closeTxId *chainhash.Hash + closeTxID *chainhash.Hash closeErr error - force bool = true ) + + force := true err = wait.Predicate(func() bool { ctxt, _ := context.WithTimeout(ctxb, channelCloseTimeout) - closeUpdates, closeTxId, closeErr = net.CloseChannel( + closeUpdates, closeTxID, closeErr = net.CloseChannel( ctxt, carol, chanPoint, force, ) return closeErr == nil @@ -8608,9 +7081,9 @@ func testRevokedCloseRetributionZeroValueRemoteOutput(net *lntest.NetworkHarness t.Fatalf("unable to find Carol's force close tx in mempool: %v", err) } - if *txid != *closeTxId { + if *txid != *closeTxID { t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxId, txid) + closeTxID, txid) } // Finally, generate a single block, wait for the final close status @@ -8925,7 +7398,7 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // feel the wrath of Dave's retribution. force := true ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeUpdates, closeTxId, err := net.CloseChannel(ctxt, carol, + closeUpdates, closeTxID, err := net.CloseChannel(ctxt, carol, chanPoint, force) if err != nil { t.Fatalf("unable to close channel: %v", err) @@ -8938,9 +7411,9 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, t.Fatalf("unable to find Carol's force close tx in mempool: %v", err) } - if *txid != *closeTxId { + if *txid != *closeTxID { t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxId, txid) + closeTxID, txid) } // Generate a single block to mine the breach transaction. @@ -8959,9 +7432,9 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, if err != nil { t.Fatalf("error while waiting for channel close: %v", err) } - if *breachTXID != *closeTxId { + if *breachTXID != *closeTxID { t.Fatalf("expected breach ID(%v) to be equal to close ID (%v)", - breachTXID, closeTxId) + breachTXID, closeTxID) } assertTxInBlock(t, block, breachTXID) @@ -9058,11 +7531,8 @@ func testRevokedCloseRetributionRemoteHodl(net *lntest.NetworkHarness, // The sole input should be spending from the commit tx. txIn := secondLevel.MsgTx().TxIn[0] - if !bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:]) { - return false - } - return true + return bytes.Equal(txIn.PreviousOutPoint.Hash[:], commitTxid[:]) } // Check that all the inputs of this transaction are spending outputs @@ -9391,7 +7861,7 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( // broadcasting his current channel state. This is actually the // commitment transaction of a prior *revoked* state, so he'll soon // feel the wrath of Dave's retribution. - closeUpdates, closeTxId, err := net.CloseChannel( + closeUpdates, closeTxID, err := net.CloseChannel( ctxb, carol, chanPoint, true, ) if err != nil { @@ -9405,9 +7875,9 @@ func testRevokedCloseRetributionAltruistWatchtowerCase( t.Fatalf("unable to find Carol's force close tx in mempool: %v", err) } - if *txid != *closeTxId { + if *txid != *closeTxID { t.Fatalf("expected closeTx(%v) in mempool, instead found %v", - closeTxId, txid) + closeTxID, txid) } // Finally, generate a single block, wait for the final close status @@ -9799,7 +8269,7 @@ func testDataLossProtection(net *lntest.NetworkHarness, t *harnessTest) { // node that Carol will pay to in order to advance the state of // the channel. // TODO(halseth): have dangling HTLCs on the commitment, able to - // retrive funds? + // retrieve funds? payReqs, _, _, err := createPayReqs( node, paymentAmt, numInvoices, ) @@ -10265,7 +8735,7 @@ type graphSubscription struct { // subscribeGraphNotifications subscribes to channel graph updates and launches // a goroutine that forwards these to the returned channel. -func subscribeGraphNotifications(t *harnessTest, ctxb context.Context, +func subscribeGraphNotifications(ctxb context.Context, t *harnessTest, node *lntest.HarnessNode) graphSubscription { // We'll first start by establishing a notification client which will @@ -10427,9 +8897,7 @@ func testGraphTopologyNtfns(net *lntest.NetworkHarness, t *harnessTest, pinned b waitForGraphSync(t, alice) // Let Alice subscribe to graph notifications. - graphSub := subscribeGraphNotifications( - t, ctxb, alice, - ) + graphSub := subscribeGraphNotifications(ctxb, t, alice) defer close(graphSub.quit) // Open a new channel between Alice and Bob. @@ -10652,7 +9120,7 @@ out: func testNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - aliceSub := subscribeGraphNotifications(t, ctxb, net.Alice) + aliceSub := subscribeGraphNotifications(ctxb, t, net.Alice) defer close(aliceSub.quit) advertisedAddrs := []string{ @@ -10722,7 +9190,7 @@ func testNodeAnnouncement(net *lntest.NetworkHarness, t *harnessTest) { for _, update := range graphUpdate.NodeUpdates { if update.IdentityKey == nodePubKey { assertAddrs( - update.Addresses, + update.Addresses, // nolint:staticcheck targetAddrs..., ) return @@ -10827,181 +9295,23 @@ func testNodeSignVerify(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(ctxt, t, net, net.Alice, aliceBobCh, false) } -// testAsyncPayments tests the performance of the async payments. -func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const ( - paymentAmt = 100 - ) - - // First establish a channel with a capacity equals to the overall - // amount of payments, between Alice and Bob, at the end of the test - // Alice should send all money from her side to Bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - channelCapacity := btcutil.Amount(paymentAmt * 2000) - chanPoint := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: channelCapacity, - }, - ) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - info, err := getChanInfo(ctxt, net.Alice) - if err != nil { - t.Fatalf("unable to get alice channel info: %v", err) - } - - // We'll create a number of invoices equal the max number of HTLCs that - // can be carried in one direction. The number on the commitment will - // likely be lower, but we can't guarantee that any more HTLCs will - // succeed due to the limited path diversity and inability of the router - // to retry via another path. - numInvoices := int(input.MaxHTLCNumber / 2) - - bobAmt := int64(numInvoices * paymentAmt) - aliceAmt := info.LocalBalance - bobAmt - - // With the channel open, we'll create invoices for Bob that Alice - // will pay to in order to advance the state of the channel. - bobPayReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // Wait for Alice to receive the channel edge from the funding manager. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("alice didn't see the alice->bob channel before "+ - "timeout: %v", err) - } - - // Simultaneously send payments from Alice to Bob using of Bob's payment - // hashes generated above. - now := time.Now() - errChan := make(chan error) - statusChan := make(chan *lnrpc.Payment) - for i := 0; i < numInvoices; i++ { - payReq := bobPayReqs[i] - go func() { - ctxt, _ = context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) - stream, err := net.Alice.RouterClient.SendPaymentV2( - ctxt, - &routerrpc.SendPaymentRequest{ - PaymentRequest: payReq, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - }, - ) - if err != nil { - errChan <- err - } - result, err := getPaymentResult(stream) - if err != nil { - errChan <- err - } - - statusChan <- result - }() - } - - // Wait until all the payments have settled. - for i := 0; i < numInvoices; i++ { - select { - case result := <-statusChan: - if result.Status == lnrpc.Payment_SUCCEEDED { - continue - } - - case err := <-errChan: - t.Fatalf("payment error: %v", err) - } - } - - // All payments have been sent, mark the finish time. - timeTaken := time.Since(now) - - // Next query for Bob's and Alice's channel states, in order to confirm - // that all payment have been successful transmitted. - - // Wait for the revocation to be received so alice no longer has pending - // htlcs listed and has correct balances. This is needed due to the fact - // that we now pipeline the settles. - err = wait.Predicate(func() bool { - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceChan, err := getChanInfo(ctxt, net.Alice) - if err != nil { - return false - } - if len(aliceChan.PendingHtlcs) != 0 { - return false - } - if aliceChan.RemoteBalance != bobAmt { - return false - } - if aliceChan.LocalBalance != aliceAmt { - return false - } - - return true - }, time.Second*5) - if err != nil { - t.Fatalf("failed to assert alice's pending htlcs and/or remote/local balance") - } - - // Wait for Bob to receive revocation from Alice. - time.Sleep(2 * time.Second) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - bobChan, err := getChanInfo(ctxt, net.Bob) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - if len(bobChan.PendingHtlcs) != 0 { - t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ - "expected %v", len(bobChan.PendingHtlcs), 0) - } - if bobChan.LocalBalance != bobAmt { - t.Fatalf("bob's local balance is incorrect, got %v, expected"+ - " %v", bobChan.LocalBalance, bobAmt) - } - if bobChan.RemoteBalance != aliceAmt { - t.Fatalf("bob's remote balance is incorrect, got %v, "+ - "expected %v", bobChan.RemoteBalance, aliceAmt) - } - - t.Log("\tBenchmark info: Elapsed time: ", timeTaken) - t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/float64(timeTaken.Seconds())) - - // Finally, immediately close the channel. This function will also - // block until the channel is closed and will additionally assert the - // relevant channel closing post conditions. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) -} - -// testBidirectionalAsyncPayments tests that nodes are able to send the -// payments to each other in async manner without blocking. -func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { +// testAsyncPayments tests the performance of the async payments. +func testAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const ( - paymentAmt = 1000 + paymentAmt = 100 ) // First establish a channel with a capacity equals to the overall // amount of payments, between Alice and Bob, at the end of the test // Alice should send all money from her side to Bob. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) + channelCapacity := btcutil.Amount(paymentAmt * 2000) chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: paymentAmt * 2000, - PushAmt: paymentAmt * 1000, + Amt: channelCapacity, }, ) @@ -11018,10 +9328,8 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) // to retry via another path. numInvoices := int(input.MaxHTLCNumber / 2) - // Nodes should exchange the same amount of money and because of this - // at the end balances should remain the same. - aliceAmt := info.LocalBalance - bobAmt := info.RemoteBalance + bobAmt := int64(numInvoices * paymentAmt) + aliceAmt := info.LocalBalance - bobAmt // With the channel open, we'll create invoices for Bob that Alice // will pay to in order to advance the state of the channel. @@ -11032,608 +9340,448 @@ func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) t.Fatalf("unable to create pay reqs: %v", err) } - // With the channel open, we'll create invoices for Alice that Bob - // will pay to in order to advance the state of the channel. - alicePayReqs, _, _, err := createPayReqs( - net.Alice, paymentAmt, numInvoices, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - // Wait for Alice to receive the channel edge from the funding manager. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint) + if err != nil { t.Fatalf("alice didn't see the alice->bob channel before "+ "timeout: %v", err) } - if err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { - t.Fatalf("bob didn't see the bob->alice channel before "+ - "timeout: %v", err) - } - - // Reset mission control to prevent previous payment results from - // interfering with this test. A new channel has been opened, but - // mission control operates on node pairs. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - _, err = net.Alice.RouterClient.ResetMissionControl( - ctxt, &routerrpc.ResetMissionControlRequest{}, - ) - if err != nil { - t.Fatalf("unable to reset mc for alice: %v", err) - } - // Send payments from Alice to Bob and from Bob to Alice in async - // manner. + // Simultaneously send payments from Alice to Bob using of Bob's payment + // hashes generated above. + now := time.Now() errChan := make(chan error) statusChan := make(chan *lnrpc.Payment) - - send := func(node *lntest.HarnessNode, payReq string) { + for i := 0; i < numInvoices; i++ { + payReq := bobPayReqs[i] go func() { - ctxt, _ = context.WithTimeout( - ctxb, lntest.AsyncBenchmarkTimeout, - ) - stream, err := node.RouterClient.SendPaymentV2( + ctxt, _ = context.WithTimeout(ctxb, lntest.AsyncBenchmarkTimeout) + stream, err := net.Alice.RouterClient.SendPaymentV2( ctxt, &routerrpc.SendPaymentRequest{ PaymentRequest: payReq, TimeoutSeconds: 60, FeeLimitMsat: noFeeLimitMsat, - }, - ) - if err != nil { - errChan <- err - } - result, err := getPaymentResult(stream) - if err != nil { - errChan <- err - } - - statusChan <- result - }() - } - - for i := 0; i < numInvoices; i++ { - send(net.Bob, alicePayReqs[i]) - send(net.Alice, bobPayReqs[i]) - } - - // Expect all payments to succeed. - for i := 0; i < 2*numInvoices; i++ { - select { - case result := <-statusChan: - if result.Status != lnrpc.Payment_SUCCEEDED { - t.Fatalf("payment error: %v", result.Status) - } - - case err := <-errChan: - t.Fatalf("payment error: %v", err) - } - } - - // Wait for Alice and Bob to receive revocations messages, and update - // states, i.e. balance info. - time.Sleep(1 * time.Second) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceInfo, err := getChanInfo(ctxt, net.Alice) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - if aliceInfo.RemoteBalance != bobAmt { - t.Fatalf("alice's remote balance is incorrect, got %v, "+ - "expected %v", aliceInfo.RemoteBalance, bobAmt) - } - if aliceInfo.LocalBalance != aliceAmt { - t.Fatalf("alice's local balance is incorrect, got %v, "+ - "expected %v", aliceInfo.LocalBalance, aliceAmt) - } - if len(aliceInfo.PendingHtlcs) != 0 { - t.Fatalf("alice's pending htlcs is incorrect, got %v, "+ - "expected %v", len(aliceInfo.PendingHtlcs), 0) - } - - // Next query for Bob's and Alice's channel states, in order to confirm - // that all payment have been successful transmitted. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - bobInfo, err := getChanInfo(ctxt, net.Bob) - if err != nil { - t.Fatalf("unable to get bob's channel info: %v", err) - } - - if bobInfo.LocalBalance != bobAmt { - t.Fatalf("bob's local balance is incorrect, got %v, expected"+ - " %v", bobInfo.LocalBalance, bobAmt) - } - if bobInfo.RemoteBalance != aliceAmt { - t.Fatalf("bob's remote balance is incorrect, got %v, "+ - "expected %v", bobInfo.RemoteBalance, aliceAmt) - } - if len(bobInfo.PendingHtlcs) != 0 { - t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ - "expected %v", len(bobInfo.PendingHtlcs), 0) - } - - // Finally, immediately close the channel. This function will also - // block until the channel is closed and will additionally assert the - // relevant channel closing post conditions. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) -} - -// assertActiveHtlcs makes sure all the passed nodes have the _exact_ HTLCs -// matching payHashes on _all_ their channels. -func assertActiveHtlcs(nodes []*lntest.HarnessNode, payHashes ...[]byte) error { - ctxb := context.Background() - - req := &lnrpc.ListChannelsRequest{} - for _, node := range nodes { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - nodeChans, err := node.ListChannels(ctxt, req) - if err != nil { - return fmt.Errorf("unable to get node chans: %v", err) - } - - for _, channel := range nodeChans.Channels { - // Record all payment hashes active for this channel. - htlcHashes := make(map[string]struct{}) - for _, htlc := range channel.PendingHtlcs { - h := hex.EncodeToString(htlc.HashLock) - _, ok := htlcHashes[h] - if ok { - return fmt.Errorf("duplicate HashLock") - } - htlcHashes[h] = struct{}{} - } - - // Channel should have exactly the payHashes active. - if len(payHashes) != len(htlcHashes) { - return fmt.Errorf("node %x had %v htlcs active, "+ - "expected %v", node.PubKey[:], - len(htlcHashes), len(payHashes)) - } - - // Make sure all the payHashes are active. - for _, payHash := range payHashes { - h := hex.EncodeToString(payHash) - if _, ok := htlcHashes[h]; ok { - continue - } - return fmt.Errorf("node %x didn't have the "+ - "payHash %v active", node.PubKey[:], - h) - } - } - } - - return nil -} - -func assertNumActiveHtlcsChanPoint(node *lntest.HarnessNode, - chanPoint wire.OutPoint, numHtlcs int) error { - ctxb := context.Background() - - req := &lnrpc.ListChannelsRequest{} - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - nodeChans, err := node.ListChannels(ctxt, req) - if err != nil { - return err - } - - for _, channel := range nodeChans.Channels { - if channel.ChannelPoint != chanPoint.String() { - continue - } - - if len(channel.PendingHtlcs) != numHtlcs { - return fmt.Errorf("expected %v active HTLCs, got %v", - numHtlcs, len(channel.PendingHtlcs)) - } - return nil - } - - return fmt.Errorf("channel point %v not found", chanPoint) -} - -func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { - ctxb := context.Background() - - req := &lnrpc.ListChannelsRequest{} - for _, node := range nodes { - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) - nodeChans, err := node.ListChannels(ctxt, req) - if err != nil { - return err - } - - for _, channel := range nodeChans.Channels { - if len(channel.PendingHtlcs) != numHtlcs { - return fmt.Errorf("expected %v HTLCs, got %v", - numHtlcs, len(channel.PendingHtlcs)) - } - } - } - - return nil -} - -func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { - - tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) - return tx.TxHash() -} - -// getSpendingTxInMempool waits for a transaction spending the given outpoint to -// appear in the mempool and returns that tx in full. -func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, - timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { - - breakTimeout := time.After(timeout) - ticker := time.NewTicker(50 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-breakTimeout: - t.Fatalf("didn't find tx in mempool") - case <-ticker.C: - mempool, err := miner.GetRawMempool() + }, + ) if err != nil { - t.Fatalf("unable to get mempool: %v", err) + errChan <- err + } + result, err := getPaymentResult(stream) + if err != nil { + errChan <- err } - if len(mempool) == 0 { + statusChan <- result + }() + } + + // Wait until all the payments have settled. + for i := 0; i < numInvoices; i++ { + select { + case result := <-statusChan: + if result.Status == lnrpc.Payment_SUCCEEDED { continue } - for _, txid := range mempool { - tx, err := miner.GetRawTransaction(txid) - if err != nil { - t.Fatalf("unable to fetch tx: %v", err) - } + case err := <-errChan: + t.Fatalf("payment error: %v", err) + } + } - msgTx := tx.MsgTx() - for _, txIn := range msgTx.TxIn { - if txIn.PreviousOutPoint == chanPoint { - return msgTx - } - } - } + // All payments have been sent, mark the finish time. + timeTaken := time.Since(now) + + // Next query for Bob's and Alice's channel states, in order to confirm + // that all payment have been successful transmitted. + + // Wait for the revocation to be received so alice no longer has pending + // htlcs listed and has correct balances. This is needed due to the fact + // that we now pipeline the settles. + err = wait.Predicate(func() bool { + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + aliceChan, err := getChanInfo(ctxt, net.Alice) + if err != nil { + return false } + if len(aliceChan.PendingHtlcs) != 0 { + return false + } + if aliceChan.RemoteBalance != bobAmt { + return false + } + if aliceChan.LocalBalance != aliceAmt { + return false + } + + return true + }, time.Second*5) + if err != nil { + t.Fatalf("failed to assert alice's pending htlcs and/or remote/local balance") + } + + // Wait for Bob to receive revocation from Alice. + time.Sleep(2 * time.Second) + + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + bobChan, err := getChanInfo(ctxt, net.Bob) + if err != nil { + t.Fatalf("unable to get bob's channel info: %v", err) + } + if len(bobChan.PendingHtlcs) != 0 { + t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ + "expected %v", len(bobChan.PendingHtlcs), 0) + } + if bobChan.LocalBalance != bobAmt { + t.Fatalf("bob's local balance is incorrect, got %v, expected"+ + " %v", bobChan.LocalBalance, bobAmt) + } + if bobChan.RemoteBalance != aliceAmt { + t.Fatalf("bob's remote balance is incorrect, got %v, "+ + "expected %v", bobChan.RemoteBalance, aliceAmt) } + + t.Log("\tBenchmark info: Elapsed time: ", timeTaken) + t.Log("\tBenchmark info: TPS: ", float64(numInvoices)/timeTaken.Seconds()) + + // Finally, immediately close the channel. This function will also + // block until the channel is closed and will additionally assert the + // relevant channel closing post conditions. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) } -// testSwitchCircuitPersistence creates a multihop network to ensure the sender -// and intermediaries are persisting their open payment circuits. After -// forwarding a packet via an outgoing link, all are restarted, and expected to -// forward a response back from the receiver once back online. -// -// The general flow of this test: -// 1. Carol --> Dave --> Alice --> Bob forward payment -// 2. X X X Bob restart sender and intermediaries -// 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate -func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { +// testBidirectionalAsyncPayments tests that nodes are able to send the +// payments to each other in async manner without blocking. +func testBidirectionalAsyncPayments(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - const chanAmt = btcutil.Amount(1000000) - const pushAmt = btcutil.Amount(900000) - var networkChans []*lnrpc.ChannelPoint + const ( + paymentAmt = 1000 + ) - // Open a channel with 100k satoshis between Alice and Bob with Alice - // being the sole funder of the channel. + // First establish a channel with a capacity equals to the overall + // amount of payments, between Alice and Bob, at the end of the test + // Alice should send all money from her side to Bob. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( + chanPoint := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, + Amt: paymentAmt * 2000, + PushAmt: paymentAmt * 1000, }, ) - networkChans = append(networkChans, chanPointAlice) - aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + info, err := getChanInfo(ctxt, net.Alice) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, + t.Fatalf("unable to get alice channel info: %v", err) } - // As preliminary setup, we'll create two new nodes: Carol and Dave, - // such that we now have a 4 ndoe, 3 channel topology. Dave will make - // a channel with Alice, and Carol with Dave. After this setup, the - // network topology should now look like: - // Carol -> Dave -> Alice -> Bob - // - // First, we'll create Dave and establish a channel to Alice. - dave, err := net.NewNode("Dave", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, dave) + // We'll create a number of invoices equal the max number of HTLCs that + // can be carried in one direction. The number on the commitment will + // likely be lower, but we can't guarantee that any more HTLCs will + // succeed due to the limited path diversity and inability of the router + // to retry via another path. + numInvoices := int(input.MaxHTLCNumber / 2) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to connect dave to alice: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, dave) - if err != nil { - t.Fatalf("unable to send coins to dave: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointDave := openChannelAndAssert( - ctxt, t, net, dave, net.Alice, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - }, + // Nodes should exchange the same amount of money and because of this + // at the end balances should remain the same. + aliceAmt := info.LocalBalance + bobAmt := info.RemoteBalance + + // With the channel open, we'll create invoices for Bob that Alice + // will pay to in order to advance the state of the channel. + bobPayReqs, _, _, err := createPayReqs( + net.Bob, paymentAmt, numInvoices, ) - networkChans = append(networkChans, chanPointDave) - daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - daveFundPoint := wire.OutPoint{ - Hash: *daveChanTXID, - Index: chanPointDave.OutputIndex, + t.Fatalf("unable to create pay reqs: %v", err) } - // Next, we'll create Carol and establish a channel to from her to - // Dave. Carol is started in htlchodl mode so that we can disconnect the - // intermediary hops before starting the settle. - carol, err := net.NewNode("Carol", []string{"--hodl.exit-settle"}) + // With the channel open, we'll create invoices for Alice that Bob + // will pay to in order to advance the state of the channel. + alicePayReqs, _, _, err := createPayReqs( + net.Alice, paymentAmt, numInvoices, + ) if err != nil { - t.Fatalf("unable to create new nodes: %v", err) + t.Fatalf("unable to create pay reqs: %v", err) } - defer shutdownAndAssert(net, t, carol) + // Wait for Alice to receive the channel edge from the funding manager. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, dave); err != nil { - t.Fatalf("unable to connect carol to dave: %v", err) + if err = net.Alice.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + t.Fatalf("alice didn't see the alice->bob channel before "+ + "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) - if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) + if err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPoint); err != nil { + t.Fatalf("bob didn't see the bob->alice channel before "+ + "timeout: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - PushAmt: pushAmt, - }, - ) - networkChans = append(networkChans, chanPointCarol) - carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + // Reset mission control to prevent previous payment results from + // interfering with this test. A new channel has been opened, but + // mission control operates on node pairs. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + _, err = net.Alice.RouterClient.ResetMissionControl( + ctxt, &routerrpc.ResetMissionControlRequest{}, + ) if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - carolFundPoint := wire.OutPoint{ - Hash: *carolChanTXID, - Index: chanPointCarol.OutputIndex, + t.Fatalf("unable to reset mc for alice: %v", err) } - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) + // Send payments from Alice to Bob and from Bob to Alice in async + // manner. + errChan := make(chan error) + statusChan := make(chan *lnrpc.Payment) + + send := func(node *lntest.HarnessNode, payReq string) { + go func() { + ctxt, _ = context.WithTimeout( + ctxb, lntest.AsyncBenchmarkTimeout, + ) + stream, err := node.RouterClient.SendPaymentV2( + ctxt, + &routerrpc.SendPaymentRequest{ + PaymentRequest: payReq, + TimeoutSeconds: 60, + FeeLimitMsat: noFeeLimitMsat, + }, + ) if err != nil { - t.Fatalf("unable to get txid: %v", err) + errChan <- err } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, + result, err := getPaymentResult(stream) + if err != nil { + errChan <- err } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) + statusChan <- result + }() + } + + for i := 0; i < numInvoices; i++ { + send(net.Bob, alicePayReqs[i]) + send(net.Alice, bobPayReqs[i]) + } + + // Expect all payments to succeed. + for i := 0; i < 2*numInvoices; i++ { + select { + case result := <-statusChan: + if result.Status != lnrpc.Payment_SUCCEEDED { + t.Fatalf("payment error: %v", result.Status) } + + case err := <-errChan: + t.Fatalf("payment error: %v", err) } } - // Create 5 invoices for Carol, which expect a payment from Bob for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - carol, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } + // Wait for Alice and Bob to receive revocations messages, and update + // states, i.e. balance info. + time.Sleep(1 * time.Second) - // We'll wait for all parties to recognize the new channels within the - // network. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + aliceInfo, err := getChanInfo(ctxt, net.Alice) if err != nil { - t.Fatalf("dave didn't advertise his channel: %v", err) + t.Fatalf("unable to get bob's channel info: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("carol didn't advertise her channel in time: %v", - err) + if aliceInfo.RemoteBalance != bobAmt { + t.Fatalf("alice's remote balance is incorrect, got %v, "+ + "expected %v", aliceInfo.RemoteBalance, bobAmt) + } + if aliceInfo.LocalBalance != aliceAmt { + t.Fatalf("alice's local balance is incorrect, got %v, "+ + "expected %v", aliceInfo.LocalBalance, aliceAmt) + } + if len(aliceInfo.PendingHtlcs) != 0 { + t.Fatalf("alice's pending htlcs is incorrect, got %v, "+ + "expected %v", len(aliceInfo.PendingHtlcs), 0) } - time.Sleep(time.Millisecond * 50) - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. + // Next query for Bob's and Alice's channel states, in order to confirm + // that all payment have been successful transmitted. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Bob, net.Bob.RouterClient, payReqs, false, - ) + bobInfo, err := getChanInfo(ctxt, net.Bob) if err != nil { - t.Fatalf("unable to send payments: %v", err) + t.Fatalf("unable to get bob's channel info: %v", err) } - // Wait until all nodes in the network have 5 outstanding htlcs. - var predErr error - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - if predErr != nil { - return false - } - return true - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) + if bobInfo.LocalBalance != bobAmt { + t.Fatalf("bob's local balance is incorrect, got %v, expected"+ + " %v", bobInfo.LocalBalance, bobAmt) } - - // Restart the intermediaries and the sender. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + if bobInfo.RemoteBalance != aliceAmt { + t.Fatalf("bob's remote balance is incorrect, got %v, "+ + "expected %v", bobInfo.RemoteBalance, aliceAmt) } - - if err := net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + if len(bobInfo.PendingHtlcs) != 0 { + t.Fatalf("bob's pending htlcs is incorrect, got %v, "+ + "expected %v", len(bobInfo.PendingHtlcs), 0) } - if err := net.RestartNode(net.Bob, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) - } + // Finally, immediately close the channel. This function will also + // block until the channel is closed and will additionally assert the + // relevant channel closing post conditions. + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPoint, false) +} - // Ensure all of the intermediate links are reconnected. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, net.Alice, dave) - if err != nil { - t.Fatalf("unable to reconnect alice and dave: %v", err) - } +// assertActiveHtlcs makes sure all the passed nodes have the _exact_ HTLCs +// matching payHashes on _all_ their channels. +func assertActiveHtlcs(nodes []*lntest.HarnessNode, payHashes ...[]byte) error { + ctxb := context.Background() - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, net.Bob, net.Alice) - if err != nil { - t.Fatalf("unable to reconnect bob and alice: %v", err) - } + req := &lnrpc.ListChannelsRequest{} + for _, node := range nodes { + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + nodeChans, err := node.ListChannels(ctxt, req) + if err != nil { + return fmt.Errorf("unable to get node chans: %v", err) + } - // Ensure all nodes in the network still have 5 outstanding htlcs. - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) - } + for _, channel := range nodeChans.Channels { + // Record all payment hashes active for this channel. + htlcHashes := make(map[string]struct{}) + for _, htlc := range channel.PendingHtlcs { + h := hex.EncodeToString(htlc.HashLock) + _, ok := htlcHashes[h] + if ok { + return fmt.Errorf("duplicate HashLock") + } + htlcHashes[h] = struct{}{} + } - // Now restart carol without hodl mode, to settle back the outstanding - // payments. - carol.SetExtraArgs(nil) - if err := net.RestartNode(carol, nil); err != nil { - t.Fatalf("Node restart failed: %v", err) + // Channel should have exactly the payHashes active. + if len(payHashes) != len(htlcHashes) { + return fmt.Errorf("node %x had %v htlcs active, "+ + "expected %v", node.PubKey[:], + len(htlcHashes), len(payHashes)) + } + + // Make sure all the payHashes are active. + for _, payHash := range payHashes { + h := hex.EncodeToString(payHash) + if _, ok := htlcHashes[h]; ok { + continue + } + return fmt.Errorf("node %x didn't have the "+ + "payHash %v active", node.PubKey[:], + h) + } + } } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, carol) + return nil +} + +func assertNumActiveHtlcsChanPoint(node *lntest.HarnessNode, + chanPoint wire.OutPoint, numHtlcs int) error { + ctxb := context.Background() + + req := &lnrpc.ListChannelsRequest{} + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + nodeChans, err := node.ListChannels(ctxt, req) if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) + return err } - // After the payments settle, there should be no active htlcs on any of - // the nodes in the network. - err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodes, 0) - return predErr == nil + for _, channel := range nodeChans.Channels { + if channel.ChannelPoint != chanPoint.String() { + continue + } - }, defaultTimeout) - if err != nil { - t.Fatalf("htlc mismatch: %v", predErr) + if len(channel.PendingHtlcs) != numHtlcs { + return fmt.Errorf("expected %v active HTLCs, got %v", + numHtlcs, len(channel.PendingHtlcs)) + } + return nil } - // When asserting the amount of satoshis moved, we'll factor in the - // default base fee, as we didn't modify the fee structure when - // creating the seed nodes in the network. - const baseFee = 1 + return fmt.Errorf("channel point %v not found", chanPoint) +} - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Carol, the sink within the - // payment flow generated above. The order of asserts corresponds to - // increasing of time is needed to embed the HTLC in commitment - // transaction, in channel Bob->Alice->David->Carol, order is Carol, - // David, Alice, Bob. - var amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) +func assertNumActiveHtlcs(nodes []*lntest.HarnessNode, numHtlcs int) error { + ctxb := context.Background() - // Lastly, we will send one more payment to ensure all channels are - // still functioning properly. - finalInvoice := &lnrpc.Invoice{ - Memo: "testing", - Value: paymentAmt, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - resp, err := carol.AddInvoice(ctxt, finalInvoice) - if err != nil { - t.Fatalf("unable to add invoice: %v", err) + req := &lnrpc.ListChannelsRequest{} + for _, node := range nodes { + ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + nodeChans, err := node.ListChannels(ctxt, req) + if err != nil { + return err + } + + for _, channel := range nodeChans.Channels { + if len(channel.PendingHtlcs) != numHtlcs { + return fmt.Errorf("expected %v HTLCs, got %v", + numHtlcs, len(channel.PendingHtlcs)) + } + } } - payReqs = []string{resp.PaymentRequest} + return nil +} - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests( - ctxt, net.Bob, net.Bob.RouterClient, payReqs, true, - ) - if err != nil { - t.Fatalf("unable to send payments: %v", err) - } +func assertSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, + timeout time.Duration, chanPoint wire.OutPoint) chainhash.Hash { - amountPaid = int64(6000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, - daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) - assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, - daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, - aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) - assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, - aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) + tx := getSpendingTxInMempool(t, miner, timeout, chanPoint) + return tx.TxHash() +} - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) +// getSpendingTxInMempool waits for a transaction spending the given outpoint to +// appear in the mempool and returns that tx in full. +func getSpendingTxInMempool(t *harnessTest, miner *rpcclient.Client, + timeout time.Duration, chanPoint wire.OutPoint) *wire.MsgTx { + + breakTimeout := time.After(timeout) + ticker := time.NewTicker(50 * time.Millisecond) + defer ticker.Stop() + + for { + select { + case <-breakTimeout: + t.Fatalf("didn't find tx in mempool") + case <-ticker.C: + mempool, err := miner.GetRawMempool() + if err != nil { + t.Fatalf("unable to get mempool: %v", err) + } + + if len(mempool) == 0 { + continue + } + + for _, txid := range mempool { + tx, err := miner.GetRawTransaction(txid) + if err != nil { + t.Fatalf("unable to fetch tx: %v", err) + } + + msgTx := tx.MsgTx() + for _, txIn := range msgTx.TxIn { + if txIn.PreviousOutPoint == chanPoint { + return msgTx + } + } + } + } + } } -// testSwitchOfflineDelivery constructs a set of multihop payments, and tests -// that the returning payments are not lost if a peer on the backwards path is -// offline when the settle/fails are received. We expect the payments to be -// buffered in memory, and transmitted as soon as the disconnect link comes back -// online. +// testSwitchCircuitPersistence creates a multihop network to ensure the sender +// and intermediaries are persisting their open payment circuits. After +// forwarding a packet via an outgoing link, all are restarted, and expected to +// forward a response back from the receiver once back online. // // The general flow of this test: // 1. Carol --> Dave --> Alice --> Bob forward payment -// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries -// 3. Carol --- Dave X Alice <-- Bob settle last hop -// 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate -func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { +// 2. X X X Bob restart sender and intermediaries +// 3. Carol <-- Dave <-- Alice <-- Bob expect settle to propagate +func testSwitchCircuitPersistence(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) @@ -11807,44 +9955,52 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to send payments: %v", err) } - // Wait for all of the payments to reach Carol. + // Wait until all nodes in the network have 5 outstanding htlcs. var predErr error err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) - return predErr == nil + if predErr != nil { + return false + } + return true }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // First, disconnect Dave and Alice so that their link is broken. + // Restart the intermediaries and the sender. + if err := net.RestartNode(dave, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) + } + + if err := net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) + } + + if err := net.RestartNode(net.Bob, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) + } + + // Ensure all of the intermediate links are reconnected. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to disconnect alice from dave: %v", err) + err = net.EnsureConnected(ctxt, net.Alice, dave) + if err != nil { + t.Fatalf("unable to reconnect alice and dave: %v", err) } - // Then, reconnect them to ensure Dave doesn't just fail back the htlc. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to reconnect alice to dave: %v", err) + err = net.EnsureConnected(ctxt, net.Bob, net.Alice) + if err != nil { + t.Fatalf("unable to reconnect bob and alice: %v", err) } - // Wait to ensure that the payment remain are not failed back after - // reconnecting. All node should report the number payments initiated - // for the duration of the interval. - err = wait.Invariant(func() bool { + // Ensure all nodes in the network still have 5 outstanding htlcs. + err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) return predErr == nil }, defaultTimeout) if err != nil { - t.Fatalf("htlc change: %v", predErr) - } - - // Now, disconnect Dave from Alice again before settling back the - // payment. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to disconnect alice from dave: %v", err) + t.Fatalf("htlc mismatch: %v", predErr) } // Now restart carol without hodl mode, to settle back the outstanding @@ -11877,13 +10033,17 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { // Now that the settles have reached Dave, reconnect him with Alice, // allowing the settles to return to the sender. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.EnsureConnected(ctxt, dave, net.Alice); err != nil { - t.Fatalf("unable to reconnect alice to dave: %v", err) + err = net.EnsureConnected(ctxt, dave, carol) + if err != nil { + t.Fatalf("unable to reconnect dave and carol: %v", err) } - // Wait until all outstanding htlcs in the network have been settled. + // After the payments settle, there should be no active htlcs on any of + // the nodes in the network. err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, 0) == nil + predErr = assertNumActiveHtlcs(nodes, 0) + return predErr == nil + }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) @@ -11960,19 +10120,18 @@ func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testSwitchOfflineDeliveryPersistence constructs a set of multihop payments, -// and tests that the returning payments are not lost if a peer on the backwards -// path is offline when the settle/fails are received AND the peer buffering the -// responses is completely restarts. We expect the payments to be reloaded from -// disk, and transmitted as soon as the intermediaries are reconnected. +// testSwitchOfflineDelivery constructs a set of multihop payments, and tests +// that the returning payments are not lost if a peer on the backwards path is +// offline when the settle/fails are received. We expect the payments to be +// buffered in memory, and transmitted as soon as the disconnect link comes back +// online. // // The general flow of this test: // 1. Carol --> Dave --> Alice --> Bob forward payment // 2. Carol --- Dave X Alice --- Bob disconnect intermediaries // 3. Carol --- Dave X Alice <-- Bob settle last hop -// 4. Carol --- Dave X X Bob restart Alice -// 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate -func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harnessTest) { +// 4. Carol <-- Dave <-- Alice --- Bob reconnect, expect settle to propagate +func testSwitchOfflineDelivery(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) @@ -12030,7 +10189,6 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness PushAmt: pushAmt, }, ) - networkChans = append(networkChans, chanPointDave) daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { @@ -12127,6 +10285,8 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness err) } + time.Sleep(time.Millisecond * 50) + // Using Carol as the source, pay to the 5 invoices from Bob created // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -12137,23 +10297,44 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness t.Fatalf("unable to send payments: %v", err) } + // Wait for all of the payments to reach Carol. var predErr error err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(nodes, numPayments) - if predErr != nil { - return false - } - return true - + return predErr == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Disconnect the two intermediaries, Alice and Dave, by shutting down - // Alice. - if err := net.StopNode(net.Alice); err != nil { - t.Fatalf("unable to shutdown alice: %v", err) + // First, disconnect Dave and Alice so that their link is broken. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to disconnect alice from dave: %v", err) + } + + // Then, reconnect them to ensure Dave doesn't just fail back the htlc. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to reconnect alice to dave: %v", err) + } + + // Wait to ensure that the payment remain are not failed back after + // reconnecting. All node should report the number payments initiated + // for the duration of the interval. + err = wait.Invariant(func() bool { + predErr = assertNumActiveHtlcs(nodes, numPayments) + return predErr == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc change: %v", predErr) + } + + // Now, disconnect Dave from Alice again before settling back the + // payment. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + if err := net.DisconnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to disconnect alice from dave: %v", err) } // Now restart carol without hodl mode, to settle back the outstanding @@ -12163,49 +10344,24 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness t.Fatalf("Node restart failed: %v", err) } - // Make Carol and Dave are reconnected before waiting for the htlcs to - // clear. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, carol) - if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) - } - - // Wait for Carol to report no outstanding htlcs, and also for Dav to - // receive all the settles from Carol. + // Wait for Carol to report no outstanding htlcs. carolNode := []*lntest.HarnessNode{carol} err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(carolNode, 0) - if predErr != nil { - return false - } - - predErr = assertNumActiveHtlcsChanPoint(dave, carolFundPoint, 0) return predErr == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Finally, restart dave who received the settles, but was unable to - // deliver them to Alice since they were disconnected. - if err := net.RestartNode(dave, nil); err != nil { - t.Fatalf("unable to restart dave: %v", err) - } - if err = net.RestartNode(net.Alice, nil); err != nil { - t.Fatalf("unable to restart alice: %v", err) - } - - // Force Dave and Alice to reconnect before waiting for the htlcs to - // clear. + // Now that the settles have reached Dave, reconnect him with Alice, + // allowing the settles to return to the sender. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, net.Alice) - if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) + if err := net.EnsureConnected(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to reconnect alice to dave: %v", err) } - // After reconnection succeeds, the settles should be propagated all - // the way back to the sender. All nodes should report no active htlcs. + // Wait until all outstanding htlcs in the network have been settled. err = wait.Predicate(func() bool { return assertNumActiveHtlcs(nodes, 0) == nil }, defaultTimeout) @@ -12252,14 +10408,6 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness payReqs = []string{resp.PaymentRequest} - // Before completing the final payment request, ensure that the - // connection between Dave and Carol has been healed. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.EnsureConnected(ctxt, dave, carol) - if err != nil { - t.Fatalf("unable to reconnect dave and carol: %v", err) - } - // Using Carol as the source, pay to the 5 invoices from Bob created // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) @@ -12292,7 +10440,7 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop payments, +// testSwitchOfflineDeliveryPersistence constructs a set of multihop payments, // and tests that the returning payments are not lost if a peer on the backwards // path is offline when the settle/fails are received AND the peer buffering the // responses is completely restarts. We expect the payments to be reloaded from @@ -12302,10 +10450,9 @@ func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harness // 1. Carol --> Dave --> Alice --> Bob forward payment // 2. Carol --- Dave X Alice --- Bob disconnect intermediaries // 3. Carol --- Dave X Alice <-- Bob settle last hop -// 4. Carol --- Dave X X shutdown Bob, restart Alice -// 5. Carol <-- Dave <-- Alice X expect settle to propagate -func testSwitchOfflineDeliveryOutgoingOffline( - net *lntest.NetworkHarness, t *harnessTest) { +// 4. Carol --- Dave X X Bob restart Alice +// 5. Carol <-- Dave <-- Alice --- Bob expect settle to propagate +func testSwitchOfflineDeliveryPersistence(net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() const chanAmt = btcutil.Amount(1000000) @@ -12363,6 +10510,7 @@ func testSwitchOfflineDeliveryOutgoingOffline( PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointDave) daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) if err != nil { @@ -12380,6 +10528,8 @@ func testSwitchOfflineDeliveryOutgoingOffline( if err != nil { t.Fatalf("unable to create new nodes: %v", err) } + defer shutdownAndAssert(net, t, carol) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) if err := net.ConnectNodes(ctxt, carol, dave); err != nil { t.Fatalf("unable to connect carol to dave: %v", err) @@ -12467,17 +10617,18 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("unable to send payments: %v", err) } - // Wait for all payments to reach Carol. var predErr error err = wait.Predicate(func() bool { - return assertNumActiveHtlcs(nodes, numPayments) == nil + predErr = assertNumActiveHtlcs(nodes, numPayments) + return predErr == nil + }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) } - // Disconnect the two intermediaries, Alice and Dave, so that when carol - // restarts, the response will be held by Dave. + // Disconnect the two intermediaries, Alice and Dave, by shutting down + // Alice. if err := net.StopNode(net.Alice); err != nil { t.Fatalf("unable to shutdown alice: %v", err) } @@ -12489,7 +10640,16 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("Node restart failed: %v", err) } - // Wait for Carol to report no outstanding htlcs. + // Make Carol and Dave are reconnected before waiting for the htlcs to + // clear. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.EnsureConnected(ctxt, dave, carol) + if err != nil { + t.Fatalf("unable to reconnect dave and carol: %v", err) + } + + // Wait for Carol to report no outstanding htlcs, and also for Dav to + // receive all the settles from Carol. carolNode := []*lntest.HarnessNode{carol} err = wait.Predicate(func() bool { predErr = assertNumActiveHtlcs(carolNode, 0) @@ -12504,22 +10664,8 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("htlc mismatch: %v", predErr) } - // Now check that the total amount was transferred from Dave to Carol. - // The amount transferred should be exactly equal to the invoice total - // payment amount, 5k satsohis. - const amountPaid = int64(5000) - assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, - carolFundPoint, int64(0), amountPaid) - assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, - carolFundPoint, amountPaid, int64(0)) - - // Shutdown carol and leave her offline for the rest of the test. This - // is critical, as we wish to see if Dave can propragate settles even if - // the outgoing link is never revived. - shutdownAndAssert(net, t, carol) - - // Now restart Dave, ensuring he is both persisting the settles, and is - // able to reforward them to Alice after recovering from a restart. + // Finally, restart dave who received the settles, but was unable to + // deliver them to Alice since they were disconnected. if err := net.RestartNode(dave, nil); err != nil { t.Fatalf("unable to restart dave: %v", err) } @@ -12527,20 +10673,18 @@ func testSwitchOfflineDeliveryOutgoingOffline( t.Fatalf("unable to restart alice: %v", err) } - // Ensure that Dave is reconnected to Alice before waiting for the - // htlcs to clear. + // Force Dave and Alice to reconnect before waiting for the htlcs to + // clear. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = net.EnsureConnected(ctxt, dave, net.Alice) if err != nil { - t.Fatalf("unable to reconnect alice and dave: %v", err) + t.Fatalf("unable to reconnect dave and carol: %v", err) } - // Since Carol has been shutdown permanently, we will wait until all - // other nodes in the network report no active htlcs. - nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave} + // After reconnection succeeds, the settles should be propagated all + // the way back to the sender. All nodes should report no active htlcs. err = wait.Predicate(func() bool { - predErr = assertNumActiveHtlcs(nodesMinusCarol, 0) - return predErr == nil + return assertNumActiveHtlcs(nodes, 0) == nil }, defaultTimeout) if err != nil { t.Fatalf("htlc mismatch: %v", predErr) @@ -12551,11 +10695,17 @@ func testSwitchOfflineDeliveryOutgoingOffline( // creating the seed nodes in the network. const baseFee = 1 - // At this point, all channels (minus Carol, who is shutdown) should - // show a shift of 5k satoshis towards Carol. The order of asserts - // corresponds to increasing of time is needed to embed the HTLC in - // commitment transaction, in channel Bob->Alice->David, order is + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Carol, the sink within the + // payment flow generated above. The order of asserts corresponds to + // increasing of time is needed to embed the HTLC in commitment + // transaction, in channel Bob->Alice->David->Carol, order is Carol, // David, Alice, Bob. + var amountPaid = int64(5000) + assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, + carolFundPoint, amountPaid, int64(0)) assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, @@ -12565,596 +10715,337 @@ func testSwitchOfflineDeliveryOutgoingOffline( assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) -} - -// computeFee calculates the payment fee as specified in BOLT07 -func computeFee(baseFee, feeRate, amt lnwire.MilliSatoshi) lnwire.MilliSatoshi { - return baseFee + amt*feeRate/1000000 -} - -// testQueryRoutes checks the response of queryroutes. -// We'll create the following network topology: -// Alice --> Bob --> Carol --> Dave -// and query the daemon for routes from Alice to Dave. -func testQueryRoutes(net *lntest.NetworkHarness, t *harnessTest) { - ctxb := context.Background() - - const chanAmt = btcutil.Amount(100000) - var networkChans []*lnrpc.ChannelPoint - - // Open a channel between Alice and Bob. - ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAlice := openChannelAndAssert( - ctxt, t, net, net.Alice, net.Bob, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointAlice) - - // Create Carol and establish a channel from Bob. - carol, err := net.NewNode("Carol", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Bob); err != nil { - t.Fatalf("unable to connect carol to bob: %v", err) + // Lastly, we will send one more payment to ensure all channels are + // still functioning properly. + finalInvoice := &lnrpc.Invoice{ + Memo: "testing", + Value: paymentAmt, } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, net.Bob) + resp, err := carol.AddInvoice(ctxt, finalInvoice) if err != nil { - t.Fatalf("unable to send coins to bob: %v", err) + t.Fatalf("unable to add invoice: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBob := openChannelAndAssert( - ctxt, t, net, net.Bob, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointBob) - // Create Dave and establish a channel from Carol. - dave, err := net.NewNode("Dave", nil) - if err != nil { - t.Fatalf("unable to create new nodes: %v", err) - } - defer shutdownAndAssert(net, t, dave) + payReqs = []string{resp.PaymentRequest} + // Before completing the final payment request, ensure that the + // connection between Dave and Carol has been healed. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, carol); err != nil { - t.Fatalf("unable to connect dave to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + err = net.EnsureConnected(ctxt, dave, carol) if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) - } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarol := openChannelAndAssert( - ctxt, t, net, carol, dave, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - networkChans = append(networkChans, chanPointCarol) - - // Wait for all nodes to have seen all channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} - for _, chanPoint := range networkChans { - for i, node := range nodes { - txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - point := wire.OutPoint{ - Hash: *txid, - Index: chanPoint.OutputIndex, - } - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) - if err != nil { - t.Fatalf("%s(%d): timeout waiting for "+ - "channel(%s) open: %v", nodeNames[i], - node.NodeID, point, err) - } - } + t.Fatalf("unable to reconnect dave and carol: %v", err) } - // Query for routes to pay from Alice to Dave. - const paymentAmt = 1000 - routesReq := &lnrpc.QueryRoutesRequest{ - PubKey: dave.PubKeyStr, - Amt: paymentAmt, - } + // Using Carol as the source, pay to the 5 invoices from Bob created + // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routesRes, err := net.Alice.QueryRoutes(ctxt, routesReq) + err = completePaymentRequests( + ctxt, net.Bob, net.Bob.RouterClient, payReqs, true, + ) if err != nil { - t.Fatalf("unable to get route: %v", err) - } - - const mSat = 1000 - feePerHopMSat := computeFee(1000, 1, paymentAmt*mSat) - - for i, route := range routesRes.Routes { - expectedTotalFeesMSat := - lnwire.MilliSatoshi(len(route.Hops)-1) * feePerHopMSat - expectedTotalAmtMSat := (paymentAmt * mSat) + expectedTotalFeesMSat - - if route.TotalFees != route.TotalFeesMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v: total fees %v (msat) does not "+ - "round down to %v (sat)", - i, route.TotalFeesMsat, route.TotalFees) // nolint:staticcheck - } - if route.TotalFeesMsat != int64(expectedTotalFeesMSat) { - t.Fatalf("route %v: total fees in msat expected %v got %v", - i, expectedTotalFeesMSat, route.TotalFeesMsat) - } - - if route.TotalAmt != route.TotalAmtMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v: total amt %v (msat) does not "+ - "round down to %v (sat)", - i, route.TotalAmtMsat, route.TotalAmt) // nolint:staticcheck - } - if route.TotalAmtMsat != int64(expectedTotalAmtMSat) { - t.Fatalf("route %v: total amt in msat expected %v got %v", - i, expectedTotalAmtMSat, route.TotalAmtMsat) - } - - // For all hops except the last, we check that fee equals feePerHop - // and amount to forward deducts feePerHop on each hop. - expectedAmtToForwardMSat := expectedTotalAmtMSat - for j, hop := range route.Hops[:len(route.Hops)-1] { - expectedAmtToForwardMSat -= feePerHopMSat - - if hop.Fee != hop.FeeMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: fee %v (msat) does not "+ - "round down to %v (sat)", - i, j, hop.FeeMsat, hop.Fee) // nolint:staticcheck - } - if hop.FeeMsat != int64(feePerHopMSat) { - t.Fatalf("route %v hop %v: fee in msat expected %v got %v", - i, j, feePerHopMSat, hop.FeeMsat) - } - - if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ - "round down to %v (sat)", - i, j, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck - } - if hop.AmtToForwardMsat != int64(expectedAmtToForwardMSat) { - t.Fatalf("route %v hop %v: amt to forward in msat "+ - "expected %v got %v", - i, j, expectedAmtToForwardMSat, hop.AmtToForwardMsat) - } - } - // Last hop should have zero fee and amount to forward should equal - // payment amount. - hop := route.Hops[len(route.Hops)-1] - - if hop.Fee != 0 || hop.FeeMsat != 0 { // nolint:staticcheck - t.Fatalf("route %v hop %v: fee expected 0 got %v (sat) %v (msat)", - i, len(route.Hops)-1, hop.Fee, hop.FeeMsat) // nolint:staticcheck - } - - if hop.AmtToForward != hop.AmtToForwardMsat/mSat { // nolint:staticcheck - t.Fatalf("route %v hop %v: amt to forward %v (msat) does not "+ - "round down to %v (sat)", - i, len(route.Hops)-1, hop.AmtToForwardMsat, hop.AmtToForward) // nolint:staticcheck - } - if hop.AmtToForwardMsat != paymentAmt*mSat { - t.Fatalf("route %v hop %v: amt to forward in msat "+ - "expected %v got %v", - i, len(route.Hops)-1, paymentAmt*mSat, hop.AmtToForwardMsat) - } + t.Fatalf("unable to send payments: %v", err) } - // While we're here, we test updating mission control's config values - // and assert that they are correctly updated and check that our mission - // control import function updates appropriately. - testMissionControlCfg(t.t, net.Alice) - testMissionControlImport( - t.t, net.Alice, net.Bob.PubKey[:], carol.PubKey[:], - ) + amountPaid = int64(6000) + assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, + carolFundPoint, amountPaid, int64(0)) + assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, + daveFundPoint, int64(0), amountPaid+(baseFee*(numPayments+1))) + assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, + daveFundPoint, amountPaid+(baseFee*(numPayments+1)), int64(0)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, + aliceFundPoint, int64(0), amountPaid+((baseFee*(numPayments+1))*2)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, + aliceFundPoint, amountPaid+(baseFee*(numPayments+1))*2, int64(0)) - // We clean up the test case by closing channels that were created for - // the duration of the tests. ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBob, false) + closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false) } -// testMissionControlCfg tests getting and setting of a node's mission control -// config, resetting to the original values after testing so that no other -// tests are affected. -func testMissionControlCfg(t *testing.T, node *lntest.HarnessNode) { - ctxb := context.Background() - startCfg, err := node.RouterClient.GetMissionControlConfig( - ctxb, &routerrpc.GetMissionControlConfigRequest{}, - ) - require.NoError(t, err) - - cfg := &routerrpc.MissionControlConfig{ - HalfLifeSeconds: 8000, - HopProbability: 0.8, - Weight: 0.3, - MaximumPaymentResults: 30, - MinimumFailureRelaxInterval: 60, - } - - _, err = node.RouterClient.SetMissionControlConfig( - ctxb, &routerrpc.SetMissionControlConfigRequest{ - Config: cfg, - }, - ) - require.NoError(t, err) - - resp, err := node.RouterClient.GetMissionControlConfig( - ctxb, &routerrpc.GetMissionControlConfigRequest{}, - ) - require.NoError(t, err) - require.True(t, proto.Equal(cfg, resp.Config)) - - _, err = node.RouterClient.SetMissionControlConfig( - ctxb, &routerrpc.SetMissionControlConfigRequest{ - Config: startCfg.Config, - }, - ) - require.NoError(t, err) -} - -// testMissionControlImport tests import of mission control results from an -// external source. -func testMissionControlImport(t *testing.T, node *lntest.HarnessNode, - fromNode, toNode []byte) { - - ctxb := context.Background() - - // Reset mission control so that our query will return the default - // probability for our first request. - _, err := node.RouterClient.ResetMissionControl( - ctxb, &routerrpc.ResetMissionControlRequest{}, - ) - require.NoError(t, err, "could not reset mission control") - - // Get our baseline probability for a 10 msat hop between our target - // nodes. - var amount int64 = 10 - probReq := &routerrpc.QueryProbabilityRequest{ - FromNode: fromNode, - ToNode: toNode, - AmtMsat: amount, - } - - importHistory := &routerrpc.PairData{ - FailTime: time.Now().Unix(), - FailAmtMsat: amount, - } - - // Assert that our history is not already equal to the value we want to - // set. This should not happen because we have just cleared our state. - resp1, err := node.RouterClient.QueryProbability(ctxb, probReq) - require.NoError(t, err, "query probability failed") - require.Zero(t, resp1.History.FailTime) - require.Zero(t, resp1.History.FailAmtMsat) - - // Now, we import a single entry which tracks a failure of the amount - // we want to query between our nodes. - req := &routerrpc.XImportMissionControlRequest{ - Pairs: []*routerrpc.PairHistory{ - { - NodeFrom: fromNode, - NodeTo: toNode, - History: importHistory, - }, - }, - } - - _, err = node.RouterClient.XImportMissionControl(ctxb, req) - require.NoError(t, err, "could not import config") - - resp2, err := node.RouterClient.QueryProbability(ctxb, probReq) - require.NoError(t, err, "query probability failed") - require.Equal(t, importHistory.FailTime, resp2.History.FailTime) - require.Equal(t, importHistory.FailAmtMsat, resp2.History.FailAmtMsat) - - // Finally, check that we will fail if inconsistent sat/msat values are - // set. - importHistory.FailAmtSat = amount * 2 - _, err = node.RouterClient.XImportMissionControl(ctxb, req) - require.Error(t, err, "mismatched import amounts succeeded") -} - -// testRouteFeeCutoff tests that we are able to prevent querying routes and -// sending payments that incur a fee higher than the fee limit. -func testRouteFeeCutoff(net *lntest.NetworkHarness, t *harnessTest) { +// testSwitchOfflineDeliveryOutgoingOffline constructs a set of multihop payments, +// and tests that the returning payments are not lost if a peer on the backwards +// path is offline when the settle/fails are received AND the peer buffering the +// responses is completely restarts. We expect the payments to be reloaded from +// disk, and transmitted as soon as the intermediaries are reconnected. +// +// The general flow of this test: +// 1. Carol --> Dave --> Alice --> Bob forward payment +// 2. Carol --- Dave X Alice --- Bob disconnect intermediaries +// 3. Carol --- Dave X Alice <-- Bob settle last hop +// 4. Carol --- Dave X X shutdown Bob, restart Alice +// 5. Carol <-- Dave <-- Alice X expect settle to propagate +func testSwitchOfflineDeliveryOutgoingOffline( + net *lntest.NetworkHarness, t *harnessTest) { ctxb := context.Background() - // For this test, we'll create the following topology: - // - // --- Bob --- - // / \ - // Alice ---- ---- Dave - // \ / - // -- Carol -- - // - // Alice will attempt to send payments to Dave that should not incur a - // fee greater than the fee limit expressed as a percentage of the - // amount and as a fixed amount of satoshis. - const chanAmt = btcutil.Amount(100000) + const chanAmt = btcutil.Amount(1000000) + const pushAmt = btcutil.Amount(900000) + var networkChans []*lnrpc.ChannelPoint - // Open a channel between Alice and Bob. + // Open a channel with 100k satoshis between Alice and Bob with Alice + // being the sole funder of the channel. ctxt, _ := context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAliceBob := openChannelAndAssert( + chanPointAlice := openChannelAndAssert( ctxt, t, net, net.Alice, net.Bob, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointAlice) - // Create Carol's node and open a channel between her and Alice with - // Alice being the funder. - carol, err := net.NewNode("Carol", nil) + aliceChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointAlice) if err != nil { - t.Fatalf("unable to create carol's node: %v", err) - } - defer shutdownAndAssert(net, t, carol) - - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, carol, net.Alice); err != nil { - t.Fatalf("unable to connect carol to alice: %v", err) + t.Fatalf("unable to get txid: %v", err) } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) - if err != nil { - t.Fatalf("unable to send coins to carol: %v", err) + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, } - ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointAliceCarol := openChannelAndAssert( - ctxt, t, net, net.Alice, carol, - lntest.OpenChannelParams{ - Amt: chanAmt, - }, - ) - // Create Dave's node and open a channel between him and Bob with Bob - // being the funder. + // As preliminary setup, we'll create two new nodes: Carol and Dave, + // such that we now have a 4 ndoe, 3 channel topology. Dave will make + // a channel with Alice, and Carol with Dave. After this setup, the + // network topology should now look like: + // Carol -> Dave -> Alice -> Bob + // + // First, we'll create Dave and establish a channel to Alice. dave, err := net.NewNode("Dave", nil) if err != nil { - t.Fatalf("unable to create dave's node: %v", err) + t.Fatalf("unable to create new nodes: %v", err) } defer shutdownAndAssert(net, t, dave) ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if err := net.ConnectNodes(ctxt, dave, net.Bob); err != nil { - t.Fatalf("unable to connect dave to bob: %v", err) + if err := net.ConnectNodes(ctxt, dave, net.Alice); err != nil { + t.Fatalf("unable to connect dave to alice: %v", err) + } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, dave) + if err != nil { + t.Fatalf("unable to send coins to dave: %v", err) } ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointBobDave := openChannelAndAssert( - ctxt, t, net, net.Bob, dave, + chanPointDave := openChannelAndAssert( + ctxt, t, net, dave, net.Alice, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointDave) + daveChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointDave) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + daveFundPoint := wire.OutPoint{ + Hash: *daveChanTXID, + Index: chanPointDave.OutputIndex, + } - // Open a channel between Carol and Dave. + // Next, we'll create Carol and establish a channel to from her to + // Dave. Carol is started in htlchodl mode so that we can disconnect the + // intermediary hops before starting the settle. + carol, err := net.NewNode("Carol", []string{"--hodl.exit-settle"}) + if err != nil { + t.Fatalf("unable to create new nodes: %v", err) + } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) if err := net.ConnectNodes(ctxt, carol, dave); err != nil { t.Fatalf("unable to connect carol to dave: %v", err) } + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.SendCoins(ctxt, btcutil.SatoshiPerBitcoin, carol) + if err != nil { + t.Fatalf("unable to send coins to carol: %v", err) + } ctxt, _ = context.WithTimeout(ctxb, channelOpenTimeout) - chanPointCarolDave := openChannelAndAssert( + chanPointCarol := openChannelAndAssert( ctxt, t, net, carol, dave, lntest.OpenChannelParams{ - Amt: chanAmt, + Amt: chanAmt, + PushAmt: pushAmt, }, ) + networkChans = append(networkChans, chanPointCarol) - // Now that all the channels were set up, we'll wait for all the nodes - // to have seen all the channels. - nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} - nodeNames := []string{"alice", "bob", "carol", "dave"} - networkChans := []*lnrpc.ChannelPoint{ - chanPointAliceBob, chanPointAliceCarol, chanPointBobDave, - chanPointCarolDave, + carolChanTXID, err := lnrpc.GetChanPointFundingTxid(chanPointCarol) + if err != nil { + t.Fatalf("unable to get txid: %v", err) } + carolFundPoint := wire.OutPoint{ + Hash: *carolChanTXID, + Index: chanPointCarol.OutputIndex, + } + + // Wait for all nodes to have seen all channels. + nodes := []*lntest.HarnessNode{net.Alice, net.Bob, carol, dave} + nodeNames := []string{"Alice", "Bob", "Carol", "Dave"} for _, chanPoint := range networkChans { for i, node := range nodes { txid, err := lnrpc.GetChanPointFundingTxid(chanPoint) if err != nil { t.Fatalf("unable to get txid: %v", err) } - outpoint := wire.OutPoint{ + point := wire.OutPoint{ Hash: *txid, Index: chanPoint.OutputIndex, } - ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) err = node.WaitForNetworkChannelOpen(ctxt, chanPoint) if err != nil { - t.Fatalf("%s(%d) timed out waiting for "+ + t.Fatalf("%s(%d): timeout waiting for "+ "channel(%s) open: %v", nodeNames[i], - node.NodeID, outpoint, err) + node.NodeID, point, err) } } } - // The payments should only be successful across the route: - // Alice -> Bob -> Dave - // Therefore, we'll update the fee policy on Carol's side for the - // channel between her and Dave to invalidate the route: - // Alice -> Carol -> Dave - baseFee := int64(10000) - feeRate := int64(5) - timeLockDelta := uint32(chainreg.DefaultBitcoinTimeLockDelta) - maxHtlc := calculateMaxHtlc(chanAmt) - - expectedPolicy := &lnrpc.RoutingPolicy{ - FeeBaseMsat: baseFee, - FeeRateMilliMsat: testFeeBase * feeRate, - TimeLockDelta: timeLockDelta, - MinHtlc: 1000, // default value - MaxHtlcMsat: maxHtlc, + // Create 5 invoices for Carol, which expect a payment from Bob for 1k + // satoshis with a different preimage each time. + const numPayments = 5 + const paymentAmt = 1000 + payReqs, _, _, err := createPayReqs( + carol, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) } - updateFeeReq := &lnrpc.PolicyUpdateRequest{ - BaseFeeMsat: baseFee, - FeeRate: float64(feeRate), - TimeLockDelta: timeLockDelta, - MaxHtlcMsat: maxHtlc, - Scope: &lnrpc.PolicyUpdateRequest_ChanPoint{ - ChanPoint: chanPointCarolDave, - }, + // We'll wait for all parties to recognize the new channels within the + // network. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = dave.WaitForNetworkChannelOpen(ctxt, chanPointDave) + if err != nil { + t.Fatalf("dave didn't advertise his channel: %v", err) } ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - if _, err := carol.UpdateChannelPolicy(ctxt, updateFeeReq); err != nil { - t.Fatalf("unable to update chan policy: %v", err) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't advertise her channel in time: %v", + err) } - // Wait for Alice to receive the channel update from Carol. + // Using Carol as the source, pay to the 5 invoices from Bob created + // above. ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - aliceSub := subscribeGraphNotifications(t, ctxt, net.Alice) - defer close(aliceSub.quit) - - waitForChannelUpdate( - t, aliceSub, - []expectedChanUpdate{ - {carol.PubKeyStr, expectedPolicy, chanPointCarolDave}, - }, + err = completePaymentRequests( + ctxt, net.Bob, net.Bob.RouterClient, payReqs, false, ) - - // We'll also need the channel IDs for Bob's channels in order to - // confirm the route of the payments. - listReq := &lnrpc.ListChannelsRequest{} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - listResp, err := net.Bob.ListChannels(ctxt, listReq) if err != nil { - t.Fatalf("unable to retrieve bob's channels: %v", err) + t.Fatalf("unable to send payments: %v", err) } - var aliceBobChanID, bobDaveChanID uint64 - for _, channel := range listResp.Channels { - switch channel.RemotePubkey { - case net.Alice.PubKeyStr: - aliceBobChanID = channel.ChanId - case dave.PubKeyStr: - bobDaveChanID = channel.ChanId - } + // Wait for all payments to reach Carol. + var predErr error + err = wait.Predicate(func() bool { + return assertNumActiveHtlcs(nodes, numPayments) == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc mismatch: %v", predErr) } - if aliceBobChanID == 0 { - t.Fatalf("channel between alice and bob not found") - } - if bobDaveChanID == 0 { - t.Fatalf("channel between bob and dave not found") + // Disconnect the two intermediaries, Alice and Dave, so that when carol + // restarts, the response will be held by Dave. + if err := net.StopNode(net.Alice); err != nil { + t.Fatalf("unable to shutdown alice: %v", err) } - hopChanIDs := []uint64{aliceBobChanID, bobDaveChanID} - - // checkRoute is a helper closure to ensure the route contains the - // correct intermediate hops. - checkRoute := func(route *lnrpc.Route) { - if len(route.Hops) != 2 { - t.Fatalf("expected two hops, got %d", len(route.Hops)) - } - for i, hop := range route.Hops { - if hop.ChanId != hopChanIDs[i] { - t.Fatalf("expected chan id %d, got %d", - hopChanIDs[i], hop.ChanId) - } - } + // Now restart carol without hodl mode, to settle back the outstanding + // payments. + carol.SetExtraArgs(nil) + if err := net.RestartNode(carol, nil); err != nil { + t.Fatalf("Node restart failed: %v", err) } - // We'll be attempting to send two payments from Alice to Dave. One will - // have a fee cutoff expressed as a percentage of the amount and the - // other will have it expressed as a fixed amount of satoshis. - const paymentAmt = 100 - carolFee := computeFee(lnwire.MilliSatoshi(baseFee), 1, paymentAmt) - - // testFeeCutoff is a helper closure that will ensure the different - // types of fee limits work as intended when querying routes and sending - // payments. - testFeeCutoff := func(feeLimit *lnrpc.FeeLimit) { - queryRoutesReq := &lnrpc.QueryRoutesRequest{ - PubKey: dave.PubKeyStr, - Amt: paymentAmt, - FeeLimit: feeLimit, - } - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - routesResp, err := net.Alice.QueryRoutes(ctxt, queryRoutesReq) - if err != nil { - t.Fatalf("unable to get routes: %v", err) + // Wait for Carol to report no outstanding htlcs. + carolNode := []*lntest.HarnessNode{carol} + err = wait.Predicate(func() bool { + predErr = assertNumActiveHtlcs(carolNode, 0) + if predErr != nil { + return false } - checkRoute(routesResp.Routes[0]) - - invoice := &lnrpc.Invoice{Value: paymentAmt} - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - invoiceResp, err := dave.AddInvoice(ctxt, invoice) - if err != nil { - t.Fatalf("unable to create invoice: %v", err) - } + predErr = assertNumActiveHtlcsChanPoint(dave, carolFundPoint, 0) + return predErr == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc mismatch: %v", predErr) + } - sendReq := &routerrpc.SendPaymentRequest{ - PaymentRequest: invoiceResp.PaymentRequest, - TimeoutSeconds: 60, - FeeLimitMsat: noFeeLimitMsat, - } - switch limit := feeLimit.Limit.(type) { - case *lnrpc.FeeLimit_Fixed: - sendReq.FeeLimitMsat = 1000 * limit.Fixed - case *lnrpc.FeeLimit_Percent: - sendReq.FeeLimitMsat = 1000 * paymentAmt * limit.Percent / 100 - } + // Now check that the total amount was transferred from Dave to Carol. + // The amount transferred should be exactly equal to the invoice total + // payment amount, 5k satsohis. + const amountPaid = int64(5000) + assertAmountPaid(t, "Dave(local) => Carol(remote)", carol, + carolFundPoint, int64(0), amountPaid) + assertAmountPaid(t, "Dave(local) => Carol(remote)", dave, + carolFundPoint, amountPaid, int64(0)) - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - result := sendAndAssertSuccess(ctxt, t, net.Alice, sendReq) + // Shutdown carol and leave her offline for the rest of the test. This + // is critical, as we wish to see if Dave can propragate settles even if + // the outgoing link is never revived. + shutdownAndAssert(net, t, carol) - checkRoute(result.Htlcs[0].Route) + // Now restart Dave, ensuring he is both persisting the settles, and is + // able to reforward them to Alice after recovering from a restart. + if err := net.RestartNode(dave, nil); err != nil { + t.Fatalf("unable to restart dave: %v", err) + } + if err = net.RestartNode(net.Alice, nil); err != nil { + t.Fatalf("unable to restart alice: %v", err) } - // We'll start off using percentages first. Since the fee along the - // route using Carol as an intermediate hop is 10% of the payment's - // amount, we'll use a lower percentage in order to invalid that route. - feeLimitPercent := &lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Percent{ - Percent: baseFee/1000 - 1, - }, + // Ensure that Dave is reconnected to Alice before waiting for the + // htlcs to clear. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = net.EnsureConnected(ctxt, dave, net.Alice) + if err != nil { + t.Fatalf("unable to reconnect alice and dave: %v", err) } - testFeeCutoff(feeLimitPercent) - // Now we'll test using fixed fee limit amounts. Since we computed the - // fee for the route using Carol as an intermediate hop earlier, we can - // use a smaller value in order to invalidate that route. - feeLimitFixed := &lnrpc.FeeLimit{ - Limit: &lnrpc.FeeLimit_Fixed{ - Fixed: int64(carolFee.ToSatoshis()) - 1, - }, + // Since Carol has been shutdown permanently, we will wait until all + // other nodes in the network report no active htlcs. + nodesMinusCarol := []*lntest.HarnessNode{net.Bob, net.Alice, dave} + err = wait.Predicate(func() bool { + predErr = assertNumActiveHtlcs(nodesMinusCarol, 0) + return predErr == nil + }, defaultTimeout) + if err != nil { + t.Fatalf("htlc mismatch: %v", predErr) } - testFeeCutoff(feeLimitFixed) - // Once we're done, close the channels and shut down the nodes created - // throughout this test. - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceBob, false) - ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAliceCarol, false) + // When asserting the amount of satoshis moved, we'll factor in the + // default base fee, as we didn't modify the fee structure when + // creating the seed nodes in the network. + const baseFee = 1 + + // At this point, all channels (minus Carol, who is shutdown) should + // show a shift of 5k satoshis towards Carol. The order of asserts + // corresponds to increasing of time is needed to embed the HTLC in + // commitment transaction, in channel Bob->Alice->David, order is + // David, Alice, Bob. + assertAmountPaid(t, "Alice(local) => Dave(remote)", dave, + daveFundPoint, int64(0), amountPaid+(baseFee*numPayments)) + assertAmountPaid(t, "Alice(local) => Dave(remote)", net.Alice, + daveFundPoint, amountPaid+(baseFee*numPayments), int64(0)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Alice, + aliceFundPoint, int64(0), amountPaid+((baseFee*numPayments)*2)) + assertAmountPaid(t, "Bob(local) => Alice(remote)", net.Bob, + aliceFundPoint, amountPaid+(baseFee*numPayments)*2, int64(0)) + ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, net.Bob, chanPointBobDave, false) + closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false) ctxt, _ = context.WithTimeout(ctxb, channelCloseTimeout) - closeChannelAndAssert(ctxt, t, net, carol, chanPointCarolDave, false) + closeChannelAndAssert(ctxt, t, net, dave, chanPointDave, false) } // testSendUpdateDisableChannel ensures that a channel update with the disable @@ -13254,7 +11145,7 @@ func testSendUpdateDisableChannel(net *lntest.NetworkHarness, t *harnessTest) { t.Fatalf("unable to connect bob to dave: %v", err) } - daveSub := subscribeGraphNotifications(t, ctxb, dave) + daveSub := subscribeGraphNotifications(ctxb, t, dave) defer close(daveSub.quit) // We should expect to see a channel update with the default routing diff --git a/lntest/itest/lnd_test_list_on_test.go b/lntest/itest/lnd_test_list_on_test.go index d5ecc34a32d..d3430f4fdd9 100644 --- a/lntest/itest/lnd_test_list_on_test.go +++ b/lntest/itest/lnd_test_list_on_test.go @@ -107,6 +107,11 @@ var allTestCases = []*testCase{ name: "private channels", test: testPrivateChannels, }, + { + name: "private channel update policy", + test: testUpdateChannelPolicyForPrivateChannel, + }, + { name: "invoice routing hints", test: testInvoiceRoutingHints, diff --git a/lntest/itest/log_error_whitelist.txt b/lntest/itest/log_error_whitelist.txt index 82fe9f3b06c..07e8a15052f 100644 --- a/lntest/itest/log_error_whitelist.txt +++ b/lntest/itest/log_error_whitelist.txt @@ -285,3 +285,4 @@