lntemp+itest: refactor testSingleHopSendToRoute

This commit is contained in:
yyforyongyu
2022-08-09 01:20:00 +08:00
parent ede86cf08f
commit c5785967aa
5 changed files with 116 additions and 216 deletions

View File

@ -2014,3 +2014,31 @@ func (h *HarnessTest) AssertChannelCommitHeight(hn *node.HarnessNode,
require.NoError(h, err, "timeout while waiting for commit height") require.NoError(h, err, "timeout while waiting for commit height")
} }
// AssertNumInvoices asserts that the number of invoices made within the test
// scope is as expected.
func (h *HarnessTest) AssertNumInvoices(hn *node.HarnessNode,
num int) []*lnrpc.Invoice {
have := hn.State.Invoice.Total
req := &lnrpc.ListInvoiceRequest{
NumMaxInvoices: math.MaxUint64,
IndexOffset: hn.State.Invoice.LastIndexOffset,
}
var invoices []*lnrpc.Invoice
err := wait.NoError(func() error {
resp := hn.RPC.ListInvoices(req)
invoices = resp.Invoices
if len(invoices) == num {
return nil
}
return errNumNotMatched(hn.Name(), "num of invoices",
num, len(invoices), have+len(invoices), have)
}, DefaultTimeout)
require.NoError(h, err, "timeout checking num of invoices")
return invoices
}

View File

@ -3,6 +3,7 @@ package rpc
import ( import (
"context" "context"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
) )
@ -92,3 +93,16 @@ func (h *HarnessRPC) ResetMissionControl() {
_, err := h.Router.ResetMissionControl(ctxt, req) _, err := h.Router.ResetMissionControl(ctxt, req)
h.NoError(err, "ResetMissionControl") h.NoError(err, "ResetMissionControl")
} }
// SendToRouteV2 makes a RPC call to SendToRouteV2 and asserts.
func (h *HarnessRPC) SendToRouteV2(
req *routerrpc.SendToRouteRequest) *lnrpc.HTLCAttempt {
ctxt, cancel := context.WithTimeout(h.runCtx, DefaultTimeout)
defer cancel()
resp, err := h.Router.SendToRouteV2(ctxt, req)
h.NoError(err, "SendToRouteV2")
return resp
}

View File

@ -305,7 +305,12 @@ var allTestCasesTemp = []*lntemp.TestCase{
TestFunc: testRevokedCloseRetributionRemoteHodl, TestFunc: testRevokedCloseRetributionRemoteHodl,
}, },
{ {
Name: "revoked uncooperative close retribution altruist watchtower", Name: "revoked uncooperative close retribution altruist " +
"watchtower",
TestFunc: testRevokedCloseRetributionAltruistWatchtower, TestFunc: testRevokedCloseRetributionAltruistWatchtower,
}, },
{
Name: "single-hop send to route",
TestFunc: testSingleHopSendToRoute,
},
} }

View File

@ -1,7 +1,6 @@
package itest package itest
import ( import (
"bytes"
"context" "context"
"encoding/hex" "encoding/hex"
"fmt" "fmt"
@ -14,6 +13,7 @@ import (
"github.com/lightningnetwork/lnd/chainreg" "github.com/lightningnetwork/lnd/chainreg"
"github.com/lightningnetwork/lnd/lnrpc" "github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc" "github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lntemp"
"github.com/lightningnetwork/lnd/lntest" "github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/wait" "github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -68,23 +68,18 @@ var singleHopSendToRouteCases = []singleHopSendToRouteCase{
// by feeding the route back into the various SendToRoute RPC methods. Here we // 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 // test all three SendToRoute endpoints, forcing each to perform both a regular
// payment and an MPP payment. // payment and an MPP payment.
func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) { func testSingleHopSendToRoute(ht *lntemp.HarnessTest) {
for _, test := range singleHopSendToRouteCases { for _, test := range singleHopSendToRouteCases {
test := test test := test
t.t.Run(test.name, func(t1 *testing.T) { ht.Run(test.name, func(t1 *testing.T) {
ht := newHarnessTest(t1, t.lndHarness) st := ht.Subtest(t1)
ht.RunTestCase(&testCase{ testSingleHopSendToRouteCase(st, test)
name: test.name,
test: func(_ *lntest.NetworkHarness, tt *harnessTest) {
testSingleHopSendToRouteCase(net, tt, test)
},
})
}) })
} }
} }
func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest, func testSingleHopSendToRouteCase(ht *lntemp.HarnessTest,
test singleHopSendToRouteCase) { test singleHopSendToRouteCase) {
const chanAmt = btcutil.Amount(100000) const chanAmt = btcutil.Amount(100000)
@ -92,96 +87,41 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
const numPayments = 5 const numPayments = 5
const amountPaid = int64(numPayments * paymentAmtSat) const amountPaid = int64(numPayments * paymentAmtSat)
ctxb := context.Background()
var networkChans []*lnrpc.ChannelPoint
// Create Carol and Dave, then establish a channel between them. Carol // Create Carol and Dave, then establish a channel between them. Carol
// is the sole funder of the channel with 100k satoshis. The network // is the sole funder of the channel with 100k satoshis. The network
// topology should look like: // topology should look like:
// Carol -> 100k -> Dave // Carol -> 100k -> Dave
carol := net.NewNode(t.t, "Carol", nil) carol := ht.NewNode("Carol", nil)
defer shutdownAndAssert(net, t, carol) dave := ht.NewNode("Dave", nil)
dave := net.NewNode(t.t, "Dave", nil) ht.ConnectNodes(carol, dave)
defer shutdownAndAssert(net, t, dave) ht.FundCoins(btcutil.SatoshiPerBitcoin, carol)
net.ConnectNodes(t.t, carol, dave)
net.SendCoins(t.t, btcutil.SatoshiPerBitcoin, carol)
// Open a channel with 100k satoshis between Carol and Dave with Carol // Open a channel with 100k satoshis between Carol and Dave with Carol
// being the sole funder of the channel. // being the sole funder of the channel.
chanPointCarol := openChannelAndAssert( chanPointCarol := ht.OpenChannel(
t, net, carol, dave, carol, dave, lntemp.OpenChannelParams{Amt: chanAmt},
lntest.OpenChannelParams{
Amt: chanAmt,
},
) )
networkChans = append(networkChans, chanPointCarol) defer ht.CloseChannel(carol, 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,
}
err = node.WaitForNetworkChannelOpen(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. // Create invoices for Dave, which expect a payment from Carol.
payReqs, rHashes, _, err := createPayReqs( payReqs, rHashes, _ := ht.CreatePayReqs(
dave, paymentAmtSat, numPayments, dave, paymentAmtSat, numPayments,
) )
if err != nil {
t.Fatalf("unable to create pay reqs: %v", err)
}
// Reconstruct payment addresses. // Reconstruct payment addresses.
var payAddrs [][]byte var payAddrs [][]byte
for _, payReq := range payReqs { for _, payReq := range payReqs {
ctx, _ := context.WithTimeout( resp := dave.RPC.DecodePayReq(payReq)
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) payAddrs = append(payAddrs, resp.PaymentAddr)
} }
// Assert Carol and Dave are synced to the chain before proceeding, to // 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 // ensure the queried route will have a valid final CLTV once the HTLC
// reaches Dave. // reaches Dave.
_, minerHeight, err := net.Miner.Client.GetBestBlock() _, minerHeight := ht.Miner.GetBestBlock()
if err != nil { ht.WaitForNodeBlockHeight(carol, minerHeight)
t.Fatalf("unable to get best height: %v", err) ht.WaitForNodeBlockHeight(dave, minerHeight)
}
require.NoError(t.t, waitForNodeBlockHeight(carol, minerHeight))
require.NoError(t.t, waitForNodeBlockHeight(dave, minerHeight))
// Query for routes to pay from Carol to Dave using the default CLTV // Query for routes to pay from Carol to Dave using the default CLTV
// config. // config.
@ -189,12 +129,7 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
PubKey: dave.PubKeyStr, PubKey: dave.PubKeyStr,
Amt: paymentAmtSat, Amt: paymentAmtSat,
} }
ctxt, _ := context.WithTimeout(ctxb, defaultTimeout) routes := carol.RPC.QueryRoutes(routesReq)
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. // There should only be one route to try, so take the first item.
r := routes.Routes[0] r := routes.Routes[0]
@ -222,27 +157,14 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
PaymentHash: rHash, PaymentHash: rHash,
Route: r, Route: r,
} }
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) resp := carol.RPC.SendToRouteSync(sendReq)
resp, err := carol.SendToRouteSync( require.Emptyf(ht, resp.PaymentError,
ctxt, sendReq, "received payment error from %s: %v",
)
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) carol.Name(), resp.PaymentError)
} }
} }
}
sendToRouteStream := func() { sendToRouteStream := func() {
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) alicePayStream := carol.RPC.SendToRoute()
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 { for i, rHash := range rHashes {
setMPPFields(i) setMPPFields(i)
@ -252,19 +174,13 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
Route: routes.Routes[0], Route: routes.Routes[0],
} }
err := alicePayStream.Send(sendReq) err := alicePayStream.Send(sendReq)
require.NoError(ht, err, "unable to send payment")
if err != nil {
t.Fatalf("unable to send payment: %v", err)
}
resp, err := alicePayStream.Recv() resp, err := alicePayStream.Recv()
if err != nil { require.NoError(ht, err, "unable to receive stream")
t.Fatalf("unable to send payment: %v", err) require.Emptyf(ht, resp.PaymentError,
} "received payment error from %s: %v",
if resp.PaymentError != "" { carol.Name(), resp.PaymentError)
t.Fatalf("received payment error: %v",
resp.PaymentError)
}
} }
} }
sendToRouteRouterRPC := func() { sendToRouteRouterRPC := func() {
@ -275,23 +191,14 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
PaymentHash: rHash, PaymentHash: rHash,
Route: r, Route: r,
} }
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) resp := carol.RPC.SendToRouteV2(sendReq)
resp, err := carol.RouterClient.SendToRouteV2( require.Nilf(ht, resp.Failure, "received payment "+
ctxt, sendReq, "error from %s", carol.Name())
)
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 // Using Carol as the node as the source, send the payments
// synchronously via the the routerrpc's SendToRoute, or via the main RPC // synchronously via the routerrpc's SendToRoute, or via the main RPC
// server's SendToRoute streaming or sync calls. // server's SendToRoute streaming or sync calls.
switch { switch {
case !test.routerrpc && test.streaming: case !test.routerrpc && test.streaming:
@ -301,118 +208,70 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
case test.routerrpc && !test.streaming: case test.routerrpc && !test.streaming:
sendToRouteRouterRPC() sendToRouteRouterRPC()
default: default:
t.Fatalf("routerrpc does not support streaming send_to_route") require.Fail(ht, "routerrpc does not support "+
"streaming send_to_route")
} }
// Verify that the payment's from Carol's PoV have the correct payment // Verify that the payment's from Carol's PoV have the correct payment
// hash and amount. // hash and amount.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) payments := ht.AssertNumPayments(carol, numPayments)
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 { for i, p := range payments {
// Assert that the payment hashes for each payment match up. // Assert that the payment hashes for each payment match up.
rHashHex := hex.EncodeToString(rHashes[i]) rHashHex := hex.EncodeToString(rHashes[i])
if p.PaymentHash != rHashHex { require.Equalf(ht, rHashHex, p.PaymentHash,
t.Fatalf("incorrect payment hash for payment %d, "+ "incorrect payment hash for payment %d", i)
"want: %s got: %s",
i, rHashHex, p.PaymentHash)
}
// Assert that each payment has no invoice since the payment was // Assert that each payment has no invoice since the payment
// completed using SendToRoute. // was completed using SendToRoute.
if p.PaymentRequest != "" { require.Emptyf(ht, p.PaymentRequest,
t.Fatalf("incorrect payment request for payment: %d, "+ "incorrect payment request for payment: %d", i)
"want: \"\", got: %s",
i, p.PaymentRequest)
}
// Assert the payment amount is correct. // Assert the payment amount is correct.
if p.ValueSat != paymentAmtSat { require.EqualValues(ht, paymentAmtSat, p.ValueSat,
t.Fatalf("incorrect payment amt for payment %d, "+ "incorrect payment amt for payment %d, ", i)
"want: %d, got: %d",
i, paymentAmtSat, p.ValueSat)
}
// Assert exactly one htlc was made. // Assert exactly one htlc was made.
if len(p.Htlcs) != 1 { require.Lenf(ht, p.Htlcs, 1,
t.Fatalf("expected 1 htlc for payment %d, got: %d", "expected 1 htlc for payment %d", i)
i, len(p.Htlcs))
}
// Assert the htlc's route is populated. // Assert the htlc's route is populated.
htlc := p.Htlcs[0] htlc := p.Htlcs[0]
if htlc.Route == nil { require.NotNilf(ht, htlc.Route,
t.Fatalf("expected route for payment %d", i) "expected route for payment %d", i)
}
// Assert the hop has exactly one hop. // Assert the hop has exactly one hop.
if len(htlc.Route.Hops) != 1 { require.Lenf(ht, htlc.Route.Hops, 1,
t.Fatalf("expected 1 hop for payment %d, got: %d", "expected 1 hop for payment %d", i)
i, len(htlc.Route.Hops))
}
// If this is an MPP test, assert the MPP record's fields are // If this is an MPP test, assert the MPP record's fields are
// properly populated. Otherwise the hop should not have an MPP // properly populated. Otherwise the hop should not have an MPP
// record. // record.
hop := htlc.Route.Hops[0] hop := htlc.Route.Hops[0]
if hop.MppRecord == nil { require.NotNilf(ht, hop.MppRecord,
t.Fatalf("expected mpp record for mpp payment") "expected mpp record for mpp payment %d", i)
}
if hop.MppRecord.TotalAmtMsat != paymentAmtSat*1000 { require.EqualValues(ht, paymentAmtSat*1000,
t.Fatalf("incorrect mpp total msat for payment %d "+ hop.MppRecord.TotalAmtMsat,
"want: %d, got: %d", "incorrect mpp total msat for payment %d", i)
i, paymentAmtSat*1000,
hop.MppRecord.TotalAmtMsat)
}
expAddr := payAddrs[i] expAddr := payAddrs[i]
if !bytes.Equal(hop.MppRecord.PaymentAddr, expAddr) { require.Equal(ht, expAddr, hop.MppRecord.PaymentAddr,
t.Fatalf("incorrect mpp payment addr for payment %d "+ "incorrect mpp payment addr for payment %d ", i)
"want: %x, got: %x",
i, expAddr, hop.MppRecord.PaymentAddr)
}
} }
// Verify that the invoices's from Dave's PoV have the correct payment // Verify that the invoices's from Dave's PoV have the correct payment
// hash and amount. // hash and amount.
ctxt, _ = context.WithTimeout(ctxb, defaultTimeout) invoices := ht.AssertNumInvoices(dave, numPayments)
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 { for i, inv := range invoices {
// Assert that the payment hashes match up. // Assert that the payment hashes match up.
if !bytes.Equal(inv.RHash, rHashes[i]) { require.Equal(ht, rHashes[i], inv.RHash,
t.Fatalf("incorrect payment hash for invoice %d, "+ "incorrect payment hash for invoice %d", i)
"want: %x got: %x",
i, rHashes[i], inv.RHash)
}
// Assert that the amount paid to the invoice is correct. // Assert that the amount paid to the invoice is correct.
if inv.AmtPaidSat != paymentAmtSat { require.EqualValues(ht, paymentAmtSat, inv.AmtPaidSat,
t.Fatalf("incorrect payment amt for invoice %d, "+ "incorrect payment amt for invoice %d, ", i)
"want: %d, got %d",
i, paymentAmtSat, inv.AmtPaidSat)
}
} }
// At this point all the channels within our proto network should be // At this point all the channels within our proto network should be
@ -420,12 +279,10 @@ func testSingleHopSendToRouteCase(net *lntest.NetworkHarness, t *harnessTest,
// payment flow generated above. The order of asserts corresponds to // payment flow generated above. The order of asserts corresponds to
// increasing of time is needed to embed the HTLC in commitment // increasing of time is needed to embed the HTLC in commitment
// transaction, in channel Carol->Dave, order is Dave and then Carol. // transaction, in channel Carol->Dave, order is Dave and then Carol.
assertAmountPaid(t, "Carol(local) => Dave(remote)", dave, ht.AssertAmountPaid("Carol(local) => Dave(remote)", dave,
carolFundPoint, int64(0), amountPaid) chanPointCarol, int64(0), amountPaid)
assertAmountPaid(t, "Carol(local) => Dave(remote)", carol, ht.AssertAmountPaid("Carol(local) => Dave(remote)", carol,
carolFundPoint, amountPaid, int64(0)) chanPointCarol, amountPaid, int64(0))
closeChannelAndAssert(t, net, carol, chanPointCarol, false)
} }
// testMultiHopSendToRoute tests that payments are properly processed // testMultiHopSendToRoute tests that payments are properly processed

View File

@ -8,10 +8,6 @@ var allTestCases = []*testCase{
name: "single hop invoice", name: "single hop invoice",
test: testSingleHopInvoice, test: testSingleHopInvoice,
}, },
{
name: "single-hop send to route",
test: testSingleHopSendToRoute,
},
{ {
name: "multi-hop send to route", name: "multi-hop send to route",
test: testMultiHopSendToRoute, test: testMultiHopSendToRoute,