@@ -22,6 +22,7 @@ import (
2222 "github.com/lightningnetwork/lnd/lntypes"
2323 "github.com/lightningnetwork/lnd/lnwire"
2424 "github.com/lightningnetwork/lnd/record"
25+ "github.com/lightningnetwork/lnd/routing"
2526 "github.com/stretchr/testify/require"
2627)
2728
@@ -1396,3 +1397,214 @@ func testSendPaymentKeysendMPPFail(ht *lntest.HarnessTest) {
13961397 _ , err = ht .ReceivePaymentUpdate (client )
13971398 require .Error (ht , err )
13981399}
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