Merge pull request #2497 from joostjager/querysingleroute

lnrpc: deprecate QueryRoutes with more than one route
This commit is contained in:
Olaoluwa Osuntokun 2019-03-12 21:15:18 -07:00 committed by GitHub
commit ad8849056b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1458 additions and 1107 deletions

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
}

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

File diff suppressed because it is too large Load Diff

View File

@ -1467,8 +1467,11 @@ message QueryRoutesRequest {
/// The amount to send expressed in satoshis
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
int32 final_cltv_delta = 4;
@ -1480,7 +1483,37 @@ message QueryRoutesRequest {
send the payment.
*/
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 {
repeated Route routes = 1 [json_name = "routes"];
}

View File

@ -564,7 +564,7 @@
},
{
"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",
"required": false,
"type": "integer",
@ -593,6 +593,24 @@
"required": false,
"type": "string",
"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": [

View File

@ -46,7 +46,7 @@ type missionControl struct {
// 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
// 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
// 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 {
return &missionControl{
failedEdges: make(map[edgeLocator]time.Time),
failedEdges: make(map[EdgeLocator]time.Time),
failedVertexes: make(map[Vertex]time.Time),
selfNode: selfNode,
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
// routing attempts in the past.
type graphPruneView struct {
edges map[edgeLocator]struct{}
edges map[EdgeLocator]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
// rather than the decay for vertexes.
edges := make(map[edgeLocator]struct{})
edges := make(map[EdgeLocator]struct{})
for edge, pruneTime := range m.failedEdges {
if now.Sub(pruneTime) >= edgeDecay {
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
// payment's destination.
func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
target *btcec.PublicKey) (*paymentSession, error) {
target Vertex) (*paymentSession, error) {
viewSnapshot := m.GraphPruneView()
@ -175,7 +175,13 @@ func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
if i != len(routeHint)-1 {
endNode.AddPubKey(routeHint[i+1].NodeID)
} 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
@ -217,7 +223,7 @@ func (m *missionControl) NewPaymentSession(routeHints [][]HopHint,
pruneViewSnapshot: viewSnapshot,
additionalEdges: edges,
bandwidthHints: bandwidthHints,
errFailedPolicyChans: make(map[edgeLocator]struct{}),
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
mc: m,
}, nil
}
@ -231,7 +237,7 @@ func (m *missionControl) NewPaymentSessionFromRoutes(routes []*Route) *paymentSe
pruneViewSnapshot: m.GraphPruneView(),
haveRoutes: true,
preBuiltRoutes: routes,
errFailedPolicyChans: make(map[edgeLocator]struct{}),
errFailedPolicyChans: make(map[EdgeLocator]struct{}),
mc: m,
}
}
@ -275,7 +281,7 @@ func generateBandwidthHints(sourceNode *channeldb.LightningNode,
// if no payment attempts have been made.
func (m *missionControl) ResetHistory() {
m.Lock()
m.failedEdges = make(map[edgeLocator]time.Time)
m.failedEdges = make(map[EdgeLocator]time.Time)
m.failedVertexes = make(map[Vertex]time.Time)
m.Unlock()
}

View File

@ -1,7 +1,6 @@
package routing
import (
"bytes"
"encoding/binary"
"fmt"
"math"
@ -210,7 +209,7 @@ func (r *Route) ToHopPayloads() []sphinx.HopData {
//
// NOTE: The passed slice of ChannelHops MUST be sorted in forward order: from
// the source to the target node of the path finding attempt.
func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
func newRoute(amtToSend lnwire.MilliSatoshi, sourceVertex Vertex,
pathEdges []*channeldb.ChannelEdgePolicy, currentHeight uint32,
finalCLTVDelta uint16) (*Route, error) {
@ -310,13 +309,6 @@ func newRoute(amtToSend, feeLimit lnwire.MilliSatoshi, sourceVertex Vertex,
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
}
@ -405,24 +397,24 @@ type graphParams struct {
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.
type restrictParams struct {
// ignoredNodes is an optional set of nodes that should be ignored if
type RestrictParams struct {
// IgnoredNodes is an optional set of nodes that should be ignored if
// 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.
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.
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.
outgoingChannelID *uint64
OutgoingChannelID *uint64
}
// 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
// that need to be paid along the path and accurately check the amount
// to forward at every node against the available bandwidth.
func findPath(g *graphParams, r *restrictParams,
sourceNode *channeldb.LightningNode, target *btcec.PublicKey,
func findPath(g *graphParams, r *RestrictParams, source, target Vertex,
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, 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
// advertised to the network and included in the graph.ForEachNode call
// 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
// point of the graph traversal. We are searching backwards to get the
// fees first time right and correctly match channel bandwidth.
targetVertex := NewVertex(target)
targetNode := &channeldb.LightningNode{PubKeyBytes: targetVertex}
distance[targetVertex] = nodeWithDist{
targetNode := &channeldb.LightningNode{PubKeyBytes: target}
distance[target] = nodeWithDist{
dist: 0,
node: targetNode,
amountToReceive: amt,
@ -520,6 +508,15 @@ func findPath(g *graphParams, r *restrictParams,
// mapped to within `next`.
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
// satisfy our specific requirements.
processEdge := func(fromNode *channeldb.LightningNode,
@ -532,7 +529,7 @@ func findPath(g *graphParams, r *restrictParams,
// skip it.
// TODO(halseth): also ignore disable flags for non-local
// channels if bandwidth hint is set?
isSourceChan := fromVertex == sourceVertex
isSourceChan := fromVertex == source
edgeFlags := edge.ChannelFlags
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
// the specified channel, skip it.
if isSourceChan && r.outgoingChannelID != nil &&
*r.outgoingChannelID != edge.ChannelID {
if isSourceChan && r.OutgoingChannelID != nil &&
*r.OutgoingChannelID != edge.ChannelID {
return
}
// If this vertex or edge has been black listed, then we'll
// skip exploring this edge.
if _, ok := r.ignoredNodes[fromVertex]; ok {
if _, ok := ignoredNodes[fromVertex]; ok {
return
}
locator := newEdgeLocator(edge)
if _, ok := r.ignoredEdges[*locator]; ok {
if _, ok := ignoredEdges[*locator]; ok {
return
}
@ -595,7 +592,7 @@ func findPath(g *graphParams, r *restrictParams,
// node, no additional timelock is required.
var fee lnwire.MilliSatoshi
var timeLockDelta uint16
if fromVertex != sourceVertex {
if fromVertex != source {
fee = computeFee(amountToSend, edge)
timeLockDelta = edge.TimeLockDelta
}
@ -610,7 +607,7 @@ func findPath(g *graphParams, r *restrictParams,
// Check if accumulated fees would exceed fee limit when this
// node would be added to the path.
totalFee := amountToReceive - amt
if totalFee > r.feeLimit {
if totalFee > r.FeeLimit {
return
}
@ -664,7 +661,7 @@ func findPath(g *graphParams, r *restrictParams,
// To start, our target node will the sole item within our distance
// heap.
heap.Push(&nodeHeap, distance[targetVertex])
heap.Push(&nodeHeap, distance[target])
for nodeHeap.Len() != 0 {
// 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
// edges), then we're done here and can exit the graph
// traversal early.
if bytes.Equal(bestNode.PubKeyBytes[:], sourceVertex[:]) {
if bestNode.PubKeyBytes == source {
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
// 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 "+
"destination")
}
@ -750,8 +747,8 @@ func findPath(g *graphParams, r *restrictParams,
// Use the nextHop map to unravel the forward path from source to
// target.
pathEdges := make([]*channeldb.ChannelEdgePolicy, 0, len(next))
currentNode := sourceVertex
for currentNode != targetVertex { // TODO(roasbeef): assumes no cycles
currentNode := source
for currentNode != target { // TODO(roasbeef): assumes no cycles
// Determine the next hop forward using the next map.
nextNode := next[currentNode]
@ -787,12 +784,10 @@ func findPath(g *graphParams, r *restrictParams,
// algorithm, rather than attempting to use an unmodified path finding
// algorithm in a block box manner.
func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
source *channeldb.LightningNode, target *btcec.PublicKey,
amt lnwire.MilliSatoshi, feeLimit lnwire.MilliSatoshi, numPaths uint32,
bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*channeldb.ChannelEdgePolicy, error) {
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
source, target Vertex, amt lnwire.MilliSatoshi,
restrictions *RestrictParams, numPaths uint32,
bandwidthHints map[uint64]lnwire.MilliSatoshi) (
[][]*channeldb.ChannelEdgePolicy, error) {
// TODO(roasbeef): modifying ordering within heap to eliminate final
// sorting step?
@ -810,12 +805,7 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
graph: graph,
bandwidthHints: bandwidthHints,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: feeLimit,
},
source, target, amt,
restrictions, source, target, amt,
)
if err != nil {
log.Errorf("Unable to find path: %v", err)
@ -827,7 +817,7 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph,
// function properly.
firstPath := make([]*channeldb.ChannelEdgePolicy, 0, len(startingPath)+1)
firstPath = append(firstPath, &channeldb.ChannelEdgePolicy{
Node: source,
Node: &channeldb.LightningNode{PubKeyBytes: source},
})
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.
// These are required to ensure the paths are unique
// and loopless.
ignoredEdges = make(map[edgeLocator]struct{})
ignoredVertexes = make(map[Vertex]struct{})
ignoredEdges := make(map[EdgeLocator]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
// 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
// root path removed, we'll attempt to find another
// 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(
&graphParams{
tx: tx,
graph: graph,
bandwidthHints: bandwidthHints,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: feeLimit,
}, spurNode, target, amt,
spurRestrictions, spurNode.PubKeyBytes,
target, amt,
)
// If we weren't able to find a path, we'll continue to

View File

@ -47,6 +47,12 @@ const (
noFeeLimit = lnwire.MilliSatoshi(math.MaxUint32)
)
var (
noRestrictions = &RestrictParams{
FeeLimit: noFeeLimit,
}
)
var (
testSig = &btcec.Signature{
R: new(big.Int),
@ -156,7 +162,7 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
return nil, err
}
aliasMap := make(map[string]*btcec.PublicKey)
aliasMap := make(map[string]Vertex)
var source *channeldb.LightningNode
// First we insert all the nodes within the graph as vertexes.
@ -183,14 +189,9 @@ func parseTestGraph(path string) (*testGraphInstance, error) {
"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
// 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
// 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
// provided in order to allow easily look up from the human memorable alias
// 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
// able to mock a remote node's signing behaviour.
@ -388,7 +389,7 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
return nil, err
}
aliasMap := make(map[string]*btcec.PublicKey)
aliasMap := make(map[string]Vertex)
privKeyMap := make(map[string]*btcec.PrivateKey)
nodeIndex := byte(0)
@ -423,7 +424,7 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
return nil, err
}
aliasMap[alias] = pubKey
aliasMap[alias] = dbNode.PubKeyBytes
nodeIndex++
return dbNode, nil
@ -476,16 +477,13 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc
AuthProof: &testAuthProof,
ChannelPoint: *fundingPoint,
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)
if err != nil && err != channeldb.ErrEdgeAlreadyExist {
return nil, err
@ -598,9 +596,6 @@ func TestFindLowestFeePath(t *testing.T) {
}
sourceVertex := Vertex(sourceNode.PubKeyBytes)
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
const (
startingHeight = 100
finalHopCLTV = 1
@ -612,38 +607,35 @@ func TestFindLowestFeePath(t *testing.T) {
&graphParams{
graph: testGraphInstance.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, paymentAmt,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
}
route, err := newRoute(
paymentAmt, infinity, sourceVertex, path, startingHeight,
paymentAmt, sourceVertex, path, startingHeight,
finalHopCLTV)
if err != nil {
t.Fatalf("unable to create path: %v", err)
}
// Assert that the lowest fee route is returned.
if !bytes.Equal(route.Hops[1].PubKeyBytes[:],
testGraphInstance.aliasMap["b"].SerializeCompressed()) {
if route.Hops[1].PubKeyBytes != testGraphInstance.aliasMap["b"] {
t.Fatalf("expected route to pass through b, "+
"but got a route through %v",
getAliasFromPubKey(route.Hops[1].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[1].PubKeyBytes,
testGraphInstance.aliasMap))
}
}
func getAliasFromPubKey(pubKey []byte,
aliases map[string]*btcec.PublicKey) string {
func getAliasFromPubKey(pubKey Vertex,
aliases map[string]Vertex) string {
for alias, key := range aliases {
if bytes.Equal(key.SerializeCompressed(), pubKey) {
if key == pubKey {
return alias
}
}
@ -744,9 +736,6 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
}
sourceVertex := Vertex(sourceNode.PubKeyBytes)
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
const (
startingHeight = 100
finalHopCLTV = 1
@ -758,12 +747,10 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
&graphParams{
graph: graphInstance.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: test.feeLimit,
&RestrictParams{
FeeLimit: test.feeLimit,
},
sourceNode, target, paymentAmt,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if test.expectFailureNoPath {
if err == nil {
@ -776,7 +763,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
}
route, err := newRoute(
paymentAmt, test.feeLimit, sourceVertex, path, startingHeight,
paymentAmt, sourceVertex, path, startingHeight,
finalHopCLTV,
)
if err != nil {
@ -790,12 +777,11 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
// Check hop nodes
for i := 0; i < len(expectedHops); i++ {
if !bytes.Equal(route.Hops[i].PubKeyBytes[:],
aliases[expectedHops[i].alias].SerializeCompressed()) {
if route.Hops[i].PubKeyBytes != aliases[expectedHops[i].alias] {
t.Fatalf("%v-th hop should be %v, is instead: %v",
i, expectedHops[i],
getAliasFromPubKey(route.Hops[i].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[i].PubKeyBytes,
aliases))
}
}
@ -904,6 +890,8 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
doge := &channeldb.LightningNode{}
doge.AddPubKey(dogePubKey)
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
// our map of additional edges.
@ -916,7 +904,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
}
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.
@ -925,10 +913,10 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
graph: graph.graph,
additionalEdges: additionalEdges,
},
&restrictParams{
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, dogePubKey, paymentAmt,
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
)
if err != nil {
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:
// roasbeef -> songoku -> doge
assertExpectedPath(t, path, "songoku", "doge")
assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
}
func TestKShortestPathFinding(t *testing.T) {
@ -963,9 +951,12 @@ func TestKShortestPathFinding(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := graph.aliasMap["luoji"]
restrictions := &RestrictParams{
FeeLimit: noFeeLimit,
}
paths, err := findPaths(
nil, graph.graph, sourceNode, target, paymentAmt, noFeeLimit, 100,
nil,
nil, graph.graph, sourceNode.PubKeyBytes, target, paymentAmt,
restrictions, 100, nil,
)
if err != nil {
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.
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.
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
@ -1056,8 +1049,6 @@ func TestNewRoute(t *testing.T) {
// expectedErrorCode indicates the expected error code when
// expectError is true.
expectedErrorCode errorCode
feeLimit lnwire.MilliSatoshi
}{
{
// For a single hop payment, no fees are expected to be paid.
@ -1070,7 +1061,6 @@ func TestNewRoute(t *testing.T) {
expectedTimeLocks: []uint32{1},
expectedTotalAmount: 100000,
expectedTotalTimeLock: 1,
feeLimit: noFeeLimit,
}, {
// For a two hop payment, only the fee for the first hop
// needs to be paid. The destination hop does not require
@ -1085,7 +1075,6 @@ func TestNewRoute(t *testing.T) {
expectedTimeLocks: []uint32{1, 1},
expectedTotalAmount: 100130,
expectedTotalTimeLock: 6,
feeLimit: noFeeLimit,
}, {
// A three hop payment where the first and second hop
// will both charge 1 msat. The fee for the first hop
@ -1103,7 +1092,6 @@ func TestNewRoute(t *testing.T) {
expectedTotalAmount: 100002,
expectedTimeLocks: []uint32{4, 1, 1},
expectedTotalTimeLock: 9,
feeLimit: noFeeLimit,
}, {
// A three hop payment where the fee of the first hop
// is slightly higher (11) than the fee at the second hop,
@ -1119,7 +1107,6 @@ func TestNewRoute(t *testing.T) {
expectedTotalAmount: 102010,
expectedTimeLocks: []uint32{4, 1, 1},
expectedTotalTimeLock: 9,
feeLimit: noFeeLimit,
}, {
// A three hop payment where the fee policies of the first and
// second hop are just high enough to show the fee carry over
@ -1141,53 +1128,6 @@ func TestNewRoute(t *testing.T) {
expectedTotalAmount: 101101,
expectedTimeLocks: []uint32{4, 1, 1},
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 {
@ -1238,7 +1178,6 @@ func TestNewRoute(t *testing.T) {
t.Run(testCase.name, func(t *testing.T) {
route, err := newRoute(testCase.paymentAmount,
testCase.feeLimit,
sourceVertex, testCase.hops, startingHeight,
finalHopCLTV)
@ -1278,9 +1217,6 @@ func TestNewRoutePathTooLong(t *testing.T) {
t.Fatalf("unable to fetch source node: %v", err)
}
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
paymentAmt := lnwire.NewMSatFromSatoshis(100)
// We start by confirming that routing a payment 20 hops away is
@ -1290,12 +1226,10 @@ func TestNewRoutePathTooLong(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, paymentAmt,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if err != nil {
t.Fatalf("path should have been found")
@ -1308,12 +1242,10 @@ func TestNewRoutePathTooLong(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, paymentAmt,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if err == nil {
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)
}
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
// With the test graph loaded, we'll test that queries for target that
// are either unreachable within the graph, or unknown result in an
// error.
@ -1348,21 +1277,17 @@ func TestPathNotAvailable(t *testing.T) {
if err != nil {
t.Fatalf("unable to parse bytes: %v", err)
}
unknownNode, err := btcec.ParsePubKey(unknownNodeBytes, btcec.S256())
if err != nil {
t.Fatalf("unable to parse pubkey: %v", err)
}
var unknownNode Vertex
copy(unknownNode[:], unknownNodeBytes)
_, err = findPath(
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, unknownNode, 100,
sourceNode.PubKeyBytes, unknownNode, 100,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("path shouldn't have been found: %v", err)
@ -1382,8 +1307,6 @@ func TestPathInsufficientCapacity(t *testing.T) {
if err != nil {
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
// channel graph cannot support due to insufficient capacity triggers
@ -1400,12 +1323,10 @@ func TestPathInsufficientCapacity(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1427,8 +1348,6 @@ func TestRouteFailMinHTLC(t *testing.T) {
if err != nil {
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
// 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{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1492,8 +1409,6 @@ func TestRouteFailMaxHTLC(t *testing.T) {
if err != nil {
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
// about to set, which should succeed.
@ -1503,12 +1418,10 @@ func TestRouteFailMaxHTLC(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if err != nil {
t.Fatalf("graph should've been able to support payment: %v", err)
@ -1529,12 +1442,10 @@ func TestRouteFailMaxHTLC(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1559,8 +1470,6 @@ func TestRouteFailDisabledEdge(t *testing.T) {
if err != nil {
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
// succeed without issue, and return a single path via phamnuwen
@ -1570,12 +1479,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -1602,12 +1509,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
@ -1631,12 +1536,10 @@ func TestRouteFailDisabledEdge(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1659,8 +1562,6 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
if err != nil {
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
// succeed without issue, and return a path via songoku, as that's the
@ -1671,17 +1572,15 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
&graphParams{
graph: graph.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if err != nil {
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
// roasbeef->phamnuwen to 0.
@ -1699,12 +1598,10 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph,
bandwidthHints: bandwidths,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if !IsError(err, ErrNoPathFound) {
t.Fatalf("graph shouldn't be able to support payment: %v", err)
@ -1721,17 +1618,15 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph,
bandwidthHints: bandwidths,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if err != nil {
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
// disable flag.
@ -1756,17 +1651,15 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph,
bandwidthHints: bandwidths,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, payAmt,
sourceNode.PubKeyBytes, target, payAmt,
)
if err != nil {
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) {
@ -1803,7 +1696,11 @@ func TestPathFindSpecExample(t *testing.T) {
// Carol, so we set "B" as the source node so path finding starts from
// Bob.
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 {
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.
carol := ctx.aliases["C"]
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 {
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
// the proper route for any queries starting with Alice.
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 {
t.Fatalf("unable to find alice: %v", err)
}
@ -1869,13 +1772,15 @@ func TestPathFindSpecExample(t *testing.T) {
if err != nil {
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")
}
// We'll now request a route from A -> B -> C.
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 {
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,
nodeAliases ...string) {
func assertExpectedPath(t *testing.T, aliasMap map[string]Vertex,
path []*channeldb.ChannelEdgePolicy, nodeAliases ...string) {
if len(path) != len(nodeAliases) {
t.Fatal("number of hops and number of aliases do not match")
}
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 "+
"%v was", nodeAliases[i], i, hop.Node.Alias)
}
@ -2076,9 +1981,6 @@ func TestRestrictOutgoingChannel(t *testing.T) {
}
sourceVertex := Vertex(sourceNode.PubKeyBytes)
ignoredEdges := make(map[edgeLocator]struct{})
ignoredVertexes := make(map[Vertex]struct{})
const (
startingHeight = 100
finalHopCLTV = 1
@ -2094,19 +1996,17 @@ func TestRestrictOutgoingChannel(t *testing.T) {
&graphParams{
graph: testGraphInstance.graph,
},
&restrictParams{
ignoredNodes: ignoredVertexes,
ignoredEdges: ignoredEdges,
feeLimit: noFeeLimit,
outgoingChannelID: &outgoingChannelID,
&RestrictParams{
FeeLimit: noFeeLimit,
OutgoingChannelID: &outgoingChannelID,
},
sourceNode, target, paymentAmt,
sourceVertex, target, paymentAmt,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)
}
route, err := newRoute(
paymentAmt, infinity, sourceVertex, path, startingHeight,
paymentAmt, sourceVertex, path, startingHeight,
finalHopCLTV,
)
if err != nil {

View File

@ -27,7 +27,7 @@ type paymentSession struct {
// 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
// require pruning, but any subsequent ones do.
errFailedPolicyChans map[edgeLocator]struct{}
errFailedPolicyChans map[EdgeLocator]struct{}
mc *missionControl
@ -61,7 +61,7 @@ func (p *paymentSession) ReportVertexFailure(v Vertex) {
// retrying an edge after its pruning has expired.
//
// 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)
// 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
// new channel updates.
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
// this channel. If so, then we'll prune out the vertex.
@ -147,13 +147,14 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
additionalEdges: p.additionalEdges,
bandwidthHints: p.bandwidthHints,
},
&restrictParams{
ignoredNodes: pruneView.vertexes,
ignoredEdges: pruneView.edges,
feeLimit: payment.FeeLimit,
outgoingChannelID: payment.OutgoingChannelID,
&RestrictParams{
IgnoredNodes: pruneView.vertexes,
IgnoredEdges: pruneView.edges,
FeeLimit: payment.FeeLimit,
OutgoingChannelID: payment.OutgoingChannelID,
},
p.mc.selfNode, payment.Target, payment.Amount,
p.mc.selfNode.PubKeyBytes, payment.Target,
payment.Amount,
)
if err != nil {
return nil, err
@ -163,8 +164,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
// a route by applying the time-lock and fee requirements.
sourceVertex := Vertex(p.mc.selfNode.PubKeyBytes)
route, err := newRoute(
payment.Amount, payment.FeeLimit, sourceVertex, path, height,
finalCltvDelta,
payment.Amount, sourceVertex, path, height, finalCltvDelta,
)
if err != nil {
// TODO(roasbeef): return which edge/vertex didn't work

View File

@ -215,18 +215,20 @@ func newRouteTuple(amt lnwire.MilliSatoshi, dest []byte) routeTuple {
return r
}
// edgeLocator is a struct used to identify a specific edge. The direction
// fields takes the value of 0 or 1 and is identical in definition to the
// channel direction flag. A value of 0 means the direction from the lower node
// pubkey to the higher.
type edgeLocator struct {
channelID uint64
direction uint8
// EdgeLocator is a struct used to identify a specific edge.
type EdgeLocator struct {
// ChannelID is the channel of this edge.
ChannelID uint64
// Direction takes the value of 0 or 1 and is identical in definition to
// 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
// 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
// pubkeys.
var direction uint8
@ -234,24 +236,24 @@ func newEdgeLocatorByPubkeys(channelID uint64, fromNode, toNode *Vertex) *edgeLo
direction = 1
}
return &edgeLocator{
channelID: channelID,
direction: direction,
return &EdgeLocator{
ChannelID: channelID,
Direction: direction,
}
}
// newEdgeLocator extracts an edgeLocator based for a full edge policy
// structure.
func newEdgeLocator(edge *channeldb.ChannelEdgePolicy) *edgeLocator {
return &edgeLocator{
channelID: edge.ChannelID,
direction: uint8(edge.ChannelFlags & lnwire.ChanUpdateDirection),
func newEdgeLocator(edge *channeldb.ChannelEdgePolicy) *EdgeLocator {
return &EdgeLocator{
ChannelID: edge.ChannelID,
Direction: uint8(edge.ChannelFlags & lnwire.ChanUpdateDirection),
}
}
// String returns a human readable version of the edgeLocator values.
func (e *edgeLocator) String() string {
return fmt.Sprintf("%v:%v", e.channelID, e.direction)
func (e *EdgeLocator) String() string {
return fmt.Sprintf("%v:%v", e.ChannelID, e.Direction)
}
// 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
// total payment flow after fees are calculated.
func pathsToFeeSortedRoutes(source Vertex, paths [][]*channeldb.ChannelEdgePolicy,
finalCLTVDelta uint16, amt, feeLimit lnwire.MilliSatoshi,
finalCLTVDelta uint16, amt lnwire.MilliSatoshi,
currentHeight uint32) ([]*Route, error) {
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
// by our KSP algorithm.
route, err := newRoute(
amt, feeLimit, source, path[1:], currentHeight,
finalCLTVDelta,
amt, source, path[1:], currentHeight, finalCLTVDelta,
)
if err != nil {
// 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
// route that will be ranked the highest is the one with the lowest cumulative
// fee along the route.
func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
amt, feeLimit lnwire.MilliSatoshi, numPaths uint32,
func (r *ChannelRouter) FindRoutes(source, target Vertex,
amt lnwire.MilliSatoshi, restrictions *RestrictParams, numPaths uint32,
finalExpiry ...uint16) ([]*Route, error) {
var finalCLTVDelta uint16
@ -1335,13 +1336,16 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
finalCLTVDelta = finalExpiry[0]
}
dest := target.SerializeCompressed()
log.Debugf("Searching for path to %x, sending %v", dest, amt)
log.Debugf("Searching for path to %x, sending %v", target, amt)
// Before attempting to perform a series of graph traversals to find
// the k-shortest paths to the destination, we'll first consult our
// path cache
rt := newRouteTuple(amt, dest)
// Before attempting to perform a series of graph traversals to find the
// k-shortest paths to the destination, we'll first consult our path
// cache
//
// 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()
routes, ok := r.routeCache[rt]
r.routeCacheMtx.RUnlock()
@ -1360,11 +1364,10 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// We can short circuit the routing by opportunistically checking to
// see if the target vertex event exists in the current graph.
targetVertex := NewVertex(target)
if _, exists, err := r.cfg.Graph.HasLightningNode(targetVertex); err != nil {
if _, exists, err := r.cfg.Graph.HasLightningNode(target); err != nil {
return nil, err
} 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")
}
@ -1395,8 +1398,8 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// we'll execute our KSP algorithm to find the k-shortest paths from
// our source to the destination.
shortestPaths, err := findPaths(
tx, r.cfg.Graph, r.selfNode, target, amt, feeLimit, numPaths,
bandwidthHints,
tx, r.cfg.Graph, source, target, amt, restrictions,
numPaths, bandwidthHints,
)
if err != nil {
tx.Rollback()
@ -1412,7 +1415,7 @@ func (r *ChannelRouter) FindRoutes(target *btcec.PublicKey,
// factored in.
sourceVertex := Vertex(r.selfNode.PubKeyBytes)
validRoutes, err := pathsToFeeSortedRoutes(
sourceVertex, shortestPaths, finalCLTVDelta, amt, feeLimit,
sourceVertex, shortestPaths, finalCLTVDelta, amt,
uint32(currentHeight),
)
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),
amt, dest, newLogClosure(func() string {
amt, target, newLogClosure(func() string {
return spew.Sdump(validRoutes)
}),
)
@ -1512,7 +1515,7 @@ func generateSphinxPacket(route *Route, paymentHash []byte) ([]byte,
// final destination.
type LightningPayment struct {
// 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
// milli-satoshis.
@ -1607,12 +1610,6 @@ func (r *ChannelRouter) sendPayment(payment *LightningPayment,
log.Tracef("Dispatching route for lightning payment: %v",
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 _, hopHint := range routeHint {
hopHint.NodeID.Curve = nil
@ -1973,13 +1970,13 @@ func (r *ChannelRouter) processSendError(paySession *paymentSession,
// we'll prune the channel in both directions and
// continue with the rest of the routes.
case *lnwire.FailPermanentChannelFailure:
paySession.ReportEdgeFailure(&edgeLocator{
channelID: failedEdge.channelID,
direction: 0,
paySession.ReportEdgeFailure(&EdgeLocator{
ChannelID: failedEdge.ChannelID,
Direction: 0,
})
paySession.ReportEdgeFailure(&edgeLocator{
channelID: failedEdge.channelID,
direction: 1,
paySession.ReportEdgeFailure(&EdgeLocator{
ChannelID: failedEdge.ChannelID,
Direction: 1,
})
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
// associated with the outgoing channel of the error node.
func getFailedEdge(route *Route, errSource Vertex) (
*edgeLocator, error) {
*EdgeLocator, error) {
hopCount := len(route.Hops)
fromNode := route.SourcePubKey

View File

@ -30,7 +30,7 @@ type testCtx struct {
graph *channeldb.ChannelGraph
aliases map[string]*btcec.PublicKey
aliases map[string]Vertex
chain *mockChain
@ -184,7 +184,8 @@ func TestFindRoutesFeeSorting(t *testing.T) {
paymentAmt := lnwire.NewMSatFromSatoshis(100)
target := ctx.aliases["luoji"]
routes, err := ctx.router.FindRoutes(
target, paymentAmt, noFeeLimit, defaultNumRoutes,
ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, noRestrictions, defaultNumRoutes,
DefaultFinalCLTVDelta,
)
if err != nil {
@ -240,10 +241,13 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
// see the first route.
target := ctx.aliases["sophon"]
paymentAmt := lnwire.NewMSatFromSatoshis(100)
feeLimit := lnwire.NewMSatFromSatoshis(10)
restrictions := &RestrictParams{
FeeLimit: lnwire.NewMSatFromSatoshis(10),
}
routes, err := ctx.router.FindRoutes(
target, paymentAmt, feeLimit, defaultNumRoutes,
ctx.router.selfNode.PubKeyBytes,
target, paymentAmt, restrictions, defaultNumRoutes,
DefaultFinalCLTVDelta,
)
if err != nil {
@ -254,7 +258,7 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
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]))
}
@ -263,11 +267,10 @@ func TestFindRoutesWithFeeLimit(t *testing.T) {
t.Fatalf("expected 2 hops, got %d", len(hops))
}
if !bytes.Equal(hops[0].PubKeyBytes[:],
ctx.aliases["songoku"].SerializeCompressed()) {
if hops[0].PubKeyBytes != ctx.aliases["songoku"] {
t.Fatalf("expected first hop through songoku, got %s",
getAliasFromPubKey(hops[0].PubKeyBytes[:],
getAliasFromPubKey(hops[0].PubKeyBytes,
ctx.aliases))
}
}
@ -345,12 +348,11 @@ func TestSendPaymentRouteFailureFallback(t *testing.T) {
}
// The route should have satoshi as the first hop.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
ctx.aliases["satoshi"].SerializeCompressed()) {
if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] {
t.Fatalf("route should go through satoshi as first hop, "+
"instead passes through: %v",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[0].PubKeyBytes,
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
// in a call to SendToRoute. SendToRoute also applies channel updates,
// but it saves us from including RequestRoute in the test scope too.
var hop1 [33]byte
copy(hop1[:], ctx.aliases["b"].SerializeCompressed())
hop1 := ctx.aliases["b"]
var hop2 [33]byte
copy(hop2[:], ctx.aliases["c"].SerializeCompressed())
hop2 := ctx.aliases["c"]
hops := []*Hop{
{
@ -427,7 +427,7 @@ func TestChannelUpdateValidation(t *testing.T) {
route, err := NewRouteFromHops(
lnwire.MilliSatoshi(10000), 100,
NewVertex(ctx.aliases["a"]), hops,
ctx.aliases["a"], hops,
)
if err != nil {
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,
_ *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{
ErrorSource: ctx.aliases["b"],
ErrorSource: source,
FailureMessage: &lnwire.FailFeeInsufficient{
Update: errChanUpdate,
},
@ -574,8 +582,15 @@ func TestSendPaymentErrorRepeatedFeeInsufficient(t *testing.T) {
roasbeefSongoku := lnwire.NewShortChanIDFromInt(chanID)
if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceNode,
ErrorSource: sourceKey,
// Within our error, we'll add a channel update
// 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.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
ctx.aliases["phamnuwen"].SerializeCompressed()) {
if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] {
t.Fatalf("route should go through satoshi as first hop, "+
"instead passes through: %v",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases))
}
}
@ -682,8 +696,15 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceNode,
ErrorSource: sourceKey,
FailureMessage: &lnwire.FailExpiryTooSoon{
Update: errChanUpdate,
},
@ -710,12 +731,11 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
}
// The route should have satoshi as the first hop.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
ctx.aliases["phamnuwen"].SerializeCompressed()) {
if route.Hops[0].PubKeyBytes != ctx.aliases["phamnuwen"] {
t.Fatalf("route should go through phamnuwen as first hop, "+
"instead passes through: %v",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases))
}
}
@ -737,8 +757,15 @@ func TestSendPaymentErrorNonFinalTimeLockErrors(t *testing.T) {
_ *lnwire.UpdateAddHTLC, _ *sphinx.Circuit) ([32]byte, error) {
if firstHop == roasbeefSongoku {
sourceKey, err := btcec.ParsePubKey(
sourceNode[:], btcec.S256(),
)
if err != nil {
t.Fatal(err)
}
return [32]byte{}, &htlcswitch.ForwardingError{
ErrorSource: sourceNode,
ErrorSource: sourceKey,
FailureMessage: &lnwire.FailIncorrectCltvExpiry{
Update: errChanUpdate,
},
@ -821,8 +848,16 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
// prune out the rest of the routes.
roasbeefSatoshi := lnwire.NewShortChanIDFromInt(2340213491)
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{
ErrorSource: ctx.aliases["satoshi"],
ErrorSource: key,
FailureMessage: &lnwire.FailUnknownNextPeer{},
}
}
@ -880,12 +915,11 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
t.Fatalf("incorrect preimage used: expected %x got %x",
preImage[:], paymentPreImage[:])
}
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
ctx.aliases["satoshi"].SerializeCompressed()) {
if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] {
t.Fatalf("route should go through satoshi as first hop, "+
"instead passes through: %v",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases))
}
@ -928,12 +962,11 @@ func TestSendPaymentErrorPathPruning(t *testing.T) {
}
// The route should have satoshi as the first hop.
if !bytes.Equal(route.Hops[0].PubKeyBytes[:],
ctx.aliases["satoshi"].SerializeCompressed()) {
if route.Hops[0].PubKeyBytes != ctx.aliases["satoshi"] {
t.Fatalf("route should go through satoshi as first hop, "+
"instead passes through: %v",
getAliasFromPubKey(route.Hops[0].PubKeyBytes[:],
getAliasFromPubKey(route.Hops[0].PubKeyBytes,
ctx.aliases))
}
}
@ -1231,8 +1264,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
// We will connect node 1 to "sophon"
connectNode := ctx.aliases["sophon"]
if connectNode == nil {
t.Fatalf("could not find node to connect to")
connectNodeKey, err := btcec.ParsePubKey(connectNode[:], btcec.S256())
if err != nil {
t.Fatal(err)
}
var (
@ -1240,12 +1274,12 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
pubKey2 *btcec.PublicKey
)
node1Bytes := priv1.PubKey().SerializeCompressed()
node2Bytes := connectNode.SerializeCompressed()
if bytes.Compare(node1Bytes, node2Bytes) == -1 {
node2Bytes := connectNode
if bytes.Compare(node1Bytes[:], node2Bytes[:]) == -1 {
pubKey1 = priv1.PubKey()
pubKey2 = connectNode
pubKey2 = connectNodeKey
} else {
pubKey1 = connectNode
pubKey1 = connectNodeKey
pubKey2 = priv1.PubKey()
}
@ -1265,9 +1299,9 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
AuthProof: nil,
}
copy(edge.NodeKey1Bytes[:], node1Bytes)
copy(edge.NodeKey2Bytes[:], node2Bytes)
edge.NodeKey2Bytes = node2Bytes
copy(edge.BitcoinKey1Bytes[:], node1Bytes)
copy(edge.BitcoinKey2Bytes[:], node2Bytes)
edge.BitcoinKey2Bytes = node2Bytes
if err := ctx.router.AddEdge(edge); err != nil {
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.
paymentAmt := lnwire.NewMSatFromSatoshis(100)
targetNode := priv2.PubKey()
var targetPubKeyBytes Vertex
copy(targetPubKeyBytes[:], targetNode.SerializeCompressed())
routes, err := ctx.router.FindRoutes(
targetNode, paymentAmt, noFeeLimit, defaultNumRoutes,
ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
DefaultFinalCLTVDelta,
)
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
// updated.
routes, err = ctx.router.FindRoutes(
targetNode, paymentAmt, noFeeLimit, defaultNumRoutes,
ctx.router.selfNode.PubKeyBytes,
targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes,
DefaultFinalCLTVDelta,
)
if err != nil {
@ -1949,15 +1987,9 @@ func TestFindPathFeeWeighting(t *testing.T) {
t.Fatalf("unable to fetch source node: %v", err)
}
ignoreVertex := make(map[Vertex]struct{})
ignoreEdge := make(map[edgeLocator]struct{})
amt := lnwire.MilliSatoshi(100)
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
// the edge weighting, we should select the direct path over the 2 hop
@ -1966,12 +1998,10 @@ func TestFindPathFeeWeighting(t *testing.T) {
&graphParams{
graph: ctx.graph,
},
&restrictParams{
ignoredNodes: ignoreVertex,
ignoredEdges: ignoreEdge,
feeLimit: noFeeLimit,
&RestrictParams{
FeeLimit: noFeeLimit,
},
sourceNode, target, amt,
sourceNode.PubKeyBytes, target, amt,
)
if err != nil {
t.Fatalf("unable to find path: %v", err)

View File

@ -17,6 +17,8 @@ import (
"sync/atomic"
"time"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@ -388,6 +390,10 @@ type rpcServer struct {
// connect to the main gRPC server to proxy all incoming requests.
tlsCfg *tls.Config
// RouterBackend contains the backend implementation of the router
// rpc sub server.
RouterBackend *routerrpc.RouterBackend
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
// gRPC server, and register the main lnrpc server along side.
grpcServer := grpc.NewServer(serverOpts...)
@ -480,6 +508,7 @@ func newRPCServer(s *server, macService *macaroons.Service,
tlsCfg: tlsCfg,
grpcServer: grpcServer,
server: s,
RouterBackend: RouterBackend,
quit: make(chan struct{}, 1),
}
lnrpc.RegisterLightningServer(grpcServer, rootRPCServer)
@ -2764,7 +2793,7 @@ func unmarshallSendToRouteRequest(req *lnrpc.SendToRouteRequest,
type rpcPaymentIntent struct {
msat lnwire.MilliSatoshi
feeLimit lnwire.MilliSatoshi
dest *btcec.PublicKey
dest routing.Vertex
rHash [32]byte
cltvDelta uint16
routeHints [][]routing.HopHint
@ -2778,7 +2807,6 @@ type rpcPaymentIntent struct {
// three ways a client can specify their payment details: a payment request,
// via manual details, or via a complete route.
func extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPaymentIntent, error) {
var err error
payIntent := rpcPaymentIntent{}
// 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[:])
payIntent.dest = payReq.Destination
destKey := payReq.Destination.SerializeCompressed()
copy(payIntent.dest[:], destKey)
payIntent.cltvDelta = uint16(payReq.MinFinalCLTVExpiry())
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
// into the proper representation now. The destination will either be
// encoded as raw bytes, or via a hex string.
var pubBytes []byte
if len(rpcPayReq.Dest) != 0 {
payIntent.dest, err = btcec.ParsePubKey(
rpcPayReq.Dest, btcec.S256(),
)
if err != nil {
return payIntent, err
}
pubBytes = rpcPayReq.Dest
} else {
pubBytes, err := hex.DecodeString(rpcPayReq.DestString)
if err != nil {
return payIntent, err
}
payIntent.dest, err = btcec.ParsePubKey(pubBytes, btcec.S256())
var err error
pubBytes, err = hex.DecodeString(rpcPayReq.DestString)
if err != nil {
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
// (and a custom route wasn't specified), construct the payment
@ -3157,7 +3182,7 @@ func (r *rpcServer) sendPayment(stream *paymentStream) error {
return
}
marshalledRouted := r.marshallRoute(resp.Route)
marshalledRouted := r.RouterBackend.MarshallRoute(resp.Route)
err := stream.send(&lnrpc.SendResponse{
PaymentHash: payIntent.rHash[:],
PaymentPreimage: resp.Preimage[:],
@ -3242,7 +3267,7 @@ func (r *rpcServer) sendPaymentSync(ctx context.Context,
return &lnrpc.SendResponse{
PaymentHash: payIntent.rHash[:],
PaymentPreimage: resp.Preimage[:],
PaymentRoute: r.marshallRoute(resp.Route),
PaymentRoute: r.RouterBackend.MarshallRoute(resp.Route),
}, nil
}
@ -4001,121 +4026,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
func (r *rpcServer) QueryRoutes(ctx context.Context,
in *lnrpc.QueryRoutesRequest) (*lnrpc.QueryRoutesResponse, error) {
// First parse the hex-encoded public key into a full public key object
// 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
return r.RouterBackend.QueryRoutes(ctx, in)
}
// unmarshallHopByChannelLookup unmarshalls an rpc hop for which the pub key is