routing: use mission control to select blinded paths

Add a `FindBlindedPaths` method to the `ChannelRouter` which will use
the new `findBlindedPaths` function to get a set of candidate blinded
path routes. It then uses mission control to select the best of these
paths.

Note that as of this commit, the MC data we get from these queries won't
mean much since we wont have data about a channel in the direction
towards us. But we do this now in preparation for a future PR which will
start writing mission control success pairs for successful receives from
blinded route paths.
This commit is contained in:
Elle Mouton committed 2024-07-26 09:54:17 +02:00
1 parent 1039aedd0c
commit e12a226272
2 files changed
+345

No files matched your search

+219
View File
@@ -7,6 +7,7 @@ import (
"math"
"math/rand"
"net"
"strings"
"sync"
"sync/atomic"
"testing"
@@ -2589,3 +2590,221 @@ func createChannelEdge(bitcoinKey1, bitcoinKey2 []byte,
return fundingTx, &chanUtxo, chanID, nil
}
// TestFindBlindedPathsWithMC tests that the FindBlindedPaths method correctly
// selects a set of blinded paths by using mission control data to select the
// paths with the highest success probability.
func TestFindBlindedPathsWithMC(t *testing.T) {
t.Parallel()
rbFeatureBits := []lnwire.FeatureBit{
lnwire.RouteBlindingOptional,
}
// Create the following graph and let all the nodes advertise support
// for blinded paths.
//
// C
// / \
// / \
// E -- A -- F -- D
// \ /
// \ /
// B
//
featuresWithRouteBlinding := lnwire.NewFeatureVector(
lnwire.NewRawFeatureVector(rbFeatureBits...), lnwire.Features,
)
policyWithRouteBlinding := &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
MaxHTLC: 100000000,
Features: featuresWithRouteBlinding,
}
testChannels := []*testChannel{
symmetricTestChannel(
"eve", "alice", 100000, policyWithRouteBlinding, 1,
),
symmetricTestChannel(
"alice", "charlie", 100000, policyWithRouteBlinding, 2,
),
symmetricTestChannel(
"alice", "bob", 100000, policyWithRouteBlinding, 3,
),
symmetricTestChannel(
"charlie", "dave", 100000, policyWithRouteBlinding, 4,
),
symmetricTestChannel(
"bob", "dave", 100000, policyWithRouteBlinding, 5,
),
symmetricTestChannel(
"alice", "frank", 100000, policyWithRouteBlinding, 6,
),
symmetricTestChannel(
"frank", "dave", 100000, policyWithRouteBlinding, 7,
),
}
testGraph, err := createTestGraphFromChannels(
t, true, testChannels, "dave", rbFeatureBits...,
)
require.NoError(t, err)
ctx := createTestCtxFromGraphInstance(t, 101, testGraph)
var (
alice = ctx.aliases["alice"]
bob = ctx.aliases["bob"]
charlie = ctx.aliases["charlie"]
dave = ctx.aliases["dave"]
eve = ctx.aliases["eve"]
frank = ctx.aliases["frank"]
)
// Create a mission control store which initially sets the success
// probability of each node pair to 1.
missionControl := map[route.Vertex]map[route.Vertex]float64{
eve: {alice: 1},
alice: {
charlie: 1,
bob: 1,
frank: 1,
},
charlie: {dave: 1},
bob: {dave: 1},
frank: {dave: 1},
}
// probabilitySrc is a helper that returns the mission control success
// probability of a forward between two vertices.
probabilitySrc := func(from route.Vertex, to route.Vertex,
amt lnwire.MilliSatoshi, capacity btcutil.Amount) float64 {
return missionControl[from][to]
}
// All the probabilities are set to 1. So if we restrict the path length
// to 2 and allow a max of 3 routes, then we expect three paths here.
routes, err := ctx.router.FindBlindedPaths(
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
MinDistanceFromIntroNode: 2,
NumHops: 2,
MaxNumPaths: 3,
},
)
require.NoError(t, err)
require.Len(t, routes, 3)
// assertPaths checks that the resulting set of paths is equal to the
// expected set and that the order of the paths is correct.
assertPaths := func(paths []*route.Route, expectedPaths []string) {
require.Len(t, paths, len(expectedPaths))
var actualPaths []string
for _, path := range paths {
label := getAliasFromPubKey(
path.SourcePubKey, ctx.aliases,
) + ","
for _, hop := range path.Hops {
label += getAliasFromPubKey(
hop.PubKeyBytes, ctx.aliases,
) + ","
}
actualPaths = append(
actualPaths, strings.TrimRight(label, ","),
)
}
for i, path := range expectedPaths {
require.Equal(t, expectedPaths[i], path)
}
}
// Now, let's lower the MC probability of the B-D to 0.5 and F-D link to
// 0.25. We will leave the MaxNumPaths as 3 and so all paths should
// still be returned but the order should be:
// 1) A -> C -> D
// 2) A -> B -> D
// 3) A -> F -> D
missionControl[bob][dave] = 0.5
missionControl[frank][dave] = 0.25
routes, err = ctx.router.FindBlindedPaths(
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
MinDistanceFromIntroNode: 2,
NumHops: 2,
MaxNumPaths: 3,
},
)
require.NoError(t, err)
assertPaths(routes, []string{
"alice,charlie,dave",
"alice,bob,dave",
"alice,frank,dave",
})
// Just to show that the above result was not a fluke, let's change
// the C->D link to be the weak one.
missionControl[charlie][dave] = 0.125
routes, err = ctx.router.FindBlindedPaths(
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
MinDistanceFromIntroNode: 2,
NumHops: 2,
MaxNumPaths: 3,
},
)
require.NoError(t, err)
assertPaths(routes, []string{
"alice,bob,dave",
"alice,frank,dave",
"alice,charlie,dave",
})
// Change the MaxNumPaths to 1 to assert that only the best route is
// returned.
routes, err = ctx.router.FindBlindedPaths(
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
MinDistanceFromIntroNode: 2,
NumHops: 2,
MaxNumPaths: 1,
},
)
require.NoError(t, err)
assertPaths(routes, []string{
"alice,bob,dave",
})
// Test the edge case where Dave, the recipient, is also the
// introduction node.
routes, err = ctx.router.FindBlindedPaths(
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
MinDistanceFromIntroNode: 0,
NumHops: 0,
MaxNumPaths: 1,
},
)
require.NoError(t, err)
assertPaths(routes, []string{
"dave",
})
// Finally, we make one of the routes have a probability less than the
// minimum. This means we expect that route not to be chosen.
missionControl[charlie][dave] = DefaultMinRouteProbability
routes, err = ctx.router.FindBlindedPaths(
dave, 1000, probabilitySrc, &BlindedPathRestrictions{
MinDistanceFromIntroNode: 2,
NumHops: 2,
MaxNumPaths: 3,
},
)
require.NoError(t, err)
assertPaths(routes, []string{
"alice,bob,dave",
"alice,frank,dave",
})
}