Merge pull request #3841 from joostjager/check-max-onion-size

routing: check max routing info size
This commit is contained in:
Joost Jager 2019-12-20 14:42:47 +01:00 committed by GitHub
commit 6e0cfe579b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 380 additions and 602 deletions

@ -13,6 +13,7 @@ import (
"github.com/coreos/bbolt" "github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
) )
var ( var (
@ -145,7 +146,14 @@ func (d *databaseChannelGraph) addRandChannel(node1, node2 *btcec.PublicKey,
fetchNode := func(pub *btcec.PublicKey) (*channeldb.LightningNode, error) { fetchNode := func(pub *btcec.PublicKey) (*channeldb.LightningNode, error) {
if pub != nil { if pub != nil {
dbNode, err := d.db.FetchLightningNode(pub) vertex, err := route.NewVertexFromBytes(
pub.SerializeCompressed(),
)
if err != nil {
return nil, err
}
dbNode, err := d.db.FetchLightningNode(nil, vertex)
switch { switch {
case err == channeldb.ErrGraphNodeNotFound: case err == channeldb.ErrGraphNodeNotFound:
fallthrough fallthrough

@ -20,6 +20,7 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/coreos/bbolt" "github.com/coreos/bbolt"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
) )
var ( var (
@ -513,7 +514,7 @@ func (c *ChannelGraph) LookupAlias(pub *btcec.PublicKey) (string, error) {
// DeleteLightningNode starts a new database transaction to remove a vertex/node // DeleteLightningNode starts a new database transaction to remove a vertex/node
// from the database according to the node's public key. // from the database according to the node's public key.
func (c *ChannelGraph) DeleteLightningNode(nodePub *btcec.PublicKey) error { func (c *ChannelGraph) DeleteLightningNode(nodePub route.Vertex) error {
// TODO(roasbeef): ensure dangling edges are removed... // TODO(roasbeef): ensure dangling edges are removed...
return c.db.Update(func(tx *bbolt.Tx) error { return c.db.Update(func(tx *bbolt.Tx) error {
nodes := tx.Bucket(nodeBucket) nodes := tx.Bucket(nodeBucket)
@ -521,9 +522,7 @@ func (c *ChannelGraph) DeleteLightningNode(nodePub *btcec.PublicKey) error {
return ErrGraphNodeNotFound return ErrGraphNodeNotFound
} }
return c.deleteLightningNode( return c.deleteLightningNode(nodes, nodePub[:])
nodes, nodePub.SerializeCompressed(),
)
}) })
} }
@ -2180,10 +2179,17 @@ func (l *LightningNode) isPublic(tx *bbolt.Tx, sourcePubKey []byte) (bool, error
// FetchLightningNode attempts to look up a target node by its identity public // FetchLightningNode attempts to look up a target node by its identity public
// key. If the node isn't found in the database, then ErrGraphNodeNotFound is // key. If the node isn't found in the database, then ErrGraphNodeNotFound is
// returned. // returned.
func (c *ChannelGraph) FetchLightningNode(pub *btcec.PublicKey) (*LightningNode, error) { //
// If the caller wishes to re-use an existing boltdb transaction, then it
// should be passed as the first argument. Otherwise the first argument should
// be nil and a fresh transaction will be created to execute the graph
// traversal.
func (c *ChannelGraph) FetchLightningNode(tx *bbolt.Tx, nodePub route.Vertex) (
*LightningNode, error) {
var node *LightningNode var node *LightningNode
nodePub := pub.SerializeCompressed()
err := c.db.View(func(tx *bbolt.Tx) error { fetchNode := func(tx *bbolt.Tx) error {
// First grab the nodes bucket which stores the mapping from // First grab the nodes bucket which stores the mapping from
// pubKey to node information. // pubKey to node information.
nodes := tx.Bucket(nodeBucket) nodes := tx.Bucket(nodeBucket)
@ -2193,7 +2199,7 @@ func (c *ChannelGraph) FetchLightningNode(pub *btcec.PublicKey) (*LightningNode,
// If a key for this serialized public key isn't found, then // If a key for this serialized public key isn't found, then
// the target node doesn't exist within the database. // the target node doesn't exist within the database.
nodeBytes := nodes.Get(nodePub) nodeBytes := nodes.Get(nodePub[:])
if nodeBytes == nil { if nodeBytes == nil {
return ErrGraphNodeNotFound return ErrGraphNodeNotFound
} }
@ -2210,7 +2216,14 @@ func (c *ChannelGraph) FetchLightningNode(pub *btcec.PublicKey) (*LightningNode,
node = &n node = &n
return nil return nil
}) }
var err error
if tx == nil {
err = c.db.View(fetchNode)
} else {
err = fetchNode(tx)
}
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -20,6 +20,7 @@ import (
"github.com/coreos/bbolt" "github.com/coreos/bbolt"
"github.com/davecgh/go-spew/spew" "github.com/davecgh/go-spew/spew"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/routing/route"
) )
var ( var (
@ -37,6 +38,8 @@ var (
_, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10) _, _ = testSig.S.SetString("18801056069249825825291287104931333862866033135609736119018462340006816851118", 10)
testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features) testFeatures = lnwire.NewFeatureVector(nil, lnwire.Features)
testPub = route.Vertex{2, 202, 4}
) )
func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) { func createLightningNode(db *DB, priv *btcec.PrivateKey) (*LightningNode, error) {
@ -80,7 +83,6 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// We'd like to test basic insertion/deletion for vertexes from the // We'd like to test basic insertion/deletion for vertexes from the
// graph, so we'll create a test vertex to start with. // graph, so we'll create a test vertex to start with.
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{ node := &LightningNode{
HaveNodeAnnouncement: true, HaveNodeAnnouncement: true,
AuthSigBytes: testSig.Serialize(), AuthSigBytes: testSig.Serialize(),
@ -90,9 +92,9 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
Features: testFeatures, Features: testFeatures,
Addresses: testAddrs, Addresses: testAddrs,
ExtraOpaqueData: []byte("extra new data"), ExtraOpaqueData: []byte("extra new data"),
PubKeyBytes: testPub,
db: db, db: db,
} }
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
// First, insert the node into the graph DB. This should succeed // First, insert the node into the graph DB. This should succeed
// without any errors. // without any errors.
@ -102,7 +104,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// Next, fetch the node from the database to ensure everything was // Next, fetch the node from the database to ensure everything was
// serialized properly. // serialized properly.
dbNode, err := graph.FetchLightningNode(testPub) dbNode, err := graph.FetchLightningNode(nil, testPub)
if err != nil { if err != nil {
t.Fatalf("unable to locate node: %v", err) t.Fatalf("unable to locate node: %v", err)
} }
@ -126,7 +128,7 @@ func TestNodeInsertionAndDeletion(t *testing.T) {
// Finally, attempt to fetch the node again. This should fail as the // Finally, attempt to fetch the node again. This should fail as the
// node should have been deleted from the database. // node should have been deleted from the database.
_, err = graph.FetchLightningNode(testPub) _, err = graph.FetchLightningNode(nil, testPub)
if err != ErrGraphNodeNotFound { if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!") t.Fatalf("fetch after delete should fail!")
} }
@ -147,11 +149,10 @@ func TestPartialNode(t *testing.T) {
// We want to be able to insert nodes into the graph that only has the // We want to be able to insert nodes into the graph that only has the
// PubKey set. // PubKey set.
_, testPub := btcec.PrivKeyFromBytes(btcec.S256(), key[:])
node := &LightningNode{ node := &LightningNode{
HaveNodeAnnouncement: false, HaveNodeAnnouncement: false,
PubKeyBytes: testPub,
} }
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
if err := graph.AddLightningNode(node); err != nil { if err := graph.AddLightningNode(node); err != nil {
t.Fatalf("unable to add node: %v", err) t.Fatalf("unable to add node: %v", err)
@ -159,7 +160,7 @@ func TestPartialNode(t *testing.T) {
// Next, fetch the node from the database to ensure everything was // Next, fetch the node from the database to ensure everything was
// serialized properly. // serialized properly.
dbNode, err := graph.FetchLightningNode(testPub) dbNode, err := graph.FetchLightningNode(nil, testPub)
if err != nil { if err != nil {
t.Fatalf("unable to locate node: %v", err) t.Fatalf("unable to locate node: %v", err)
} }
@ -175,9 +176,9 @@ func TestPartialNode(t *testing.T) {
node = &LightningNode{ node = &LightningNode{
HaveNodeAnnouncement: false, HaveNodeAnnouncement: false,
LastUpdate: time.Unix(0, 0), LastUpdate: time.Unix(0, 0),
PubKeyBytes: testPub,
db: db, db: db,
} }
copy(node.PubKeyBytes[:], testPub.SerializeCompressed())
if err := compareNodes(node, dbNode); err != nil { if err := compareNodes(node, dbNode); err != nil {
t.Fatalf("nodes don't match: %v", err) t.Fatalf("nodes don't match: %v", err)
@ -191,7 +192,7 @@ func TestPartialNode(t *testing.T) {
// Finally, attempt to fetch the node again. This should fail as the // Finally, attempt to fetch the node again. This should fail as the
// node should have been deleted from the database. // node should have been deleted from the database.
_, err = graph.FetchLightningNode(testPub) _, err = graph.FetchLightningNode(nil, testPub)
if err != ErrGraphNodeNotFound { if err != ErrGraphNodeNotFound {
t.Fatalf("fetch after delete should fail!") t.Fatalf("fetch after delete should fail!")
} }
@ -2386,11 +2387,8 @@ func TestPruneGraphNodes(t *testing.T) {
// Finally, we'll ensure that node3, the only fully unconnected node as // Finally, we'll ensure that node3, the only fully unconnected node as
// properly deleted from the graph and not another node in its place. // properly deleted from the graph and not another node in its place.
node3Pub, err := node3.PubKey() _, err = graph.FetchLightningNode(nil, node3.PubKeyBytes)
if err != nil { if err == nil {
t.Fatalf("unable to fetch the pubkey of node3: %v", err)
}
if _, err := graph.FetchLightningNode(node3Pub); err == nil {
t.Fatalf("node 3 should have been deleted!") t.Fatalf("node 3 should have been deleted!")
} }
} }
@ -2430,18 +2428,9 @@ func TestAddChannelEdgeShellNodes(t *testing.T) {
t.Fatalf("unable to add edge: %v", err) t.Fatalf("unable to add edge: %v", err)
} }
node1Pub, err := node1.PubKey()
if err != nil {
t.Fatalf("unable to parse node 1 pub: %v", err)
}
node2Pub, err := node2.PubKey()
if err != nil {
t.Fatalf("unable to parse node 2 pub: %v", err)
}
// Ensure that node1 was inserted as a full node, while node2 only has // Ensure that node1 was inserted as a full node, while node2 only has
// a shell node present. // a shell node present.
node1, err = graph.FetchLightningNode(node1Pub) node1, err = graph.FetchLightningNode(nil, node1.PubKeyBytes)
if err != nil { if err != nil {
t.Fatalf("unable to fetch node1: %v", err) t.Fatalf("unable to fetch node1: %v", err)
} }
@ -2449,7 +2438,7 @@ func TestAddChannelEdgeShellNodes(t *testing.T) {
t.Fatalf("have shell announcement for node1, shouldn't") t.Fatalf("have shell announcement for node1, shouldn't")
} }
node2, err = graph.FetchLightningNode(node2Pub) node2, err = graph.FetchLightningNode(nil, node2.PubKeyBytes)
if err != nil { if err != nil {
t.Fatalf("unable to fetch node2: %v", err) t.Fatalf("unable to fetch node2: %v", err)
} }
@ -2504,8 +2493,7 @@ func TestNodePruningUpdateIndexDeletion(t *testing.T) {
// We'll now delete the node from the graph, this should result in it // We'll now delete the node from the graph, this should result in it
// being removed from the update index as well. // being removed from the update index as well.
nodePub, _ := node1.PubKey() if err := graph.DeleteLightningNode(node1.PubKeyBytes); err != nil {
if err := graph.DeleteLightningNode(nodePub); err != nil {
t.Fatalf("unable to delete node: %v", err) t.Fatalf("unable to delete node: %v", err)
} }

@ -98,6 +98,11 @@ func (r *MPP) Record() tlv.Record {
) )
} }
// PayloadSize returns the size this record takes up in encoded form.
func (r *MPP) PayloadSize() uint64 {
return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
}
// String returns a human-readable representation of the mpp payload field. // String returns a human-readable representation of the mpp payload field.
func (r *MPP) String() string { func (r *MPP) String() string {
return fmt.Sprintf("total=%v, addr=%x", r.totalMsat, r.paymentAddr) return fmt.Sprintf("total=%v, addr=%x", r.totalMsat, r.paymentAddr)

@ -24,9 +24,10 @@ type nodeWithDist struct {
// amount that includes also the fees for subsequent hops. // amount that includes also the fees for subsequent hops.
amountToReceive lnwire.MilliSatoshi amountToReceive lnwire.MilliSatoshi
// incomingCltv is the expected cltv value for the incoming htlc of this // incomingCltv is the expected absolute expiry height for the incoming
// node. This value does not include the final cltv. // htlc of this node. This value should already include the final cltv
incomingCltv uint32 // delta.
incomingCltv int32
// probability is the probability that from this node onward the route // probability is the probability that from this node onward the route
// is successful. // is successful.
@ -39,6 +40,10 @@ type nodeWithDist struct {
// nextHop is the edge this route comes from. // nextHop is the edge this route comes from.
nextHop *channeldb.ChannelEdgePolicy nextHop *channeldb.ChannelEdgePolicy
// routingInfoSize is the total size requirement for the payloads field
// in the onion packet from this hop towards the final destination.
routingInfoSize uint64
} }
// distanceHeap is a min-distance heap that's used within our path finding // distanceHeap is a min-distance heap that's used within our path finding

@ -7,8 +7,8 @@ import (
"math" "math"
"time" "time"
"github.com/btcsuite/btcd/btcec"
"github.com/coreos/bbolt" "github.com/coreos/bbolt"
sphinx "github.com/lightningnetwork/lightning-onion"
"github.com/lightningnetwork/lnd/channeldb" "github.com/lightningnetwork/lnd/channeldb"
"github.com/lightningnetwork/lnd/feature" "github.com/lightningnetwork/lnd/feature"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
@ -17,12 +17,6 @@ import (
) )
const ( const (
// HopLimit is the maximum number hops that is permissible as a route.
// Any potential paths found that lie above this limit will be rejected
// with an error. This value is computed using the current fixed-size
// packet length of the Sphinx construction.
HopLimit = 20
// infinity is used as a starting distance in our shortest path search. // infinity is used as a starting distance in our shortest path search.
infinity = math.MaxInt64 infinity = math.MaxInt64
@ -48,7 +42,8 @@ const (
// pathFinder defines the interface of a path finding algorithm. // pathFinder defines the interface of a path finding algorithm.
type pathFinder = func(g *graphParams, r *RestrictParams, type pathFinder = func(g *graphParams, r *RestrictParams,
cfg *PathFindingConfig, source, target route.Vertex, cfg *PathFindingConfig, source, target route.Vertex,
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, error) amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
[]*channeldb.ChannelEdgePolicy, error)
var ( var (
// DefaultPaymentAttemptPenalty is the virtual cost in path finding weight // DefaultPaymentAttemptPenalty is the virtual cost in path finding weight
@ -79,10 +74,6 @@ var (
// not exist in the graph. // not exist in the graph.
errNoPathFound = errors.New("unable to find a path to destination") errNoPathFound = errors.New("unable to find a path to destination")
// errMaxHopsExceeded is returned when a candidate path is found, but
// the length of that path exceeds HopLimit.
errMaxHopsExceeded = errors.New("potential path has too many hops")
// errInsufficientLocalBalance is returned when none of the local // errInsufficientLocalBalance is returned when none of the local
// channels have enough balance for the payment. // channels have enough balance for the payment.
errInsufficientBalance = errors.New("insufficient local balance") errInsufficientBalance = errors.New("insufficient local balance")
@ -412,7 +403,7 @@ func getMaxOutgoingAmt(node route.Vertex, outgoingChan *uint64,
// 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, cfg *PathFindingConfig, func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
source, target route.Vertex, amt lnwire.MilliSatoshi) ( source, target route.Vertex, amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
[]*channeldb.ChannelEdgePolicy, error) { []*channeldb.ChannelEdgePolicy, error) {
// Pathfinding can be a significant portion of the total payment // Pathfinding can be a significant portion of the total payment
@ -447,12 +438,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// we have for the target node from our graph. // we have for the target node from our graph.
features := r.DestFeatures features := r.DestFeatures
if features == nil { if features == nil {
targetKey, err := btcec.ParsePubKey(target[:], btcec.S256()) targetNode, err := g.graph.FetchLightningNode(tx, target)
if err != nil {
return nil, err
}
targetNode, err := g.graph.FetchLightningNode(targetKey)
switch { switch {
// If the node exists and has features, use them directly. // If the node exists and has features, use them directly.
@ -534,6 +520,23 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
} }
} }
// Build a preliminary destination hop structure to obtain the payload
// size.
var mpp *record.MPP
if r.PaymentAddr != nil {
mpp = record.NewMPP(amt, *r.PaymentAddr)
}
finalHop := route.Hop{
AmtToForward: amt,
OutgoingTimeLock: uint32(finalHtlcExpiry),
CustomRecords: r.DestCustomRecords,
LegacyPayload: !features.HasFeature(
lnwire.TLVOnionPayloadOptional,
),
MPP: mpp,
}
// 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 so we'll manually include the target node. // advertised to the network so we'll manually include the target node.
// The target node charges no fee. Distance is set to 0, because this is // The target node charges no fee. Distance is set to 0, because this is
@ -548,13 +551,19 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
weight: 0, weight: 0,
node: target, node: target,
amountToReceive: amt, amountToReceive: amt,
incomingCltv: 0, incomingCltv: finalHtlcExpiry,
probability: 1, probability: 1,
routingInfoSize: finalHop.PayloadSize(0),
} }
// Calculate the absolute cltv limit. Use uint64 to prevent an overflow
// if the cltv limit is MaxUint32.
absoluteCltvLimit := uint64(r.CltvLimit) + uint64(finalHtlcExpiry)
// 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(fromVertex route.Vertex, processEdge := func(fromVertex route.Vertex,
fromFeatures *lnwire.FeatureVector,
edge *channeldb.ChannelEdgePolicy, toNodeDist *nodeWithDist) { edge *channeldb.ChannelEdgePolicy, toNodeDist *nodeWithDist) {
edgesExpanded++ edgesExpanded++
@ -596,11 +605,10 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
timeLockDelta = edge.TimeLockDelta timeLockDelta = edge.TimeLockDelta
} }
incomingCltv := toNodeDist.incomingCltv + incomingCltv := toNodeDist.incomingCltv + int32(timeLockDelta)
uint32(timeLockDelta)
// Check that we are within our CLTV limit. // Check that we are within our CLTV limit.
if incomingCltv > r.CltvLimit { if uint64(incomingCltv) > absoluteCltvLimit {
return return
} }
@ -676,34 +684,32 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
edge.ChannelID) edge.ChannelID)
} }
switch { // Calculate the total routing info size if this hop were to be
// included. If we are coming from the source hop, the payload
// size is zero, because the original htlc isn't in the onion
// blob.
var payloadSize uint64
if fromVertex != source {
supportsTlv := fromFeatures.HasFeature(
lnwire.TLVOnionPayloadOptional,
)
// If this edge takes us to the final hop, we'll set the node hop := route.Hop{
// features to those determined above. These are either taken AmtToForward: amountToSend,
// from the destination features, e.g. virtual or invoice OutgoingTimeLock: uint32(
// features, or loaded as a fallback from the graph. The toNodeDist.incomingCltv,
// transitive dependencies were already validated above, so no ),
// need to do so now. LegacyPayload: !supportsTlv,
//
// NOTE: This may overwrite features loaded from the graph if
// destination features were provided. This is fine though,
// since our route construction does not care where the features
// are actually taken from. In the future we may wish to do
// route construction within findPath, and avoid using
// ChannelEdgePolicy altogether.
case edge.Node.PubKeyBytes == target:
edge.Node.Features = features
// Otherwise, this is some other intermediary node. Verify the
// transitive feature dependencies for this node, and skip the
// channel if they are invalid.
default:
err := feature.ValidateDeps(edge.Node.Features)
if err != nil {
log.Tracef("Node %x has invalid features",
edge.Node.PubKeyBytes)
return
} }
payloadSize = hop.PayloadSize(edge.ChannelID)
}
routingInfoSize := toNodeDist.routingInfoSize + payloadSize
// Skip paths that would exceed the maximum routing info size.
if routingInfoSize > sphinx.MaxPayloadSize {
return
} }
// All conditions are met and this new tentative distance is // All conditions are met and this new tentative distance is
@ -718,6 +724,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
incomingCltv: incomingCltv, incomingCltv: incomingCltv,
probability: probability, probability: probability,
nextHop: edge, nextHop: edge,
routingInfoSize: routingInfoSize,
} }
distance[fromVertex] = withDist distance[fromVertex] = withDist
@ -730,6 +737,44 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
// TODO(roasbeef): also add path caching // TODO(roasbeef): also add path caching
// * similar to route caching, but doesn't factor in the amount // * similar to route caching, but doesn't factor in the amount
// Cache features because we visit nodes multiple times.
featureCache := make(map[route.Vertex]*lnwire.FeatureVector)
// getGraphFeatures returns (cached) node features from the graph.
getGraphFeatures := func(node route.Vertex) (*lnwire.FeatureVector,
error) {
// Check cache for features of the fromNode.
fromFeatures, ok := featureCache[node]
if !ok {
targetNode, err := g.graph.FetchLightningNode(tx, node)
switch {
// If the node exists and has valid features, use them.
case err == nil:
err := feature.ValidateDeps(targetNode.Features)
if err == nil {
fromFeatures = targetNode.Features
}
// If an error other than the node not existing is hit,
// abort.
case err != channeldb.ErrGraphNodeNotFound:
return nil, err
// Otherwise, we couldn't find a node announcement,
// populate a blank feature vector.
default:
fromFeatures = lnwire.EmptyFeatureVector()
}
// Update cache.
featureCache[node] = fromFeatures
}
return fromFeatures, nil
}
routeToSelf := source == target routeToSelf := source == target
for { for {
nodesVisited++ nodesVisited++
@ -777,9 +822,20 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
continue continue
} }
// Get feature vector for fromNode.
fromFeatures, err := getGraphFeatures(fromNode)
if err != nil {
return nil, err
}
// If there are no valid features, skip this node.
if fromFeatures == nil {
continue
}
// Check if this candidate node is better than what we // Check if this candidate node is better than what we
// already have. // already have.
processEdge(fromNode, policy, partialPath) processEdge(fromNode, fromFeatures, policy, partialPath)
} }
if nodeHeap.Len() == 0 { if nodeHeap.Len() == 0 {
@ -824,17 +880,21 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
} }
} }
// The route is invalid if it spans more than 20 hops. The current // For the final hop, we'll set the node features to those determined
// Sphinx (onion routing) implementation can only encode up to 20 hops // above. These are either taken from the destination features, e.g.
// as the entire packet is fixed size. If this route is more than 20 // virtual or invoice features, or loaded as a fallback from the graph.
// hops, then it's invalid. // The transitive dependencies were already validated above, so no need
numEdges := len(pathEdges) // to do so now.
if numEdges > HopLimit { //
return nil, errMaxHopsExceeded // NOTE: This may overwrite features loaded from the graph if
} // destination features were provided. This is fine though, since our
// route construction does not care where the features are actually
// taken from. In the future we may wish to do route construction within
// findPath, and avoid using ChannelEdgePolicy altogether.
pathEdges[len(pathEdges)-1].Node.Features = features
log.Debugf("Found route: probability=%v, hops=%v, fee=%v\n", log.Debugf("Found route: probability=%v, hops=%v, fee=%v",
distance[source].probability, numEdges, distance[source].probability, len(pathEdges),
distance[source].amountToReceive-amt) distance[source].amountToReceive-amt)
return pathEdges, nil return pathEdges, nil

@ -823,6 +823,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
}, },
testPathFindingConfig, testPathFindingConfig,
sourceNode.PubKeyBytes, target, paymentAmt, sourceNode.PubKeyBytes, target, paymentAmt,
startingHeight+finalHopCLTV,
) )
if test.expectFailureNoPath { if test.expectFailureNoPath {
if err == nil { if err == nil {
@ -1012,6 +1013,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) {
}, },
r, testPathFindingConfig, r, testPathFindingConfig,
sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt, sourceNode.PubKeyBytes, doge.PubKeyBytes, paymentAmt,
0,
) )
} }
@ -1358,53 +1360,53 @@ func TestNewRoute(t *testing.T) {
} }
func TestNewRoutePathTooLong(t *testing.T) { func TestNewRoutePathTooLong(t *testing.T) {
t.Skip() t.Parallel()
// Ensure that potential paths which are over the maximum hop-limit are var testChannels []*testChannel
// rejected.
graph, err := parseTestGraph(excessiveHopsGraphFilePath) // Setup a linear network of 21 hops.
fromNode := "start"
for i := 0; i < 21; i++ {
toNode := fmt.Sprintf("node-%v", i+1)
c := symmetricTestChannel(fromNode, toNode, 100000, &testChannelPolicy{
Expiry: 144,
FeeRate: 400,
MinHTLC: 1,
MaxHTLC: 100000001,
})
testChannels = append(testChannels, c)
fromNode = toNode
}
ctx := newPathFindingTestContext(t, testChannels, "start")
defer ctx.cleanup()
// Assert that we can find 20 hop routes.
node20 := ctx.keyFromAlias("node-20")
payAmt := lnwire.MilliSatoshi(100001)
_, err := ctx.findPath(node20, payAmt)
if err != nil { if err != nil {
t.Fatalf("unable to create graph: %v", err) t.Fatalf("unexpected pathfinding failure: %v", err)
}
defer graph.cleanUp()
sourceNode, err := graph.graph.SourceNode()
if err != nil {
t.Fatalf("unable to fetch source node: %v", err)
} }
paymentAmt := lnwire.NewMSatFromSatoshis(100) // Assert that finding a 21 hop route fails.
node21 := ctx.keyFromAlias("node-21")
// We start by confirming that routing a payment 20 hops away is _, err = ctx.findPath(node21, payAmt)
// possible. Alice should be able to find a valid route to ursula. if err != errNoPathFound {
target := graph.aliasMap["ursula"] t.Fatalf("not route error expected, but got %v", err)
_, err = findPath(
&graphParams{
graph: graph.graph,
},
noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, paymentAmt,
)
if err != nil {
t.Fatalf("path should have been found")
} }
// Vincent is 21 hops away from Alice, and thus no valid route should be // Assert that we can't find a 20 hop route if custom records make it
// presented to Alice. // exceed the maximum payload size.
target = graph.aliasMap["vincent"] ctx.restrictParams.DestFeatures = tlvFeatures
path, err := findPath( ctx.restrictParams.DestCustomRecords = map[uint64][]byte{
&graphParams{ 100000: bytes.Repeat([]byte{1}, 100),
graph: graph.graph, }
}, _, err = ctx.findPath(node20, payAmt)
noRestrictions, testPathFindingConfig, if err != errNoPathFound {
sourceNode.PubKeyBytes, target, paymentAmt, t.Fatalf("not route error expected, but got %v", err)
)
if err == nil {
t.Fatalf("should not have been able to find path, supposed to be "+
"greater than 20 hops, found route with %v hops",
len(path))
} }
} }
func TestPathNotAvailable(t *testing.T) { func TestPathNotAvailable(t *testing.T) {
@ -1437,7 +1439,7 @@ func TestPathNotAvailable(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, unknownNode, 100, sourceNode.PubKeyBytes, unknownNode, 100, 0,
) )
if err != errNoPathFound { if err != errNoPathFound {
t.Fatalf("path shouldn't have been found: %v", err) t.Fatalf("path shouldn't have been found: %v", err)
@ -1495,7 +1497,7 @@ func TestDestTLVGraphFallback(t *testing.T) {
graph: ctx.graphParams.graph, graph: ctx.graphParams.graph,
}, },
r, testPathFindingConfig, r, testPathFindingConfig,
sourceNode.PubKeyBytes, target, 100, sourceNode.PubKeyBytes, target, 100, 0,
) )
} }
@ -1604,7 +1606,7 @@ func TestMissingFeatureDep(t *testing.T) {
graph: ctx.graphParams.graph, graph: ctx.graphParams.graph,
}, },
r, testPathFindingConfig, r, testPathFindingConfig,
sourceNode.PubKeyBytes, target, 100, sourceNode.PubKeyBytes, target, 100, 0,
) )
} }
@ -1682,7 +1684,7 @@ func TestDestPaymentAddr(t *testing.T) {
graph: ctx.graphParams.graph, graph: ctx.graphParams.graph,
}, },
r, testPathFindingConfig, r, testPathFindingConfig,
sourceNode.PubKeyBytes, target, 100, sourceNode.PubKeyBytes, target, 100, 0,
) )
} }
@ -1743,7 +1745,7 @@ func TestPathInsufficientCapacity(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != errNoPathFound { if 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)
@ -1776,7 +1778,7 @@ func TestRouteFailMinHTLC(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != errNoPathFound { if 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)
@ -1875,7 +1877,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@ -1903,7 +1905,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@ -1928,7 +1930,7 @@ func TestRouteFailDisabledEdge(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != errNoPathFound { if 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)
@ -1962,7 +1964,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
graph: graph.graph, graph: graph.graph,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@ -1986,7 +1988,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
bandwidthHints: bandwidths, bandwidthHints: bandwidths,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != errNoPathFound { if 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)
@ -2004,7 +2006,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
bandwidthHints: bandwidths, bandwidthHints: bandwidths,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@ -2035,7 +2037,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) {
bandwidthHints: bandwidths, bandwidthHints: bandwidths,
}, },
noRestrictions, testPathFindingConfig, noRestrictions, testPathFindingConfig,
sourceNode.PubKeyBytes, target, payAmt, sourceNode.PubKeyBytes, target, payAmt, 0,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)
@ -2070,11 +2072,7 @@ 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"]
bobKey, err := btcec.ParsePubKey(bob[:], btcec.S256()) bobNode, err := ctx.graph.FetchLightningNode(nil, bob)
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)
} }
@ -2123,11 +2121,7 @@ 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"]
aliceKey, err := btcec.ParsePubKey(alice[:], btcec.S256()) aliceNode, err := ctx.graph.FetchLightningNode(nil, alice)
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)
} }
@ -2880,7 +2874,7 @@ func (c *pathFindingTestContext) findPath(target route.Vertex,
return findPath( return findPath(
&c.graphParams, &c.restrictParams, &c.pathFindingConfig, &c.graphParams, &c.restrictParams, &c.pathFindingConfig,
c.source, target, amt, c.source, target, amt, 0,
) )
} }

@ -196,7 +196,6 @@ func errorToPaymentFailure(err error) channeldb.FailureReason {
errNoTlvPayload, errNoTlvPayload,
errNoPaymentAddr, errNoPaymentAddr,
errNoPathFound, errNoPathFound,
errMaxHopsExceeded,
errPrebuiltRouteTried: errPrebuiltRouteTried:
return channeldb.FailureReasonNoRoute return channeldb.FailureReasonNoRoute

@ -113,6 +113,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
return nil, err return nil, err
} }
finalHtlcExpiry := int32(height) + int32(finalCltvDelta)
path, err := p.pathFinder( path, err := p.pathFinder(
&graphParams{ &graphParams{
graph: ss.Graph, graph: ss.Graph,
@ -121,7 +123,7 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment,
}, },
restrictions, &ss.PathFindingConfig, restrictions, &ss.PathFindingConfig,
ss.SelfNode.PubKeyBytes, payment.Target, ss.SelfNode.PubKeyBytes, payment.Target,
payment.Amount, payment.Amount, finalHtlcExpiry,
) )
if err != nil { if err != nil {
return nil, err return nil, err

@ -15,8 +15,8 @@ func TestRequestRoute(t *testing.T) {
findPath := func(g *graphParams, r *RestrictParams, findPath := func(g *graphParams, r *RestrictParams,
cfg *PathFindingConfig, source, target route.Vertex, cfg *PathFindingConfig, source, target route.Vertex,
amt lnwire.MilliSatoshi) ([]*channeldb.ChannelEdgePolicy, amt lnwire.MilliSatoshi, finalHtlcExpiry int32) (
error) { []*channeldb.ChannelEdgePolicy, error) {
// We expect find path to receive a cltv limit excluding the // We expect find path to receive a cltv limit excluding the
// final cltv delta (including the block padding). // final cltv delta (including the block padding).

@ -184,6 +184,53 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64) error {
return tlvStream.Encode(w) return tlvStream.Encode(w)
} }
// Size returns the total size this hop's payload would take up in the onion
// packet.
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
if h.LegacyPayload {
return sphinx.LegacyHopDataSize
}
var payloadSize uint64
addRecord := func(tlvType tlv.Type, length uint64) {
payloadSize += tlv.VarIntSize(uint64(tlvType)) +
tlv.VarIntSize(length) + length
}
// Add amount size.
addRecord(record.AmtOnionType, tlv.SizeTUint64(uint64(h.AmtToForward)))
// Add lock time size.
addRecord(
record.LockTimeOnionType,
tlv.SizeTUint64(uint64(h.OutgoingTimeLock)),
)
// Add next hop if present.
if nextChanID != 0 {
addRecord(record.NextHopOnionType, 8)
}
// Add mpp if present.
if h.MPP != nil {
addRecord(record.MPPOnionType, h.MPP.PayloadSize())
}
// Add custom records.
for k, v := range h.CustomRecords {
addRecord(tlv.Type(k), uint64(len(v)))
}
// Add the size required to encode the payload length.
payloadSize += tlv.VarIntSize(payloadSize)
// Add HMAC.
payloadSize += sphinx.HMACSize
return payloadSize
}
// Route represents a path through the channel graph which runs over one or // Route represents a path through the channel graph which runs over one or
// more channels in succession. This struct carries all the information // more channels in succession. This struct carries all the information
// required to craft the Sphinx onion packet, and send the payment along the // required to craft the Sphinx onion packet, and send the payment along the

@ -2,12 +2,20 @@ package route
import ( import (
"bytes" "bytes"
"encoding/hex"
"testing" "testing"
"github.com/btcsuite/btcd/btcec"
"github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/lnwire"
"github.com/lightningnetwork/lnd/record" "github.com/lightningnetwork/lnd/record"
) )
var (
testPrivKeyBytes, _ = hex.DecodeString("e126f68f7eafcc8b74f54d269fe206be715000f94dac067d1c04a8ca3b2db734")
_, testPubKey = btcec.PrivKeyFromBytes(btcec.S256(), testPrivKeyBytes)
testPubKeyBytes, _ = NewVertexFromBytes(testPubKey.SerializeCompressed())
)
// TestRouteTotalFees checks that a route reports the expected total fee. // TestRouteTotalFees checks that a route reports the expected total fee.
func TestRouteTotalFees(t *testing.T) { func TestRouteTotalFees(t *testing.T) {
t.Parallel() t.Parallel()
@ -56,7 +64,6 @@ func TestRouteTotalFees(t *testing.T) {
if r.TotalFees() != fee { if r.TotalFees() != fee {
t.Fatalf("expected %v fees, got %v", fee, r.TotalFees()) t.Fatalf("expected %v fees, got %v", fee, r.TotalFees())
} }
} }
var ( var (
@ -93,3 +100,57 @@ func TestMPPHop(t *testing.T) {
t.Fatalf("expected err: %v, got: %v", nil, err) t.Fatalf("expected err: %v, got: %v", nil, err)
} }
} }
// TestPayloadSize tests the payload size calculation that is provided by Hop
// structs.
func TestPayloadSize(t *testing.T) {
hops := []*Hop{
{
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1000,
OutgoingTimeLock: 600000,
ChannelID: 3432483437438,
LegacyPayload: true,
},
{
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1200,
OutgoingTimeLock: 700000,
ChannelID: 63584534844,
},
{
PubKeyBytes: testPubKeyBytes,
AmtToForward: 1200,
OutgoingTimeLock: 700000,
MPP: record.NewMPP(500, [32]byte{}),
CustomRecords: map[uint64][]byte{
100000: {1, 2, 3},
1000000: {4, 5},
},
},
}
rt := Route{
Hops: hops,
}
path, err := rt.ToSphinxPath()
if err != nil {
t.Fatal(err)
}
for i, onionHop := range path[:path.TrueRouteLength()] {
hop := hops[i]
var nextChan uint64
if i < len(hops)-1 {
nextChan = hops[i+1].ChannelID
}
expected := uint64(onionHop.HopPayload.NumBytes())
actual := hop.PayloadSize(nextChan)
if expected != actual {
t.Fatalf("unexpected payload size at hop %v: "+
"expected %v, got %v",
i, expected, actual)
}
}
}

@ -1431,27 +1431,29 @@ func (r *ChannelRouter) FindRoute(source, target route.Vertex,
return nil, err return nil, err
} }
// We'll fetch the current block height so we can properly calculate the
// required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
// Now that we know the destination is reachable within the graph, we'll // Now that we know the destination is reachable within the graph, we'll
// execute our path finding algorithm. // execute our path finding algorithm.
finalHtlcExpiry := currentHeight + int32(finalCLTVDelta)
path, err := findPath( path, err := findPath(
&graphParams{ &graphParams{
graph: r.cfg.Graph, graph: r.cfg.Graph,
bandwidthHints: bandwidthHints, bandwidthHints: bandwidthHints,
}, },
restrictions, &r.cfg.PathFindingConfig, restrictions, &r.cfg.PathFindingConfig,
source, target, amt, source, target, amt, finalHtlcExpiry,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
// We'll fetch the current block height so we can properly calculate the
// required HTLC time locks within the route.
_, currentHeight, err := r.cfg.Chain.GetBestBlock()
if err != nil {
return nil, err
}
// Create the route with absolute time lock values. // Create the route with absolute time lock values.
route, err := newRoute( route, err := newRoute(
source, path, uint32(currentHeight), source, path, uint32(currentHeight),
@ -2089,11 +2091,7 @@ func (r *ChannelRouter) GetChannelByID(chanID lnwire.ShortChannelID) (
// //
// NOTE: This method is part of the ChannelGraphSource interface. // NOTE: This method is part of the ChannelGraphSource interface.
func (r *ChannelRouter) FetchLightningNode(node route.Vertex) (*channeldb.LightningNode, error) { func (r *ChannelRouter) FetchLightningNode(node route.Vertex) (*channeldb.LightningNode, error) {
pubKey, err := btcec.ParsePubKey(node[:], btcec.S256()) return r.cfg.Graph.FetchLightningNode(nil, node)
if err != nil {
return nil, fmt.Errorf("unable to parse raw public key: %v", err)
}
return r.cfg.Graph.FetchLightningNode(pubKey)
} }
// ForEachNode is used to iterate over every node in router topology. // ForEachNode is used to iterate over every node in router topology.

@ -1319,7 +1319,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
t.Fatalf("unable to find any routes: %v", err) t.Fatalf("unable to find any routes: %v", err)
} }
copy1, err := ctx.graph.FetchLightningNode(priv1.PubKey()) copy1, err := ctx.graph.FetchLightningNode(nil, pub1)
if err != nil { if err != nil {
t.Fatalf("unable to fetch node: %v", err) t.Fatalf("unable to fetch node: %v", err)
} }
@ -1328,7 +1328,7 @@ func TestAddEdgeUnknownVertexes(t *testing.T) {
t.Fatalf("fetched node not equal to original") t.Fatalf("fetched node not equal to original")
} }
copy2, err := ctx.graph.FetchLightningNode(priv2.PubKey()) copy2, err := ctx.graph.FetchLightningNode(nil, pub2)
if err != nil { if err != nil {
t.Fatalf("unable to fetch node: %v", err) t.Fatalf("unable to fetch node: %v", err)
} }
@ -2174,7 +2174,7 @@ func TestFindPathFeeWeighting(t *testing.T) {
}, },
noRestrictions, noRestrictions,
testPathFindingConfig, testPathFindingConfig,
sourceNode.PubKeyBytes, target, amt, sourceNode.PubKeyBytes, target, amt, 0,
) )
if err != nil { if err != nil {
t.Fatalf("unable to find path: %v", err) t.Fatalf("unable to find path: %v", err)

@ -1,410 +0,0 @@
{
"nodes": [
{
"source": true,
"pubkey": "021b96642e723592ee0b095983fe3a26c8b40b8926968d8b7510e51c9429d4562c",
"alias": "alice"
},
{
"source": false,
"pubkey": "022096b2b0ac083e708074a5ab57288bc821b6bef7b964185b307e073772c3748f",
"alias": "bob"
},
{
"source": false,
"pubkey": "022a190ce901ab2b6f349483f18b28a1d72c64a7bccb8057291f25784c0899840f",
"alias": "carol"
},
{
"source": false,
"pubkey": "022d855d09971dd047b7ecf929b23c6f147b568d4668af67fb2226eb8c15c4660d",
"alias": "dave"
},
{
"source": false,
"pubkey": "024ca436834b0d38d9dc7ee4d95aa21db321c45598dc5921a4a52304a8e0dd2952",
"alias": "eve"
},
{
"source": false,
"pubkey": "025234a0c44cbf1b20c18e2c397107ad731376831e1c43ddb360b41dbb98c10266",
"alias": "fez"
},
{
"source": false,
"pubkey": "0253e9d03030f2ff08d3a7f1d824ad6d8c0dae422f324e72d5bb313e3f2a2d45a8",
"alias": "gabby"
},
{
"source": false,
"pubkey": "0263d4f2baca258ff3bd5bce86c2754e95daaea27f268ae1a048c1253ff20de56e",
"alias": "harold"
},
{
"source": false,
"pubkey": "02650db8e44302f75e265e9427264bc0d7e2337831d6b9ceb7c58ed1e725d4576a",
"alias": "inez"
},
{
"source": false,
"pubkey": "02727bfd298aa055a6419404931dfc1ccb4f0eb7c9660a7df346b93d0025df3ba1",
"alias": "jake"
},
{
"source": false,
"pubkey": "0280c83b3eded413dcec12f7952410e2738f079bd9cbc9a7c462e32ed4d74bd5b7",
"alias": "karen"
},
{
"source": false,
"pubkey": "0290bf454f4b95baf9227801301b331e35d477c6b6e7f36a599983ae58747b3828",
"alias": "liam"
},
{
"source": false,
"pubkey": "0297c8de635d17e3dd5775edfa2797be0874c53b0026f69009787cecd2fa577de8",
"alias": "maggie"
},
{
"source": false,
"pubkey": "02a27227113c71eab0c8609ac0cdc7e76791fc3163c16e643cb4658d1080c7e336",
"alias": "nick"
},
{
"source": false,
"pubkey": "02f5f6bb6373fc60528118003f803557b916fbecd90c3a0c5df4c86c6a6e962fd1",
"alias": "ophelia"
},
{
"source": false,
"pubkey": "02fd7a5f04d550cf0ba8af6053a20e0080d956f41b1221357a35fab3a363e5f78e",
"alias": "patrick"
},
{
"source": false,
"pubkey": "030da942ed7cfc7d3096811b3264e15115778e692eaacb2b7a76fb27a58cbb5359",
"alias": "quinn"
},
{
"source": false,
"pubkey": "0319d6b038e26ac89802e856d7e78f293e9d109c414614f98e3fa5c626f20934be",
"alias": "rick"
},
{
"source": false,
"pubkey": "03384439e78e87d168fecabe8d88218dfd5983c5e14fd8fa6dc89caeb3cc0fb171",
"alias": "sarah"
},
{
"source": false,
"pubkey": "0362002b8fbc1a799c839c8bcea43fce38a147467a00bc450414bbeab5c7a19efe",
"alias": "tim"
},
{
"source": false,
"pubkey": "0369bca64993fce966745d32c09b882f668958d9bd7aabb60ba35ef1884013be1d",
"alias": "ursula"
},
{
"source": false,
"pubkey": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"alias": "vincent"
}
],
"edges": [
{
"node_1": "021b96642e723592ee0b095983fe3a26c8b40b8926968d8b7510e51c9429d4562c",
"node_2": "022096b2b0ac083e708074a5ab57288bc821b6bef7b964185b307e073772c3748f",
"channel_id": 12345,
"channel_point": "99dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "022096b2b0ac083e708074a5ab57288bc821b6bef7b964185b307e073772c3748f",
"node_2": "022a190ce901ab2b6f349483f18b28a1d72c64a7bccb8057291f25784c0899840f",
"channel_id": 12346,
"channel_point": "79dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "022a190ce901ab2b6f349483f18b28a1d72c64a7bccb8057291f25784c0899840f",
"node_2": "022d855d09971dd047b7ecf929b23c6f147b568d4668af67fb2226eb8c15c4660d",
"channel_id": 12347,
"channel_point": "69dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "022d855d09971dd047b7ecf929b23c6f147b568d4668af67fb2226eb8c15c4660d",
"node_2": "024ca436834b0d38d9dc7ee4d95aa21db321c45598dc5921a4a52304a8e0dd2952",
"channel_id": 12348,
"channel_point": "59dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "024ca436834b0d38d9dc7ee4d95aa21db321c45598dc5921a4a52304a8e0dd2952",
"node_2": "025234a0c44cbf1b20c18e2c397107ad731376831e1c43ddb360b41dbb98c10266",
"channel_id": 12349,
"channel_point": "49dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "025234a0c44cbf1b20c18e2c397107ad731376831e1c43ddb360b41dbb98c10266",
"node_2": "0253e9d03030f2ff08d3a7f1d824ad6d8c0dae422f324e72d5bb313e3f2a2d45a8",
"channel_id": 12340,
"channel_point": "39dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0253e9d03030f2ff08d3a7f1d824ad6d8c0dae422f324e72d5bb313e3f2a2d45a8",
"node_2": "0263d4f2baca258ff3bd5bce86c2754e95daaea27f268ae1a048c1253ff20de56e",
"channel_id": 12344,
"channel_point": "29dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0263d4f2baca258ff3bd5bce86c2754e95daaea27f268ae1a048c1253ff20de56e",
"node_2": "02650db8e44302f75e265e9427264bc0d7e2337831d6b9ceb7c58ed1e725d4576a",
"channel_id": 12343,
"channel_point": "19dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02650db8e44302f75e265e9427264bc0d7e2337831d6b9ceb7c58ed1e725d4576a",
"node_2": "02727bfd298aa055a6419404931dfc1ccb4f0eb7c9660a7df346b93d0025df3ba1",
"channel_id": 12342,
"channel_point": "88dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02727bfd298aa055a6419404931dfc1ccb4f0eb7c9660a7df346b93d0025df3ba1",
"node_2": "0280c83b3eded413dcec12f7952410e2738f079bd9cbc9a7c462e32ed4d74bd5b7",
"channel_id": 12341,
"channel_point": "87dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0280c83b3eded413dcec12f7952410e2738f079bd9cbc9a7c462e32ed4d74bd5b7",
"node_2": "0290bf454f4b95baf9227801301b331e35d477c6b6e7f36a599983ae58747b3828",
"channel_id": 12355,
"channel_point": "86dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0290bf454f4b95baf9227801301b331e35d477c6b6e7f36a599983ae58747b3828",
"node_2": "0297c8de635d17e3dd5775edfa2797be0874c53b0026f69009787cecd2fa577de8",
"channel_id": 12365,
"channel_point": "85dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0297c8de635d17e3dd5775edfa2797be0874c53b0026f69009787cecd2fa577de8",
"node_2": "02a27227113c71eab0c8609ac0cdc7e76791fc3163c16e643cb4658d1080c7e336",
"channel_id": 12375,
"channel_point": "84dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02a27227113c71eab0c8609ac0cdc7e76791fc3163c16e643cb4658d1080c7e336",
"node_2": "02f5f6bb6373fc60528118003f803557b916fbecd90c3a0c5df4c86c6a6e962fd1",
"channel_id": 12385,
"channel_point": "83dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02f5f6bb6373fc60528118003f803557b916fbecd90c3a0c5df4c86c6a6e962fd1",
"node_2": "02fd7a5f04d550cf0ba8af6053a20e0080d956f41b1221357a35fab3a363e5f78e",
"channel_id": 12395,
"channel_point": "82dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "02fd7a5f04d550cf0ba8af6053a20e0080d956f41b1221357a35fab3a363e5f78e",
"node_2": "030da942ed7cfc7d3096811b3264e15115778e692eaacb2b7a76fb27a58cbb5359",
"channel_id": 12305,
"channel_point": "81dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "030da942ed7cfc7d3096811b3264e15115778e692eaacb2b7a76fb27a58cbb5359",
"node_2": "0319d6b038e26ac89802e856d7e78f293e9d109c414614f98e3fa5c626f20934be",
"channel_id": 12335,
"channel_point": "80dc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0319d6b038e26ac89802e856d7e78f293e9d109c414614f98e3fa5c626f20934be",
"node_2": "03384439e78e87d168fecabe8d88218dfd5983c5e14fd8fa6dc89caeb3cc0fb171",
"channel_id": 12325,
"channel_point": "89ec56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "03384439e78e87d168fecabe8d88218dfd5983c5e14fd8fa6dc89caeb3cc0fb171",
"node_2": "0362002b8fbc1a799c839c8bcea43fce38a147467a00bc450414bbeab5c7a19efe",
"channel_id": 12315,
"channel_point": "89fc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0362002b8fbc1a799c839c8bcea43fce38a147467a00bc450414bbeab5c7a19efe",
"node_2": "0369bca64993fce966745d32c09b882f668958d9bd7aabb60ba35ef1884013be1d",
"channel_id": 12445,
"channel_point": "89cc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
},
{
"node_1": "0369bca64993fce966745d32c09b882f668958d9bd7aabb60ba35ef1884013be1d",
"node_2": "0367cec75158a4129177bfb8b269cb586efe93d751b43800d456485e81c2620ca6",
"channel_id": 12545,
"channel_point": "89bc56859c6a082d15ba1a7f6cb6be3fea62e1746e2cb8497b1189155c21a233:0",
"channel_flags": 0,
"message_flags": 1,
"expiry": 1,
"min_htlc": 1,
"max_htlc": 100000000,
"fee_base_msat": 10,
"fee_rate": 0.001,
"capacity": 100000
}
]
}

@ -4231,11 +4231,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
// First, parse the hex-encoded public key into a full in-memory public // First, parse the hex-encoded public key into a full in-memory public
// key object we can work with for querying. // key object we can work with for querying.
pubKeyBytes, err := hex.DecodeString(in.PubKey) pubKey, err := route.NewVertexFromStr(in.PubKey)
if err != nil {
return nil, err
}
pubKey, err := btcec.ParsePubKey(pubKeyBytes, btcec.S256())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -4243,7 +4239,7 @@ func (r *rpcServer) GetNodeInfo(ctx context.Context,
// With the public key decoded, attempt to fetch the node corresponding // With the public key decoded, attempt to fetch the node corresponding
// to this public key. If the node cannot be found, then an error will // to this public key. If the node cannot be found, then an error will
// be returned. // be returned.
node, err := graph.FetchLightningNode(pubKey) node, err := graph.FetchLightningNode(nil, pubKey)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -3369,7 +3369,12 @@ func computeNextBackoff(currBackoff time.Duration) time.Duration {
// fetchNodeAdvertisedAddr attempts to fetch an advertised address of a node. // fetchNodeAdvertisedAddr attempts to fetch an advertised address of a node.
func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) { func (s *server) fetchNodeAdvertisedAddr(pub *btcec.PublicKey) (net.Addr, error) {
node, err := s.chanDB.ChannelGraph().FetchLightningNode(pub) vertex, err := route.NewVertexFromBytes(pub.SerializeCompressed())
if err != nil {
return nil, err
}
node, err := s.chanDB.ChannelGraph().FetchLightningNode(nil, vertex)
if err != nil { if err != nil {
return nil, err return nil, err
} }

@ -4,6 +4,8 @@ import (
"encoding/binary" "encoding/binary"
"errors" "errors"
"io" "io"
"github.com/btcsuite/btcd/wire"
) )
// ErrVarIntNotCanonical signals that the decoded varint was not minimally encoded. // ErrVarIntNotCanonical signals that the decoded varint was not minimally encoded.
@ -107,3 +109,8 @@ func WriteVarInt(w io.Writer, val uint64, buf *[8]byte) error {
_, err := w.Write(buf[:length]) _, err := w.Write(buf[:length])
return err return err
} }
// VarIntSize returns the required number of bytes to encode a var int.
func VarIntSize(val uint64) uint64 {
return uint64(wire.VarIntSerializeSize(val))
}