diff --git a/routing/pathfind.go b/routing/pathfind.go index 90c4e576..428caf42 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 3647d9cb..bcd40cf0 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 093ee8bc..b5203671 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