Merge pull request #2497 from joostjager/querysingleroute

lnrpc: deprecate QueryRoutes with more than one route
This commit is contained in:
Olaoluwa Osuntokun authored and GitHub committed 2019-03-12 21:15:18 -07:00
commit ad8849056b
12 files changed
+1458 -1107

No files matched your search

+229
View File
@@ -0,0 +1,229 @@
package routerrpc
import (
"encoding/hex"
"errors"
"fmt"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
context "golang.org/x/net/context"
)
// RouterBackend contains the backend implementation of the router rpc sub
// server calls.
type RouterBackend struct {
MaxPaymentMSat lnwire.MilliSatoshi
SelfNode routing.Vertex
FetchChannelCapacity func(chanID uint64) (btcutil.Amount, error)
FindRoutes func(source, target routing.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
numPaths uint32, finalExpiry ...uint16) (
[]*routing.Route, error)
}
// QueryRoutes attempts to query the daemons' Channel Router for a possible
// route to a target destination capable of carrying a specific amount of
// satoshis within the route's flow. The retuned route contains the full
// details required to craft and send an HTLC, also including the necessary
// information that should be present within the Sphinx packet encapsulated
// within the HTLC.
//
// TODO(roasbeef): should return a slice of routes in reality
// * create separate PR to send based on well formatted route
func (r *RouterBackend) QueryRoutes(ctx context.Context,
in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) {
parsePubKey := func(key string) (routing.Vertex, error) {
pubKeyBytes, err := hex.DecodeString(key)
if err != nil {
return routing.Vertex{}, err
}
if len(pubKeyBytes) != 33 {
return routing.Vertex{},
errors.New("invalid key length")
}
var v routing.Vertex
copy(v[:], pubKeyBytes)
return v, nil
}
// Parse the hex-encoded source and target public keys into full public
// key objects we can properly manipulate.
targetPubKey, err := parsePubKey(in.PubKey)
if err != nil {
return nil, err
}
var sourcePubKey routing.Vertex
if in.SourcePubKey != "" {
var err error
sourcePubKey, err = parsePubKey(in.SourcePubKey)
if err != nil {
return nil, err
}
} else {
// If no source is specified, use self.
sourcePubKey = r.SelfNode
}
// Currently, within the bootstrap phase of the network, we limit the
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
// satoshis.
amt := btcutil.Amount(in.Amt)
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > r.MaxPaymentMSat {
return nil, fmt.Errorf("payment of %v is too large, max payment "+
"allowed is %v", amt, r.MaxPaymentMSat.ToSatoshis())
}
// Unmarshall restrictions from request.
feeLimit := calculateFeeLimit(in.FeeLimit, amtMSat)
ignoredNodes := make(map[routing.Vertex]struct{})
for _, ignorePubKey := range in.IgnoredNodes {
if len(ignorePubKey) != 33 {
return nil, fmt.Errorf("invalid ignore node pubkey")
}
var ignoreVertex routing.Vertex
copy(ignoreVertex[:], ignorePubKey)
ignoredNodes[ignoreVertex] = struct{}{}
}
ignoredEdges := make(map[routing.EdgeLocator]struct{})
for _, ignoredEdge := range in.IgnoredEdges {
locator := routing.EdgeLocator{
ChannelID: ignoredEdge.ChannelId,
}
if ignoredEdge.DirectionReverse {
locator.Direction = 1
}
ignoredEdges[locator] = struct{}{}
}
restrictions := &routing.RestrictParams{
FeeLimit: feeLimit,
IgnoredNodes: ignoredNodes,
IgnoredEdges: ignoredEdges,
}
// numRoutes will default to 10 if not specified explicitly.
numRoutesIn := uint32(in.NumRoutes)
if numRoutesIn == 0 {
numRoutesIn = 10
}
// Query the channel router for a possible path to the destination that
// can carry `in.Amt` satoshis _including_ the total fee required on
// the route.
var (
routes []*routing.Route
findErr error
)
if in.FinalCltvDelta == 0 {
routes, findErr = r.FindRoutes(
sourcePubKey, targetPubKey, amtMSat, restrictions, numRoutesIn,
)
} else {
routes, findErr = r.FindRoutes(
sourcePubKey, targetPubKey, amtMSat, restrictions, numRoutesIn,
uint16(in.FinalCltvDelta),
)
}
if findErr != nil {
return nil, findErr
}
// As the number of returned routes can be less than the number of
// requested routes, we'll clamp down the length of the response to the
// minimum of the two.
numRoutes := uint32(len(routes))
if numRoutesIn < numRoutes {
numRoutes = numRoutesIn
}
// For each valid route, we'll convert the result into the format
// required by the RPC system.
routeResp := &lnrpc.QueryRoutesResponse{
Routes: make([]*lnrpc.Route, 0, in.NumRoutes),
}
for i := uint32(0); i < numRoutes; i++ {
routeResp.Routes = append(
routeResp.Routes,
r.MarshallRoute(routes[i]),
)
}
return routeResp, nil
}
// calculateFeeLimit returns the fee limit in millisatoshis. If a percentage
// based fee limit has been requested, we'll factor in the ratio provided with
// the amount of the payment.
func calculateFeeLimit(feeLimit *lnrpc.FeeLimit,
amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {
switch feeLimit.GetLimit().(type) {
case *lnrpc.FeeLimit_Fixed:
return lnwire.NewMSatFromSatoshis(
btcutil.Amount(feeLimit.GetFixed()),
)
case *lnrpc.FeeLimit_Percent:
return amount * lnwire.MilliSatoshi(feeLimit.GetPercent()) / 100
default:
// If a fee limit was not specified, we'll use the payment's
// amount as an upper bound in order to avoid payment attempts
// from incurring fees higher than the payment amount itself.
return amount
}
}
// MarshallRoute marshalls an internal route to an rpc route struct.
func (r *RouterBackend) MarshallRoute(route *routing.Route) *lnrpc.Route {
resp := &lnrpc.Route{
TotalTimeLock: route.TotalTimeLock,
TotalFees: int64(route.TotalFees.ToSatoshis()),
TotalFeesMsat: int64(route.TotalFees),
TotalAmt: int64(route.TotalAmount.ToSatoshis()),
TotalAmtMsat: int64(route.TotalAmount),
Hops: make([]*lnrpc.Hop, len(route.Hops)),
}
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.
chanCapacity, err := r.FetchChannelCapacity(hop.ChannelID)
if err != nil {
// 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.ChannelID,
ChanCapacity: int64(chanCapacity),
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
AmtToForwardMsat: int64(hop.AmtToForward),
Fee: int64(fee.ToSatoshis()),
FeeMsat: int64(fee),
Expiry: uint32(hop.OutgoingTimeLock),
PubKey: hex.EncodeToString(
hop.PubKeyBytes[:]),
}
incomingAmt = hop.AmtToForward
}
return resp
}
+126
View File
@@ -0,0 +1,126 @@
package routerrpc
import (
"bytes"
"context"
"encoding/hex"
"testing"
"github.com/btcsuite/btcutil"
"github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/lnrpc"
)
const (
destKey = "0286098b97bc843372b4426d4b276cea9aa2f48f0428d6f5b66ae101befc14f8b4"
ignoreNodeKey = "02f274f48f3c0d590449a6776e3ce8825076ac376e470e992246eebc565ef8bb2a"
)
var (
sourceKey = routing.Vertex{1, 2, 3}
)
// TestQueryRoutes asserts that query routes rpc parameters are properly parsed
// and passed onto path finding.
func TestQueryRoutes(t *testing.T) {
ignoreNodeBytes, err := hex.DecodeString(ignoreNodeKey)
if err != nil {
t.Fatal(err)
}
var ignoreNodeVertex routing.Vertex
copy(ignoreNodeVertex[:], ignoreNodeBytes)
destNodeBytes, err := hex.DecodeString(destKey)
if err != nil {
t.Fatal(err)
}
request := &lnrpc.QueryRoutesRequest{
PubKey: destKey,
Amt: 100000,
NumRoutes: 1,
FinalCltvDelta: 100,
FeeLimit: &lnrpc.FeeLimit{
Limit: &lnrpc.FeeLimit_Fixed{
Fixed: 250,
},
},
IgnoredNodes: [][]byte{ignoreNodeBytes},
IgnoredEdges: []*lnrpc.EdgeLocator{&lnrpc.EdgeLocator{
ChannelId: 555,
DirectionReverse: true,
}},
}
route := &routing.Route{}
findRoutes := func(source, target routing.Vertex,
amt lnwire.MilliSatoshi, restrictions *routing.RestrictParams,
numPaths uint32, finalExpiry ...uint16) (
[]*routing.Route, error) {
if int64(amt) != request.Amt*1000 {
t.Fatal("unexpected amount")
}
if numPaths != 1 {
t.Fatal("unexpected number of routes")
}
if source != sourceKey {
t.Fatal("unexpected source key")
}
if !bytes.Equal(target[:], destNodeBytes) {
t.Fatal("unexpected target key")
}
if restrictions.FeeLimit != 250*1000 {
t.Fatal("unexpected fee limit")
}
if len(restrictions.IgnoredEdges) != 1 {
t.Fatal("unexpected ignored edges map size")
}
if _, ok := restrictions.IgnoredEdges[routing.EdgeLocator{
ChannelID: 555, Direction: 1,
}]; !ok {
t.Fatal("unexpected ignored edge")
}
if len(restrictions.IgnoredNodes) != 1 {
t.Fatal("unexpected ignored nodes map size")
}
if _, ok := restrictions.IgnoredNodes[ignoreNodeVertex]; !ok {
t.Fatal("unexpected ignored node")
}
return []*routing.Route{
route,
}, nil
}
backend := &RouterBackend{
MaxPaymentMSat: lnwire.NewMSatFromSatoshis(1000000),
FindRoutes: findRoutes,
SelfNode: routing.Vertex{1, 2, 3},
FetchChannelCapacity: func(chanID uint64) (
btcutil.Amount, error) {
return 1, nil
},
}
resp, err := backend.QueryRoutes(context.Background(), request)
if err != nil {
t.Fatal(err)
}
if len(resp.Routes) != 1 {
t.Fatal("expected a single route response")
}
}
+666 -572
View File
File diff suppressed because it is too large. Load diff
+35 -2
View File
@@ -1467,8 +1467,11 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis /// The amount to send expressed in satoshis
int64 amt = 2; int64 amt = 2;
/// The max number of routes to return. /**
int32 num_routes = 3; Deprecated. The max number of routes to return. In the future, QueryRoutes
will only return a single route.
*/
int32 num_routes = 3 [deprecated = true];
/// An optional CLTV delta from the current height that should be used for the timelock of the final hop /// An optional CLTV delta from the current height that should be used for the timelock of the final hop
int32 final_cltv_delta = 4; int32 final_cltv_delta = 4;
@@ -1480,7 +1483,37 @@ message QueryRoutesRequest {
send the payment. send the payment.
*/ */
FeeLimit fee_limit = 5; FeeLimit fee_limit = 5;
/**
A list of nodes to ignore during path finding.
*/
repeated bytes ignored_nodes = 6;
/**
A list of edges to ignore during path finding.
*/
repeated EdgeLocator ignored_edges = 7;
/**
The source node where the request route should originated from. If empty,
self is assumed.
*/
string source_pub_key = 8;
} }
message EdgeLocator {
/// The short channel id of this edge.
uint64 channel_id = 1;
/**
The direction of this edge. If direction_reverse is false, the direction
of this edge is from the channel endpoint with the lexicographically smaller
pub key to the endpoint with the larger pub key. If direction_reverse is
is true, the edge goes the other way.
*/
bool direction_reverse = 2;
}
message QueryRoutesResponse { message QueryRoutesResponse {
repeated Route routes = 1 [json_name = "routes"]; repeated Route routes = 1 [json_name = "routes"];
} }
+19 -1
View File
@@ -564,7 +564,7 @@
}, },
{ {
"name": "num_routes", "name": "num_routes",
"description": "/ The max number of routes to return.", "description": "*\nDeprecated. The max number of routes to return. In the future, QueryRoutes\nwill only return a single route.",
"in": "query", "in": "query",
"required": false, "required": false,
"type": "integer", "type": "integer",
@@ -593,6 +593,24 @@
"required": false, "required": false,
"type": "string", "type": "string",
"format": "int64" "format": "int64"
},
{
"name": "ignored_nodes",
"description": "*\nA list of nodes to ignore during path finding.",
"in": "query",
"required": false,
"type": "array",
"items": {
"type": "string",
"format": "byte"
}
},
{
"name": "source_pub_key",
"description": "*\nThe source node where the request route should originated from. If empty,\nself is assumed.",
"in": "query",
"required": false,
"type": "string"
} }
], ],
"tags": [ "tags": [
+15 -9
View File
@@ -46,7 +46,7 @@ type missionControl struct {
// it was added to the prune view. Edges are added to this map if a // it was added to the prune view. Edges are added to this map if a
// caller reports to missionControl a failure localized to that edge // caller reports to missionControl a failure localized to that edge
// when sending a payment. // when sending a payment.
failedEdges map[edgeLocator]time.Time failedEdges map[EdgeLocator]time.Time
// failedVertexes maps a node's public key that should be pruned, to // failedVertexes maps a node's public key that should be pruned, to
// the time that it was added to the prune view. Vertexes are added to // the time that it was added to the prune view. Vertexes are added to
@@ -75,7 +75,7 @@ func newMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningN
qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *missionControl { qb func(*channeldb.ChannelEdgeInfo) lnwire.MilliSatoshi) *missionControl {
return &missionControl{ return &missionControl{
failedEdges: make(map[edgeLocator]time.Time), failedEdges: make(map[EdgeLocator]time.Time),
failedVertexes: make(map[Vertex]time.Time), failedVertexes: make(map[Vertex]time.Time),
selfNode: selfNode, selfNode: selfNode,
queryBandwidth: qb, queryBandwidth: qb,
@@ -89,7 +89,7 @@ func newMissionControl(g *channeldb.ChannelGraph, selfNode *channeldb.LightningN
// state of the wider network from the PoV of mission control compiled via HTLC // state of the wider network from the PoV of mission control compiled via HTLC
// routing attempts in the past. // routing attempts in the past.
type graphPruneView struct { type graphPruneView struct {
edges map[edgeLocator]struct{} edges map[EdgeLocator]struct{}
vertexes map[Vertex]struct{} vertexes map[Vertex]struct{}
} }
@@ -124,7 +124,7 @@ func (m *missionControl) GraphPruneView() graphPruneView {
// We'll also do the same for edges, but use the edgeDecay this time // We'll also do the same for edges, but use the edgeDecay this time
// rather than the decay for vertexes. // rather than the decay for vertexes.
edges := make(map[edgeLocator]struct{}) edges := make(map[EdgeLocator]struct{})
for edge, pruneTime := range m.failedEdges { for edge, pruneTime := range m.failedEdges {
if now.Sub(pruneTime) >= edgeDecay { if now.Sub(pruneTime) >= edgeDecay {
log.Tracef("Pruning decayed failure report for edge %v "+ log.Tracef("Pruning decayed failure report for edge %v "+
@@ -153,7 +153,7 @@ func (m *missionControl) GraphPruneView() graphPruneView {
// in order to populate additional edges to explore when finding a path to the // in order to populate additional edges to explore when finding a path to the
// payment's destination. // payment's destination.
func (m *missionControl) NewPaymentSession(routeHints [][]HopHint, func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
target *btcec.PublicKey) (*paymentSession, error) { target Vertex) (*paymentSession, error) {
viewSnapshot := m.GraphPruneView() viewSnapshot := m.GraphPruneView()
@@ -175,7 +175,13 @@ func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
if i != len(routeHint)-1 { if i != len(routeHint)-1 {
endNode.AddPubKey(routeHint[i+1].NodeID) endNode.AddPubKey(routeHint[i+1].NodeID)
} else { } else {
endNode.AddPubKey(target) targetPubKey, err := btcec.ParsePubKey(
target[:], btcec.S256(),
)
if err != nil {
return nil, err
}
endNode.AddPubKey(targetPubKey)
} }
// Finally, create the channel edge from the hop hint // Finally, create the channel edge from the hop hint
@@ -217,7 +223,7 @@ func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
pruneViewSnapshot: viewSnapshot, pruneViewSnapshot: viewSnapshot,
additionalEdges: edges, additionalEdges: edges,
bandwidthHints: bandwidthHints, bandwidthHints: bandwidthHints,
errFailedPolicyChans: make(map[edgeLocator]struct{}), errFailedPolicyChans: make(map[EdgeLocator]struct{}),
mc: m, mc: m,
}, nil }, nil
} }
@@ -231,7 +237,7 @@ func (m *missionControl) NewPaymentSessionFromRoutes(routes []*Route) *paymentSe
pruneViewSnapshot: m.GraphPruneView(), pruneViewSnapshot: m.GraphPruneView(),
haveRoutes: true, haveRoutes: true,
preBuiltRoutes: routes, preBuiltRoutes: routes,
errFailedPolicyChans: make(map[edgeLocator]struct{}), errFailedPolicyChans: make(map[EdgeLocator]struct{}),
mc: m, mc: m,
} }
} }
@@ -275,7 +281,7 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode,
// if no payment attempts have been made. // if no payment attempts have been made.
func (m *missionControl) ResetHistory() { func (m *missionControl) ResetHistory() {
m.Lock() m.Lock()
m.failedEdges = make(map[edgeLocator]time.Time) m.failedEdges = make(map[EdgeLocator]time.Time)
m.failedVertexes = make(map[Vertex]time.Time) m.failedVertexes = make(map[Vertex]time.Time)
m.Unlock() m.Unlock()
} }
+65 -58
View File
@@ -1,7 +1,6 @@
package routing package routing
import ( import (
"bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"math" "math"
@@ -210,7 +209,7 @@ 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 lnwire.MilliSatoshi, sourceVertex Vertex,
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32, pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
finalCLTVDelta uint16) (*Route, error) { finalCLTVDelta uint16) (*Route, error) {
@@ -310,13 +309,6 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
return nil, err return nil, err
} }
// 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 return newRoute, nil
} }
@@ -405,24 +397,24 @@ type graphParams struct {
bandwidthHints map[uint64]lnwire.MilliSatoshi bandwidthHints map[uint64]lnwire.MilliSatoshi
} }
// restrictParams wraps the set of restrictions passed to findPath that the // RestrictParams wraps the set of restrictions passed to findPath that the
// found path must adhere to. // found path must adhere to.
type restrictParams struct { type RestrictParams struct {
// ignoredNodes is an optional set of nodes that should be ignored if // IgnoredNodes is an optional set of nodes that should be ignored if
// encountered during path finding. // encountered during path finding.
ignoredNodes map[Vertex]struct{} IgnoredNodes map[Vertex]struct{}
// ignoredEdges is an optional set of edges that should be ignored if // IgnoredEdges is an optional set of edges that should be ignored if
// encountered during path finding. // encountered during path finding.
ignoredEdges map[edgeLocator]struct{} IgnoredEdges map[EdgeLocator]struct{}
// feeLimit is a maximum fee amount allowed to be used on the path from // FeeLimit is a maximum fee amount allowed to be used on the path from
// the source to the target. // the source to the target.
feeLimit lnwire.MilliSatoshi FeeLimit lnwire.MilliSatoshi
// outgoingChannelID is the channel that needs to be taken to the first // OutgoingChannelID is the channel that needs to be taken to the first
// hop. If nil, any channel may be used. // hop. If nil, any channel may be used.
outgoingChannelID *uint64 OutgoingChannelID *uint64
} }
// findPath attempts to find a path from the source node within the // findPath attempts to find a path from the source node within the
@@ -436,8 +428,7 @@ type restrictParams struct {
// destination node back to source. This is to properly accumulate fees // destination node back to source. This is to properly accumulate fees
// that need to be paid along the path and accurately check the amount // that need to be paid along the path and accurately check the amount
// to forward at every node against the available bandwidth. // to forward at every node against the available bandwidth.
func findPath(g *graphParams, r *restrictParams, func findPath(g *graphParams, r *RestrictParams, source, target Vertex,
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error) { amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error) {
var err error var err error
@@ -498,17 +489,14 @@ func findPath(g *graphParams, r *restrictParams,
} }
} }
sourceVertex := Vertex(sourceNode.PubKeyBytes)
// We can't always assume that the end destination is publicly // We can't always assume that the end destination is publicly
// advertised to the network and included in the graph.ForEachNode call // advertised to the network and included in the graph.ForEachNode call
// above, so we'll manually include the target node. The target node // above, so we'll manually include the target node. The target node
// charges no fee. Distance is set to 0, because this is the starting // charges no fee. Distance is set to 0, because this is the starting
// point of the graph traversal. We are searching backwards to get the // point of the graph traversal. We are searching backwards to get the
// fees first time right and correctly match channel bandwidth. // fees first time right and correctly match channel bandwidth.
targetVertex := NewVertex(target) targetNode := &channeldb.LightningNode{PubKeyBytes: target}
targetNode := &channeldb.LightningNode{PubKeyBytes: targetVertex} distance[target] = nodeWithDist{
distance[targetVertex] = nodeWithDist{
dist: 0, dist: 0,
node: targetNode, node: targetNode,
amountToReceive: amt, amountToReceive: amt,
@@ -520,6 +508,15 @@ func findPath(g *graphParams, r *restrictParams,
// mapped to within `next`. // mapped to within `next`.
next := make(map[Vertex]*channeldb.ChannelEdgePolicy) next := make(map[Vertex]*channeldb.ChannelEdgePolicy)
ignoredEdges := r.IgnoredEdges
if ignoredEdges == nil {
ignoredEdges = make(map[EdgeLocator]struct{})
}
ignoredNodes := r.IgnoredNodes
if ignoredNodes == nil {
ignoredNodes = make(map[Vertex]struct{})
}
// 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.
processEdge := func(fromNode *channeldb.LightningNode, processEdge := func(fromNode *channeldb.LightningNode,
@@ -532,7 +529,7 @@ func findPath(g *graphParams, r *restrictParams,
// skip it. // skip it.
// TODO(halseth): also ignore disable flags for non-local // TODO(halseth): also ignore disable flags for non-local
// channels if bandwidth hint is set? // channels if bandwidth hint is set?
isSourceChan := fromVertex == sourceVertex isSourceChan := fromVertex == source
edgeFlags := edge.ChannelFlags edgeFlags := edge.ChannelFlags
isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0 isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0
@@ -543,20 +540,20 @@ func findPath(g *graphParams, r *restrictParams,
// If we have an outgoing channel restriction and this is not // If we have an outgoing channel restriction and this is not
// the specified channel, skip it. // the specified channel, skip it.
if isSourceChan && r.outgoingChannelID != nil && if isSourceChan && r.OutgoingChannelID != nil &&
*r.outgoingChannelID != edge.ChannelID { *r.OutgoingChannelID != edge.ChannelID {
return return
} }
// If this vertex or edge has been black listed, then we'll // If this vertex or edge has been black listed, then we'll
// skip exploring this edge. // skip exploring this edge.
if _, ok := r.ignoredNodes[fromVertex]; ok { if _, ok := ignoredNodes[fromVertex]; ok {
return return
} }
locator := newEdgeLocator(edge) locator := newEdgeLocator(edge)
if _, ok := r.ignoredEdges[*locator]; ok { if _, ok := ignoredEdges[*locator]; ok {
return return
} }
@@ -595,7 +592,7 @@ func findPath(g *graphParams, r *restrictParams,
// node, no additional timelock is required. // node, no additional timelock is required.
var fee lnwire.MilliSatoshi var fee lnwire.MilliSatoshi
var timeLockDelta uint16 var timeLockDelta uint16
if fromVertex != sourceVertex { if fromVertex != source {
fee = computeFee(amountToSend, edge) fee = computeFee(amountToSend, edge)
timeLockDelta = edge.TimeLockDelta timeLockDelta = edge.TimeLockDelta
} }
@@ -610,7 +607,7 @@ func findPath(g *graphParams, r *restrictParams,
// Check if accumulated fees would exceed fee limit when this // Check if accumulated fees would exceed fee limit when this
// node would be added to the path. // node would be added to the path.
totalFee := amountToReceive - amt totalFee := amountToReceive - amt
if totalFee > r.feeLimit { if totalFee > r.FeeLimit {
return return
} }
@@ -664,7 +661,7 @@ func findPath(g *graphParams, r *restrictParams,
// To start, our target node will the sole item within our distance // To start, our target node will the sole item within our distance
// heap. // heap.
heap.Push(&nodeHeap, distance[targetVertex]) heap.Push(&nodeHeap, distance[target])
for nodeHeap.Len() != 0 { for nodeHeap.Len() != 0 {
// Fetch the node within the smallest distance from our source // Fetch the node within the smallest distance from our source
@@ -675,7 +672,7 @@ func findPath(g *graphParams, r *restrictParams,
// If we've reached our source (or we don't have any incoming // If we've reached our source (or we don't have any incoming
// edges), then we're done here and can exit the graph // edges), then we're done here and can exit the graph
// traversal early. // traversal early.
if bytes.Equal(bestNode.PubKeyBytes[:], sourceVertex[:]) { if bestNode.PubKeyBytes == source {
break break
} }
@@ -742,7 +739,7 @@ func findPath(g *graphParams, r *restrictParams,
// If the source node isn't found in the next hop map, then a path // If the source node isn't found in the next hop map, then a path
// doesn't exist, so we terminate in an error. // doesn't exist, so we terminate in an error.
if _, ok := next[sourceVertex]; !ok { if _, ok := next[source]; !ok {
return nil, newErrf(ErrNoPathFound, "unable to find a path to "+ return nil, newErrf(ErrNoPathFound, "unable to find a path to "+
"destination") "destination")
} }
@@ -750,8 +747,8 @@ func findPath(g *graphParams, r *restrictParams,
// Use the nextHop map to unravel the forward path from source to // Use the nextHop map to unravel the forward path from source to
// target. // target.
pathEdges := make([]*channeldb.ChannelEdgePolicy, 0, len(next)) pathEdges := make([]*channeldb.ChannelEdgePolicy, 0, len(next))
currentNode := sourceVertex currentNode := source
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles for currentNode != target { // TODO(roasbeef): assumes no cycles
// Determine the next hop forward using the next map. // Determine the next hop forward using the next map.
nextNode := next[currentNode] nextNode := next[currentNode]
@@ -787,12 +784,10 @@ func findPath(g *graphParams, r *restrictParams,
// algorithm, rather than attempting to use an unmodified path finding // algorithm, rather than attempting to use an unmodified path finding
// algorithm in a block box manner. // algorithm in a block box manner.
func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph, func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
source *channeldb.LightningNode, target *btcec.PublicKey, source, target Vertex, amt lnwire.MilliSatoshi,
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi, numPaths uint32, restrictions *RestrictParams, numPaths uint32,
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*channeldb.ChannelEdgePolicy, error) { bandwidthHints map[uint64]lnwire.MilliSatoshi) (
[][]*channeldb.ChannelEdgePolicy, error) {
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// TODO(roasbeef): modifying ordering within heap to eliminate final // TODO(roasbeef): modifying ordering within heap to eliminate final
// sorting step? // sorting step?
@@ -810,12 +805,7 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
graph: graph, graph: graph,
bandwidthHints: bandwidthHints, bandwidthHints: bandwidthHints,
}, },
&restrictParams{ restrictions, source, target, amt,
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: feeLimit,
},
source, target, amt,
) )
if err != nil { if err != nil {
log.Errorf("Unable to find path: %v", err) log.Errorf("Unable to find path: %v", err)
@@ -827,7 +817,7 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
// function properly. // function properly.
firstPath := make([]*channeldb.ChannelEdgePolicy, 0, len(startingPath)+1) firstPath := make([]*channeldb.ChannelEdgePolicy, 0, len(startingPath)+1)
firstPath = append(firstPath, &channeldb.ChannelEdgePolicy{ firstPath = append(firstPath, &channeldb.ChannelEdgePolicy{
Node: source, Node: &channeldb.LightningNode{PubKeyBytes: source},
}) })
firstPath = append(firstPath, startingPath...) firstPath = append(firstPath, startingPath...)
@@ -846,8 +836,15 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
// we'll exclude from the next path finding attempt. // we'll exclude from the next path finding attempt.
// These are required to ensure the paths are unique // These are required to ensure the paths are unique
// and loopless. // and loopless.
ignoredEdges = make(map[edgeLocator]struct{}) ignoredEdges := make(map[EdgeLocator]struct{})
ignoredVertexes = make(map[Vertex]struct{}) ignoredVertexes := make(map[Vertex]struct{})
for e := range restrictions.IgnoredEdges {
ignoredEdges[e] = struct{}{}
}
for n := range restrictions.IgnoredNodes {
ignoredVertexes[n] = struct{}{}
}
// Our spur node is the i-th node in the prior shortest // Our spur node is the i-th node in the prior shortest
// path, and our root path will be all nodes in the // path, and our root path will be all nodes in the
@@ -888,17 +885,27 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
// the Vertexes (other than the spur path) within the // the Vertexes (other than the spur path) within the
// root path removed, we'll attempt to find another // root path removed, we'll attempt to find another
// shortest path from the spur node to the destination. // shortest path from the spur node to the destination.
//
// TODO: Fee limit passed to spur path finding isn't
// correct, because it doesn't take into account the
// fees already paid on the root path.
//
// TODO: Outgoing channel restriction isn't obeyed for
// spur paths.
spurRestrictions := &RestrictParams{
IgnoredEdges: ignoredEdges,
IgnoredNodes: ignoredVertexes,
FeeLimit: restrictions.FeeLimit,
}
spurPath, err := findPath( spurPath, err := findPath(
&graphParams{ &graphParams{
tx: tx, tx: tx,
graph: graph, graph: graph,
bandwidthHints: bandwidthHints, bandwidthHints: bandwidthHints,
}, },
&restrictParams{ spurRestrictions, spurNode.PubKeyBytes,
ignoredNodes: ignoredVertexes, target, amt,
ignoredEdges: ignoredEdges,
feeLimit: feeLimit,
}, spurNode, target, amt,
) )
// If we weren't able to find a path, we'll continue to // If we weren't able to find a path, we'll continue to
+119 -219
View File
@@ -47,6 +47,12 @@ const (
noFeeLimit = lnwire.MilliSatoshi(math.MaxUint32) noFeeLimit = lnwire.MilliSatoshi(math.MaxUint32)
) )
var (
noRestrictions = &RestrictParams{
FeeLimit: noFeeLimit,
}
)
var ( var (
testSig = &btcec.Signature{ testSig = &btcec.Signature{
R: new(big.Int), R: new(big.Int),
@@ -156,7 +162,7 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
return nil, err return nil, err
} }
aliasMap := make(map[string]*btcec.PublicKey) aliasMap := make(map[string]Vertex)
var source *channeldb.LightningNode var source *channeldb.LightningNode
// First we insert all the nodes within the graph as vertexes. // First we insert all the nodes within the graph as vertexes.
@@ -183,14 +189,9 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
"must be unique!") "must be unique!")
} }
pub, err := btcec.ParsePubKey(pubBytes, btcec.S256())
if err != nil {
return nil, err
}
// If the alias is unique, then add the node to the // If the alias is unique, then add the node to the
// alias map for easy lookup. // alias map for easy lookup.
aliasMap[node.Alias] = pub aliasMap[node.Alias] = dbNode.PubKeyBytes
// If the node is tagged as the source, then we create a // If the node is tagged as the source, then we create a
// pointer to is so we can mark the source in the graph // pointer to is so we can mark the source in the graph
@@ -359,7 +360,7 @@ type testGraphInstance struct {
// aliasMap is a map from a node's alias to its public key. This type is // aliasMap is a map from a node's alias to its public key. This type is
// provided in order to allow easily look up from the human memorable alias // provided in order to allow easily look up from the human memorable alias
// to an exact node's public key. // to an exact node's public key.
aliasMap map[string]*btcec.PublicKey aliasMap map[string]Vertex
// privKeyMap maps a node alias to its private key. This is used to be // privKeyMap maps a node alias to its private key. This is used to be
// able to mock a remote node's signing behaviour. // able to mock a remote node's signing behaviour.
@@ -388,7 +389,7 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
return nil, err return nil, err
} }
aliasMap := make(map[string]*btcec.PublicKey) aliasMap := make(map[string]Vertex)
privKeyMap := make(map[string]*btcec.PrivateKey) privKeyMap := make(map[string]*btcec.PrivateKey)
nodeIndex := byte(0) nodeIndex := byte(0)
@@ -423,7 +424,7 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
return nil, err return nil, err
} }
aliasMap[alias] = pubKey aliasMap[alias] = dbNode.PubKeyBytes
nodeIndex++ nodeIndex++
return dbNode, nil return dbNode, nil
@@ -476,16 +477,13 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
AuthProof: &testAuthProof, AuthProof: &testAuthProof,
ChannelPoint: *fundingPoint, ChannelPoint: *fundingPoint,
Capacity: testChannel.Capacity, Capacity: testChannel.Capacity,
NodeKey1Bytes: aliasMap[testChannel.Node1.Alias],
BitcoinKey1Bytes: aliasMap[testChannel.Node1.Alias],
NodeKey2Bytes: aliasMap[testChannel.Node2.Alias],
BitcoinKey2Bytes: aliasMap[testChannel.Node2.Alias],
} }
node1Bytes := aliasMap[testChannel.Node1.Alias].SerializeCompressed()
node2Bytes := aliasMap[testChannel.Node2.Alias].SerializeCompressed()
copy(edgeInfo.NodeKey1Bytes[:], node1Bytes)
copy(edgeInfo.NodeKey2Bytes[:], node2Bytes)
copy(edgeInfo.BitcoinKey1Bytes[:], node1Bytes)
copy(edgeInfo.BitcoinKey2Bytes[:], node2Bytes)
err = graph.AddChannelEdge(&edgeInfo) err = graph.AddChannelEdge(&edgeInfo)
if err != nil && err != channeldb.ErrEdgeAlreadyExist { if err != nil && err != channeldb.ErrEdgeAlreadyExist {
return nil, err return nil, err
@@ -598,9 +596,6 @@ func TestFindLowestFeePath(t *testing.T) {
} }
sourceVertex := Vertex(sourceNode.PubKeyBytes) sourceVertex := Vertex(sourceNode.PubKeyBytes)
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
const ( const (
startingHeight = 100 startingHeight = 100
finalHopCLTV = 1 finalHopCLTV = 1
@@ -612,38 +607,35 @@ func TestFindLowestFeePath(t *testing.T) {
&graphParams{ &graphParams{
graph: testGraphInstance.graph, graph: testGraphInstance.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, paymentAmt, sourceNode.PubKeyBytes, target, paymentAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
} }
route, err := newRoute( route, err := newRoute(
paymentAmt, infinity, sourceVertex, path, startingHeight, paymentAmt, sourceVertex, path, startingHeight,
finalHopCLTV) finalHopCLTV)
if err != nil { if err != nil {
t.Fatalf("unable to create path: %v", err) t.Fatalf("unable to create path: %v", err)
} }
// Assert that the lowest fee route is returned. // Assert that the lowest fee route is returned.
if !bytes.Equal(route.Hops[1].PubKeyBytes[:], if route.Hops[1].PubKeyBytes != testGraphInstance.aliasMap["b"] {
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",
getAliasFromPubKey(route.Hops[1].PubKeyBytes[:], getAliasFromPubKey(route.Hops[1].PubKeyBytes,
testGraphInstance.aliasMap)) testGraphInstance.aliasMap))
} }
} }
func getAliasFromPubKey(pubKey []byte, func getAliasFromPubKey(pubKey Vertex,
aliases map[string]*btcec.PublicKey) string { aliases map[string]Vertex) string {
for alias, key := range aliases { for alias, key := range aliases {
if bytes.Equal(key.SerializeCompressed(), pubKey) { if key == pubKey {
return alias return alias
} }
} }
@@ -744,9 +736,6 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
} }
sourceVertex := Vertex(sourceNode.PubKeyBytes) sourceVertex := Vertex(sourceNode.PubKeyBytes)
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
const ( const (
startingHeight = 100 startingHeight = 100
finalHopCLTV = 1 finalHopCLTV = 1
@@ -758,12 +747,10 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
&graphParams{ &graphParams{
graph: graphInstance.graph, graph: graphInstance.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: test.feeLimit,
ignoredEdges: ignoredEdges,
feeLimit: test.feeLimit,
}, },
sourceNode, target, paymentAmt, sourceNode.PubKeyBytes, target, paymentAmt,
) )
if test.expectFailureNoPath { if test.expectFailureNoPath {
if err == nil { if err == nil {
@@ -776,7 +763,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
} }
route, err := newRoute( route, err := newRoute(
paymentAmt, test.feeLimit, sourceVertex, path, startingHeight, paymentAmt, sourceVertex, path, startingHeight,
finalHopCLTV, finalHopCLTV,
) )
if err != nil { if err != nil {
@@ -790,12 +777,11 @@ 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].PubKeyBytes[:], if route.Hops[i].PubKeyBytes != aliases[expectedHops[i].alias] {
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], i, expectedHops[i],
getAliasFromPubKey(route.Hops[i].PubKeyBytes[:], getAliasFromPubKey(route.Hops[i].PubKeyBytes,
aliases)) aliases))
} }
} }
@@ -904,6 +890,8 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
doge := &channeldb.LightningNode{} doge := &channeldb.LightningNode{}
doge.AddPubKey(dogePubKey) doge.AddPubKey(dogePubKey)
doge.Alias = "doge" doge.Alias = "doge"
copy(doge.PubKeyBytes[:], dogePubKeyBytes)
graph.aliasMap["doge"] = doge.PubKeyBytes
// Create the channel edge going from songoku to doge and include it in // Create the channel edge going from songoku to doge and include it in
// our map of additional edges. // our map of additional edges.
@@ -916,7 +904,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
} }
additionalEdges := map[Vertex][]*channeldb.ChannelEdgePolicy{ additionalEdges := map[Vertex][]*channeldb.ChannelEdgePolicy{
NewVertex(graph.aliasMap["songoku"]): {songokuToDoge}, graph.aliasMap["songoku"]: {songokuToDoge},
} }
// We should now be able to find a path from roasbeef to doge. // We should now be able to find a path from roasbeef to doge.
@@ -925,10 +913,10 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
graph: graph.graph, graph: graph.graph,
additionalEdges: additionalEdges, additionalEdges: additionalEdges,
}, },
&restrictParams{ &RestrictParams{
feeLimit: noFeeLimit, FeeLimit: noFeeLimit,
}, },
sourceNode, dogePubKey, paymentAmt, sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find private path to doge: %v", err) t.Fatalf("unable to find private path to doge: %v", err)
@@ -936,7 +924,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
// The path should represent the following hops: // The path should represent the following hops:
// roasbeef -> songoku -> doge // roasbeef -> songoku -> doge
assertExpectedPath(t, path, "songoku", "doge") assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
} }
func TestKShortestPathFinding(t *testing.T) { func TestKShortestPathFinding(t *testing.T) {
@@ -963,9 +951,12 @@ func TestKShortestPathFinding(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := graph.aliasMap["luoji"] target := graph.aliasMap["luoji"]
restrictions := &RestrictParams{
FeeLimit: noFeeLimit,
}
paths, err := findPaths( paths, err := findPaths(
nil, graph.graph, sourceNode, target, paymentAmt, noFeeLimit, 100, nil, graph.graph, sourceNode.PubKeyBytes, target, paymentAmt,
nil, restrictions, 100, nil,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find paths between roasbeef and "+ t.Fatalf("unable to find paths between roasbeef and "+
@@ -985,10 +976,12 @@ func TestKShortestPathFinding(t *testing.T) {
} }
// The first route should be a direct route to luo ji. // The first route should be a direct route to luo ji.
assertExpectedPath(t, paths[0], "roasbeef", "luoji") assertExpectedPath(t, graph.aliasMap, paths[0], "roasbeef", "luoji")
// The second route should be a route to luo ji via satoshi. // The second route should be a route to luo ji via satoshi.
assertExpectedPath(t, paths[1], "roasbeef", "satoshi", "luoji") assertExpectedPath(
t, graph.aliasMap, paths[1], "roasbeef", "satoshi", "luoji",
)
} }
// TestNewRoute tests whether the construction of hop payloads by newRoute // TestNewRoute tests whether the construction of hop payloads by newRoute
@@ -1056,8 +1049,6 @@ func TestNewRoute(t *testing.T) {
// expectedErrorCode indicates the expected error code when // expectedErrorCode indicates the expected error code when
// expectError is true. // expectError is true.
expectedErrorCode errorCode expectedErrorCode errorCode
feeLimit lnwire.MilliSatoshi
}{ }{
{ {
// For a single hop payment, no fees are expected to be paid. // For a single hop payment, no fees are expected to be paid.
@@ -1070,7 +1061,6 @@ func TestNewRoute(t *testing.T) {
expectedTimeLocks: []uint32{1}, expectedTimeLocks: []uint32{1},
expectedTotalAmount: 100000, expectedTotalAmount: 100000,
expectedTotalTimeLock: 1, expectedTotalTimeLock: 1,
feeLimit: noFeeLimit,
}, { }, {
// For a two hop payment, only the fee for the first hop // For a two hop payment, only the fee for the first hop
// needs to be paid. The destination hop does not require // needs to be paid. The destination hop does not require
@@ -1085,7 +1075,6 @@ func TestNewRoute(t *testing.T) {
expectedTimeLocks: []uint32{1, 1}, expectedTimeLocks: []uint32{1, 1},
expectedTotalAmount: 100130, expectedTotalAmount: 100130,
expectedTotalTimeLock: 6, expectedTotalTimeLock: 6,
feeLimit: noFeeLimit,
}, { }, {
// 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
@@ -1103,7 +1092,6 @@ func TestNewRoute(t *testing.T) {
expectedTotalAmount: 100002, expectedTotalAmount: 100002,
expectedTimeLocks: []uint32{4, 1, 1}, expectedTimeLocks: []uint32{4, 1, 1},
expectedTotalTimeLock: 9, expectedTotalTimeLock: 9,
feeLimit: noFeeLimit,
}, { }, {
// A three hop payment where the fee of the first hop // A three hop payment where the fee of the first hop
// is slightly higher (11) than the fee at the second hop, // is slightly higher (11) than the fee at the second hop,
@@ -1119,7 +1107,6 @@ func TestNewRoute(t *testing.T) {
expectedTotalAmount: 102010, expectedTotalAmount: 102010,
expectedTimeLocks: []uint32{4, 1, 1}, expectedTimeLocks: []uint32{4, 1, 1},
expectedTotalTimeLock: 9, expectedTotalTimeLock: 9,
feeLimit: noFeeLimit,
}, { }, {
// A three hop payment where the fee policies of the first and // A three hop payment where the fee policies of the first and
// second hop are just high enough to show the fee carry over // second hop are just high enough to show the fee carry over
@@ -1141,53 +1128,6 @@ func TestNewRoute(t *testing.T) {
expectedTotalAmount: 101101, expectedTotalAmount: 101101,
expectedTimeLocks: []uint32{4, 1, 1}, expectedTimeLocks: []uint32{4, 1, 1},
expectedTotalTimeLock: 9, expectedTotalTimeLock: 9,
feeLimit: noFeeLimit,
},
// Check fee limit behaviour
{
name: "two hop success with fee limit (greater)",
paymentAmount: 100000,
hops: []*channeldb.ChannelEdgePolicy{
createHop(0, 1000, 1000000, 144),
createHop(0, 1000, 1000000, 144),
},
expectedTotalAmount: 100100,
expectedFees: []lnwire.MilliSatoshi{100, 0},
expectedTimeLocks: []uint32{1, 1},
expectedTotalTimeLock: 145,
feeLimit: 150,
}, {
name: "two hop success with fee limit (equal)",
paymentAmount: 100000,
hops: []*channeldb.ChannelEdgePolicy{
createHop(0, 1000, 1000000, 144),
createHop(0, 1000, 1000000, 144),
},
expectedTotalAmount: 100100,
expectedFees: []lnwire.MilliSatoshi{100, 0},
expectedTimeLocks: []uint32{1, 1},
expectedTotalTimeLock: 145,
feeLimit: 100,
}, {
name: "two hop failure with fee limit (smaller)",
paymentAmount: 100000,
hops: []*channeldb.ChannelEdgePolicy{
createHop(0, 1000, 1000000, 144),
createHop(0, 1000, 1000000, 144),
},
feeLimit: 50,
expectError: true,
expectedErrorCode: ErrFeeLimitExceeded,
}, {
name: "two hop failure with fee limit (zero)",
paymentAmount: 100000,
hops: []*channeldb.ChannelEdgePolicy{
createHop(0, 1000, 1000000, 144),
createHop(0, 1000, 1000000, 144),
},
feeLimit: 0,
expectError: true,
expectedErrorCode: ErrFeeLimitExceeded,
}} }}
for _, testCase := range testCases { for _, testCase := range testCases {
@@ -1238,7 +1178,6 @@ func TestNewRoute(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) { t.Run(testCase.name, func(t *testing.T) {
route, err := newRoute(testCase.paymentAmount, route, err := newRoute(testCase.paymentAmount,
testCase.feeLimit,
sourceVertex, testCase.hops, startingHeight, sourceVertex, testCase.hops, startingHeight,
finalHopCLTV) finalHopCLTV)
@@ -1278,9 +1217,6 @@ func TestNewRoutePathTooLong(t *testing.T) {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
// We start by confirming that routing a payment 20 hops away is // We start by confirming that routing a payment 20 hops away is
@@ -1290,12 +1226,10 @@ func TestNewRoutePathTooLong(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, paymentAmt, sourceNode.PubKeyBytes, target, paymentAmt,
) )
if err != nil { if err != nil {
t.Fatalf("path should have been found") t.Fatalf("path should have been found")
@@ -1308,12 +1242,10 @@ func TestNewRoutePathTooLong(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, paymentAmt, sourceNode.PubKeyBytes, target, paymentAmt,
) )
if err == nil { if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+ t.Fatalf("should not have been able to find path, supposed to be "+
@@ -1337,9 +1269,6 @@ func TestPathNotAvailable(t *testing.T) {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// With the test graph loaded, we'll test that queries for target that // With the test graph loaded, we'll test that queries for target that
// are either unreachable within the graph, or unknown result in an // are either unreachable within the graph, or unknown result in an
// error. // error.
@@ -1348,21 +1277,17 @@ func TestPathNotAvailable(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to parse bytes: %v", err) t.Fatalf("unable to parse bytes: %v", err)
} }
unknownNode, err := btcec.ParsePubKey(unknownNodeBytes, btcec.S256()) var unknownNode Vertex
if err != nil { copy(unknownNode[:], unknownNodeBytes)
t.Fatalf("unable to parse pubkey: %v", err)
}
_, err = findPath( _, err = findPath(
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, unknownNode, 100, sourceNode.PubKeyBytes, unknownNode, 100,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("path shouldn't have been found: %v", err) t.Fatalf("path shouldn't have been found: %v", err)
@@ -1382,8 +1307,6 @@ func TestPathInsufficientCapacity(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// Next, test that attempting to find a path in which the current // Next, test that attempting to find a path in which the current
// channel graph cannot support due to insufficient capacity triggers // channel graph cannot support due to insufficient capacity triggers
@@ -1400,12 +1323,10 @@ func TestPathInsufficientCapacity(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -1427,8 +1348,6 @@ func TestRouteFailMinHTLC(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// We'll not attempt to route an HTLC of 10 SAT from roasbeef to Son // We'll not attempt to route an HTLC of 10 SAT from roasbeef to Son
// Goku. However, the min HTLC of Son Goku is 1k SAT, as a result, this // Goku. However, the min HTLC of Son Goku is 1k SAT, as a result, this
@@ -1439,12 +1358,10 @@ func TestRouteFailMinHTLC(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -1492,8 +1409,6 @@ func TestRouteFailMaxHTLC(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// First, attempt to send a payment greater than the max HTLC we are // First, attempt to send a payment greater than the max HTLC we are
// about to set, which should succeed. // about to set, which should succeed.
@@ -1503,12 +1418,10 @@ func TestRouteFailMaxHTLC(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if err != nil { if err != nil {
t.Fatalf("graph should've been able to support payment: %v", err) t.Fatalf("graph should've been able to support payment: %v", err)
@@ -1529,12 +1442,10 @@ func TestRouteFailMaxHTLC(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -1559,8 +1470,6 @@ func TestRouteFailDisabledEdge(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// First, we'll try to route from roasbeef -> sophon. This should // First, we'll try to route from roasbeef -> sophon. This should
// succeed without issue, and return a single path via phamnuwen // succeed without issue, and return a single path via phamnuwen
@@ -1570,12 +1479,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@@ -1602,12 +1509,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@@ -1631,12 +1536,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -1659,8 +1562,6 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// First, we'll try to route from roasbeef -> sophon. This should // First, we'll try to route from roasbeef -> sophon. This should
// succeed without issue, and return a path via songoku, as that's the // succeed without issue, and return a path via songoku, as that's the
@@ -1671,17 +1572,15 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
&graphParams{ &graphParams{
graph: graph.graph, graph: graph.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
} }
assertExpectedPath(t, path, "songoku", "sophon") assertExpectedPath(t, graph.aliasMap, path, "songoku", "sophon")
// Now we'll set the bandwidth of the edge roasbeef->songoku and // Now we'll set the bandwidth of the edge roasbeef->songoku and
// roasbeef->phamnuwen to 0. // roasbeef->phamnuwen to 0.
@@ -1699,12 +1598,10 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph, graph: graph.graph,
bandwidthHints: bandwidths, bandwidthHints: bandwidths,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if !IsError(err, ErrNoPathFound) { if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err) t.Fatalf("graph shouldn't be able to support payment: %v", err)
@@ -1721,17 +1618,15 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph, graph: graph.graph,
bandwidthHints: bandwidths, bandwidthHints: bandwidths,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
} }
assertExpectedPath(t, path, "phamnuwen", "sophon") assertExpectedPath(t, graph.aliasMap, path, "phamnuwen", "sophon")
// Finally, set the roasbeef->songoku bandwidth, but also set its // Finally, set the roasbeef->songoku bandwidth, but also set its
// disable flag. // disable flag.
@@ -1756,17 +1651,15 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph, graph: graph.graph,
bandwidthHints: bandwidths, bandwidthHints: bandwidths,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
}, },
sourceNode, target, payAmt, sourceNode.PubKeyBytes, target, payAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
} }
assertExpectedPath(t, path, "songoku", "sophon") assertExpectedPath(t, graph.aliasMap, path, "songoku", "sophon")
} }
func TestPathInsufficientCapacityWithFee(t *testing.T) { func TestPathInsufficientCapacityWithFee(t *testing.T) {
@@ -1803,7 +1696,11 @@ func TestPathFindSpecExample(t *testing.T) {
// Carol, so we set "B" as the source node so path finding starts from // Carol, so we set "B" as the source node so path finding starts from
// Bob. // Bob.
bob := ctx.aliases["B"] bob := ctx.aliases["B"]
bobNode, err := ctx.graph.FetchLightningNode(bob) bobKey, err := btcec.ParsePubKey(bob[:], btcec.S256())
if err != nil {
t.Fatal(err)
}
bobNode, err := ctx.graph.FetchLightningNode(bobKey)
if err != nil { if err != nil {
t.Fatalf("unable to find bob: %v", err) t.Fatalf("unable to find bob: %v", err)
} }
@@ -1814,7 +1711,9 @@ func TestPathFindSpecExample(t *testing.T) {
// Query for a route of 4,999,999 mSAT to carol. // Query for a route of 4,999,999 mSAT to carol.
carol := ctx.aliases["C"] carol := ctx.aliases["C"]
const amt lnwire.MilliSatoshi = 4999999 const amt lnwire.MilliSatoshi = 4999999
routes, err := ctx.router.FindRoutes(carol, amt, noFeeLimit, 100) routes, err := ctx.router.FindRoutes(
bobNode.PubKeyBytes, carol, amt, noRestrictions, 100,
)
if err != nil { if err != nil {
t.Fatalf("unable to find route: %v", err) t.Fatalf("unable to find route: %v", err)
} }
@@ -1857,7 +1756,11 @@ func TestPathFindSpecExample(t *testing.T) {
// Next, we'll set A as the source node so we can assert that we create // Next, we'll set A as the source node so we can assert that we create
// the proper route for any queries starting with Alice. // the proper route for any queries starting with Alice.
alice := ctx.aliases["A"] alice := ctx.aliases["A"]
aliceNode, err := ctx.graph.FetchLightningNode(alice) aliceKey, err := btcec.ParsePubKey(alice[:], btcec.S256())
if err != nil {
t.Fatal(err)
}
aliceNode, err := ctx.graph.FetchLightningNode(aliceKey)
if err != nil { if err != nil {
t.Fatalf("unable to find alice: %v", err) t.Fatalf("unable to find alice: %v", err)
} }
@@ -1869,13 +1772,15 @@ func TestPathFindSpecExample(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("unable to retrieve source node: %v", err) t.Fatalf("unable to retrieve source node: %v", err)
} }
if !bytes.Equal(source.PubKeyBytes[:], alice.SerializeCompressed()) { if source.PubKeyBytes != alice {
t.Fatalf("source node not set") t.Fatalf("source node not set")
} }
// We'll now request a route from A -> B -> C. // We'll now request a route from A -> B -> C.
ctx.router.routeCache = make(map[routeTuple][]*Route) ctx.router.routeCache = make(map[routeTuple][]*Route)
routes, err = ctx.router.FindRoutes(carol, amt, noFeeLimit, 100) routes, err = ctx.router.FindRoutes(
source.PubKeyBytes, carol, amt, noRestrictions, 100,
)
if err != nil { if err != nil {
t.Fatalf("unable to find routes: %v", err) t.Fatalf("unable to find routes: %v", err)
} }
@@ -2002,15 +1907,15 @@ func TestPathFindSpecExample(t *testing.T) {
} }
} }
func assertExpectedPath(t *testing.T, path []*channeldb.ChannelEdgePolicy, func assertExpectedPath(t *testing.T, aliasMap map[string]Vertex,
nodeAliases ...string) { 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")
} }
for i, hop := range path { for i, hop := range path {
if hop.Node.Alias != nodeAliases[i] { if hop.Node.PubKeyBytes != aliasMap[nodeAliases[i]] {
t.Fatalf("expected %v to be pos #%v in hop, instead "+ t.Fatalf("expected %v to be pos #%v in hop, instead "+
"%v was", nodeAliases[i], i, hop.Node.Alias) "%v was", nodeAliases[i], i, hop.Node.Alias)
} }
@@ -2076,9 +1981,6 @@ func TestRestrictOutgoingChannel(t *testing.T) {
} }
sourceVertex := Vertex(sourceNode.PubKeyBytes) sourceVertex := Vertex(sourceNode.PubKeyBytes)
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
const ( const (
startingHeight = 100 startingHeight = 100
finalHopCLTV = 1 finalHopCLTV = 1
@@ -2094,19 +1996,17 @@ func TestRestrictOutgoingChannel(t *testing.T) {
&graphParams{ &graphParams{
graph: testGraphInstance.graph, graph: testGraphInstance.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoredVertexes, FeeLimit: noFeeLimit,
ignoredEdges: ignoredEdges, OutgoingChannelID: &outgoingChannelID,
feeLimit: noFeeLimit,
outgoingChannelID: &outgoingChannelID,
}, },
sourceNode, target, paymentAmt, sourceVertex, target, paymentAmt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
} }
route, err := newRoute( route, err := newRoute(
paymentAmt, infinity, sourceVertex, path, startingHeight, paymentAmt, sourceVertex, path, startingHeight,
finalHopCLTV, finalHopCLTV,
) )
if err != nil { if err != nil {
+11 -11
View File
@@ -27,7 +27,7 @@ type paymentSession struct {
// source of policy related routing failures during this payment attempt. // source of policy related routing failures during this payment attempt.
// We'll use this map to prune out channels when the first error may not // We'll use this map to prune out channels when the first error may not
// require pruning, but any subsequent ones do. // require pruning, but any subsequent ones do.
errFailedPolicyChans map[edgeLocator]struct{} errFailedPolicyChans map[EdgeLocator]struct{}
mc *missionControl mc *missionControl
@@ -61,7 +61,7 @@ func (p *paymentSession) ReportVertexFailure(v Vertex) {
// retrying an edge after its pruning has expired. // retrying an edge after its pruning has expired.
// //
// TODO(roasbeef): also add value attempted to send and capacity of channel // TODO(roasbeef): also add value attempted to send and capacity of channel
func (p *paymentSession) ReportEdgeFailure(e *edgeLocator) { func (p *paymentSession) ReportEdgeFailure(e *EdgeLocator) {
log.Debugf("Reporting edge %v failure to Mission Control", e) log.Debugf("Reporting edge %v failure to Mission Control", e)
// First, we'll add the failed edge to our local prune view snapshot. // First, we'll add the failed edge to our local prune view snapshot.
@@ -82,7 +82,7 @@ func (p *paymentSession) ReportEdgeFailure(e *edgeLocator) {
// pruned. This is to prevent nodes from keeping us busy by continuously sending // pruned. This is to prevent nodes from keeping us busy by continuously sending
// new channel updates. // new channel updates.
func (p *paymentSession) ReportEdgePolicyFailure( func (p *paymentSession) ReportEdgePolicyFailure(
errSource Vertex, failedEdge *edgeLocator) { errSource Vertex, failedEdge *EdgeLocator) {
// Check to see if we've already reported a policy related failure for // Check to see if we've already reported a policy related failure for
// this channel. If so, then we'll prune out the vertex. // this channel. If so, then we'll prune out the vertex.
@@ -147,13 +147,14 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
additionalEdges: p.additionalEdges, additionalEdges: p.additionalEdges,
bandwidthHints: p.bandwidthHints, bandwidthHints: p.bandwidthHints,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: pruneView.vertexes, IgnoredNodes: pruneView.vertexes,
ignoredEdges: pruneView.edges, IgnoredEdges: pruneView.edges,
feeLimit: payment.FeeLimit, FeeLimit: payment.FeeLimit,
outgoingChannelID: payment.OutgoingChannelID, OutgoingChannelID: payment.OutgoingChannelID,
}, },
p.mc.selfNode, payment.Target, payment.Amount, p.mc.selfNode.PubKeyBytes, payment.Target,
payment.Amount,
) )
if err != nil { if err != nil {
return nil, err return nil, err
@@ -163,8 +164,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
// a route by applying the time-lock and fee requirements. // a route by applying the time-lock and fee requirements.
sourceVertex := Vertex(p.mc.selfNode.PubKeyBytes) sourceVertex := Vertex(p.mc.selfNode.PubKeyBytes)
route, err := newRoute( route, err := newRoute(
payment.Amount, payment.FeeLimit, sourceVertex, path, height, payment.Amount, sourceVertex, path, height, finalCltvDelta,
finalCltvDelta,
) )
if err != nil { if err != nil {
// TODO(roasbeef): return which edge/vertex didn't work // TODO(roasbeef): return which edge/vertex didn't work
+46 -49
View File
@@ -215,18 +215,20 @@ func newRouteTuple(amt lnwire.MilliSatoshi, dest []byte) routeTuple {
return r return r
} }
// edgeLocator is a struct used to identify a specific edge. The direction // EdgeLocator is a struct used to identify a specific edge.
// fields takes the value of 0 or 1 and is identical in definition to the type EdgeLocator struct {
// channel direction flag. A value of 0 means the direction from the lower node // ChannelID is the channel of this edge.
// pubkey to the higher. ChannelID uint64
type edgeLocator struct {
channelID uint64 // Direction takes the value of 0 or 1 and is identical in definition to
direction uint8 // the channel direction flag. A value of 0 means the direction from the
// lower node pubkey to the higher.
Direction uint8
} }
// newEdgeLocatorByPubkeys returns an edgeLocator based on its end point // newEdgeLocatorByPubkeys returns an edgeLocator based on its end point
// pubkeys. // pubkeys.
func newEdgeLocatorByPubkeys(channelID uint64, fromNode, toNode *Vertex) *edgeLocator { func newEdgeLocatorByPubkeys(channelID uint64, fromNode, toNode *Vertex) *EdgeLocator {
// Determine direction based on lexicographical ordering of both // Determine direction based on lexicographical ordering of both
// pubkeys. // pubkeys.
var direction uint8 var direction uint8
@@ -234,24 +236,24 @@ func newEdgeLocatorByPubkeys(channelID uint64, fromNode, toNode *Vertex) *edgeLo
direction = 1 direction = 1
} }
return &edgeLocator{ return &EdgeLocator{
channelID: channelID, ChannelID: channelID,
direction: direction, Direction: direction,
} }
} }
// newEdgeLocator extracts an edgeLocator based for a full edge policy // newEdgeLocator extracts an edgeLocator based for a full edge policy
// structure. // structure.
func newEdgeLocator(edge *channeldb.ChannelEdgePolicy) *edgeLocator { func newEdgeLocator(edge *channeldb.ChannelEdgePolicy) *EdgeLocator {
return &edgeLocator{ return &EdgeLocator{
channelID: edge.ChannelID, ChannelID: edge.ChannelID,
direction: uint8(edge.ChannelFlags & lnwire.ChanUpdateDirection), Direction: uint8(edge.ChannelFlags & lnwire.ChanUpdateDirection),
} }
} }
// String returns a human readable version of the edgeLocator values. // String returns a human readable version of the edgeLocator values.
func (e *edgeLocator) String() string { func (e *EdgeLocator) String() string {
return fmt.Sprintf("%v:%v", e.channelID, e.direction) return fmt.Sprintf("%v:%v", e.ChannelID, e.Direction)
} }
// ChannelRouter is the layer 3 router within the Lightning stack. Below the // ChannelRouter is the layer 3 router within the Lightning stack. Below the
@@ -1266,7 +1268,7 @@ type routingMsg struct {
// 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 [][]*channeldb.ChannelEdgePolicy, func pathsToFeeSortedRoutes(source Vertex, paths [][]*channeldb.ChannelEdgePolicy,
finalCLTVDelta uint16, amt, feeLimit lnwire.MilliSatoshi, finalCLTVDelta uint16, amt lnwire.MilliSatoshi,
currentHeight uint32) ([]*Route, error) { currentHeight uint32) ([]*Route, error) {
validRoutes := make([]*Route, 0, len(paths)) validRoutes := make([]*Route, 0, len(paths))
@@ -1275,8 +1277,7 @@ func pathsToFeeSortedRoutes(source Vertex, paths [][]*channeldb.ChannelEdgePolic
// hop in the path as it contains a "self-hop" that is inserted // hop in the path as it contains a "self-hop" that is inserted
// by our KSP algorithm. // by our KSP algorithm.
route, err := newRoute( route, err := newRoute(
amt, feeLimit, source, path[1:], currentHeight, amt, source, path[1:], currentHeight, finalCLTVDelta,
finalCLTVDelta,
) )
if err != nil { if err != nil {
// TODO(roasbeef): report straw breaking edge? // TODO(roasbeef): report straw breaking edge?
@@ -1324,8 +1325,8 @@ func pathsToFeeSortedRoutes(source Vertex, paths [][]*channeldb.ChannelEdgePolic
// the required fee and time lock values running backwards along the route. The // the required fee and time lock values running backwards along the route. The
// route that will be ranked the highest is the one with the lowest cumulative // route that will be ranked the highest is the one with the lowest cumulative
// fee along the route. // fee along the route.
func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey, func (r *ChannelRouter) FindRoutes(source, target Vertex,
amt, feeLimit lnwire.MilliSatoshi, numPaths uint32, amt lnwire.MilliSatoshi, restrictions *RestrictParams, numPaths uint32,
finalExpiry ...uint16) ([]*Route, error) { finalExpiry ...uint16) ([]*Route, error) {
var finalCLTVDelta uint16 var finalCLTVDelta uint16
@@ -1335,13 +1336,16 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
finalCLTVDelta = finalExpiry[0] finalCLTVDelta = finalExpiry[0]
} }
dest := target.SerializeCompressed() log.Debugf("Searching for path to %x, sending %v", target, amt)
log.Debugf("Searching for path to %x, sending %v", dest, amt)
// Before attempting to perform a series of graph traversals to find // Before attempting to perform a series of graph traversals to find the
// the k-shortest paths to the destination, we'll first consult our // k-shortest paths to the destination, we'll first consult our path
// path cache // cache
rt := newRouteTuple(amt, dest) //
// TODO: Route cache should store all request parameters instead of just
// amt and target. Currently false positives are returned if just the
// restrictions (fee limit, ignore lists) or finalExpiry are different.
rt := newRouteTuple(amt, target[:])
r.routeCacheMtx.RLock() r.routeCacheMtx.RLock()
routes, ok := r.routeCache[rt] routes, ok := r.routeCache[rt]
r.routeCacheMtx.RUnlock() r.routeCacheMtx.RUnlock()
@@ -1360,11 +1364,10 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// We can short circuit the routing by opportunistically checking to // We can short circuit the routing by opportunistically checking to
// see if the target vertex event exists in the current graph. // see if the target vertex event exists in the current graph.
targetVertex := NewVertex(target) if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil {
if _, exists, err := r.cfg.Graph.HasLightningNode(targetVertex); err != nil {
return nil, err return nil, err
} else if !exists { } else if !exists {
log.Debugf("Target %x is not in known graph", dest) log.Debugf("Target %x is not in known graph", target)
return nil, newErrf(ErrTargetNotInNetwork, "target not found") return nil, newErrf(ErrTargetNotInNetwork, "target not found")
} }
@@ -1395,8 +1398,8 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// we'll execute our KSP algorithm to find the k-shortest paths from // we'll execute our KSP algorithm to find the k-shortest paths from
// our source to the destination. // our source to the destination.
shortestPaths, err := findPaths( shortestPaths, err := findPaths(
tx, r.cfg.Graph, r.selfNode, target, amt, feeLimit, numPaths, tx, r.cfg.Graph, source, target, amt, restrictions,
bandwidthHints, numPaths, bandwidthHints,
) )
if err != nil { if err != nil {
tx.Rollback() tx.Rollback()
@@ -1412,7 +1415,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// factored in. // factored in.
sourceVertex := Vertex(r.selfNode.PubKeyBytes) sourceVertex := Vertex(r.selfNode.PubKeyBytes)
validRoutes, err := pathsToFeeSortedRoutes( validRoutes, err := pathsToFeeSortedRoutes(
sourceVertex, shortestPaths, finalCLTVDelta, amt, feeLimit, sourceVertex, shortestPaths, finalCLTVDelta, amt,
uint32(currentHeight), uint32(currentHeight),
) )
if err != nil { if err != nil {
@@ -1420,7 +1423,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
} }
go log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes), go log.Tracef("Obtained %v paths sending %v to %x: %v", len(validRoutes),
amt, dest, newLogClosure(func() string { amt, target, newLogClosure(func() string {
return spew.Sdump(validRoutes) return spew.Sdump(validRoutes)
}), }),
) )
@@ -1512,7 +1515,7 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte,
// final destination. // final destination.
type LightningPayment struct { type LightningPayment struct {
// Target is the node in which the payment should be routed towards. // Target is the node in which the payment should be routed towards.
Target *btcec.PublicKey Target Vertex
// Amount is the value of the payment to send through the network in // Amount is the value of the payment to send through the network in
// milli-satoshis. // milli-satoshis.
@@ -1607,12 +1610,6 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment,
log.Tracef("Dispatching route for lightning payment: %v", log.Tracef("Dispatching route for lightning payment: %v",
newLogClosure(func() string { newLogClosure(func() string {
// Remove the public key curve parameters when logging
// the route to prevent spamming the logs.
if payment.Target != nil {
payment.Target.Curve = nil
}
for _, routeHint := range payment.RouteHints { for _, routeHint := range payment.RouteHints {
for _, hopHint := range routeHint { for _, hopHint := range routeHint {
hopHint.NodeID.Curve = nil hopHint.NodeID.Curve = nil
@@ -1973,13 +1970,13 @@ func (r *ChannelRouter) processSendError(paySession *paymentSession,
// we'll prune the channel in both directions and // we'll prune the channel in both directions and
// continue with the rest of the routes. // continue with the rest of the routes.
case *lnwire.FailPermanentChannelFailure: case *lnwire.FailPermanentChannelFailure:
paySession.ReportEdgeFailure(&edgeLocator{ paySession.ReportEdgeFailure(&EdgeLocator{
channelID: failedEdge.channelID, ChannelID: failedEdge.ChannelID,
direction: 0, Direction: 0,
}) })
paySession.ReportEdgeFailure(&edgeLocator{ paySession.ReportEdgeFailure(&EdgeLocator{
channelID: failedEdge.channelID, ChannelID: failedEdge.ChannelID,
direction: 1, Direction: 1,
}) })
return false return false
@@ -1992,7 +1989,7 @@ func (r *ChannelRouter) processSendError(paySession *paymentSession,
// pubkey of the node that sent the error. It will assume that the error is // pubkey of the node that sent the error. It will assume that the error is
// associated with the outgoing channel of the error node. // associated with the outgoing channel of the error node.
func getFailedEdge(route *Route, errSource Vertex) ( func getFailedEdge(route *Route, errSource Vertex) (
*edgeLocator, error) { *EdgeLocator, error) {
hopCount := len(route.Hops) hopCount := len(route.Hops)
fromNode := route.SourcePubKey fromNode := route.SourcePubKey
+84 -54
View File
@@ -30,7 +30,7 @@ type testCtx struct {
graph *channeldb.ChannelGraph graph *channeldb.ChannelGraph
aliases map[string]*btcec.PublicKey aliases map[string]Vertex
chain *mockChain chain *mockChain
@@ -184,7 +184,8 @@ func TestFindRoutesFeeSorting(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.aliases["luoji"] target := ctx.aliases["luoji"]
routes, err := ctx.router.FindRoutes( routes, err := ctx.router.FindRoutes(
target, paymentAmt, noFeeLimit, defaultNumRoutes, ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, noRestrictions, defaultNumRoutes,
DefaultFinalCLTVDelta, DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
@@ -240,10 +241,13 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
// see the first route. // see the first route.
target := ctx.aliases["sophon"] target := ctx.aliases["sophon"]
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
feeLimit := lnwire.NewMSatFromSatoshis(10) restrictions := &RestrictParams{
FeeLimit: lnwire.NewMSatFromSatoshis(10),
}
routes, err := ctx.router.FindRoutes( routes, err := ctx.router.FindRoutes(
target, paymentAmt, feeLimit, defaultNumRoutes, ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, restrictions, defaultNumRoutes,
DefaultFinalCLTVDelta, DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
@@ -254,7 +258,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
t.Fatalf("expected 1 route, got %d", len(routes)) t.Fatalf("expected 1 route, got %d", len(routes))
} }
if routes[0].TotalFees > feeLimit { if routes[0].TotalFees > restrictions.FeeLimit {
t.Fatalf("route exceeded fee limit: %v", spew.Sdump(routes[0])) t.Fatalf("route exceeded fee limit: %v", spew.Sdump(routes[0]))
} }
@@ -263,11 +267,10 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
t.Fatalf("expected 2 hops, got %d", len(hops)) t.Fatalf("expected 2 hops, got %d", len(hops))
} }
if !bytes.Equal(hops[0].PubKeyBytes[:], if hops[0].PubKeyBytes != ctx.aliases["songoku"] {
ctx.aliases["songoku"].SerializeCompressed()) {
t.Fatalf("expected first hop through songoku, got %s", t.Fatalf("expected first hop through songoku, got %s",
getAliasFromPubKey(hops[0].PubKeyBytes[:], getAliasFromPubKey(hops[0].PubKeyBytes,
ctx.aliases)) ctx.aliases))
} }
} }
@@ -345,12 +348,11 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
} }
// The route should have satoshi as the first hop. // The route should have satoshi as the first hop.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:], if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] {
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",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:], getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases)) ctx.aliases))
} }
} }
@@ -408,11 +410,9 @@ func TestChannelUpdateValidation(t *testing.T) {
// Setup a route from source a to destination c. The route will be used // Setup a route from source a to destination c. The route will be used
// in a call to SendToRoute. SendToRoute also applies channel updates, // in a call to SendToRoute. SendToRoute also applies channel updates,
// but it saves us from including RequestRoute in the test scope too. // but it saves us from including RequestRoute in the test scope too.
var hop1 [33]byte hop1 := ctx.aliases["b"]
copy(hop1[:], ctx.aliases["b"].SerializeCompressed())
var hop2 [33]byte hop2 := ctx.aliases["c"]
copy(hop2[:], ctx.aliases["c"].SerializeCompressed())
hops := []*Hop{ hops := []*Hop{
{ {
@@ -427,7 +427,7 @@ func TestChannelUpdateValidation(t *testing.T) {
route, err := NewRouteFromHops( route, err := NewRouteFromHops(
lnwire.MilliSatoshi(10000), 100, lnwire.MilliSatoshi(10000), 100,
NewVertex(ctx.aliases["a"]), hops, ctx.aliases["a"], hops,
) )
if err != nil { if err != nil {
t.Fatalf("unable to create route: %v", err) t.Fatalf("unable to create route: %v", err)
@@ -449,8 +449,16 @@ func TestChannelUpdateValidation(t *testing.T) {
ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID, ctx.router.cfg.SendToSwitch = func(firstHop lnwire.ShortChannelID,
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) { _ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
v := ctx.aliases["b"]
source, err := btcec.ParsePubKey(
v[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: ctx.aliases["b"], ErrorSource: source,
FailureMessage: &lnwire.FailFeeInsufficient{ FailureMessage: &lnwire.FailFeeInsufficient{
Update: errChanUpdate, Update: errChanUpdate,
}, },
@@ -574,8 +582,15 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID) roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceNode, ErrorSource: sourceKey,
// Within our error, we'll add a channel update // Within our error, we'll add a channel update
// which is meant to reflect he new fee // which is meant to reflect he new fee
@@ -609,12 +624,11 @@ 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 !bytes.Equal(route.Hops[0].PubKeyBytes[:], if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] {
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",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:], getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases)) ctx.aliases))
} }
} }
@@ -682,8 +696,15 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) { _ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceNode, ErrorSource: sourceKey,
FailureMessage: &lnwire.FailExpiryTooSoon{ FailureMessage: &lnwire.FailExpiryTooSoon{
Update: errChanUpdate, Update: errChanUpdate,
}, },
@@ -710,12 +731,11 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
} }
// The route should have satoshi as the first hop. // The route should have satoshi as the first hop.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:], if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] {
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",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:], getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases)) ctx.aliases))
} }
} }
@@ -737,8 +757,15 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) { _ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
if firstHop == roasbeefSongoku { if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceNode, ErrorSource: sourceKey,
FailureMessage: &lnwire.FailIncorrectCltvExpiry{ FailureMessage: &lnwire.FailIncorrectCltvExpiry{
Update: errChanUpdate, Update: errChanUpdate,
}, },
@@ -821,8 +848,16 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// prune out the rest of the routes. // prune out the rest of the routes.
roasbeefSatoshi := lnwire.NewShortChanIDFromInt(2340213491) roasbeefSatoshi := lnwire.NewShortChanIDFromInt(2340213491)
if firstHop == roasbeefSatoshi { if firstHop == roasbeefSatoshi {
vertex := ctx.aliases["satoshi"]
key, err := btcec.ParsePubKey(
vertex[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{ return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: ctx.aliases["satoshi"], ErrorSource: key,
FailureMessage: &lnwire.FailUnknownNextPeer{}, FailureMessage: &lnwire.FailUnknownNextPeer{},
} }
} }
@@ -880,12 +915,11 @@ 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 !bytes.Equal(route.Hops[0].PubKeyBytes[:], if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] {
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",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:], getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases)) ctx.aliases))
} }
@@ -928,12 +962,11 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
} }
// The route should have satoshi as the first hop. // The route should have satoshi as the first hop.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:], if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] {
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",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:], getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases)) ctx.aliases))
} }
} }
@@ -1231,8 +1264,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
// We will connect node 1 to "sophon" // We will connect node 1 to "sophon"
connectNode := ctx.aliases["sophon"] connectNode := ctx.aliases["sophon"]
if connectNode == nil { connectNodeKey, err := btcec.ParsePubKey(connectNode[:], btcec.S256())
t.Fatalf("could not find node to connect to") if err != nil {
t.Fatal(err)
} }
var ( var (
@@ -1240,12 +1274,12 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
pubKey2 *btcec.PublicKey pubKey2 *btcec.PublicKey
) )
node1Bytes := priv1.PubKey().SerializeCompressed() node1Bytes := priv1.PubKey().SerializeCompressed()
node2Bytes := connectNode.SerializeCompressed() node2Bytes := connectNode
if bytes.Compare(node1Bytes, node2Bytes) == -1 { if bytes.Compare(node1Bytes[:], node2Bytes[:]) == -1 {
pubKey1 = priv1.PubKey() pubKey1 = priv1.PubKey()
pubKey2 = connectNode pubKey2 = connectNodeKey
} else { } else {
pubKey1 = connectNode pubKey1 = connectNodeKey
pubKey2 = priv1.PubKey() pubKey2 = priv1.PubKey()
} }
@@ -1265,9 +1299,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
AuthProof: nil, AuthProof: nil,
} }
copy(edge.NodeKey1Bytes[:], node1Bytes) copy(edge.NodeKey1Bytes[:], node1Bytes)
copy(edge.NodeKey2Bytes[:], node2Bytes) edge.NodeKey2Bytes = node2Bytes
copy(edge.BitcoinKey1Bytes[:], node1Bytes) copy(edge.BitcoinKey1Bytes[:], node1Bytes)
copy(edge.BitcoinKey2Bytes[:], node2Bytes) edge.BitcoinKey2Bytes = node2Bytes
if err := ctx.router.AddEdge(edge); err != nil { if err := ctx.router.AddEdge(edge); err != nil {
t.Fatalf("unable to add edge to the channel graph: %v.", err) t.Fatalf("unable to add edge to the channel graph: %v.", err)
@@ -1306,8 +1340,11 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
// We should now be able to find two routes to node 2. // We should now be able to find two routes to node 2.
paymentAmt := lnwire.NewMSatFromSatoshis(100) paymentAmt := lnwire.NewMSatFromSatoshis(100)
targetNode := priv2.PubKey() targetNode := priv2.PubKey()
var targetPubKeyBytes Vertex
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
routes, err := ctx.router.FindRoutes( routes, err := ctx.router.FindRoutes(
targetNode, paymentAmt, noFeeLimit, defaultNumRoutes, ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
DefaultFinalCLTVDelta, DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
@@ -1352,7 +1389,8 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
// Should still be able to find the routes, and the info should be // Should still be able to find the routes, and the info should be
// updated. // updated.
routes, err = ctx.router.FindRoutes( routes, err = ctx.router.FindRoutes(
targetNode, paymentAmt, noFeeLimit, defaultNumRoutes, ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
DefaultFinalCLTVDelta, DefaultFinalCLTVDelta,
) )
if err != nil { if err != nil {
@@ -1949,15 +1987,9 @@ func TestFindPathFeeWeighting(t *testing.T) {
t.Fatalf("unable to fetch source node: %v", err) t.Fatalf("unable to fetch source node: %v", err)
} }
ignoreVertex := make(map[Vertex]struct{})
ignoreEdge := make(map[edgeLocator]struct{})
amt := lnwire.MilliSatoshi(100) amt := lnwire.MilliSatoshi(100)
target := ctx.aliases["luoji"] target := ctx.aliases["luoji"]
if target == nil {
t.Fatalf("unable to find target node")
}
// We'll now attempt a path finding attempt using this set up. Due to // We'll now attempt a path finding attempt using this set up. Due to
// the edge weighting, we should select the direct path over the 2 hop // the edge weighting, we should select the direct path over the 2 hop
@@ -1966,12 +1998,10 @@ func TestFindPathFeeWeighting(t *testing.T) {
&graphParams{ &graphParams{
graph: ctx.graph, graph: ctx.graph,
}, },
&restrictParams{ &RestrictParams{
ignoredNodes: ignoreVertex, FeeLimit: noFeeLimit,
ignoredEdges: ignoreEdge,
feeLimit: noFeeLimit,
}, },
sourceNode, target, amt, sourceNode.PubKeyBytes, target, amt,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
+43 -132
View File
@@ -17,6 +17,8 @@ import (
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/btcsuite/btcd/blockchain" "github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/chaincfg/chainhash"
@@ -388,6 +390,10 @@ type rpcServer struct {
// connect to the main gRPC server to proxy all incoming requests. // connect to the main gRPC server to proxy all incoming requests.
tlsCfg *tls.Config tlsCfg *tls.Config
// RouterBackend contains the backend implementation of the router
// rpc sub server.
RouterBackend *routerrpc.RouterBackend
quit chan struct{} quit chan struct{}
} }
@@ -471,6 +477,28 @@ func newRPCServer(s *server, macService *macaroons.Service,
) )
} }
// Set up router rpc backend.
channelGraph := s.chanDB.ChannelGraph()
selfNode, err := channelGraph.SourceNode()
if err != nil {
return nil, err
}
graph := s.chanDB.ChannelGraph()
RouterBackend := &routerrpc.RouterBackend{
MaxPaymentMSat: maxPaymentMSat,
SelfNode: selfNode.PubKeyBytes,
FetchChannelCapacity: func(chanID uint64) (btcutil.Amount,
error) {
info, _, _, err := graph.FetchChannelEdgesByID(chanID)
if err != nil {
return 0, err
}
return info.Capacity, nil
},
FindRoutes: s.chanRouter.FindRoutes,
}
// Finally, with all the pre-set up complete, we can create the main // Finally, with all the pre-set up complete, we can create the main
// gRPC server, and register the main lnrpc server along side. // gRPC server, and register the main lnrpc server along side.
grpcServer := grpc.NewServer(serverOpts...) grpcServer := grpc.NewServer(serverOpts...)
@@ -480,6 +508,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
tlsCfg: tlsCfg, tlsCfg: tlsCfg,
grpcServer: grpcServer, grpcServer: grpcServer,
server: s, server: s,
RouterBackend: RouterBackend,
quit: make(chan struct{}, 1), quit: make(chan struct{}, 1),
} }
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer) lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
@@ -2764,7 +2793,7 @@ func unmarshallSendToRouteRequest(req *lnrpc.SendToRouteRequest,
type rpcPaymentIntent struct { type rpcPaymentIntent struct {
msat lnwire.MilliSatoshi msat lnwire.MilliSatoshi
feeLimit lnwire.MilliSatoshi feeLimit lnwire.MilliSatoshi
dest *btcec.PublicKey dest routing.Vertex
rHash [32]byte rHash [32]byte
cltvDelta uint16 cltvDelta uint16
routeHints [][]routing.HopHint routeHints [][]routing.HopHint
@@ -2778,7 +2807,6 @@ type rpcPaymentIntent struct {
// three ways a client can specify their payment details: a payment request, // three ways a client can specify their payment details: a payment request,
// via manual details, or via a complete route. // via manual details, or via a complete route.
func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error) { func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error) {
var err error
payIntent := rpcPaymentIntent{} payIntent := rpcPaymentIntent{}
// If a route was specified, then we can use that directly. // If a route was specified, then we can use that directly.
@@ -2849,7 +2877,8 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error
) )
copy(payIntent.rHash[:], payReq.PaymentHash[:]) copy(payIntent.rHash[:], payReq.PaymentHash[:])
payIntent.dest = payReq.Destination destKey := payReq.Destination.SerializeCompressed()
copy(payIntent.dest[:], destKey)
payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry()) payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
payIntent.routeHints = payReq.RouteHints payIntent.routeHints = payReq.RouteHints
@@ -2859,24 +2888,20 @@ func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error
// At this point, a destination MUST be specified, so we'll convert it // At this point, a destination MUST be specified, so we'll convert it
// into the proper representation now. The destination will either be // into the proper representation now. The destination will either be
// encoded as raw bytes, or via a hex string. // encoded as raw bytes, or via a hex string.
var pubBytes []byte
if len(rpcPayReq.Dest) != 0 { if len(rpcPayReq.Dest) != 0 {
payIntent.dest, err = btcec.ParsePubKey( pubBytes = rpcPayReq.Dest
rpcPayReq.Dest, btcec.S256(),
)
if err != nil {
return payIntent, err
}
} else { } else {
pubBytes, err := hex.DecodeString(rpcPayReq.DestString) var err error
if err != nil { pubBytes, err = hex.DecodeString(rpcPayReq.DestString)
return payIntent, err
}
payIntent.dest, err = btcec.ParsePubKey(pubBytes, btcec.S256())
if err != nil { if err != nil {
return payIntent, err return payIntent, err
} }
} }
if len(pubBytes) != 33 {
return payIntent, errors.New("invalid key length")
}
copy(payIntent.dest[:], pubBytes)
// Otherwise, If the payment request field was not specified // Otherwise, If the payment request field was not specified
// (and a custom route wasn't specified), construct the payment // (and a custom route wasn't specified), construct the payment
@@ -3157,7 +3182,7 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
return return
} }
marshalledRouted := r.marshallRoute(resp.Route) marshalledRouted := r.RouterBackend.MarshallRoute(resp.Route)
err := stream.send(&lnrpc.SendResponse{ err := stream.send(&lnrpc.SendResponse{
PaymentHash: payIntent.rHash[:], PaymentHash: payIntent.rHash[:],
PaymentPreimage: resp.Preimage[:], PaymentPreimage: resp.Preimage[:],
@@ -3242,7 +3267,7 @@ func (r *rpcServer) sendPaymentSync(ctx context.Context,
return &lnrpc.SendResponse{ return &lnrpc.SendResponse{
PaymentHash: payIntent.rHash[:], PaymentHash: payIntent.rHash[:],
PaymentPreimage: resp.Preimage[:], PaymentPreimage: resp.Preimage[:],
PaymentRoute: r.marshallRoute(resp.Route), PaymentRoute: r.RouterBackend.MarshallRoute(resp.Route),
}, nil }, nil
} }
@@ -4001,121 +4026,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
func (r *rpcServer) QueryRoutes(ctx context.Context, func (r *rpcServer) QueryRoutes(ctx context.Context,
in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) { in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) {
// First parse the hex-encoded public key into a full public key object return r.RouterBackend.QueryRoutes(ctx, in)
// we can properly manipulate.
pubKeyBytes, err := hex.DecodeString(in.PubKey)
if err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
if err != nil {
return nil, err
}
// Currently, within the bootstrap phase of the network, we limit the
// largest payment size allotted to (2^32) - 1 mSAT or 4.29 million
// satoshis.
amt := btcutil.Amount(in.Amt)
amtMSat := lnwire.NewMSatFromSatoshis(amt)
if amtMSat > maxPaymentMSat {
return nil, fmt.Errorf("payment of %v is too large, max payment "+
"allowed is %v", amt, maxPaymentMSat.ToSatoshis())
}
feeLimit := calculateFeeLimit(in.FeeLimit, amtMSat)
// numRoutes will default to 10 if not specified explicitly.
numRoutesIn := uint32(in.NumRoutes)
if numRoutesIn == 0 {
numRoutesIn = 10
}
// Query the channel router for a possible path to the destination that
// can carry `in.Amt` satoshis _including_ the total fee required on
// the route.
var (
routes []*routing.Route
findErr error
)
if in.FinalCltvDelta == 0 {
routes, findErr = r.server.chanRouter.FindRoutes(
pubKey, amtMSat, feeLimit, numRoutesIn,
)
} else {
routes, findErr = r.server.chanRouter.FindRoutes(
pubKey, amtMSat, feeLimit, numRoutesIn,
uint16(in.FinalCltvDelta),
)
}
if findErr != nil {
return nil, findErr
}
// As the number of returned routes can be less than the number of
// requested routes, we'll clamp down the length of the response to the
// minimum of the two.
numRoutes := uint32(len(routes))
if numRoutesIn < numRoutes {
numRoutes = numRoutesIn
}
// For each valid route, we'll convert the result into the format
// required by the RPC system.
routeResp := &lnrpc.QueryRoutesResponse{
Routes: make([]*lnrpc.Route, 0, in.NumRoutes),
}
for i := uint32(0); i < numRoutes; i++ {
routeResp.Routes = append(
routeResp.Routes, r.marshallRoute(routes[i]),
)
}
return routeResp, nil
}
func (r *rpcServer) marshallRoute(route *routing.Route) *lnrpc.Route {
resp := &lnrpc.Route{
TotalTimeLock: route.TotalTimeLock,
TotalFees: int64(route.TotalFees.ToSatoshis()),
TotalFeesMsat: int64(route.TotalFees),
TotalAmt: int64(route.TotalAmount.ToSatoshis()),
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.ChannelID,
ChanCapacity: int64(chanCapacity),
AmtToForward: int64(hop.AmtToForward.ToSatoshis()),
AmtToForwardMsat: int64(hop.AmtToForward),
Fee: int64(fee.ToSatoshis()),
FeeMsat: int64(fee),
Expiry: uint32(hop.OutgoingTimeLock),
PubKey: hex.EncodeToString(
hop.PubKeyBytes[:]),
}
incomingAmt = hop.AmtToForward
}
return resp
} }
// unmarshallHopByChannelLookup unmarshalls an rpc hop for which the pub key is // unmarshallHopByChannelLookup unmarshalls an rpc hop for which the pub key is