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.
|
||||
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.
|
||||
hops []*ChannelHop
|
||||
hops []*channeldb.ChannelEdgePolicy
|
||||
}
|
||||
|
||||
// pathHeap is a min-heap that stores potential paths to be considered within
|
||||
|
@ -9,7 +9,6 @@ import (
|
||||
"container/heap"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/coreos/bbolt"
|
||||
"github.com/lightningnetwork/lightning-onion"
|
||||
"github.com/lightningnetwork/lnd/channeldb"
|
||||
@ -62,30 +61,6 @@ type HopHint struct {
|
||||
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
|
||||
// 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
|
||||
@ -93,9 +68,13 @@ type ChannelHop struct {
|
||||
// next hop. It is also used to encode the per-hop payload included within
|
||||
// the Sphinx packet.
|
||||
type Hop struct {
|
||||
// Channel is the active payment channel edge along which the packet
|
||||
// travels to reach this hop. This is the _incoming_ channel to this hop.
|
||||
Channel *ChannelHop
|
||||
// PubKeyBytes is the raw bytes of the public key of the target node.
|
||||
PubKeyBytes Vertex
|
||||
|
||||
// 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
|
||||
// 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
|
||||
// carries as a fee will be subtracted by the hop.
|
||||
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
|
||||
@ -131,7 +105,7 @@ func computeFee(amt lnwire.MilliSatoshi,
|
||||
|
||||
// isSamePath returns true if path1 and path2 travel through the exact same
|
||||
// edges, and false otherwise.
|
||||
func isSamePath(path1, path2 []*ChannelHop) bool {
|
||||
func isSamePath(path1, path2 []*channeldb.ChannelEdgePolicy) bool {
|
||||
if len(path1) != len(path2) {
|
||||
return false
|
||||
}
|
||||
@ -188,25 +162,38 @@ type Route struct {
|
||||
// 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
|
||||
// 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
|
||||
// within the route. With this map, we can easily look up the previous
|
||||
// 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
|
||||
// target node is not found in the route, then false is returned.
|
||||
func (r *Route) nextHopVertex(n *btcec.PublicKey) (Vertex, bool) {
|
||||
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
|
||||
// target node. If the target node is not found in the route, then false is
|
||||
// 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)]
|
||||
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
|
||||
// target node. If the target node is not found in the route, then false is
|
||||
// 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)]
|
||||
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"
|
||||
// field to be the channel that directly follows it.
|
||||
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[:],
|
||||
@ -276,44 +263,20 @@ func (r *Route) ToHopPayloads() []sphinx.HopData {
|
||||
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
|
||||
// the source to the target node of the path finding attempt.
|
||||
func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
|
||||
pathEdges []*ChannelHop, currentHeight uint32,
|
||||
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
|
||||
finalCLTVDelta uint16) (*Route, error) {
|
||||
|
||||
// First, we'll create a new empty route with enough hops to match the
|
||||
// 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),
|
||||
}
|
||||
var hops []*Hop
|
||||
|
||||
// We'll populate the next hop map for the _source_ node with the
|
||||
// information for the first hop so the mapping is sound.
|
||||
route.nextHopMap[sourceVertex] = pathEdges[0]
|
||||
totalTimeLock := currentHeight
|
||||
|
||||
pathLength := len(pathEdges)
|
||||
|
||||
var nextIncomingAmount lnwire.MilliSatoshi
|
||||
|
||||
for i := pathLength - 1; i >= 0; 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
|
||||
// payload for the hop this edge is leading to. This hop will
|
||||
// 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
|
||||
// to pay for transit over the next link.
|
||||
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
|
||||
// based on how much the next hop forwards plus the fee
|
||||
// that needs to be paid to the next hop.
|
||||
amtToForward = nextHop.AmtToForward + nextHop.Fee
|
||||
// equal to the incoming amount of the next hop.
|
||||
amtToForward = nextIncomingAmount
|
||||
|
||||
// The fee that needs to be paid to the current hop is
|
||||
// based on the amount that this hop needs to forward
|
||||
// and its policy for the outgoing channel. This policy
|
||||
// is stored as part of the incoming channel of
|
||||
// the next hop.
|
||||
fee = computeFee(amtToForward, nextHop.Channel.ChannelEdgePolicy)
|
||||
}
|
||||
|
||||
// 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)
|
||||
fee = computeFee(amtToForward, pathEdges[i+1])
|
||||
}
|
||||
|
||||
// If this is the last hop, then for verification purposes, the
|
||||
// value of the outgoing time-lock should be _exactly_ the
|
||||
// absolute time out they'd expect in the HTLC.
|
||||
var outgoingTimeLock uint32
|
||||
if i == len(pathEdges)-1 {
|
||||
// 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.
|
||||
route.TotalTimeLock += uint32(finalCLTVDelta)
|
||||
totalTimeLock += uint32(finalCLTVDelta)
|
||||
|
||||
currentHop.OutgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||
outgoingTimeLock = currentHeight + uint32(finalCLTVDelta)
|
||||
} else {
|
||||
// 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.
|
||||
delta := uint32(pathEdges[i+1].TimeLockDelta)
|
||||
route.TotalTimeLock += delta
|
||||
totalTimeLock += delta
|
||||
|
||||
// Otherwise, the value of the outgoing time-lock will
|
||||
// be the value of the time-lock for the _outgoing_
|
||||
// HTLC, so we factor in their specified grace period
|
||||
// (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,
|
||||
}
|
||||
|
||||
hops = append([]*Hop{currentHop}, hops...)
|
||||
|
||||
nextIncomingAmount = amtToForward + fee
|
||||
}
|
||||
|
||||
// We'll then make a second run through our route in order to set up
|
||||
// our prev hop mapping.
|
||||
for _, hop := range route.Hops {
|
||||
vertex := Vertex(hop.Channel.Node.PubKeyBytes)
|
||||
route.prevHopMap[vertex] = hop.Channel
|
||||
// With the base routing data expressed as hops, build the full route
|
||||
// structure.
|
||||
newRoute := NewRouteFromHops(nextIncomingAmount, totalTimeLock,
|
||||
sourceVertex, hops)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// The total amount required for this route will be the value
|
||||
// that the first hop needs to forward plus the fee that
|
||||
// the first hop charges for this. Note that the sender of the
|
||||
// payment is not a hop in the route.
|
||||
route.TotalAmount = route.Hops[0].AmtToForward + route.Hops[0].Fee
|
||||
return newRoute, nil
|
||||
}
|
||||
|
||||
return route, 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
|
||||
@ -474,7 +452,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
|
||||
ignoredNodes map[Vertex]struct{}, ignoredEdges map[uint64]struct{},
|
||||
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi,
|
||||
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*ChannelHop, error) {
|
||||
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error) {
|
||||
|
||||
var err error
|
||||
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
|
||||
// from `Vertex` to the target node, we'll take the edge that it's
|
||||
// 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
|
||||
// satisfy our specific requirements.
|
||||
@ -663,10 +641,7 @@ func findPath(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||
fee: fee,
|
||||
}
|
||||
|
||||
next[fromVertex] = &ChannelHop{
|
||||
ChannelEdgePolicy: edge,
|
||||
Bandwidth: bandwidth,
|
||||
}
|
||||
next[fromVertex] = edge
|
||||
|
||||
// Add this new node to our heap as we'd like to further
|
||||
// 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.
|
||||
pathEdges := make([]*ChannelHop, 0, len(next))
|
||||
pathEdges := make([]*channeldb.ChannelEdgePolicy, 0, len(next))
|
||||
currentNode := sourceVertex
|
||||
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles
|
||||
// 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,
|
||||
source *channeldb.LightningNode, target *btcec.PublicKey,
|
||||
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{})
|
||||
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
|
||||
// sorting step?
|
||||
var (
|
||||
shortestPaths [][]*ChannelHop
|
||||
shortestPaths [][]*channeldb.ChannelEdgePolicy
|
||||
candidatePaths pathHeap
|
||||
)
|
||||
|
||||
@ -828,11 +803,9 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||
// Manually insert a "self" edge emanating from ourselves. This
|
||||
// self-edge is required in order for the path finding algorithm to
|
||||
// function properly.
|
||||
firstPath := make([]*ChannelHop, 0, len(startingPath)+1)
|
||||
firstPath = append(firstPath, &ChannelHop{
|
||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
||||
Node: source,
|
||||
},
|
||||
firstPath := make([]*channeldb.ChannelEdgePolicy, 0, len(startingPath)+1)
|
||||
firstPath = append(firstPath, &channeldb.ChannelEdgePolicy{
|
||||
Node: source,
|
||||
})
|
||||
firstPath = append(firstPath, startingPath...)
|
||||
|
||||
@ -908,7 +881,7 @@ func findPaths(tx *bolt.Tx, graph *channeldb.ChannelGraph,
|
||||
// rootPath to the spurPath.
|
||||
newPathLen := len(rootPath) + len(spurPath)
|
||||
newPath := path{
|
||||
hops: make([]*ChannelHop, 0, newPathLen),
|
||||
hops: make([]*channeldb.ChannelEdgePolicy, 0, newPathLen),
|
||||
dist: newPathLen,
|
||||
}
|
||||
newPath.hops = append(newPath.hops, rootPath...)
|
||||
|
@ -592,14 +592,26 @@ func TestFindLowestFeePath(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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()) {
|
||||
t.Fatalf("expected route to pass through b, "+
|
||||
"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 {
|
||||
alias string
|
||||
fee lnwire.MilliSatoshi
|
||||
@ -733,11 +745,13 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
||||
|
||||
// Check hop nodes
|
||||
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()) {
|
||||
|
||||
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
|
||||
for i := 0; i < len(expectedHops)-1; i++ {
|
||||
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[:]) {
|
||||
t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
|
||||
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
|
||||
// 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",
|
||||
i, expectedHops[i].fee, route.Hops[i].Fee)
|
||||
i, expectedHops[i].fee, fee)
|
||||
}
|
||||
|
||||
if route.Hops[i].AmtToForward != expectedHops[i].fwdAmount {
|
||||
@ -815,9 +830,9 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
|
||||
if !ok {
|
||||
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",
|
||||
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 {
|
||||
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",
|
||||
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,
|
||||
feeRate lnwire.MilliSatoshi,
|
||||
bandwidth lnwire.MilliSatoshi,
|
||||
timeLockDelta uint16) *ChannelHop {
|
||||
timeLockDelta uint16) *channeldb.ChannelEdgePolicy {
|
||||
|
||||
return &ChannelHop{
|
||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
||||
Node: &channeldb.LightningNode{},
|
||||
FeeProportionalMillionths: feeRate,
|
||||
FeeBaseMSat: baseFee,
|
||||
TimeLockDelta: timeLockDelta,
|
||||
},
|
||||
Bandwidth: bandwidth,
|
||||
return &channeldb.ChannelEdgePolicy{
|
||||
Node: &channeldb.LightningNode{},
|
||||
FeeProportionalMillionths: feeRate,
|
||||
FeeBaseMSat: baseFee,
|
||||
TimeLockDelta: timeLockDelta,
|
||||
}
|
||||
}
|
||||
|
||||
@ -988,7 +1000,7 @@ func TestNewRoute(t *testing.T) {
|
||||
|
||||
// hops is the list of hops (the route) that gets passed into
|
||||
// the call to newRoute.
|
||||
hops []*ChannelHop
|
||||
hops []*channeldb.ChannelEdgePolicy
|
||||
|
||||
// paymentAmount is the amount that is send into the route
|
||||
// indicated by hops.
|
||||
@ -1029,7 +1041,7 @@ func TestNewRoute(t *testing.T) {
|
||||
// For a single hop payment, no fees are expected to be paid.
|
||||
name: "single hop",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(100, 1000, 1000000, 10),
|
||||
},
|
||||
expectedFees: []lnwire.MilliSatoshi{0},
|
||||
@ -1043,7 +1055,7 @@ func TestNewRoute(t *testing.T) {
|
||||
// a fee to receive the payment.
|
||||
name: "two hop",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(0, 1000, 1000000, 10),
|
||||
createHop(30, 1000, 1000000, 5),
|
||||
},
|
||||
@ -1052,17 +1064,6 @@ func TestNewRoute(t *testing.T) {
|
||||
expectedTotalAmount: 100130,
|
||||
expectedTotalTimeLock: 6,
|
||||
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
|
||||
// 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.
|
||||
name: "three hop",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(0, 10, 1000000, 10),
|
||||
createHop(0, 10, 1000000, 5),
|
||||
createHop(0, 10, 1000000, 3),
|
||||
@ -1087,7 +1088,7 @@ func TestNewRoute(t *testing.T) {
|
||||
// because of the increase amount to forward.
|
||||
name: "three hop with fee carry over",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(0, 10000, 1000000, 10),
|
||||
createHop(0, 10000, 1000000, 5),
|
||||
createHop(0, 10000, 1000000, 3),
|
||||
@ -1103,7 +1104,7 @@ func TestNewRoute(t *testing.T) {
|
||||
// effect.
|
||||
name: "three hop with minimal fees for carry over",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
createHop(0, 10000, 1000000, 10),
|
||||
|
||||
// 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)",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
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)",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
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)",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
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)",
|
||||
paymentAmount: 100000,
|
||||
hops: []*ChannelHop{
|
||||
hops: []*channeldb.ChannelEdgePolicy{
|
||||
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++ {
|
||||
if testCase.expectedFees[i] !=
|
||||
route.Hops[i].Fee {
|
||||
fee := route.HopFee(i)
|
||||
if testCase.expectedFees[i] != fee {
|
||||
|
||||
t.Errorf("Expected fee for hop %v to "+
|
||||
"be %v, but got %v instead",
|
||||
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",
|
||||
firstRoute.Hops[0].AmtToForward, amt)
|
||||
}
|
||||
if firstRoute.Hops[0].Fee != 0 {
|
||||
t.Fatalf("wrong hop fee: got %v, expected %v",
|
||||
firstRoute.Hops[0].Fee, 0)
|
||||
|
||||
fee := firstRoute.HopFee(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
|
||||
@ -1600,16 +1602,17 @@ func TestPathFindSpecExample(t *testing.T) {
|
||||
// hop, so we should get a fee of exactly:
|
||||
//
|
||||
// * 200 + 4999999 * 2000 / 1000000 = 10199
|
||||
if routes[0].Hops[0].Fee != 10199 {
|
||||
t.Fatalf("wrong hop fee: got %v, expected %v",
|
||||
routes[0].Hops[0].Fee, 10199)
|
||||
|
||||
fee = routes[0].HopFee(0)
|
||||
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
|
||||
// pay no fee.
|
||||
if routes[0].Hops[1].Fee != 0 {
|
||||
t.Fatalf("wrong hop fee: got %v, expected %v",
|
||||
routes[0].Hops[0].Fee, 0)
|
||||
fee = routes[0].HopFee(1)
|
||||
if 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
|
||||
@ -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) {
|
||||
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
|
||||
// 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.
|
||||
func pathsToFeeSortedRoutes(source Vertex, paths [][]*ChannelHop,
|
||||
func pathsToFeeSortedRoutes(source Vertex, paths [][]*channeldb.ChannelEdgePolicy,
|
||||
finalCLTVDelta uint16, amt, feeLimit lnwire.MilliSatoshi,
|
||||
currentHeight uint32) ([]*Route, error) {
|
||||
|
||||
@ -1461,19 +1461,13 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte,
|
||||
// in each hop.
|
||||
nodes := make([]*btcec.PublicKey, len(route.Hops))
|
||||
for i, hop := range route.Hops {
|
||||
// We create a new instance of the public key to avoid possibly
|
||||
// mutating the curve parameters, which are unset in a higher
|
||||
// level in order to avoid spamming the logs.
|
||||
nodePub, err := hop.Channel.Node.PubKey()
|
||||
pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:],
|
||||
btcec.S256())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
pub := btcec.PublicKey{
|
||||
Curve: btcec.S256(),
|
||||
X: nodePub.X,
|
||||
Y: nodePub.Y,
|
||||
}
|
||||
nodes[i] = &pub
|
||||
|
||||
nodes[i] = pub
|
||||
}
|
||||
|
||||
// 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
|
||||
// to the next available route.
|
||||
firstHop := lnwire.NewShortChanIDFromInt(
|
||||
route.Hops[0].Channel.ChannelID,
|
||||
route.Hops[0].ChannelID,
|
||||
)
|
||||
preImage, sendError = r.cfg.SendToSwitch(
|
||||
firstHop, htlcAdd, circuit,
|
||||
|
@ -262,9 +262,12 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
|
||||
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",
|
||||
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.
|
||||
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, "+
|
||||
"instead passes through: %v",
|
||||
route.Hops[0].Channel.Node.Alias)
|
||||
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||
ctx.aliases))
|
||||
}
|
||||
}
|
||||
|
||||
@ -406,24 +412,12 @@ func TestChannelUpdateValidation(t *testing.T) {
|
||||
|
||||
hops := []*Hop{
|
||||
{
|
||||
Channel: &ChannelHop{
|
||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
||||
ChannelID: 1,
|
||||
Node: &channeldb.LightningNode{
|
||||
PubKeyBytes: hop1,
|
||||
},
|
||||
},
|
||||
},
|
||||
ChannelID: 1,
|
||||
PubKeyBytes: hop1,
|
||||
},
|
||||
{
|
||||
Channel: &ChannelHop{
|
||||
ChannelEdgePolicy: &channeldb.ChannelEdgePolicy{
|
||||
ChannelID: 2,
|
||||
Node: &channeldb.LightningNode{
|
||||
PubKeyBytes: hop2,
|
||||
},
|
||||
},
|
||||
},
|
||||
ChannelID: 2,
|
||||
PubKeyBytes: hop2,
|
||||
},
|
||||
}
|
||||
|
||||
@ -605,10 +599,13 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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, "+
|
||||
"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.
|
||||
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, "+
|
||||
"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",
|
||||
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, "+
|
||||
"instead passes through: %v",
|
||||
route.Hops[0].Channel.Node.Alias)
|
||||
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
|
||||
ctx.aliases))
|
||||
}
|
||||
|
||||
ctx.router.missionControl.ResetHistory()
|
||||
@ -913,10 +916,13 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
|
||||
}
|
||||
|
||||
// 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, "+
|
||||
"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))
|
||||
for i, hop := range route.Hops {
|
||||
hopPub := hop.Channel.Node.PubKeyBytes
|
||||
hopPub := hop.PubKeyBytes
|
||||
copy(paymentPath[i][:], hopPub[:])
|
||||
}
|
||||
|
||||
@ -2023,7 +2023,7 @@ func (r *rpcServer) SendToRoute(stream lnrpc.Lightning_SendToRouteServer) error
|
||||
|
||||
routes := make([]*routing.Route, len(req.Routes))
|
||||
for i, rpcroute := range req.Routes {
|
||||
route, err := unmarshallRoute(rpcroute, graph)
|
||||
route, err := r.unmarshallRoute(rpcroute, graph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -2437,7 +2437,7 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
|
||||
return
|
||||
}
|
||||
|
||||
marshalledRouted := marshallRoute(resp.Route)
|
||||
marshalledRouted := r.marshallRoute(resp.Route)
|
||||
err := stream.send(&lnrpc.SendResponse{
|
||||
PaymentPreimage: resp.Preimage[:],
|
||||
PaymentRoute: marshalledRouted,
|
||||
@ -2478,7 +2478,7 @@ func (r *rpcServer) SendToRouteSync(ctx context.Context,
|
||||
|
||||
routes := make([]*routing.Route, len(req.Routes))
|
||||
for i, route := range req.Routes {
|
||||
route, err := unmarshallRoute(route, graph)
|
||||
route, err := r.unmarshallRoute(route, graph)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -2528,7 +2528,7 @@ func (r *rpcServer) sendPaymentSync(ctx context.Context,
|
||||
|
||||
return &lnrpc.SendResponse{
|
||||
PaymentPreimage: resp.Preimage[:],
|
||||
PaymentRoute: marshallRoute(resp.Route),
|
||||
PaymentRoute: r.marshallRoute(resp.Route),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -3406,14 +3406,14 @@ func (r *rpcServer) QueryRoutes(ctx context.Context,
|
||||
}
|
||||
for i := int32(0); i < numRoutes; i++ {
|
||||
routeResp.Routes = append(
|
||||
routeResp.Routes, marshallRoute(routes[i]),
|
||||
routeResp.Routes, r.marshallRoute(routes[i]),
|
||||
)
|
||||
}
|
||||
|
||||
return routeResp, nil
|
||||
}
|
||||
|
||||
func marshallRoute(route *routing.Route) *lnrpc.Route {
|
||||
func (r *rpcServer) marshallRoute(route *routing.Route) *lnrpc.Route {
|
||||
resp := &lnrpc.Route{
|
||||
TotalTimeLock: route.TotalTimeLock,
|
||||
TotalFees: int64(route.TotalFees.ToSatoshis()),
|
||||
@ -3422,72 +3422,87 @@ func marshallRoute(route *routing.Route) *lnrpc.Route {
|
||||
TotalAmtMsat: int64(route.TotalAmount),
|
||||
Hops: make([]*lnrpc.Hop, len(route.Hops)),
|
||||
}
|
||||
graph := r.server.chanDB.ChannelGraph()
|
||||
incomingAmt := route.TotalAmount
|
||||
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{
|
||||
ChanId: hop.Channel.ChannelID,
|
||||
ChanCapacity: int64(hop.Channel.Bandwidth.ToSatoshis()),
|
||||
ChanId: hop.ChannelID,
|
||||
ChanCapacity: int64(chanCapacity),
|
||||
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
|
||||
AmtToForwardMsat: int64(hop.AmtToForward),
|
||||
Fee: int64(hop.Fee.ToSatoshis()),
|
||||
FeeMsat: int64(hop.Fee),
|
||||
Fee: int64(fee.ToSatoshis()),
|
||||
FeeMsat: int64(fee),
|
||||
Expiry: uint32(hop.OutgoingTimeLock),
|
||||
}
|
||||
incomingAmt = hop.AmtToForward
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
func unmarshallRoute(rpcroute *lnrpc.Route,
|
||||
func (r *rpcServer) unmarshallRoute(rpcroute *lnrpc.Route,
|
||||
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()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to fetch source node from graph "+
|
||||
"while unmarshaling route. %v", err)
|
||||
}
|
||||
|
||||
nodePubKeyBytes := node.PubKeyBytes[:]
|
||||
|
||||
hops := make([]*routing.Hop, len(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 {
|
||||
return nil, fmt.Errorf("unable to fetch channel edges by "+
|
||||
"channel ID for hop (%d): %v", i, err)
|
||||
}
|
||||
|
||||
var channelEdgePolicy *channeldb.ChannelEdgePolicy
|
||||
|
||||
var pubKeyBytes [33]byte
|
||||
switch {
|
||||
case bytes.Equal(node.PubKeyBytes[:], c1.Node.PubKeyBytes[:]):
|
||||
channelEdgePolicy = c2
|
||||
node = c2.Node
|
||||
case bytes.Equal(node.PubKeyBytes[:], c2.Node.PubKeyBytes[:]):
|
||||
channelEdgePolicy = c1
|
||||
node = c1.Node
|
||||
case bytes.Equal(nodePubKeyBytes[:], edgeInfo.NodeKey1Bytes[:]):
|
||||
pubKeyBytes = edgeInfo.NodeKey2Bytes
|
||||
case bytes.Equal(nodePubKeyBytes[:], edgeInfo.NodeKey2Bytes[:]):
|
||||
pubKeyBytes = edgeInfo.NodeKey1Bytes
|
||||
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{
|
||||
ChannelEdgePolicy: channelEdgePolicy,
|
||||
Bandwidth: lnwire.NewMSatFromSatoshis(
|
||||
btcutil.Amount(hop.ChanCapacity)),
|
||||
Chain: edgeInfo.ChainHash,
|
||||
}
|
||||
|
||||
route.Hops[i] = &routing.Hop{
|
||||
Channel: routingHop,
|
||||
hops[i] = &routing.Hop{
|
||||
OutgoingTimeLock: hop.Expiry,
|
||||
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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user