From 990d55d08c0204a43f983ca05ffcd26b81db7b7f Mon Sep 17 00:00:00 2001 From: Conner Fromknecht Date: Wed, 18 Dec 2019 23:54:31 -0800 Subject: [PATCH] routing/pathfind: ensure final hop supports payment addrs This commit adds an optional PaymentAddr field to the RestrictParams, so that we can verify the final hop can support it before doing an expensive round of pathfindig. --- routing/pathfind.go | 18 ++++++++++ routing/pathfind_test.go | 65 ++++++++++++++++++++++++++++++++++++ routing/payment_lifecycle.go | 6 +++- 3 files changed, 88 insertions(+), 1 deletion(-) diff --git a/routing/pathfind.go b/routing/pathfind.go index 90c4e5761..428caf42a 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -70,6 +70,11 @@ var ( errNoTlvPayload = errors.New("destination hop doesn't " + "understand new TLV payloads") + // errNoPaymentAddr is returned when the destination hop does not + // support payment addresses. + errNoPaymentAddr = errors.New("destination hop doesn't " + + "understand payment addresses") + // errNoPathFound is returned when a path to the target destination does // not exist in the graph. errNoPathFound = errors.New("unable to find a path to destination") @@ -298,6 +303,11 @@ type RestrictParams struct { // supports. If none are provided, pathfinding will try to inspect any // features on the node announcement instead. DestFeatures *lnwire.FeatureVector + + // PaymentAddr is a random 32-byte value generated by the receiver to + // mitigate probing vectors and payment sniping attacks on overpaid + // invoices. + PaymentAddr *[32]byte } // PathFindingConfig defines global parameters that control the trade-off in @@ -447,6 +457,14 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig, return nil, errNoTlvPayload } + // If the caller has a payment address to attach, check that our + // destination feature vector supports them. + if r.PaymentAddr != nil && + !features.HasFeature(lnwire.PaymentAddrOptional) { + + return nil, errNoPaymentAddr + } + // If we are routing from ourselves, check that we have enough local // balance available. if source == self { diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index 3647d9cbc..bcd40cf0e 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -1532,6 +1532,71 @@ func TestMissingFeatureDep(t *testing.T) { assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "conner") } +// TestDestPaymentAddr asserts that we properly detect when we can send a +// payment address to a receiver, and also that we fallback to the receiver's +// node announcement if we don't have an invoice features. +func TestDestPaymentAddr(t *testing.T) { + t.Parallel() + + testChannels := []*testChannel{ + symmetricTestChannel("roasbeef", "luoji", 100000, + &testChannelPolicy{ + Expiry: 144, + FeeRate: 400, + MinHTLC: 1, + MaxHTLC: 100000000, + }, + ), + } + + ctx := newPathFindingTestContext(t, testChannels, "roasbeef") + defer ctx.cleanup() + + sourceNode, err := ctx.graphParams.graph.SourceNode() + if err != nil { + t.Fatalf("unable to fetch source node: %v", err) + + } + + find := func(r *RestrictParams, + target route.Vertex) ([]*channeldb.ChannelEdgePolicy, error) { + + return findPath( + &graphParams{ + graph: ctx.graphParams.graph, + }, + r, testPathFindingConfig, + sourceNode.PubKeyBytes, target, 100, + ) + } + + luoji := ctx.testGraphInstance.aliasMap["luoji"] + + restrictions := *noRestrictions + + // Add payment address w/o any invoice features. + restrictions.PaymentAddr = &[32]byte{1} + + // Add empty destination features. This should cause us to fail, since + // this overrides anything in the graph. + restrictions.DestFeatures = lnwire.EmptyFeatureVector() + + _, err = find(&restrictions, luoji) + if err != errNoPaymentAddr { + t.Fatalf("path shouldn't have been found: %v", err) + } + + // Now, set the TLV and payment address features for the destination. We + // should succeed in finding a path to luoji. + restrictions.DestFeatures = tlvPayAddrFeatures + + path, err := find(&restrictions, luoji) + if err != nil { + t.Fatalf("path should have been found: %v", err) + } + assertExpectedPath(t, ctx.testGraphInstance.aliasMap, path, "luoji") +} + func TestPathInsufficientCapacity(t *testing.T) { t.Parallel() diff --git a/routing/payment_lifecycle.go b/routing/payment_lifecycle.go index 093ee8bc9..b5203671c 100644 --- a/routing/payment_lifecycle.go +++ b/routing/payment_lifecycle.go @@ -192,7 +192,11 @@ func (p *paymentLifecycle) resumePayment() ([32]byte, *route.Route, error) { // payment-level failure. func errorToPaymentFailure(err error) channeldb.FailureReason { switch err { - case errNoTlvPayload, errNoPathFound, errMaxHopsExceeded, + case + errNoTlvPayload, + errNoPaymentAddr, + errNoPathFound, + errMaxHopsExceeded, errPrebuiltRouteTried: return channeldb.FailureReasonNoRoute