routing: backward searching

This commit is contained in:
Joost Jager 2018-06-04 22:10:05 +02:00 committed by Olaoluwa Osuntokun
parent e52d829168
commit 3e7473f4f0
7 changed files with 295 additions and 152 deletions

@ -1,6 +1,9 @@
package routing
import "github.com/lightningnetwork/lnd/channeldb"
import (
"github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire"
)
// nodeWithDist is a helper struct that couples the distance from the current
// source to a node with a pointer to the node itself.
@ -12,6 +15,14 @@ type nodeWithDist struct {
// node is the vertex itself. This pointer can be used to explore all
// the outgoing edges (channels) emanating from a node.
node *channeldb.LightningNode
// amountToReceive is the amount that should be received by this node.
// Either as final payment to the final node or as an intermediate
// amount that includes also the fees for subsequent hops.
amountToReceive lnwire.MilliSatoshi
// fee is the fee that this node is charging for forwarding.
fee lnwire.MilliSatoshi
}
// distanceHeap is a min-distance heap that's used within our path finding

@ -375,7 +375,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
path, err := findPath(
nil, p.mc.graph, p.additionalEdges, p.mc.selfNode,
payment.Target, pruneView.vertexes, pruneView.edges,
payment.Amount, p.bandwidthHints,
payment.Amount, payment.FeeLimit, p.bandwidthHints,
)
if err != nil {
return nil, err

@ -10,7 +10,6 @@ import (
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/coreos/bbolt"
"github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb"
@ -69,9 +68,13 @@ type HopHint struct {
// edge), as well as the total capacity. It also includes the origin chain of
// the channel itself.
type ChannelHop struct {
// Capacity is the total capacity of the channel being traversed. This
// value is expressed for stability in satoshis.
Capacity btcutil.Amount
// Bandwidth is an estimate of the maximum amount that can be sent
// through the channel in the direction indicated by ChannelEdgePolicy.
// It is based on the on-chain capacity of the channel, bandwidth
// hints passed in via SendRoute RPC and/or running amounts that
// represent pending payments. These running amounts have msat as
// unit. Therefore this property is expressed in msat too.
Bandwidth lnwire.MilliSatoshi
// Chain is a 32-byte has that denotes the base blockchain network of
// the channel. The 32-byte hash is the "genesis" block of the
@ -109,6 +112,14 @@ type Hop struct {
Fee lnwire.MilliSatoshi
}
// edgePolicyWithSource is a helper struct to keep track of the source node
// of a channel edge. ChannelEdgePolicy only contains to destination node
// of the edge.
type edgePolicyWithSource struct {
sourceNode *channeldb.LightningNode
edge *channeldb.ChannelEdgePolicy
}
// computeFee computes the fee to forward an HTLC of `amt` milli-satoshis over
// the passed active payment channel. This value is currently computed as
// specified in BOLT07, but will likely change in the near future.
@ -358,13 +369,12 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
// enough capacity to carry the required amount which
// includes the fee dictated at each hop. Make the comparison
// in msat to prevent rounding errors.
if currentHop.AmtToForward+fee > lnwire.NewMSatFromSatoshis(
currentHop.Channel.Capacity) {
if currentHop.AmtToForward+fee > currentHop.Channel.Bandwidth {
err := fmt.Sprintf("channel graph has insufficient "+
"capacity for the payment: need %v, have %v",
currentHop.AmtToForward.ToSatoshis(),
currentHop.Channel.Capacity)
currentHop.AmtToForward+fee,
currentHop.Channel.Bandwidth)
return nil, newErrf(ErrInsufficientCapacity, err)
}
@ -430,32 +440,22 @@ func (v Vertex) String() string {
return fmt.Sprintf("%x", v[:])
}
// edgeWithPrev is a helper struct used in path finding that couples an
// directional edge with the node's ID in the opposite direction.
type edgeWithPrev struct {
edge *ChannelHop
prevNode [33]byte
}
// edgeWeight computes the weight of an edge. This value is used when searching
// for the shortest path within the channel graph between two nodes. Weight is
// is the fee itself plus a time lock penalty added to it. This benefits
// channels with shorter time lock deltas and shorter (hops) routes in general.
// RiskFactor controls the influence of time lock on route selection. This is
// currently a fixed value, but might be configurable in the future.
func edgeWeight(amt lnwire.MilliSatoshi, e *channeldb.ChannelEdgePolicy) int64 {
// First, we'll compute the "pure" fee through this hop. We say pure,
// as this may not be what's ultimately paid as fees are properly
// calculated backwards, while we're going in the reverse direction.
pureFee := int64(computeFee(amt, e))
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
timeLockDelta uint16) int64 {
// timeLockPenalty is the penalty for the time lock delta of this channel.
// It is controlled by RiskFactorBillionths and scales proportional
// to the amount that will pass through channel. Rationale is that it if
// a twice as large amount gets locked up, it is twice as bad.
timeLockPenalty := int64(amt) * int64(e.TimeLockDelta) * RiskFactorBillionths / 1000000000
timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) *
RiskFactorBillionths / 1000000000
return pureFee + timeLockPenalty
return int64(fee) + timeLockPenalty
}
// findPath attempts to find a path from the source node within the
@ -465,12 +465,15 @@ func edgeWeight(amt lnwire.MilliSatoshi, e *channeldb.ChannelEdgePolicy) int64 {
// and the destination. The distance metric used for edges is related to the
// time-lock+fee costs along a particular edge. If a path is found, this
// function returns a slice of ChannelHop structs which encoded the chosen path
// from the target to the source.
// from the target to the source. The search is performed backwards from
// destination node back to source. This is to properly accumulate fees
// that need to be paid along the path and accurately check the amount
// to forward at every node against the available bandwidth.
func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy,
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
amt lnwire.MilliSatoshi,
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi,
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*ChannelHop, error) {
var err error
@ -488,7 +491,9 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
var nodeHeap distanceHeap
// For each node in the graph, we create an entry in the distance
// map for the node set with a distance of "infinity".
// map for the node set with a distance of "infinity". graph.ForEachNode
// also returns the source node, so there is no need to add the source
// node explictly.
distance := make(map[Vertex]nodeWithDist)
if err := graph.ForEachNode(tx, func(_ *bolt.Tx, node *channeldb.LightningNode) error {
// TODO(roasbeef): with larger graph can just use disk seeks
@ -502,36 +507,60 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
return nil, err
}
// We'll also include all the nodes found within the additional edges
// that are not known to us yet in the distance map.
for vertex := range additionalEdges {
additionalEdgesWithSrc := make(map[Vertex][]*edgePolicyWithSource)
for vertex, outgoingEdgePolicies := range additionalEdges {
// We'll also include all the nodes found within the additional edges
// that are not known to us yet in the distance map.
node := &channeldb.LightningNode{PubKeyBytes: vertex}
distance[vertex] = nodeWithDist{
dist: infinity,
node: node,
}
// Build reverse lookup to find incoming edges. Needed
// because search is taken place from target to source.
for _, outgoingEdgePolicy := range outgoingEdgePolicies {
toVertex := outgoingEdgePolicy.Node.PubKeyBytes
incomingEdgePolicy := &edgePolicyWithSource{
sourceNode: node,
edge: outgoingEdgePolicy,
}
additionalEdgesWithSrc[toVertex] =
append(additionalEdgesWithSrc[toVertex],
incomingEdgePolicy)
}
}
sourceVertex := Vertex(sourceNode.PubKeyBytes)
// We can't always assume that the end destination is publicly
// advertised to the network and included in the graph.ForEachNode call
// above, so we'll manually include the target node.
// above, so we'll manually include the target node. The target
// node charges no fee. Distance is set to 0, because this is the
// starting point of the graph traversal. We are searching backwards to
// get the fees first time right and correctly match channel bandwidth.
targetVertex := NewVertex(target)
targetNode := &channeldb.LightningNode{PubKeyBytes: targetVertex}
distance[targetVertex] = nodeWithDist{
dist: infinity,
node: targetNode,
dist: 0,
node: targetNode,
amountToReceive: amt,
fee: 0,
}
// We'll use this map as a series of "previous" hop pointers. So to get
// to `Vertex` we'll take the edge that it's mapped to within `prev`.
prev := make(map[Vertex]edgeWithPrev)
// We'll use this map as a series of "next" hop pointers. So to get
// from `Vertex` to the target node, we'll take the edge that it's
// mapped to within `next`.
next := make(map[Vertex]*ChannelHop)
// processEdge is a helper closure that will be used to make sure edges
// satisfy our specific requirements.
processEdge := func(edge *channeldb.ChannelEdgePolicy,
bandwidth lnwire.MilliSatoshi, pivot Vertex) {
processEdge := func(fromNode *channeldb.LightningNode,
edge *channeldb.ChannelEdgePolicy,
bandwidth lnwire.MilliSatoshi, toNode Vertex) {
v := Vertex(edge.Node.PubKeyBytes)
fromVertex := Vertex(fromNode.PubKeyBytes)
// If the edge is currently disabled, then we'll stop here, as
// we shouldn't attempt to route through it.
@ -542,61 +571,114 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// If this vertex or edge has been black listed, then we'll skip
// exploring this edge.
if _, ok := ignoredNodes[v]; ok {
if _, ok := ignoredNodes[fromVertex]; ok {
return
}
if _, ok := ignoredEdges[edge.ChannelID]; ok {
return
}
// Compute the tentative distance to this new channel/edge which
// is the distance to our pivot node plus the weight of this
// edge.
tempDist := distance[pivot].dist + edgeWeight(amt, edge)
toNodeDist := distance[toNode]
// If this new tentative distance is better than the current
// best known distance to this node, then we record the new
// better distance, and also populate our "next hop" map with
// this edge. We'll also shave off irrelevant edges by adding
// the sufficient capacity of an edge and clearing their
// min-htlc amount to our relaxation condition.
if tempDist < distance[v].dist && bandwidth >= amt &&
amt >= edge.MinHTLC && edge.TimeLockDelta != 0 {
amountToSend := toNodeDist.amountToReceive
distance[v] = nodeWithDist{
dist: tempDist,
node: edge.Node,
}
prev[v] = edgeWithPrev{
edge: &ChannelHop{
ChannelEdgePolicy: edge,
Capacity: bandwidth.ToSatoshis(),
},
prevNode: pivot,
}
// Add this new node to our heap as we'd like to further
// explore down this edge.
heap.Push(&nodeHeap, distance[v])
// If the estimated band width of the channel edge is not able
// to carry the amount that needs to be send, return.
if bandwidth < amountToSend {
return
}
// If the amountToSend is less than the minimum required amount,
// return.
if amountToSend < edge.MinHTLC {
return
}
// Compute fee that fromNode is charging. It is based on the
// amount that needs to be sent to the next node in the route.
//
// Source node has no precedessor to pay a fee. Therefore set
// fee to zero, because it should not be included in the
// fee limit check and edge weight.
//
// Also determine the time lock delta that will be added to
// the route if fromNode is selected. If fromNode is the
// source node, no additional timelock is required.
var fee lnwire.MilliSatoshi
var timeLockDelta uint16
if fromVertex != sourceVertex {
fee = computeFee(amountToSend, edge)
timeLockDelta = edge.TimeLockDelta
}
// amountToReceive is the amount that the node that
// is added to the distance map needs to receive from
// a (to be found) previous node in the route. That
// previous node will need to pay the amount that this
// node forwards plus the fee it charges.
amountToReceive := amountToSend + fee
// Check if accumulated fees would exceed fee limit when
// this node would be added to the path.
totalFee := amountToReceive - amt
if totalFee > feeLimit {
return
}
// By adding fromNode in the route, there will be an extra
// weight composed of the fee that this node will charge and
// the amount that will be locked for timeLockDelta blocks
// in the HTLC that is handed out to fromNode.
weight := edgeWeight(amountToReceive, fee, timeLockDelta)
// Compute the tentative distance to this new channel/edge which
// is the distance from our toNode to the target node plus the
// weight of this edge.
tempDist := toNodeDist.dist + weight
// If this new tentative distance is not better than the current
// best known distance to this node, return.
if tempDist >= distance[fromVertex].dist {
return
}
// If the edge has no time lock delta, the payment will always
// fail, so return.
//
// TODO(joostjager): Is this really true? Can't it be that
// nodes take this risk in exchange for a extraordinary high
// fee?
if edge.TimeLockDelta == 0 {
return
}
// All conditions are met and this new tentative distance is
// better than the current best known distance to this node.
// The new better distance is recorded, and also our
// "next hop" map is populated with this edge.
distance[fromVertex] = nodeWithDist{
dist: tempDist,
node: fromNode,
amountToReceive: amountToReceive,
fee: fee,
}
next[fromVertex] = &ChannelHop{
ChannelEdgePolicy: edge,
Bandwidth: bandwidth,
}
// Add this new node to our heap as we'd like to further
// explore backwards through this edge.
heap.Push(&nodeHeap, distance[fromVertex])
}
// TODO(roasbeef): also add path caching
// * similar to route caching, but doesn't factor in the amount
// To start, we add the source of our path finding attempt to the
// distance map with a distance of 0. This indicates our starting
// point in the graph traversal.
sourceVertex := Vertex(sourceNode.PubKeyBytes)
distance[sourceVertex] = nodeWithDist{
dist: 0,
node: sourceNode,
}
// To start, our source node will the sole item within our distance
// To start, our target node will the sole item within our distance
// heap.
heap.Push(&nodeHeap, distance[sourceVertex])
heap.Push(&nodeHeap, distance[targetVertex])
for nodeHeap.Len() != 0 {
// Fetch the node within the smallest distance from our source
@ -604,20 +686,28 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
partialPath := heap.Pop(&nodeHeap).(nodeWithDist)
bestNode := partialPath.node
// If we've reached our target (or we don't have any outgoing
// If we've reached our source (or we don't have any incoming
// edges), then we're done here and can exit the graph
// traversal early.
if bytes.Equal(bestNode.PubKeyBytes[:], targetVertex[:]) {
if bytes.Equal(bestNode.PubKeyBytes[:], sourceVertex[:]) {
break
}
// Now that we've found the next potential step to take we'll
// examine all the outgoing edge (channels) from this node to
// examine all the incoming edges (channels) from this node to
// further our graph traversal.
pivot := Vertex(bestNode.PubKeyBytes)
err := bestNode.ForEachChannel(tx, func(tx *bolt.Tx,
edgeInfo *channeldb.ChannelEdgeInfo,
outEdge, _ *channeldb.ChannelEdgePolicy) error {
_, inEdge *channeldb.ChannelEdgePolicy) error {
// If there is no edge policy for this candidate
// node, skip. Note that we are searching backwards
// so this node would have come prior to the pivot
// node in the route.
if inEdge == nil {
return nil
}
// We'll query the lower layer to see if we can obtain
// any more up to date information concerning the
@ -632,7 +722,31 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
)
}
processEdge(outEdge, edgeBandwidth, pivot)
// Lookup the source node at the other side of the
// channel via edgeInfo. This is necessary because this
// information is not present in inEdge.
channelSourcePubKeyBytes, err := edgeInfo.OtherNodeKeyBytes(pivot[:])
if err != nil {
return err
}
channelSourcePubKey, err := btcec.ParsePubKey(
channelSourcePubKeyBytes[:], btcec.S256())
if err != nil {
return err
}
// Lookup the full node details in order to be able to
// later iterate over all incoming edges of the source
// node.
channelSource, err := graph.FetchLightningNode(channelSourcePubKey)
if err != nil {
return err
}
// Check if this candidate node is better than what
// we already have.
processEdge(channelSource, inEdge, edgeBandwidth, pivot)
// TODO(roasbeef): return min HTLC as error in end?
@ -647,31 +761,31 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// of the private channel, we'll assume it was selected as a
// routing hint due to having enough capacity for the payment
// and use the payment amount as its capacity.
for _, edge := range additionalEdges[bestNode.PubKeyBytes] {
processEdge(edge, amt, pivot)
bandWidth := partialPath.amountToReceive
for _, reverseEdge := range additionalEdgesWithSrc[bestNode.PubKeyBytes] {
processEdge(reverseEdge.sourceNode, reverseEdge.edge, bandWidth, pivot)
}
}
// If the target node isn't found in the prev hop map, then a path
// If the source node isn't found in the next hop map, then a path
// doesn't exist, so we terminate in an error.
if _, ok := prev[NewVertex(target)]; !ok {
if _, ok := next[sourceVertex]; !ok {
return nil, newErrf(ErrNoPathFound, "unable to find a path to "+
"destination")
}
// If the potential route if below the max hop limit, then we'll use
// the prevHop map to unravel the path. We end up with a list of edges
// in the reverse direction which we'll use to properly calculate the
// timelock and fee values.
pathEdges := make([]*ChannelHop, 0, len(prev))
prevNode := NewVertex(target)
for prevNode != sourceVertex { // TODO(roasbeef): assumes no cycles
// Add the current hop to the limit of path edges then walk
// backwards from this hop via the prev pointer for this hop
// within the prevHop map.
pathEdges = append(pathEdges, prev[prevNode].edge)
// Use the nextHop map to unravel the forward path from source to target.
pathEdges := make([]*ChannelHop, 0, len(next))
currentNode := sourceVertex
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles
// Determine the next hop forward using the next map.
nextNode := next[currentNode]
prevNode = Vertex(prev[prevNode].prevNode)
// Add the next hop to the list of path edges.
pathEdges = append(pathEdges, nextNode)
// Advance current node.
currentNode = Vertex(nextNode.Node.PubKeyBytes)
}
// The route is invalid if it spans more than 20 hops. The current
@ -684,13 +798,6 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
"too many hops")
}
// As our traversal of the prev map above walked backwards from the
// target to the source in the route, we need to reverse it before
// returning the final route.
for i := 0; i < numEdges/2; i++ {
pathEdges[i], pathEdges[numEdges-i-1] = pathEdges[numEdges-i-1], pathEdges[i]
}
return pathEdges, nil
}
@ -707,7 +814,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// algorithm in a block box manner.
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
source *channeldb.LightningNode, target *btcec.PublicKey,
amt lnwire.MilliSatoshi, numPaths uint32,
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi, numPaths uint32,
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*ChannelHop, error) {
ignoredEdges := make(map[uint64]struct{})
@ -725,7 +832,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// satoshis along the path before fees are calculated.
startingPath, err := findPath(
tx, graph, nil, source, target, ignoredVertexes, ignoredEdges,
amt, bandwidthHints,
amt, feeLimit, bandwidthHints,
)
if err != nil {
log.Errorf("Unable to find path: %v", err)
@ -799,7 +906,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
// shortest path from the spur node to the destination.
spurPath, err := findPath(
tx, graph, nil, spurNode, target,
ignoredVertexes, ignoredEdges, amt,
ignoredVertexes, ignoredEdges, amt, feeLimit,
bandwidthHints,
)

@ -491,7 +491,12 @@ func TestFindLowestFeePath(t *testing.T) {
// paths have equal total time locks, but the path through b has lower
// fees (700 compared to 800 for the path through a).
testChannels := []*testChannel{
symmetricTestChannel("roasbeef", "a", 100000, &testChannelPolicy{
symmetricTestChannel("roasbeef", "first", 100000, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
}),
symmetricTestChannel("first", "a", 100000, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
@ -501,7 +506,7 @@ func TestFindLowestFeePath(t *testing.T) {
FeeRate: 400,
MinHTLC: 1,
}),
symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{
symmetricTestChannel("first", "b", 100000, &testChannelPolicy{
Expiry: 144,
FeeRate: 100,
MinHTLC: 1,
@ -537,7 +542,7 @@ func TestFindLowestFeePath(t *testing.T) {
target := aliases["target"]
path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, nil,
ignoredEdges, paymentAmt, noFeeLimit, nil,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -550,11 +555,11 @@ func TestFindLowestFeePath(t *testing.T) {
}
// Assert that the lowest fee route is returned.
if !bytes.Equal(route.Hops[0].Channel.Node.PubKeyBytes[:],
if !bytes.Equal(route.Hops[1].Channel.Node.PubKeyBytes[:],
aliases["b"].SerializeCompressed()) {
t.Fatalf("expected route to pass through b, "+
"but got a route through %v",
route.Hops[0].Channel.Node.Alias)
route.Hops[1].Channel.Node.Alias)
}
}
@ -566,28 +571,35 @@ type expectedHop struct {
}
type basicGraphPathFindingTestCase struct {
target string
paymentAmt btcutil.Amount
totalAmt lnwire.MilliSatoshi
totalTimeLock uint32
expectedHops []expectedHop
target string
paymentAmt btcutil.Amount
feeLimit lnwire.MilliSatoshi
expectedTotalAmt lnwire.MilliSatoshi
expectedTotalTimeLock uint32
expectedHops []expectedHop
expectFailureNoPath bool
}
var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{
// Basic route with one intermediate hop
{target: "sophon", paymentAmt: 100, totalTimeLock: 102, totalAmt: 100110,
// Basic route with one intermediate hop.
{target: "sophon", paymentAmt: 100, feeLimit: noFeeLimit,
expectedTotalTimeLock: 102, expectedTotalAmt: 100110,
expectedHops: []expectedHop{
{alias: "songoku", fwdAmount: 100000, fee: 110, timeLock: 101},
{alias: "sophon", fwdAmount: 100000, fee: 0, timeLock: 101},
}},
// Basic direct (one hop) route
{target: "luoji", paymentAmt: 100, totalTimeLock: 101, totalAmt: 100000,
// Basic direct (one hop) route.
{target: "luoji", paymentAmt: 100, feeLimit: noFeeLimit,
expectedTotalTimeLock: 101, expectedTotalAmt: 100000,
expectedHops: []expectedHop{
{alias: "luoji", fwdAmount: 100000, fee: 0, timeLock: 101},
}},
// Three hop route where fees need to be added in to the forwarding amount.
// The high fee hop phamnewun should be avoided
{target: "elst", paymentAmt: 50000, totalTimeLock: 103, totalAmt: 50050210,
// The high fee hop phamnewun should be avoided.
{target: "elst", paymentAmt: 50000, feeLimit: noFeeLimit,
expectedTotalTimeLock: 103, expectedTotalAmt: 50050210,
expectedHops: []expectedHop{
{alias: "songoku", fwdAmount: 50000200, fee: 50010, timeLock: 102},
{alias: "sophon", fwdAmount: 50000000, fee: 200, timeLock: 101},
@ -598,12 +610,18 @@ var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{
// songoku channel. Then there is no other option than to choose the
// expensive phamnuwen channel. This test case was failing before
// the route search was executed backwards.
{target: "elst", paymentAmt: 100000, totalTimeLock: 103, totalAmt: 110010220,
{target: "elst", paymentAmt: 100000, feeLimit: noFeeLimit,
expectedTotalTimeLock: 103, expectedTotalAmt: 110010220,
expectedHops: []expectedHop{
{alias: "phamnuwen", fwdAmount: 100000200, fee: 10010020, timeLock: 102},
{alias: "sophon", fwdAmount: 100000000, fee: 200, timeLock: 101},
{alias: "elst", fwdAmount: 100000000, fee: 0, timeLock: 101},
}}}
}},
// Basic route with fee limit.
{target: "sophon", paymentAmt: 100, feeLimit: 50,
expectFailureNoPath: true,
}}
func TestBasicGraphPathFinding(t *testing.T) {
t.Parallel()
@ -650,14 +668,20 @@ func testBasicGraphPathFindingCase(t *testing.T, graph *channeldb.ChannelGraph,
target := aliases[test.target]
path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, nil,
ignoredEdges, paymentAmt, test.feeLimit, nil,
)
if test.expectFailureNoPath {
if err == nil {
t.Fatal("expected no path to be found")
}
return
}
if err != nil {
t.Fatalf("unable to find path: %v", err)
}
route, err := newRoute(
paymentAmt, noFeeLimit, sourceVertex, path, startingHeight,
paymentAmt, test.feeLimit, sourceVertex, path, startingHeight,
finalHopCLTV,
)
if err != nil {
@ -707,7 +731,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graph *channeldb.ChannelGraph,
exitHop[:], hopPayloads[lastHopIndex].NextAddress)
}
var expectedTotalFee lnwire.MilliSatoshi = 0
var expectedTotalFee lnwire.MilliSatoshi
for i := 0; i < expectedHopCount; i++ {
// We'll ensure that the amount to forward, and fees
// computed for each hop are correct.
@ -736,13 +760,13 @@ func testBasicGraphPathFindingCase(t *testing.T, graph *channeldb.ChannelGraph,
expectedTotalFee += expectedHops[i].fee
}
if route.TotalAmount != test.totalAmt {
if route.TotalAmount != test.expectedTotalAmt {
t.Fatalf("total amount incorrect: "+
"expected %v, got %v",
test.totalAmt, route.TotalAmount)
test.expectedTotalAmt, route.TotalAmount)
}
if route.TotalTimeLock != test.totalTimeLock {
if route.TotalTimeLock != test.expectedTotalTimeLock {
t.Fatalf("expected time lock of %v, instead have %v", 2,
route.TotalTimeLock)
}
@ -829,7 +853,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
// We should now be able to find a path from roasbeef to doge.
path, err := findPath(
nil, graph, additionalEdges, sourceNode, dogePubKey, nil, nil,
paymentAmt, nil,
paymentAmt, noFeeLimit, nil,
)
if err != nil {
t.Fatalf("unable to find private path to doge: %v", err)
@ -865,7 +889,7 @@ func TestKShortestPathFinding(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := aliases["luoji"]
paths, err := findPaths(
nil, graph, sourceNode, target, paymentAmt, 100,
nil, graph, sourceNode, target, paymentAmt, noFeeLimit, 100,
nil,
)
if err != nil {
@ -916,7 +940,7 @@ func TestNewRoute(t *testing.T) {
FeeBaseMSat: baseFee,
TimeLockDelta: timeLockDelta,
},
Capacity: capacity,
Bandwidth: lnwire.NewMSatFromSatoshis(capacity),
}
}
@ -1148,7 +1172,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
target := aliases["ursula"]
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, nil,
ignoredEdges, paymentAmt, noFeeLimit, nil,
)
if err != nil {
t.Fatalf("path should have been found")
@ -1159,7 +1183,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
target = aliases["vincent"]
path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, paymentAmt, nil,
ignoredEdges, paymentAmt, noFeeLimit, nil,
)
if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+
@ -1201,7 +1225,7 @@ func TestPathNotAvailable(t *testing.T) {
_, err = findPath(
nil, graph, nil, sourceNode, unknownNode, ignoredVertexes,
ignoredEdges, 100, nil,
ignoredEdges, 100, noFeeLimit, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("path shouldn't have been found: %v", err)
@ -1237,7 +1261,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, nil,
ignoredEdges, payAmt, noFeeLimit, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1269,7 +1293,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
payAmt := lnwire.MilliSatoshi(10)
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, nil,
ignoredEdges, payAmt, noFeeLimit, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1298,10 +1322,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
// First, we'll try to route from roasbeef -> sophon. This should
// succeed without issue, and return a single path via phamnuwen
target := aliases["sophon"]
payAmt := lnwire.NewMSatFromSatoshis(120000)
payAmt := lnwire.NewMSatFromSatoshis(105000)
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, nil,
ignoredEdges, payAmt, noFeeLimit, nil,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -1322,7 +1346,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
// failure as it is no longer eligible.
_, err = findPath(
nil, graph, nil, sourceNode, target, ignoredVertexes,
ignoredEdges, payAmt, nil,
ignoredEdges, payAmt, noFeeLimit, nil,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1353,7 +1377,7 @@ func TestRouteExceededFeeLimit(t *testing.T) {
amt := lnwire.NewMSatFromSatoshis(100)
path, err := findPath(
nil, graph, nil, sourceNode, target, ignoredVertices,
ignoredEdges, amt, nil,
ignoredEdges, amt, noFeeLimit, nil,
)
if err != nil {
t.Fatalf("unable to find path from roasbeef to phamnuwen for "+

@ -1384,7 +1384,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// we'll execute our KSP algorithm to find the k-shortest paths from
// our source to the destination.
shortestPaths, err := findPaths(
tx, r.cfg.Graph, r.selfNode, target, amt, numPaths,
tx, r.cfg.Graph, r.selfNode, target, amt, feeLimit, numPaths,
bandwidthHints,
)
if err != nil {

@ -1687,7 +1687,7 @@ func TestFindPathFeeWeighting(t *testing.T) {
// path even though the direct path has a higher potential time lock.
path, err := findPath(
nil, ctx.graph, nil, sourceNode, target, ignoreVertex,
ignoreEdge, amt, nil,
ignoreEdge, amt, noFeeLimit, nil,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)

@ -3264,7 +3264,7 @@ func marshallRoute(route *routing.Route) *lnrpc.Route {
for i, hop := range route.Hops {
resp.Hops[i] = &lnrpc.Hop{
ChanId: hop.Channel.ChannelID,
ChanCapacity: int64(hop.Channel.Capacity),
ChanCapacity: int64(hop.Channel.Bandwidth.ToSatoshis()),
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
AmtToForwardMsat: int64(hop.AmtToForward),
Fee: int64(hop.Fee.ToSatoshis()),
@ -3314,8 +3314,9 @@ func unmarshallRoute(rpcroute *lnrpc.Route,
routingHop := &routing.ChannelHop{
ChannelEdgePolicy: channelEdgePolicy,
Capacity: btcutil.Amount(hop.ChanCapacity),
Chain: edgeInfo.ChainHash,
Bandwidth: lnwire.NewMSatFromSatoshis(
btcutil.Amount(hop.ChanCapacity)),
Chain: edgeInfo.ChainHash,
}
route.Hops[i] = &routing.Hop{