mirror of
https://github.com/lightningnetwork/lnd.git
synced 2025-09-28 17:56:25 +02:00
Merge pull request #8976 from ellemouton/rb-follow-ups
route blinding: follow ups
This commit is contained in:
@@ -132,20 +132,28 @@ func BuildBlindedPaymentPaths(cfg *BuildBlindedPathCfg) (
|
||||
// For each route returned, we will construct the associated blinded
|
||||
// payment path.
|
||||
for _, route := range routes {
|
||||
path, err := buildBlindedPaymentPath(
|
||||
cfg, extractCandidatePath(route),
|
||||
)
|
||||
// Extract the information we need from the route.
|
||||
candidatePath := extractCandidatePath(route)
|
||||
|
||||
// Pad the given route with dummy hops until the minimum number
|
||||
// of hops is met.
|
||||
candidatePath.padWithDummyHops(cfg.MinNumHops)
|
||||
|
||||
path, err := buildBlindedPaymentPath(cfg, candidatePath)
|
||||
if errors.Is(err, errInvalidBlindedPath) {
|
||||
log.Debugf("Not using route (%s) as a blinded path "+
|
||||
"since it resulted in an invalid blinded path",
|
||||
route)
|
||||
|
||||
continue
|
||||
} else if err != nil {
|
||||
log.Errorf("Not using route (%s) as a blinded path: %v",
|
||||
err)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Route selected for blinded path: %s", candidatePath)
|
||||
|
||||
paths = append(paths, path)
|
||||
}
|
||||
@@ -162,13 +170,6 @@ func BuildBlindedPaymentPaths(cfg *BuildBlindedPathCfg) (
|
||||
func buildBlindedPaymentPath(cfg *BuildBlindedPathCfg, path *candidatePath) (
|
||||
*zpay32.BlindedPaymentPath, error) {
|
||||
|
||||
// Pad the given route with dummy hops until the minimum number of hops
|
||||
// is met.
|
||||
err := path.padWithDummyHops(cfg.MinNumHops)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hops, minHTLC, maxHTLC, err := collectRelayInfo(cfg, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not collect blinded path relay "+
|
||||
@@ -663,19 +664,34 @@ type candidatePath struct {
|
||||
hops []*blindedPathHop
|
||||
}
|
||||
|
||||
// String returns a string representation of the candidatePath which can be
|
||||
// useful for logging and debugging.
|
||||
func (c *candidatePath) String() string {
|
||||
str := fmt.Sprintf("[%s (intro node)]", c.introNode)
|
||||
|
||||
for _, hop := range c.hops {
|
||||
if hop.isDummy {
|
||||
str += "--->[dummy hop]"
|
||||
continue
|
||||
}
|
||||
|
||||
str += fmt.Sprintf("--<%d>-->[%s]", hop.channelID, hop.pubKey)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
// padWithDummyHops will append n dummy hops to the candidatePath hop set. The
|
||||
// pub key for the dummy hop will be the same as the pub key for the final hop
|
||||
// of the path. That way, the final hop will be able to decrypt the data
|
||||
// encrypted for each dummy hop.
|
||||
func (c *candidatePath) padWithDummyHops(n uint8) error {
|
||||
func (c *candidatePath) padWithDummyHops(n uint8) {
|
||||
for len(c.hops) < int(n) {
|
||||
c.hops = append(c.hops, &blindedPathHop{
|
||||
pubKey: c.finalNodeID,
|
||||
isDummy: true,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// blindedPathHop holds the information we need to know about a hop in a route
|
||||
|
@@ -922,6 +922,75 @@ func TestBuildBlindedPathWithDummyHops(t *testing.T) {
|
||||
HtlcMinimumMsat: 1000,
|
||||
}, data.Constraints.UnwrapOrFail(t).Val)
|
||||
require.Equal(t, []byte{1, 2, 3}, data.PathID.UnwrapOrFail(t).Val)
|
||||
|
||||
// Demonstrate that BuildBlindedPaymentPaths continues to use any
|
||||
// functioning paths even if some routes cant be used to build a blinded
|
||||
// path. We do this by forcing FetchChannelEdgesByID to error out for
|
||||
// the first 2 calls. FindRoutes returns 3 routes and so by the end, we
|
||||
// still get 1 valid path.
|
||||
var errCount int
|
||||
paths, err = BuildBlindedPaymentPaths(&BuildBlindedPathCfg{
|
||||
FindRoutes: func(_ lnwire.MilliSatoshi) ([]*route.Route,
|
||||
error) {
|
||||
|
||||
return []*route.Route{realRoute, realRoute, realRoute},
|
||||
nil
|
||||
},
|
||||
FetchChannelEdgesByID: func(chanID uint64) (
|
||||
*models.ChannelEdgeInfo, *models.ChannelEdgePolicy,
|
||||
*models.ChannelEdgePolicy, error) {
|
||||
|
||||
// Force the call to error for the first 2 channels.
|
||||
if errCount < 2 {
|
||||
errCount++
|
||||
|
||||
return nil, nil, nil,
|
||||
fmt.Errorf("edge not found")
|
||||
}
|
||||
|
||||
policy, ok := realPolicies[chanID]
|
||||
if !ok {
|
||||
return nil, nil, nil,
|
||||
fmt.Errorf("edge not found")
|
||||
}
|
||||
|
||||
return nil, policy, nil, nil
|
||||
},
|
||||
BestHeight: func() (uint32, error) {
|
||||
return 1000, nil
|
||||
},
|
||||
// In the spec example, all the policies get replaced with
|
||||
// the same static values.
|
||||
AddPolicyBuffer: func(_ *BlindedHopPolicy) (
|
||||
*BlindedHopPolicy, error) {
|
||||
|
||||
return &BlindedHopPolicy{
|
||||
FeeRate: 500,
|
||||
BaseFee: 100,
|
||||
CLTVExpiryDelta: 144,
|
||||
MinHTLCMsat: 1000,
|
||||
MaxHTLCMsat: lnwire.MaxMilliSatoshi,
|
||||
}, nil
|
||||
},
|
||||
PathID: []byte{1, 2, 3},
|
||||
ValueMsat: 1000,
|
||||
MinFinalCLTVExpiryDelta: 12,
|
||||
BlocksUntilExpiry: 200,
|
||||
|
||||
// By setting the minimum number of hops to 4, we force 2 dummy
|
||||
// hops to be added to the real route.
|
||||
MinNumHops: 4,
|
||||
|
||||
DefaultDummyHopPolicy: &BlindedHopPolicy{
|
||||
CLTVExpiryDelta: 50,
|
||||
FeeRate: 100,
|
||||
BaseFee: 100,
|
||||
MinHTLCMsat: 1000,
|
||||
MaxHTLCMsat: lnwire.MaxMilliSatoshi,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, paths, 1)
|
||||
}
|
||||
|
||||
// TestSingleHopBlindedPath tests that blinded path construction is done
|
||||
|
@@ -14,6 +14,7 @@ import (
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
"github.com/lightningnetwork/lnd/channeldb/models"
|
||||
"github.com/lightningnetwork/lnd/feature"
|
||||
"github.com/lightningnetwork/lnd/fn"
|
||||
"github.com/lightningnetwork/lnd/lnutils"
|
||||
"github.com/lightningnetwork/lnd/lnwire"
|
||||
"github.com/lightningnetwork/lnd/record"
|
||||
@@ -1150,6 +1151,10 @@ type blindedPathRestrictions struct {
|
||||
// path. This doesn't include our node, so if the maximum is 1, then
|
||||
// the path will contain our node along with an introduction node hop.
|
||||
maxNumHops uint8
|
||||
|
||||
// nodeOmissionSet holds a set of node IDs of nodes that we should
|
||||
// ignore during blinded path selection.
|
||||
nodeOmissionSet fn.Set[route.Vertex]
|
||||
}
|
||||
|
||||
// blindedHop holds the information about a hop we have selected for a blinded
|
||||
@@ -1253,6 +1258,12 @@ func processNodeForBlindedPath(g Graph, node route.Vertex,
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// If we have explicitly been told to ignore this node for blinded paths
|
||||
// then we skip it too.
|
||||
if restrictions.nodeOmissionSet.Contains(node) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
supports, err := supportsRouteBlinding(node)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
|
@@ -3705,7 +3705,26 @@ func TestFindBlindedPaths(t *testing.T) {
|
||||
"charlie,eve,bob,dave",
|
||||
})
|
||||
|
||||
// 4) Finally, we will test the special case where the destination node
|
||||
// 4) Repeat the above test but instruct the function to never use
|
||||
// charlie.
|
||||
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
|
||||
minNumHops: 2,
|
||||
maxNumHops: 3,
|
||||
nodeOmissionSet: fn.NewSet[route.Vertex](
|
||||
ctx.keyFromAlias("charlie"),
|
||||
),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
// We expect the following paths:
|
||||
// - F, B, D
|
||||
// - E, B, D
|
||||
assertPaths(paths, []string{
|
||||
"frank,bob,dave",
|
||||
"eve,bob,dave",
|
||||
})
|
||||
|
||||
// 5) Finally, we will test the special case where the destination node
|
||||
// is also the recipient.
|
||||
paths, err = ctx.findBlindedPaths(&blindedPathRestrictions{
|
||||
minNumHops: 0,
|
||||
|
@@ -599,6 +599,10 @@ type BlindedPathRestrictions struct {
|
||||
|
||||
// MaxNumPaths is the maximum number of blinded paths to select.
|
||||
MaxNumPaths uint8
|
||||
|
||||
// NodeOmissionSet is a set of nodes that should not be used within any
|
||||
// of the blinded paths that we generate.
|
||||
NodeOmissionSet fn.Set[route.Vertex]
|
||||
}
|
||||
|
||||
// FindBlindedPaths finds a selection of paths to the destination node that can
|
||||
@@ -611,8 +615,9 @@ func (r *ChannelRouter) FindBlindedPaths(destination route.Vertex,
|
||||
// path length restrictions.
|
||||
paths, err := findBlindedPaths(
|
||||
r.cfg.RoutingGraph, destination, &blindedPathRestrictions{
|
||||
minNumHops: restrictions.MinDistanceFromIntroNode,
|
||||
maxNumHops: restrictions.NumHops,
|
||||
minNumHops: restrictions.MinDistanceFromIntroNode,
|
||||
maxNumHops: restrictions.NumHops,
|
||||
nodeOmissionSet: restrictions.NodeOmissionSet,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -681,7 +686,7 @@ func (r *ChannelRouter) FindBlindedPaths(destination route.Vertex,
|
||||
|
||||
// Sort the routes based on probability.
|
||||
sort.Slice(routes, func(i, j int) bool {
|
||||
return routes[i].probability < routes[j].probability
|
||||
return routes[i].probability > routes[j].probability
|
||||
})
|
||||
|
||||
// Now just choose the best paths up until the maximum number of allowed
|
||||
|
@@ -3158,7 +3158,7 @@ func TestFindBlindedPathsWithMC(t *testing.T) {
|
||||
}
|
||||
|
||||
for i, path := range expectedPaths {
|
||||
require.Equal(t, expectedPaths[i], path)
|
||||
require.Equal(t, path, actualPaths[i])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3244,4 +3244,19 @@ func TestFindBlindedPathsWithMC(t *testing.T) {
|
||||
"alice,bob,dave",
|
||||
"alice,frank,dave",
|
||||
})
|
||||
|
||||
// Test that if the user explicitly indicates that we should ignore
|
||||
// the Frank node during path selection, then this is done.
|
||||
routes, err = ctx.router.FindBlindedPaths(
|
||||
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
|
||||
MinDistanceFromIntroNode: 2,
|
||||
NumHops: 2,
|
||||
MaxNumPaths: 3,
|
||||
NodeOmissionSet: fn.NewSet(frank),
|
||||
},
|
||||
)
|
||||
require.NoError(t, err)
|
||||
assertPaths(routes, []string{
|
||||
"alice,bob,dave",
|
||||
})
|
||||
}
|
||||
|
Reference in New Issue
Block a user