routing: backward searching
This commit is contained in:
parent
e52d829168
commit
3e7473f4f0
@ -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{
|
||||
|
Loading…
Reference in New Issue
Block a user