@@ -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,161 @@ 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
+ // testQueryRoutesRouteHints tests that QueryRoutes successfully
1451
+ // finds a route through a private channel when provided with route hints.
1452
+ func testQueryRoutesRouteHints (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
+ queryReq := & lnrpc.QueryRoutesRequest {
1467
+ PubKey : carol .PubKeyStr ,
1468
+ Amt : 10_000 ,
1469
+ FinalCltvDelta : int32 (routing .MinCLTVDelta ),
1470
+ // RouteHints omitted initially.
1471
+ UseMissionControl : true , // Use MC for realistic pathfinding
1472
+ }
1473
+
1474
+ // Query routes without hints - should fail (find no routes).
1475
+ // Call the client directly to check the error without halting the test.
1476
+ _ , err := alice .RPC .LN .QueryRoutes (ht .Context (), queryReq )
1477
+ require .Error (ht , err ,
1478
+ "QueryRoutes without hints should return an error" )
1479
+
1480
+ // Now add the hints and query again - should succeed.
1481
+ // Use the helper function here as we expect success.
1482
+ queryReq .RouteHints = hints
1483
+ routes := alice .RPC .QueryRoutes (queryReq )
1484
+
1485
+ // Assert that a route was found and it goes Alice -> Bob -> Carol.
1486
+ require .NotEmpty (ht , routes .Routes ,
1487
+ "QueryRoutes with hints should find a route" )
1488
+ require .Len (ht , routes .Routes [0 ].Hops , 2 , "Route should have 2 hops" )
1489
+
1490
+ hop1 := routes .Routes [0 ].Hops [0 ]
1491
+ hop2 := routes .Routes [0 ].Hops [1 ]
1492
+
1493
+ require .Equal (ht , bob .PubKeyStr , hop1 .PubKey , "Hop 1 should be Bob" )
1494
+ require .Equal (ht , carol .PubKeyStr , hop2 .PubKey , "Hop 2 should be Carol" )
1495
+ }
1496
+
1497
+ // createRouteHintFromChannel takes a source node and a channel object and
1498
+ // constructs a RouteHint slice containing a single hop hint for that channel.
1499
+ func createRouteHintFromChannel (sourceNode * node.HarnessNode ,
1500
+ channel * lnrpc.Channel ) []* lnrpc.RouteHint {
1501
+
1502
+ return []* lnrpc.RouteHint {{HopHints : []* lnrpc.HopHint {
1503
+ {
1504
+ NodeId : sourceNode .PubKeyStr ,
1505
+ ChanId : channel .ChanId ,
1506
+ FeeBaseMsat : 1000 ,
1507
+ FeeProportionalMillionths : 1 ,
1508
+ CltvExpiryDelta : routing .MinCLTVDelta ,
1509
+ }}},
1510
+ }
1511
+ }
1512
+
1513
+ // setupThreeNodeNetwork sets up a standard three-node network topology:
1514
+ // Alice -> Bob -> Carol. It creates the nodes, connects them, funds Alice and
1515
+ // Bob, opens a channel between Alice and Bob, and optionally opens a private
1516
+ // channel between Bob and Carol. It returns the nodes, channel points, and a
1517
+ // cleanup function.
1518
+ func setupThreeNodeNetwork (ht * lntest.HarnessTest , chanAmt btcutil.Amount ,
1519
+ bobCarolPrivate bool ) (* node.HarnessNode , * node.HarnessNode ,
1520
+ * node.HarnessNode , * lnrpc.ChannelPoint , * lnrpc.ChannelPoint , func ()) {
1521
+
1522
+ // Create nodes.
1523
+ alice := ht .NewNode ("Alice" , nil )
1524
+ bob := ht .NewNode ("Bob" , nil )
1525
+ carol := ht .NewNode ("Carol" , nil )
1526
+
1527
+ // Connect nodes.
1528
+ ht .ConnectNodes (alice , bob )
1529
+ ht .ConnectNodes (bob , carol )
1530
+
1531
+ // Fund nodes.
1532
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , alice )
1533
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , bob )
1534
+
1535
+ // Open Alice -> Bob channel (public).
1536
+ aliceBobChanPoint := ht .OpenChannel (
1537
+ alice , bob , lntest.OpenChannelParams {Amt : chanAmt },
1538
+ )
1539
+
1540
+ // Open Bob -> Carol channel (potentially private).
1541
+ bobCarolParams := lntest.OpenChannelParams {
1542
+ Amt : chanAmt ,
1543
+ Private : bobCarolPrivate ,
1544
+ }
1545
+ bobCarolChanPoint := ht .OpenChannel (bob , carol , bobCarolParams )
1546
+
1547
+ // Define cleanup function.
1548
+ cleanup := func () {
1549
+ ht .CloseChannel (alice , aliceBobChanPoint )
1550
+ ht .CloseChannel (bob , bobCarolChanPoint )
1551
+ ht .Shutdown (alice )
1552
+ ht .Shutdown (bob )
1553
+ ht .Shutdown (carol )
1554
+ }
1555
+
1556
+ return alice , bob , carol , aliceBobChanPoint , bobCarolChanPoint , cleanup
1557
+ }
0 commit comments