Repository: lightningnetwork/lnd
Pull request #9134 (2024-09-24 03:37:29)

routerrpc: check payaddr before using for probing
As in other places where we extract and use the payment address from an invoice, we first check that it is set before dereferencing it.



coderabbitai[bot]: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are limited to specific labels.
> 
> <details>
> <summary>Labels to auto review (1)</summary>
> 
> * llm-review
> 
> </details>
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai --><!-- tips_start -->

---

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

<details>
<summary>Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
-- `I pushed a fix in commit <commit_id>, please review it.`
-- `Generate unit testing code for this file.`
	- `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
-- `@coderabbitai generate unit testing code for this file.`
--	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
-- `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
-- `@coderabbitai read src/utils.ts and generate unit testing code.`
-- `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
-- `@coderabbitai help me debug CodeRabbit configuration file.`

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://coderabbit.ai/docs) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](https://discord.com/invite/GsXnASn26c) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

Patch:
diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md
index 8d3a908fd..47562b074 100644
--- a/docs/release-notes/release-notes-0.19.0.md
+++ b/docs/release-notes/release-notes-0.19.0.md
@@ -23,6 +23,10 @@
   propagate mission control and debug level config values to the main LND config
   struct so that the GetDebugInfo response is accurate.
 
+* [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9134) that would 
+  cause a nil pointer dereference during the probing of a payment request that 
+  does not contain a payment address.
+
 # New Features
 ## Functional Enhancements
 ## RPC Additions
@@ -69,5 +73,6 @@
 # Contributors (Alphabetical Order)
 
 * CharlieZKSmith
+* Elle Mouton
 * Pins
 * Ziggie
diff --git a/feature/default_sets.go b/feature/default_sets.go
index 447870b27..cc802fe85 100644
--- a/feature/default_sets.go
+++ b/feature/default_sets.go
@@ -92,7 +92,4 @@ var defaultSetDesc = setDesc{
 		SetInit:    {}, // I
 		SetNodeAnn: {}, // N
 	},
-	lnwire.Bolt11BlindedPathsOptional: {
-		SetInvoice: {}, // I
-	},
 }
diff --git a/invoices/modification_interceptor.go b/invoices/modification_interceptor.go
index e2d9a9051..47f0de80b 100644
--- a/invoices/modification_interceptor.go
+++ b/invoices/modification_interceptor.go
@@ -66,6 +66,9 @@ func (s *safeCallback) Exec(req HtlcModifyRequest) (*HtlcModifyResponse,
 // settle an invoice, enabling a subscribed client to modify certain aspects of
 // those HTLCs.
 type HtlcModificationInterceptor struct {
+	started atomic.Bool
+	stopped atomic.Bool
+
 	// callback is the wrapped client callback function that is called when
 	// an invoice is intercepted. This function gives the client the ability
 	// to determine how the invoice should be settled.
@@ -79,6 +82,7 @@ type HtlcModificationInterceptor struct {
 func NewHtlcModificationInterceptor() *HtlcModificationInterceptor {
 	return &HtlcModificationInterceptor{
 		callback: &safeCallback{},
+		quit:     make(chan struct{}),
 	}
 }
 
@@ -163,11 +167,19 @@ func (s *HtlcModificationInterceptor) RegisterInterceptor(
 
 // Start starts the service.
 func (s *HtlcModificationInterceptor) Start() error {
+	if !s.started.CompareAndSwap(false, true) {
+		return nil
+	}
+
 	return nil
 }
 
 // Stop stops the service.
 func (s *HtlcModificationInterceptor) Stop() error {
+	if !s.stopped.CompareAndSwap(false, true) {
+		return nil
+	}
+
 	close(s.quit)
 
 	return nil
diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go
index e58ed4d27..f2d21750a 100644
--- a/lnrpc/routerrpc/router_backend.go
+++ b/lnrpc/routerrpc/router_backend.go
@@ -16,6 +16,7 @@ import (
 	sphinx "github.com/lightningnetwork/lightning-onion"
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/feature"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/htlcswitch"
 	"github.com/lightningnetwork/lnd/lnrpc"
 	"github.com/lightningnetwork/lnd/lntypes"
@@ -1011,7 +1012,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
 			if len(rpcPayReq.PaymentAddr) > 0 {
 				var addr [32]byte
 				copy(addr[:], rpcPayReq.PaymentAddr)
-				payAddr = &addr
+				payAddr = fn.Some(addr)
 			}
 		} else {
 			err = payIntent.SetPaymentHash(*payReq.PaymentHash)
@@ -1128,7 +1129,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
 			} else {
 				copy(payAddr[:], rpcPayReq.PaymentAddr)
 			}
-			payIntent.PaymentAddr = &payAddr
+			payIntent.PaymentAddr = fn.Some(payAddr)
 
 			// Generate random SetID and root share.
 			var setID [32]byte
@@ -1167,7 +1168,7 @@ func (r *RouterBackend) extractIntentFromSendRequest(
 				var payAddr [32]byte
 				copy(payAddr[:], rpcPayReq.PaymentAddr)
 
-				payIntent.PaymentAddr = &payAddr
+				payIntent.PaymentAddr = fn.Some(payAddr)
 			}
 		}
 
diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go
index 8091998a9..a4112ba64 100644
--- a/lnrpc/routerrpc/router_server.go
+++ b/lnrpc/routerrpc/router_server.go
@@ -528,11 +528,16 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
 		AmtMsat:          amtMsat,
 		PaymentHash:      paymentHash[:],
 		FeeLimitSat:      routeFeeLimitSat,
-		PaymentAddr:      payReq.PaymentAddr[:],
 		FinalCltvDelta:   int32(payReq.MinFinalCLTVExpiry()),
 		DestFeatures:     MarshalFeatures(payReq.Features),
 	}
 
+	// If the payment addresses is specified, then we'll also populate that
+	// now as well.
+	payReq.PaymentAddr.WhenSome(func(addr [32]byte) {
+		copy(probeRequest.PaymentAddr, addr[:])
+	})
+
 	hints := payReq.RouteHints
 
 	// If the hints don't indicate an LSP then chances are that our probe
@@ -1453,12 +1458,12 @@ func (s *Server) BuildRoute(_ context.Context,
 		outgoingChan = &req.OutgoingChanId
 	}
 
-	var payAddr *[32]byte
+	var payAddr fn.Option[[32]byte]
 	if len(req.PaymentAddr) != 0 {
 		var backingPayAddr [32]byte
 		copy(backingPayAddr[:], req.PaymentAddr)
 
-		payAddr = &backingPayAddr
+		payAddr = fn.Some(backingPayAddr)
 	}
 
 	if req.FinalCltvDelta == 0 {
diff --git a/routing/pathfind.go b/routing/pathfind.go
index 6f394ea58..43eae7103 100644
--- a/routing/pathfind.go
+++ b/routing/pathfind.go
@@ -111,7 +111,7 @@ type finalHopParams struct {
 	cltvDelta uint16
 
 	records     record.CustomSet
-	paymentAddr *[32]byte
+	paymentAddr fn.Option[[32]byte]
 
 	// metadata is additional data that is sent along with the payment to
 	// the payee.
@@ -226,7 +226,7 @@ func newRoute(sourceVertex route.Vertex,
 			// If we're attaching a payment addr but the receiver
 			// doesn't support both TLV and payment addrs, fail.
 			payAddr := supports(lnwire.PaymentAddrOptional)
-			if !payAddr && finalHop.paymentAddr != nil {
+			if !payAddr && finalHop.paymentAddr.IsSome() {
 				return nil, errors.New("cannot attach " +
 					"payment addr")
 			}
@@ -234,12 +234,9 @@ func newRoute(sourceVertex route.Vertex,
 			// Otherwise attach the mpp record if it exists.
 			// TODO(halseth): move this to payment life cycle,
 			// where AMP options are set.
-			if finalHop.paymentAddr != nil {
-				mpp = record.NewMPP(
-					finalHop.totalAmt,
-					*finalHop.paymentAddr,
-				)
-			}
+			finalHop.paymentAddr.WhenSome(func(addr [32]byte) {
+				mpp = record.NewMPP(finalHop.totalAmt, addr)
+			})
 
 			metadata = finalHop.metadata
 
@@ -452,7 +449,7 @@ type RestrictParams struct {
 	// 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
+	PaymentAddr fn.Option[[32]byte]
 
 	// Amp signals to the pathfinder that this payment is an AMP payment
 	// and therefore it needs to account for additional AMP data in the
@@ -608,7 +605,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
 	// checking that it supports the features we need. If the caller has a
 	// payment address to attach, check that our destination feature vector
 	// supports them.
-	if r.PaymentAddr != nil &&
+	if r.PaymentAddr.IsSome() &&
 		!features.HasFeature(lnwire.PaymentAddrOptional) {
 
 		return nil, 0, errNoPaymentAddr
@@ -1435,9 +1432,9 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
 	}
 
 	var mpp *record.MPP
-	if r.PaymentAddr != nil {
-		mpp = record.NewMPP(amount, *r.PaymentAddr)
-	}
+	r.PaymentAddr.WhenSome(func(addr [32]byte) {
+		mpp = record.NewMPP(amount, addr)
+	})
 
 	var amp *record.AMP
 	if r.Amp != nil {
diff --git a/routing/payment_session.go b/routing/payment_session.go
index b47f6ee0c..9ddb62600 100644
--- a/routing/payment_session.go
+++ b/routing/payment_session.go
@@ -344,7 +344,7 @@ func (p *paymentSession) RequestRoute(maxAmt, feeLimit lnwire.MilliSatoshi,
 			// record. If it has a blinded path though, then we
 			// can split. Split payments to blinded paths won't have
 			// MPP records.
-			if p.payment.PaymentAddr == nil &&
+			if p.payment.PaymentAddr.IsNone() &&
 				p.payment.BlindedPathSet == nil {
 
 				p.log.Debugf("not splitting because payment " +
diff --git a/routing/router.go b/routing/router.go
index 40b7ecb49..3b340f35a 100644
--- a/routing/router.go
+++ b/routing/router.go
@@ -862,7 +862,7 @@ type LightningPayment struct {
 	// PaymentAddr is the payment address specified by the receiver. This
 	// field should be a random 32-byte nonce presented in the receiver's
 	// invoice to prevent probing of the destination.
-	PaymentAddr *[32]byte
+	PaymentAddr fn.Option[[32]byte]
 
 	// PaymentRequest is an optional payment request that this payment is
 	// attempting to complete.
@@ -1063,9 +1063,10 @@ func (r *ChannelRouter) PreparePayment(payment *LightningPayment) (
 	switch {
 	// If this is an AMP payment, we'll use the AMP shard tracker.
 	case payment.amp != nil:
+		addr := payment.PaymentAddr.UnwrapOr([32]byte{})
 		shardTracker = amp.NewShardTracker(
-			payment.amp.RootShare, payment.amp.SetID,
-			*payment.PaymentAddr, payment.Amount,
+			payment.amp.RootShare, payment.amp.SetID, addr,
+			payment.Amount,
 		)
 
 	// Otherwise we'll use the simple tracker that will map each attempt to
@@ -1367,8 +1368,8 @@ func (e ErrNoChannel) Error() string {
 // outgoing channel, use the outgoingChan parameter.
 func (r *ChannelRouter) BuildRoute(amt fn.Option[lnwire.MilliSatoshi],
 	hops []route.Vertex, outgoingChan *uint64, finalCltvDelta int32,
-	payAddr *[32]byte, firstHopBlob fn.Option[[]byte]) (*route.Route,
-	error) {
+	payAddr fn.Option[[32]byte], firstHopBlob fn.Option[[]byte]) (
+	*route.Route, error) {
 
 	log.Tracef("BuildRoute called: hopsCount=%v, amt=%v", len(hops), amt)
 
diff --git a/rpcserver.go b/rpcserver.go
index 5a1569a28..44a3ae391 100644
--- a/rpcserver.go
+++ b/rpcserver.go
@@ -5321,7 +5321,7 @@ type rpcPaymentIntent struct {
 	outgoingChannelIDs []uint64
 	lastHop            *route.Vertex
 	destFeatures       *lnwire.FeatureVector
-	paymentAddr        *[32]byte
+	paymentAddr        fn.Option[[32]byte]
 	payReq             []byte
 	metadata           []byte
 	blindedPathSet     *routing.BlindedPaymentPathSet
@@ -5530,8 +5530,9 @@ func (r *rpcServer) extractPaymentIntent(rpcPayReq *rpcPaymentRequest) (rpcPayme
 	// Note that the payment address for the payIntent should be nil if none
 	// was provided with the rpcPaymentRequest.
 	if len(rpcPayReq.PaymentAddr) != 0 {
-		payIntent.paymentAddr = &[32]byte{}
-		copy(payIntent.paymentAddr[:], rpcPayReq.PaymentAddr)
+		var addr [32]byte
+		copy(addr[:], rpcPayReq.PaymentAddr)
+		payIntent.paymentAddr = fn.Some(addr)
 	}
 
 	// Otherwise, If the payment request field was not specified
@@ -7380,10 +7381,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
 	}
 
 	// Extract the payment address from the payment request, if present.
-	var paymentAddr []byte
-	if payReq.PaymentAddr != nil {
-		paymentAddr = payReq.PaymentAddr[:]
-	}
+	paymentAddr := payReq.PaymentAddr.UnwrapOr([32]byte{})
 
 	dest := payReq.Destination.SerializeCompressed()
 	return &lnrpc.PayReq{
@@ -7399,7 +7397,7 @@ func (r *rpcServer) DecodePayReq(ctx context.Context,
 		CltvExpiry:      int64(payReq.MinFinalCLTVExpiry()),
 		RouteHints:      routeHints,
 		BlindedPaths:    blindedPaymentPaths,
-		PaymentAddr:     paymentAddr,
+		PaymentAddr:     paymentAddr[:],
 		Features:        invoicesrpc.CreateRPCFeatures(payReq.Features),
 	}, nil
 }
diff --git a/server.go b/server.go
index 4097233e0..9a7276948 100644
--- a/server.go
+++ b/server.go
@@ -2411,6 +2411,14 @@ func (s *server) Stop() error {
 		if err := s.invoices.Stop(); err != nil {
 			srvrLog.Warnf("failed to stop invoices: %v", err)
 		}
+		if err := s.interceptableSwitch.Stop(); err != nil {
+			srvrLog.Warnf("failed to stop interceptable "+
+				"switch: %v", err)
+		}
+		if err := s.invoiceHtlcModifier.Stop(); err != nil {
+			srvrLog.Warnf("failed to stop htlc invoices "+
+				"modifier: %v", err)
+		}
 		if err := s.chanRouter.Stop(); err != nil {
 			srvrLog.Warnf("failed to stop chanRouter: %v", err)
 		}
diff --git a/zpay32/decode.go b/zpay32/decode.go
index fcfc7edfe..61099cf2f 100644
--- a/zpay32/decode.go
+++ b/zpay32/decode.go
@@ -14,6 +14,7 @@ import (
 	"github.com/btcsuite/btcd/btcutil/bech32"
 	"github.com/btcsuite/btcd/chaincfg"
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/lnwire"
 )
 
@@ -278,13 +279,19 @@ func parseTaggedFields(invoice *Invoice, fields []byte, net *chaincfg.Params) er
 			invoice.PaymentHash, err = parse32Bytes(base32Data)
 
 		case fieldTypeS:
-			if invoice.PaymentAddr != nil {
+			if invoice.PaymentAddr.IsSome() {
 				// We skip the field if we have already seen a
 				// supported one.
 				continue
 			}
 
-			invoice.PaymentAddr, err = parse32Bytes(base32Data)
+			addr, err := parse32Bytes(base32Data)
+			if err != nil {
+				return err
+			}
+			if addr != nil {
+				invoice.PaymentAddr = fn.Some(*addr)
+			}
 
 		case fieldTypeD:
 			if invoice.Description != nil {
diff --git a/zpay32/encode.go b/zpay32/encode.go
index 130abdcfd..3e2d79977 100644
--- a/zpay32/encode.go
+++ b/zpay32/encode.go
@@ -9,6 +9,7 @@ import (
 	"github.com/btcsuite/btcd/btcutil/bech32"
 	"github.com/btcsuite/btcd/chaincfg"
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/lnwire"
 )
 
@@ -301,14 +302,14 @@ func writeTaggedFields(bufferBase32 *bytes.Buffer, invoice *Invoice) error {
 			return err
 		}
 	}
-	if invoice.PaymentAddr != nil {
-		err := writeBytes32(
-			bufferBase32, fieldTypeS, *invoice.PaymentAddr,
-		)
-		if err != nil {
-			return err
-		}
+
+	err := fn.MapOptionZ(invoice.PaymentAddr, func(addr [32]byte) error {
+		return writeBytes32(bufferBase32, fieldTypeS, addr)
+	})
+	if err != nil {
+		return err
 	}
+
 	if invoice.Features.SerializeSize32() > 0 {
 		var b bytes.Buffer
 		err := invoice.Features.RawFeatureVector.EncodeBase32(&b)
diff --git a/zpay32/invoice.go b/zpay32/invoice.go
index 8b1457ab4..7c18253eb 100644
--- a/zpay32/invoice.go
+++ b/zpay32/invoice.go
@@ -8,6 +8,7 @@ import (
 	"github.com/btcsuite/btcd/btcec/v2"
 	"github.com/btcsuite/btcd/btcutil"
 	"github.com/btcsuite/btcd/chaincfg"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/lnwire"
 )
 
@@ -136,7 +137,7 @@ type Invoice struct {
 
 	// PaymentAddr is the payment address to be used by payments to prevent
 	// probing of the destination.
-	PaymentAddr *[32]byte
+	PaymentAddr fn.Option[[32]byte]
 
 	// Destination is the public key of the target node. This will always
 	// be set after decoding, and can optionally be set before encoding to
@@ -301,7 +302,7 @@ func Features(features *lnwire.FeatureVector) func(*Invoice) {
 // the desired payment address that is advertised on the invoice.
 func PaymentAddr(addr [32]byte) func(*Invoice) {
 	return func(i *Invoice) {
-		i.PaymentAddr = &addr
+		i.PaymentAddr = fn.Some(addr)
 	}
 }
 


Test patch:
diff --git a/routing/integrated_routing_context_test.go b/routing/integrated_routing_context_test.go
index 785fa1a50..061dcfc3a 100644
--- a/routing/integrated_routing_context_test.go
+++ b/routing/integrated_routing_context_test.go
@@ -187,7 +187,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
 		FinalCLTVDelta: uint16(c.finalExpiry),
 		FeeLimit:       lnwire.MaxMilliSatoshi,
 		Target:         c.target.pubkey,
-		PaymentAddr:    &paymentAddr,
+		PaymentAddr:    fn.Some(paymentAddr),
 		DestFeatures: lnwire.NewFeatureVector(
 			baseFeatureBits, lnwire.Features,
 		),
diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go
index 38942a31d..72f71600d 100644
--- a/routing/pathfind_test.go
+++ b/routing/pathfind_test.go
@@ -1367,7 +1367,7 @@ func TestNewRoute(t *testing.T) {
 		// overwrite the final hop's feature vector in the graph.
 		destFeatures *lnwire.FeatureVector
 
-		paymentAddr *[32]byte
+		paymentAddr fn.Option[[32]byte]
 
 		// metadata is the payment metadata to attach to the route.
 		metadata []byte
@@ -1446,7 +1446,7 @@ func TestNewRoute(t *testing.T) {
 			// a fee to receive the payment.
 			name:          "two hop single shot mpp",
 			destFeatures:  tlvPayAddrFeatures,
-			paymentAddr:   &testPaymentAddr,
+			paymentAddr:   fn.Some(testPaymentAddr),
 			paymentAmount: 100000,
 			hops: []*models.CachedEdgePolicy{
 				createHop(0, 1000, 1000000, 10),
@@ -1911,7 +1911,7 @@ func runDestPaymentAddr(t *testing.T, useCache bool) {
 	luoji := ctx.keyFromAlias("luoji")
 
 	// Add payment address w/o any invoice features.
-	ctx.restrictParams.PaymentAddr = &[32]byte{1}
+	ctx.restrictParams.PaymentAddr = fn.Some([32]byte{1})
 
 	// Add empty destination features. This should cause us to fail, since
 	// this overrides anything in the graph.
@@ -2955,7 +2955,7 @@ func runInboundFees(t *testing.T, useCache bool) {
 	ctx := newPathFindingTestContext(t, useCache, testChannels, "a")
 
 	payAddr := [32]byte{1}
-	ctx.restrictParams.PaymentAddr = &payAddr
+	ctx.restrictParams.PaymentAddr = fn.Some(payAddr)
 	ctx.restrictParams.DestFeatures = tlvPayAddrFeatures
 
 	const (
@@ -2974,7 +2974,7 @@ func runInboundFees(t *testing.T, useCache bool) {
 			amt:         paymentAmt,
 			cltvDelta:   finalHopCLTV,
 			records:     nil,
-			paymentAddr: &payAddr,
+			paymentAddr: fn.Some(payAddr),
 			totalAmt:    paymentAmt,
 		},
 		nil,
@@ -3469,7 +3469,7 @@ func TestLastHopPayloadSize(t *testing.T) {
 		{
 			name: "Non blinded final hop",
 			restrictions: &RestrictParams{
-				PaymentAddr:       paymentAddr,
+				PaymentAddr:       fn.Some(*paymentAddr),
 				DestCustomRecords: customRecords,
 				Metadata:          metadata,
 				Amp:               ampOptions,
@@ -3501,12 +3501,10 @@ func TestLastHopPayloadSize(t *testing.T) {
 		t.Run(tc.name, func(t *testing.T) {
 			t.Parallel()
 
-			var mpp *record.MPP
-			if tc.restrictions.PaymentAddr != nil {
-				mpp = record.NewMPP(
-					tc.amount, *tc.restrictions.PaymentAddr,
-				)
-			}
+			mpp := fn.MapOptionZ(tc.restrictions.PaymentAddr,
+				func(addr [32]byte) *record.MPP {
+					return record.NewMPP(tc.amount, addr)
+				})
 
 			// In case it's an AMP payment we use the max AMP record
 			// size to estimate the final hop size.
diff --git a/routing/router_test.go b/routing/router_test.go
index 62b015abb..0380bab00 100644
--- a/routing/router_test.go
+++ b/routing/router_test.go
@@ -1653,14 +1653,14 @@ func TestBuildRoute(t *testing.T) {
 	// Test that we can't build a route when no hops are given.
 	hops = []route.Vertex{}
 	_, err = ctx.router.BuildRoute(
-		noAmt, hops, nil, 40, nil, fn.None[[]byte](),
+		noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
 	)
 	require.Error(t, err)
 
 	// Create hop list for an unknown destination.
 	hops := []route.Vertex{ctx.aliases["b"], ctx.aliases["y"]}
 	_, err = ctx.router.BuildRoute(
-		noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
+		noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
 	)
 	noChanErr := ErrNoChannel{}
 	require.ErrorAs(t, err, &noChanErr)
@@ -1672,7 +1672,8 @@ func TestBuildRoute(t *testing.T) {
 
 	// Build the route for the given amount.
 	rt, err := ctx.router.BuildRoute(
-		fn.Some(amt), hops, nil, 40, &payAddr, fn.None[[]byte](),
+		fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
+		fn.None[[]byte](),
 	)
 	require.NoError(t, err)
 
@@ -1684,7 +1685,7 @@ func TestBuildRoute(t *testing.T) {
 
 	// Build the route for the minimum amount.
 	rt, err = ctx.router.BuildRoute(
-		noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
+		noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
 	)
 	require.NoError(t, err)
 
@@ -1702,7 +1703,7 @@ func TestBuildRoute(t *testing.T) {
 	// There is no amount that can pass through both channel 5 and 4.
 	hops = []route.Vertex{ctx.aliases["e"], ctx.aliases["c"]}
 	_, err = ctx.router.BuildRoute(
-		noAmt, hops, nil, 40, nil, fn.None[[]byte](),
+		noAmt, hops, nil, 40, fn.None[[32]byte](), fn.None[[]byte](),
 	)
 	require.Error(t, err)
 	noChanErr = ErrNoChannel{}
@@ -1722,7 +1723,7 @@ func TestBuildRoute(t *testing.T) {
 	// policy of channel 3.
 	hops = []route.Vertex{ctx.aliases["b"], ctx.aliases["z"]}
 	rt, err = ctx.router.BuildRoute(
-		noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
+		noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
 	)
 	require.NoError(t, err)
 	checkHops(rt, []uint64{1, 8}, payAddr)
@@ -1736,7 +1737,8 @@ func TestBuildRoute(t *testing.T) {
 	hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
 	amt = lnwire.NewMSatFromSatoshis(100)
 	rt, err = ctx.router.BuildRoute(
-		fn.Some(amt), hops, nil, 40, &payAddr, fn.None[[]byte](),
+		fn.Some(amt), hops, nil, 40, fn.Some(payAddr),
+		fn.None[[]byte](),
 	)
 	require.NoError(t, err)
 	checkHops(rt, []uint64{9, 10}, payAddr)
@@ -1752,7 +1754,7 @@ func TestBuildRoute(t *testing.T) {
 	// is a third pass through newRoute in which this gets corrected to end
 	hops = []route.Vertex{ctx.aliases["d"], ctx.aliases["f"]}
 	rt, err = ctx.router.BuildRoute(
-		noAmt, hops, nil, 40, &payAddr, fn.None[[]byte](),
+		noAmt, hops, nil, 40, fn.Some(payAddr), fn.None[[]byte](),
 	)
 	require.NoError(t, err)
 	checkHops(rt, []uint64{9, 10}, payAddr)
diff --git a/zpay32/invoice_test.go b/zpay32/invoice_test.go
index 6a726daf3..d982a14a2 100644
--- a/zpay32/invoice_test.go
+++ b/zpay32/invoice_test.go
@@ -17,6 +17,7 @@ import (
 	"github.com/btcsuite/btcd/chaincfg"
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	sphinx "github.com/lightningnetwork/lightning-onion"
+	"github.com/lightningnetwork/lnd/fn"
 	"github.com/lightningnetwork/lnd/lnwire"
 	"github.com/stretchr/testify/require"
 )
@@ -562,7 +563,7 @@ func TestDecodeEncode(t *testing.T) {
 					MilliSat:    &testMillisat25mBTC,
 					Timestamp:   time.Unix(1496314658, 0),
 					PaymentHash: &testPaymentHash,
-					PaymentAddr: &specPaymentAddr,
+					PaymentAddr: fn.Some(specPaymentAddr),
 					Description: &testCoffeeBeans,
 					Destination: testPubKey,
 					Features: lnwire.NewFeatureVector(
@@ -589,7 +590,7 @@ func TestDecodeEncode(t *testing.T) {
 					MilliSat:    &testMillisat25mBTC,
 					Timestamp:   time.Unix(1496314658, 0),
 					PaymentHash: &testPaymentHash,
-					PaymentAddr: &specPaymentAddr,
+					PaymentAddr: fn.Some(specPaymentAddr),
 					Description: &testCoffeeBeans,
 					Destination: testPubKey,
 					Features: lnwire.NewFeatureVector(
@@ -718,7 +719,7 @@ func TestDecodeEncode(t *testing.T) {
 					PaymentHash: &testPaymentHash,
 					Description: &testPaymentMetadata,
 					Destination: testPubKey,
-					PaymentAddr: &specPaymentAddr,
+					PaymentAddr: fn.Some(specPaymentAddr),
 					Features: lnwire.NewFeatureVector(
 						lnwire.NewRawFeatureVector(8, 14, 48),
 						lnwire.Features,
@@ -772,7 +773,7 @@ func TestDecodeEncode(t *testing.T) {
 					MilliSat:    &testMillisat25mBTC,
 					Timestamp:   time.Unix(1496314658, 0),
 					PaymentHash: &testPaymentHash,
-					PaymentAddr: &specPaymentAddr,
+					PaymentAddr: fn.Some(specPaymentAddr),
 					Description: &testCoffeeBeans,
 					Destination: testPubKey,
 					Features: lnwire.NewFeatureVector(
@@ -803,7 +804,7 @@ func TestDecodeEncode(t *testing.T) {
 					MilliSat:    &testMillisat25mBTC,
 					Timestamp:   time.Unix(1496314658, 0),
 					PaymentHash: &testPaymentHash,
-					PaymentAddr: &specPaymentAddr,
+					PaymentAddr: fn.Some(specPaymentAddr),
 					Description: &testCoffeeBeans,
 					Destination: testPubKey,
 					Features: lnwire.NewFeatureVector(