Merge pull request #1888 from joostjager/routestruct

lnrpc+routing: fix unmarshallRoute and simplify route structure
This commit is contained in:
Olaoluwa Osuntokun 2018-10-23 17:07:23 -07:00 committed by GitHub
commit fbd91feace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 269 additions and 276 deletions

@ -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))
}
}

@ -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
}