routing+routerrpc: add multiple outgoing channel restriction
This commit is contained in:
11 files changed
+287
-214
No files matched your search
+19
-8
@@ -284,9 +284,9 @@ type RestrictParams struct {
|
||||
// the source to the target.
|
||||
FeeLimit lnwire.MilliSatoshi
|
||||
|
||||
// OutgoingChannelID is the channel that needs to be taken to the first
|
||||
// hop. If nil, any channel may be used.
|
||||
OutgoingChannelID *uint64
|
||||
// OutgoingChannelIDs is the list of channels that are allowed for the
|
||||
// first hop. If nil, any channel may be used.
|
||||
OutgoingChannelIDs []uint64
|
||||
|
||||
// LastHop is the pubkey of the last node before the final destination
|
||||
// is reached. If nil, any node may be used.
|
||||
@@ -329,7 +329,7 @@ type PathFindingConfig struct {
|
||||
// getOutgoingBalance returns the maximum available balance in any of the
|
||||
// channels of the given node. The second return parameters is the total
|
||||
// available balance.
|
||||
func getOutgoingBalance(node route.Vertex, outgoingChan *uint64,
|
||||
func getOutgoingBalance(node route.Vertex, outgoingChans map[uint64]struct{},
|
||||
bandwidthHints map[uint64]lnwire.MilliSatoshi,
|
||||
g routingGraph) (lnwire.MilliSatoshi, lnwire.MilliSatoshi, error) {
|
||||
|
||||
@@ -344,8 +344,10 @@ func getOutgoingBalance(node route.Vertex, outgoingChan *uint64,
|
||||
chanID := outEdge.ChannelID
|
||||
|
||||
// Enforce outgoing channel restriction.
|
||||
if outgoingChan != nil && chanID != *outgoingChan {
|
||||
return nil
|
||||
if outgoingChans != nil {
|
||||
if _, ok := outgoingChans[chanID]; !ok {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
bandwidth, ok := bandwidthHints[chanID]
|
||||
@@ -447,13 +449,22 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
||||
return nil, errNoPaymentAddr
|
||||
}
|
||||
|
||||
// Set up outgoing channel map for quicker access.
|
||||
var outgoingChanMap map[uint64]struct{}
|
||||
if len(r.OutgoingChannelIDs) > 0 {
|
||||
outgoingChanMap = make(map[uint64]struct{})
|
||||
for _, outChan := range r.OutgoingChannelIDs {
|
||||
outgoingChanMap[outChan] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are routing from ourselves, check that we have enough local
|
||||
// balance available.
|
||||
self := g.graph.sourceNode()
|
||||
|
||||
if source == self {
|
||||
max, total, err := getOutgoingBalance(
|
||||
self, r.OutgoingChannelID, g.bandwidthHints, g.graph,
|
||||
self, outgoingChanMap, g.bandwidthHints, g.graph,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -763,7 +774,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
||||
pivot := partialPath.node
|
||||
|
||||
// Create unified policies for all incoming connections.
|
||||
u := newUnifiedPolicies(self, pivot, r.OutgoingChannelID)
|
||||
u := newUnifiedPolicies(self, pivot, outgoingChanMap)
|
||||
|
||||
err := u.addGraphPolicies(g.graph)
|
||||
if err != nil {
|
||||
|
||||
@@ -2249,11 +2249,12 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
||||
|
||||
// Define channel id constants
|
||||
const (
|
||||
chanSourceA = 1
|
||||
chanATarget = 4
|
||||
chanSourceB1 = 2
|
||||
chanSourceB2 = 3
|
||||
chanBTarget = 5
|
||||
chanSourceA = 1
|
||||
chanATarget = 4
|
||||
chanSourceB1 = 2
|
||||
chanSourceB2 = 3
|
||||
chanBTarget = 5
|
||||
chanSourceTarget = 6
|
||||
)
|
||||
|
||||
// Set up a test graph with three possible paths from roasbeef to
|
||||
@@ -2276,6 +2277,9 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
||||
Expiry: 144,
|
||||
FeeRate: 800,
|
||||
}, chanBTarget),
|
||||
symmetricTestChannel("roasbeef", "target", 100000, &testChannelPolicy{
|
||||
Expiry: 144,
|
||||
}, chanSourceTarget),
|
||||
}
|
||||
|
||||
ctx := newPathFindingTestContext(t, testChannels, "roasbeef")
|
||||
@@ -2290,9 +2294,9 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
||||
target := ctx.keyFromAlias("target")
|
||||
outgoingChannelID := uint64(chanSourceB1)
|
||||
|
||||
// Find the best path given the restriction to only use chanSourceB1 as
|
||||
// the outgoing channel.
|
||||
ctx.restrictParams.OutgoingChannelID = &outgoingChannelID
|
||||
// Find the best path given the restriction to only use channel 2 as the
|
||||
// outgoing channel.
|
||||
ctx.restrictParams.OutgoingChannelIDs = []uint64{outgoingChannelID}
|
||||
path, err := ctx.findPath(target, paymentAmt)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find path: %v", err)
|
||||
@@ -2305,6 +2309,20 @@ func TestRestrictOutgoingChannel(t *testing.T) {
|
||||
"but channel %v was selected instead", chanSourceB1,
|
||||
path[0].ChannelID)
|
||||
}
|
||||
|
||||
// If a direct channel to target is allowed as well, that channel is
|
||||
// expected to be selected because the routing fees are zero.
|
||||
ctx.restrictParams.OutgoingChannelIDs = []uint64{
|
||||
chanSourceB1, chanSourceTarget,
|
||||
}
|
||||
path, err = ctx.findPath(target, paymentAmt)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to find path: %v", err)
|
||||
}
|
||||
if path[0].ChannelID != chanSourceTarget {
|
||||
t.Fatalf("expected route to pass through channel %v",
|
||||
chanSourceTarget)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRestrictLastHop asserts that a last hop restriction is obeyed by the path
|
||||
@@ -2766,7 +2784,7 @@ func TestRouteToSelf(t *testing.T) {
|
||||
|
||||
outgoingChanID := uint64(1)
|
||||
lastHop := ctx.keyFromAlias("b")
|
||||
ctx.restrictParams.OutgoingChannelID = &outgoingChanID
|
||||
ctx.restrictParams.OutgoingChannelIDs = []uint64{outgoingChanID}
|
||||
ctx.restrictParams.LastHop = &lastHop
|
||||
|
||||
// Find the best path to self given that we want to go out via channel 1
|
||||
|
||||
@@ -202,14 +202,14 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
|
||||
// to our destination, respecting the recommendations from
|
||||
// MissionControl.
|
||||
restrictions := &RestrictParams{
|
||||
ProbabilitySource: p.missionControl.GetProbability,
|
||||
FeeLimit: feeLimit,
|
||||
OutgoingChannelID: p.payment.OutgoingChannelID,
|
||||
LastHop: p.payment.LastHop,
|
||||
CltvLimit: cltvLimit,
|
||||
DestCustomRecords: p.payment.DestCustomRecords,
|
||||
DestFeatures: p.payment.DestFeatures,
|
||||
PaymentAddr: p.payment.PaymentAddr,
|
||||
ProbabilitySource: p.missionControl.GetProbability,
|
||||
FeeLimit: feeLimit,
|
||||
OutgoingChannelIDs: p.payment.OutgoingChannelIDs,
|
||||
LastHop: p.payment.LastHop,
|
||||
CltvLimit: cltvLimit,
|
||||
DestCustomRecords: p.payment.DestCustomRecords,
|
||||
DestFeatures: p.payment.DestFeatures,
|
||||
PaymentAddr: p.payment.PaymentAddr,
|
||||
}
|
||||
|
||||
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
|
||||
|
||||
+11
-4
@@ -1594,9 +1594,9 @@ type LightningPayment struct {
|
||||
// destination successfully.
|
||||
RouteHints [][]zpay32.HopHint
|
||||
|
||||
// OutgoingChannelID is the channel that needs to be taken to the first
|
||||
// hop. If nil, any channel may be used.
|
||||
OutgoingChannelID *uint64
|
||||
// OutgoingChannelIDs is the list of channels that are allowed for the
|
||||
// first hop. If nil, any channel may be used.
|
||||
OutgoingChannelIDs []uint64
|
||||
|
||||
// LastHop is the pubkey of the last node before the final destination
|
||||
// is reached. If nil, any node may be used.
|
||||
@@ -2379,6 +2379,13 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
||||
log.Tracef("BuildRoute called: hopsCount=%v, amt=%v",
|
||||
len(hops), amt)
|
||||
|
||||
var outgoingChans map[uint64]struct{}
|
||||
if outgoingChan != nil {
|
||||
outgoingChans = map[uint64]struct{}{
|
||||
*outgoingChan: {},
|
||||
}
|
||||
}
|
||||
|
||||
// If no amount is specified, we need to build a route for the minimum
|
||||
// amount that this route can carry.
|
||||
useMinAmt := amt == nil
|
||||
@@ -2444,7 +2451,7 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
||||
|
||||
// Build unified policies for this hop based on the channels
|
||||
// known in the graph.
|
||||
u := newUnifiedPolicies(source, toNode, outgoingChan)
|
||||
u := newUnifiedPolicies(source, toNode, outgoingChans)
|
||||
|
||||
err := u.addGraphPolicies(routingTx)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,13 +21,13 @@ type unifiedPolicies struct {
|
||||
|
||||
// outChanRestr is an optional outgoing channel restriction for the
|
||||
// local channel to use.
|
||||
outChanRestr *uint64
|
||||
outChanRestr map[uint64]struct{}
|
||||
}
|
||||
|
||||
// newUnifiedPolicies instantiates a new unifiedPolicies object. Channel
|
||||
// policies can be added to this object.
|
||||
func newUnifiedPolicies(sourceNode, toNode route.Vertex,
|
||||
outChanRestr *uint64) *unifiedPolicies {
|
||||
outChanRestr map[uint64]struct{}) *unifiedPolicies {
|
||||
|
||||
return &unifiedPolicies{
|
||||
policies: make(map[route.Vertex]*unifiedPolicy),
|
||||
@@ -45,10 +45,10 @@ func (u *unifiedPolicies) addPolicy(fromNode route.Vertex,
|
||||
localChan := fromNode == u.sourceNode
|
||||
|
||||
// Skip channels if there is an outgoing channel restriction.
|
||||
if localChan && u.outChanRestr != nil &&
|
||||
*u.outChanRestr != edge.ChannelID {
|
||||
|
||||
return
|
||||
if localChan && u.outChanRestr != nil {
|
||||
if _, ok := u.outChanRestr[edge.ChannelID]; !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Update the policies map.
|
||||
|
||||
Reference in new issue
Block a user