routing: backward searching
This commit is contained in:
1 parent
e52d829168
commit
3e7473f4f0
7 files changed
+295
-152
No files matched your search
+12
-1
@@ -1,6 +1,9 @@
|
|||||||
package routing
|
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
|
// nodeWithDist is a helper struct that couples the distance from the current
|
||||||
// source to a node with a pointer to the node itself.
|
// 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
|
// node is the vertex itself. This pointer can be used to explore all
|
||||||
// the outgoing edges (channels) emanating from a node.
|
// the outgoing edges (channels) emanating from a node.
|
||||||
node *channeldb.LightningNode
|
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
|
// 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(
|
path, err := findPath(
|
||||||
nil, p.mc.graph, p.additionalEdges, p.mc.selfNode,
|
nil, p.mc.graph, p.additionalEdges, p.mc.selfNode,
|
||||||
payment.Target, pruneView.vertexes, pruneView.edges,
|
payment.Target, pruneView.vertexes, pruneView.edges,
|
||||||
payment.Amount, p.bandwidthHints,
|
payment.Amount, payment.FeeLimit, p.bandwidthHints,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
+216
-109
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
"github.com/coreos/bbolt"
|
"github.com/coreos/bbolt"
|
||||||
"github.com/lightningnetwork/lightning-onion"
|
"github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"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
|
// edge), as well as the total capacity. It also includes the origin chain of
|
||||||
// the channel itself.
|
// the channel itself.
|
||||||
type ChannelHop struct {
|
type ChannelHop struct {
|
||||||
// Capacity is the total capacity of the channel being traversed. This
|
// Bandwidth is an estimate of the maximum amount that can be sent
|
||||||
// value is expressed for stability in satoshis.
|
// through the channel in the direction indicated by ChannelEdgePolicy.
|
||||||
Capacity btcutil.Amount
|
// 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
|
// 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
|
// the channel. The 32-byte hash is the "genesis" block of the
|
||||||
@@ -109,6 +112,14 @@ type Hop struct {
|
|||||||
Fee lnwire.MilliSatoshi
|
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
|
// computeFee computes the fee to forward an HTLC of `amt` milli-satoshis over
|
||||||
// the passed active payment channel. This value is currently computed as
|
// the passed active payment channel. This value is currently computed as
|
||||||
// specified in BOLT07, but will likely change in the near future.
|
// 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
|
// enough capacity to carry the required amount which
|
||||||
// includes the fee dictated at each hop. Make the comparison
|
// includes the fee dictated at each hop. Make the comparison
|
||||||
// in msat to prevent rounding errors.
|
// in msat to prevent rounding errors.
|
||||||
if currentHop.AmtToForward+fee > lnwire.NewMSatFromSatoshis(
|
if currentHop.AmtToForward+fee > currentHop.Channel.Bandwidth {
|
||||||
currentHop.Channel.Capacity) {
|
|
||||||
|
|
||||||
err := fmt.Sprintf("channel graph has insufficient "+
|
err := fmt.Sprintf("channel graph has insufficient "+
|
||||||
"capacity for the payment: need %v, have %v",
|
"capacity for the payment: need %v, have %v",
|
||||||
currentHop.AmtToForward.ToSatoshis(),
|
currentHop.AmtToForward+fee,
|
||||||
currentHop.Channel.Capacity)
|
currentHop.Channel.Bandwidth)
|
||||||
|
|
||||||
return nil, newErrf(ErrInsufficientCapacity, err)
|
return nil, newErrf(ErrInsufficientCapacity, err)
|
||||||
}
|
}
|
||||||
@@ -430,32 +440,22 @@ func (v Vertex) String() string {
|
|||||||
return fmt.Sprintf("%x", v[:])
|
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
|
// 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
|
// 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
|
// 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.
|
// channels with shorter time lock deltas and shorter (hops) routes in general.
|
||||||
// RiskFactor controls the influence of time lock on route selection. This is
|
// RiskFactor controls the influence of time lock on route selection. This is
|
||||||
// currently a fixed value, but might be configurable in the future.
|
// currently a fixed value, but might be configurable in the future.
|
||||||
func edgeWeight(amt lnwire.MilliSatoshi, e *channeldb.ChannelEdgePolicy) int64 {
|
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
|
||||||
// First, we'll compute the "pure" fee through this hop. We say pure,
|
timeLockDelta uint16) int64 {
|
||||||
// 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))
|
|
||||||
|
|
||||||
// timeLockPenalty is the penalty for the time lock delta of this channel.
|
// timeLockPenalty is the penalty for the time lock delta of this channel.
|
||||||
// It is controlled by RiskFactorBillionths and scales proportional
|
// It is controlled by RiskFactorBillionths and scales proportional
|
||||||
// to the amount that will pass through channel. Rationale is that it if
|
// 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.
|
// 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
|
// 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
|
// 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
|
// 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
|
// 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,
|
func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||||
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy,
|
additionalEdges map[Vertex][]*channeldb.ChannelEdgePolicy,
|
||||||
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
|
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
|
||||||
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
|
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
|
||||||
amt lnwire.MilliSatoshi,
|
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi,
|
||||||
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*ChannelHop, error) {
|
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*ChannelHop, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
@@ -488,7 +491,9 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
var nodeHeap distanceHeap
|
var nodeHeap distanceHeap
|
||||||
|
|
||||||
// For each node in the graph, we create an entry in the distance
|
// 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)
|
distance := make(map[Vertex]nodeWithDist)
|
||||||
if err := graph.ForEachNode(tx, func(_ *bolt.Tx, node *channeldb.LightningNode) error {
|
if err := graph.ForEachNode(tx, func(_ *bolt.Tx, node *channeldb.LightningNode) error {
|
||||||
// TODO(roasbeef): with larger graph can just use disk seeks
|
// 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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll also include all the nodes found within the additional edges
|
additionalEdgesWithSrc := make(map[Vertex][]*edgePolicyWithSource)
|
||||||
// that are not known to us yet in the distance map.
|
for vertex, outgoingEdgePolicies := range additionalEdges {
|
||||||
for vertex := 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}
|
node := &channeldb.LightningNode{PubKeyBytes: vertex}
|
||||||
distance[vertex] = nodeWithDist{
|
distance[vertex] = nodeWithDist{
|
||||||
dist: infinity,
|
dist: infinity,
|
||||||
node: node,
|
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
|
// We can't always assume that the end destination is publicly
|
||||||
// advertised to the network and included in the graph.ForEachNode call
|
// 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)
|
targetVertex := NewVertex(target)
|
||||||
targetNode := &channeldb.LightningNode{PubKeyBytes: targetVertex}
|
targetNode := &channeldb.LightningNode{PubKeyBytes: targetVertex}
|
||||||
distance[targetVertex] = nodeWithDist{
|
distance[targetVertex] = nodeWithDist{
|
||||||
dist: infinity,
|
dist: 0,
|
||||||
node: targetNode,
|
node: targetNode,
|
||||||
|
amountToReceive: amt,
|
||||||
|
fee: 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll use this map as a series of "previous" hop pointers. So to get
|
// We'll use this map as a series of "next" hop pointers. So to get
|
||||||
// to `Vertex` we'll take the edge that it's mapped to within `prev`.
|
// from `Vertex` to the target node, we'll take the edge that it's
|
||||||
prev := make(map[Vertex]edgeWithPrev)
|
// mapped to within `next`.
|
||||||
|
next := make(map[Vertex]*ChannelHop)
|
||||||
|
|
||||||
// processEdge is a helper closure that will be used to make sure edges
|
// processEdge is a helper closure that will be used to make sure edges
|
||||||
// satisfy our specific requirements.
|
// satisfy our specific requirements.
|
||||||
processEdge := func(edge *channeldb.ChannelEdgePolicy,
|
processEdge := func(fromNode *channeldb.LightningNode,
|
||||||
bandwidth lnwire.MilliSatoshi, pivot Vertex) {
|
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
|
// If the edge is currently disabled, then we'll stop here, as
|
||||||
// we shouldn't attempt to route through it.
|
// 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
|
// If this vertex or edge has been black listed, then we'll skip
|
||||||
// exploring this edge.
|
// exploring this edge.
|
||||||
if _, ok := ignoredNodes[v]; ok {
|
if _, ok := ignoredNodes[fromVertex]; ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if _, ok := ignoredEdges[edge.ChannelID]; ok {
|
if _, ok := ignoredEdges[edge.ChannelID]; ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute the tentative distance to this new channel/edge which
|
toNodeDist := distance[toNode]
|
||||||
// is the distance to our pivot node plus the weight of this
|
|
||||||
// edge.
|
|
||||||
tempDist := distance[pivot].dist + edgeWeight(amt, edge)
|
|
||||||
|
|
||||||
// If this new tentative distance is better than the current
|
amountToSend := toNodeDist.amountToReceive
|
||||||
// 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 {
|
|
||||||
|
|
||||||
distance[v] = nodeWithDist{
|
// If the estimated band width of the channel edge is not able
|
||||||
dist: tempDist,
|
// to carry the amount that needs to be send, return.
|
||||||
node: edge.Node,
|
if bandwidth < amountToSend {
|
||||||
}
|
return
|
||||||
|
|
||||||
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 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
|
// TODO(roasbeef): also add path caching
|
||||||
// * similar to route caching, but doesn't factor in the amount
|
// * similar to route caching, but doesn't factor in the amount
|
||||||
|
|
||||||
// To start, we add the source of our path finding attempt to the
|
// To start, our target node will the sole item within our distance
|
||||||
// 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
|
|
||||||
// heap.
|
// heap.
|
||||||
heap.Push(&nodeHeap, distance[sourceVertex])
|
heap.Push(&nodeHeap, distance[targetVertex])
|
||||||
|
|
||||||
for nodeHeap.Len() != 0 {
|
for nodeHeap.Len() != 0 {
|
||||||
// Fetch the node within the smallest distance from our source
|
// 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)
|
partialPath := heap.Pop(&nodeHeap).(nodeWithDist)
|
||||||
bestNode := partialPath.node
|
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
|
// edges), then we're done here and can exit the graph
|
||||||
// traversal early.
|
// traversal early.
|
||||||
if bytes.Equal(bestNode.PubKeyBytes[:], targetVertex[:]) {
|
if bytes.Equal(bestNode.PubKeyBytes[:], sourceVertex[:]) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we've found the next potential step to take we'll
|
// 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.
|
// further our graph traversal.
|
||||||
pivot := Vertex(bestNode.PubKeyBytes)
|
pivot := Vertex(bestNode.PubKeyBytes)
|
||||||
err := bestNode.ForEachChannel(tx, func(tx *bolt.Tx,
|
err := bestNode.ForEachChannel(tx, func(tx *bolt.Tx,
|
||||||
edgeInfo *channeldb.ChannelEdgeInfo,
|
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
|
// We'll query the lower layer to see if we can obtain
|
||||||
// any more up to date information concerning the
|
// 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?
|
// 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
|
// of the private channel, we'll assume it was selected as a
|
||||||
// routing hint due to having enough capacity for the payment
|
// routing hint due to having enough capacity for the payment
|
||||||
// and use the payment amount as its capacity.
|
// and use the payment amount as its capacity.
|
||||||
for _, edge := range additionalEdges[bestNode.PubKeyBytes] {
|
bandWidth := partialPath.amountToReceive
|
||||||
processEdge(edge, amt, pivot)
|
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.
|
// 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 "+
|
return nil, newErrf(ErrNoPathFound, "unable to find a path to "+
|
||||||
"destination")
|
"destination")
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the potential route if below the max hop limit, then we'll use
|
// Use the nextHop map to unravel the forward path from source to target.
|
||||||
// the prevHop map to unravel the path. We end up with a list of edges
|
pathEdges := make([]*ChannelHop, 0, len(next))
|
||||||
// in the reverse direction which we'll use to properly calculate the
|
currentNode := sourceVertex
|
||||||
// timelock and fee values.
|
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles
|
||||||
pathEdges := make([]*ChannelHop, 0, len(prev))
|
// Determine the next hop forward using the next map.
|
||||||
prevNode := NewVertex(target)
|
nextNode := next[currentNode]
|
||||||
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)
|
|
||||||
|
|
||||||
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
|
// 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")
|
"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
|
return pathEdges, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -707,7 +814,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
// algorithm in a block box manner.
|
// algorithm in a block box manner.
|
||||||
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||||
source *channeldb.LightningNode, target *btcec.PublicKey,
|
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) {
|
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*ChannelHop, error) {
|
||||||
|
|
||||||
ignoredEdges := make(map[uint64]struct{})
|
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.
|
// satoshis along the path before fees are calculated.
|
||||||
startingPath, err := findPath(
|
startingPath, err := findPath(
|
||||||
tx, graph, nil, source, target, ignoredVertexes, ignoredEdges,
|
tx, graph, nil, source, target, ignoredVertexes, ignoredEdges,
|
||||||
amt, bandwidthHints,
|
amt, feeLimit, bandwidthHints,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("Unable to find path: %v", err)
|
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.
|
// shortest path from the spur node to the destination.
|
||||||
spurPath, err := findPath(
|
spurPath, err := findPath(
|
||||||
tx, graph, nil, spurNode, target,
|
tx, graph, nil, spurNode, target,
|
||||||
ignoredVertexes, ignoredEdges, amt,
|
ignoredVertexes, ignoredEdges, amt, feeLimit,
|
||||||
bandwidthHints,
|
bandwidthHints,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+60
-36
@@ -491,7 +491,12 @@ func TestFindLowestFeePath(t *testing.T) {
|
|||||||
// paths have equal total time locks, but the path through b has lower
|
// paths have equal total time locks, but the path through b has lower
|
||||||
// fees (700 compared to 800 for the path through a).
|
// fees (700 compared to 800 for the path through a).
|
||||||
testChannels := []*testChannel{
|
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,
|
Expiry: 144,
|
||||||
FeeRate: 400,
|
FeeRate: 400,
|
||||||
MinHTLC: 1,
|
MinHTLC: 1,
|
||||||
@@ -501,7 +506,7 @@ func TestFindLowestFeePath(t *testing.T) {
|
|||||||
FeeRate: 400,
|
FeeRate: 400,
|
||||||
MinHTLC: 1,
|
MinHTLC: 1,
|
||||||
}),
|
}),
|
||||||
symmetricTestChannel("roasbeef", "b", 100000, &testChannelPolicy{
|
symmetricTestChannel("first", "b", 100000, &testChannelPolicy{
|
||||||
Expiry: 144,
|
Expiry: 144,
|
||||||
FeeRate: 100,
|
FeeRate: 100,
|
||||||
MinHTLC: 1,
|
MinHTLC: 1,
|
||||||
@@ -537,7 +542,7 @@ func TestFindLowestFeePath(t *testing.T) {
|
|||||||
target := aliases["target"]
|
target := aliases["target"]
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, paymentAmt, nil,
|
ignoredEdges, paymentAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find path: %v", err)
|
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.
|
// 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()) {
|
aliases["b"].SerializeCompressed()) {
|
||||||
t.Fatalf("expected route to pass through b, "+
|
t.Fatalf("expected route to pass through b, "+
|
||||||
"but got a route through %v",
|
"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 {
|
type basicGraphPathFindingTestCase struct {
|
||||||
target string
|
target string
|
||||||
paymentAmt btcutil.Amount
|
paymentAmt btcutil.Amount
|
||||||
totalAmt lnwire.MilliSatoshi
|
feeLimit lnwire.MilliSatoshi
|
||||||
totalTimeLock uint32
|
expectedTotalAmt lnwire.MilliSatoshi
|
||||||
expectedHops []expectedHop
|
expectedTotalTimeLock uint32
|
||||||
|
expectedHops []expectedHop
|
||||||
|
expectFailureNoPath bool
|
||||||
}
|
}
|
||||||
|
|
||||||
var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{
|
var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{
|
||||||
// Basic route with one intermediate hop
|
// Basic route with one intermediate hop.
|
||||||
{target: "sophon", paymentAmt: 100, totalTimeLock: 102, totalAmt: 100110,
|
{target: "sophon", paymentAmt: 100, feeLimit: noFeeLimit,
|
||||||
|
expectedTotalTimeLock: 102, expectedTotalAmt: 100110,
|
||||||
expectedHops: []expectedHop{
|
expectedHops: []expectedHop{
|
||||||
{alias: "songoku", fwdAmount: 100000, fee: 110, timeLock: 101},
|
{alias: "songoku", fwdAmount: 100000, fee: 110, timeLock: 101},
|
||||||
{alias: "sophon", fwdAmount: 100000, fee: 0, 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{
|
expectedHops: []expectedHop{
|
||||||
{alias: "luoji", fwdAmount: 100000, fee: 0, timeLock: 101},
|
{alias: "luoji", fwdAmount: 100000, fee: 0, timeLock: 101},
|
||||||
}},
|
}},
|
||||||
|
|
||||||
// Three hop route where fees need to be added in to the forwarding amount.
|
// Three hop route where fees need to be added in to the forwarding amount.
|
||||||
// The high fee hop phamnewun should be avoided
|
// The high fee hop phamnewun should be avoided.
|
||||||
{target: "elst", paymentAmt: 50000, totalTimeLock: 103, totalAmt: 50050210,
|
{target: "elst", paymentAmt: 50000, feeLimit: noFeeLimit,
|
||||||
|
expectedTotalTimeLock: 103, expectedTotalAmt: 50050210,
|
||||||
expectedHops: []expectedHop{
|
expectedHops: []expectedHop{
|
||||||
{alias: "songoku", fwdAmount: 50000200, fee: 50010, timeLock: 102},
|
{alias: "songoku", fwdAmount: 50000200, fee: 50010, timeLock: 102},
|
||||||
{alias: "sophon", fwdAmount: 50000000, fee: 200, timeLock: 101},
|
{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
|
// songoku channel. Then there is no other option than to choose the
|
||||||
// expensive phamnuwen channel. This test case was failing before
|
// expensive phamnuwen channel. This test case was failing before
|
||||||
// the route search was executed backwards.
|
// 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{
|
expectedHops: []expectedHop{
|
||||||
{alias: "phamnuwen", fwdAmount: 100000200, fee: 10010020, timeLock: 102},
|
{alias: "phamnuwen", fwdAmount: 100000200, fee: 10010020, timeLock: 102},
|
||||||
{alias: "sophon", fwdAmount: 100000000, fee: 200, timeLock: 101},
|
{alias: "sophon", fwdAmount: 100000000, fee: 200, timeLock: 101},
|
||||||
{alias: "elst", fwdAmount: 100000000, fee: 0, 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) {
|
func TestBasicGraphPathFinding(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
@@ -650,14 +668,20 @@ func testBasicGraphPathFindingCase(t *testing.T, graph *channeldb.ChannelGraph,
|
|||||||
target := aliases[test.target]
|
target := aliases[test.target]
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
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 {
|
if err != nil {
|
||||||
t.Fatalf("unable to find path: %v", err)
|
t.Fatalf("unable to find path: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
route, err := newRoute(
|
route, err := newRoute(
|
||||||
paymentAmt, noFeeLimit, sourceVertex, path, startingHeight,
|
paymentAmt, test.feeLimit, sourceVertex, path, startingHeight,
|
||||||
finalHopCLTV,
|
finalHopCLTV,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -707,7 +731,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graph *channeldb.ChannelGraph,
|
|||||||
exitHop[:], hopPayloads[lastHopIndex].NextAddress)
|
exitHop[:], hopPayloads[lastHopIndex].NextAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
var expectedTotalFee lnwire.MilliSatoshi = 0
|
var expectedTotalFee lnwire.MilliSatoshi
|
||||||
for i := 0; i < expectedHopCount; i++ {
|
for i := 0; i < expectedHopCount; i++ {
|
||||||
// We'll ensure that the amount to forward, and fees
|
// We'll ensure that the amount to forward, and fees
|
||||||
// computed for each hop are correct.
|
// computed for each hop are correct.
|
||||||
@@ -736,13 +760,13 @@ func testBasicGraphPathFindingCase(t *testing.T, graph *channeldb.ChannelGraph,
|
|||||||
expectedTotalFee += expectedHops[i].fee
|
expectedTotalFee += expectedHops[i].fee
|
||||||
}
|
}
|
||||||
|
|
||||||
if route.TotalAmount != test.totalAmt {
|
if route.TotalAmount != test.expectedTotalAmt {
|
||||||
t.Fatalf("total amount incorrect: "+
|
t.Fatalf("total amount incorrect: "+
|
||||||
"expected %v, got %v",
|
"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,
|
t.Fatalf("expected time lock of %v, instead have %v", 2,
|
||||||
route.TotalTimeLock)
|
route.TotalTimeLock)
|
||||||
}
|
}
|
||||||
@@ -829,7 +853,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
|
|||||||
// We should now be able to find a path from roasbeef to doge.
|
// We should now be able to find a path from roasbeef to doge.
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
nil, graph, additionalEdges, sourceNode, dogePubKey, nil, nil,
|
nil, graph, additionalEdges, sourceNode, dogePubKey, nil, nil,
|
||||||
paymentAmt, nil,
|
paymentAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find private path to doge: %v", err)
|
t.Fatalf("unable to find private path to doge: %v", err)
|
||||||
@@ -865,7 +889,7 @@ func TestKShortestPathFinding(t *testing.T) {
|
|||||||
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
paymentAmt := lnwire.NewMSatFromSatoshis(100)
|
||||||
target := aliases["luoji"]
|
target := aliases["luoji"]
|
||||||
paths, err := findPaths(
|
paths, err := findPaths(
|
||||||
nil, graph, sourceNode, target, paymentAmt, 100,
|
nil, graph, sourceNode, target, paymentAmt, noFeeLimit, 100,
|
||||||
nil,
|
nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -916,7 +940,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
FeeBaseMSat: baseFee,
|
FeeBaseMSat: baseFee,
|
||||||
TimeLockDelta: timeLockDelta,
|
TimeLockDelta: timeLockDelta,
|
||||||
},
|
},
|
||||||
Capacity: capacity,
|
Bandwidth: lnwire.NewMSatFromSatoshis(capacity),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1148,7 +1172,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
|
|||||||
target := aliases["ursula"]
|
target := aliases["ursula"]
|
||||||
_, err = findPath(
|
_, err = findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, paymentAmt, nil,
|
ignoredEdges, paymentAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("path should have been found")
|
t.Fatalf("path should have been found")
|
||||||
@@ -1159,7 +1183,7 @@ func TestNewRoutePathTooLong(t *testing.T) {
|
|||||||
target = aliases["vincent"]
|
target = aliases["vincent"]
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, paymentAmt, nil,
|
ignoredEdges, paymentAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should not have been able to find path, supposed to be "+
|
t.Fatalf("should not have been able to find path, supposed to be "+
|
||||||
@@ -1201,7 +1225,7 @@ func TestPathNotAvailable(t *testing.T) {
|
|||||||
|
|
||||||
_, err = findPath(
|
_, err = findPath(
|
||||||
nil, graph, nil, sourceNode, unknownNode, ignoredVertexes,
|
nil, graph, nil, sourceNode, unknownNode, ignoredVertexes,
|
||||||
ignoredEdges, 100, nil,
|
ignoredEdges, 100, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
t.Fatalf("path shouldn't have been found: %v", err)
|
t.Fatalf("path shouldn't have been found: %v", err)
|
||||||
@@ -1237,7 +1261,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
|
|||||||
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
payAmt := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
|
||||||
_, err = findPath(
|
_, err = findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, payAmt, nil,
|
ignoredEdges, payAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
t.Fatalf("graph shouldn't be able to support payment: %v", err)
|
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)
|
payAmt := lnwire.MilliSatoshi(10)
|
||||||
_, err = findPath(
|
_, err = findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, payAmt, nil,
|
ignoredEdges, payAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
t.Fatalf("graph shouldn't be able to support payment: %v", err)
|
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
|
// First, we'll try to route from roasbeef -> sophon. This should
|
||||||
// succeed without issue, and return a single path via phamnuwen
|
// succeed without issue, and return a single path via phamnuwen
|
||||||
target := aliases["sophon"]
|
target := aliases["sophon"]
|
||||||
payAmt := lnwire.NewMSatFromSatoshis(120000)
|
payAmt := lnwire.NewMSatFromSatoshis(105000)
|
||||||
_, err = findPath(
|
_, err = findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, payAmt, nil,
|
ignoredEdges, payAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find path: %v", err)
|
t.Fatalf("unable to find path: %v", err)
|
||||||
@@ -1322,7 +1346,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
|
|||||||
// failure as it is no longer eligible.
|
// failure as it is no longer eligible.
|
||||||
_, err = findPath(
|
_, err = findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
nil, graph, nil, sourceNode, target, ignoredVertexes,
|
||||||
ignoredEdges, payAmt, nil,
|
ignoredEdges, payAmt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if !IsError(err, ErrNoPathFound) {
|
if !IsError(err, ErrNoPathFound) {
|
||||||
t.Fatalf("graph shouldn't be able to support payment: %v", err)
|
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)
|
amt := lnwire.NewMSatFromSatoshis(100)
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
nil, graph, nil, sourceNode, target, ignoredVertices,
|
nil, graph, nil, sourceNode, target, ignoredVertices,
|
||||||
ignoredEdges, amt, nil,
|
ignoredEdges, amt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find path from roasbeef to phamnuwen for "+
|
t.Fatalf("unable to find path from roasbeef to phamnuwen for "+
|
||||||
|
|||||||
+1
-1
@@ -1384,7 +1384,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
|
|||||||
// we'll execute our KSP algorithm to find the k-shortest paths from
|
// we'll execute our KSP algorithm to find the k-shortest paths from
|
||||||
// our source to the destination.
|
// our source to the destination.
|
||||||
shortestPaths, err := findPaths(
|
shortestPaths, err := findPaths(
|
||||||
tx, r.cfg.Graph, r.selfNode, target, amt, numPaths,
|
tx, r.cfg.Graph, r.selfNode, target, amt, feeLimit, numPaths,
|
||||||
bandwidthHints,
|
bandwidthHints,
|
||||||
)
|
)
|
||||||
if err != nil {
|
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 even though the direct path has a higher potential time lock.
|
||||||
path, err := findPath(
|
path, err := findPath(
|
||||||
nil, ctx.graph, nil, sourceNode, target, ignoreVertex,
|
nil, ctx.graph, nil, sourceNode, target, ignoreVertex,
|
||||||
ignoreEdge, amt, nil,
|
ignoreEdge, amt, noFeeLimit, nil,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to find path: %v", err)
|
t.Fatalf("unable to find path: %v", err)
|
||||||
|
|||||||
+4
-3
@@ -3264,7 +3264,7 @@ func marshallRoute(route *routing.Route) *lnrpc.Route {
|
|||||||
for i, hop := range route.Hops {
|
for i, hop := range route.Hops {
|
||||||
resp.Hops[i] = &lnrpc.Hop{
|
resp.Hops[i] = &lnrpc.Hop{
|
||||||
ChanId: hop.Channel.ChannelID,
|
ChanId: hop.Channel.ChannelID,
|
||||||
ChanCapacity: int64(hop.Channel.Capacity),
|
ChanCapacity: int64(hop.Channel.Bandwidth.ToSatoshis()),
|
||||||
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
|
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
|
||||||
AmtToForwardMsat: int64(hop.AmtToForward),
|
AmtToForwardMsat: int64(hop.AmtToForward),
|
||||||
Fee: int64(hop.Fee.ToSatoshis()),
|
Fee: int64(hop.Fee.ToSatoshis()),
|
||||||
@@ -3314,8 +3314,9 @@ func unmarshallRoute(rpcroute *lnrpc.Route,
|
|||||||
|
|
||||||
routingHop := &routing.ChannelHop{
|
routingHop := &routing.ChannelHop{
|
||||||
ChannelEdgePolicy: channelEdgePolicy,
|
ChannelEdgePolicy: channelEdgePolicy,
|
||||||
Capacity: btcutil.Amount(hop.ChanCapacity),
|
Bandwidth: lnwire.NewMSatFromSatoshis(
|
||||||
Chain: edgeInfo.ChainHash,
|
btcutil.Amount(hop.ChanCapacity)),
|
||||||
|
Chain: edgeInfo.ChainHash,
|
||||||
}
|
}
|
||||||
|
|
||||||
route.Hops[i] = &routing.Hop{
|
route.Hops[i] = &routing.Hop{
|
||||||
|
|||||||
Reference in new issue
Block a user