Merge pull request #1888 from joostjager/routestruct
lnrpc+routing: fix unmarshallRoute and simplify route structure
This commit is contained in:
commit
fbd91feace
@ -78,9 +78,9 @@ type path struct {
|
|||||||
// that the path requires.
|
// that the path requires.
|
||||||
dist int
|
dist int
|
||||||
|
|
||||||
// hops is an ordered list of edge that comprises a potential payment
|
// hops is an ordered list of edges that comprises a potential payment
|
||||||
// path.
|
// path.
|
||||||
hops []*ChannelHop
|
hops []*channeldb.ChannelEdgePolicy
|
||||||
}
|
}
|
||||||
|
|
||||||
// pathHeap is a min-heap that stores potential paths to be considered within
|
// pathHeap is a min-heap that stores potential paths to be considered within
|
||||||
|
@ -9,7 +9,6 @@ import (
|
|||||||
"container/heap"
|
"container/heap"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/btcec"
|
"github.com/btcsuite/btcd/btcec"
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/coreos/bbolt"
|
"github.com/coreos/bbolt"
|
||||||
"github.com/lightningnetwork/lightning-onion"
|
"github.com/lightningnetwork/lightning-onion"
|
||||||
"github.com/lightningnetwork/lnd/channeldb"
|
"github.com/lightningnetwork/lnd/channeldb"
|
||||||
@ -62,30 +61,6 @@ type HopHint struct {
|
|||||||
CLTVExpiryDelta uint16
|
CLTVExpiryDelta uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChannelHop describes the channel through which an intermediate or final
|
|
||||||
// hop can be reached. This struct contains the relevant routing policy of
|
|
||||||
// the particular edge (which is a property of the source node of the channel
|
|
||||||
// edge), as well as the total capacity. It also includes the origin chain of
|
|
||||||
// the channel itself.
|
|
||||||
type ChannelHop struct {
|
|
||||||
// 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
|
|
||||||
// blockchain, or the very first block in the chain.
|
|
||||||
//
|
|
||||||
// TODO(roasbeef): store chain within edge info/policy in database.
|
|
||||||
Chain chainhash.Hash
|
|
||||||
|
|
||||||
*channeldb.ChannelEdgePolicy
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hop represents an intermediate or final node of the route. This naming
|
// Hop represents an intermediate or final node of the route. This naming
|
||||||
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
|
||||||
// The struct houses the channel along which this hop can be reached and
|
// The struct houses the channel along which this hop can be reached and
|
||||||
@ -93,9 +68,13 @@ type ChannelHop struct {
|
|||||||
// next hop. It is also used to encode the per-hop payload included within
|
// next hop. It is also used to encode the per-hop payload included within
|
||||||
// the Sphinx packet.
|
// the Sphinx packet.
|
||||||
type Hop struct {
|
type Hop struct {
|
||||||
// Channel is the active payment channel edge along which the packet
|
// PubKeyBytes is the raw bytes of the public key of the target node.
|
||||||
// travels to reach this hop. This is the _incoming_ channel to this hop.
|
PubKeyBytes Vertex
|
||||||
Channel *ChannelHop
|
|
||||||
|
// ChannelID is the unique channel ID for the channel. The first 3
|
||||||
|
// bytes are the block height, the next 3 the index within the block,
|
||||||
|
// and the last 2 bytes are the output index for the channel.
|
||||||
|
ChannelID uint64
|
||||||
|
|
||||||
// OutgoingTimeLock is the timelock value that should be used when
|
// OutgoingTimeLock is the timelock value that should be used when
|
||||||
// crafting the _outgoing_ HTLC from this hop.
|
// crafting the _outgoing_ HTLC from this hop.
|
||||||
@ -105,11 +84,6 @@ type Hop struct {
|
|||||||
// hop. This value is less than the value that the incoming HTLC
|
// hop. This value is less than the value that the incoming HTLC
|
||||||
// carries as a fee will be subtracted by the hop.
|
// carries as a fee will be subtracted by the hop.
|
||||||
AmtToForward lnwire.MilliSatoshi
|
AmtToForward lnwire.MilliSatoshi
|
||||||
|
|
||||||
// Fee is the total fee that this hop will subtract from the incoming
|
|
||||||
// payment, this difference nets the hop fees for forwarding the
|
|
||||||
// payment.
|
|
||||||
Fee lnwire.MilliSatoshi
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// edgePolicyWithSource is a helper struct to keep track of the source node
|
// edgePolicyWithSource is a helper struct to keep track of the source node
|
||||||
@ -131,7 +105,7 @@ func computeFee(amt lnwire.MilliSatoshi,
|
|||||||
|
|
||||||
// isSamePath returns true if path1 and path2 travel through the exact same
|
// isSamePath returns true if path1 and path2 travel through the exact same
|
||||||
// edges, and false otherwise.
|
// edges, and false otherwise.
|
||||||
func isSamePath(path1, path2 []*ChannelHop) bool {
|
func isSamePath(path1, path2 []*channeldb.ChannelEdgePolicy) bool {
|
||||||
if len(path1) != len(path2) {
|
if len(path1) != len(path2) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -188,25 +162,38 @@ type Route struct {
|
|||||||
// nextHop maps a node, to the next channel that it will pass the HTLC
|
// nextHop maps a node, to the next channel that it will pass the HTLC
|
||||||
// off to. With this map, we can easily look up the next outgoing
|
// off to. With this map, we can easily look up the next outgoing
|
||||||
// channel or node for pruning purposes.
|
// channel or node for pruning purposes.
|
||||||
nextHopMap map[Vertex]*ChannelHop
|
nextHopMap map[Vertex]*Hop
|
||||||
|
|
||||||
// prevHop maps a node, to the channel that was directly before it
|
// prevHop maps a node, to the channel that was directly before it
|
||||||
// within the route. With this map, we can easily look up the previous
|
// within the route. With this map, we can easily look up the previous
|
||||||
// channel or node for pruning purposes.
|
// channel or node for pruning purposes.
|
||||||
prevHopMap map[Vertex]*ChannelHop
|
prevHopMap map[Vertex]*Hop
|
||||||
|
}
|
||||||
|
|
||||||
|
// HopFee returns the fee charged by the route hop indicated by hopIndex.
|
||||||
|
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
|
||||||
|
var incomingAmt lnwire.MilliSatoshi
|
||||||
|
if hopIndex == 0 {
|
||||||
|
incomingAmt = r.TotalAmount
|
||||||
|
} else {
|
||||||
|
incomingAmt = r.Hops[hopIndex-1].AmtToForward
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fee is calculated as difference between incoming and outgoing amount.
|
||||||
|
return incomingAmt - r.Hops[hopIndex].AmtToForward
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextHopVertex returns the next hop (by Vertex) after the target node. If the
|
// nextHopVertex returns the next hop (by Vertex) after the target node. If the
|
||||||
// target node is not found in the route, then false is returned.
|
// target node is not found in the route, then false is returned.
|
||||||
func (r *Route) nextHopVertex(n *btcec.PublicKey) (Vertex, bool) {
|
func (r *Route) nextHopVertex(n *btcec.PublicKey) (Vertex, bool) {
|
||||||
hop, ok := r.nextHopMap[NewVertex(n)]
|
hop, ok := r.nextHopMap[NewVertex(n)]
|
||||||
return Vertex(hop.Node.PubKeyBytes), ok
|
return Vertex(hop.PubKeyBytes), ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// nextHopChannel returns the uint64 channel ID of the next hop after the
|
// nextHopChannel returns the uint64 channel ID of the next hop after the
|
||||||
// target node. If the target node is not found in the route, then false is
|
// target node. If the target node is not found in the route, then false is
|
||||||
// returned.
|
// returned.
|
||||||
func (r *Route) nextHopChannel(n *btcec.PublicKey) (*ChannelHop, bool) {
|
func (r *Route) nextHopChannel(n *btcec.PublicKey) (*Hop, bool) {
|
||||||
hop, ok := r.nextHopMap[NewVertex(n)]
|
hop, ok := r.nextHopMap[NewVertex(n)]
|
||||||
return hop, ok
|
return hop, ok
|
||||||
}
|
}
|
||||||
@ -214,7 +201,7 @@ func (r *Route) nextHopChannel(n *btcec.PublicKey) (*ChannelHop, bool) {
|
|||||||
// prevHopChannel returns the uint64 channel ID of the before hop after the
|
// prevHopChannel returns the uint64 channel ID of the before hop after the
|
||||||
// target node. If the target node is not found in the route, then false is
|
// target node. If the target node is not found in the route, then false is
|
||||||
// returned.
|
// returned.
|
||||||
func (r *Route) prevHopChannel(n *btcec.PublicKey) (*ChannelHop, bool) {
|
func (r *Route) prevHopChannel(n *btcec.PublicKey) (*Hop, bool) {
|
||||||
hop, ok := r.prevHopMap[NewVertex(n)]
|
hop, ok := r.prevHopMap[NewVertex(n)]
|
||||||
return hop, ok
|
return hop, ok
|
||||||
}
|
}
|
||||||
@ -258,7 +245,7 @@ func (r *Route) ToHopPayloads() []sphinx.HopData {
|
|||||||
// If we aren't on the last hop, then we set the "next address"
|
// If we aren't on the last hop, then we set the "next address"
|
||||||
// field to be the channel that directly follows it.
|
// field to be the channel that directly follows it.
|
||||||
if i != len(r.Hops)-1 {
|
if i != len(r.Hops)-1 {
|
||||||
nextHop = r.Hops[i+1].Channel.ChannelID
|
nextHop = r.Hops[i+1].ChannelID
|
||||||
}
|
}
|
||||||
|
|
||||||
binary.BigEndian.PutUint64(hopPayloads[i].NextAddress[:],
|
binary.BigEndian.PutUint64(hopPayloads[i].NextAddress[:],
|
||||||
@ -276,44 +263,20 @@ func (r *Route) ToHopPayloads() []sphinx.HopData {
|
|||||||
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
|
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
|
||||||
// the source to the target node of the path finding attempt.
|
// the source to the target node of the path finding attempt.
|
||||||
func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||||
pathEdges []*ChannelHop, currentHeight uint32,
|
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
|
||||||
finalCLTVDelta uint16) (*Route, error) {
|
finalCLTVDelta uint16) (*Route, error) {
|
||||||
|
|
||||||
// First, we'll create a new empty route with enough hops to match the
|
var hops []*Hop
|
||||||
// amount of path edges. We set the TotalTimeLock to the current block
|
|
||||||
// height, as this is the basis that all of the time locks will be
|
|
||||||
// calculated from.
|
|
||||||
route := &Route{
|
|
||||||
Hops: make([]*Hop, len(pathEdges)),
|
|
||||||
TotalTimeLock: currentHeight,
|
|
||||||
nodeIndex: make(map[Vertex]struct{}),
|
|
||||||
chanIndex: make(map[uint64]struct{}),
|
|
||||||
nextHopMap: make(map[Vertex]*ChannelHop),
|
|
||||||
prevHopMap: make(map[Vertex]*ChannelHop),
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'll populate the next hop map for the _source_ node with the
|
totalTimeLock := currentHeight
|
||||||
// information for the first hop so the mapping is sound.
|
|
||||||
route.nextHopMap[sourceVertex] = pathEdges[0]
|
|
||||||
|
|
||||||
pathLength := len(pathEdges)
|
pathLength := len(pathEdges)
|
||||||
|
|
||||||
|
var nextIncomingAmount lnwire.MilliSatoshi
|
||||||
|
|
||||||
for i := pathLength - 1; i >= 0; i-- {
|
for i := pathLength - 1; i >= 0; i-- {
|
||||||
edge := pathEdges[i]
|
edge := pathEdges[i]
|
||||||
|
|
||||||
// First, we'll update both the node and channel index, to
|
|
||||||
// indicate that this Vertex, and outgoing channel link are
|
|
||||||
// present within this route.
|
|
||||||
v := Vertex(edge.Node.PubKeyBytes)
|
|
||||||
route.nodeIndex[v] = struct{}{}
|
|
||||||
route.chanIndex[edge.ChannelID] = struct{}{}
|
|
||||||
|
|
||||||
// If this isn't a direct payment, and this isn't the edge to
|
|
||||||
// the last hop in the route, then we'll also populate the
|
|
||||||
// nextHop map to allow easy route traversal by callers.
|
|
||||||
if len(pathEdges) > 1 && i != len(pathEdges)-1 {
|
|
||||||
route.nextHopMap[v] = route.Hops[i+1].Channel
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now we'll start to calculate the items within the per-hop
|
// Now we'll start to calculate the items within the per-hop
|
||||||
// payload for the hop this edge is leading to. This hop will
|
// payload for the hop this edge is leading to. This hop will
|
||||||
// be called the 'current hop'.
|
// be called the 'current hop'.
|
||||||
@ -330,97 +293,112 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
|||||||
// If the current hop isn't the last hop, then add enough funds
|
// If the current hop isn't the last hop, then add enough funds
|
||||||
// to pay for transit over the next link.
|
// to pay for transit over the next link.
|
||||||
if i != len(pathEdges)-1 {
|
if i != len(pathEdges)-1 {
|
||||||
// We'll grab the per-hop payload of the next hop (the
|
|
||||||
// hop _after_ the hop this edge leads to) in the
|
|
||||||
// route so we can calculate fees properly.
|
|
||||||
nextHop := route.Hops[i+1]
|
|
||||||
|
|
||||||
// The amount that the current hop needs to forward is
|
// The amount that the current hop needs to forward is
|
||||||
// based on how much the next hop forwards plus the fee
|
// equal to the incoming amount of the next hop.
|
||||||
// that needs to be paid to the next hop.
|
amtToForward = nextIncomingAmount
|
||||||
amtToForward = nextHop.AmtToForward + nextHop.Fee
|
|
||||||
|
|
||||||
// The fee that needs to be paid to the current hop is
|
// The fee that needs to be paid to the current hop is
|
||||||
// based on the amount that this hop needs to forward
|
// based on the amount that this hop needs to forward
|
||||||
// and its policy for the outgoing channel. This policy
|
// and its policy for the outgoing channel. This policy
|
||||||
// is stored as part of the incoming channel of
|
// is stored as part of the incoming channel of
|
||||||
// the next hop.
|
// the next hop.
|
||||||
fee = computeFee(amtToForward, nextHop.Channel.ChannelEdgePolicy)
|
fee = computeFee(amtToForward, pathEdges[i+1])
|
||||||
}
|
|
||||||
|
|
||||||
// Now we create the hop struct for the current hop.
|
|
||||||
currentHop := &Hop{
|
|
||||||
Channel: edge,
|
|
||||||
AmtToForward: amtToForward,
|
|
||||||
Fee: fee,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Accumulate all fees.
|
|
||||||
route.TotalFees += currentHop.Fee
|
|
||||||
|
|
||||||
// Invalidate this route if its total fees exceed our fee limit.
|
|
||||||
if route.TotalFees > feeLimit {
|
|
||||||
err := fmt.Sprintf("total route fees exceeded fee "+
|
|
||||||
"limit of %v", feeLimit)
|
|
||||||
return nil, newErrf(ErrFeeLimitExceeded, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// As a sanity check, we ensure that the incoming channel has
|
|
||||||
// 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 > currentHop.Channel.Bandwidth {
|
|
||||||
|
|
||||||
err := fmt.Sprintf("channel graph has insufficient "+
|
|
||||||
"capacity for the payment: need %v, have %v",
|
|
||||||
currentHop.AmtToForward+fee,
|
|
||||||
currentHop.Channel.Bandwidth)
|
|
||||||
|
|
||||||
return nil, newErrf(ErrInsufficientCapacity, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this is the last hop, then for verification purposes, the
|
// If this is the last hop, then for verification purposes, the
|
||||||
// value of the outgoing time-lock should be _exactly_ the
|
// value of the outgoing time-lock should be _exactly_ the
|
||||||
// absolute time out they'd expect in the HTLC.
|
// absolute time out they'd expect in the HTLC.
|
||||||
|
var outgoingTimeLock uint32
|
||||||
if i == len(pathEdges)-1 {
|
if i == len(pathEdges)-1 {
|
||||||
// As this is the last hop, we'll use the specified
|
// As this is the last hop, we'll use the specified
|
||||||
// final CLTV delta value instead of the value from the
|
// final CLTV delta value instead of the value from the
|
||||||
// last link in the route.
|
// last link in the route.
|
||||||
route.TotalTimeLock += uint32(finalCLTVDelta)
|
totalTimeLock += uint32(finalCLTVDelta)
|
||||||
|
|
||||||
currentHop.OutgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
outgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||||
} else {
|
} else {
|
||||||
// Next, increment the total timelock of the entire
|
// Next, increment the total timelock of the entire
|
||||||
// route such that each hops time lock increases as we
|
// route such that each hops time lock increases as we
|
||||||
// walk backwards in the route, using the delta of the
|
// walk backwards in the route, using the delta of the
|
||||||
// previous hop.
|
// previous hop.
|
||||||
delta := uint32(pathEdges[i+1].TimeLockDelta)
|
delta := uint32(pathEdges[i+1].TimeLockDelta)
|
||||||
route.TotalTimeLock += delta
|
totalTimeLock += delta
|
||||||
|
|
||||||
// Otherwise, the value of the outgoing time-lock will
|
// Otherwise, the value of the outgoing time-lock will
|
||||||
// be the value of the time-lock for the _outgoing_
|
// be the value of the time-lock for the _outgoing_
|
||||||
// HTLC, so we factor in their specified grace period
|
// HTLC, so we factor in their specified grace period
|
||||||
// (time lock delta).
|
// (time lock delta).
|
||||||
currentHop.OutgoingTimeLock = route.TotalTimeLock - delta
|
outgoingTimeLock = totalTimeLock - delta
|
||||||
}
|
}
|
||||||
|
|
||||||
route.Hops[i] = currentHop
|
// Now we create the hop struct for the current hop.
|
||||||
|
currentHop := &Hop{
|
||||||
|
PubKeyBytes: Vertex(edge.Node.PubKeyBytes),
|
||||||
|
ChannelID: edge.ChannelID,
|
||||||
|
AmtToForward: amtToForward,
|
||||||
|
OutgoingTimeLock: outgoingTimeLock,
|
||||||
}
|
}
|
||||||
|
|
||||||
// We'll then make a second run through our route in order to set up
|
hops = append([]*Hop{currentHop}, hops...)
|
||||||
// our prev hop mapping.
|
|
||||||
for _, hop := range route.Hops {
|
nextIncomingAmount = amtToForward + fee
|
||||||
vertex := Vertex(hop.Channel.Node.PubKeyBytes)
|
|
||||||
route.prevHopMap[vertex] = hop.Channel
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// The total amount required for this route will be the value
|
// With the base routing data expressed as hops, build the full route
|
||||||
// that the first hop needs to forward plus the fee that
|
// structure.
|
||||||
// the first hop charges for this. Note that the sender of the
|
newRoute := NewRouteFromHops(nextIncomingAmount, totalTimeLock,
|
||||||
// payment is not a hop in the route.
|
sourceVertex, hops)
|
||||||
route.TotalAmount = route.Hops[0].AmtToForward + route.Hops[0].Fee
|
|
||||||
|
|
||||||
return route, nil
|
// Invalidate this route if its total fees exceed our fee limit.
|
||||||
|
if newRoute.TotalFees > feeLimit {
|
||||||
|
err := fmt.Sprintf("total route fees exceeded fee "+
|
||||||
|
"limit of %v", feeLimit)
|
||||||
|
return nil, newErrf(ErrFeeLimitExceeded, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newRoute, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouteFromHops creates a new Route structure from the minimally
|
||||||
|
// required information to perform the payment. It infers fee amounts and
|
||||||
|
// populates the node, chan and prev/next hop maps.
|
||||||
|
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
|
||||||
|
sourceVertex Vertex, hops []*Hop) *Route {
|
||||||
|
|
||||||
|
// First, we'll create a route struct and populate it with the fields
|
||||||
|
// for which the values are provided as arguments of this function.
|
||||||
|
// TotalFees is determined based on the difference between the amount
|
||||||
|
// that is send from the source and the final amount that is received by
|
||||||
|
// the destination.
|
||||||
|
route := &Route{
|
||||||
|
Hops: hops,
|
||||||
|
TotalTimeLock: timeLock,
|
||||||
|
TotalAmount: amtToSend,
|
||||||
|
TotalFees: amtToSend - hops[len(hops)-1].AmtToForward,
|
||||||
|
nodeIndex: make(map[Vertex]struct{}),
|
||||||
|
chanIndex: make(map[uint64]struct{}),
|
||||||
|
nextHopMap: make(map[Vertex]*Hop),
|
||||||
|
prevHopMap: make(map[Vertex]*Hop),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then we'll update the node and channel index, to indicate that this
|
||||||
|
// Vertex and incoming channel link are present within this route. Also,
|
||||||
|
// the prev and next hop maps will be populated.
|
||||||
|
prevNode := sourceVertex
|
||||||
|
for i := 0; i < len(hops); i++ {
|
||||||
|
hop := hops[i]
|
||||||
|
|
||||||
|
v := Vertex(hop.PubKeyBytes)
|
||||||
|
|
||||||
|
route.nodeIndex[v] = struct{}{}
|
||||||
|
route.chanIndex[hop.ChannelID] = struct{}{}
|
||||||
|
route.prevHopMap[v] = hop
|
||||||
|
route.nextHopMap[prevNode] = hop
|
||||||
|
|
||||||
|
prevNode = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return route
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vertex is a simple alias for the serialization of a compressed Bitcoin
|
// Vertex is a simple alias for the serialization of a compressed Bitcoin
|
||||||
@ -474,7 +452,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
|
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
|
||||||
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
|
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
|
||||||
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi,
|
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi,
|
||||||
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*ChannelHop, error) {
|
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
if tx == nil {
|
if tx == nil {
|
||||||
@ -552,7 +530,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
// We'll use this map as a series of "next" hop pointers. So to get
|
// 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
|
// from `Vertex` to the target node, we'll take the edge that it's
|
||||||
// mapped to within `next`.
|
// mapped to within `next`.
|
||||||
next := make(map[Vertex]*ChannelHop)
|
next := make(map[Vertex]*channeldb.ChannelEdgePolicy)
|
||||||
|
|
||||||
// processEdge is a helper closure that will be used to make sure edges
|
// processEdge is a helper closure that will be used to make sure edges
|
||||||
// satisfy our specific requirements.
|
// satisfy our specific requirements.
|
||||||
@ -663,10 +641,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
fee: fee,
|
fee: fee,
|
||||||
}
|
}
|
||||||
|
|
||||||
next[fromVertex] = &ChannelHop{
|
next[fromVertex] = edge
|
||||||
ChannelEdgePolicy: edge,
|
|
||||||
Bandwidth: bandwidth,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add this new node to our heap as we'd like to further
|
// Add this new node to our heap as we'd like to further
|
||||||
// explore backwards through this edge.
|
// explore backwards through this edge.
|
||||||
@ -761,7 +736,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Use the nextHop map to unravel the forward path from source to target.
|
// Use the nextHop map to unravel the forward path from source to target.
|
||||||
pathEdges := make([]*ChannelHop, 0, len(next))
|
pathEdges := make([]*channeldb.ChannelEdgePolicy, 0, len(next))
|
||||||
currentNode := sourceVertex
|
currentNode := sourceVertex
|
||||||
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles
|
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles
|
||||||
// Determine the next hop forward using the next map.
|
// Determine the next hop forward using the next map.
|
||||||
@ -801,7 +776,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||||
source *channeldb.LightningNode, target *btcec.PublicKey,
|
source *channeldb.LightningNode, target *btcec.PublicKey,
|
||||||
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi, numPaths uint32,
|
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi, numPaths uint32,
|
||||||
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*ChannelHop, error) {
|
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*channeldb.ChannelEdgePolicy, error) {
|
||||||
|
|
||||||
ignoredEdges := make(map[uint64]struct{})
|
ignoredEdges := make(map[uint64]struct{})
|
||||||
ignoredVertexes := make(map[Vertex]struct{})
|
ignoredVertexes := make(map[Vertex]struct{})
|
||||||
@ -809,7 +784,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
// TODO(roasbeef): modifying ordering within heap to eliminate final
|
// TODO(roasbeef): modifying ordering within heap to eliminate final
|
||||||
// sorting step?
|
// sorting step?
|
||||||
var (
|
var (
|
||||||
shortestPaths [][]*ChannelHop
|
shortestPaths [][]*channeldb.ChannelEdgePolicy
|
||||||
candidatePaths pathHeap
|
candidatePaths pathHeap
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -828,11 +803,9 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
// Manually insert a "self" edge emanating from ourselves. This
|
// Manually insert a "self" edge emanating from ourselves. This
|
||||||
// self-edge is required in order for the path finding algorithm to
|
// self-edge is required in order for the path finding algorithm to
|
||||||
// function properly.
|
// function properly.
|
||||||
firstPath := make([]*ChannelHop, 0, len(startingPath)+1)
|
firstPath := make([]*channeldb.ChannelEdgePolicy, 0, len(startingPath)+1)
|
||||||
firstPath = append(firstPath, &ChannelHop{
|
firstPath = append(firstPath, &channeldb.ChannelEdgePolicy{
|
||||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
|
||||||
Node: source,
|
Node: source,
|
||||||
},
|
|
||||||
})
|
})
|
||||||
firstPath = append(firstPath, startingPath...)
|
firstPath = append(firstPath, startingPath...)
|
||||||
|
|
||||||
@ -908,7 +881,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
|||||||
// rootPath to the spurPath.
|
// rootPath to the spurPath.
|
||||||
newPathLen := len(rootPath) + len(spurPath)
|
newPathLen := len(rootPath) + len(spurPath)
|
||||||
newPath := path{
|
newPath := path{
|
||||||
hops: make([]*ChannelHop, 0, newPathLen),
|
hops: make([]*channeldb.ChannelEdgePolicy, 0, newPathLen),
|
||||||
dist: newPathLen,
|
dist: newPathLen,
|
||||||
}
|
}
|
||||||
newPath.hops = append(newPath.hops, rootPath...)
|
newPath.hops = append(newPath.hops, rootPath...)
|
||||||
|
@ -592,14 +592,26 @@ func TestFindLowestFeePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assert that the lowest fee route is returned.
|
// Assert that the lowest fee route is returned.
|
||||||
if !bytes.Equal(route.Hops[1].Channel.Node.PubKeyBytes[:],
|
if !bytes.Equal(route.Hops[1].PubKeyBytes[:],
|
||||||
testGraphInstance.aliasMap["b"].SerializeCompressed()) {
|
testGraphInstance.aliasMap["b"].SerializeCompressed()) {
|
||||||
t.Fatalf("expected route to pass through b, "+
|
t.Fatalf("expected route to pass through b, "+
|
||||||
"but got a route through %v",
|
"but got a route through %v",
|
||||||
route.Hops[1].Channel.Node.Alias)
|
getAliasFromPubKey(route.Hops[1].PubKeyBytes[:],
|
||||||
|
testGraphInstance.aliasMap))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getAliasFromPubKey(pubKey []byte,
|
||||||
|
aliases map[string]*btcec.PublicKey) string {
|
||||||
|
|
||||||
|
for alias, key := range aliases {
|
||||||
|
if bytes.Equal(key.SerializeCompressed(), pubKey) {
|
||||||
|
return alias
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type expectedHop struct {
|
type expectedHop struct {
|
||||||
alias string
|
alias string
|
||||||
fee lnwire.MilliSatoshi
|
fee lnwire.MilliSatoshi
|
||||||
@ -733,11 +745,13 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
|
|
||||||
// Check hop nodes
|
// Check hop nodes
|
||||||
for i := 0; i < len(expectedHops); i++ {
|
for i := 0; i < len(expectedHops); i++ {
|
||||||
if !bytes.Equal(route.Hops[i].Channel.Node.PubKeyBytes[:],
|
if !bytes.Equal(route.Hops[i].PubKeyBytes[:],
|
||||||
aliases[expectedHops[i].alias].SerializeCompressed()) {
|
aliases[expectedHops[i].alias].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("%v-th hop should be %v, is instead: %v",
|
t.Fatalf("%v-th hop should be %v, is instead: %v",
|
||||||
i, expectedHops[i], route.Hops[i].Channel.Node.Alias)
|
i, expectedHops[i],
|
||||||
|
getAliasFromPubKey(route.Hops[i].PubKeyBytes[:],
|
||||||
|
aliases))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -753,7 +767,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
// Hops should point to the next hop
|
// Hops should point to the next hop
|
||||||
for i := 0; i < len(expectedHops)-1; i++ {
|
for i := 0; i < len(expectedHops)-1; i++ {
|
||||||
var expectedHop [8]byte
|
var expectedHop [8]byte
|
||||||
binary.BigEndian.PutUint64(expectedHop[:], route.Hops[i+1].Channel.ChannelID)
|
binary.BigEndian.PutUint64(expectedHop[:], route.Hops[i+1].ChannelID)
|
||||||
if !bytes.Equal(hopPayloads[i].NextAddress[:], expectedHop[:]) {
|
if !bytes.Equal(hopPayloads[i].NextAddress[:], expectedHop[:]) {
|
||||||
t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
|
t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
|
||||||
expectedHop[:], hopPayloads[i].NextAddress)
|
expectedHop[:], hopPayloads[i].NextAddress)
|
||||||
@ -774,9 +788,10 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
// We'll ensure that the amount to forward, and fees
|
// We'll ensure that the amount to forward, and fees
|
||||||
// computed for each hop are correct.
|
// computed for each hop are correct.
|
||||||
|
|
||||||
if route.Hops[i].Fee != expectedHops[i].fee {
|
fee := route.HopFee(i)
|
||||||
|
if fee != expectedHops[i].fee {
|
||||||
t.Fatalf("fee incorrect for hop %v: expected %v, got %v",
|
t.Fatalf("fee incorrect for hop %v: expected %v, got %v",
|
||||||
i, expectedHops[i].fee, route.Hops[i].Fee)
|
i, expectedHops[i].fee, fee)
|
||||||
}
|
}
|
||||||
|
|
||||||
if route.Hops[i].AmtToForward != expectedHops[i].fwdAmount {
|
if route.Hops[i].AmtToForward != expectedHops[i].fwdAmount {
|
||||||
@ -815,9 +830,9 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("hop didn't have prev chan but should have")
|
t.Fatalf("hop didn't have prev chan but should have")
|
||||||
}
|
}
|
||||||
if prevChan.ChannelID != route.Hops[i].Channel.ChannelID {
|
if prevChan.ChannelID != route.Hops[i].ChannelID {
|
||||||
t.Fatalf("incorrect prev chan: expected %v, got %v",
|
t.Fatalf("incorrect prev chan: expected %v, got %v",
|
||||||
prevChan.ChannelID, route.Hops[i].Channel.ChannelID)
|
prevChan.ChannelID, route.Hops[i].ChannelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -826,9 +841,9 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
|||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("hop didn't have prev chan but should have")
|
t.Fatalf("hop didn't have prev chan but should have")
|
||||||
}
|
}
|
||||||
if nextChan.ChannelID != route.Hops[i+1].Channel.ChannelID {
|
if nextChan.ChannelID != route.Hops[i+1].ChannelID {
|
||||||
t.Fatalf("incorrect prev chan: expected %v, got %v",
|
t.Fatalf("incorrect prev chan: expected %v, got %v",
|
||||||
nextChan.ChannelID, route.Hops[i+1].Channel.ChannelID)
|
nextChan.ChannelID, route.Hops[i+1].ChannelID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -969,16 +984,13 @@ func TestNewRoute(t *testing.T) {
|
|||||||
createHop := func(baseFee lnwire.MilliSatoshi,
|
createHop := func(baseFee lnwire.MilliSatoshi,
|
||||||
feeRate lnwire.MilliSatoshi,
|
feeRate lnwire.MilliSatoshi,
|
||||||
bandwidth lnwire.MilliSatoshi,
|
bandwidth lnwire.MilliSatoshi,
|
||||||
timeLockDelta uint16) *ChannelHop {
|
timeLockDelta uint16) *channeldb.ChannelEdgePolicy {
|
||||||
|
|
||||||
return &ChannelHop{
|
return &channeldb.ChannelEdgePolicy{
|
||||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
|
||||||
Node: &channeldb.LightningNode{},
|
Node: &channeldb.LightningNode{},
|
||||||
FeeProportionalMillionths: feeRate,
|
FeeProportionalMillionths: feeRate,
|
||||||
FeeBaseMSat: baseFee,
|
FeeBaseMSat: baseFee,
|
||||||
TimeLockDelta: timeLockDelta,
|
TimeLockDelta: timeLockDelta,
|
||||||
},
|
|
||||||
Bandwidth: bandwidth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -988,7 +1000,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
|
|
||||||
// hops is the list of hops (the route) that gets passed into
|
// hops is the list of hops (the route) that gets passed into
|
||||||
// the call to newRoute.
|
// the call to newRoute.
|
||||||
hops []*ChannelHop
|
hops []*channeldb.ChannelEdgePolicy
|
||||||
|
|
||||||
// paymentAmount is the amount that is send into the route
|
// paymentAmount is the amount that is send into the route
|
||||||
// indicated by hops.
|
// indicated by hops.
|
||||||
@ -1029,7 +1041,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// For a single hop payment, no fees are expected to be paid.
|
// For a single hop payment, no fees are expected to be paid.
|
||||||
name: "single hop",
|
name: "single hop",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(100, 1000, 1000000, 10),
|
createHop(100, 1000, 1000000, 10),
|
||||||
},
|
},
|
||||||
expectedFees: []lnwire.MilliSatoshi{0},
|
expectedFees: []lnwire.MilliSatoshi{0},
|
||||||
@ -1043,7 +1055,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// a fee to receive the payment.
|
// a fee to receive the payment.
|
||||||
name: "two hop",
|
name: "two hop",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 1000, 1000000, 10),
|
createHop(0, 1000, 1000000, 10),
|
||||||
createHop(30, 1000, 1000000, 5),
|
createHop(30, 1000, 1000000, 5),
|
||||||
},
|
},
|
||||||
@ -1052,17 +1064,6 @@ func TestNewRoute(t *testing.T) {
|
|||||||
expectedTotalAmount: 100130,
|
expectedTotalAmount: 100130,
|
||||||
expectedTotalTimeLock: 6,
|
expectedTotalTimeLock: 6,
|
||||||
feeLimit: noFeeLimit,
|
feeLimit: noFeeLimit,
|
||||||
}, {
|
|
||||||
// Insufficient capacity in first channel when fees are added.
|
|
||||||
name: "two hop insufficient",
|
|
||||||
paymentAmount: 100000,
|
|
||||||
hops: []*ChannelHop{
|
|
||||||
createHop(0, 1000, 100000, 10),
|
|
||||||
createHop(0, 1000, 1000000, 5),
|
|
||||||
},
|
|
||||||
feeLimit: noFeeLimit,
|
|
||||||
expectError: true,
|
|
||||||
expectedErrorCode: ErrInsufficientCapacity,
|
|
||||||
}, {
|
}, {
|
||||||
// A three hop payment where the first and second hop
|
// A three hop payment where the first and second hop
|
||||||
// will both charge 1 msat. The fee for the first hop
|
// will both charge 1 msat. The fee for the first hop
|
||||||
@ -1071,7 +1072,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// gets rounded down to 1.
|
// gets rounded down to 1.
|
||||||
name: "three hop",
|
name: "three hop",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 10, 1000000, 10),
|
createHop(0, 10, 1000000, 10),
|
||||||
createHop(0, 10, 1000000, 5),
|
createHop(0, 10, 1000000, 5),
|
||||||
createHop(0, 10, 1000000, 3),
|
createHop(0, 10, 1000000, 3),
|
||||||
@ -1087,7 +1088,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// because of the increase amount to forward.
|
// because of the increase amount to forward.
|
||||||
name: "three hop with fee carry over",
|
name: "three hop with fee carry over",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 10000, 1000000, 10),
|
createHop(0, 10000, 1000000, 10),
|
||||||
createHop(0, 10000, 1000000, 5),
|
createHop(0, 10000, 1000000, 5),
|
||||||
createHop(0, 10000, 1000000, 3),
|
createHop(0, 10000, 1000000, 3),
|
||||||
@ -1103,7 +1104,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
// effect.
|
// effect.
|
||||||
name: "three hop with minimal fees for carry over",
|
name: "three hop with minimal fees for carry over",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 10000, 1000000, 10),
|
createHop(0, 10000, 1000000, 10),
|
||||||
|
|
||||||
// First hop charges 0.1% so the second hop fee
|
// First hop charges 0.1% so the second hop fee
|
||||||
@ -1124,7 +1125,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
{
|
{
|
||||||
name: "two hop success with fee limit (greater)",
|
name: "two hop success with fee limit (greater)",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
},
|
},
|
||||||
@ -1136,7 +1137,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "two hop success with fee limit (equal)",
|
name: "two hop success with fee limit (equal)",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
},
|
},
|
||||||
@ -1148,7 +1149,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "two hop failure with fee limit (smaller)",
|
name: "two hop failure with fee limit (smaller)",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
},
|
},
|
||||||
@ -1158,7 +1159,7 @@ func TestNewRoute(t *testing.T) {
|
|||||||
}, {
|
}, {
|
||||||
name: "two hop failure with fee limit (zero)",
|
name: "two hop failure with fee limit (zero)",
|
||||||
paymentAmount: 100000,
|
paymentAmount: 100000,
|
||||||
hops: []*ChannelHop{
|
hops: []*channeldb.ChannelEdgePolicy{
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
createHop(0, 1000, 1000000, 144),
|
createHop(0, 1000, 1000000, 144),
|
||||||
},
|
},
|
||||||
@ -1177,13 +1178,13 @@ func TestNewRoute(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < len(testCase.expectedFees); i++ {
|
for i := 0; i < len(testCase.expectedFees); i++ {
|
||||||
if testCase.expectedFees[i] !=
|
fee := route.HopFee(i)
|
||||||
route.Hops[i].Fee {
|
if testCase.expectedFees[i] != fee {
|
||||||
|
|
||||||
t.Errorf("Expected fee for hop %v to "+
|
t.Errorf("Expected fee for hop %v to "+
|
||||||
"be %v, but got %v instead",
|
"be %v, but got %v instead",
|
||||||
i, testCase.expectedFees[i],
|
i, testCase.expectedFees[i],
|
||||||
route.Hops[i].Fee)
|
fee)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1515,9 +1516,10 @@ func TestPathFindSpecExample(t *testing.T) {
|
|||||||
t.Fatalf("wrong forward amount: got %v, expected %v",
|
t.Fatalf("wrong forward amount: got %v, expected %v",
|
||||||
firstRoute.Hops[0].AmtToForward, amt)
|
firstRoute.Hops[0].AmtToForward, amt)
|
||||||
}
|
}
|
||||||
if firstRoute.Hops[0].Fee != 0 {
|
|
||||||
t.Fatalf("wrong hop fee: got %v, expected %v",
|
fee := firstRoute.HopFee(0)
|
||||||
firstRoute.Hops[0].Fee, 0)
|
if fee != 0 {
|
||||||
|
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The CLTV expiry should be the current height plus 9 (the expiry for
|
// The CLTV expiry should be the current height plus 9 (the expiry for
|
||||||
@ -1600,16 +1602,17 @@ func TestPathFindSpecExample(t *testing.T) {
|
|||||||
// hop, so we should get a fee of exactly:
|
// hop, so we should get a fee of exactly:
|
||||||
//
|
//
|
||||||
// * 200 + 4999999 * 2000 / 1000000 = 10199
|
// * 200 + 4999999 * 2000 / 1000000 = 10199
|
||||||
if routes[0].Hops[0].Fee != 10199 {
|
|
||||||
t.Fatalf("wrong hop fee: got %v, expected %v",
|
fee = routes[0].HopFee(0)
|
||||||
routes[0].Hops[0].Fee, 10199)
|
if fee != 10199 {
|
||||||
|
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 10199)
|
||||||
}
|
}
|
||||||
|
|
||||||
// While for the final hop, as there's no additional hop afterwards, we
|
// While for the final hop, as there's no additional hop afterwards, we
|
||||||
// pay no fee.
|
// pay no fee.
|
||||||
if routes[0].Hops[1].Fee != 0 {
|
fee = routes[0].HopFee(1)
|
||||||
t.Fatalf("wrong hop fee: got %v, expected %v",
|
if fee != 0 {
|
||||||
routes[0].Hops[0].Fee, 0)
|
t.Fatalf("wrong hop fee: got %v, expected %v", fee, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
// The outgoing CLTV value itself should be the current height plus 30
|
// The outgoing CLTV value itself should be the current height plus 30
|
||||||
@ -1677,7 +1680,9 @@ func TestPathFindSpecExample(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertExpectedPath(t *testing.T, path []*ChannelHop, nodeAliases ...string) {
|
func assertExpectedPath(t *testing.T, path []*channeldb.ChannelEdgePolicy,
|
||||||
|
nodeAliases ...string) {
|
||||||
|
|
||||||
if len(path) != len(nodeAliases) {
|
if len(path) != len(nodeAliases) {
|
||||||
t.Fatal("number of hops and number of aliases do not match")
|
t.Fatal("number of hops and number of aliases do not match")
|
||||||
}
|
}
|
||||||
|
@ -1274,7 +1274,7 @@ func pruneChannelFromRoutes(routes []*Route, skipChan uint64) []*Route {
|
|||||||
// fee information attached. The set of routes returned may be less than the
|
// fee information attached. The set of routes returned may be less than the
|
||||||
// initial set of paths as it's possible we drop a route if it can't handle the
|
// initial set of paths as it's possible we drop a route if it can't handle the
|
||||||
// total payment flow after fees are calculated.
|
// total payment flow after fees are calculated.
|
||||||
func pathsToFeeSortedRoutes(source Vertex, paths [][]*ChannelHop,
|
func pathsToFeeSortedRoutes(source Vertex, paths [][]*channeldb.ChannelEdgePolicy,
|
||||||
finalCLTVDelta uint16, amt, feeLimit lnwire.MilliSatoshi,
|
finalCLTVDelta uint16, amt, feeLimit lnwire.MilliSatoshi,
|
||||||
currentHeight uint32) ([]*Route, error) {
|
currentHeight uint32) ([]*Route, error) {
|
||||||
|
|
||||||
@ -1461,19 +1461,13 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte,
|
|||||||
// in each hop.
|
// in each hop.
|
||||||
nodes := make([]*btcec.PublicKey, len(route.Hops))
|
nodes := make([]*btcec.PublicKey, len(route.Hops))
|
||||||
for i, hop := range route.Hops {
|
for i, hop := range route.Hops {
|
||||||
// We create a new instance of the public key to avoid possibly
|
pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:],
|
||||||
// mutating the curve parameters, which are unset in a higher
|
btcec.S256())
|
||||||
// level in order to avoid spamming the logs.
|
|
||||||
nodePub, err := hop.Channel.Node.PubKey()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
pub := btcec.PublicKey{
|
|
||||||
Curve: btcec.S256(),
|
nodes[i] = pub
|
||||||
X: nodePub.X,
|
|
||||||
Y: nodePub.Y,
|
|
||||||
}
|
|
||||||
nodes[i] = &pub
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next we generate the per-hop payload which gives each node within
|
// Next we generate the per-hop payload which gives each node within
|
||||||
@ -1736,7 +1730,7 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment,
|
|||||||
// the payment. If this attempt fails, then we'll continue on
|
// the payment. If this attempt fails, then we'll continue on
|
||||||
// to the next available route.
|
// to the next available route.
|
||||||
firstHop := lnwire.NewShortChanIDFromInt(
|
firstHop := lnwire.NewShortChanIDFromInt(
|
||||||
route.Hops[0].Channel.ChannelID,
|
route.Hops[0].ChannelID,
|
||||||
)
|
)
|
||||||
preImage, sendError = r.cfg.SendToSwitch(
|
preImage, sendError = r.cfg.SendToSwitch(
|
||||||
firstHop, htlcAdd, circuit,
|
firstHop, htlcAdd, circuit,
|
||||||
|
@ -262,9 +262,12 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
|
|||||||
t.Fatalf("expected 2 hops, got %d", len(hops))
|
t.Fatalf("expected 2 hops, got %d", len(hops))
|
||||||
}
|
}
|
||||||
|
|
||||||
if hops[0].Channel.Node.Alias != "songoku" {
|
if !bytes.Equal(hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases["songoku"].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("expected first hop through songoku, got %s",
|
t.Fatalf("expected first hop through songoku, got %s",
|
||||||
hops[0].Channel.Node.Alias)
|
getAliasFromPubKey(hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,10 +344,13 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The route should have satoshi as the first hop.
|
// The route should have satoshi as the first hop.
|
||||||
if route.Hops[0].Channel.Node.Alias != "satoshi" {
|
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases["satoshi"].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("route should go through satoshi as first hop, "+
|
t.Fatalf("route should go through satoshi as first hop, "+
|
||||||
"instead passes through: %v",
|
"instead passes through: %v",
|
||||||
route.Hops[0].Channel.Node.Alias)
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -406,25 +412,13 @@ func TestChannelUpdateValidation(t *testing.T) {
|
|||||||
|
|
||||||
hops := []*Hop{
|
hops := []*Hop{
|
||||||
{
|
{
|
||||||
Channel: &ChannelHop{
|
|
||||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
|
||||||
ChannelID: 1,
|
ChannelID: 1,
|
||||||
Node: &channeldb.LightningNode{
|
|
||||||
PubKeyBytes: hop1,
|
PubKeyBytes: hop1,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
Channel: &ChannelHop{
|
|
||||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
|
||||||
ChannelID: 2,
|
ChannelID: 2,
|
||||||
Node: &channeldb.LightningNode{
|
|
||||||
PubKeyBytes: hop2,
|
PubKeyBytes: hop2,
|
||||||
},
|
},
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
route := &Route{
|
route := &Route{
|
||||||
@ -605,10 +599,13 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The route should have pham nuwen as the first hop.
|
// The route should have pham nuwen as the first hop.
|
||||||
if route.Hops[0].Channel.Node.Alias != "phamnuwen" {
|
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases["phamnuwen"].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("route should go through satoshi as first hop, "+
|
t.Fatalf("route should go through satoshi as first hop, "+
|
||||||
"instead passes through: %v",
|
"instead passes through: %v",
|
||||||
route.Hops[0].Channel.Node.Alias)
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -701,10 +698,13 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The route should have satoshi as the first hop.
|
// The route should have satoshi as the first hop.
|
||||||
if route.Hops[0].Channel.Node.Alias != "phamnuwen" {
|
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases["phamnuwen"].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("route should go through phamnuwen as first hop, "+
|
t.Fatalf("route should go through phamnuwen as first hop, "+
|
||||||
"instead passes through: %v",
|
"instead passes through: %v",
|
||||||
route.Hops[0].Channel.Node.Alias)
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -868,10 +868,13 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
t.Fatalf("incorrect preimage used: expected %x got %x",
|
t.Fatalf("incorrect preimage used: expected %x got %x",
|
||||||
preImage[:], paymentPreImage[:])
|
preImage[:], paymentPreImage[:])
|
||||||
}
|
}
|
||||||
if route.Hops[0].Channel.Node.Alias != "satoshi" {
|
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases["satoshi"].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("route should go through satoshi as first hop, "+
|
t.Fatalf("route should go through satoshi as first hop, "+
|
||||||
"instead passes through: %v",
|
"instead passes through: %v",
|
||||||
route.Hops[0].Channel.Node.Alias)
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases))
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.router.missionControl.ResetHistory()
|
ctx.router.missionControl.ResetHistory()
|
||||||
@ -913,10 +916,13 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The route should have satoshi as the first hop.
|
// The route should have satoshi as the first hop.
|
||||||
if route.Hops[0].Channel.Node.Alias != "satoshi" {
|
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases["satoshi"].SerializeCompressed()) {
|
||||||
|
|
||||||
t.Fatalf("route should go through satoshi as first hop, "+
|
t.Fatalf("route should go through satoshi as first hop, "+
|
||||||
"instead passes through: %v",
|
"instead passes through: %v",
|
||||||
route.Hops[0].Channel.Node.Alias)
|
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||||
|
ctx.aliases))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
93
rpcserver.go
93
rpcserver.go
@ -1904,7 +1904,7 @@ func (r *rpcServer) savePayment(route *routing.Route,
|
|||||||
|
|
||||||
paymentPath := make([][33]byte, len(route.Hops))
|
paymentPath := make([][33]byte, len(route.Hops))
|
||||||
for i, hop := range route.Hops {
|
for i, hop := range route.Hops {
|
||||||
hopPub := hop.Channel.Node.PubKeyBytes
|
hopPub := hop.PubKeyBytes
|
||||||
copy(paymentPath[i][:], hopPub[:])
|
copy(paymentPath[i][:], hopPub[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2023,7 +2023,7 @@ func (r *rpcServer) SendToRoute(stream lnrpc.Lightning_SendToRouteServer) error
|
|||||||
|
|
||||||
routes := make([]*routing.Route, len(req.Routes))
|
routes := make([]*routing.Route, len(req.Routes))
|
||||||
for i, rpcroute := range req.Routes {
|
for i, rpcroute := range req.Routes {
|
||||||
route, err := unmarshallRoute(rpcroute, graph)
|
route, err := r.unmarshallRoute(rpcroute, graph)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -2437,7 +2437,7 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
marshalledRouted := marshallRoute(resp.Route)
|
marshalledRouted := r.marshallRoute(resp.Route)
|
||||||
err := stream.send(&lnrpc.SendResponse{
|
err := stream.send(&lnrpc.SendResponse{
|
||||||
PaymentPreimage: resp.Preimage[:],
|
PaymentPreimage: resp.Preimage[:],
|
||||||
PaymentRoute: marshalledRouted,
|
PaymentRoute: marshalledRouted,
|
||||||
@ -2478,7 +2478,7 @@ func (r *rpcServer) SendToRouteSync(ctx context.Context,
|
|||||||
|
|
||||||
routes := make([]*routing.Route, len(req.Routes))
|
routes := make([]*routing.Route, len(req.Routes))
|
||||||
for i, route := range req.Routes {
|
for i, route := range req.Routes {
|
||||||
route, err := unmarshallRoute(route, graph)
|
route, err := r.unmarshallRoute(route, graph)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -2528,7 +2528,7 @@ func (r *rpcServer) sendPaymentSync(ctx context.Context,
|
|||||||
|
|
||||||
return &lnrpc.SendResponse{
|
return &lnrpc.SendResponse{
|
||||||
PaymentPreimage: resp.Preimage[:],
|
PaymentPreimage: resp.Preimage[:],
|
||||||
PaymentRoute: marshallRoute(resp.Route),
|
PaymentRoute: r.marshallRoute(resp.Route),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3406,14 +3406,14 @@ func (r *rpcServer) QueryRoutes(ctx context.Context,
|
|||||||
}
|
}
|
||||||
for i := int32(0); i < numRoutes; i++ {
|
for i := int32(0); i < numRoutes; i++ {
|
||||||
routeResp.Routes = append(
|
routeResp.Routes = append(
|
||||||
routeResp.Routes, marshallRoute(routes[i]),
|
routeResp.Routes, r.marshallRoute(routes[i]),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return routeResp, nil
|
return routeResp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func marshallRoute(route *routing.Route) *lnrpc.Route {
|
func (r *rpcServer) marshallRoute(route *routing.Route) *lnrpc.Route {
|
||||||
resp := &lnrpc.Route{
|
resp := &lnrpc.Route{
|
||||||
TotalTimeLock: route.TotalTimeLock,
|
TotalTimeLock: route.TotalTimeLock,
|
||||||
TotalFees: int64(route.TotalFees.ToSatoshis()),
|
TotalFees: int64(route.TotalFees.ToSatoshis()),
|
||||||
@ -3422,72 +3422,87 @@ func marshallRoute(route *routing.Route) *lnrpc.Route {
|
|||||||
TotalAmtMsat: int64(route.TotalAmount),
|
TotalAmtMsat: int64(route.TotalAmount),
|
||||||
Hops: make([]*lnrpc.Hop, len(route.Hops)),
|
Hops: make([]*lnrpc.Hop, len(route.Hops)),
|
||||||
}
|
}
|
||||||
|
graph := r.server.chanDB.ChannelGraph()
|
||||||
|
incomingAmt := route.TotalAmount
|
||||||
for i, hop := range route.Hops {
|
for i, hop := range route.Hops {
|
||||||
|
fee := route.HopFee(i)
|
||||||
|
|
||||||
|
// Channel capacity is not a defining property of a route. For
|
||||||
|
// backwards RPC compatibility, we retrieve it here from the
|
||||||
|
// graph.
|
||||||
|
var chanCapacity btcutil.Amount
|
||||||
|
info, _, _, err := graph.FetchChannelEdgesByID(hop.ChannelID)
|
||||||
|
if err == nil {
|
||||||
|
chanCapacity = info.Capacity
|
||||||
|
} else {
|
||||||
|
// If capacity cannot be retrieved, this may be a
|
||||||
|
// not-yet-received or private channel. Then report
|
||||||
|
// amount that is sent through the channel as capacity.
|
||||||
|
chanCapacity = incomingAmt.ToSatoshis()
|
||||||
|
}
|
||||||
|
|
||||||
resp.Hops[i] = &lnrpc.Hop{
|
resp.Hops[i] = &lnrpc.Hop{
|
||||||
ChanId: hop.Channel.ChannelID,
|
ChanId: hop.ChannelID,
|
||||||
ChanCapacity: int64(hop.Channel.Bandwidth.ToSatoshis()),
|
ChanCapacity: int64(chanCapacity),
|
||||||
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
|
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
|
||||||
AmtToForwardMsat: int64(hop.AmtToForward),
|
AmtToForwardMsat: int64(hop.AmtToForward),
|
||||||
Fee: int64(hop.Fee.ToSatoshis()),
|
Fee: int64(fee.ToSatoshis()),
|
||||||
FeeMsat: int64(hop.Fee),
|
FeeMsat: int64(fee),
|
||||||
Expiry: uint32(hop.OutgoingTimeLock),
|
Expiry: uint32(hop.OutgoingTimeLock),
|
||||||
}
|
}
|
||||||
|
incomingAmt = hop.AmtToForward
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
}
|
}
|
||||||
|
|
||||||
func unmarshallRoute(rpcroute *lnrpc.Route,
|
func (r *rpcServer) unmarshallRoute(rpcroute *lnrpc.Route,
|
||||||
graph *channeldb.ChannelGraph) (*routing.Route, error) {
|
graph *channeldb.ChannelGraph) (*routing.Route, error) {
|
||||||
|
|
||||||
route := &routing.Route{
|
|
||||||
TotalTimeLock: rpcroute.TotalTimeLock,
|
|
||||||
TotalFees: lnwire.MilliSatoshi(rpcroute.TotalFeesMsat),
|
|
||||||
TotalAmount: lnwire.MilliSatoshi(rpcroute.TotalAmtMsat),
|
|
||||||
Hops: make([]*routing.Hop, len(rpcroute.Hops)),
|
|
||||||
}
|
|
||||||
|
|
||||||
node, err := graph.SourceNode()
|
node, err := graph.SourceNode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to fetch source node from graph "+
|
return nil, fmt.Errorf("unable to fetch source node from graph "+
|
||||||
"while unmarshaling route. %v", err)
|
"while unmarshaling route. %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodePubKeyBytes := node.PubKeyBytes[:]
|
||||||
|
|
||||||
|
hops := make([]*routing.Hop, len(rpcroute.Hops))
|
||||||
for i, hop := range rpcroute.Hops {
|
for i, hop := range rpcroute.Hops {
|
||||||
edgeInfo, c1, c2, err := graph.FetchChannelEdgesByID(hop.ChanId)
|
// Discard edge policies, because they may be nil.
|
||||||
|
edgeInfo, _, _, err := graph.FetchChannelEdgesByID(hop.ChanId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("unable to fetch channel edges by "+
|
return nil, fmt.Errorf("unable to fetch channel edges by "+
|
||||||
"channel ID for hop (%d): %v", i, err)
|
"channel ID for hop (%d): %v", i, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var channelEdgePolicy *channeldb.ChannelEdgePolicy
|
var pubKeyBytes [33]byte
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case bytes.Equal(node.PubKeyBytes[:], c1.Node.PubKeyBytes[:]):
|
case bytes.Equal(nodePubKeyBytes[:], edgeInfo.NodeKey1Bytes[:]):
|
||||||
channelEdgePolicy = c2
|
pubKeyBytes = edgeInfo.NodeKey2Bytes
|
||||||
node = c2.Node
|
case bytes.Equal(nodePubKeyBytes[:], edgeInfo.NodeKey2Bytes[:]):
|
||||||
case bytes.Equal(node.PubKeyBytes[:], c2.Node.PubKeyBytes[:]):
|
pubKeyBytes = edgeInfo.NodeKey1Bytes
|
||||||
channelEdgePolicy = c1
|
|
||||||
node = c1.Node
|
|
||||||
default:
|
default:
|
||||||
return nil, fmt.Errorf("could not find channel edge for hop=%d", i)
|
return nil, fmt.Errorf("channel edge does not match expected node")
|
||||||
}
|
}
|
||||||
|
|
||||||
routingHop := &routing.ChannelHop{
|
hops[i] = &routing.Hop{
|
||||||
ChannelEdgePolicy: channelEdgePolicy,
|
|
||||||
Bandwidth: lnwire.NewMSatFromSatoshis(
|
|
||||||
btcutil.Amount(hop.ChanCapacity)),
|
|
||||||
Chain: edgeInfo.ChainHash,
|
|
||||||
}
|
|
||||||
|
|
||||||
route.Hops[i] = &routing.Hop{
|
|
||||||
Channel: routingHop,
|
|
||||||
OutgoingTimeLock: hop.Expiry,
|
OutgoingTimeLock: hop.Expiry,
|
||||||
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForwardMsat),
|
AmtToForward: lnwire.MilliSatoshi(hop.AmtToForwardMsat),
|
||||||
Fee: lnwire.MilliSatoshi(hop.FeeMsat),
|
PubKeyBytes: pubKeyBytes,
|
||||||
|
ChannelID: edgeInfo.ChannelID,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodePubKeyBytes = pubKeyBytes[:]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
route := routing.NewRouteFromHops(
|
||||||
|
lnwire.MilliSatoshi(rpcroute.TotalAmtMsat),
|
||||||
|
rpcroute.TotalTimeLock,
|
||||||
|
node.PubKeyBytes,
|
||||||
|
hops,
|
||||||
|
)
|
||||||
|
|
||||||
return route, nil
|
return route, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user