2016-12-19 09:14:01 +03:00
|
|
|
package routing
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
|
|
|
|
import (
|
2017-03-20 00:15:58 +03:00
|
|
|
"container/heap"
|
2019-07-31 07:41:58 +03:00
|
|
|
"fmt"
|
2019-04-05 18:36:11 +03:00
|
|
|
"math"
|
2019-09-06 09:56:59 +03:00
|
|
|
"time"
|
2017-03-20 00:15:58 +03:00
|
|
|
|
2019-08-20 19:27:03 +03:00
|
|
|
"github.com/btcsuite/btcd/btcec"
|
2018-03-11 06:00:57 +03:00
|
|
|
"github.com/coreos/bbolt"
|
2019-01-16 17:47:43 +03:00
|
|
|
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
"github.com/lightningnetwork/lnd/channeldb"
|
2017-08-22 09:43:20 +03:00
|
|
|
"github.com/lightningnetwork/lnd/lnwire"
|
2019-04-05 18:36:11 +03:00
|
|
|
"github.com/lightningnetwork/lnd/routing/route"
|
2019-07-31 07:41:58 +03:00
|
|
|
"github.com/lightningnetwork/lnd/tlv"
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// HopLimit is the maximum number hops that is permissible as a route.
|
|
|
|
// Any potential paths found that lie above this limit will be rejected
|
|
|
|
// with an error. This value is computed using the current fixed-size
|
|
|
|
// packet length of the Sphinx construction.
|
|
|
|
HopLimit = 20
|
|
|
|
|
|
|
|
// infinity is used as a starting distance in our shortest path search.
|
2018-02-13 03:27:30 +03:00
|
|
|
infinity = math.MaxInt64
|
2018-06-09 23:36:48 +03:00
|
|
|
|
|
|
|
// RiskFactorBillionths controls the influence of time lock delta
|
|
|
|
// of a channel on route selection. It is expressed as billionths
|
|
|
|
// of msat per msat sent through the channel per time lock delta
|
|
|
|
// block. See edgeWeight function below for more details.
|
|
|
|
// The chosen value is based on the previous incorrect weight function
|
|
|
|
// 1 + timelock + fee * fee. In this function, the fee penalty
|
|
|
|
// diminishes the time lock penalty for all but the smallest amounts.
|
|
|
|
// To not change the behaviour of path finding too drastically, a
|
|
|
|
// relatively small value is chosen which is still big enough to give
|
|
|
|
// some effect with smaller time lock values. The value may need
|
|
|
|
// tweaking and/or be made configurable in the future.
|
|
|
|
RiskFactorBillionths = 15
|
2019-08-23 18:27:02 +03:00
|
|
|
|
|
|
|
// estimatedNodeCount is used to preallocate the path finding structures
|
|
|
|
// to avoid resizing and copies. It should be number on the same order as
|
|
|
|
// the number of active nodes in the network.
|
|
|
|
estimatedNodeCount = 10000
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
)
|
|
|
|
|
2019-02-13 13:35:55 +03:00
|
|
|
// pathFinder defines the interface of a path finding algorithm.
|
|
|
|
type pathFinder = func(g *graphParams, r *RestrictParams,
|
2019-06-20 13:03:45 +03:00
|
|
|
cfg *PathFindingConfig, source, target route.Vertex,
|
|
|
|
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error)
|
2019-02-13 13:35:55 +03:00
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
var (
|
|
|
|
// DefaultPaymentAttemptPenalty is the virtual cost in path finding weight
|
|
|
|
// units of executing a payment attempt that fails. It is used to trade
|
|
|
|
// off potentially better routes against their probability of
|
|
|
|
// succeeding.
|
|
|
|
DefaultPaymentAttemptPenalty = lnwire.NewMSatFromSatoshis(100)
|
2019-05-13 18:00:35 +03:00
|
|
|
|
2019-05-22 12:56:04 +03:00
|
|
|
// DefaultMinRouteProbability is the default minimum probability for routes
|
2019-05-13 18:00:35 +03:00
|
|
|
// returned from findPath.
|
2019-05-22 12:56:04 +03:00
|
|
|
DefaultMinRouteProbability = float64(0.01)
|
|
|
|
|
|
|
|
// DefaultAprioriHopProbability is the default a priori probability for
|
|
|
|
// a hop.
|
2019-07-29 15:20:06 +03:00
|
|
|
DefaultAprioriHopProbability = float64(0.6)
|
2019-03-19 13:45:10 +03:00
|
|
|
)
|
|
|
|
|
2018-06-04 23:10:05 +03:00
|
|
|
// 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 {
|
2019-06-19 05:19:37 +03:00
|
|
|
sourceNode route.Vertex
|
2018-06-04 23:10:05 +03:00
|
|
|
edge *channeldb.ChannelEdgePolicy
|
|
|
|
}
|
|
|
|
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
// newRoute returns a fully valid route between the source and target that's
|
|
|
|
// capable of supporting a payment of `amtToSend` after fees are fully
|
2017-03-20 01:15:24 +03:00
|
|
|
// computed. If the route is too long, or the selected path cannot support the
|
|
|
|
// fully payment including fees, then a non-nil error is returned.
|
|
|
|
//
|
2017-03-21 04:11:22 +03:00
|
|
|
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
|
|
|
|
// the source to the target node of the path finding attempt.
|
2019-04-05 18:36:11 +03:00
|
|
|
func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex route.Vertex,
|
2018-08-09 16:36:28 +03:00
|
|
|
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
|
2019-07-31 07:41:58 +03:00
|
|
|
finalCLTVDelta uint16,
|
|
|
|
finalDestRecords []tlv.Record) (*route.Route, error) {
|
2017-08-03 07:01:49 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
var (
|
2019-04-05 18:36:11 +03:00
|
|
|
hops []*route.Hop
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
// totalTimeLock will accumulate the cumulative time lock
|
|
|
|
// across the entire route. This value represents how long the
|
|
|
|
// sender will need to wait in the *worst* case.
|
|
|
|
totalTimeLock = currentHeight
|
2017-10-11 05:45:09 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
// nextIncomingAmount is the amount that will need to flow into
|
|
|
|
// the *next* hop. Since we're going to be walking the route
|
|
|
|
// backwards below, this next hop gets closer and closer to the
|
|
|
|
// sender of the payment.
|
|
|
|
nextIncomingAmount lnwire.MilliSatoshi
|
|
|
|
)
|
2017-10-11 05:41:47 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
pathLength := len(pathEdges)
|
2018-08-09 16:36:28 +03:00
|
|
|
for i := pathLength - 1; i >= 0; i-- {
|
2017-10-25 04:27:29 +03:00
|
|
|
// Now we'll start to calculate the items within the per-hop
|
2018-10-24 03:16:39 +03:00
|
|
|
// payload for the hop this edge is leading to.
|
|
|
|
edge := pathEdges[i]
|
2018-06-12 14:04:40 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
// If this is the last hop, then the hop payload will contain
|
2018-06-12 14:04:40 +03:00
|
|
|
// the exact amount. In BOLT #4: Onion Routing
|
|
|
|
// Protocol / "Payload for the Last Node", this is detailed.
|
|
|
|
amtToForward := amtToSend
|
|
|
|
|
|
|
|
// Fee is not part of the hop payload, but only used for
|
|
|
|
// reporting through RPC. Set to zero for the final hop.
|
2017-10-25 04:27:29 +03:00
|
|
|
fee := lnwire.MilliSatoshi(0)
|
|
|
|
|
2018-07-31 10:19:49 +03:00
|
|
|
// If the current hop isn't the last hop, then add enough funds
|
2018-06-12 14:04:40 +03:00
|
|
|
// to pay for transit over the next link.
|
2017-10-25 04:27:29 +03:00
|
|
|
if i != len(pathEdges)-1 {
|
2018-07-31 10:19:49 +03:00
|
|
|
// The amount that the current hop needs to forward is
|
2018-08-09 16:36:28 +03:00
|
|
|
// equal to the incoming amount of the next hop.
|
|
|
|
amtToForward = nextIncomingAmount
|
2018-06-12 14:04:40 +03:00
|
|
|
|
|
|
|
// The fee that needs to be paid to the current hop is
|
2018-07-31 10:19:49 +03:00
|
|
|
// based on the amount that this hop needs to forward
|
2018-06-12 14:04:40 +03:00
|
|
|
// and its policy for the outgoing channel. This policy
|
|
|
|
// is stored as part of the incoming channel of
|
|
|
|
// the next hop.
|
2019-01-24 23:14:05 +03:00
|
|
|
fee = pathEdges[i+1].ComputeFee(amtToForward)
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
}
|
|
|
|
|
2017-06-16 23:33:50 +03:00
|
|
|
// If this is the last hop, then for verification purposes, the
|
2017-09-12 22:27:41 +03:00
|
|
|
// value of the outgoing time-lock should be _exactly_ the
|
|
|
|
// absolute time out they'd expect in the HTLC.
|
2018-08-09 16:36:28 +03:00
|
|
|
var outgoingTimeLock uint32
|
2017-06-16 23:33:50 +03:00
|
|
|
if i == len(pathEdges)-1 {
|
2017-10-19 07:43:53 +03:00
|
|
|
// As this is the last hop, we'll use the specified
|
|
|
|
// final CLTV delta value instead of the value from the
|
|
|
|
// last link in the route.
|
2018-08-09 16:36:28 +03:00
|
|
|
totalTimeLock += uint32(finalCLTVDelta)
|
2017-10-19 07:43:53 +03:00
|
|
|
|
2018-08-09 16:36:28 +03:00
|
|
|
outgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
2017-06-16 23:33:50 +03:00
|
|
|
} else {
|
2017-10-19 07:43:53 +03:00
|
|
|
// Next, increment the total timelock of the entire
|
|
|
|
// route such that each hops time lock increases as we
|
|
|
|
// walk backwards in the route, using the delta of the
|
|
|
|
// previous hop.
|
2018-06-26 06:27:46 +03:00
|
|
|
delta := uint32(pathEdges[i+1].TimeLockDelta)
|
2018-08-09 16:36:28 +03:00
|
|
|
totalTimeLock += delta
|
2017-10-19 07:43:53 +03:00
|
|
|
|
2017-06-16 23:33:50 +03:00
|
|
|
// Otherwise, the value of the outgoing time-lock will
|
|
|
|
// be the value of the time-lock for the _outgoing_
|
2017-08-03 07:01:49 +03:00
|
|
|
// HTLC, so we factor in their specified grace period
|
|
|
|
// (time lock delta).
|
2018-08-09 16:36:28 +03:00
|
|
|
outgoingTimeLock = totalTimeLock - delta
|
2017-06-16 23:33:50 +03:00
|
|
|
}
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
// Since we're traversing the path backwards atm, we prepend
|
|
|
|
// each new hop such that, the final slice of hops will be in
|
|
|
|
// the forwards order.
|
2019-04-05 18:36:11 +03:00
|
|
|
currentHop := &route.Hop{
|
|
|
|
PubKeyBytes: edge.Node.PubKeyBytes,
|
2018-08-09 16:36:28 +03:00
|
|
|
ChannelID: edge.ChannelID,
|
|
|
|
AmtToForward: amtToForward,
|
|
|
|
OutgoingTimeLock: outgoingTimeLock,
|
2019-07-31 07:41:58 +03:00
|
|
|
LegacyPayload: true,
|
2018-08-09 16:36:28 +03:00
|
|
|
}
|
2019-07-31 07:41:58 +03:00
|
|
|
|
|
|
|
// We start out above by assuming that this node needs the
|
|
|
|
// legacy payload, as if we don't have the full
|
|
|
|
// NodeAnnouncement information for this node, then we can't
|
|
|
|
// assume it knows the latest features. If we do have a feature
|
|
|
|
// vector for this node, then we'll update the info now.
|
|
|
|
if edge.Node.Features != nil {
|
|
|
|
features := edge.Node.Features
|
|
|
|
currentHop.LegacyPayload = !features.HasFeature(
|
|
|
|
lnwire.TLVOnionPayloadOptional,
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
// If this is the last hop, then we'll populate any TLV records
|
|
|
|
// destined for it.
|
|
|
|
if i == len(pathEdges)-1 && len(finalDestRecords) != 0 {
|
|
|
|
currentHop.TLVRecords = finalDestRecords
|
|
|
|
}
|
|
|
|
|
2019-04-05 18:36:11 +03:00
|
|
|
hops = append([]*route.Hop{currentHop}, hops...)
|
2018-08-09 16:36:28 +03:00
|
|
|
|
2018-10-24 03:16:39 +03:00
|
|
|
// Finally, we update the amount that needs to flow into the
|
|
|
|
// *next* hop, which is the amount this hop needs to forward,
|
|
|
|
// accounting for the fee that it takes.
|
2018-08-09 16:36:28 +03:00
|
|
|
nextIncomingAmount = amtToForward + fee
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
}
|
|
|
|
|
2018-08-09 16:36:28 +03:00
|
|
|
// With the base routing data expressed as hops, build the full route
|
2019-04-05 18:36:11 +03:00
|
|
|
newRoute, err := route.NewRouteFromHops(
|
2019-07-31 07:41:58 +03:00
|
|
|
nextIncomingAmount, totalTimeLock, route.Vertex(sourceVertex),
|
|
|
|
hops,
|
2018-10-24 03:16:39 +03:00
|
|
|
)
|
2018-12-10 11:41:03 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2018-08-09 16:36:28 +03:00
|
|
|
|
|
|
|
return newRoute, nil
|
|
|
|
}
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
|
|
|
|
// edgeWeight computes the weight of an edge. This value is used when searching
|
2018-06-09 23:36:48 +03:00
|
|
|
// for the shortest path within the channel graph between two nodes. Weight is
|
2018-07-31 10:19:49 +03:00
|
|
|
// 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
|
2018-06-09 23:36:48 +03:00
|
|
|
// currently a fixed value, but might be configurable in the future.
|
2018-06-04 23:10:05 +03:00
|
|
|
func edgeWeight(lockedAmt lnwire.MilliSatoshi, fee lnwire.MilliSatoshi,
|
|
|
|
timeLockDelta uint16) int64 {
|
2018-06-09 23:36:48 +03:00
|
|
|
// 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.
|
2018-06-04 23:10:05 +03:00
|
|
|
timeLockPenalty := int64(lockedAmt) * int64(timeLockDelta) *
|
|
|
|
RiskFactorBillionths / 1000000000
|
2018-02-13 03:27:30 +03:00
|
|
|
|
2018-06-04 23:10:05 +03:00
|
|
|
return int64(fee) + timeLockPenalty
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
}
|
|
|
|
|
2018-10-25 00:06:12 +03:00
|
|
|
// graphParams wraps the set of graph parameters passed to findPath.
|
|
|
|
type graphParams struct {
|
|
|
|
// tx can be set to an existing db transaction. If not set, a new
|
|
|
|
// transaction will be started.
|
|
|
|
tx *bbolt.Tx
|
|
|
|
|
|
|
|
// graph is the ChannelGraph to be used during path finding.
|
|
|
|
graph *channeldb.ChannelGraph
|
|
|
|
|
|
|
|
// additionalEdges is an optional set of edges that should be
|
|
|
|
// considered during path finding, that is not already found in the
|
|
|
|
// channel graph.
|
2019-04-05 18:36:11 +03:00
|
|
|
additionalEdges map[route.Vertex][]*channeldb.ChannelEdgePolicy
|
2018-10-25 00:06:12 +03:00
|
|
|
|
|
|
|
// bandwidthHints is an optional map from channels to bandwidths that
|
|
|
|
// can be populated if the caller has a better estimate of the current
|
2018-10-26 21:41:55 +03:00
|
|
|
// channel bandwidth than what is found in the graph. If set, it will
|
|
|
|
// override the capacities and disabled flags found in the graph for
|
|
|
|
// local channels when doing path finding. In particular, it should be
|
|
|
|
// set to the current available sending bandwidth for active local
|
|
|
|
// channels, and 0 for inactive channels.
|
2018-10-25 00:06:12 +03:00
|
|
|
bandwidthHints map[uint64]lnwire.MilliSatoshi
|
|
|
|
}
|
|
|
|
|
2019-03-05 13:13:44 +03:00
|
|
|
// RestrictParams wraps the set of restrictions passed to findPath that the
|
2018-10-25 00:06:12 +03:00
|
|
|
// found path must adhere to.
|
2019-03-05 13:13:44 +03:00
|
|
|
type RestrictParams struct {
|
2019-03-19 13:45:10 +03:00
|
|
|
// ProbabilitySource is a callback that is expected to return the
|
|
|
|
// success probability of traversing the channel from the node.
|
2019-07-29 16:10:58 +03:00
|
|
|
ProbabilitySource func(route.Vertex, route.Vertex,
|
2019-03-19 19:09:27 +03:00
|
|
|
lnwire.MilliSatoshi) float64
|
2018-10-25 00:06:12 +03:00
|
|
|
|
2019-03-05 13:13:44 +03:00
|
|
|
// FeeLimit is a maximum fee amount allowed to be used on the path from
|
2018-10-25 00:06:12 +03:00
|
|
|
// the source to the target.
|
2019-03-05 13:13:44 +03:00
|
|
|
FeeLimit lnwire.MilliSatoshi
|
2019-02-01 15:53:27 +03:00
|
|
|
|
2019-03-05 13:13:44 +03:00
|
|
|
// OutgoingChannelID is the channel that needs to be taken to the first
|
2019-02-01 15:53:27 +03:00
|
|
|
// hop. If nil, any channel may be used.
|
2019-03-05 13:13:44 +03:00
|
|
|
OutgoingChannelID *uint64
|
2019-02-13 12:08:53 +03:00
|
|
|
|
2019-11-18 13:54:15 +03:00
|
|
|
// LastHop is the pubkey of the last node before the final destination
|
|
|
|
// is reached. If nil, any node may be used.
|
|
|
|
LastHop *route.Vertex
|
|
|
|
|
2019-02-13 12:08:53 +03:00
|
|
|
// CltvLimit is the maximum time lock of the route excluding the final
|
|
|
|
// ctlv. After path finding is complete, the caller needs to increase
|
|
|
|
// all cltv expiry heights with the required final cltv delta.
|
2019-10-11 22:46:10 +03:00
|
|
|
CltvLimit uint32
|
2019-07-31 07:41:58 +03:00
|
|
|
|
|
|
|
// DestPayloadTLV should be set to true if we need to drop off a TLV
|
|
|
|
// payload at the final hop in order to properly complete this payment
|
|
|
|
// attempt.
|
|
|
|
DestPayloadTLV bool
|
2019-06-20 13:03:45 +03:00
|
|
|
}
|
2019-03-19 13:45:10 +03:00
|
|
|
|
2019-06-20 13:03:45 +03:00
|
|
|
// PathFindingConfig defines global parameters that control the trade-off in
|
|
|
|
// path finding between fees and probabiity.
|
|
|
|
type PathFindingConfig struct {
|
2019-03-19 13:45:10 +03:00
|
|
|
// PaymentAttemptPenalty is the virtual cost in path finding weight
|
|
|
|
// units of executing a payment attempt that fails. It is used to trade
|
|
|
|
// off potentially better routes against their probability of
|
|
|
|
// succeeding.
|
|
|
|
PaymentAttemptPenalty lnwire.MilliSatoshi
|
2019-05-13 18:00:35 +03:00
|
|
|
|
|
|
|
// MinProbability defines the minimum success probability of the
|
|
|
|
// returned route.
|
|
|
|
MinProbability float64
|
2018-10-25 00:06:12 +03:00
|
|
|
}
|
|
|
|
|
2017-03-21 04:15:50 +03:00
|
|
|
// findPath attempts to find a path from the source node within the
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
// ChannelGraph to the target node that's capable of supporting a payment of
|
2017-03-20 01:15:24 +03:00
|
|
|
// `amt` value. The current approach implemented is modified version of
|
|
|
|
// Dijkstra's algorithm to find a single shortest path between the source node
|
|
|
|
// 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
|
2018-06-04 23:10:05 +03:00
|
|
|
// 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.
|
2019-06-20 13:03:45 +03:00
|
|
|
func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
|
|
|
|
source, target route.Vertex, amt lnwire.MilliSatoshi) (
|
|
|
|
[]*channeldb.ChannelEdgePolicy, error) {
|
2017-10-11 07:39:54 +03:00
|
|
|
|
2019-09-06 09:56:59 +03:00
|
|
|
// Pathfinding can be a significant portion of the total payment
|
|
|
|
// latency, especially on low-powered devices. Log several metrics to
|
|
|
|
// aid in the analysis performance problems in this area.
|
|
|
|
start := time.Now()
|
|
|
|
nodesVisited := 0
|
|
|
|
edgesExpanded := 0
|
|
|
|
defer func() {
|
|
|
|
timeElapsed := time.Since(start)
|
|
|
|
log.Debugf("Pathfinding perf metrics: nodes=%v, edges=%v, "+
|
|
|
|
"time=%v", nodesVisited, edgesExpanded, timeElapsed)
|
|
|
|
}()
|
|
|
|
|
2017-10-11 07:39:54 +03:00
|
|
|
var err error
|
2018-10-25 00:06:12 +03:00
|
|
|
tx := g.tx
|
2017-10-11 07:39:54 +03:00
|
|
|
if tx == nil {
|
2018-10-25 00:06:12 +03:00
|
|
|
tx, err = g.graph.Database().Begin(false)
|
2017-10-11 07:39:54 +03:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
defer tx.Rollback()
|
|
|
|
}
|
2017-03-20 00:15:58 +03:00
|
|
|
|
2019-08-20 19:27:03 +03:00
|
|
|
if r.DestPayloadTLV {
|
|
|
|
// Check if the target has TLV enabled
|
2019-07-31 07:41:58 +03:00
|
|
|
|
2019-08-20 19:27:03 +03:00
|
|
|
targetKey, err := btcec.ParsePubKey(target[:], btcec.S256())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-31 07:41:58 +03:00
|
|
|
}
|
|
|
|
|
2019-08-20 19:27:03 +03:00
|
|
|
targetNode, err := g.graph.FetchLightningNode(targetKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-07-31 07:41:58 +03:00
|
|
|
}
|
|
|
|
|
2019-08-20 19:27:03 +03:00
|
|
|
if targetNode.Features != nil {
|
|
|
|
supportsTLV := targetNode.Features.HasFeature(
|
|
|
|
lnwire.TLVOnionPayloadOptional,
|
|
|
|
)
|
|
|
|
if !supportsTLV {
|
|
|
|
return nil, fmt.Errorf("destination hop doesn't " +
|
|
|
|
"understand new TLV paylods")
|
|
|
|
}
|
|
|
|
}
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
}
|
|
|
|
|
2019-08-23 18:27:02 +03:00
|
|
|
// First we'll initialize an empty heap which'll help us to quickly
|
|
|
|
// locate the next edge we should visit next during our graph
|
|
|
|
// traversal.
|
|
|
|
nodeHeap := newDistanceHeap(estimatedNodeCount)
|
|
|
|
|
|
|
|
// Holds the current best distance for a given node.
|
2019-08-25 04:52:13 +03:00
|
|
|
distance := make(map[route.Vertex]*nodeWithDist, estimatedNodeCount)
|
2019-08-23 18:27:02 +03:00
|
|
|
|
2019-04-05 18:36:11 +03:00
|
|
|
additionalEdgesWithSrc := make(map[route.Vertex][]*edgePolicyWithSource)
|
2018-10-25 00:06:12 +03:00
|
|
|
for vertex, outgoingEdgePolicies := range g.additionalEdges {
|
2018-06-04 23:10:05 +03:00
|
|
|
|
2018-08-10 06:47:56 +03:00
|
|
|
// Build reverse lookup to find incoming edges. Needed because
|
|
|
|
// search is taken place from target to source.
|
2018-06-04 23:10:05 +03:00
|
|
|
for _, outgoingEdgePolicy := range outgoingEdgePolicies {
|
|
|
|
toVertex := outgoingEdgePolicy.Node.PubKeyBytes
|
|
|
|
incomingEdgePolicy := &edgePolicyWithSource{
|
2019-06-19 05:19:37 +03:00
|
|
|
sourceNode: vertex,
|
2018-06-04 23:10:05 +03:00
|
|
|
edge: outgoingEdgePolicy,
|
|
|
|
}
|
|
|
|
|
|
|
|
additionalEdgesWithSrc[toVertex] =
|
|
|
|
append(additionalEdgesWithSrc[toVertex],
|
|
|
|
incomingEdgePolicy)
|
|
|
|
}
|
2018-03-27 07:14:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// We can't always assume that the end destination is publicly
|
2019-08-20 19:27:03 +03:00
|
|
|
// advertised to the network so we'll manually include the target node.
|
2019-11-18 12:19:20 +03:00
|
|
|
// 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.
|
|
|
|
//
|
|
|
|
// Don't record the initial partial path in the distance map and reserve
|
|
|
|
// that key for the source key in the case we route to ourselves.
|
|
|
|
partialPath := &nodeWithDist{
|
2018-06-04 23:10:05 +03:00
|
|
|
dist: 0,
|
2019-03-19 13:45:10 +03:00
|
|
|
weight: 0,
|
2019-06-19 05:19:37 +03:00
|
|
|
node: target,
|
2018-06-04 23:10:05 +03:00
|
|
|
amountToReceive: amt,
|
2019-02-13 12:08:53 +03:00
|
|
|
incomingCltv: 0,
|
2019-03-19 13:45:10 +03:00
|
|
|
probability: 1,
|
2018-03-27 07:14:10 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// processEdge is a helper closure that will be used to make sure edges
|
|
|
|
// satisfy our specific requirements.
|
2019-09-30 14:14:49 +03:00
|
|
|
processEdge := func(fromVertex route.Vertex,
|
2019-08-25 07:24:06 +03:00
|
|
|
edge *channeldb.ChannelEdgePolicy, toNodeDist *nodeWithDist) {
|
2018-03-27 07:14:10 +03:00
|
|
|
|
2019-09-06 09:56:59 +03:00
|
|
|
edgesExpanded++
|
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
// Calculate amount that the candidate node would have to sent
|
|
|
|
// out.
|
|
|
|
amountToSend := toNodeDist.amountToReceive
|
2018-11-29 18:48:17 +03:00
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
// Request the success probability for this edge.
|
|
|
|
edgeProbability := r.ProbabilitySource(
|
2019-08-25 07:24:06 +03:00
|
|
|
fromVertex, toNodeDist.node, amountToSend,
|
2019-03-19 13:45:10 +03:00
|
|
|
)
|
2018-03-27 07:14:10 +03:00
|
|
|
|
2019-08-04 05:01:16 +03:00
|
|
|
log.Trace(newLogClosure(func() string {
|
|
|
|
return fmt.Sprintf("path finding probability: fromnode=%v,"+
|
2019-08-25 07:24:06 +03:00
|
|
|
" tonode=%v, probability=%v", fromVertex, toNodeDist.node,
|
2019-08-04 05:01:16 +03:00
|
|
|
edgeProbability)
|
|
|
|
}))
|
2018-06-04 23:10:05 +03:00
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
// If the probability is zero, there is no point in trying.
|
|
|
|
if edgeProbability == 0 {
|
|
|
|
return
|
|
|
|
}
|
2018-06-04 23:10:05 +03:00
|
|
|
|
2019-06-19 05:19:37 +03:00
|
|
|
// Compute fee that fromVertex is charging. It is based on the
|
2018-06-04 23:10:05 +03:00
|
|
|
// amount that needs to be sent to the next node in the route.
|
|
|
|
//
|
2018-09-06 11:48:46 +03:00
|
|
|
// Source node has no predecessor to pay a fee. Therefore set
|
2018-08-10 06:47:56 +03:00
|
|
|
// fee to zero, because it should not be included in the fee
|
|
|
|
// limit check and edge weight.
|
2018-06-04 23:10:05 +03:00
|
|
|
//
|
2018-08-10 06:47:56 +03:00
|
|
|
// Also determine the time lock delta that will be added to the
|
2019-06-19 05:19:37 +03:00
|
|
|
// route if fromVertex is selected. If fromVertex is the source
|
2018-08-10 06:47:56 +03:00
|
|
|
// node, no additional timelock is required.
|
2018-06-04 23:10:05 +03:00
|
|
|
var fee lnwire.MilliSatoshi
|
|
|
|
var timeLockDelta uint16
|
2019-03-05 18:55:19 +03:00
|
|
|
if fromVertex != source {
|
2019-01-24 23:14:05 +03:00
|
|
|
fee = edge.ComputeFee(amountToSend)
|
2018-06-04 23:10:05 +03:00
|
|
|
timeLockDelta = edge.TimeLockDelta
|
|
|
|
}
|
|
|
|
|
2019-02-13 12:08:53 +03:00
|
|
|
incomingCltv := toNodeDist.incomingCltv +
|
|
|
|
uint32(timeLockDelta)
|
|
|
|
|
2019-10-11 22:46:10 +03:00
|
|
|
// Check that we are within our CLTV limit.
|
|
|
|
if incomingCltv > r.CltvLimit {
|
2019-02-13 12:08:53 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2018-08-10 06:47:56 +03:00
|
|
|
// 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.
|
2018-06-04 23:10:05 +03:00
|
|
|
amountToReceive := amountToSend + fee
|
|
|
|
|
2018-08-10 06:47:56 +03:00
|
|
|
// Check if accumulated fees would exceed fee limit when this
|
|
|
|
// node would be added to the path.
|
2018-06-04 23:10:05 +03:00
|
|
|
totalFee := amountToReceive - amt
|
2019-03-05 13:13:44 +03:00
|
|
|
if totalFee > r.FeeLimit {
|
2018-06-04 23:10:05 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
// Calculate total probability of successfully reaching target
|
|
|
|
// by multiplying the probabilities. Both this edge and the rest
|
|
|
|
// of the route must succeed.
|
|
|
|
probability := toNodeDist.probability * edgeProbability
|
|
|
|
|
2019-05-13 18:00:35 +03:00
|
|
|
// If the probability is below the specified lower bound, we can
|
|
|
|
// abandon this direction. Adding further nodes can only lower
|
|
|
|
// the probability more.
|
2019-06-20 13:03:45 +03:00
|
|
|
if probability < cfg.MinProbability {
|
2019-05-13 18:00:35 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-06-19 05:19:37 +03:00
|
|
|
// By adding fromVertex in the route, there will be an extra
|
2018-06-04 23:10:05 +03:00
|
|
|
// weight composed of the fee that this node will charge and
|
2018-08-10 06:47:56 +03:00
|
|
|
// the amount that will be locked for timeLockDelta blocks in
|
2019-06-19 05:19:37 +03:00
|
|
|
// the HTLC that is handed out to fromVertex.
|
2018-06-04 23:10:05 +03:00
|
|
|
weight := edgeWeight(amountToReceive, fee, timeLockDelta)
|
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
// Compute the tentative weight to this new channel/edge
|
|
|
|
// which is the weight from our toNode to the target node
|
2018-08-10 06:47:56 +03:00
|
|
|
// plus the weight of this edge.
|
2019-03-19 13:45:10 +03:00
|
|
|
tempWeight := toNodeDist.weight + weight
|
|
|
|
|
|
|
|
// Add an extra factor to the weight to take into account the
|
|
|
|
// probability.
|
|
|
|
tempDist := getProbabilityBasedDist(
|
2019-06-20 13:03:45 +03:00
|
|
|
tempWeight, probability,
|
|
|
|
int64(cfg.PaymentAttemptPenalty),
|
2019-03-19 13:45:10 +03:00
|
|
|
)
|
|
|
|
|
2019-12-01 16:55:01 +03:00
|
|
|
// If there is already a best route stored, compare this
|
|
|
|
// candidate route with the best route so far.
|
2019-08-20 19:27:03 +03:00
|
|
|
current, ok := distance[fromVertex]
|
2019-12-01 16:55:01 +03:00
|
|
|
if ok {
|
|
|
|
// If this route is worse than what we already found,
|
|
|
|
// skip this route.
|
|
|
|
if tempDist > current.dist {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the route is equally good and the probability
|
|
|
|
// isn't better, skip this route. It is important to
|
|
|
|
// also return if both cost and probability are equal,
|
|
|
|
// because otherwise the algorithm could run into an
|
|
|
|
// endless loop.
|
|
|
|
probNotBetter := probability <= current.probability
|
|
|
|
if tempDist == current.dist && probNotBetter {
|
|
|
|
return
|
|
|
|
}
|
2018-06-04 23:10:05 +03:00
|
|
|
}
|
2018-03-27 07:14:10 +03:00
|
|
|
|
2019-02-13 13:21:10 +03:00
|
|
|
// Every edge should have a positive time lock delta. If we
|
|
|
|
// encounter a zero delta, log a warning line.
|
2018-06-04 23:10:05 +03:00
|
|
|
if edge.TimeLockDelta == 0 {
|
2019-02-13 13:21:10 +03:00
|
|
|
log.Warnf("Channel %v has zero cltv delta",
|
|
|
|
edge.ChannelID)
|
2018-03-27 07:14:10 +03:00
|
|
|
}
|
2018-06-04 23:10:05 +03:00
|
|
|
|
|
|
|
// All conditions are met and this new tentative distance is
|
|
|
|
// better than the current best known distance to this node.
|
2018-08-10 06:47:56 +03:00
|
|
|
// The new better distance is recorded, and also our "next hop"
|
|
|
|
// map is populated with this edge.
|
2019-08-25 04:52:13 +03:00
|
|
|
withDist := &nodeWithDist{
|
2018-06-04 23:10:05 +03:00
|
|
|
dist: tempDist,
|
2019-03-19 13:45:10 +03:00
|
|
|
weight: tempWeight,
|
2019-06-19 05:19:37 +03:00
|
|
|
node: fromVertex,
|
2018-06-04 23:10:05 +03:00
|
|
|
amountToReceive: amountToReceive,
|
2019-02-13 12:08:53 +03:00
|
|
|
incomingCltv: incomingCltv,
|
2019-03-19 13:45:10 +03:00
|
|
|
probability: probability,
|
2019-08-25 04:52:13 +03:00
|
|
|
nextHop: edge,
|
2018-06-04 23:10:05 +03:00
|
|
|
}
|
2019-08-25 02:02:43 +03:00
|
|
|
distance[fromVertex] = withDist
|
2018-06-04 23:10:05 +03:00
|
|
|
|
2019-08-25 02:02:43 +03:00
|
|
|
// Either push withDist onto the heap if the node
|
2019-06-19 05:26:09 +03:00
|
|
|
// represented by fromVertex is not already on the heap OR adjust
|
|
|
|
// its position within the heap via heap.Fix.
|
2019-08-25 02:02:43 +03:00
|
|
|
nodeHeap.PushOrFix(withDist)
|
2018-03-27 07:14:10 +03:00
|
|
|
}
|
|
|
|
|
2017-10-11 07:39:54 +03:00
|
|
|
// TODO(roasbeef): also add path caching
|
|
|
|
// * similar to route caching, but doesn't factor in the amount
|
|
|
|
|
2019-11-18 12:19:20 +03:00
|
|
|
routeToSelf := source == target
|
2019-11-18 11:24:09 +03:00
|
|
|
for {
|
2019-09-06 09:56:59 +03:00
|
|
|
nodesVisited++
|
|
|
|
|
2019-06-19 05:19:37 +03:00
|
|
|
pivot := partialPath.node
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
|
2019-09-30 14:14:49 +03:00
|
|
|
// Create unified policies for all incoming connections.
|
|
|
|
u := newUnifiedPolicies(source, pivot, r.OutgoingChannelID)
|
2018-06-04 23:10:05 +03:00
|
|
|
|
2019-09-30 14:14:49 +03:00
|
|
|
err := u.addGraphPolicies(g.graph, tx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2017-03-09 01:24:59 +03:00
|
|
|
|
2019-09-30 14:14:49 +03:00
|
|
|
for _, reverseEdge := range additionalEdgesWithSrc[pivot] {
|
|
|
|
u.addPolicy(reverseEdge.sourceNode, reverseEdge.edge, 0)
|
|
|
|
}
|
2018-05-08 07:04:31 +03:00
|
|
|
|
2019-09-30 14:14:49 +03:00
|
|
|
amtToSend := partialPath.amountToReceive
|
|
|
|
|
|
|
|
// Expand all connections using the optimal policy for each
|
|
|
|
// connection.
|
|
|
|
for fromNode, unifiedPolicy := range u.policies {
|
2019-11-18 12:19:20 +03:00
|
|
|
// The target node is not recorded in the distance map.
|
|
|
|
// Therefore we need to have this check to prevent
|
|
|
|
// creating a cycle. Only when we intend to route to
|
|
|
|
// self, we allow this cycle to form. In that case we'll
|
|
|
|
// also break out of the search loop below.
|
|
|
|
if !routeToSelf && fromNode == target {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-11-18 13:54:15 +03:00
|
|
|
// Apply last hop restriction if set.
|
|
|
|
if r.LastHop != nil &&
|
|
|
|
pivot == target && fromNode != *r.LastHop {
|
|
|
|
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2019-09-30 14:14:49 +03:00
|
|
|
policy := unifiedPolicy.getPolicy(
|
|
|
|
amtToSend, g.bandwidthHints,
|
|
|
|
)
|
|
|
|
|
|
|
|
if policy == nil {
|
|
|
|
continue
|
2018-06-04 23:10:05 +03:00
|
|
|
}
|
|
|
|
|
2018-08-10 06:47:56 +03:00
|
|
|
// Check if this candidate node is better than what we
|
|
|
|
// already have.
|
2019-09-30 14:14:49 +03:00
|
|
|
processEdge(fromNode, policy, partialPath)
|
2018-03-27 07:14:10 +03:00
|
|
|
}
|
2019-11-18 11:24:09 +03:00
|
|
|
|
|
|
|
if nodeHeap.Len() == 0 {
|
|
|
|
break
|
|
|
|
}
|
2019-11-18 12:00:16 +03:00
|
|
|
|
|
|
|
// Fetch the node within the smallest distance from our source
|
|
|
|
// from the heap.
|
|
|
|
partialPath = heap.Pop(&nodeHeap).(*nodeWithDist)
|
|
|
|
|
|
|
|
// 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 partialPath.node == source {
|
|
|
|
break
|
|
|
|
}
|
routing: rewrite package to conform to BOLT07 and factor in fees+timelocks
This commit overhauls the routing package significantly to simplify the
code, conform to the rest of the coding style within the package, and
observe the new authenticated gossiping scheme outlined in BOLT07.
As a major step towards a more realistic path finding algorithm, fees
are properly calculated and observed during path finding. If a path has
sufficient capacity _before_ fees are applied, but afterwards the
finalized route would exceed the capacity of a single link, the route
is marked as invalid.
Currently a naive weighting algorithm is used which only factors in the
time-lock delta at each hop, thereby optimizing for the lowest time
lock. Fee calculation also isn’t finalized since we aren’t yet using
milli-satoshi throughout the daemon. The final TODO item within the PR
is to properly perform a multi-path search and rank the results based
on a summation heuristic rather than just return the first (out of
many) route found.
On the server side, once nodes are initially connected to the daemon,
our routing table will be synced with the peer’s using a naive “just
send everything scheme” to hold us over until I spec out some a
efficient graph reconciliation protocol. Additionally, the routing
table is now pruned by the channel router itself once new blocks arrive
rather than depending on peers to tell us when a channel flaps or is
closed.
Finally, the validation of peer announcements aren’t yet fully
implemented as they’ll be implemented within the pending discovery
package that was blocking on the completion of this package. Most off
the routing message processing will be moved out of this package and
into the discovery package where full validation will be carried out.
2016-12-27 08:20:26 +03:00
|
|
|
}
|
|
|
|
|
2019-08-25 04:52:13 +03:00
|
|
|
// Use the distance map to unravel the forward path from source to
|
2018-10-25 00:06:12 +03:00
|
|
|
// target.
|
2019-08-23 18:27:02 +03:00
|
|
|
var pathEdges []*channeldb.ChannelEdgePolicy
|
2019-03-05 18:55:19 +03:00
|
|
|
currentNode := source
|
2019-11-18 11:24:09 +03:00
|
|
|
for {
|
2018-06-04 23:10:05 +03:00
|
|
|
// Determine the next hop forward using the next map.
|
2019-08-25 04:52:13 +03:00
|
|
|
currentNodeWithDist, ok := distance[currentNode]
|
|
|
|
if !ok {
|
|
|
|
// If the node doesnt have a next hop it means we didn't find a path.
|
|
|
|
return nil, newErrf(ErrNoPathFound, "unable to find a "+
|
|
|
|
"path to destination")
|
|
|
|
}
|
2018-06-04 23:10:05 +03:00
|
|
|
|
|
|
|
// Add the next hop to the list of path edges.
|
2019-08-25 04:52:13 +03:00
|
|
|
pathEdges = append(pathEdges, currentNodeWithDist.nextHop)
|
2018-06-04 23:10:05 +03:00
|
|
|
|
|
|
|
// Advance current node.
|
2019-08-25 04:52:13 +03:00
|
|
|
currentNode = currentNodeWithDist.nextHop.Node.PubKeyBytes
|
2019-11-18 11:24:09 +03:00
|
|
|
|
2019-11-18 12:19:20 +03:00
|
|
|
// Check stop condition at the end of this loop. This prevents
|
|
|
|
// breaking out too soon for self-payments that have target set
|
|
|
|
// to source.
|
2019-11-18 11:24:09 +03:00
|
|
|
if currentNode == target {
|
|
|
|
break
|
|
|
|
}
|
2017-03-20 01:15:24 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// The route is invalid if it spans more than 20 hops. The current
|
|
|
|
// Sphinx (onion routing) implementation can only encode up to 20 hops
|
|
|
|
// as the entire packet is fixed size. If this route is more than 20
|
|
|
|
// hops, then it's invalid.
|
2017-03-21 04:15:50 +03:00
|
|
|
numEdges := len(pathEdges)
|
|
|
|
if numEdges > HopLimit {
|
2017-03-19 21:40:25 +03:00
|
|
|
return nil, newErr(ErrMaxHopsExceeded, "potential path has "+
|
|
|
|
"too many hops")
|
2017-03-20 01:15:24 +03:00
|
|
|
}
|
|
|
|
|
2019-03-19 13:45:10 +03:00
|
|
|
log.Debugf("Found route: probability=%v, hops=%v, fee=%v\n",
|
|
|
|
distance[source].probability, numEdges,
|
|
|
|
distance[source].amountToReceive-amt)
|
|
|
|
|
2017-03-20 01:15:24 +03:00
|
|
|
return pathEdges, nil
|
|
|
|
}
|
2019-03-19 13:45:10 +03:00
|
|
|
|
|
|
|
// getProbabilityBasedDist converts a weight into a distance that takes into
|
|
|
|
// account the success probability and the (virtual) cost of a failed payment
|
|
|
|
// attempt.
|
|
|
|
//
|
|
|
|
// Derivation:
|
|
|
|
//
|
|
|
|
// Suppose there are two routes A and B with fees Fa and Fb and success
|
|
|
|
// probabilities Pa and Pb.
|
|
|
|
//
|
|
|
|
// Is the expected cost of trying route A first and then B lower than trying the
|
|
|
|
// other way around?
|
|
|
|
//
|
|
|
|
// The expected cost of A-then-B is: Pa*Fa + (1-Pa)*Pb*(c+Fb)
|
|
|
|
//
|
|
|
|
// The expected cost of B-then-A is: Pb*Fb + (1-Pb)*Pa*(c+Fa)
|
|
|
|
//
|
|
|
|
// In these equations, the term representing the case where both A and B fail is
|
|
|
|
// left out because its value would be the same in both cases.
|
|
|
|
//
|
|
|
|
// Pa*Fa + (1-Pa)*Pb*(c+Fb) < Pb*Fb + (1-Pb)*Pa*(c+Fa)
|
|
|
|
//
|
|
|
|
// Pa*Fa + Pb*c + Pb*Fb - Pa*Pb*c - Pa*Pb*Fb < Pb*Fb + Pa*c + Pa*Fa - Pa*Pb*c - Pa*Pb*Fa
|
|
|
|
//
|
|
|
|
// Removing terms that cancel out:
|
|
|
|
// Pb*c - Pa*Pb*Fb < Pa*c - Pa*Pb*Fa
|
|
|
|
//
|
|
|
|
// Divide by Pa*Pb:
|
|
|
|
// c/Pa - Fb < c/Pb - Fa
|
|
|
|
//
|
|
|
|
// Move terms around:
|
|
|
|
// Fa + c/Pa < Fb + c/Pb
|
|
|
|
//
|
|
|
|
// So the value of F + c/P can be used to compare routes.
|
|
|
|
func getProbabilityBasedDist(weight int64, probability float64, penalty int64) int64 {
|
|
|
|
// Clamp probability to prevent overflow.
|
|
|
|
const minProbability = 0.00001
|
|
|
|
|
|
|
|
if probability < minProbability {
|
|
|
|
return infinity
|
|
|
|
}
|
|
|
|
|
|
|
|
return weight + int64(float64(penalty)/probability)
|
|
|
|
}
|