@@ -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,214 @@ 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
+ alice := ht .NewNode ("Alice" , nil )
1406
+ defer ht .Shutdown (alice )
1407
+ bob := ht .NewNode ("Bob" , nil )
1408
+ defer ht .Shutdown (bob )
1409
+ // Ensure Carol accepts keysend payments.
1410
+ carol := ht .NewNode ("Carol" , []string {"--accept-keysend" })
1411
+ defer ht .Shutdown (carol )
1412
+
1413
+ ht .ConnectNodes (alice , bob )
1414
+ ht .ConnectNodes (bob , carol )
1415
+
1416
+ // Fund Alice and Bob.
1417
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , alice )
1418
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , bob )
1419
+
1420
+ // Open channels: Alice -> Bob (public), Bob -> Carol (private).
1421
+ const chanAmt = btcutil .Amount (1_000_000 )
1422
+ aliceBobChanPoint := ht .OpenChannel (
1423
+ alice , bob , lntest.OpenChannelParams {Amt : chanAmt },
1424
+ )
1425
+ defer ht .CloseChannel (alice , aliceBobChanPoint )
1426
+ bobCarolChanPoint := ht .OpenChannel (bob , carol ,
1427
+ lntest.OpenChannelParams {Amt : chanAmt , Private : true },
1428
+ )
1429
+ defer ht .CloseChannel (bob , bobCarolChanPoint )
1430
+
1431
+ // Manually create route hints for the private Bob -> Carol channel.
1432
+ bobChan := ht .GetChannelByChanPoint (bob , bobCarolChanPoint )
1433
+ require .NotNil (ht , bobChan , "Bob should have channel with Carol" )
1434
+
1435
+ hints := []* lnrpc.RouteHint {{HopHints : []* lnrpc.HopHint {
1436
+ {
1437
+ NodeId : bob .PubKeyStr ,
1438
+ ChanId : bobChan .ChanId ,
1439
+ FeeBaseMsat : 1000 ,
1440
+ FeeProportionalMillionths : 1 ,
1441
+ CltvExpiryDelta : routing .MinCLTVDelta ,
1442
+ }}},
1443
+ }
1444
+
1445
+ // Prepare Keysend payment details.
1446
+ preimage := ht .RandomPreimage ()
1447
+ payHash := preimage .Hash ()
1448
+ destBytes , err := hex .DecodeString (carol .PubKeyStr )
1449
+ require .NoError (ht , err )
1450
+
1451
+ sendReq := & routerrpc.SendPaymentRequest {
1452
+ Dest : destBytes ,
1453
+ Amt : 10_000 ,
1454
+ PaymentHash : payHash [:],
1455
+ FinalCltvDelta : int32 (routing .MinCLTVDelta ),
1456
+ DestCustomRecords : map [uint64 ][]byte {
1457
+ record .KeySendType : preimage [:],
1458
+ },
1459
+ RouteHints : hints ,
1460
+ FeeLimitSat : int64 (chanAmt ),
1461
+ TimeoutSeconds : 60 ,
1462
+ }
1463
+
1464
+ // Send keysend payment and assert success.
1465
+ ht .AssertPaymentStatusFromStream (
1466
+ alice .RPC .SendPayment (sendReq ),
1467
+ lnrpc .Payment_SUCCEEDED ,
1468
+ )
1469
+ }
1470
+
1471
+ // testSendPaymentRouteHintsAMP tests sending an AMP payment using
1472
+ // manually provided route hints derived from a private channel.
1473
+ func testSendPaymentRouteHintsAMP (ht * lntest.HarnessTest ) {
1474
+ // Setup a three-node network: Alice -> Bob -> Carol.
1475
+ alice := ht .NewNode ("Alice" , nil )
1476
+ defer ht .Shutdown (alice )
1477
+ bob := ht .NewNode ("Bob" , nil )
1478
+ defer ht .Shutdown (bob )
1479
+ carol := ht .NewNode ("Carol" , nil )
1480
+ defer ht .Shutdown (carol )
1481
+
1482
+ ht .ConnectNodes (alice , bob )
1483
+ ht .ConnectNodes (bob , carol )
1484
+
1485
+ // Fund Alice and Bob.
1486
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , alice )
1487
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , bob )
1488
+
1489
+ // Open channels: Alice -> Bob (public), Bob -> Carol (private).
1490
+ const chanAmt = btcutil .Amount (1_000_000 )
1491
+ aliceBobChanPoint := ht .OpenChannel (
1492
+ alice , bob , lntest.OpenChannelParams {Amt : chanAmt },
1493
+ )
1494
+ defer ht .CloseChannel (alice , aliceBobChanPoint )
1495
+ bobCarolChanPoint := ht .OpenChannel (bob , carol ,
1496
+ lntest.OpenChannelParams {Amt : chanAmt , Private : true },
1497
+ )
1498
+ defer ht .CloseChannel (bob , bobCarolChanPoint )
1499
+
1500
+ // Manually create route hints for the private Bob -> Carol channel.
1501
+ bobChan := ht .GetChannelByChanPoint (bob , bobCarolChanPoint )
1502
+ require .NotNil (ht , bobChan , "Bob should have channel with Carol" )
1503
+
1504
+ hints := []* lnrpc.RouteHint {{HopHints : []* lnrpc.HopHint {
1505
+ {
1506
+ NodeId : bob .PubKeyStr ,
1507
+ ChanId : bobChan .ChanId ,
1508
+ FeeBaseMsat : 1000 ,
1509
+ FeeProportionalMillionths : 1 ,
1510
+ CltvExpiryDelta : routing .MinCLTVDelta ,
1511
+ }}},
1512
+ }
1513
+
1514
+ // Carol creates an AMP invoice
1515
+ const paymentAmtSat = 10_000
1516
+ carolDestBytes , err := hex .DecodeString (carol .PubKeyStr )
1517
+ require .NoError (ht , err )
1518
+
1519
+ invoiceTemplate := & lnrpc.Invoice {
1520
+ Value : paymentAmtSat ,
1521
+ Private : true ,
1522
+ IsAmp : true ,
1523
+ }
1524
+ addInvoiceResp := carol .RPC .AddInvoice (invoiceTemplate )
1525
+
1526
+ // We need to decode to get CltvExpiry.
1527
+ decodedPayReq := alice .RPC .DecodePayReq (addInvoiceResp .PaymentRequest )
1528
+
1529
+ sendReq := & routerrpc.SendPaymentRequest {
1530
+ Dest : carolDestBytes ,
1531
+ Amt : paymentAmtSat ,
1532
+ // PaymentHash is omitted for AMP
1533
+ PaymentAddr : addInvoiceResp .PaymentAddr ,
1534
+ FinalCltvDelta : int32 (decodedPayReq .CltvExpiry ),
1535
+ Amp : true ,
1536
+ RouteHints : hints ,
1537
+ FeeLimitSat : int64 (chanAmt ),
1538
+ TimeoutSeconds : 60 ,
1539
+ }
1540
+
1541
+ // Send AMP payment and assert success
1542
+ ht .AssertPaymentStatusFromStream (
1543
+ alice .RPC .SendPayment (sendReq ),
1544
+ lnrpc .Payment_SUCCEEDED ,
1545
+ )
1546
+ }
1547
+
1548
+ // testQueryRoutesRouteHints tests that QueryRoutes successfully
1549
+ // finds a route through a private channel when provided with route hints.
1550
+ func testQueryRoutesRouteHints (ht * lntest.HarnessTest ) {
1551
+ // Setup a three-node network: Alice -> Bob -> Carol.
1552
+ alice := ht .NewNode ("Alice" , nil )
1553
+ defer ht .Shutdown (alice )
1554
+ bob := ht .NewNode ("Bob" , nil )
1555
+ defer ht .Shutdown (bob )
1556
+ carol := ht .NewNode ("Carol" , nil )
1557
+ defer ht .Shutdown (carol )
1558
+
1559
+ ht .ConnectNodes (alice , bob )
1560
+ ht .ConnectNodes (bob , carol )
1561
+
1562
+ // Fund Alice and Bob.
1563
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , alice )
1564
+ ht .FundCoins (btcutil .SatoshiPerBitcoin , bob )
1565
+
1566
+ // Open channels: Alice -> Bob (public), Bob -> Carol (private).
1567
+ const chanAmt = btcutil .Amount (1_000_000 )
1568
+ aliceBobChanPoint := ht .OpenChannel (
1569
+ alice , bob , lntest.OpenChannelParams {Amt : chanAmt },
1570
+ )
1571
+ defer ht .CloseChannel (alice , aliceBobChanPoint )
1572
+ bobCarolChanPoint := ht .OpenChannel (bob , carol ,
1573
+ lntest.OpenChannelParams {Amt : chanAmt , Private : true },
1574
+ )
1575
+ defer ht .CloseChannel (bob , bobCarolChanPoint )
1576
+
1577
+ // Manually create route hints for the private Bob -> Carol channel.
1578
+ bobChan := ht .GetChannelByChanPoint (bob , bobCarolChanPoint )
1579
+ require .NotNil (ht , bobChan , "Bob should have channel with Carol" )
1580
+
1581
+ hints := []* lnrpc.RouteHint {{HopHints : []* lnrpc.HopHint {
1582
+ {
1583
+ NodeId : bob .PubKeyStr ,
1584
+ ChanId : bobChan .ChanId ,
1585
+ FeeBaseMsat : 1000 ,
1586
+ FeeProportionalMillionths : 1 ,
1587
+ CltvExpiryDelta : routing .MinCLTVDelta ,
1588
+ }}},
1589
+ }
1590
+
1591
+ queryReq := & lnrpc.QueryRoutesRequest {
1592
+ PubKey : carol .PubKeyStr ,
1593
+ Amt : 10_000 ,
1594
+ FinalCltvDelta : int32 (routing .MinCLTVDelta ),
1595
+ RouteHints : hints ,
1596
+ UseMissionControl : true , // Use MC for realistic pathfinding
1597
+ }
1598
+
1599
+ routes := alice .RPC .QueryRoutes (queryReq )
1600
+
1601
+ // Assert that a route was found and it goes Alice -> Bob -> Carol.
1602
+ require .NotEmpty (ht , routes .Routes , "QueryRoutes should find a route" )
1603
+ require .Len (ht , routes .Routes [0 ].Hops , 2 , "Route should have 2 hops" )
1604
+
1605
+ hop1 := routes .Routes [0 ].Hops [0 ]
1606
+ hop2 := routes .Routes [0 ].Hops [1 ]
1607
+
1608
+ require .Equal (ht , bob .PubKeyStr , hop1 .PubKey , "Hop 1 should be Bob" )
1609
+ require .Equal (ht , carol .PubKeyStr , hop2 .PubKey , "Hop 2 should be Carol" )
1610
+ }
0 commit comments