diff --git a/lntest/itest/lnd_multi-hop-payments.go b/lntest/itest/lnd_multi-hop-payments.go new file mode 100644 index 000000000..fde4c8c90 --- /dev/null +++ b/lntest/itest/lnd_multi-hop-payments.go @@ -0,0 +1,289 @@ +// +build rpctest + +package itest + +import ( + "context" + "time" + + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + "github.com/lightningnetwork/lnd" + "github.com/lightningnetwork/lnd/lnrpc" + "github.com/lightningnetwork/lnd/lntest" +) + +func testMultiHopPayments(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 := lnd.GetChanPointFundingTxid(chanPointAlice) + if err != nil { + t.Fatalf("unable to get txid: %v", err) + } + aliceFundPoint := wire.OutPoint{ + Hash: *aliceChanTXID, + Index: chanPointAlice.OutputIndex, + } + + // As preliminary setup, we'll create two new nodes: Carol and Dave, + // such that we now have a 4 node, 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 will + // be running an older node that requires the legacy onion payload. + daveArgs := []string{"--protocol.legacyonion"} + dave, err := net.NewNode("Dave", daveArgs) + 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 := lnd.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 to from her to + // Dave. + 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 := lnd.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 := lnd.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 Bob, which expect a payment from Carol for 1k + // satoshis with a different preimage each time. + const numPayments = 5 + const paymentAmt = 1000 + payReqs, _, _, err := createPayReqs( + net.Bob, paymentAmt, numPayments, + ) + if err != nil { + t.Fatalf("unable to create pay reqs: %v", err) + } + + // 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) + err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) + if err != nil { + t.Fatalf("carol didn't advertise her channel in time: %v", + err) + } + + time.Sleep(time.Millisecond * 50) + + // Set the fee policies of the Alice -> Bob and the Dave -> Alice + // channel edges to relatively large non default values. This makes it + // possible to pick up more subtle fee calculation errors. + maxHtlc := uint64(calculateMaxHtlc(chanAmt)) + updateChannelPolicy( + t, net.Alice, chanPointAlice, 1000, 100000, + lnd.DefaultBitcoinTimeLockDelta, maxHtlc, carol, + ) + + updateChannelPolicy( + t, dave, chanPointDave, 5000, 150000, + lnd.DefaultBitcoinTimeLockDelta, maxHtlc, carol, + ) + + // Using Carol as the source, pay to the 5 invoices from Bob created + // above. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + err = completePaymentRequests(ctxt, carol, 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 + + // At this point all the channels within our proto network should be + // shifted by 5k satoshis in the direction of Bob, 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->David->Alice->Bob, order is Bob, + // Alice, David, Carol. + + // The final node bob expects to get paid five times 1000 sat. + expectedAmountPaidAtoB := int64(5 * 1000) + + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, + aliceFundPoint, int64(0), expectedAmountPaidAtoB) + assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, + aliceFundPoint, expectedAmountPaidAtoB, int64(0)) + + // To forward a payment of 1000 sat, Alice is charging a fee of + // 1 sat + 10% = 101 sat. + const expectedFeeAlice = 5 * 101 + + // Dave needs to pay what Alice pays plus Alice's fee. + expectedAmountPaidDtoA := expectedAmountPaidAtoB + expectedFeeAlice + + assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, + daveFundPoint, int64(0), expectedAmountPaidDtoA) + assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, + daveFundPoint, expectedAmountPaidDtoA, int64(0)) + + // To forward a payment of 1101 sat, Dave is charging a fee of + // 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170. + const expectedFeeDave = 5 * 170 + + // Carol needs to pay what Dave pays plus Dave's fee. + expectedAmountPaidCtoD := expectedAmountPaidDtoA + expectedFeeDave + + assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, + carolFundPoint, int64(0), expectedAmountPaidCtoD) + assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, + carolFundPoint, expectedAmountPaidCtoD, int64(0)) + + // Now that we know all the balances have been settled out properly, + // we'll ensure that our internal record keeping for completed circuits + // was properly updated. + + // First, check that the FeeReport response shows the proper fees + // accrued over each time range. Dave should've earned 170 satoshi for + // each of the forwarded payments. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + feeReport, err := dave.FeeReport(ctxt, &lnrpc.FeeReportRequest{}) + if err != nil { + t.Fatalf("unable to query for fee report: %v", err) + } + + if feeReport.DayFeeSum != uint64(expectedFeeDave) { + t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave, + feeReport.DayFeeSum) + } + if feeReport.WeekFeeSum != uint64(expectedFeeDave) { + t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave, + feeReport.WeekFeeSum) + } + if feeReport.MonthFeeSum != uint64(expectedFeeDave) { + t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave, + feeReport.MonthFeeSum) + } + + // Next, ensure that if we issue the vanilla query for the forwarding + // history, it returns 5 values, and each entry is formatted properly. + ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) + fwdingHistory, err := dave.ForwardingHistory( + ctxt, &lnrpc.ForwardingHistoryRequest{}, + ) + if err != nil { + t.Fatalf("unable to query for fee report: %v", err) + } + if len(fwdingHistory.ForwardingEvents) != 5 { + t.Fatalf("wrong number of forwarding event: expected %v, "+ + "got %v", 5, len(fwdingHistory.ForwardingEvents)) + } + expectedForwardingFee := uint64(expectedFeeDave / numPayments) + for _, event := range fwdingHistory.ForwardingEvents { + // Each event should show a fee of 170 satoshi. + if event.Fee != expectedForwardingFee { + t.Fatalf("fee mismatch: expected %v, got %v", + expectedForwardingFee, event.Fee) + } + } + + 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) +} diff --git a/lntest/itest/lnd_test.go b/lntest/itest/lnd_test.go index b5e1eade4..9d56f108f 100644 --- a/lntest/itest/lnd_test.go +++ b/lntest/itest/lnd_test.go @@ -4504,281 +4504,6 @@ func updateChannelPolicy(t *harnessTest, node *lntest.HarnessNode, ) } -func testMultiHopPayments(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 := lnd.GetChanPointFundingTxid(chanPointAlice) - if err != nil { - t.Fatalf("unable to get txid: %v", err) - } - aliceFundPoint := wire.OutPoint{ - Hash: *aliceChanTXID, - Index: chanPointAlice.OutputIndex, - } - - // As preliminary setup, we'll create two new nodes: Carol and Dave, - // such that we now have a 4 node, 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 will - // be running an older node that requires the legacy onion payload. - daveArgs := []string{"--protocol.legacyonion"} - dave, err := net.NewNode("Dave", daveArgs) - 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 := lnd.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 to from her to - // Dave. - 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 := lnd.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 := lnd.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 Bob, which expect a payment from Carol for 1k - // satoshis with a different preimage each time. - const numPayments = 5 - const paymentAmt = 1000 - payReqs, _, _, err := createPayReqs( - net.Bob, paymentAmt, numPayments, - ) - if err != nil { - t.Fatalf("unable to create pay reqs: %v", err) - } - - // 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) - err = carol.WaitForNetworkChannelOpen(ctxt, chanPointCarol) - if err != nil { - t.Fatalf("carol didn't advertise her channel in time: %v", - err) - } - - time.Sleep(time.Millisecond * 50) - - // Set the fee policies of the Alice -> Bob and the Dave -> Alice - // channel edges to relatively large non default values. This makes it - // possible to pick up more subtle fee calculation errors. - maxHtlc := uint64(calculateMaxHtlc(chanAmt)) - updateChannelPolicy( - t, net.Alice, chanPointAlice, 1000, 100000, - lnd.DefaultBitcoinTimeLockDelta, maxHtlc, carol, - ) - - updateChannelPolicy( - t, dave, chanPointDave, 5000, 150000, - lnd.DefaultBitcoinTimeLockDelta, maxHtlc, carol, - ) - - // Using Carol as the source, pay to the 5 invoices from Bob created - // above. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - err = completePaymentRequests(ctxt, carol, 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 - - // At this point all the channels within our proto network should be - // shifted by 5k satoshis in the direction of Bob, 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->David->Alice->Bob, order is Bob, - // Alice, David, Carol. - - // The final node bob expects to get paid five times 1000 sat. - expectedAmountPaidAtoB := int64(5 * 1000) - - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Bob, - aliceFundPoint, int64(0), expectedAmountPaidAtoB) - assertAmountPaid(t, "Alice(local) => Bob(remote)", net.Alice, - aliceFundPoint, expectedAmountPaidAtoB, int64(0)) - - // To forward a payment of 1000 sat, Alice is charging a fee of - // 1 sat + 10% = 101 sat. - const expectedFeeAlice = 5 * 101 - - // Dave needs to pay what Alice pays plus Alice's fee. - expectedAmountPaidDtoA := expectedAmountPaidAtoB + expectedFeeAlice - - assertAmountPaid(t, "Dave(local) => Alice(remote)", net.Alice, - daveFundPoint, int64(0), expectedAmountPaidDtoA) - assertAmountPaid(t, "Dave(local) => Alice(remote)", dave, - daveFundPoint, expectedAmountPaidDtoA, int64(0)) - - // To forward a payment of 1101 sat, Dave is charging a fee of - // 5 sat + 15% = 170.15 sat. This is rounded down in rpcserver to 170. - const expectedFeeDave = 5 * 170 - - // Carol needs to pay what Dave pays plus Dave's fee. - expectedAmountPaidCtoD := expectedAmountPaidDtoA + expectedFeeDave - - assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, - carolFundPoint, int64(0), expectedAmountPaidCtoD) - assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, - carolFundPoint, expectedAmountPaidCtoD, int64(0)) - - // Now that we know all the balances have been settled out properly, - // we'll ensure that our internal record keeping for completed circuits - // was properly updated. - - // First, check that the FeeReport response shows the proper fees - // accrued over each time range. Dave should've earned 170 satoshi for - // each of the forwarded payments. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - feeReport, err := dave.FeeReport(ctxt, &lnrpc.FeeReportRequest{}) - if err != nil { - t.Fatalf("unable to query for fee report: %v", err) - } - - if feeReport.DayFeeSum != uint64(expectedFeeDave) { - t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave, - feeReport.DayFeeSum) - } - if feeReport.WeekFeeSum != uint64(expectedFeeDave) { - t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave, - feeReport.WeekFeeSum) - } - if feeReport.MonthFeeSum != uint64(expectedFeeDave) { - t.Fatalf("fee mismatch: expected %v, got %v", expectedFeeDave, - feeReport.MonthFeeSum) - } - - // Next, ensure that if we issue the vanilla query for the forwarding - // history, it returns 5 values, and each entry is formatted properly. - ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) - fwdingHistory, err := dave.ForwardingHistory( - ctxt, &lnrpc.ForwardingHistoryRequest{}, - ) - if err != nil { - t.Fatalf("unable to query for fee report: %v", err) - } - if len(fwdingHistory.ForwardingEvents) != 5 { - t.Fatalf("wrong number of forwarding event: expected %v, "+ - "got %v", 5, len(fwdingHistory.ForwardingEvents)) - } - expectedForwardingFee := uint64(expectedFeeDave / numPayments) - for _, event := range fwdingHistory.ForwardingEvents { - // Each event should show a fee of 170 satoshi. - if event.Fee != expectedForwardingFee { - t.Fatalf("fee mismatch: expected %v, got %v", - expectedForwardingFee, event.Fee) - } - } - - 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) -} - type singleHopSendToRouteCase struct { name string