Skip to content

Commit fc906f2

Browse files
authored
Merge pull request #9127 from MPins/issue-8993
Add the option on path creator to specify the incoming channel on blinded path
2 parents aec16ee + 6fb2d47 commit fc906f2

File tree

15 files changed

+1693
-1196
lines changed

15 files changed

+1693
-1196
lines changed

cmd/commands/cmd_invoice.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/hex"
55
"fmt"
66
"strconv"
7+
"strings"
78

89
"github.com/lightningnetwork/lnd/lnrpc"
910
"github.com/urfave/cli"
@@ -116,6 +117,12 @@ var AddInvoiceCommand = cli.Command{
116117
"use on a blinded path. The flag may be " +
117118
"specified multiple times.",
118119
},
120+
cli.StringFlag{
121+
Name: "blinded_path_incoming_channel_list",
122+
Usage: "The chained channels specified via channel " +
123+
"id (separated by commas), starting from a " +
124+
"channel which points to the self node.",
125+
},
119126
},
120127
Action: actionDecorator(addInvoice),
121128
}
@@ -202,7 +209,8 @@ func parseBlindedPathCfg(ctx *cli.Context) (*lnrpc.BlindedPathConfig, error) {
202209
if ctx.IsSet("min_real_blinded_hops") ||
203210
ctx.IsSet("num_blinded_hops") ||
204211
ctx.IsSet("max_blinded_paths") ||
205-
ctx.IsSet("blinded_path_omit_node") {
212+
ctx.IsSet("blinded_path_omit_node") ||
213+
ctx.IsSet("blinded_path_incoming_channel_list") {
206214

207215
return nil, fmt.Errorf("blinded path options are " +
208216
"only used if the `--blind` options is set")
@@ -239,6 +247,21 @@ func parseBlindedPathCfg(ctx *cli.Context) (*lnrpc.BlindedPathConfig, error) {
239247
)
240248
}
241249

250+
if ctx.IsSet("blinded_path_incoming_channel_list") {
251+
channels := strings.Split(
252+
ctx.String("blinded_path_incoming_channel_list"), ",",
253+
)
254+
for _, channelID := range channels {
255+
chanID, err := strconv.ParseUint(channelID, 10, 64)
256+
if err != nil {
257+
return nil, err
258+
}
259+
blindCfg.IncomingChannelList = append(
260+
blindCfg.IncomingChannelList, chanID,
261+
)
262+
}
263+
}
264+
242265
return &blindCfg, nil
243266
}
244267

docs/release-notes/release-notes-0.20.0.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626

2727
## RPC Additions
2828

29+
* The `lncli addinvoice --blind` command now has the option to include a
30+
[chained channels](https://github.com/lightningnetwork/lnd/pull/9127)
31+
incoming list `--blinded_path_incoming_channel_list` which gives users the
32+
control of specifying the channels they prefer to receive the payment on. With
33+
the option to specify multiple channels this control can be extended to
34+
multiple hops leading to the node.
35+
2936
## lncli Additions
3037

3138
* [`lncli sendpayment` and `lncli queryroutes` now support the
@@ -86,3 +93,5 @@
8693
## Tooling and Documentation
8794

8895
# Contributors (Alphabetical Order)
96+
97+
Pins

itest/list_on_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,10 @@ var allTestCases = []*lntest.TestCase{
707707
Name: "bump fee low budget",
708708
TestFunc: testBumpFeeLowBudget,
709709
},
710+
{
711+
Name: "partially specified route blinded invoice",
712+
TestFunc: testPartiallySpecifiedBlindedPath,
713+
},
710714
}
711715

712716
// appendPrefixed is used to add a prefix to each test name in the subtests

itest/lnd_route_blinding_test.go

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1420,3 +1420,179 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
14201420
require.Fail(ht, "timeout waiting for sending payment")
14211421
}
14221422
}
1423+
1424+
// testPartiallySpecifiedBlindedPath tests lnd's ability to:
1425+
// - Assert the error when attempting to create a blinded payment with an
1426+
// invalid partially specified path.
1427+
// - Create a blinded payment path when the blinded path is partially
1428+
// pre-specified.
1429+
func testPartiallySpecifiedBlindedPath(ht *lntest.HarnessTest) {
1430+
// Create a six hop network:
1431+
// Alice -> Bob -> Carol -> Dave -> Eve -> Frank.
1432+
chanAmt := btcutil.Amount(100000)
1433+
cfgs := [][]string{nil, nil, nil, nil, nil, nil}
1434+
chanPoints, nodes := ht.CreateSimpleNetwork(
1435+
cfgs, lntest.OpenChannelParams{Amt: chanAmt},
1436+
)
1437+
1438+
alice, bob, carol, dave, eve := nodes[0], nodes[1], nodes[2], nodes[3],
1439+
nodes[4]
1440+
1441+
chanPointAliceBob, chanPointBobCarol, chanPointCarolDave :=
1442+
chanPoints[0], chanPoints[1], chanPoints[2]
1443+
1444+
// Lookup full channel info so that we have channel ids for our route.
1445+
aliceBobChan := ht.GetChannelByChanPoint(alice, chanPointAliceBob)
1446+
bobCarolChan := ht.GetChannelByChanPoint(bob, chanPointBobCarol)
1447+
carolDaveChan := ht.GetChannelByChanPoint(carol, chanPointCarolDave)
1448+
1449+
// Let carol set no incoming channel restrictions.
1450+
var (
1451+
minNumRealHops uint32 = 1
1452+
numHops uint32 = 1
1453+
)
1454+
invoice := carol.RPC.AddInvoice(&lnrpc.Invoice{
1455+
Memo: "test",
1456+
ValueMsat: 10_000_000,
1457+
IsBlinded: true,
1458+
BlindedPathConfig: &lnrpc.BlindedPathConfig{
1459+
MinNumRealHops: &minNumRealHops,
1460+
NumHops: &numHops,
1461+
},
1462+
})
1463+
1464+
introNodesFound := make([][]byte, 0)
1465+
introNodesExpected := [][]byte{bob.PubKey[:], dave.PubKey[:]}
1466+
1467+
// Assert that it contains two blinded path with only 2 hops each one.
1468+
payReq := carol.RPC.DecodePayReq(invoice.PaymentRequest)
1469+
require.Len(ht, payReq.BlindedPaths, 2)
1470+
path := payReq.BlindedPaths[0].BlindedPath
1471+
require.Len(ht, path.BlindedHops, 2)
1472+
introNodesFound = append(introNodesFound, path.IntroductionNode)
1473+
path = payReq.BlindedPaths[1].BlindedPath
1474+
require.Len(ht, path.BlindedHops, 2)
1475+
introNodesFound = append(introNodesFound, path.IntroductionNode)
1476+
1477+
// Assert the introduction nodes without caring about the routes order.
1478+
require.ElementsMatch(ht, introNodesExpected, introNodesFound)
1479+
1480+
// Let carol choose the wrong incoming channel.
1481+
var (
1482+
incomingChannelList = []uint64{aliceBobChan.ChanId}
1483+
)
1484+
1485+
err := fmt.Sprintf("incoming channel %v is not seen as owned by node",
1486+
aliceBobChan.ChanId)
1487+
1488+
carol.RPC.AddInvoiceAssertErr(
1489+
&lnrpc.Invoice{
1490+
Memo: "test",
1491+
ValueMsat: 10_000_000,
1492+
IsBlinded: true,
1493+
BlindedPathConfig: &lnrpc.BlindedPathConfig{
1494+
MinNumRealHops: &minNumRealHops,
1495+
NumHops: &numHops,
1496+
IncomingChannelList: incomingChannelList,
1497+
},
1498+
},
1499+
err,
1500+
)
1501+
1502+
// Let Carol set the incoming channel list greater than minimum number
1503+
// of real hops.
1504+
incomingChannelList = []uint64{aliceBobChan.ChanId, bobCarolChan.ChanId}
1505+
err = fmt.Sprintf("minimum number of blinded path hops (%d) must be "+
1506+
"greater than or equal to the number of hops specified on "+
1507+
"the chained channels (%d)", minNumRealHops,
1508+
len(incomingChannelList),
1509+
)
1510+
1511+
carol.RPC.AddInvoiceAssertErr(
1512+
&lnrpc.Invoice{
1513+
Memo: "test",
1514+
ValueMsat: 10_000_000,
1515+
IsBlinded: true,
1516+
BlindedPathConfig: &lnrpc.BlindedPathConfig{
1517+
MinNumRealHops: &minNumRealHops,
1518+
NumHops: &numHops,
1519+
IncomingChannelList: incomingChannelList,
1520+
},
1521+
},
1522+
err,
1523+
)
1524+
1525+
// Let Carol choose an incoming channel that points to a node in the
1526+
// omission set.
1527+
incomingChannelList = []uint64{bobCarolChan.ChanId}
1528+
var nodeOmissionList [][]byte
1529+
nodeOmissionList = append(nodeOmissionList, bob.PubKey[:])
1530+
1531+
err = fmt.Sprintf("cannot simultaneously be included in the omission " +
1532+
"set and in the partially specified path",
1533+
)
1534+
1535+
carol.RPC.AddInvoiceAssertErr(
1536+
&lnrpc.Invoice{
1537+
Memo: "test",
1538+
ValueMsat: 10_000_000,
1539+
IsBlinded: true,
1540+
BlindedPathConfig: &lnrpc.BlindedPathConfig{
1541+
MinNumRealHops: &minNumRealHops,
1542+
NumHops: &numHops,
1543+
IncomingChannelList: incomingChannelList,
1544+
NodeOmissionList: nodeOmissionList,
1545+
},
1546+
},
1547+
err,
1548+
)
1549+
1550+
// Let carol restrict bob as incoming channel.
1551+
incomingChannelList = []uint64{bobCarolChan.ChanId}
1552+
1553+
invoice = carol.RPC.AddInvoice(&lnrpc.Invoice{
1554+
Memo: "test",
1555+
ValueMsat: 10_000_000,
1556+
IsBlinded: true,
1557+
BlindedPathConfig: &lnrpc.BlindedPathConfig{
1558+
MinNumRealHops: &minNumRealHops,
1559+
NumHops: &numHops,
1560+
IncomingChannelList: incomingChannelList,
1561+
},
1562+
})
1563+
1564+
// Assert that it contains a single blinded path with only
1565+
// 2 hops, with bob as the introduction node.
1566+
payReq = carol.RPC.DecodePayReq(invoice.PaymentRequest)
1567+
require.Len(ht, payReq.BlindedPaths, 1)
1568+
path = payReq.BlindedPaths[0].BlindedPath
1569+
require.Len(ht, path.BlindedHops, 2)
1570+
require.EqualValues(ht, path.IntroductionNode, bob.PubKey[:])
1571+
1572+
// Now let alice pay the invoice.
1573+
ht.CompletePaymentRequests(alice, []string{invoice.PaymentRequest})
1574+
1575+
// Let carol restrict dave as incoming channel and max Hops as 2
1576+
numHops = 2
1577+
incomingChannelList = []uint64{carolDaveChan.ChanId}
1578+
1579+
invoice = carol.RPC.AddInvoice(&lnrpc.Invoice{
1580+
Memo: "test",
1581+
ValueMsat: 10_000_000,
1582+
IsBlinded: true,
1583+
BlindedPathConfig: &lnrpc.BlindedPathConfig{
1584+
MinNumRealHops: &minNumRealHops,
1585+
NumHops: &numHops,
1586+
IncomingChannelList: incomingChannelList,
1587+
},
1588+
})
1589+
1590+
// Assert that it contains one path with 3 hops, with dave as the
1591+
// introduction node. The path alice -> bob -> carol is discarded
1592+
// because alice is a dead-end.
1593+
payReq = carol.RPC.DecodePayReq(invoice.PaymentRequest)
1594+
require.Len(ht, payReq.BlindedPaths, 1)
1595+
path = payReq.BlindedPaths[0].BlindedPath
1596+
require.Len(ht, path.BlindedHops, 3)
1597+
require.EqualValues(ht, path.IntroductionNode, eve.PubKey[:])
1598+
}

lncfg/routing.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ func (r *Routing) Validate() error {
3434
"number of hops expected to be included in each path")
3535
}
3636

37+
if r.BlindedPaths.MaxNumPaths == 0 {
38+
return fmt.Errorf("blinded max num paths cannot be 0")
39+
}
40+
3741
if r.BlindedPaths.PolicyIncreaseMultiplier < 1 {
3842
return fmt.Errorf("the blinded route policy increase " +
3943
"multiplier must be greater than or equal to 1")

lnrpc/invoicesrpc/invoices.swagger.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,14 @@
534534
"format": "byte"
535535
},
536536
"description": "A list of node IDs of nodes that should not be used in any of our generated\nblinded paths."
537+
},
538+
"incoming_channel_list": {
539+
"type": "array",
540+
"items": {
541+
"type": "string",
542+
"format": "uint64"
543+
},
544+
"description": "The chained channels list specified via channel id (separated by commas),\nstarting from a channel owned by the receiver node."
537545
}
538546
}
539547
},

0 commit comments

Comments
 (0)