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:
2 files changed
+345
No files matched your search
@@ -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",
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user