tests: add integration tests for send to route
This commit add 3 integration tests for send-to-route RPC call. One test ensures that single-hop payments are processed. Another test checks that payments through a multi-hop route are processed. Lastly, there is a test to check error propagation while sending payments via fake predefined route.
This commit is contained in:
parent
f4c9192df3
commit
e4e8b86137
481
lnd_test.go
481
lnd_test.go
@ -3003,6 +3003,475 @@ func testMultiHopPayments(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
}
|
||||
}
|
||||
|
||||
// testSingleHopSendToRoute tests that payments are properly processed
|
||||
// through a provided route with a single hop. We'll create the
|
||||
// following network topology:
|
||||
// Alice --100k--> Bob
|
||||
// We'll query the daemon for routes from Alice to Bob and then
|
||||
// send payments through the route.
|
||||
func testSingleHopSendToRoute(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
ctxb := context.Background()
|
||||
timeout := time.Duration(time.Second * 15)
|
||||
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, timeout)
|
||||
chanPointAlice := openChannelAndAssert(ctxt, t, net, net.Alice,
|
||||
net.Bob, chanAmt, 0, false)
|
||||
networkChans = append(networkChans, chanPointAlice)
|
||||
|
||||
txidHash, err := getChanPointFundingTxid(chanPointAlice)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
aliceChanTXID, err := chainhash.NewHash(txidHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create sha hash: %v", err)
|
||||
}
|
||||
aliceFundPoint := wire.OutPoint{
|
||||
Hash: *aliceChanTXID,
|
||||
Index: chanPointAlice.OutputIndex,
|
||||
}
|
||||
|
||||
// Wait for all nodes to have seen all channels.
|
||||
nodes := []*lntest.HarnessNode{net.Alice, net.Bob}
|
||||
nodeNames := []string{"Alice", "Bob"}
|
||||
for _, chanPoint := range networkChans {
|
||||
for i, node := range nodes {
|
||||
txidHash, err := getChanPointFundingTxid(chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
txid, e := chainhash.NewHash(txidHash)
|
||||
if e != nil {
|
||||
t.Fatalf("unable to create sha hash: %v", e)
|
||||
}
|
||||
point := wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: chanPoint.OutputIndex,
|
||||
}
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
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 Bob.
|
||||
// We set FinalCltvDelta to 144 since by default QueryRoutes returns
|
||||
// the last hop with a final cltv delta of 9 where as the default in
|
||||
// htlcswitch is 144.
|
||||
const paymentAmt = 1000
|
||||
routesReq := &lnrpc.QueryRoutesRequest{
|
||||
PubKey: net.Bob.PubKeyStr,
|
||||
Amt: paymentAmt,
|
||||
NumRoutes: 1,
|
||||
FinalCltvDelta: 144,
|
||||
}
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
routes, err := net.Alice.QueryRoutes(ctxt, routesReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get route: %v", err)
|
||||
}
|
||||
|
||||
// Create 5 invoices for Bob, which expect a payment from Alice for 1k
|
||||
// satoshis with a different preimage each time.
|
||||
const numPayments = 5
|
||||
rHashes := make([][]byte, numPayments)
|
||||
for i := 0; i < numPayments; i++ {
|
||||
invoice := &lnrpc.Invoice{
|
||||
Value: paymentAmt,
|
||||
}
|
||||
resp, err := net.Bob.AddInvoice(ctxb, invoice)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add invoice: %v", err)
|
||||
}
|
||||
|
||||
rHashes[i] = resp.RHash
|
||||
}
|
||||
|
||||
// We'll wait for all parties to recognize the new channels within the
|
||||
// network.
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
err = net.Bob.WaitForNetworkChannelOpen(ctxt, chanPointAlice)
|
||||
if err != nil {
|
||||
t.Fatalf("alice didn't advertise her 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, timeout)
|
||||
alicePayStream, err := net.Alice.SendToRoute(ctxt)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create payment stream for alice: %v", err)
|
||||
}
|
||||
|
||||
for _, rHash := range rHashes {
|
||||
sendReq := &lnrpc.SendToRouteRequest{
|
||||
PaymentHash: rHash,
|
||||
Routes: routes.Routes,
|
||||
}
|
||||
err := alicePayStream.Send(sendReq)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send payment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for range rHashes {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 Alice->Bob, order is Bob and then Alice.
|
||||
const amountPaid = int64(5000)
|
||||
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Bob,
|
||||
aliceFundPoint, int64(0), amountPaid)
|
||||
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Alice,
|
||||
aliceFundPoint, amountPaid, int64(0))
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, 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) {
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
ctxb := context.Background()
|
||||
timeout := time.Duration(time.Second * 15)
|
||||
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, timeout)
|
||||
chanPointAlice := openChannelAndAssert(ctxt, t, net, net.Alice,
|
||||
net.Bob, chanAmt, 0, false)
|
||||
networkChans = append(networkChans, chanPointAlice)
|
||||
|
||||
txidHash, err := getChanPointFundingTxid(chanPointAlice)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
aliceChanTXID, err := chainhash.NewHash(txidHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create sha hash: %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)
|
||||
}
|
||||
if err := net.ConnectNodes(ctxb, carol, net.Bob); err != nil {
|
||||
t.Fatalf("unable to connect carol to alice: %v", err)
|
||||
}
|
||||
err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, net.Bob)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send coins to bob: %v", err)
|
||||
}
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
chanPointBob := openChannelAndAssert(ctxt, t, net, net.Bob,
|
||||
carol, chanAmt, 0, false)
|
||||
networkChans = append(networkChans, chanPointBob)
|
||||
txidHash, err = getChanPointFundingTxid(chanPointBob)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
bobChanTXID, err := chainhash.NewHash(txidHash)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create sha hash: %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 {
|
||||
txidHash, err := getChanPointFundingTxid(chanPoint)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get txid: %v", err)
|
||||
}
|
||||
txid, e := chainhash.NewHash(txidHash)
|
||||
if e != nil {
|
||||
t.Fatalf("unable to create sha hash: %v", e)
|
||||
}
|
||||
point := wire.OutPoint{
|
||||
Hash: *txid,
|
||||
Index: chanPoint.OutputIndex,
|
||||
}
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
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 Carol.
|
||||
// We set FinalCltvDelta to 144 since by default QueryRoutes returns
|
||||
// the last hop with a final cltv delta of 9 where as the default in
|
||||
// htlcswitch is 144.
|
||||
const paymentAmt = 1000
|
||||
routesReq := &lnrpc.QueryRoutesRequest{
|
||||
PubKey: carol.PubKeyStr,
|
||||
Amt: paymentAmt,
|
||||
NumRoutes: 1,
|
||||
FinalCltvDelta: 144,
|
||||
}
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
routes, err := net.Alice.QueryRoutes(ctxt, routesReq)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to get route: %v", err)
|
||||
}
|
||||
|
||||
// Create 5 invoices for Carol, which expect a payment from Alice for 1k
|
||||
// satoshis with a different preimage each time.
|
||||
const numPayments = 5
|
||||
rHashes := make([][]byte, numPayments)
|
||||
for i := 0; i < numPayments; i++ {
|
||||
invoice := &lnrpc.Invoice{
|
||||
Value: paymentAmt,
|
||||
}
|
||||
resp, err := carol.AddInvoice(ctxb, invoice)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to add invoice: %v", err)
|
||||
}
|
||||
|
||||
rHashes[i] = resp.RHash
|
||||
}
|
||||
|
||||
// We'll wait for all parties to recognize the new channels within the
|
||||
// network.
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
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, timeout)
|
||||
alicePayStream, err := net.Alice.SendToRoute(ctxt)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create payment stream for alice: %v", err)
|
||||
}
|
||||
|
||||
for _, rHash := range rHashes {
|
||||
sendReq := &lnrpc.SendToRouteRequest{
|
||||
PaymentHash: rHash,
|
||||
Routes: routes.Routes,
|
||||
}
|
||||
err := alicePayStream.Send(sendReq)
|
||||
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send payment: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
for range rHashes {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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, ctxb, "Bob(local) => Carol(remote)", carol,
|
||||
bobFundPoint, int64(0), amountPaid)
|
||||
assertAmountPaid(t, ctxb, "Bob(local) => Carol(remote)", net.Bob,
|
||||
bobFundPoint, amountPaid, int64(0))
|
||||
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Bob,
|
||||
aliceFundPoint, int64(0), amountPaid+(baseFee*numPayments))
|
||||
assertAmountPaid(t, ctxb, "Alice(local) => Bob(remote)", net.Alice,
|
||||
aliceFundPoint, amountPaid+(baseFee*numPayments), int64(0))
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, carol, chanPointBob, false)
|
||||
|
||||
// Finally, shutdown the nodes we created for the duration of the tests,
|
||||
// only leaving the two seed nodes (Alice and Bob) within our test
|
||||
// network.
|
||||
if err := net.ShutdownNode(carol); err != nil {
|
||||
t.Fatalf("unable to shutdown carol: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// testSendToRouteErrorPropagation tests propagation of errors that occur
|
||||
// while processing a multi-hop payment through an unknown route.
|
||||
func testSendToRouteErrorPropagation(net *lntest.NetworkHarness, t *harnessTest) {
|
||||
const chanAmt = btcutil.Amount(100000)
|
||||
ctxb := context.Background()
|
||||
timeout := time.Duration(time.Second * 5)
|
||||
|
||||
// Open a channel with 100k satoshis between Alice and Bob with Alice
|
||||
// being the sole funder of the channel.
|
||||
ctxt, _ := context.WithTimeout(ctxb, timeout)
|
||||
chanPointAlice := openChannelAndAssert(ctxt, t, net, net.Alice,
|
||||
net.Bob, chanAmt, 0, false)
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
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)
|
||||
}
|
||||
err = net.SendCoins(ctxb, 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)
|
||||
}
|
||||
err = net.SendCoins(ctxb, btcutil.SatoshiPerBitcoin, charlie)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to send coins to charlie: %v", err)
|
||||
}
|
||||
|
||||
if err := net.ConnectNodes(ctxb, carol, charlie); err != nil {
|
||||
t.Fatalf("unable to connect carol to alice: %v", err)
|
||||
}
|
||||
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
chanPointCarol := openChannelAndAssert(ctxt, t, net, carol,
|
||||
charlie, chanAmt, 0, false)
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
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),
|
||||
NumRoutes: 1,
|
||||
}
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
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,
|
||||
}
|
||||
resp, err := net.Bob.AddInvoice(ctxb, 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.
|
||||
alicePayStream, err := net.Alice.SendToRoute(ctxb)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to create payment stream for alice: %v", err)
|
||||
}
|
||||
|
||||
sendReq := &lnrpc.SendToRouteRequest{
|
||||
PaymentHash: rHash,
|
||||
Routes: fakeRoute.Routes,
|
||||
}
|
||||
|
||||
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, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, net.Alice, chanPointAlice, false)
|
||||
ctxt, _ = context.WithTimeout(ctxb, timeout)
|
||||
closeChannelAndAssert(ctxt, t, net, carol, chanPointCarol, false)
|
||||
|
||||
// Finally, shutdown the nodes we created to make fake route,
|
||||
// only leaving the two seed nodes (Alice and Bob) within our test
|
||||
// network.
|
||||
if err := net.ShutdownNode(carol); err != nil {
|
||||
t.Fatalf("unable to shutdown carol: %v", err)
|
||||
}
|
||||
|
||||
if err := net.ShutdownNode(charlie); err != nil {
|
||||
t.Fatalf("unable to shutdown charlie: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -9290,6 +9759,18 @@ var testsCases = []*testCase{
|
||||
name: "multi-hop payments",
|
||||
test: testMultiHopPayments,
|
||||
},
|
||||
{
|
||||
name: "single-hop send to route",
|
||||
test: testSingleHopSendToRoute,
|
||||
},
|
||||
{
|
||||
name: "multi-hop send to route",
|
||||
test: testMultiHopSendToRoute,
|
||||
},
|
||||
{
|
||||
name: "send to route error propagation",
|
||||
test: testSendToRouteErrorPropagation,
|
||||
},
|
||||
{
|
||||
name: "private channels",
|
||||
test: testPrivateChannels,
|
||||
|
Loading…
Reference in New Issue
Block a user