Merge pull request #9925 from ellemouton/rbIncomingFollowup

routing: clean-up & fix blinded path incoming chained channel logic
This commit is contained in:
Elle
2025-06-25 16:30:40 +02:00
committed by GitHub
5 changed files with 300 additions and 204 deletions

View File

@@ -37,7 +37,8 @@ circuit. The indices are only available for forwarding events saved after v0.20.
* The `lncli addinvoice --blind` command now has the option to include a
[chained channels](https://github.com/lightningnetwork/lnd/pull/9127)
chained channels [1](https://github.com/lightningnetwork/lnd/pull/9127)
[2](https://github.com/lightningnetwork/lnd/pull/9925)
incoming list `--blinded_path_incoming_channel_list` which gives users the
control of specifying the channels they prefer to receive the payment on. With
the option to specify multiple channels this control can be extended to

View File

@@ -1428,25 +1428,55 @@ func testBlindedPaymentHTLCReForward(ht *lntest.HarnessTest) {
// pre-specified.
func testPartiallySpecifiedBlindedPath(ht *lntest.HarnessTest) {
// Create a six hop network:
// Alice -> Bob -> Carol -> Dave -> Eve -> Frank.
chanAmt := btcutil.Amount(100000)
cfgs := [][]string{nil, nil, nil, nil, nil, nil}
// Alice -> Bob -> Carol -> Dave -> Eve -> Frank.
// Carol will be the node generating invoices containing blinded paths.
chanPoints, nodes := ht.CreateSimpleNetwork(
cfgs, lntest.OpenChannelParams{Amt: chanAmt},
[][]string{nil, nil, nil, nil, nil, nil},
lntest.OpenChannelParams{
Amt: chanAmt,
PushAmt: chanAmt / 2,
},
)
alice, bob, carol, dave, eve := nodes[0], nodes[1], nodes[2], nodes[3],
nodes[4]
chanPointAliceBob, chanPointBobCarol, chanPointCarolDave :=
chanPoints[0], chanPoints[1], chanPoints[2]
var (
alice = nodes[0]
bob = nodes[1]
carol = nodes[2]
dave = nodes[3]
eve = nodes[4]
frank = nodes[5]
)
// Lookup full channel info so that we have channel ids for our route.
aliceBobChan := ht.GetChannelByChanPoint(alice, chanPointAliceBob)
bobCarolChan := ht.GetChannelByChanPoint(bob, chanPointBobCarol)
carolDaveChan := ht.GetChannelByChanPoint(carol, chanPointCarolDave)
aliceBobChan := ht.GetChannelByChanPoint(alice, chanPoints[0])
bobCarolChan := ht.GetChannelByChanPoint(bob, chanPoints[1])
carolDaveChan := ht.GetChannelByChanPoint(carol, chanPoints[2])
// Let carol set no incoming channel restrictions.
// assertPathDetails is a helper function that asserts the details of
// the blinded paths returned in the invoice.
assertPathDetails := func(invoice *lnrpc.AddInvoiceResponse,
expPathLengths int, expIntroNodes ...[]byte) {
payReq := carol.RPC.DecodePayReq(invoice.PaymentRequest)
paths := payReq.BlindedPaths
introNodes := make([][]byte, 0, len(paths))
for _, p := range paths {
require.Len(
ht, p.BlindedPath.BlindedHops, expPathLengths,
)
introNodes = append(
introNodes, p.BlindedPath.IntroductionNode,
)
}
require.ElementsMatch(ht, introNodes, expIntroNodes)
}
// Let carol set no incoming channel restrictions. With a min of 1 hop,
// we expect the following blinded paths to be included:
// 1) Bob->Carol
// 2) Dave->Carol
var (
minNumRealHops uint32 = 1
numHops uint32 = 1
@@ -1460,122 +1490,94 @@ func testPartiallySpecifiedBlindedPath(ht *lntest.HarnessTest) {
NumHops: &numHops,
},
})
assertPathDetails(invoice, 2, bob.PubKey[:], dave.PubKey[:])
introNodesFound := make([][]byte, 0)
introNodesExpected := [][]byte{bob.PubKey[:], dave.PubKey[:]}
// Assert that it contains two blinded path with only 2 hops each one.
payReq := carol.RPC.DecodePayReq(invoice.PaymentRequest)
require.Len(ht, payReq.BlindedPaths, 2)
path := payReq.BlindedPaths[0].BlindedPath
require.Len(ht, path.BlindedHops, 2)
introNodesFound = append(introNodesFound, path.IntroductionNode)
path = payReq.BlindedPaths[1].BlindedPath
require.Len(ht, path.BlindedHops, 2)
introNodesFound = append(introNodesFound, path.IntroductionNode)
// Assert the introduction nodes without caring about the routes order.
require.ElementsMatch(ht, introNodesExpected, introNodesFound)
// Let carol choose the wrong incoming channel.
var (
incomingChannelList = []uint64{aliceBobChan.ChanId}
)
err := fmt.Sprintf("incoming channel %v is not seen as owned by node",
aliceBobChan.ChanId)
// Let carol choose an invalid incoming channel list for the blinded
// path. It is invalid since it does not connect to her node.
// Assert that the expected error is returned.
carol.RPC.AddInvoiceAssertErr(
&lnrpc.Invoice{
Memo: "test",
ValueMsat: 10_000_000,
IsBlinded: true,
BlindedPathConfig: &lnrpc.BlindedPathConfig{
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: incomingChannelList,
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: []uint64{
aliceBobChan.ChanId,
},
},
},
err,
fmt.Sprintf("incoming channel %v is not seen as owned by node",
aliceBobChan.ChanId),
)
// Let Carol set the incoming channel list greater than minimum number
// of real hops.
incomingChannelList = []uint64{aliceBobChan.ChanId, bobCarolChan.ChanId}
err = fmt.Sprintf("minimum number of blinded path hops (%d) must be "+
"greater than or equal to the number of hops specified on "+
"the chained channels (%d)", minNumRealHops,
len(incomingChannelList),
)
// Assert that the expected error is returned.
carol.RPC.AddInvoiceAssertErr(
&lnrpc.Invoice{
Memo: "test",
ValueMsat: 10_000_000,
IsBlinded: true,
BlindedPathConfig: &lnrpc.BlindedPathConfig{
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: incomingChannelList,
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: []uint64{
aliceBobChan.ChanId,
bobCarolChan.ChanId,
},
},
},
err,
"must be greater than or equal to the number of hops "+
"specified on the chained channels",
)
// Let Carol choose an incoming channel that points to a node in the
// omission set.
incomingChannelList = []uint64{bobCarolChan.ChanId}
var nodeOmissionList [][]byte
nodeOmissionList = append(nodeOmissionList, bob.PubKey[:])
err = fmt.Sprintf("cannot simultaneously be included in the omission " +
"set and in the partially specified path",
)
carol.RPC.AddInvoiceAssertErr(
&lnrpc.Invoice{
Memo: "test",
ValueMsat: 10_000_000,
IsBlinded: true,
BlindedPathConfig: &lnrpc.BlindedPathConfig{
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: incomingChannelList,
NodeOmissionList: nodeOmissionList,
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: []uint64{
bobCarolChan.ChanId,
},
NodeOmissionList: [][]byte{
bob.PubKey[:],
},
},
},
err,
"cannot simultaneously be included in the omission set and "+
"in the partially specified path",
)
// Let carol restrict bob as incoming channel.
incomingChannelList = []uint64{bobCarolChan.ChanId}
invoice = carol.RPC.AddInvoice(&lnrpc.Invoice{
Memo: "test",
ValueMsat: 10_000_000,
IsBlinded: true,
BlindedPathConfig: &lnrpc.BlindedPathConfig{
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: incomingChannelList,
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: []uint64{
bobCarolChan.ChanId,
},
},
})
// Assert that it contains a single blinded path with only
// 2 hops, with bob as the introduction node.
payReq = carol.RPC.DecodePayReq(invoice.PaymentRequest)
require.Len(ht, payReq.BlindedPaths, 1)
path = payReq.BlindedPaths[0].BlindedPath
require.Len(ht, path.BlindedHops, 2)
require.EqualValues(ht, path.IntroductionNode, bob.PubKey[:])
assertPathDetails(invoice, 2, bob.PubKey[:])
// Now let alice pay the invoice.
// Check that Alice can pay the invoice.
ht.CompletePaymentRequests(alice, []string{invoice.PaymentRequest})
// Let carol restrict dave as incoming channel and max Hops as 2
// Let carol restrict dave as incoming channel and max hops as 2.
numHops = 2
incomingChannelList = []uint64{carolDaveChan.ChanId}
invoice = carol.RPC.AddInvoice(&lnrpc.Invoice{
Memo: "test",
ValueMsat: 10_000_000,
@@ -1583,16 +1585,15 @@ func testPartiallySpecifiedBlindedPath(ht *lntest.HarnessTest) {
BlindedPathConfig: &lnrpc.BlindedPathConfig{
MinNumRealHops: &minNumRealHops,
NumHops: &numHops,
IncomingChannelList: incomingChannelList,
IncomingChannelList: []uint64{carolDaveChan.ChanId},
},
})
// Assert that it contains one path with 3 hops, with dave as the
// introduction node. The path alice -> bob -> carol is discarded
// because alice is a dead-end.
payReq = carol.RPC.DecodePayReq(invoice.PaymentRequest)
require.Len(ht, payReq.BlindedPaths, 1)
path = payReq.BlindedPaths[0].BlindedPath
require.Len(ht, path.BlindedHops, 3)
require.EqualValues(ht, path.IntroductionNode, eve.PubKey[:])
// Assert that it contains two paths: one 3 hop one starting at Eve and
// a 3 hop one starting at Dave (this one will be padded with a dummy
// hop) in order to keep all the paths equidistant.
assertPathDetails(invoice, 3, eve.PubKey[:], dave.PubKey[:])
// Check that Frank can pay the invoice.
ht.CompletePaymentRequests(frank, []string{invoice.PaymentRequest})
}

View File

@@ -1227,24 +1227,46 @@ func findBlindedPaths(g Graph, target route.Vertex,
}
var (
// The target node is always the last hop in the path.
incomingPath = []blindedHop{{vertex: target}}
whiteListedNodes = map[route.Vertex]bool{target: true}
// The target node is always the last hop in the path, and so
// we add it to the incoming path from the get-go. Any additions
// to the slice should be prepended.
incomingPath = []blindedHop{{
vertex: target,
}}
// supportsRouteBlinding is a list of nodes that we can assume
// support route blinding without needing to rely on the feature
// bits announced in their node announcement. Since we are
// finding a path to the target node, we can assume it supports
// route blinding.
supportsRouteBlinding = map[route.Vertex]bool{
target: true,
}
visited = make(map[route.Vertex]bool)
errChanFound = errors.New("found incoming channel")
nextTarget = target
haveIncomingPath = len(restrictions.incomingChainedChannels) > 0
// errChanFound is an error variable we return from the DB
// iteration call below when we have found the channel we are
// looking for. This lets us exit the iteration early.
errChanFound = errors.New("found incoming channel")
)
for _, chanID := range restrictions.incomingChainedChannels {
// Mark that we have visited this node so that we don't revisit
// it later on when we call "processNodeForBlindedPath".
visited[nextTarget] = true
err := g.ForEachNodeDirectedChannel(nextTarget,
func(channel *graphdb.DirectedChannel) error {
// Not the right channel, continue to the node's
// other channels.
// This is not the right channel, continue to
// the node's other channels.
if channel.ChannelID != chanID {
return nil
}
// We found the channel in question. Prepend it
// to the incoming path.
incomingPath = append([]blindedHop{
{
vertex: channel.OtherNode,
@@ -1259,39 +1281,37 @@ func findBlindedPaths(g Graph, target route.Vertex,
return errChanFound
},
)
if err == nil {
// We expect errChanFound to be returned if the channel in
// question was found.
if !errors.Is(err, errChanFound) && err != nil {
return nil, err
} else if err == nil {
return nil, fmt.Errorf("incoming channel %d is not "+
"seen as owned by node %v", chanID, nextTarget)
}
if !errors.Is(err, errChanFound) {
return nil, err
}
// Check that the user didn't accidentally add a channel that
// is owned by a node in the node omission set
// is owned by a node in the node omission set.
if restrictions.nodeOmissionSet.Contains(nextTarget) {
return nil, fmt.Errorf("node %v cannot simultaneously "+
"be included in the omission set and in the "+
"partially specified path", nextTarget)
}
if whiteListedNodes[nextTarget] {
// Check that we have not already visited the next target node
// since this would mean a circular incoming path.
if visited[nextTarget] {
return nil, fmt.Errorf("a circular route cannot be " +
"specified for the incoming blinded path")
}
whiteListedNodes[nextTarget] = true
supportsRouteBlinding[nextTarget] = true
}
// If the node is not the destination node, then it is required that the
// node advertise the route blinding feature-bit in order for it to be
// chosen as a node on the blinded path.
// We skip checking the target node, as accepting blinded payments
// (via invoice) doesn't imply support for routing them (via node
// announcement).
// We skip checking incomingChainedChannels nodes, as we might not yet
// have an updated node announcement for them.
supportsRouteBlinding := func(node route.Vertex) (bool, error) {
if whiteListedNodes[node] {
// A helper closure which checks if the node in question has advertised
// that it supports route blinding.
nodeSupportsRouteBlinding := func(node route.Vertex) (bool, error) {
if supportsRouteBlinding[node] {
return true, nil
}
@@ -1308,39 +1328,46 @@ func findBlindedPaths(g Graph, target route.Vertex,
// conditions such as: The maxHops number being reached or reaching
// a node that doesn't have any other edges - in that final case, the
// whole path should be ignored.
//
// NOTE: any paths returned will end at the "nextTarget" node meaning
// that if we have a fixed list of incoming chained channels, then this
// fixed list must be appended to any of the returned paths.
paths, _, err := processNodeForBlindedPath(
g, nextTarget, supportsRouteBlinding, visited, restrictions,
g, nextTarget, nodeSupportsRouteBlinding, visited, restrictions,
)
if err != nil {
return nil, err
}
orderedPaths := make([][]blindedHop, len(paths))
// Reverse each path so that the order is correct (from introduction
// node to last hop node) and then append the incoming path (if any was
// specified) to the end of the path.
orderedPaths := make([][]blindedHop, 0, len(paths))
for _, path := range paths {
sort.Slice(path, func(i, j int) bool {
return j < i
})
// When there is no path to add, but incomingChainedChannels can be
// used.
lenChainedChannels := int8(len(restrictions.incomingChainedChannels))
if len(paths) == 0 && lenChainedChannels > 0 {
orderedPaths = [][]blindedHop{incomingPath}
} else {
// Reverse each path so that the order is correct (from
// introduction node to last hop node) and then append this node
// on as the destination of each path.
for i, path := range paths {
sort.Slice(path, func(i, j int) bool {
return j < i
})
orderedPaths = append(
orderedPaths, append(path, incomingPath...),
)
}
orderedPaths[i] = append(path, incomingPath...)
}
// There is a chance we have an incoming path that by itself satisfies
// the minimum hop restriction. In that case, we add it as its own path.
if haveIncomingPath &&
len(incomingPath) > int(restrictions.minNumHops) {
orderedPaths = append(orderedPaths, incomingPath)
}
// Handle the special case that allows a blinded path with the
// introduction node as the destination node.
if restrictions.minNumHops == 0 && lenChainedChannels == 0 {
// introduction node as the destination node. This only applies if no
// incoming path was specified since in that case, by definition, the
// caller wants a non-zero length blinded path.
if restrictions.minNumHops == 0 && !haveIncomingPath {
singleHopPath := [][]blindedHop{{{vertex: target}}}
//nolint:makezero
orderedPaths = append(
orderedPaths, singleHopPath...,
)

View File

@@ -538,6 +538,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
aliasMap := make(map[string]route.Vertex)
privKeyMap := make(map[string]*btcec.PrivateKey)
channelIDs := make(map[route.Vertex]map[route.Vertex]uint64)
nodeIndex := byte(0)
addNodeWithAlias := func(alias string, features *lnwire.FeatureVector) (
@@ -652,6 +653,16 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
node1Vertex, node2Vertex = node2Vertex, node1Vertex
}
if _, ok := channelIDs[node1Vertex]; !ok {
channelIDs[node1Vertex] = map[route.Vertex]uint64{}
}
channelIDs[node1Vertex][node2Vertex] = channelID
if _, ok := channelIDs[node2Vertex]; !ok {
channelIDs[node2Vertex] = map[route.Vertex]uint64{}
}
channelIDs[node2Vertex][node1Vertex] = channelID
// We first insert the existence of the edge between the two
// nodes.
edgeInfo := models.ChannelEdgeInfo{
@@ -765,6 +776,7 @@ func createTestGraphFromChannels(t *testing.T, useCache bool,
mcBackend: graphBackend,
aliasMap: aliasMap,
privKeyMap: privKeyMap,
channelIDs: channelIDs,
links: links,
}, nil
}
@@ -3192,6 +3204,16 @@ func newPathFindingTestContext(t *testing.T, useCache bool,
return ctx
}
func (c *pathFindingTestContext) nodePairChannel(alias1, alias2 string) uint64 {
node1 := c.keyFromAlias(alias1)
node2 := c.keyFromAlias(alias2)
channel, ok := c.testGraphInstance.channelIDs[node1][node2]
require.True(c.t, ok)
return channel
}
func (c *pathFindingTestContext) keyFromAlias(alias string) route.Vertex {
return c.testGraphInstance.aliasMap[alias]
}
@@ -3876,7 +3898,7 @@ func TestFindBlindedPaths(t *testing.T) {
"eve,bob,dave",
})
// 5) Finally, we will test the special case where the destination node
// 5) We will also test the special case where the destination node
// is also the recipient.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 0,
@@ -3888,93 +3910,138 @@ func TestFindBlindedPaths(t *testing.T) {
"dave",
})
// 6) Restrict the min & max path length such that we only include paths
// with one being only the intro-node and the others with one hop other
// than the destination hop.
// 6) Now, we will test some cases where the user manually specifies
// the first few incoming channels of a route.
//
// 6.1) Let the user specify the B-D channel as the last hop with a
// max of 1 hop.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 0,
minNumHops: 1,
maxNumHops: 1,
incomingChainedChannels: []uint64{
ctx.nodePairChannel("bob", "dave"),
},
})
require.NoError(t, err)
// If the max number of hops is 1, then only the B->D path is chosen
assertPaths(paths, []string{
"bob,dave",
})
// 6.2) Extend the search to include 2 hops along with the B-D channel
// restriction.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 1,
maxNumHops: 2,
incomingChainedChannels: []uint64{
ctx.nodePairChannel("bob", "dave"),
},
})
require.NoError(t, err)
// We expect the following paths:
// - D
// - B, D
// - C, D
// The (A, D) path is not chosen since A does not advertise the route
// blinding feature bit. The (G, D) path is not chosen since G does not
// have any other known channels.
assertPaths(paths, []string{
"dave",
"bob,dave",
"charlie,dave",
})
// 7) Restrict the min & max path length such that we only include paths
// with one hop other than the destination hop. Now with bob-dave as the
// incoming channel.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 1,
maxNumHops: 1,
incomingChainedChannels: []uint64{2},
})
require.NoError(t, err)
// Expect only B->D path.
assertPaths(paths, []string{
"bob,dave",
})
// 8) Extend the search to include 2 hops other than the destination,
// with bob-dave as the incoming channel.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 1,
maxNumHops: 2,
incomingChainedChannels: []uint64{2},
})
require.NoError(t, err)
// We expect the following paths:
// - F, B, D
// - E, B, D
assertPaths(paths, []string{
"bob,dave",
"frank,bob,dave",
"eve,bob,dave",
})
// 9) Extend the search even further and also increase the minimum path
// length, but this time with charlie-dave as the incoming channel.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 2,
maxNumHops: 3,
incomingChainedChannels: []uint64{3},
})
require.NoError(t, err)
// We expect the following paths:
// - E, C, D
// - B, E, C, D
assertPaths(paths, []string{
"eve,charlie,dave",
"bob,eve,charlie,dave",
})
// 10) Repeat the above test but instruct the function to never use
// charlie.
// 6.3) Repeat the above test but instruct the function to never use
// bob. This should fail since bob owns one of the channels in the
// partially specified path.
_, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 2,
maxNumHops: 3,
nodeOmissionSet: fn.NewSet(ctx.keyFromAlias("charlie")),
incomingChainedChannels: []uint64{3},
minNumHops: 1,
maxNumHops: 2,
nodeOmissionSet: fn.NewSet(ctx.keyFromAlias("bob")),
incomingChainedChannels: []uint64{
ctx.nodePairChannel("bob", "dave"),
},
})
require.ErrorContains(t, err, "cannot simultaneously be included in "+
"the omission set and in the partially specified path")
// 11) Test the circular route error.
_, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 2,
maxNumHops: 3,
incomingChainedChannels: []uint64{2, 7, 6, 3},
// 6.4) Repeat it again but this time omit frank and demonstrate that
// the resulting set contains all the results from 6.2 except for the
// frank path.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 1,
maxNumHops: 2,
nodeOmissionSet: fn.NewSet(ctx.keyFromAlias("frank")),
incomingChainedChannels: []uint64{
ctx.nodePairChannel("bob", "dave"),
},
})
require.NoError(t, err)
// We expect the following paths:
// - B, D
// - E, B, D
assertPaths(paths, []string{
"bob,dave",
"eve,bob,dave",
})
// 6.5) Users may specify channels to nodes that do not signal route
// blinding (like A). So if we specify the A-D channel, we should get
// valid paths.
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 1,
maxNumHops: 4,
incomingChainedChannels: []uint64{
ctx.nodePairChannel("dave", "alice"),
},
})
require.NoError(t, err)
// We expect the following paths:
// - A, D
// - F, A, D
// - B, F, A, D
// - E, B, F, A, D
assertPaths(paths, []string{
"alice,dave",
"frank,alice,dave",
"bob,frank,alice,dave",
"eve,bob,frank,alice,dave",
})
// 6.6) Assert that an error is returned if a user accidentally tries
// to force a circular path.
_, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 2,
maxNumHops: 3,
incomingChainedChannels: []uint64{
ctx.nodePairChannel("dave", "alice"),
ctx.nodePairChannel("alice", "frank"),
ctx.nodePairChannel("frank", "bob"),
ctx.nodePairChannel("bob", "dave"),
},
})
require.ErrorContains(t, err, "circular route")
// 6.7) Test specifying a chain of incoming channels. We specify
// the following incoming list: [A->D, F->A].
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
minNumHops: 1,
maxNumHops: 4,
incomingChainedChannels: []uint64{
ctx.nodePairChannel("dave", "alice"),
ctx.nodePairChannel("alice", "frank"),
},
})
require.NoError(t, err)
// We expect the following paths:
// - F, A, D
// - B, F, A, D
// - E, B, F, A, D
assertPaths(paths, []string{
"frank,alice,dave",
"bob,frank,alice,dave",
"eve,bob,frank,alice,dave",
})
require.ErrorContains(t, err, "a circular route cannot be specified")
}

View File

@@ -699,8 +699,8 @@ func (r *ChannelRouter) FindBlindedPaths(destination route.Vertex,
"path since it resulted in an low "+
"probability path(%.3f)",
route.ChanIDString(routeWithProbability.route),
routeWithProbability.probability,
)
routeWithProbability.probability)
continue
}