@@ -22,6 +22,7 @@ import (
22
22
"github.com/lightningnetwork/lnd/lntypes"
23
23
"github.com/lightningnetwork/lnd/lnwire"
24
24
"github.com/lightningnetwork/lnd/record"
25
+ "github.com/lightningnetwork/lnd/routing"
25
26
"github.com/stretchr/testify/require"
26
27
)
27
28
@@ -1396,3 +1397,218 @@ func testSendPaymentKeysendMPPFail(ht *lntest.HarnessTest) {
1396
1397
_ , err = ht .ReceivePaymentUpdate (client )
1397
1398
require .Error (ht , err )
1398
1399
}
1400
+
1401
+ // testSendPaymentRouteHintsKeysend tests sending a keysend payment using
1402
+ // manually provided route hints derived from a private channel.
1403
+ func testSendPaymentRouteHintsKeysend (ht * lntest.HarnessTest ) {
1404
+ // Setup a three-node network: Alice -> Bob -> Carol.
1405
+ // The Bob->Carol channel is private.
1406
+ const chanAmt = btcutil .Amount (1_000_000 )
1407
+ alice , bob , carol , _ , bobCarolCP , cleanup := setupThreeNodeNetwork (
1408
+ ht , chanAmt , true ,
1409
+ )
1410
+ defer cleanup ()
1411
+
1412
+ // Manually create route hints for the private Bob -> Carol channel.
1413
+ bobChan := ht .GetChannelByChanPoint (bob , bobCarolCP )
1414
+ require .NotNil (ht , bobChan , "Bob should have channel with Carol" )
1415
+ hints := createRouteHintFromChannel (bob , bobChan )
1416
+
1417
+ // Prepare Keysend payment details.
1418
+ preimage := ht .RandomPreimage ()
1419
+ payHash := preimage .Hash ()
1420
+ destBytes , err := hex .DecodeString (carol .PubKeyStr )
1421
+ require .NoError (ht , err )
1422
+
1423
+ sendReq := & routerrpc.SendPaymentRequest {
1424
+ Dest : destBytes ,
1425
+ Amt : 10_000 ,
1426
+ PaymentHash : payHash [:],
1427
+ FinalCltvDelta : int32 (routing .MinCLTVDelta ),
1428
+ DestCustomRecords : map [uint64 ][]byte {
1429
+ record .KeySendType : preimage [:],
1430
+ },
1431
+ // RouteHints omitted initially.
1432
+ FeeLimitSat : int64 (chanAmt ),
1433
+ TimeoutSeconds : 30 ,
1434
+ }
1435
+
1436
+ // Attempt keysend payment without hints - should fail.
1437
+ ht .AssertPaymentStatusFromStream (
1438
+ alice .RPC .SendPayment (sendReq ),
1439
+ lnrpc .Payment_FAILED ,
1440
+ )
1441
+
1442
+ // Now, add the hints and try again - should succeed.
1443
+ sendReq .RouteHints = hints
1444
+ ht .AssertPaymentStatusFromStream (
1445
+ alice .RPC .SendPayment (sendReq ),
1446
+ lnrpc .Payment_SUCCEEDED ,
1447
+ )
1448
+ }
1449
+
1450
+ // testSendPaymentRouteHintsAMP tests sending an AMP payment using
1451
+ // manually provided route hints derived from a private channel.
1452
+ func testSendPaymentRouteHintsAMP (ht * lntest.HarnessTest ) {
1453
+ // Setup a three-node network: Alice -> Bob -> Carol.
1454
+ // The Bob->Carol channel is private.
1455
+ const chanAmt = btcutil .Amount (1_000_000 )
1456
+ alice , bob , carol , _ , bobCarolCP , cleanup := setupThreeNodeNetwork (
1457
+ ht , chanAmt , true ,
1458
+ )
1459
+ defer cleanup ()
1460
+
1461
+ // Manually create route hints for the private Bob -> Carol channel.
1462
+ bobChan := ht .GetChannelByChanPoint (bob , bobCarolCP )
1463
+ require .NotNil (ht , bobChan , "Bob should have channel with Carol" )
1464
+ hints := createRouteHintFromChannel (bob , bobChan )
1465
+
1466
+ // Carol creates an AMP invoice
1467
+ const paymentAmtSat = 10_000
1468
+ carolDestBytes , err := hex .DecodeString (carol .PubKeyStr )
1469
+ require .NoError (ht , err )
1470
+
1471
+ invoiceTemplate := & lnrpc.Invoice {
1472
+ Value : paymentAmtSat ,
1473
+ Private : true ,
1474
+ IsAmp : true ,
1475
+ }
1476
+ addInvoiceResp := carol .RPC .AddInvoice (invoiceTemplate )
1477
+
1478
+ // We need to decode to get CltvExpiry.
1479
+ decodedPayReq := alice .RPC .DecodePayReq (addInvoiceResp .PaymentRequest )
1480
+
1481
+ sendReq := & routerrpc.SendPaymentRequest {
1482
+ Dest : carolDestBytes ,
1483
+ Amt : paymentAmtSat ,
1484
+ // PaymentHash is omitted for AMP
1485
+ PaymentAddr : addInvoiceResp .PaymentAddr ,
1486
+ FinalCltvDelta : int32 (decodedPayReq .CltvExpiry ),
1487
+ Amp : true ,
1488
+ // RouteHints omitted initially.
1489
+ FeeLimitSat : int64 (chanAmt ),
1490
+ TimeoutSeconds : 30 ,
1491
+ }
1492
+
1493
+ // Attempt AMP payment without hints - should fail.
1494
+ ht .AssertPaymentStatusFromStream (
1495
+ alice .RPC .SendPayment (sendReq ),
1496
+ lnrpc .Payment_FAILED ,
1497
+ )
1498
+
1499
+ // Now, add the hints and try again - should succeed.
1500
+ sendReq .RouteHints = hints
1501
+ ht .AssertPaymentStatusFromStream (
1502
+ alice .RPC .SendPayment (sendReq ),
1503
+ lnrpc .Payment_SUCCEEDED ,
1504
+ )
1505
+ }
1506
+
1507
+ // testQueryRoutesRouteHints tests that QueryRoutes successfully
1508
+ // finds a route through a private channel when provided with route hints.
1509
+ func testQueryRoutesRouteHints (ht * lntest.HarnessTest ) {
1510
+ // Setup a three-node network: Alice -> Bob -> Carol.
1511
+ // The Bob->Carol channel is private.
1512
+ const chanAmt = btcutil .Amount (1_000_000 )
1513
+ alice , bob , carol , _ , bobCarolCP , cleanup := setupThreeNodeNetwork (
1514
+ ht , chanAmt , true ,
1515
+ )
1516
+ defer cleanup ()
1517
+
1518
+ // Manually create route hints for the private Bob -> Carol channel.
1519
+ bobChan := ht .GetChannelByChanPoint (bob , bobCarolCP )
1520
+ require .NotNil (ht , bobChan , "Bob should have channel with Carol" )
1521
+ hints := createRouteHintFromChannel (bob , bobChan )
1522
+
1523
+ queryReq := & lnrpc.QueryRoutesRequest {
1524
+ PubKey : carol .PubKeyStr ,
1525
+ Amt : 10_000 ,
1526
+ FinalCltvDelta : int32 (routing .MinCLTVDelta ),
1527
+ // RouteHints omitted initially.
1528
+ UseMissionControl : true , // Use MC for realistic pathfinding
1529
+ }
1530
+
1531
+ // Query routes without hints - should fail (find no routes).
1532
+ // Call the client directly to check the error without halting the test.
1533
+ _ , err := alice .RPC .LN .QueryRoutes (ht .Context (), queryReq )
1534
+ require .Error (ht , err ,
1535
+ "QueryRoutes without hints should return an error" )
1536
+
1537
+ // Now add the hints and query again - should succeed.
1538
+ // Use the helper function here as we expect success.
1539
+ queryReq .RouteHints = hints
1540
+ routes := alice .RPC .QueryRoutes (queryReq )
1541
+
1542
+ // Assert that a route was found and it goes Alice -> Bob -> Carol.
1543
+ require .NotEmpty (ht , routes .Routes ,
1544
+ "QueryRoutes with hints should find a route" )
1545
+ require .Len (ht , routes .Routes [0 ].Hops , 2 , "Route should have 2 hops" )
1546
+
1547
+ hop1 := routes .Routes [0 ].Hops [0 ]
1548
+ hop2 := routes .Routes [0 ].Hops [1 ]
1549
+
1550
+ require .Equal (ht , bob .PubKeyStr , hop1 .PubKey , "Hop 1 should be Bob" )
1551
+ require .Equal (ht , carol .PubKeyStr , hop2 .PubKey , "Hop 2 should be Carol" )
1552
+ }
1553
+
1554
+ // createRouteHintFromChannel takes a source node and a channel object and
1555
+ // constructs a RouteHint slice containing a single hop hint for that channel.
1556
+ func createRouteHintFromChannel (sourceNode * node.HarnessNode ,
1557
+ channel * lnrpc.Channel ) []* lnrpc.RouteHint {
1558
+
1559
+ return []* lnrpc.RouteHint {{HopHints : []* lnrpc.HopHint {
1560
+ {
1561
+ NodeId : sourceNode .PubKeyStr ,
1562
+ ChanId : channel .ChanId ,
1563
+ FeeBaseMsat : 1000 ,
1564
+ FeeProportionalMillionths : 1 ,
1565
+ CltvExpiryDelta : routing .MinCLTVDelta ,
1566
+ }}},
1567
+ }
1568
+ }
1569
+
1570
+ // setupThreeNodeNetwork sets up a standard three-node network topology:
1571
+ // Alice -> Bob -> Carol. It creates the nodes, connects them, funds Alice and
1572
+ // Bob, opens a channel between Alice and Bob, and optionally opens a private
1573
+ // channel between Bob and Carol. It returns the nodes, channel points, and a
1574
+ // cleanup function.
1575
+ func setupThreeNodeNetwork (ht * lntest.HarnessTest , chanAmt btcutil.Amount ,
1576
+ bobCarolPrivate bool ) (* node.HarnessNode , * node.HarnessNode ,
1577
+ * node.HarnessNode , * lnrpc.ChannelPoint , * lnrpc.ChannelPoint , func ()) {
1578
+
1579
+ // Create nodes.
1580
+ alice := ht .NewNode ("Alice" , nil )
1581
+ bob := ht .NewNode ("Bob" , nil )
1582
+ carol := ht .NewNode ("Carol" , nil )
1583
+
1584
+ // Connect nodes.
1585
+ ht .ConnectNodes (alice , bob )
1586
+ ht .ConnectNodes (bob , carol )
1587
+
1588
+ // Fund nodes.
1589
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , alice )
1590
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , bob )
1591
+
1592
+ // Open Alice -> Bob channel (public).
1593
+ aliceBobChanPoint := ht .OpenChannel (
1594
+ alice , bob , lntest.OpenChannelParams {Amt : chanAmt },
1595
+ )
1596
+
1597
+ // Open Bob -> Carol channel (potentially private).
1598
+ bobCarolParams := lntest.OpenChannelParams {
1599
+ Amt : chanAmt ,
1600
+ Private : bobCarolPrivate ,
1601
+ }
1602
+ bobCarolChanPoint := ht .OpenChannel (bob , carol , bobCarolParams )
1603
+
1604
+ // Define cleanup function.
1605
+ cleanup := func () {
1606
+ ht .CloseChannel (alice , aliceBobChanPoint )
1607
+ ht .CloseChannel (bob , bobCarolChanPoint )
1608
+ ht .Shutdown (alice )
1609
+ ht .Shutdown (bob )
1610
+ ht .Shutdown (carol )
1611
+ }
1612
+
1613
+ return alice , bob , carol , aliceBobChanPoint , bobCarolChanPoint , cleanup
1614
+ }
0 commit comments