diff --git a/routing/missioncontrol.go b/routing/missioncontrol.go index 510db4879..4864cab84 100644 --- a/routing/missioncontrol.go +++ b/routing/missioncontrol.go @@ -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 diff --git a/routing/pathfind.go b/routing/pathfind.go index 6a0a94777..0aa4f8000 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -1,7 +1,6 @@ package routing import ( - "bytes" "encoding/binary" "fmt" "math" @@ -429,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 @@ -491,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, @@ -534,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 @@ -597,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 } @@ -666,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 @@ -677,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 } @@ -744,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") } @@ -752,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] @@ -789,9 +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, restrictions *RestrictParams, numPaths uint32, - bandwidthHints map[uint64]lnwire.MilliSatoshi) ([][]*channeldb.ChannelEdgePolicy, error) { + 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? @@ -821,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...) @@ -908,7 +904,8 @@ func findPaths(tx *bbolt.Tx, graph *channeldb.ChannelGraph, graph: graph, bandwidthHints: bandwidthHints, }, - spurRestrictions, spurNode, target, amt, + spurRestrictions, spurNode.PubKeyBytes, + target, amt, ) // If we weren't able to find a path, we'll continue to diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 70dd62f91..3c7403baf 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -162,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. @@ -189,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 @@ -365,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. @@ -394,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) @@ -429,7 +424,7 @@ func createTestGraphFromChannels(testChannels []*testChannel) (*testGraphInstanc return nil, err } - aliasMap[alias] = pubKey + aliasMap[alias] = dbNode.PubKeyBytes nodeIndex++ return dbNode, nil @@ -482,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 @@ -618,7 +610,7 @@ func TestFindLowestFeePath(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, }, - sourceNode, target, paymentAmt, + sourceNode.PubKeyBytes, target, paymentAmt, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -631,20 +623,19 @@ func TestFindLowestFeePath(t *testing.T) { } // 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 } } @@ -759,7 +750,7 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc &RestrictParams{ FeeLimit: test.feeLimit, }, - sourceNode, target, paymentAmt, + sourceNode.PubKeyBytes, target, paymentAmt, ) if test.expectFailureNoPath { if err == nil { @@ -786,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)) } } @@ -900,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. @@ -912,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. @@ -924,7 +916,7 @@ func TestPathFindingWithAdditionalEdges(t *testing.T) { &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) @@ -932,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,8 +955,8 @@ func TestKShortestPathFinding(t *testing.T) { FeeLimit: noFeeLimit, } paths, err := findPaths( - nil, graph.graph, sourceNode, target, paymentAmt, restrictions, - 100, nil, + nil, graph.graph, sourceNode.PubKeyBytes, target, paymentAmt, + restrictions, 100, nil, ) if err != nil { t.Fatalf("unable to find paths between roasbeef and "+ @@ -984,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 @@ -1235,7 +1229,7 @@ func TestNewRoutePathTooLong(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, }, - sourceNode, target, paymentAmt, + sourceNode.PubKeyBytes, target, paymentAmt, ) if err != nil { t.Fatalf("path should have been found") @@ -1251,7 +1245,7 @@ func TestNewRoutePathTooLong(t *testing.T) { &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 "+ @@ -1283,10 +1277,8 @@ 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{ @@ -1295,7 +1287,7 @@ func TestPathNotAvailable(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, }, - sourceNode, unknownNode, 100, + sourceNode.PubKeyBytes, unknownNode, 100, ) if !IsError(err, ErrNoPathFound) { t.Fatalf("path shouldn't have been found: %v", err) @@ -1334,7 +1326,7 @@ func TestPathInsufficientCapacity(t *testing.T) { &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) @@ -1369,7 +1361,7 @@ func TestRouteFailMinHTLC(t *testing.T) { &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) @@ -1429,7 +1421,7 @@ func TestRouteFailMaxHTLC(t *testing.T) { &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) @@ -1453,7 +1445,7 @@ func TestRouteFailMaxHTLC(t *testing.T) { &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) @@ -1490,7 +1482,7 @@ func TestRouteFailDisabledEdge(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, }, - sourceNode, target, payAmt, + sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -1520,7 +1512,7 @@ func TestRouteFailDisabledEdge(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, }, - sourceNode, target, payAmt, + sourceNode.PubKeyBytes, target, payAmt, ) if err != nil { t.Fatalf("unable to find path: %v", err) @@ -1547,7 +1539,7 @@ func TestRouteFailDisabledEdge(t *testing.T) { &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) @@ -1583,12 +1575,12 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { &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. @@ -1609,7 +1601,7 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { &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) @@ -1629,12 +1621,12 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { &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. @@ -1662,12 +1654,12 @@ func TestPathSourceEdgesBandwidth(t *testing.T) { &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) { @@ -1704,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) } @@ -1715,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, noRestrictions, 100) + routes, err := ctx.router.FindRoutes( + carol, amt, noRestrictions, 100, + ) if err != nil { t.Fatalf("unable to find route: %v", err) } @@ -1758,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) } @@ -1770,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, noRestrictions, 100) + routes, err = ctx.router.FindRoutes( + carol, amt, noRestrictions, 100, + ) if err != nil { t.Fatalf("unable to find routes: %v", err) } @@ -1903,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) } @@ -1996,7 +2000,7 @@ func TestRestrictOutgoingChannel(t *testing.T) { FeeLimit: noFeeLimit, OutgoingChannelID: &outgoingChannelID, }, - sourceNode, target, paymentAmt, + sourceVertex, target, paymentAmt, ) if err != nil { t.Fatalf("unable to find path: %v", err) diff --git a/routing/payment_session.go b/routing/payment_session.go index f29a2b451..20a5808aa 100644 --- a/routing/payment_session.go +++ b/routing/payment_session.go @@ -153,7 +153,8 @@ func (p *paymentSession) RequestRoute(payment *LightningPayment, 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 diff --git a/routing/router.go b/routing/router.go index 5d126905b..d2bacc24c 100644 --- a/routing/router.go +++ b/routing/router.go @@ -1325,9 +1325,9 @@ 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 lnwire.MilliSatoshi, restrictions *RestrictParams, numPaths uint32, - finalExpiry ...uint16) ([]*Route, error) { +func (r *ChannelRouter) FindRoutes(target Vertex, amt lnwire.MilliSatoshi, + restrictions *RestrictParams, numPaths uint32, finalExpiry ...uint16) ( + []*Route, error) { var finalCLTVDelta uint16 if len(finalExpiry) == 0 { @@ -1336,13 +1336,12 @@ 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) + rt := newRouteTuple(amt, target[:]) r.routeCacheMtx.RLock() routes, ok := r.routeCache[rt] r.routeCacheMtx.RUnlock() @@ -1361,11 +1360,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") } @@ -1396,8 +1394,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, restrictions, - numPaths, bandwidthHints, + tx, r.cfg.Graph, r.selfNode.PubKeyBytes, target, amt, + restrictions, numPaths, bandwidthHints, ) if err != nil { tx.Rollback() @@ -1421,7 +1419,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) }), ) @@ -1513,7 +1511,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. @@ -1608,12 +1606,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 diff --git a/routing/router_test.go b/routing/router_test.go index f85689767..15d9128ad 100644 --- a/routing/router_test.go +++ b/routing/router_test.go @@ -30,7 +30,7 @@ type testCtx struct { graph *channeldb.ChannelGraph - aliases map[string]*btcec.PublicKey + aliases map[string]Vertex chain *mockChain @@ -265,11 +265,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)) } } @@ -347,12 +346,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)) } } @@ -410,11 +408,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{ { @@ -429,7 +425,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) @@ -451,8 +447,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, }, @@ -576,8 +580,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 @@ -611,12 +622,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)) } } @@ -684,8 +694,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, }, @@ -712,12 +729,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)) } } @@ -739,8 +755,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, }, @@ -823,8 +846,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{}, } } @@ -882,12 +913,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)) } @@ -930,12 +960,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)) } } @@ -1233,8 +1262,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 ( @@ -1242,12 +1272,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() } @@ -1267,9 +1297,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) @@ -1308,8 +1338,10 @@ 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, noRestrictions, defaultNumRoutes, + targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes, DefaultFinalCLTVDelta, ) if err != nil { @@ -1354,7 +1386,7 @@ 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, noRestrictions, defaultNumRoutes, + targetPubKeyBytes, paymentAmt, noRestrictions, defaultNumRoutes, DefaultFinalCLTVDelta, ) if err != nil { @@ -1954,9 +1986,6 @@ func TestFindPathFeeWeighting(t *testing.T) { 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 @@ -1968,7 +1997,7 @@ func TestFindPathFeeWeighting(t *testing.T) { &RestrictParams{ FeeLimit: noFeeLimit, }, - sourceNode, target, amt, + sourceNode.PubKeyBytes, target, amt, ) if err != nil { t.Fatalf("unable to find path: %v", err) diff --git a/rpcserver.go b/rpcserver.go index 5eb14acc7..c73bca3eb 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -2764,7 +2764,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 +2778,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 +2848,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 +2859,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 @@ -4001,17 +3997,19 @@ 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 + + if len(pubKeyBytes) != 33 { + return nil, errors.New("invalid key length") } + var pubKey routing.Vertex + copy(pubKey[:], pubKeyBytes) + // 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.