Merge pull request #7799 from positiveblue/fix-build-route
routing: fix `index out of range` panic
This commit is contained in:
2 files changed
+120
-32
No files matched your search
+71
-32
@@ -2743,6 +2743,19 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
// amount that this route can carry.
|
// amount that this route can carry.
|
||||||
useMinAmt := amt == nil
|
useMinAmt := amt == nil
|
||||||
|
|
||||||
|
var runningAmt lnwire.MilliSatoshi
|
||||||
|
if useMinAmt {
|
||||||
|
// For minimum amount routes, aim to deliver at least 1 msat to
|
||||||
|
// the destination. There are nodes in the wild that have a
|
||||||
|
// min_htlc channel policy of zero, which could lead to a zero
|
||||||
|
// amount payment being made.
|
||||||
|
runningAmt = 1
|
||||||
|
} else {
|
||||||
|
// If an amount is specified, we need to build a route that
|
||||||
|
// delivers exactly this amount to the final destination.
|
||||||
|
runningAmt = *amt
|
||||||
|
}
|
||||||
|
|
||||||
// We'll attempt to obtain a set of bandwidth hints that helps us select
|
// We'll attempt to obtain a set of bandwidth hints that helps us select
|
||||||
// the best outgoing channel to use in case no outgoing channel is set.
|
// the best outgoing channel to use in case no outgoing channel is set.
|
||||||
bandwidthHints, err := newBandwidthManager(
|
bandwidthHints, err := newBandwidthManager(
|
||||||
@@ -2759,24 +2772,46 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sourceNode := r.selfNode.PubKeyBytes
|
||||||
|
unifiers, senderAmt, err := getRouteUnifiers(
|
||||||
|
sourceNode, hops, useMinAmt, runningAmt, outgoingChans,
|
||||||
|
r.cachedGraph, bandwidthHints,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pathEdges, receiverAmt, err := getPathEdges(
|
||||||
|
sourceNode, senderAmt, unifiers, bandwidthHints, hops,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build and return the final route.
|
||||||
|
return newRoute(
|
||||||
|
sourceNode, pathEdges, uint32(height),
|
||||||
|
finalHopParams{
|
||||||
|
amt: receiverAmt,
|
||||||
|
totalAmt: receiverAmt,
|
||||||
|
cltvDelta: uint16(finalCltvDelta),
|
||||||
|
records: nil,
|
||||||
|
paymentAddr: payAddr,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getRouteUnifiers returns a list of edge unifiers for the given route.
|
||||||
|
func getRouteUnifiers(source route.Vertex, hops []route.Vertex,
|
||||||
|
useMinAmt bool, runningAmt lnwire.MilliSatoshi,
|
||||||
|
outgoingChans map[uint64]struct{}, graph routingGraph,
|
||||||
|
bandwidthHints *bandwidthManager) ([]*edgeUnifier, lnwire.MilliSatoshi,
|
||||||
|
error) {
|
||||||
|
|
||||||
// Allocate a list that will contain the edge unifiers for this route.
|
// Allocate a list that will contain the edge unifiers for this route.
|
||||||
unifiers := make([]*edgeUnifier, len(hops))
|
unifiers := make([]*edgeUnifier, len(hops))
|
||||||
|
|
||||||
var runningAmt lnwire.MilliSatoshi
|
|
||||||
if useMinAmt {
|
|
||||||
// For minimum amount routes, aim to deliver at least 1 msat to
|
|
||||||
// the destination. There are nodes in the wild that have a
|
|
||||||
// min_htlc channel policy of zero, which could lead to a zero
|
|
||||||
// amount payment being made.
|
|
||||||
runningAmt = 1
|
|
||||||
} else {
|
|
||||||
// If an amount is specified, we need to build a route that
|
|
||||||
// delivers exactly this amount to the final destination.
|
|
||||||
runningAmt = *amt
|
|
||||||
}
|
|
||||||
|
|
||||||
// Traverse hops backwards to accumulate fees in the running amounts.
|
// Traverse hops backwards to accumulate fees in the running amounts.
|
||||||
source := r.selfNode.PubKeyBytes
|
|
||||||
for i := len(hops) - 1; i >= 0; i-- {
|
for i := len(hops) - 1; i >= 0; i-- {
|
||||||
toNode := hops[i]
|
toNode := hops[i]
|
||||||
|
|
||||||
@@ -2793,16 +2828,16 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
// in the graph.
|
// in the graph.
|
||||||
u := newNodeEdgeUnifier(source, toNode, outgoingChans)
|
u := newNodeEdgeUnifier(source, toNode, outgoingChans)
|
||||||
|
|
||||||
err := u.addGraphPolicies(r.cachedGraph)
|
err := u.addGraphPolicies(graph)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exit if there are no channels.
|
// Exit if there are no channels.
|
||||||
edgeUnifier, ok := u.edgeUnifiers[fromNode]
|
edgeUnifier, ok := u.edgeUnifiers[fromNode]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Errorf("Cannot find policy for node %v", fromNode)
|
log.Errorf("Cannot find policy for node %v", fromNode)
|
||||||
return nil, ErrNoChannel{
|
return nil, 0, ErrNoChannel{
|
||||||
fromNode: fromNode,
|
fromNode: fromNode,
|
||||||
position: i,
|
position: i,
|
||||||
}
|
}
|
||||||
@@ -2822,7 +2857,7 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
log.Errorf("Cannot find policy with amt=%v for node %v",
|
log.Errorf("Cannot find policy with amt=%v for node %v",
|
||||||
runningAmt, fromNode)
|
runningAmt, fromNode)
|
||||||
|
|
||||||
return nil, ErrNoChannel{
|
return nil, 0, ErrNoChannel{
|
||||||
fromNode: fromNode,
|
fromNode: fromNode,
|
||||||
position: i,
|
position: i,
|
||||||
}
|
}
|
||||||
@@ -2839,17 +2874,31 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
unifiers[i] = edgeUnifier
|
unifiers[i] = edgeUnifier
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return unifiers, runningAmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPathEdges returns the edges that make up the path and the total amount,
|
||||||
|
// including fees, to send the payment.
|
||||||
|
func getPathEdges(source route.Vertex, receiverAmt lnwire.MilliSatoshi,
|
||||||
|
unifiers []*edgeUnifier, bandwidthHints *bandwidthManager,
|
||||||
|
hops []route.Vertex) ([]*channeldb.CachedEdgePolicy,
|
||||||
|
lnwire.MilliSatoshi, error) {
|
||||||
|
|
||||||
// Now that we arrived at the start of the route and found out the route
|
// Now that we arrived at the start of the route and found out the route
|
||||||
// total amount, we make a forward pass. Because the amount may have
|
// total amount, we make a forward pass. Because the amount may have
|
||||||
// been increased in the backward pass, fees need to be recalculated and
|
// been increased in the backward pass, fees need to be recalculated and
|
||||||
// amount ranges re-checked.
|
// amount ranges re-checked.
|
||||||
var pathEdges []*channeldb.CachedEdgePolicy
|
var pathEdges []*channeldb.CachedEdgePolicy
|
||||||
receiverAmt := runningAmt
|
|
||||||
for i, unifier := range unifiers {
|
for i, unifier := range unifiers {
|
||||||
edge := unifier.getEdge(receiverAmt, bandwidthHints)
|
edge := unifier.getEdge(receiverAmt, bandwidthHints)
|
||||||
if edge == nil {
|
if edge == nil {
|
||||||
return nil, ErrNoChannel{
|
fromNode := source
|
||||||
fromNode: hops[i-1],
|
if i > 0 {
|
||||||
|
fromNode = hops[i-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, 0, ErrNoChannel{
|
||||||
|
fromNode: fromNode,
|
||||||
position: i,
|
position: i,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2864,15 +2913,5 @@ func (r *ChannelRouter) BuildRoute(amt *lnwire.MilliSatoshi,
|
|||||||
pathEdges = append(pathEdges, edge.policy)
|
pathEdges = append(pathEdges, edge.policy)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build and return the final route.
|
return pathEdges, receiverAmt, nil
|
||||||
return newRoute(
|
|
||||||
source, pathEdges, uint32(height),
|
|
||||||
finalHopParams{
|
|
||||||
amt: receiverAmt,
|
|
||||||
totalAmt: receiverAmt,
|
|
||||||
cltvDelta: uint16(finalCltvDelta),
|
|
||||||
records: nil,
|
|
||||||
paymentAddr: payAddr,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
@@ -3187,6 +3187,55 @@ func TestBuildRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestGetPathEdges tests that the getPathEdges function returns the expected
|
||||||
|
// edges and amount when given a set of unifiers and does not panic.
|
||||||
|
func TestGetPathEdges(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
const startingBlockHeight = 101
|
||||||
|
ctx := createTestCtxFromFile(t, startingBlockHeight, basicGraphFilePath)
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
sourceNode route.Vertex
|
||||||
|
amt lnwire.MilliSatoshi
|
||||||
|
unifiers []*edgeUnifier
|
||||||
|
bandwidthHints *bandwidthManager
|
||||||
|
hops []route.Vertex
|
||||||
|
|
||||||
|
expectedEdges []*channeldb.CachedEdgePolicy
|
||||||
|
expectedAmt lnwire.MilliSatoshi
|
||||||
|
expectedErr string
|
||||||
|
}{{
|
||||||
|
sourceNode: ctx.aliases["roasbeef"],
|
||||||
|
unifiers: []*edgeUnifier{
|
||||||
|
{
|
||||||
|
edges: []*unifiedEdge{},
|
||||||
|
localChan: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErr: fmt.Sprintf("no matching outgoing channel "+
|
||||||
|
"available for node 0 (%v)", ctx.aliases["roasbeef"]),
|
||||||
|
}}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
pathEdges, amt, err := getPathEdges(
|
||||||
|
tc.sourceNode, tc.amt, tc.unifiers, tc.bandwidthHints,
|
||||||
|
tc.hops,
|
||||||
|
)
|
||||||
|
|
||||||
|
if tc.expectedErr != "" {
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, tc.expectedErr)
|
||||||
|
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, pathEdges, tc.expectedEdges)
|
||||||
|
require.Equal(t, amt, tc.expectedAmt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// edgeCreationModifier is an enum-like type used to modify steps that are
|
// edgeCreationModifier is an enum-like type used to modify steps that are
|
||||||
// skipped when creating a channel in the test context.
|
// skipped when creating a channel in the test context.
|
||||||
type edgeCreationModifier uint8
|
type edgeCreationModifier uint8
|
||||||
|
|||||||
Reference in new issue
Block a user