Repository: lightningnetwork/lnd
Pull request #9626 (2025-03-21 04:04:40)

payment lifecycle small fix
While working on the payment lifecycle I came across this case. When trying to send a keysend with a max shard restriction we would not fail the payment properly and it would hang indefinitely being marked as `INFLIGHT` in the db although no payment was attempted.

Just try sending a payment via this cmd:

`lncli sendpayment --dest XXX  --amt 100 --keysend --max_shard_size_sat 10`

The payment lifecycle will fail here because keysend payments are not MPP payments but we try to shard them:

https://github.com/lightningnetwork/lnd/blob/5d921723b1929c08c7397bbbef52b8639a55ac21/channeldb/payment_control.go#L419-L422






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>:label: 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 -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<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?utm_source=oss&utm_medium=github&utm_campaign=lightningnetwork/lnd&utm_content=9626):

- 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 generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this 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://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) 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/.gitignore b/.gitignore
index 1e498b0cf..5ddd602d3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,5 +80,3 @@ coverage.txt
 # Release build directory (to avoid build.vcs.modified Golang build tag to be
 # set to true by having untracked files in the working directory).
 /lnd-*/
-
-.aider*
diff --git a/build/version.go b/build/version.go
index d15e652fb..c3db5dc70 100644
--- a/build/version.go
+++ b/build/version.go
@@ -44,14 +44,14 @@ const (
 	AppMajor uint = 0
 
 	// AppMinor defines the minor version of this binary.
-	AppMinor uint = 19
+	AppMinor uint = 18
 
 	// AppPatch defines the application patch for this binary.
-	AppPatch uint = 00
+	AppPatch uint = 99
 
 	// AppPreRelease MUST only contain characters from semanticAlphabet per
 	// the semantic versioning spec.
-	AppPreRelease = "beta.rc1"
+	AppPreRelease = "beta"
 )
 
 func init() {
diff --git a/chainio/dispatcher.go b/chainio/dispatcher.go
index f1fce35b5..3f22948bd 100644
--- a/chainio/dispatcher.go
+++ b/chainio/dispatcher.go
@@ -162,7 +162,7 @@ func (b *BlockbeatDispatcher) dispatchBlocks(
 			// new block arrives for subsystems.
 			clog.Debugf("%v", lnutils.NewSeparatorClosure())
 
-			clog.Debugf("Received new block %v at height %d, "+
+			clog.Infof("Received new block %v at height %d, "+
 				"notifying consumers...", blockEpoch.Hash,
 				blockEpoch.Height)
 
@@ -179,7 +179,7 @@ func (b *BlockbeatDispatcher) dispatchBlocks(
 				b.log().Errorf("Notify block failed: %v", err)
 			}
 
-			b.log().Debugf("Notified all consumers on new block "+
+			b.log().Infof("Notified all consumers on new block "+
 				"in %v", time.Since(start))
 
 		// A query has been made to fetch the current height, we now
diff --git a/chainreg/chainparams.go b/chainreg/chainparams.go
index 166a1fec5..33006d5a2 100644
--- a/chainreg/chainparams.go
+++ b/chainreg/chainparams.go
@@ -22,14 +22,6 @@ var BitcoinTestNetParams = BitcoinNetParams{
 	CoinType: keychain.CoinTypeTestnet,
 }
 
-// BitcoinTestNet4Params contains parameters specific to the 4th version of the
-// test network.
-var BitcoinTestNet4Params = BitcoinNetParams{
-	Params:   &bitcoinCfg.TestNet4Params,
-	RPCPort:  "48334",
-	CoinType: keychain.CoinTypeTestnet,
-}
-
 // BitcoinMainNetParams contains parameters specific to the current Bitcoin
 // mainnet.
 var BitcoinMainNetParams = BitcoinNetParams{
@@ -61,9 +53,8 @@ var BitcoinRegTestNetParams = BitcoinNetParams{
 	CoinType: keychain.CoinTypeTestnet,
 }
 
-// IsTestnet tests if the given params correspond to a testnet parameter
-// configuration.
+// IsTestnet tests if the givern params correspond to a testnet
+// parameter configuration.
 func IsTestnet(params *BitcoinNetParams) bool {
-	return params.Params.Net == bitcoinWire.TestNet3 ||
-		params.Params.Net == bitcoinWire.TestNet4
+	return params.Params.Net == bitcoinWire.TestNet3
 }
diff --git a/chainreg/chainregistry.go b/chainreg/chainregistry.go
index 3f9f738b2..a9a9ede70 100644
--- a/chainreg/chainregistry.go
+++ b/chainreg/chainregistry.go
@@ -838,15 +838,6 @@ var (
 		0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00,
 	})
 
-	// BitcoinTestnet4Genesis is the genesis hash of Bitcoin's testnet4
-	// chain.
-	BitcoinTestnet4Genesis = chainhash.Hash([chainhash.HashSize]byte{
-		0x43, 0xf0, 0x8b, 0xda, 0xb0, 0x50, 0xe3, 0x5b,
-		0x56, 0x7c, 0x86, 0x4b, 0x91, 0xf4, 0x7f, 0x50,
-		0xae, 0x72, 0x5a, 0xe2, 0xde, 0x53, 0xbc, 0xfb,
-		0xba, 0xf2, 0x84, 0xda, 0x00, 0x00, 0x00, 0x00,
-	})
-
 	// BitcoinSignetGenesis is the genesis hash of Bitcoin's signet chain.
 	BitcoinSignetGenesis = chainhash.Hash([chainhash.HashSize]byte{
 		0xf6, 0x1e, 0xee, 0x3b, 0x63, 0xa3, 0x80, 0xa4,
diff --git a/cmd/commands/commands.go b/cmd/commands/commands.go
index 9cbd0bf7a..7969cd20a 100644
--- a/cmd/commands/commands.go
+++ b/cmd/commands/commands.go
@@ -2228,11 +2228,9 @@ var listChainTxnsCommand = cli.Command{
 		cli.Int64Flag{
 			Name: "end_height",
 			Usage: "the block height until which to list " +
-				"transactions, inclusive; by default this " +
-				"will return all transactions up to the " +
-				"chain tip including unconfirmed " +
-				"transactions",
-			Value: -1,
+				"transactions, inclusive, to get " +
+				"transactions until the chain tip, including " +
+				"unconfirmed, set this value to -1",
 		},
 		cli.UintFlag{
 			Name: "index_offset",
@@ -2243,7 +2241,7 @@ var listChainTxnsCommand = cli.Command{
 		},
 		cli.IntFlag{
 			Name: "max_transactions",
-			Usage: "the max number of transactions to " +
+			Usage: "(optional) the max number of transactions to " +
 				"return; leave at default of 0 to return " +
 				"all transactions",
 			Value: 0,
@@ -2253,51 +2251,33 @@ var listChainTxnsCommand = cli.Command{
 	List all transactions an address of the wallet was involved in.
 
 	This call will return a list of wallet related transactions that paid
-	to an address our wallet controls, or spent utxos that we held.
-
-	By default, this call will get all transactions until the chain tip, 
-	including unconfirmed transactions (end_height=-1).`,
+	to an address our wallet controls, or spent utxos that we held. The
+	start_height and end_height flags can be used to specify an inclusive
+	block range over which to query for transactions. If the end_height is
+	less than the start_height, transactions will be queried in reverse.
+	To get all transactions until the chain tip, including unconfirmed
+	transactions (identifiable with BlockHeight=0), set end_height to -1.
+	By default, this call will get all transactions our wallet was involved
+	in, including unconfirmed transactions.
+`,
 	Action: actionDecorator(listChainTxns),
 }
 
-func parseBlockHeightInputs(ctx *cli.Context) (int32, int32, error) {
-	startHeight := int32(ctx.Int64("start_height"))
-	endHeight := int32(ctx.Int64("end_height"))
-
-	if ctx.IsSet("start_height") && ctx.IsSet("end_height") {
-		if endHeight != -1 && startHeight > endHeight {
-			return startHeight, endHeight,
-				errors.New("start_height should " +
-					"be less than end_height if " +
-					"end_height is not equal to -1")
-		}
-	}
-
-	if startHeight < 0 {
-		return startHeight, endHeight,
-			errors.New("start_height should " +
-				"be greater than or " +
-				"equal to 0")
-	}
-
-	return startHeight, endHeight, nil
-}
-
 func listChainTxns(ctx *cli.Context) error {
 	ctxc := getContext()
 	client, cleanUp := getClient(ctx)
 	defer cleanUp()
 
-	startHeight, endHeight, err := parseBlockHeightInputs(ctx)
-	if err != nil {
-		return err
-	}
-
 	req := &lnrpc.GetTransactionsRequest{
 		IndexOffset:     uint32(ctx.Uint64("index_offset")),
 		MaxTransactions: uint32(ctx.Uint64("max_transactions")),
-		StartHeight:     startHeight,
-		EndHeight:       endHeight,
+	}
+
+	if ctx.IsSet("start_height") {
+		req.StartHeight = int32(ctx.Int64("start_height"))
+	}
+	if ctx.IsSet("end_height") {
+		req.EndHeight = int32(ctx.Int64("end_height"))
 	}
 
 	resp, err := client.GetTransactions(ctxc, req)
diff --git a/cmd/commands/main.go b/cmd/commands/main.go
index ec593f1ad..71a8531d9 100644
--- a/cmd/commands/main.go
+++ b/cmd/commands/main.go
@@ -286,7 +286,7 @@ func contextWithMetadata(ctx context.Context,
 func extractPathArgs(ctx *cli.Context) (string, string, error) {
 	network := strings.ToLower(ctx.GlobalString("network"))
 	switch network {
-	case "mainnet", "testnet", "testnet4", "regtest", "simnet", "signet":
+	case "mainnet", "testnet", "regtest", "simnet", "signet":
 	default:
 		return "", "", fmt.Errorf("unknown network: %v", network)
 	}
@@ -386,9 +386,8 @@ func Main() {
 		},
 		cli.StringFlag{
 			Name: "network, n",
-			Usage: "The network lnd is running on; valid values " +
-				"are: mainnet, testnet, testnet4, regtest, " +
-				"signet and simnet.",
+			Usage: "The network lnd is running on, e.g. mainnet, " +
+				"testnet, etc.",
 			Value:  "mainnet",
 			EnvVar: envVarNetwork,
 		},
@@ -556,12 +555,9 @@ func networkParams(ctx *cli.Context) (*chaincfg.Params, error) {
 	case "mainnet":
 		return &chaincfg.MainNetParams, nil
 
-	case "testnet", "testnet3":
+	case "testnet":
 		return &chaincfg.TestNet3Params, nil
 
-	case "testnet4":
-		return &chaincfg.TestNet4Params, nil
-
 	case "regtest":
 		return &chaincfg.RegressionNetParams, nil
 
diff --git a/config.go b/config.go
index 824bcc55f..be0838be2 100644
--- a/config.go
+++ b/config.go
@@ -707,8 +707,6 @@ func DefaultConfig() Config {
 			ChannelUpdateInterval: discovery.DefaultChannelUpdateInterval,
 			SubBatchDelay:         discovery.DefaultSubBatchDelay,
 			AnnouncementConf:      discovery.DefaultProofMatureDelta,
-			MsgRateBytes:          discovery.DefaultMsgBytesPerSecond,
-			MsgBurstBytes:         discovery.DefaultMsgBytesBurst,
 		},
 		Invoices: &lncfg.Invoices{
 			HoldExpiryDelta: lncfg.DefaultHoldInvoiceExpiryDelta,
@@ -1210,10 +1208,6 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
 		numNets++
 		cfg.ActiveNetParams = chainreg.BitcoinTestNetParams
 	}
-	if cfg.Bitcoin.TestNet4 {
-		numNets++
-		cfg.ActiveNetParams = chainreg.BitcoinTestNet4Params
-	}
 	if cfg.Bitcoin.RegTest {
 		numNets++
 		cfg.ActiveNetParams = chainreg.BitcoinRegTestNetParams
@@ -1271,8 +1265,8 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
 		cfg.ActiveNetParams.Params = &chainParams
 	}
 	if numNets > 1 {
-		str := "The mainnet, testnet, testnet4, regtest, simnet and " +
-			"signet params can't be used together -- choose one " +
+		str := "The mainnet, testnet, regtest, simnet and signet " +
+			"params can't be used together -- choose one " +
 			"of the five"
 
 		return nil, mkErr(str)
@@ -1281,10 +1275,9 @@ func ValidateConfig(cfg Config, interceptor signal.Interceptor, fileParser,
 	// The target network must be provided, otherwise, we won't
 	// know how to initialize the daemon.
 	if numNets == 0 {
-		str := "either --bitcoin.mainnet, or --bitcoin.testnet, " +
-			"--bitcoin.testnet4, --bitcoin.simnet, " +
-			"--bitcoin.regtest or --bitcoin.signet must be " +
-			"specified"
+		str := "either --bitcoin.mainnet, or bitcoin.testnet, " +
+			"bitcoin.simnet, bitcoin.regtest or bitcoin.signet " +
+			"must be specified"
 
 		return nil, mkErr(str)
 	}
@@ -2209,7 +2202,7 @@ func extractBitcoindRPCParams(networkName, bitcoindDataDir, bitcoindConfigPath,
 	switch networkName {
 	case "mainnet":
 		chainDir = ""
-	case "regtest", "testnet3", "testnet4", "signet":
+	case "regtest", "testnet3", "signet":
 		chainDir = networkName
 	default:
 		return "", "", "", "", fmt.Errorf("unexpected networkname %v", networkName)
diff --git a/discovery/gossiper.go b/discovery/gossiper.go
index cdeaa2942..1a74e3e15 100644
--- a/discovery/gossiper.go
+++ b/discovery/gossiper.go
@@ -390,14 +390,6 @@ type Config struct {
 	// spent-ness of channel outpoints. For neutrino, this saves long
 	// rescans from blocking initial usage of the daemon.
 	AssumeChannelValid bool
-
-	// MsgRateBytes is the rate limit for the number of bytes per second
-	// that we'll allocate to outbound gossip messages.
-	MsgRateBytes uint64
-
-	// MsgBurstBytes is the allotted burst amount in bytes. This is the
-	// number of starting tokens in our token bucket algorithm.
-	MsgBurstBytes uint64
 }
 
 // processedNetworkMsg is a wrapper around networkMsg and a boolean. It is
@@ -582,18 +574,16 @@ func New(cfg Config, selfKeyDesc *keychain.KeyDescriptor) *AuthenticatedGossiper
 	gossiper.vb = NewValidationBarrier(1000, gossiper.quit)
 
 	gossiper.syncMgr = newSyncManager(&SyncManagerCfg{
-		ChainHash:                cfg.ChainHash,
-		ChanSeries:               cfg.ChanSeries,
-		RotateTicker:             cfg.RotateTicker,
-		HistoricalSyncTicker:     cfg.HistoricalSyncTicker,
-		NumActiveSyncers:         cfg.NumActiveSyncers,
-		NoTimestampQueries:       cfg.NoTimestampQueries,
-		IgnoreHistoricalFilters:  cfg.IgnoreHistoricalFilters,
-		BestHeight:               gossiper.latestHeight,
-		PinnedSyncers:            cfg.PinnedSyncers,
-		IsStillZombieChannel:     cfg.IsStillZombieChannel,
-		AllotedMsgBytesPerSecond: cfg.MsgRateBytes,
-		AllotedMsgBytesBurst:     cfg.MsgBurstBytes,
+		ChainHash:               cfg.ChainHash,
+		ChanSeries:              cfg.ChanSeries,
+		RotateTicker:            cfg.RotateTicker,
+		HistoricalSyncTicker:    cfg.HistoricalSyncTicker,
+		NumActiveSyncers:        cfg.NumActiveSyncers,
+		NoTimestampQueries:      cfg.NoTimestampQueries,
+		IgnoreHistoricalFilters: cfg.IgnoreHistoricalFilters,
+		BestHeight:              gossiper.latestHeight,
+		PinnedSyncers:           cfg.PinnedSyncers,
+		IsStillZombieChannel:    cfg.IsStillZombieChannel,
 	})
 
 	gossiper.reliableSender = newReliableSender(&reliableSenderCfg{
diff --git a/discovery/sync_manager.go b/discovery/sync_manager.go
index b1cb208cd..9c589d638 100644
--- a/discovery/sync_manager.go
+++ b/discovery/sync_manager.go
@@ -1,7 +1,6 @@
 package discovery
 
 import (
-	"context"
 	"errors"
 	"sync"
 	"sync/atomic"
@@ -12,7 +11,6 @@ import (
 	"github.com/lightningnetwork/lnd/lnwire"
 	"github.com/lightningnetwork/lnd/routing/route"
 	"github.com/lightningnetwork/lnd/ticker"
-	"golang.org/x/time/rate"
 )
 
 const (
@@ -27,21 +25,6 @@ const (
 
 	// filterSemaSize is the capacity of gossipFilterSema.
 	filterSemaSize = 5
-
-	// DefaultMsgBytesBurst is the allotted burst in bytes we'll permit.
-	// This is the most that can be sent in a given go. Requests beyond
-	// this, will block indefinitely. Once tokens (bytes are depleted),
-	// they'll be refilled at the DefaultMsgBytesPerSecond rate.
-	DefaultMsgBytesBurst = 2 * 100 * 1_024
-
-	// DefaultMsgBytesPerSecond is the max bytes/s we'll permit for outgoing
-	// messages. Once tokens (bytes) have been taken from the bucket,
-	// they'll be refilled at this rate.
-	DefaultMsgBytesPerSecond = 100 * 1_024
-
-	// assumedMsgSize is the assumed size of a message if we can't compute
-	// its serialized size. This comes out to 1 KB.
-	assumedMsgSize = 1_024
 )
 
 var (
@@ -127,15 +110,6 @@ type SyncManagerCfg struct {
 	// updates for a channel and returns true if the channel should be
 	// considered a zombie based on these timestamps.
 	IsStillZombieChannel func(time.Time, time.Time) bool
-
-	// AllotedMsgBytesPerSecond is the allotted bandwidth rate, expressed in
-	// bytes/second that the gossip manager can consume. Once we exceed this
-	// rate, message sending will block until we're below the rate.
-	AllotedMsgBytesPerSecond uint64
-
-	// AllotedMsgBytesBurst is the amount of burst bytes we'll permit, if
-	// we've exceeded the hard upper limit.
-	AllotedMsgBytesBurst uint64
 }
 
 // SyncManager is a subsystem of the gossiper that manages the gossip syncers
@@ -194,12 +168,6 @@ type SyncManager struct {
 	// queries.
 	gossipFilterSema chan struct{}
 
-	// rateLimiter dictates the frequency with which we will reply to gossip
-	// queries from a peer. This is used to delay responses to peers to
-	// prevent DOS vulnerabilities if they are spamming with an unreasonable
-	// number of queries.
-	rateLimiter *rate.Limiter
-
 	wg   sync.WaitGroup
 	quit chan struct{}
 }
@@ -212,25 +180,8 @@ func newSyncManager(cfg *SyncManagerCfg) *SyncManager {
 		filterSema <- struct{}{}
 	}
 
-	bytesPerSecond := cfg.AllotedMsgBytesPerSecond
-	if bytesPerSecond == 0 {
-		bytesPerSecond = DefaultMsgBytesPerSecond
-	}
-
-	bytesBurst := cfg.AllotedMsgBytesBurst
-	if bytesBurst == 0 {
-		bytesBurst = DefaultMsgBytesBurst
-	}
-
-	// We'll use this rate limiter to limit our total outbound bandwidth for
-	// gossip queries peers.
-	rateLimiter := rate.NewLimiter(
-		rate.Limit(bytesPerSecond), int(bytesBurst),
-	)
-
 	return &SyncManager{
 		cfg:          *cfg,
-		rateLimiter:  rateLimiter,
 		newSyncers:   make(chan *newSyncer),
 		staleSyncers: make(chan *staleSyncer),
 		activeSyncers: make(
@@ -543,95 +494,6 @@ func (m *SyncManager) isPinnedSyncer(s *GossipSyncer) bool {
 	return isPinnedSyncer
 }
 
-// deriveRateLimitReservation will take the current message and derive a
-// reservation that can be used to wait on the rate limiter.
-func (m *SyncManager) deriveRateLimitReservation(msg lnwire.Message,
-) (*rate.Reservation, error) {
-
-	var (
-		msgSize uint32
-		err     error
-	)
-
-	// Figure out the serialized size of the message. If we can't easily
-	// compute it, then we'll used the assumed msg size.
-	if sMsg, ok := msg.(lnwire.SizeableMessage); ok {
-		msgSize, err = sMsg.SerializedSize()
-		if err != nil {
-			return nil, err
-		}
-	} else {
-		log.Warnf("Unable to compute serialized size of %T", msg)
-
-		msgSize = assumedMsgSize
-	}
-
-	return m.rateLimiter.ReserveN(time.Now(), int(msgSize)), nil
-}
-
-// waitMsgDelay takes a delay, and waits until it has finished.
-func (m *SyncManager) waitMsgDelay(ctx context.Context, peerPub [33]byte,
-	limitReservation *rate.Reservation) error {
-
-	// If we've already replied a handful of times, we will start to delay
-	// responses back to the remote peer. This can help prevent DOS attacks
-	// where the remote peer spams us endlessly.
-	//
-	// We skip checking for reservation.OK() here, as during config
-	// validation, we ensure that the burst is enough for a single message
-	// to be sent.
-	delay := limitReservation.Delay()
-	if delay > 0 {
-		log.Infof("GossipSyncer(%x): rate limiting gossip replies, "+
-			"responding in %s", peerPub, delay)
-
-		select {
-		case <-time.After(delay):
-
-		case <-ctx.Done():
-			limitReservation.Cancel()
-
-			return ErrGossipSyncerExiting
-
-		case <-m.quit:
-			limitReservation.Cancel()
-
-			return ErrGossipSyncerExiting
-		}
-	}
-
-	return nil
-}
-
-// maybeRateLimitMsg takes a message, and may wait a period of time to rate
-// limit the msg.
-func (m *SyncManager) maybeRateLimitMsg(ctx context.Context, peerPub [33]byte,
-	msg lnwire.Message) error {
-
-	delay, err := m.deriveRateLimitReservation(msg)
-	if err != nil {
-		return nil
-	}
-
-	return m.waitMsgDelay(ctx, peerPub, delay)
-}
-
-// sendMessages sends a set of messages to the remote peer.
-func (m *SyncManager) sendMessages(ctx context.Context, sync bool,
-	peer lnpeer.Peer, nodeID route.Vertex, msgs ...lnwire.Message) error {
-
-	for _, msg := range msgs {
-		if err := m.maybeRateLimitMsg(ctx, nodeID, msg); err != nil {
-			return err
-		}
-		if err := peer.SendMessageLazy(sync, msg); err != nil {
-			return err
-		}
-	}
-
-	return nil
-}
-
 // createGossipSyncer creates the GossipSyncer for a newly connected peer.
 func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer {
 	nodeID := route.Vertex(peer.PubKey())
@@ -645,22 +507,20 @@ func (m *SyncManager) createGossipSyncer(peer lnpeer.Peer) *GossipSyncer {
 		encodingType:  encoding,
 		chunkSize:     encodingTypeToChunkSize[encoding],
 		batchSize:     requestBatchSize,
-		sendToPeer: func(ctx context.Context,
-			msgs ...lnwire.Message) error {
-
-			return m.sendMessages(ctx, false, peer, nodeID, msgs...)
+		sendToPeer: func(msgs ...lnwire.Message) error {
+			return peer.SendMessageLazy(false, msgs...)
 		},
-		sendToPeerSync: func(ctx context.Context,
-			msgs ...lnwire.Message) error {
-
-			return m.sendMessages(ctx, true, peer, nodeID, msgs...)
+		sendToPeerSync: func(msgs ...lnwire.Message) error {
+			return peer.SendMessageLazy(true, msgs...)
 		},
-		ignoreHistoricalFilters:  m.cfg.IgnoreHistoricalFilters,
-		bestHeight:               m.cfg.BestHeight,
-		markGraphSynced:          m.markGraphSynced,
-		maxQueryChanRangeReplies: maxQueryChanRangeReplies,
-		noTimestampQueryOption:   m.cfg.NoTimestampQueries,
-		isStillZombieChannel:     m.cfg.IsStillZombieChannel,
+		ignoreHistoricalFilters:   m.cfg.IgnoreHistoricalFilters,
+		maxUndelayedQueryReplies:  DefaultMaxUndelayedQueryReplies,
+		delayedQueryReplyInterval: DefaultDelayedQueryReplyInterval,
+		bestHeight:                m.cfg.BestHeight,
+		markGraphSynced:           m.markGraphSynced,
+		maxQueryChanRangeReplies:  maxQueryChanRangeReplies,
+		noTimestampQueryOption:    m.cfg.NoTimestampQueries,
+		isStillZombieChannel:      m.cfg.IsStillZombieChannel,
 	}, m.gossipFilterSema)
 
 	// Gossip syncers are initialized by default in a PassiveSync type
diff --git a/discovery/syncer.go b/discovery/syncer.go
index da3749050..991bd75d3 100644
--- a/discovery/syncer.go
+++ b/discovery/syncer.go
@@ -1,7 +1,6 @@
 package discovery
 
 import (
-	"context"
 	"errors"
 	"fmt"
 	"math"
@@ -12,11 +11,11 @@ import (
 	"time"
 
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
-	"github.com/lightningnetwork/lnd/fn/v2"
 	"github.com/lightningnetwork/lnd/graph"
 	graphdb "github.com/lightningnetwork/lnd/graph/db"
 	"github.com/lightningnetwork/lnd/lnpeer"
 	"github.com/lightningnetwork/lnd/lnwire"
+	"golang.org/x/time/rate"
 )
 
 // SyncerType encapsulates the different types of syncing mechanisms for a
@@ -153,6 +152,15 @@ func (s syncerState) String() string {
 }
 
 const (
+	// DefaultMaxUndelayedQueryReplies specifies how many gossip queries we
+	// will respond to immediately before starting to delay responses.
+	DefaultMaxUndelayedQueryReplies = 10
+
+	// DefaultDelayedQueryReplyInterval is the length of time we will wait
+	// before responding to gossip queries after replying to
+	// maxUndelayedQueryReplies queries.
+	DefaultDelayedQueryReplyInterval = 5 * time.Second
+
 	// maxQueryChanRangeReplies specifies the default limit of replies to
 	// process for a single QueryChannelRange request.
 	maxQueryChanRangeReplies = 500
@@ -242,12 +250,21 @@ type gossipSyncerCfg struct {
 	// sendToPeer sends a variadic number of messages to the remote peer.
 	// This method should not block while waiting for sends to be written
 	// to the wire.
-	sendToPeer func(context.Context, ...lnwire.Message) error
+	sendToPeer func(...lnwire.Message) error
 
 	// sendToPeerSync sends a variadic number of messages to the remote
 	// peer, blocking until all messages have been sent successfully or a
 	// write error is encountered.
-	sendToPeerSync func(context.Context, ...lnwire.Message) error
+	sendToPeerSync func(...lnwire.Message) error
+
+	// maxUndelayedQueryReplies specifies how many gossip queries we will
+	// respond to immediately before starting to delay responses.
+	maxUndelayedQueryReplies int
+
+	// delayedQueryReplyInterval is the length of time we will wait before
+	// responding to gossip queries after replying to
+	// maxUndelayedQueryReplies queries.
+	delayedQueryReplyInterval time.Duration
 
 	// noSyncChannels will prevent the GossipSyncer from spawning a
 	// channelGraphSyncer, meaning we will not try to reconcile unknown
@@ -373,6 +390,12 @@ type GossipSyncer struct {
 
 	cfg gossipSyncerCfg
 
+	// rateLimiter dictates the frequency with which we will reply to gossip
+	// queries from a peer. This is used to delay responses to peers to
+	// prevent DOS vulnerabilities if they are spamming with an unreasonable
+	// number of queries.
+	rateLimiter *rate.Limiter
+
 	// syncedSignal is a channel that, if set, will be closed when the
 	// GossipSyncer reaches its terminal chansSynced state.
 	syncedSignal chan struct{}
@@ -383,23 +406,43 @@ type GossipSyncer struct {
 
 	sync.Mutex
 
-	// cg is a helper that encapsulates a wait group and quit channel and
-	// allows contexts that either block or cancel on those depending on
-	// the use case.
-	cg *fn.ContextGuard
+	quit chan struct{}
+	wg   sync.WaitGroup
 }
 
 // newGossipSyncer returns a new instance of the GossipSyncer populated using
 // the passed config.
 func newGossipSyncer(cfg gossipSyncerCfg, sema chan struct{}) *GossipSyncer {
+	// If no parameter was specified for max undelayed query replies, set it
+	// to the default of 5 queries.
+	if cfg.maxUndelayedQueryReplies <= 0 {
+		cfg.maxUndelayedQueryReplies = DefaultMaxUndelayedQueryReplies
+	}
+
+	// If no parameter was specified for delayed query reply interval, set
+	// to the default of 5 seconds.
+	if cfg.delayedQueryReplyInterval <= 0 {
+		cfg.delayedQueryReplyInterval = DefaultDelayedQueryReplyInterval
+	}
+
+	// Construct a rate limiter that will govern how frequently we reply to
+	// gossip queries from this peer. The limiter will automatically adjust
+	// during periods of quiescence, and increase the reply interval under
+	// load.
+	interval := rate.Every(cfg.delayedQueryReplyInterval)
+	rateLimiter := rate.NewLimiter(
+		interval, cfg.maxUndelayedQueryReplies,
+	)
+
 	return &GossipSyncer{
 		cfg:                cfg,
+		rateLimiter:        rateLimiter,
 		syncTransitionReqs: make(chan *syncTransitionReq),
 		historicalSyncReqs: make(chan *historicalSyncReq),
 		gossipMsgs:         make(chan lnwire.Message, syncerBufferSize),
 		queryMsgs:          make(chan lnwire.Message, syncerBufferSize),
 		syncerSema:         sema,
-		cg:                 fn.NewContextGuard(),
+		quit:               make(chan struct{}),
 	}
 }
 
@@ -413,11 +456,11 @@ func (g *GossipSyncer) Start() {
 		// supports gossip queries, and only spawn replyHandler if we
 		// advertise support
 		if !g.cfg.noSyncChannels {
-			g.cg.WgAdd(1)
+			g.wg.Add(1)
 			go g.channelGraphSyncer()
 		}
 		if !g.cfg.noReplyQueries {
-			g.cg.WgAdd(1)
+			g.wg.Add(1)
 			go g.replyHandler()
 		}
 	})
@@ -430,7 +473,8 @@ func (g *GossipSyncer) Stop() {
 		log.Debugf("Stopping GossipSyncer(%x)", g.cfg.peerPub[:])
 		defer log.Debugf("GossipSyncer(%x) stopped", g.cfg.peerPub[:])
 
-		g.cg.Quit()
+		close(g.quit)
+		g.wg.Wait()
 	})
 }
 
@@ -456,8 +500,7 @@ func (g *GossipSyncer) handleSyncingChans() {
 
 	// Send the msg to the remote peer, which is non-blocking as
 	// `sendToPeer` only queues the msg in Brontide.
-	ctx, _ := g.cg.Create(context.Background())
-	err = g.cfg.sendToPeer(ctx, queryRangeMsg)
+	err = g.cfg.sendToPeer(queryRangeMsg)
 	if err != nil {
 		log.Errorf("Unable to send chan range query: %v", err)
 		return
@@ -472,7 +515,7 @@ func (g *GossipSyncer) handleSyncingChans() {
 // properly channel graph state with the remote peer, and also that we only
 // send them messages which actually pass their defined update horizon.
 func (g *GossipSyncer) channelGraphSyncer() {
-	defer g.cg.WgDone()
+	defer g.wg.Done()
 
 	for {
 		state := g.syncState()
@@ -520,7 +563,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
 				log.Warnf("Unexpected message: %T in state=%v",
 					msg, state)
 
-			case <-g.cg.Done():
+			case <-g.quit:
 				return
 			}
 
@@ -570,7 +613,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
 				log.Warnf("Unexpected message: %T in state=%v",
 					msg, state)
 
-			case <-g.cg.Done():
+			case <-g.quit:
 				return
 			}
 
@@ -616,7 +659,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
 			case req := <-g.historicalSyncReqs:
 				g.handleHistoricalSync(req)
 
-			case <-g.cg.Done():
+			case <-g.quit:
 				return
 			}
 		}
@@ -631,7 +674,7 @@ func (g *GossipSyncer) channelGraphSyncer() {
 //
 // NOTE: This method MUST be run as a goroutine.
 func (g *GossipSyncer) replyHandler() {
-	defer g.cg.WgDone()
+	defer g.wg.Done()
 
 	for {
 		select {
@@ -649,7 +692,7 @@ func (g *GossipSyncer) replyHandler() {
 					"query: %v", err)
 			}
 
-		case <-g.cg.Done():
+		case <-g.quit:
 			return
 		}
 	}
@@ -673,8 +716,7 @@ func (g *GossipSyncer) sendGossipTimestampRange(firstTimestamp time.Time,
 		TimestampRange: timestampRange,
 	}
 
-	ctx, _ := g.cg.Create(context.Background())
-	if err := g.cfg.sendToPeer(ctx, localUpdateHorizon); err != nil {
+	if err := g.cfg.sendToPeer(localUpdateHorizon); err != nil {
 		return err
 	}
 
@@ -730,8 +772,7 @@ func (g *GossipSyncer) synchronizeChanIDs() bool {
 
 	// With our chunk obtained, we'll send over our next query, then return
 	// false indicating that we're net yet fully synced.
-	ctx, _ := g.cg.Create(context.Background())
-	err := g.cfg.sendToPeer(ctx, &lnwire.QueryShortChanIDs{
+	err := g.cfg.sendToPeer(&lnwire.QueryShortChanIDs{
 		ChainHash:    g.cfg.chainHash,
 		EncodingType: lnwire.EncodingSortedPlain,
 		ShortChanIDs: queryChunk,
@@ -1006,6 +1047,23 @@ func (g *GossipSyncer) genChanRangeQuery(
 // replyPeerQueries is called in response to any query by the remote peer.
 // We'll examine our state and send back our best response.
 func (g *GossipSyncer) replyPeerQueries(msg lnwire.Message) error {
+	reservation := g.rateLimiter.Reserve()
+	delay := reservation.Delay()
+
+	// If we've already replied a handful of times, we will start to delay
+	// responses back to the remote peer. This can help prevent DOS attacks
+	// where the remote peer spams us endlessly.
+	if delay > 0 {
+		log.Infof("GossipSyncer(%x): rate limiting gossip replies, "+
+			"responding in %s", g.cfg.peerPub[:], delay)
+
+		select {
+		case <-time.After(delay):
+		case <-g.quit:
+			return ErrGossipSyncerExiting
+		}
+	}
+
 	switch msg := msg.(type) {
 
 	// In this state, we'll also handle any incoming channel range queries
@@ -1038,9 +1096,7 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro
 			"chain=%v, we're on chain=%v", query.ChainHash,
 			g.cfg.chainHash)
 
-		ctx, _ := g.cg.Create(context.Background())
-
-		return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
+		return g.cfg.sendToPeerSync(&lnwire.ReplyChannelRange{
 			ChainHash:        query.ChainHash,
 			FirstBlockHeight: query.FirstBlockHeight,
 			NumBlocks:        query.NumBlocks,
@@ -1113,9 +1169,7 @@ func (g *GossipSyncer) replyChanRangeQuery(query *lnwire.QueryChannelRange) erro
 			)
 		}
 
-		ctx, _ := g.cg.Create(context.Background())
-
-		return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyChannelRange{
+		return g.cfg.sendToPeerSync(&lnwire.ReplyChannelRange{
 			ChainHash:        query.ChainHash,
 			NumBlocks:        numBlocks,
 			FirstBlockHeight: firstHeight,
@@ -1219,9 +1273,7 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
 			"chain=%v, we're on chain=%v", query.ChainHash,
 			g.cfg.chainHash)
 
-		ctx, _ := g.cg.Create(context.Background())
-
-		return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
+		return g.cfg.sendToPeerSync(&lnwire.ReplyShortChanIDsEnd{
 			ChainHash: query.ChainHash,
 			Complete:  0,
 		})
@@ -1252,8 +1304,7 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
 	// each one individually and synchronously to throttle the sends and
 	// perform buffering of responses in the syncer as opposed to the peer.
 	for _, msg := range replyMsgs {
-		ctx, _ := g.cg.Create(context.Background())
-		err := g.cfg.sendToPeerSync(ctx, msg)
+		err := g.cfg.sendToPeerSync(msg)
 		if err != nil {
 			return err
 		}
@@ -1261,9 +1312,7 @@ func (g *GossipSyncer) replyShortChanIDs(query *lnwire.QueryShortChanIDs) error
 
 	// Regardless of whether we had any messages to reply with, send over
 	// the sentinel message to signal that the stream has terminated.
-	ctx, _ := g.cg.Create(context.Background())
-
-	return g.cfg.sendToPeerSync(ctx, &lnwire.ReplyShortChanIDsEnd{
+	return g.cfg.sendToPeerSync(&lnwire.ReplyShortChanIDsEnd{
 		ChainHash: query.ChainHash,
 		Complete:  1,
 	})
@@ -1292,7 +1341,7 @@ func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er
 
 	select {
 	case <-g.syncerSema:
-	case <-g.cg.Done():
+	case <-g.quit:
 		return ErrGossipSyncerExiting
 	}
 
@@ -1323,14 +1372,13 @@ func (g *GossipSyncer) ApplyGossipFilter(filter *lnwire.GossipTimestampRange) er
 	}
 
 	// We'll conclude by launching a goroutine to send out any updates.
-	g.cg.WgAdd(1)
+	g.wg.Add(1)
 	go func() {
-		defer g.cg.WgDone()
+		defer g.wg.Done()
 		defer returnSema()
 
 		for _, msg := range newUpdatestoSend {
-			ctx, _ := g.cg.Create(context.Background())
-			err := g.cfg.sendToPeerSync(ctx, msg)
+			err := g.cfg.sendToPeerSync(msg)
 			switch {
 			case err == ErrGossipSyncerExiting:
 				return
@@ -1363,7 +1411,7 @@ func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) {
 	// If we've been signaled to exit, or are exiting, then we'll stop
 	// short.
 	select {
-	case <-g.cg.Done():
+	case <-g.quit:
 		return
 	default:
 	}
@@ -1472,11 +1520,7 @@ func (g *GossipSyncer) FilterGossipMsgs(msgs ...msgWithSenders) {
 		return
 	}
 
-	ctx, _ := g.cg.Create(context.Background())
-	if err = g.cfg.sendToPeer(ctx, msgsToSend...); err != nil {
-		log.Errorf("unable to send gossip msgs: %v", err)
-	}
-
+	g.cfg.sendToPeer(msgsToSend...)
 }
 
 // ProcessQueryMsg is used by outside callers to pass new channel time series
@@ -1509,7 +1553,7 @@ func (g *GossipSyncer) ProcessQueryMsg(msg lnwire.Message, peerQuit <-chan struc
 	select {
 	case msgChan <- msg:
 	case <-peerQuit:
-	case <-g.cg.Done():
+	case <-g.quit:
 	}
 
 	return nil
@@ -1557,14 +1601,14 @@ func (g *GossipSyncer) ProcessSyncTransition(newSyncType SyncerType) error {
 	}:
 	case <-time.After(syncTransitionTimeout):
 		return ErrSyncTransitionTimeout
-	case <-g.cg.Done():
+	case <-g.quit:
 		return ErrGossipSyncerExiting
 	}
 
 	select {
 	case err := <-errChan:
 		return err
-	case <-g.cg.Done():
+	case <-g.quit:
 		return ErrGossipSyncerExiting
 	}
 }
@@ -1643,14 +1687,14 @@ func (g *GossipSyncer) historicalSync() error {
 	}:
 	case <-time.After(syncTransitionTimeout):
 		return ErrSyncTransitionTimeout
-	case <-g.cg.Done():
+	case <-g.quit:
 		return ErrGossiperShuttingDown
 	}
 
 	select {
 	case <-done:
 		return nil
-	case <-g.cg.Done():
+	case <-g.quit:
 		return ErrGossiperShuttingDown
 	}
 }
diff --git a/docs/release-notes/release-notes-0.19.0.md b/docs/release-notes/release-notes-0.19.0.md
index f4d055d2e..eb263b0bf 100644
--- a/docs/release-notes/release-notes-0.19.0.md
+++ b/docs/release-notes/release-notes-0.19.0.md
@@ -68,10 +68,6 @@
   `estimateroutefee` to assume probing an LSP when given an invoice with a route 
   hint containing a public channel to the destination.
 
-* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/9634) that caused
-  lnd to crash due to a nil pointer dereference when `estimateroutefee` is
-  called for a payment request that contains a zero amount.
-
 * [Fix a bug](https://github.com/lightningnetwork/lnd/pull/9474) where LND would
   fail to persist (and hence, propagate) node announcements containing address 
   types (such as a DNS hostname) unknown to LND.
@@ -94,15 +90,14 @@
 
 * [Pass through](https://github.com/lightningnetwork/lnd/pull/9601) the unused
   `MaxPeers` configuration variable for neutrino mode.
-  
-* [Fixed a bug](https://github.com/lightningnetwork/lnd/pull/9543) where
-  the payment might have been failed more than once and therefore overwriting
-  the failure reason and notifying the `SubscribeAllPayments` subscribers more
-  than once.
 
 * [Fixed](https://github.com/lightningnetwork/lnd/pull/9609) a bug that may
   cause `listunspent` to give inaccurate wallet UTXOs.
 
+* [Fixed](https://github.com/lightningnetwork/lnd/pull/9626) a bug where a
+keysend payment would not fail properly and only resolve after restart. Now
+keysend payment validation is stricter.
+
 # New Features
 
 * Add support for [archiving channel backup](https://github.com/lightningnetwork/lnd/pull/9232)
@@ -239,9 +234,6 @@ close transaction.
 * [The server](https://github.com/lightningnetwork/lnd/pull/9458) now allocates
  restricted slots for certain peers. This is configured by --num-restricted-slots.
 
-* [The bitcoin `testnet4` test network is now also
-  supported](https://github.com/lightningnetwork/lnd/pull/9620).
-
 ## RPC Updates
 
 * Some RPCs that previously just returned an empty response message now at least
@@ -284,10 +276,6 @@ close transaction.
   access the RPC server would give an error saying the wallet is encrypted. This
   is now fixed to show specifically which RPC server is missing.
 
-* [Enhanced](https://github.com/lightningnetwork/lnd/pull/9558)  
-  `listchaintxns` command with improved input validation, robust error handling,
-  tests, and updated documentation.
-
 ## Code Health
 
 * [Add retry logic](https://github.com/lightningnetwork/lnd/pull/8381) for
@@ -349,13 +337,6 @@ The underlying functionality between those two options remain the same.
 ## Breaking Changes
 ## Performance Improvements
 
-* Users can now [limit the total amount of
-bandwidth](https://github.com/lightningnetwork/lnd/pull/9607) that will be allocated to
-outbound gossip traffic via two new args: `--gossip.msg-rate-bytes` and
-`--gossip.msg-rate-burst`. The burst value should be set to the largest amount
-of bytes that can be transmitted in a go without rate limiting, and the rate to
-the on going rate we'll permit.
-
 * Log rotation can now use ZSTD
 
 * [Remove redundant 
@@ -484,7 +465,6 @@ the on going rate we'll permit.
 * Oliver Gugger
 * Olaoluwa Osuntokun
 * Pins
-* kornpow
 * Viktor Tigerström
 * Yong Yu
 * Ziggie
diff --git a/go.mod b/go.mod
index d1351bca0..98f35eef2 100644
--- a/go.mod
+++ b/go.mod
@@ -4,14 +4,14 @@ require (
 	github.com/NebulousLabs/go-upnp v0.0.0-20180202185039-29b680b06c82
 	github.com/Yawning/aez v0.0.0-20211027044916-e49e68abd344
 	github.com/andybalholm/brotli v1.0.4
-	github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6
+	github.com/btcsuite/btcd v0.24.3-0.20241210095828-e646d437e95b
 	github.com/btcsuite/btcd/btcec/v2 v2.3.4
 	github.com/btcsuite/btcd/btcutil v1.1.5
 	github.com/btcsuite/btcd/btcutil/psbt v1.1.8
 	github.com/btcsuite/btcd/chaincfg/chainhash v1.1.0
 	github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c
 	github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318
-	github.com/btcsuite/btcwallet v0.16.12
+	github.com/btcsuite/btcwallet v0.16.11
 	github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5
 	github.com/btcsuite/btcwallet/wallet/txrules v1.2.2
 	github.com/btcsuite/btcwallet/walletdb v1.4.4
@@ -31,7 +31,7 @@ require (
 	github.com/jessevdk/go-flags v1.4.0
 	github.com/jrick/logrotate v1.1.2
 	github.com/kkdai/bstream v1.0.0
-	github.com/lightninglabs/neutrino v0.16.1
+	github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd
 	github.com/lightninglabs/neutrino/cache v1.1.2
 	github.com/lightningnetwork/lightning-onion v1.2.1-0.20240712235311-98bd56499dfb
 	github.com/lightningnetwork/lnd/cert v1.2.2
@@ -64,7 +64,7 @@ require (
 	google.golang.org/protobuf v1.33.0
 	gopkg.in/macaroon-bakery.v2 v2.0.1
 	gopkg.in/macaroon.v2 v2.0.0
-	pgregory.net/rapid v1.2.0
+	pgregory.net/rapid v1.1.0
 )
 
 require (
diff --git a/go.sum b/go.sum
index a79d0f443..9c69f3fc6 100644
--- a/go.sum
+++ b/go.sum
@@ -73,8 +73,8 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
 github.com/btcsuite/btcd v0.20.1-beta/go.mod h1:wVuoA8VJLEcwgqHBwHmzLRazpKxTv13Px/pDuV7OomQ=
 github.com/btcsuite/btcd v0.22.0-beta.0.20220111032746-97732e52810c/go.mod h1:tjmYdS6MLJ5/s0Fj4DbLgSbDHbEqLJrtnHecBFkdz5M=
 github.com/btcsuite/btcd v0.23.5-0.20231215221805-96c9fd8078fd/go.mod h1:nm3Bko6zh6bWP60UxwoT5LzdGJsQJaPo6HjduXq9p6A=
-github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6 h1:8n9k3I7e8DkpdQ5YAP4j8ly/LSsbe6qX9vmVbrUGvVw=
-github.com/btcsuite/btcd v0.24.3-0.20250318170759-4f4ea81776d6/go.mod h1:OmM4kFtB0klaG/ZqT86rQiyw/1iyXlJgc3UHClPhhbs=
+github.com/btcsuite/btcd v0.24.3-0.20241210095828-e646d437e95b h1:VQoobSrWdxICuqFU3tKVu/Lzk7BTk9SsCgRr5dUvC70=
+github.com/btcsuite/btcd v0.24.3-0.20241210095828-e646d437e95b/go.mod h1:zHK7t7sw8XbsCkD64WePHE3r3k9/XoGAcf6mXV14c64=
 github.com/btcsuite/btcd/btcec/v2 v2.1.0/go.mod h1:2VzYrv4Gm4apmbVVsSq5bqf1Ec8v56E48Vt0Y/umPgA=
 github.com/btcsuite/btcd/btcec/v2 v2.1.3/go.mod h1:ctjw4H1kknNJmRN4iP1R7bTQ+v3GJkZBd6mui8ZsAZE=
 github.com/btcsuite/btcd/btcec/v2 v2.3.4 h1:3EJjcN70HCu/mwqlUsGK8GcNVyLVxFDlWurTXGPFfiQ=
@@ -95,8 +95,8 @@ github.com/btcsuite/btclog v0.0.0-20241003133417-09c4e92e319c/go.mod h1:w7xnGOhw
 github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318 h1:oCjIcinPt7XQ644MP/22JcjYEC84qRc3bRBH0d7Hhd4=
 github.com/btcsuite/btclog/v2 v2.0.1-0.20250110154127-3ae4bf1cb318/go.mod h1:XItGUfVOxotJL8kkuk2Hj3EVow5KCugXl3wWfQ6K0AE=
 github.com/btcsuite/btcutil v0.0.0-20190425235716-9e5f4b9a998d/go.mod h1:+5NJ2+qvTyV9exUAL/rxXi3DcLg2Ts+ymUAY5y4NvMg=
-github.com/btcsuite/btcwallet v0.16.12 h1:9SREKY892i1xTGlGLcu6x7O+WSQFn6+uQrSuskAOqh0=
-github.com/btcsuite/btcwallet v0.16.12/go.mod h1:jBn+ThFrx/QqW0nXiGvXtJytju4aVoW7C0hY4s/+9vo=
+github.com/btcsuite/btcwallet v0.16.11 h1:c8RgW/HO79if8P+KFQLQE00ITmFnLPKAzIy9FwxE37A=
+github.com/btcsuite/btcwallet v0.16.11/go.mod h1:1HJXYbjJzgumlnxOC2+ViR1U+gnHWoOn7WeK5OfY1eU=
 github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5 h1:Rr0njWI3r341nhSPesKQ2JF+ugDSzdPoeckS75SeDZk=
 github.com/btcsuite/btcwallet/wallet/txauthor v1.3.5/go.mod h1:+tXJ3Ym0nlQc/iHSwW1qzjmPs3ev+UVWMbGgfV1OZqU=
 github.com/btcsuite/btcwallet/wallet/txrules v1.2.2 h1:YEO+Lx1ZJJAtdRrjuhXjWrYsmAk26wLTlNzxt2q0lhk=
@@ -444,8 +444,8 @@ github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
 github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI/f/O0Avg7t8sqkPo78HFzjmeYFl6DPnc=
 github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk=
-github.com/lightninglabs/neutrino v0.16.1 h1:5Kz4ToxncEVkpKC6fwUjXKtFKJhuxlG3sBB3MdJTJjs=
-github.com/lightninglabs/neutrino v0.16.1/go.mod h1:L+5UAccpUdyM7yDgmQySgixf7xmwBgJtOfs/IP26jCs=
+github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd h1:D8aRocHpoCv43hL8egXEMYyPmyOiefFHZ66338KQB2s=
+github.com/lightninglabs/neutrino v0.16.1-0.20240425105051-602843d34ffd/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk=
 github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g=
 github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo=
 github.com/lightninglabs/protobuf-go-hex-display v1.30.0-hex-display h1:pRdza2wleRN1L2fJXd6ZoQ9ZegVFTAb2bOQfruJPKcY=
@@ -1076,8 +1076,8 @@ modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA=
 modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
 modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
 modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
-pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
-pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
+pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw=
+pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
 rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
 rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
 rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
diff --git a/lncfg/chain.go b/lncfg/chain.go
index cbcb9d0ee..36586ff3a 100644
--- a/lncfg/chain.go
+++ b/lncfg/chain.go
@@ -17,7 +17,6 @@ type Chain struct {
 
 	MainNet         bool     `long:"mainnet" description:"Use the main network"`
 	TestNet3        bool     `long:"testnet" description:"Use the test network"`
-	TestNet4        bool     `long:"testnet4" description:"Use the testnet4 test network"`
 	SimNet          bool     `long:"simnet" description:"Use the simulation test network"`
 	RegTest         bool     `long:"regtest" description:"Use the regression test network"`
 	SigNet          bool     `long:"signet" description:"Use the signet test network"`
diff --git a/lncfg/config.go b/lncfg/config.go
index 178ef203b..94ca02f46 100644
--- a/lncfg/config.go
+++ b/lncfg/config.go
@@ -108,14 +108,6 @@ func CleanAndExpandPath(path string) string {
 // NormalizeNetwork returns the common name of a network type used to create
 // file paths. This allows differently versioned networks to use the same path.
 func NormalizeNetwork(network string) string {
-	// The 4th testnet isn't the "default" yet, so we'll want to explicitly
-	// point that to a "testnet4" directory.
-	if network == "testnet4" {
-		return network
-	}
-
-	// We want to collapse "testnet3" and "testnet" to the same "testnet"
-	// directory.
 	if strings.HasPrefix(network, "testnet") {
 		return "testnet"
 	}
diff --git a/lncfg/gossip.go b/lncfg/gossip.go
index 37595e439..b8cab2c5f 100644
--- a/lncfg/gossip.go
+++ b/lncfg/gossip.go
@@ -5,7 +5,6 @@ import (
 	"time"
 
 	"github.com/lightningnetwork/lnd/discovery"
-	"github.com/lightningnetwork/lnd/lnwire"
 	"github.com/lightningnetwork/lnd/routing/route"
 )
 
@@ -33,10 +32,6 @@ type Gossip struct {
 	SubBatchDelay time.Duration `long:"sub-batch-delay" description:"The duration to wait before sending the next announcement batch if there are multiple. Use a small value if there are a lot announcements and they need to be broadcast quickly."`
 
 	AnnouncementConf uint32 `long:"announcement-conf" description:"The number of confirmations required before processing channel announcements."`
-
-	MsgRateBytes uint64 `long:"msg-rate-bytes" description:"The maximum number of bytes of gossip messages that will be sent per second. This is a global limit that applies to all peers."`
-
-	MsgBurstBytes uint64 `long:"msg-burst-bytes" description:"The maximum number of bytes of gossip messages that will be sent in a burst. This is a global limit that applies to all peers. This value should be set to something greater than 130 KB"`
 }
 
 // Parse the pubkeys for the pinned syncers.
@@ -63,11 +58,6 @@ func (g *Gossip) Validate() error {
 			"%v", g.AnnouncementConf, minAnnouncementConf)
 	}
 
-	if g.MsgBurstBytes < lnwire.MaxSliceLength {
-		return fmt.Errorf("msg-burst-bytes=%v must be at least %v",
-			g.MsgBurstBytes, lnwire.MaxSliceLength)
-	}
-
 	return nil
 }
 
diff --git a/lnd.go b/lnd.go
index e63ddc196..2b46e83c9 100644
--- a/lnd.go
+++ b/lnd.go
@@ -194,9 +194,6 @@ func Main(cfg *Config, lisCfg ListenerCfg, implCfg *ImplementationCfg,
 	case cfg.Bitcoin.TestNet3:
 		network = "testnet"
 
-	case cfg.Bitcoin.TestNet4:
-		network = "testnet4"
-
 	case cfg.Bitcoin.MainNet:
 		network = "mainnet"
 
diff --git a/lnrpc/lightning.pb.go b/lnrpc/lightning.pb.go
index c2ff05f3e..42ce54284 100644
--- a/lnrpc/lightning.pb.go
+++ b/lnrpc/lightning.pb.go
@@ -6265,8 +6265,8 @@ type GetInfoResponse struct {
 	SyncedToChain bool `protobuf:"varint,9,opt,name=synced_to_chain,json=syncedToChain,proto3" json:"synced_to_chain,omitempty"`
 	// Whether we consider ourselves synced with the public channel graph.
 	SyncedToGraph bool `protobuf:"varint,18,opt,name=synced_to_graph,json=syncedToGraph,proto3" json:"synced_to_graph,omitempty"`
-	// Whether the current node is connected to testnet or testnet4. This field is
-	// deprecated and the network field should be used instead.
+	// Whether the current node is connected to testnet. This field is
+	// deprecated and the network field should be used instead
 	//
 	// Deprecated: Marked as deprecated in lightning.proto.
 	Testnet bool `protobuf:"varint,10,opt,name=testnet,proto3" json:"testnet,omitempty"`
diff --git a/lnrpc/lightning.proto b/lnrpc/lightning.proto
index ca40e4b4c..2de09da1f 100644
--- a/lnrpc/lightning.proto
+++ b/lnrpc/lightning.proto
@@ -2008,8 +2008,8 @@ message GetInfoResponse {
     bool synced_to_graph = 18;
 
     /*
-    Whether the current node is connected to testnet or testnet4. This field is
-    deprecated and the network field should be used instead.
+    Whether the current node is connected to testnet. This field is
+    deprecated and the network field should be used instead
     */
     bool testnet = 10 [deprecated = true];
 
diff --git a/lnrpc/lightning.swagger.json b/lnrpc/lightning.swagger.json
index 69478fc94..c1a5ab4b4 100644
--- a/lnrpc/lightning.swagger.json
+++ b/lnrpc/lightning.swagger.json
@@ -5196,7 +5196,7 @@
         },
         "testnet": {
           "type": "boolean",
-          "description": "Whether the current node is connected to testnet or testnet4. This field is\ndeprecated and the network field should be used instead."
+          "title": "Whether the current node is connected to testnet. This field is\ndeprecated and the network field should be used instead"
         },
         "chains": {
           "type": "array",
diff --git a/lnrpc/routerrpc/router_backend.go b/lnrpc/routerrpc/router_backend.go
index eafa78724..59d933648 100644
--- a/lnrpc/routerrpc/router_backend.go
+++ b/lnrpc/routerrpc/router_backend.go
@@ -884,6 +884,18 @@ func (r *RouterBackend) extractIntentFromSendRequest(
 	}
 	payIntent.DestCustomRecords = customRecords
 
+	// Keysend payments do not support MPP payments.
+	//
+	// NOTE: There is no need to validate the `MaxParts` value here because
+	// it is set to 1 somewhere else in case it's a keysend payment.
+	if customRecords.IsKeysend() {
+		if payIntent.MaxShardAmt != nil {
+			return nil, errors.New("keysend payments cannot " +
+				"specify a max shard amount - MPP not " +
+				"supported with keysend payments")
+		}
+	}
+
 	firstHopRecords := lnwire.CustomRecords(rpcPayReq.FirstHopCustomRecords)
 	if err := firstHopRecords.Validate(); err != nil {
 		return nil, err
diff --git a/lnrpc/routerrpc/router_server.go b/lnrpc/routerrpc/router_server.go
index 58fa23622..818464be7 100644
--- a/lnrpc/routerrpc/router_server.go
+++ b/lnrpc/routerrpc/router_server.go
@@ -519,7 +519,7 @@ func (s *Server) probePaymentRequest(ctx context.Context, paymentRequest string,
 		return nil, err
 	}
 
-	if payReq.MilliSat == nil || *payReq.MilliSat <= 0 {
+	if *payReq.MilliSat <= 0 {
 		return nil, errors.New("payment request amount must be " +
 			"greater than 0")
 	}
diff --git a/lnwire/accept_channel.go b/lnwire/accept_channel.go
index afb2f1412..e45bcf9ab 100644
--- a/lnwire/accept_channel.go
+++ b/lnwire/accept_channel.go
@@ -128,10 +128,6 @@ type AcceptChannel struct {
 // interface.
 var _ Message = (*AcceptChannel)(nil)
 
-// A compile time check to ensure AcceptChannel implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*AcceptChannel)(nil)
-
 // Encode serializes the target AcceptChannel into the passed io.Writer
 // implementation. Serialization will observe the rules defined by the passed
 // protocol version.
@@ -285,10 +281,3 @@ func (a *AcceptChannel) Decode(r io.Reader, pver uint32) error {
 func (a *AcceptChannel) MsgType() MessageType {
 	return MsgAcceptChannel
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *AcceptChannel) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
diff --git a/lnwire/announcement_signatures.go b/lnwire/announcement_signatures.go
index cf8f68be5..a2bc21f39 100644
--- a/lnwire/announcement_signatures.go
+++ b/lnwire/announcement_signatures.go
@@ -47,10 +47,6 @@ type AnnounceSignatures1 struct {
 // lnwire.Message interface.
 var _ Message = (*AnnounceSignatures1)(nil)
 
-// A compile time check to ensure AnnounceSignatures1 implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*AnnounceSignatures1)(nil)
-
 // A compile time check to ensure AnnounceSignatures1 implements the
 // lnwire.AnnounceSignatures interface.
 var _ AnnounceSignatures = (*AnnounceSignatures1)(nil)
@@ -101,13 +97,6 @@ func (a *AnnounceSignatures1) MsgType() MessageType {
 	return MsgAnnounceSignatures
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *AnnounceSignatures1) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
-
 // SCID returns the ShortChannelID of the channel.
 //
 // This is part of the lnwire.AnnounceSignatures interface.
diff --git a/lnwire/announcement_signatures_2.go b/lnwire/announcement_signatures_2.go
index 6e893dafd..a10447032 100644
--- a/lnwire/announcement_signatures_2.go
+++ b/lnwire/announcement_signatures_2.go
@@ -41,10 +41,6 @@ type AnnounceSignatures2 struct {
 // lnwire.Message interface.
 var _ Message = (*AnnounceSignatures2)(nil)
 
-// A compile time check to ensure AnnounceSignatures2 implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*AnnounceSignatures2)(nil)
-
 // Decode deserializes a serialized AnnounceSignatures2 stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -86,13 +82,6 @@ func (a *AnnounceSignatures2) MsgType() MessageType {
 	return MsgAnnounceSignatures2
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *AnnounceSignatures2) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
-
 // SCID returns the ShortChannelID of the channel.
 //
 // NOTE: this is part of the AnnounceSignatures interface.
diff --git a/lnwire/channel_announcement.go b/lnwire/channel_announcement.go
index 0a3989abb..ed4c5b97e 100644
--- a/lnwire/channel_announcement.go
+++ b/lnwire/channel_announcement.go
@@ -62,10 +62,6 @@ type ChannelAnnouncement1 struct {
 // lnwire.Message interface.
 var _ Message = (*ChannelAnnouncement1)(nil)
 
-// A compile time check to ensure ChannelAnnouncement1 implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ChannelAnnouncement1)(nil)
-
 // Decode deserializes a serialized ChannelAnnouncement stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -147,13 +143,6 @@ func (a *ChannelAnnouncement1) MsgType() MessageType {
 	return MsgChannelAnnouncement
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *ChannelAnnouncement1) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
-
 // DataToSign is used to retrieve part of the announcement message which should
 // be signed.
 func (a *ChannelAnnouncement1) DataToSign() ([]byte, error) {
diff --git a/lnwire/channel_announcement_2.go b/lnwire/channel_announcement_2.go
index 57b3a24b8..074e7d084 100644
--- a/lnwire/channel_announcement_2.go
+++ b/lnwire/channel_announcement_2.go
@@ -194,21 +194,10 @@ func (c *ChannelAnnouncement2) MsgType() MessageType {
 	return MsgChannelAnnouncement2
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ChannelAnnouncement2) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // A compile time check to ensure ChannelAnnouncement2 implements the
 // lnwire.Message interface.
 var _ Message = (*ChannelAnnouncement2)(nil)
 
-// A compile time check to ensure ChannelAnnouncement2 implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ChannelAnnouncement2)(nil)
-
 // Node1KeyBytes returns the bytes representing the public key of node 1 in the
 // channel.
 //
diff --git a/lnwire/channel_ready.go b/lnwire/channel_ready.go
index f388db1d1..912a068bd 100644
--- a/lnwire/channel_ready.go
+++ b/lnwire/channel_ready.go
@@ -63,10 +63,6 @@ func NewChannelReady(cid ChannelID, npcp *btcec.PublicKey) *ChannelReady {
 // interface.
 var _ Message = (*ChannelReady)(nil)
 
-// A compile time check to ensure ChannelReady implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ChannelReady)(nil)
-
 // Decode deserializes the serialized ChannelReady message stored in the
 // passed io.Reader into the target ChannelReady using the deserialization
 // rules defined by the passed protocol version.
@@ -174,10 +170,3 @@ func (c *ChannelReady) Encode(w *bytes.Buffer, _ uint32) error {
 func (c *ChannelReady) MsgType() MessageType {
 	return MsgChannelReady
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ChannelReady) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/channel_reestablish.go b/lnwire/channel_reestablish.go
index f26a2fc5d..577379623 100644
--- a/lnwire/channel_reestablish.go
+++ b/lnwire/channel_reestablish.go
@@ -99,10 +99,6 @@ type ChannelReestablish struct {
 // lnwire.Message interface.
 var _ Message = (*ChannelReestablish)(nil)
 
-// A compile time check to ensure ChannelReestablish implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ChannelReestablish)(nil)
-
 // Encode serializes the target ChannelReestablish into the passed io.Writer
 // observing the protocol version specified.
 //
@@ -238,10 +234,3 @@ func (a *ChannelReestablish) Decode(r io.Reader, pver uint32) error {
 func (a *ChannelReestablish) MsgType() MessageType {
 	return MsgChannelReestablish
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *ChannelReestablish) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
diff --git a/lnwire/channel_update.go b/lnwire/channel_update.go
index 88f981671..33cc3bff0 100644
--- a/lnwire/channel_update.go
+++ b/lnwire/channel_update.go
@@ -124,10 +124,6 @@ type ChannelUpdate1 struct {
 // interface.
 var _ Message = (*ChannelUpdate1)(nil)
 
-// A compile time check to ensure ChannelUpdate1 implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ChannelUpdate1)(nil)
-
 // Decode deserializes a serialized ChannelUpdate stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -371,10 +367,3 @@ func (a *ChannelUpdate1) SetSCID(scid ShortChannelID) {
 // A compile time assertion to ensure ChannelUpdate1 implements the
 // ChannelUpdate interface.
 var _ ChannelUpdate = (*ChannelUpdate1)(nil)
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *ChannelUpdate1) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
diff --git a/lnwire/channel_update_2.go b/lnwire/channel_update_2.go
index 56f7edf6b..b41f2f29f 100644
--- a/lnwire/channel_update_2.go
+++ b/lnwire/channel_update_2.go
@@ -241,13 +241,6 @@ func (c *ChannelUpdate2) MsgType() MessageType {
 	return MsgChannelUpdate2
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ChannelUpdate2) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 func (c *ChannelUpdate2) ExtraData() ExtraOpaqueData {
 	return c.ExtraOpaqueData
 }
diff --git a/lnwire/closing_complete.go b/lnwire/closing_complete.go
index 7980ef1ee..4d390fd6f 100644
--- a/lnwire/closing_complete.go
+++ b/lnwire/closing_complete.go
@@ -169,17 +169,6 @@ func (c *ClosingComplete) MsgType() MessageType {
 	return MsgClosingComplete
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ClosingComplete) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // A compile time check to ensure ClosingComplete implements the lnwire.Message
 // interface.
 var _ Message = (*ClosingComplete)(nil)
-
-// A compile time check to ensure ClosingComplete implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ClosingComplete)(nil)
diff --git a/lnwire/closing_sig.go b/lnwire/closing_sig.go
index 94a356066..2c73fa720 100644
--- a/lnwire/closing_sig.go
+++ b/lnwire/closing_sig.go
@@ -107,17 +107,6 @@ func (c *ClosingSig) MsgType() MessageType {
 	return MsgClosingSig
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ClosingSig) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // A compile time check to ensure ClosingSig implements the lnwire.Message
 // interface.
 var _ Message = (*ClosingSig)(nil)
-
-// A compile time check to ensure ClosingSig implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ClosingSig)(nil)
diff --git a/lnwire/closing_signed.go b/lnwire/closing_signed.go
index c247cfe0a..08b5bb6a7 100644
--- a/lnwire/closing_signed.go
+++ b/lnwire/closing_signed.go
@@ -59,10 +59,6 @@ func NewClosingSigned(cid ChannelID, fs btcutil.Amount,
 // interface.
 var _ Message = (*ClosingSigned)(nil)
 
-// A compile time check to ensure ClosingSigned implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ClosingSigned)(nil)
-
 // Decode deserializes a serialized ClosingSigned message stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -134,10 +130,3 @@ func (c *ClosingSigned) Encode(w *bytes.Buffer, pver uint32) error {
 func (c *ClosingSigned) MsgType() MessageType {
 	return MsgClosingSigned
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ClosingSigned) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/commit_sig.go b/lnwire/commit_sig.go
index 600ff81e7..3a475e71f 100644
--- a/lnwire/commit_sig.go
+++ b/lnwire/commit_sig.go
@@ -64,10 +64,6 @@ func NewCommitSig() *CommitSig {
 // interface.
 var _ Message = (*CommitSig)(nil)
 
-// A compile time check to ensure CommitSig implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*CommitSig)(nil)
-
 // Decode deserializes a serialized CommitSig message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -155,10 +151,3 @@ func (c *CommitSig) MsgType() MessageType {
 func (c *CommitSig) TargetChanID() ChannelID {
 	return c.ChanID
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *CommitSig) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/custom.go b/lnwire/custom.go
index 740c23a14..232a8be52 100644
--- a/lnwire/custom.go
+++ b/lnwire/custom.go
@@ -69,14 +69,10 @@ type Custom struct {
 	Data []byte
 }
 
-// A compile time check to ensure Custom implements the lnwire.Message
+// A compile time check to ensure FundingCreated implements the lnwire.Message
 // interface.
 var _ Message = (*Custom)(nil)
 
-// A compile time check to ensure Custom implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Custom)(nil)
-
 // NewCustom instantiates a new custom message.
 func NewCustom(msgType MessageType, data []byte) (*Custom, error) {
 	if msgType < CustomTypeStart && !IsCustomOverride(msgType) {
@@ -121,10 +117,3 @@ func (c *Custom) Decode(r io.Reader, pver uint32) error {
 func (c *Custom) MsgType() MessageType {
 	return c.Type
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *Custom) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/dyn_ack.go b/lnwire/dyn_ack.go
index 1cc57e955..d477461e7 100644
--- a/lnwire/dyn_ack.go
+++ b/lnwire/dyn_ack.go
@@ -41,10 +41,6 @@ type DynAck struct {
 // interface.
 var _ Message = (*DynAck)(nil)
 
-// A compile time check to ensure DynAck implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*DynAck)(nil)
-
 // Encode serializes the target DynAck into the passed io.Writer. Serialization
 // will observe the rules defined by the passed protocol version.
 //
@@ -140,10 +136,3 @@ func (da *DynAck) Decode(r io.Reader, _ uint32) error {
 func (da *DynAck) MsgType() MessageType {
 	return MsgDynAck
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (da *DynAck) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(da)
-}
diff --git a/lnwire/dyn_propose.go b/lnwire/dyn_propose.go
index cc19ec394..394fff6f3 100644
--- a/lnwire/dyn_propose.go
+++ b/lnwire/dyn_propose.go
@@ -105,10 +105,6 @@ type DynPropose struct {
 // interface.
 var _ Message = (*DynPropose)(nil)
 
-// A compile time check to ensure DynPropose implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*DynPropose)(nil)
-
 // Encode serializes the target DynPropose into the passed io.Writer.
 // Serialization will observe the rules defined by the passed protocol version.
 //
@@ -321,10 +317,3 @@ func (dp *DynPropose) Decode(r io.Reader, _ uint32) error {
 func (dp *DynPropose) MsgType() MessageType {
 	return MsgDynPropose
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (dp *DynPropose) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(dp)
-}
diff --git a/lnwire/dyn_reject.go b/lnwire/dyn_reject.go
index 51cb8cee2..2c6484424 100644
--- a/lnwire/dyn_reject.go
+++ b/lnwire/dyn_reject.go
@@ -30,10 +30,6 @@ type DynReject struct {
 // interface.
 var _ Message = (*DynReject)(nil)
 
-// A compile time check to ensure DynReject implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*DynReject)(nil)
-
 // Encode serializes the target DynReject into the passed io.Writer.
 // Serialization will observe the rules defined by the passed protocol version.
 //
@@ -78,10 +74,3 @@ func (dr *DynReject) Decode(r io.Reader, _ uint32) error {
 func (dr *DynReject) MsgType() MessageType {
 	return MsgDynReject
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (dr *DynReject) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(dr)
-}
diff --git a/lnwire/error.go b/lnwire/error.go
index fcc22ef24..120dfc950 100644
--- a/lnwire/error.go
+++ b/lnwire/error.go
@@ -70,10 +70,6 @@ func NewError() *Error {
 // interface.
 var _ Message = (*Error)(nil)
 
-// A compile time check to ensure Error implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Error)(nil)
-
 // Error returns the string representation to Error.
 //
 // NOTE: Satisfies the error interface.
@@ -117,13 +113,6 @@ func (c *Error) MsgType() MessageType {
 	return MsgError
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *Error) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // isASCII is a helper method that checks whether all bytes in `data` would be
 // printable ASCII characters if interpreted as a string.
 func isASCII(data []byte) bool {
diff --git a/lnwire/funding_created.go b/lnwire/funding_created.go
index 82d0ff87c..86aa0bb40 100644
--- a/lnwire/funding_created.go
+++ b/lnwire/funding_created.go
@@ -44,10 +44,6 @@ type FundingCreated struct {
 // interface.
 var _ Message = (*FundingCreated)(nil)
 
-// A compile time check to ensure FundingCreated implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*FundingCreated)(nil)
-
 // Encode serializes the target FundingCreated into the passed io.Writer
 // implementation. Serialization will observe the rules defined by the passed
 // protocol version.
@@ -121,10 +117,3 @@ func (f *FundingCreated) Decode(r io.Reader, pver uint32) error {
 func (f *FundingCreated) MsgType() MessageType {
 	return MsgFundingCreated
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (f *FundingCreated) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(f)
-}
diff --git a/lnwire/funding_signed.go b/lnwire/funding_signed.go
index 182e4bbde..2dd62e177 100644
--- a/lnwire/funding_signed.go
+++ b/lnwire/funding_signed.go
@@ -36,10 +36,6 @@ type FundingSigned struct {
 // interface.
 var _ Message = (*FundingSigned)(nil)
 
-// A compile time check to ensure FundingSigned implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*FundingSigned)(nil)
-
 // Encode serializes the target FundingSigned into the passed io.Writer
 // implementation. Serialization will observe the rules defined by the passed
 // protocol version.
@@ -107,10 +103,3 @@ func (f *FundingSigned) Decode(r io.Reader, pver uint32) error {
 func (f *FundingSigned) MsgType() MessageType {
 	return MsgFundingSigned
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (f *FundingSigned) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(f)
-}
diff --git a/lnwire/gossip_timestamp_range.go b/lnwire/gossip_timestamp_range.go
index 45ff1f939..7b628752a 100644
--- a/lnwire/gossip_timestamp_range.go
+++ b/lnwire/gossip_timestamp_range.go
@@ -58,10 +58,6 @@ func NewGossipTimestampRange() *GossipTimestampRange {
 // lnwire.Message interface.
 var _ Message = (*GossipTimestampRange)(nil)
 
-// A compile time check to ensure GossipTimestampRange implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*GossipTimestampRange)(nil)
-
 // Decode deserializes a serialized GossipTimestampRange message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -147,10 +143,3 @@ func (g *GossipTimestampRange) Encode(w *bytes.Buffer, pver uint32) error {
 func (g *GossipTimestampRange) MsgType() MessageType {
 	return MsgGossipTimestampRange
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (g *GossipTimestampRange) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(g)
-}
diff --git a/lnwire/init_message.go b/lnwire/init_message.go
index b88891b08..dbbddea2b 100644
--- a/lnwire/init_message.go
+++ b/lnwire/init_message.go
@@ -43,10 +43,6 @@ func NewInitMessage(gf *RawFeatureVector, f *RawFeatureVector) *Init {
 // interface.
 var _ Message = (*Init)(nil)
 
-// A compile time check to ensure Init implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Init)(nil)
-
 // Decode deserializes a serialized Init message stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -82,10 +78,3 @@ func (msg *Init) Encode(w *bytes.Buffer, pver uint32) error {
 func (msg *Init) MsgType() MessageType {
 	return MsgInit
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (msg *Init) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(msg)
-}
diff --git a/lnwire/kickoff_sig.go b/lnwire/kickoff_sig.go
index b9c4c206a..3e46db453 100644
--- a/lnwire/kickoff_sig.go
+++ b/lnwire/kickoff_sig.go
@@ -27,10 +27,6 @@ type KickoffSig struct {
 // interface.
 var _ Message = (*KickoffSig)(nil)
 
-// A compile time check to ensure KickoffSig implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*KickoffSig)(nil)
-
 // Encode serializes the target KickoffSig into the passed bytes.Buffer
 // observing the specified protocol version.
 //
@@ -58,10 +54,3 @@ func (ks *KickoffSig) Decode(r io.Reader, _ uint32) error {
 //
 // This is part of the lnwire.Message interface.
 func (ks *KickoffSig) MsgType() MessageType { return MsgKickoffSig }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (ks *KickoffSig) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(ks)
-}
diff --git a/lnwire/message.go b/lnwire/message.go
index ea480075a..68b09692e 100644
--- a/lnwire/message.go
+++ b/lnwire/message.go
@@ -12,10 +12,6 @@ import (
 	"io"
 )
 
-// MessageTypeSize is the size in bytes of the message type field in the header
-// of all messages.
-const MessageTypeSize = 2
-
 // MessageType is the unique 2 byte big-endian integer that indicates the type
 // of message on the wire. All messages have a very simple header which
 // consists simply of 2-byte message type. We omit a length field, and checksum
@@ -65,11 +61,6 @@ const (
 	MsgChannelAnnouncement2                = 267
 	MsgChannelUpdate2                      = 271
 	MsgKickoffSig                          = 777
-
-	// MsgEnd defines the end of the official message range of the protocol.
-	// If a new message is added beyond this message, then this should be
-	// modified.
-	MsgEnd = 778
 )
 
 // IsChannelUpdate is a filter function that discerns channel update messages
@@ -243,31 +234,6 @@ type LinkUpdater interface {
 	TargetChanID() ChannelID
 }
 
-// SizeableMessage is an interface that extends the base Message interface with
-// a method to calculate the serialized size of a message.
-type SizeableMessage interface {
-	Message
-
-	// SerializedSize returns the serialized size of the message in bytes.
-	// The returned size includes the message type header bytes.
-	SerializedSize() (uint32, error)
-}
-
-// MessageSerializedSize calculates the serialized size of a message in bytes.
-// This is a helper function that can be used by all message types to implement
-// the SerializedSize method.
-func MessageSerializedSize(msg Message) (uint32, error) {
-	var buf bytes.Buffer
-
-	// Encode the message to the buffer.
-	if err := msg.Encode(&buf, 0); err != nil {
-		return 0, err
-	}
-
-	// Add the size of the message type.
-	return uint32(buf.Len()) + MessageTypeSize, nil
-}
-
 // makeEmptyMessage creates a new empty message of the proper concrete type
 // based on the passed message type.
 func makeEmptyMessage(msgType MessageType) (Message, error) {
@@ -371,12 +337,6 @@ func makeEmptyMessage(msgType MessageType) (Message, error) {
 	return msg, nil
 }
 
-// MakeEmptyMessage creates a new empty message of the proper concrete type
-// based on the passed message type. This is exported to be used in tests.
-func MakeEmptyMessage(msgType MessageType) (Message, error) {
-	return makeEmptyMessage(msgType)
-}
-
 // WriteMessage writes a lightning Message to a buffer including the necessary
 // header information and returns the number of bytes written. If any error is
 // encountered, the buffer passed will be reset to its original state since we
diff --git a/lnwire/node_announcement.go b/lnwire/node_announcement.go
index 5ba2d7a1d..4f1620281 100644
--- a/lnwire/node_announcement.go
+++ b/lnwire/node_announcement.go
@@ -104,10 +104,6 @@ type NodeAnnouncement struct {
 // lnwire.Message interface.
 var _ Message = (*NodeAnnouncement)(nil)
 
-// A compile time check to ensure NodeAnnouncement implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*NodeAnnouncement)(nil)
-
 // Decode deserializes a serialized NodeAnnouncement stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -206,10 +202,3 @@ func (a *NodeAnnouncement) DataToSign() ([]byte, error) {
 
 	return buf.Bytes(), nil
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (a *NodeAnnouncement) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(a)
-}
diff --git a/lnwire/open_channel.go b/lnwire/open_channel.go
index 1751f748b..9694290f7 100644
--- a/lnwire/open_channel.go
+++ b/lnwire/open_channel.go
@@ -164,10 +164,6 @@ type OpenChannel struct {
 // interface.
 var _ Message = (*OpenChannel)(nil)
 
-// A compile time check to ensure OpenChannel implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*OpenChannel)(nil)
-
 // Encode serializes the target OpenChannel into the passed io.Writer
 // implementation. Serialization will observe the rules defined by the passed
 // protocol version.
@@ -339,10 +335,3 @@ func (o *OpenChannel) Decode(r io.Reader, pver uint32) error {
 func (o *OpenChannel) MsgType() MessageType {
 	return MsgOpenChannel
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (o *OpenChannel) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(o)
-}
diff --git a/lnwire/ping.go b/lnwire/ping.go
index 230187b84..a21f2fa8b 100644
--- a/lnwire/ping.go
+++ b/lnwire/ping.go
@@ -33,10 +33,6 @@ func NewPing(numBytes uint16) *Ping {
 // A compile time check to ensure Ping implements the lnwire.Message interface.
 var _ Message = (*Ping)(nil)
 
-// A compile time check to ensure Ping implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Ping)(nil)
-
 // Decode deserializes a serialized Ping message stored in the passed io.Reader
 // observing the specified protocol version.
 //
@@ -73,10 +69,3 @@ func (p *Ping) Encode(w *bytes.Buffer, pver uint32) error {
 func (p *Ping) MsgType() MessageType {
 	return MsgPing
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (p *Ping) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(p)
-}
diff --git a/lnwire/pong.go b/lnwire/pong.go
index b5fca24c9..3ab80d70f 100644
--- a/lnwire/pong.go
+++ b/lnwire/pong.go
@@ -39,10 +39,6 @@ func NewPong(pongBytes []byte) *Pong {
 // A compile time check to ensure Pong implements the lnwire.Message interface.
 var _ Message = (*Pong)(nil)
 
-// A compile time check to ensure Pong implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Pong)(nil)
-
 // Decode deserializes a serialized Pong message stored in the passed io.Reader
 // observing the specified protocol version.
 //
@@ -68,10 +64,3 @@ func (p *Pong) Encode(w *bytes.Buffer, pver uint32) error {
 func (p *Pong) MsgType() MessageType {
 	return MsgPong
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (p *Pong) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(p)
-}
diff --git a/lnwire/query_channel_range.go b/lnwire/query_channel_range.go
index c816a0050..1e0dcb0fa 100644
--- a/lnwire/query_channel_range.go
+++ b/lnwire/query_channel_range.go
@@ -49,10 +49,6 @@ func NewQueryChannelRange() *QueryChannelRange {
 // lnwire.Message interface.
 var _ Message = (*QueryChannelRange)(nil)
 
-// A compile time check to ensure QueryChannelRange implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*QueryChannelRange)(nil)
-
 // Decode deserializes a serialized QueryChannelRange message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -125,14 +121,6 @@ func (q *QueryChannelRange) MsgType() MessageType {
 	return MsgQueryChannelRange
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (q *QueryChannelRange) SerializedSize() (uint32, error) {
-	msgCpy := *q
-	return MessageSerializedSize(&msgCpy)
-}
-
 // LastBlockHeight returns the last block height covered by the range of a
 // QueryChannelRange message.
 func (q *QueryChannelRange) LastBlockHeight() uint32 {
diff --git a/lnwire/query_short_chan_ids.go b/lnwire/query_short_chan_ids.go
index 37a73ab7c..cb1bfa22c 100644
--- a/lnwire/query_short_chan_ids.go
+++ b/lnwire/query_short_chan_ids.go
@@ -91,10 +91,6 @@ func NewQueryShortChanIDs(h chainhash.Hash, e QueryEncoding,
 // lnwire.Message interface.
 var _ Message = (*QueryShortChanIDs)(nil)
 
-// A compile time check to ensure QueryShortChanIDs implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*QueryShortChanIDs)(nil)
-
 // Decode deserializes a serialized QueryShortChanIDs message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -431,10 +427,3 @@ func encodeShortChanIDs(w *bytes.Buffer, encodingType QueryEncoding,
 func (q *QueryShortChanIDs) MsgType() MessageType {
 	return MsgQueryShortChanIDs
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (q *QueryShortChanIDs) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(q)
-}
diff --git a/lnwire/reply_channel_range.go b/lnwire/reply_channel_range.go
index c3a744ebd..591fc2bd6 100644
--- a/lnwire/reply_channel_range.go
+++ b/lnwire/reply_channel_range.go
@@ -70,10 +70,6 @@ func NewReplyChannelRange() *ReplyChannelRange {
 // lnwire.Message interface.
 var _ Message = (*ReplyChannelRange)(nil)
 
-// A compile time check to ensure ReplyChannelRange implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ReplyChannelRange)(nil)
-
 // Decode deserializes a serialized ReplyChannelRange message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -227,10 +223,3 @@ func (c *ReplyChannelRange) LastBlockHeight() uint32 {
 	}
 	return uint32(lastBlockHeight)
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ReplyChannelRange) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/reply_short_chan_ids_end.go b/lnwire/reply_short_chan_ids_end.go
index 2e50d840f..53676f719 100644
--- a/lnwire/reply_short_chan_ids_end.go
+++ b/lnwire/reply_short_chan_ids_end.go
@@ -39,10 +39,6 @@ func NewReplyShortChanIDsEnd() *ReplyShortChanIDsEnd {
 // lnwire.Message interface.
 var _ Message = (*ReplyShortChanIDsEnd)(nil)
 
-// A compile time check to ensure ReplyShortChanIDsEnd implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*ReplyShortChanIDsEnd)(nil)
-
 // Decode deserializes a serialized ReplyShortChanIDsEnd message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -78,10 +74,3 @@ func (c *ReplyShortChanIDsEnd) Encode(w *bytes.Buffer, pver uint32) error {
 func (c *ReplyShortChanIDsEnd) MsgType() MessageType {
 	return MsgReplyShortChanIDsEnd
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *ReplyShortChanIDsEnd) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/revoke_and_ack.go b/lnwire/revoke_and_ack.go
index 3c9775c99..9dca1631a 100644
--- a/lnwire/revoke_and_ack.go
+++ b/lnwire/revoke_and_ack.go
@@ -55,10 +55,6 @@ func NewRevokeAndAck() *RevokeAndAck {
 // interface.
 var _ Message = (*RevokeAndAck)(nil)
 
-// A compile time check to ensure RevokeAndAck implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*RevokeAndAck)(nil)
-
 // Decode deserializes a serialized RevokeAndAck message stored in the
 // passed io.Reader observing the specified protocol version.
 //
@@ -140,10 +136,3 @@ func (c *RevokeAndAck) MsgType() MessageType {
 func (c *RevokeAndAck) TargetChanID() ChannelID {
 	return c.ChanID
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *RevokeAndAck) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/lnwire/shutdown.go b/lnwire/shutdown.go
index 28df9a4ca..b9899fcfb 100644
--- a/lnwire/shutdown.go
+++ b/lnwire/shutdown.go
@@ -61,10 +61,6 @@ func NewShutdown(cid ChannelID, addr DeliveryAddress) *Shutdown {
 // interface.
 var _ Message = (*Shutdown)(nil)
 
-// A compile-time check to ensure Shutdown implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Shutdown)(nil)
-
 // Decode deserializes a serialized Shutdown from the passed io.Reader,
 // observing the specified protocol version.
 //
@@ -137,10 +133,3 @@ func (s *Shutdown) Encode(w *bytes.Buffer, pver uint32) error {
 func (s *Shutdown) MsgType() MessageType {
 	return MsgShutdown
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (s *Shutdown) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(s)
-}
diff --git a/lnwire/stfu.go b/lnwire/stfu.go
index 8e57739d5..a052a517a 100644
--- a/lnwire/stfu.go
+++ b/lnwire/stfu.go
@@ -24,10 +24,6 @@ type Stfu struct {
 // A compile time check to ensure Stfu implements the lnwire.Message interface.
 var _ Message = (*Stfu)(nil)
 
-// A compile time check to ensure Stfu implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Stfu)(nil)
-
 // Encode serializes the target Stfu into the passed io.Writer.
 // Serialization will observe the rules defined by the passed protocol version.
 //
@@ -72,13 +68,6 @@ func (s *Stfu) MsgType() MessageType {
 	return MsgStfu
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (s *Stfu) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(s)
-}
-
 // A compile time check to ensure Stfu implements the
 // lnwire.LinkUpdater interface.
 var _ LinkUpdater = (*Stfu)(nil)
diff --git a/lnwire/update_add_htlc.go b/lnwire/update_add_htlc.go
index e627dbf4e..4873cd84b 100644
--- a/lnwire/update_add_htlc.go
+++ b/lnwire/update_add_htlc.go
@@ -212,14 +212,3 @@ func (c *UpdateAddHTLC) MsgType() MessageType {
 func (c *UpdateAddHTLC) TargetChanID() ChannelID {
 	return c.ChanID
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *UpdateAddHTLC) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
-// A compile time check to ensure UpdateAddHTLC implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*UpdateAddHTLC)(nil)
diff --git a/lnwire/update_fail_htlc.go b/lnwire/update_fail_htlc.go
index 1d26444ba..8cd9c7687 100644
--- a/lnwire/update_fail_htlc.go
+++ b/lnwire/update_fail_htlc.go
@@ -38,10 +38,6 @@ type UpdateFailHTLC struct {
 // interface.
 var _ Message = (*UpdateFailHTLC)(nil)
 
-// A compile time check to ensure UpdateFailHTLC implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*UpdateFailHTLC)(nil)
-
 // Decode deserializes a serialized UpdateFailHTLC message stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -55,8 +51,8 @@ func (c *UpdateFailHTLC) Decode(r io.Reader, pver uint32) error {
 	)
 }
 
-// Encode serializes the target UpdateFailHTLC into the passed io.Writer
-// observing the protocol version specified.
+// Encode serializes the target UpdateFailHTLC into the passed io.Writer observing
+// the protocol version specified.
 //
 // This is part of the lnwire.Message interface.
 func (c *UpdateFailHTLC) Encode(w *bytes.Buffer, pver uint32) error {
@@ -83,13 +79,6 @@ func (c *UpdateFailHTLC) MsgType() MessageType {
 	return MsgUpdateFailHTLC
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *UpdateFailHTLC) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // TargetChanID returns the channel id of the link for which this message is
 // intended.
 //
diff --git a/lnwire/update_fail_malformed_htlc.go b/lnwire/update_fail_malformed_htlc.go
index bc9bd9aba..f28107a9a 100644
--- a/lnwire/update_fail_malformed_htlc.go
+++ b/lnwire/update_fail_malformed_htlc.go
@@ -36,10 +36,6 @@ type UpdateFailMalformedHTLC struct {
 // lnwire.Message interface.
 var _ Message = (*UpdateFailMalformedHTLC)(nil)
 
-// A compile time check to ensure UpdateFailMalformedHTLC implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*UpdateFailMalformedHTLC)(nil)
-
 // Decode deserializes a serialized UpdateFailMalformedHTLC message stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -88,13 +84,6 @@ func (c *UpdateFailMalformedHTLC) MsgType() MessageType {
 	return MsgUpdateFailMalformedHTLC
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *UpdateFailMalformedHTLC) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // TargetChanID returns the channel id of the link for which this message is
 // intended.
 //
diff --git a/lnwire/update_fee.go b/lnwire/update_fee.go
index f32a7385a..a7026044c 100644
--- a/lnwire/update_fee.go
+++ b/lnwire/update_fee.go
@@ -36,10 +36,6 @@ func NewUpdateFee(chanID ChannelID, feePerKw uint32) *UpdateFee {
 // interface.
 var _ Message = (*UpdateFee)(nil)
 
-// A compile time check to ensure UpdateFee implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*UpdateFee)(nil)
-
 // Decode deserializes a serialized UpdateFee message stored in the passed
 // io.Reader observing the specified protocol version.
 //
@@ -76,13 +72,6 @@ func (c *UpdateFee) MsgType() MessageType {
 	return MsgUpdateFee
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *UpdateFee) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // TargetChanID returns the channel id of the link for which this message is
 // intended.
 //
diff --git a/lnwire/update_fulfill_htlc.go b/lnwire/update_fulfill_htlc.go
index 9ec747406..35aaa2ff5 100644
--- a/lnwire/update_fulfill_htlc.go
+++ b/lnwire/update_fulfill_htlc.go
@@ -44,16 +44,12 @@ func NewUpdateFulfillHTLC(chanID ChannelID, id uint64,
 	}
 }
 
-// A compile time check to ensure UpdateFulfillHTLC implements the
-// lnwire.Message interface.
+// A compile time check to ensure UpdateFulfillHTLC implements the lnwire.Message
+// interface.
 var _ Message = (*UpdateFulfillHTLC)(nil)
 
-// A compile time check to ensure UpdateFulfillHTLC implements the
-// lnwire.SizeableMessage interface.
-var _ SizeableMessage = (*UpdateFulfillHTLC)(nil)
-
-// Decode deserializes a serialized UpdateFulfillHTLC message stored in the
-// passed io.Reader observing the specified protocol version.
+// Decode deserializes a serialized UpdateFulfillHTLC message stored in the passed
+// io.Reader observing the specified protocol version.
 //
 // This is part of the lnwire.Message interface.
 func (c *UpdateFulfillHTLC) Decode(r io.Reader, pver uint32) error {
@@ -119,13 +115,6 @@ func (c *UpdateFulfillHTLC) MsgType() MessageType {
 	return MsgUpdateFulfillHTLC
 }
 
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *UpdateFulfillHTLC) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
-
 // TargetChanID returns the channel id of the link for which this message is
 // intended.
 //
diff --git a/lnwire/warning.go b/lnwire/warning.go
index 2a0b3df19..adb595cc0 100644
--- a/lnwire/warning.go
+++ b/lnwire/warning.go
@@ -29,10 +29,6 @@ type Warning struct {
 // interface.
 var _ Message = (*Warning)(nil)
 
-// A compile time check to ensure Warning implements the lnwire.SizeableMessage
-// interface.
-var _ SizeableMessage = (*Warning)(nil)
-
 // NewWarning creates a new Warning message.
 func NewWarning() *Warning {
 	return &Warning{}
@@ -78,10 +74,3 @@ func (c *Warning) Encode(w *bytes.Buffer, _ uint32) error {
 func (c *Warning) MsgType() MessageType {
 	return MsgWarning
 }
-
-// SerializedSize returns the serialized size of the message in bytes.
-//
-// This is part of the lnwire.SizeableMessage interface.
-func (c *Warning) SerializedSize() (uint32, error) {
-	return MessageSerializedSize(c)
-}
diff --git a/record/custom_records.go b/record/custom_records.go
index f9e4f3426..01952c24e 100644
--- a/record/custom_records.go
+++ b/record/custom_records.go
@@ -1,6 +1,8 @@
 package record
 
-import "fmt"
+import (
+	"fmt"
+)
 
 const (
 	// CustomTypeStart is the start of the custom tlv type range as defined
@@ -22,3 +24,8 @@ func (c CustomSet) Validate() error {
 
 	return nil
 }
+
+// IsKeysend checks if the custom records contain the key send type.
+func (c CustomSet) IsKeysend() bool {
+	return c[KeySendType] != nil
+}
diff --git a/routing/control_tower.go b/routing/control_tower.go
index 174ff6a7f..8968ca312 100644
--- a/routing/control_tower.go
+++ b/routing/control_tower.go
@@ -282,9 +282,6 @@ func (p *controlTower) FetchPayment(paymentHash lntypes.Hash) (
 // reason the payment failed. After invoking this method, InitPayment should
 // return nil on its next call for this payment hash, allowing the switch to
 // make a subsequent payment.
-//
-// NOTE: This method will overwrite the failure reason if the payment is already
-// failed.
 func (p *controlTower) FailPayment(paymentHash lntypes.Hash,
 	reason channeldb.FailureReason) error {
 
diff --git a/routing/router.go b/routing/router.go
index 3fa259ed6..71867bccd 100644
--- a/routing/router.go
+++ b/routing/router.go
@@ -1048,29 +1048,6 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
 	firstHopCustomRecords lnwire.CustomRecords) (*channeldb.HTLCAttempt,
 	error) {
 
-	// Helper function to fail a payment. It makes sure the payment is only
-	// failed once so that the failure reason is not overwritten.
-	failPayment := func(paymentIdentifier lntypes.Hash,
-		reason channeldb.FailureReason) error {
-
-		payment, fetchErr := r.cfg.Control.FetchPayment(
-			paymentIdentifier,
-		)
-		if fetchErr != nil {
-			return fetchErr
-		}
-
-		// NOTE: We cannot rely on the payment status to be failed here
-		// because it can still be in-flight although the payment is
-		// already failed.
-		_, failedReason := payment.TerminalInfo()
-		if failedReason != nil {
-			return nil
-		}
-
-		return r.cfg.Control.FailPayment(paymentIdentifier, reason)
-	}
-
 	log.Debugf("SendToRoute for payment %v with skipTempErr=%v",
 		htlcHash, skipTempErr)
 
@@ -1171,6 +1148,20 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
 		return nil, err
 	}
 
+	// We now look up the payment to see if it's already failed.
+	payment, err := p.router.cfg.Control.FetchPayment(p.identifier)
+	if err != nil {
+		return result.attempt, err
+	}
+
+	// Exit if the above error has caused the payment to be failed, we also
+	// return the error from sending attempt to mimic the old behavior of
+	// this method.
+	_, failedReason := payment.TerminalInfo()
+	if failedReason != nil {
+		return result.attempt, result.err
+	}
+
 	// Since for SendToRoute we won't retry in case the shard fails, we'll
 	// mark the payment failed with the control tower immediately if the
 	// skipTempErr is false.
@@ -1184,7 +1175,8 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
 			return result.attempt, result.err
 		}
 
-		err := failPayment(paymentIdentifier, reason)
+		// Otherwise we need to fail the payment.
+		err := r.cfg.Control.FailPayment(paymentIdentifier, reason)
 		if err != nil {
 			return nil, err
 		}
@@ -1207,7 +1199,7 @@ func (r *ChannelRouter) sendToRoute(htlcHash lntypes.Hash, rt *route.Route,
 	// An error returned from collecting the result, we'll mark the payment
 	// as failed if we don't skip temp error.
 	if !skipTempErr {
-		err := failPayment(paymentIdentifier, reason)
+		err := r.cfg.Control.FailPayment(paymentIdentifier, reason)
 		if err != nil {
 			return nil, err
 		}
@@ -1255,6 +1247,8 @@ func (r *ChannelRouter) sendPayment(ctx context.Context,
 	}
 
 	// Validate the custom records before we attempt to send the payment.
+	// TODO(ziggie): Move this check before registering the payment in the
+	// db (InitPayment).
 	if err := firstHopCustomRecords.Validate(); err != nil {
 		return [32]byte{}, nil, err
 	}
diff --git a/sample-lnd.conf b/sample-lnd.conf
index af4923846..4b9c68afa 100644
--- a/sample-lnd.conf
+++ b/sample-lnd.conf
@@ -621,9 +621,6 @@
 ; Use Bitcoin's test network.
 ; bitcoin.testnet=false
 ;
-; Use Bitcoin's 4th version test network.
-; bitcoin.testnet4=false
-;
 ; Use Bitcoin's simulation test network
 ; bitcoin.simnet=false
 
@@ -1746,17 +1743,6 @@
 ; The number of confirmations required before processing channel announcements.
 ; gossip.announcement-conf=6
 
-; The allotted bandwidth rate expressed in bytes/second that will be allocated
-; towards outbound gossip messages. Realized rates above this value will be 
-; throttled. This value is shared across all peers.
-; gossip.msg-rate-bytes=102400
-
-; The amount of bytes of gossip messages that can be sent at a given time. This
-; is used as the amount of tokens in the token bucket algorithm. This value
-; MUST be set to something about 65 KB, otherwise a single max sized message
-; can never be sent.
-; gossip.msg-burst-bytes=204800
-
 [invoices]
 
 ; If a hold invoice has accepted htlcs that reach their expiry height and are
diff --git a/server.go b/server.go
index 38243c289..ad521cd67 100644
--- a/server.go
+++ b/server.go
@@ -1188,8 +1188,6 @@ func newServer(cfg *Config, listenAddrs []net.Addr,
 		IsStillZombieChannel:    s.graphBuilder.IsZombieChannel,
 		ScidCloser:              scidCloserMan,
 		AssumeChannelValid:      cfg.Routing.AssumeChannelValid,
-		MsgRateBytes:            cfg.Gossip.MsgRateBytes,
-		MsgBurstBytes:           cfg.Gossip.MsgBurstBytes,
 	}, nodeKeyDesc)
 
 	accessCfg := &accessManConfig{
@@ -2596,12 +2594,6 @@ func (s *server) Start() error {
 				chainreg.BitcoinTestnetGenesis,
 			)
 		}
-		if s.cfg.Bitcoin.TestNet4 {
-			setSeedList(
-				s.cfg.Bitcoin.DNSSeeds,
-				chainreg.BitcoinTestnet4Genesis,
-			)
-		}
 		if s.cfg.Bitcoin.SigNet {
 			setSeedList(
 				s.cfg.Bitcoin.DNSSeeds,


Test patch:
diff --git a/cmd/commands/commands_test.go b/cmd/commands/commands_test.go
index c6baad258..61bd12920 100644
--- a/cmd/commands/commands_test.go
+++ b/cmd/commands/commands_test.go
@@ -2,14 +2,12 @@ package commands
 
 import (
 	"encoding/hex"
-	"flag"
 	"fmt"
 	"math"
 	"strconv"
 	"testing"
 
 	"github.com/stretchr/testify/require"
-	"github.com/urfave/cli"
 )
 
 // TestParseChanPoint tests parseChanPoint with various
@@ -330,107 +328,3 @@ func TestAppendChanID(t *testing.T) {
 		})
 	}
 }
-
-// TestParseBlockHeightInputs tests the input block heights and ensure that the
-// proper errors are returned when they could lead in invalid results.
-func TestParseBlockHeightInputs(t *testing.T) {
-	t.Parallel()
-
-	app := cli.NewApp()
-
-	startDefault, endDefault := int64(0), int64(-1)
-
-	testCases := []struct {
-		name          string
-		expectedStart int32
-		expectedEnd   int32
-		expectedErr   string
-	}{
-		{
-			name:          "start less than end",
-			expectedStart: 100,
-			expectedEnd:   200,
-			expectedErr:   "",
-		},
-		{
-			name:          "start greater than end",
-			expectedStart: 200,
-			expectedEnd:   100,
-			expectedErr: "start_height should be " +
-				"less than end_height if end_height is " +
-				"not equal to -1",
-		},
-		{
-			name:          "only start height set",
-			expectedStart: 100,
-			expectedEnd:   -1,
-			expectedErr:   "",
-		},
-		{
-			name:          "start set and end height set as -1",
-			expectedStart: 100,
-			expectedEnd:   -1,
-			expectedErr:   "",
-		},
-		{
-			name:          "neither start nor end heights defined",
-			expectedStart: 0,
-			expectedEnd:   -1,
-			expectedErr:   "",
-		},
-		{
-			name:          "only end height defined",
-			expectedStart: 0,
-			expectedEnd:   100,
-			expectedErr:   "",
-		},
-		{
-			name:          "start height is a negative",
-			expectedStart: -1,
-			expectedEnd:   100,
-			expectedErr: "start_height should be greater " +
-				"than or equal to 0",
-		},
-	}
-
-	for _, tc := range testCases {
-		t.Run(tc.name, func(t *testing.T) {
-			flagSet := flag.NewFlagSet(
-				"listchaintxns", flag.ContinueOnError,
-			)
-
-			var startHeight, endHeight int64
-			flagSet.Int64Var(
-				&startHeight, "start_height", startDefault, "",
-			)
-			flagSet.Int64Var(
-				&endHeight, "end_height", endDefault, "",
-			)
-
-			err := flagSet.Set(
-				"start_height",
-				strconv.Itoa(int(tc.expectedStart)),
-			)
-			require.NoError(
-				t, err, "failed to set start_height flag",
-			)
-
-			err = flagSet.Set(
-				"end_height", strconv.Itoa(int(tc.expectedEnd)),
-			)
-			require.NoError(
-				t, err, "failed to set end_height flag",
-			)
-
-			ctx := cli.NewContext(app, flagSet, nil)
-			start, end, err := parseBlockHeightInputs(ctx)
-			if tc.expectedErr != "" {
-				require.EqualError(t, err, tc.expectedErr)
-			} else {
-				require.NoError(t, err)
-			}
-			require.Equal(t, tc.expectedStart, start)
-			require.Equal(t, tc.expectedEnd, end)
-		})
-	}
-}
diff --git a/discovery/sync_manager_test.go b/discovery/sync_manager_test.go
index 4aff5b631..f4e35bddd 100644
--- a/discovery/sync_manager_test.go
+++ b/discovery/sync_manager_test.go
@@ -1,9 +1,7 @@
 package discovery
 
 import (
-	"bytes"
 	"fmt"
-	"io"
 	"reflect"
 	"sync"
 	"sync/atomic"
@@ -17,7 +15,6 @@ import (
 	"github.com/lightningnetwork/lnd/routing/route"
 	"github.com/lightningnetwork/lnd/ticker"
 	"github.com/stretchr/testify/require"
-	"golang.org/x/time/rate"
 )
 
 // randPeer creates a random peer.
@@ -688,167 +685,3 @@ func assertActiveSyncerTransition(t *testing.T, s *GossipSyncer, peer *mockPeer)
 	})
 	assertSyncerStatus(t, s, chansSynced, PassiveSync)
 }
-
-// TestSizeableMessage is a test implementation of lnwire.SizeableMessage.
-type TestSizeableMessage struct {
-	size    uint32
-	sizeErr error
-}
-
-// Decode implements the lnwire.Message interface.
-func (m *TestSizeableMessage) Decode(r io.Reader, pver uint32) error {
-	return nil
-}
-
-// Encode implements the lnwire.Message interface.
-func (m *TestSizeableMessage) Encode(w *bytes.Buffer, pver uint32) error {
-	return nil
-}
-
-// MsgType implements the lnwire.Message interface.
-func (m *TestSizeableMessage) MsgType() lnwire.MessageType {
-	// Use a custom message type for testing purposes.
-	return lnwire.MessageType(9999)
-}
-
-// SerializedSize implements the lnwire.SizeableMessage interface.
-func (m *TestSizeableMessage) SerializedSize() (uint32, error) {
-	if m.sizeErr != nil {
-		return 0, m.sizeErr
-	}
-
-	return m.size, nil
-}
-
-// TestDeriveRateLimitReservation tests that the
-// SyncManager.deriveRateLimitReservation method correctly computes delays based
-// on message size.
-func TestDeriveRateLimitReservation(t *testing.T) {
-	// Define standard test parameters for rate limiting.
-	bytesPerSec := uint64(1000) // 1000 bytes/second
-	bytesBurst := uint64(100)   // 100 bytes burst capacity
-
-	// Helper to create a fresh SyncManager with a clean rate limiter.
-	newSyncManager := func(perSec, burst uint64) *SyncManager {
-		limiter := rate.NewLimiter(rate.Limit(perSec), int(burst))
-		return &SyncManager{
-			rateLimiter: limiter,
-		}
-	}
-
-	// Test sequential messages causing increasing delays - this is the core
-	// property of the rate limiting mechanism.
-	t.Run("sequential messages have increasing delays", func(t *testing.T) {
-		sm := newSyncManager(bytesPerSec, bytesBurst)
-
-		// Send a series of messages that exactly consume the burst
-		// limit to make the behavior predictable.
-		msg := &TestSizeableMessage{
-			size: uint32(bytesBurst),
-		}
-
-		// First message should have no delay as it fits within burst.
-		delay1, err := sm.deriveRateLimitReservation(msg)
-		require.NoError(t, err)
-		require.Equal(
-			t, time.Duration(0), delay1.Delay(), "first message "+
-				"should have no delay",
-		)
-
-		// Second message should have a non-zero delay as the token
-		// bucket is now depleted.
-		delay2, err := sm.deriveRateLimitReservation(msg)
-		require.NoError(t, err)
-		require.True(
-			t, delay2.Delay() > 0, "second message should have "+
-				"non-zero delay, got: %s", delay2,
-		)
-
-		// Third message should have an even longer delay since the
-		// token bucket is still refilling at a constant rate.
-		delay3, err := sm.deriveRateLimitReservation(msg)
-		require.NoError(t, err)
-		require.True(t, delay3.Delay() > delay2.Delay(), "third "+
-			"message should have longer delay than second: %s > %s",
-			delay3, delay2)
-
-		// The expected theoretical delay when sending messages at
-		// exactly the burst size should be approximately 100ms with our
-		// parameters.
-		expectedDelay := time.Duration(
-			float64(msg.size) / float64(bytesPerSec) * float64(time.Second), //nolint:ll
-		)
-
-		// Check that delays are increasing in a coherent manner.
-		t.Logf("Delay sequence: %s → %s → %s (expected "+
-			"theoretical: %s)", delay1.Delay(), delay2.Delay(),
-			delay3.Delay(), expectedDelay)
-	})
-
-	// Test handling of errors from SerializedSize.
-	t.Run("propagates serialization errors", func(t *testing.T) {
-		sm := newSyncManager(bytesPerSec, bytesBurst)
-
-		// Create a test message that returns an error from
-		// SerializedSize.
-		errMsg := fmt.Errorf("failed to serialize message")
-		msg := &TestSizeableMessage{
-			sizeErr: errMsg,
-		}
-
-		// The error should propagate through
-		// deriveRateLimitReservation.
-		_, err := sm.deriveRateLimitReservation(msg)
-		require.Error(t, err)
-		require.Equal(
-			t, errMsg, err, "Error should be propagated unchanged",
-		)
-	})
-
-	// Test that message size affects delay.
-	t.Run("larger messages have longer delays", func(t *testing.T) {
-		// Create a rate limiter with a known burst size.
-		sm := newSyncManager(bytesPerSec, bytesBurst)
-
-		// First, empty the token bucket with a message exactly at the
-		// burst size.
-		initialMsg := &TestSizeableMessage{
-			size: uint32(bytesBurst),
-		}
-		_, err := sm.deriveRateLimitReservation(initialMsg)
-		require.NoError(t, err)
-
-		// Now send two messages of different sizes and compare their
-		// delays.
-		smallMsg := &TestSizeableMessage{
-			size: 50,
-		}
-		largeMsg := &TestSizeableMessage{
-			size: 200,
-		}
-
-		// Send the small message first.
-		smallDelay, err := sm.deriveRateLimitReservation(smallMsg)
-		require.NoError(t, err)
-
-		// Reset the limiter to the same state, then empty the bucket.
-		sm.rateLimiter = rate.NewLimiter(
-			rate.Limit(bytesPerSec), int(bytesBurst),
-		)
-		_, err = sm.deriveRateLimitReservation(initialMsg)
-		require.NoError(t, err)
-
-		// Now send the large message.
-		largeDelay, err := sm.deriveRateLimitReservation(largeMsg)
-		require.NoError(t, err)
-
-		// The large message should have a longer delay than the small
-		// one.
-		require.True(
-			t, largeDelay.Delay() > smallDelay.Delay(),
-			"large message (size %d) should have longer delay "+
-				"(%s) than small message (size %d, delay %s)",
-			largeMsg.size, largeDelay.Delay(), smallMsg.size,
-			smallDelay.Delay())
-	})
-}
diff --git a/discovery/syncer_test.go b/discovery/syncer_test.go
index a63997909..47032e1ba 100644
--- a/discovery/syncer_test.go
+++ b/discovery/syncer_test.go
@@ -1,7 +1,6 @@
 package discovery
 
 import (
-	"context"
 	"errors"
 	"fmt"
 	"math"
@@ -197,18 +196,15 @@ func newTestSyncer(hID lnwire.ShortChannelID,
 		noSyncChannels:         !syncChannels,
 		noReplyQueries:         !replyQueries,
 		noTimestampQueryOption: !timestamps,
-		sendToPeer: func(_ context.Context,
-			msgs ...lnwire.Message) error {
-
+		sendToPeer: func(msgs ...lnwire.Message) error {
 			msgChan <- msgs
 			return nil
 		},
-		sendToPeerSync: func(_ context.Context,
-			msgs ...lnwire.Message) error {
-
+		sendToPeerSync: func(msgs ...lnwire.Message) error {
 			msgChan <- msgs
 			return nil
 		},
+		delayedQueryReplyInterval: 2 * time.Second,
 		bestHeight: func() uint32 {
 			return latestKnownHeight
 		},
@@ -1561,6 +1557,161 @@ func TestGossipSyncerSynchronizeChanIDs(t *testing.T) {
 	}
 }
 
+// TestGossipSyncerDelayDOS tests that the gossip syncer will begin delaying
+// queries after its prescribed allotment of undelayed query responses. Once
+// this happens, all query replies should be delayed by the configurated
+// interval.
+func TestGossipSyncerDelayDOS(t *testing.T) {
+	t.Parallel()
+
+	// We'll modify the chunk size to be a smaller value, since we'll be
+	// sending a modest number of queries. After exhausting our undelayed
+	// gossip queries, we'll send two extra queries and ensure that they are
+	// delayed properly.
+	const chunkSize = 2
+	const numDelayedQueries = 2
+	const delayTolerance = time.Millisecond * 200
+
+	// First, we'll create two GossipSyncer instances with a canned
+	// sendToPeer message to allow us to intercept their potential sends.
+	highestID := lnwire.ShortChannelID{
+		BlockHeight: 1144,
+	}
+	msgChan1, syncer1, chanSeries1 := newTestSyncer(
+		highestID, defaultEncoding, chunkSize, true, false,
+	)
+	syncer1.Start()
+	defer syncer1.Stop()
+
+	msgChan2, syncer2, chanSeries2 := newTestSyncer(
+		highestID, defaultEncoding, chunkSize, false, true,
+	)
+	syncer2.Start()
+	defer syncer2.Stop()
+
+	// Record the delayed query reply interval used by each syncer.
+	delayedQueryInterval := syncer1.cfg.delayedQueryReplyInterval
+
+	// Record the number of undelayed queries allowed by the syncers.
+	numUndelayedQueries := syncer1.cfg.maxUndelayedQueryReplies
+
+	// We will send enough queries to exhaust the undelayed responses, and
+	// then send two more queries which should be delayed. An additional one
+	// is subtracted from the total since undelayed message will be consumed
+	// by the initial QueryChannelRange.
+	numQueryResponses := numUndelayedQueries + numDelayedQueries - 1
+
+	// The total number of responses must include the initial reply each
+	// syncer will make to QueryChannelRange.
+	numTotalQueries := 1 + numQueryResponses
+
+	// The total number of channels each syncer needs to request must be
+	// scaled by the chunk size being used.
+	numTotalChans := numQueryResponses * chunkSize
+
+	// Construct enough channels so that all of the queries will have enough
+	// channels. Since syncer1 won't know of any channels, their sets are
+	// inherently disjoint.
+	var syncer2Chans []lnwire.ShortChannelID
+	for i := 0; i < numTotalChans; i++ {
+		syncer2Chans = append([]lnwire.ShortChannelID{
+			{
+				BlockHeight: highestID.BlockHeight - uint32(i) - 1,
+				TxIndex:     uint32(i),
+			},
+		}, syncer2Chans...)
+	}
+
+	// We'll kick off the test by asserting syncer1 sends over the
+	// QueryChannelRange message the other node.
+	select {
+	case <-time.After(time.Second * 2):
+		t.Fatalf("didn't get msg from syncer1")
+
+	case msgs := <-msgChan1:
+		for _, msg := range msgs {
+			// The message MUST be a QueryChannelRange message.
+			_, ok := msg.(*lnwire.QueryChannelRange)
+			if !ok {
+				t.Fatalf("wrong message: expected "+
+					"QueryChannelRange for %T", msg)
+			}
+
+			select {
+			case <-time.After(time.Second * 2):
+				t.Fatalf("node 2 didn't read msg")
+
+			case syncer2.queryMsgs <- msg:
+
+			}
+		}
+	}
+
+	// At this point, we'll need to a response from syncer2's channel
+	// series. This will cause syncer1 to simply request the entire set of
+	// channels from syncer2. This will count as the first undelayed
+	// response for sycner2.
+	select {
+	case <-time.After(time.Second * 2):
+		t.Fatalf("no query recvd")
+
+	case <-chanSeries2.filterRangeReqs:
+		// We'll send back all the channels that it should know of.
+		chanSeries2.filterRangeResp <- syncer2Chans
+	}
+
+	// At this point, we'll assert that the ReplyChannelRange message is
+	// sent by sycner2.
+	for i := 0; i < numQueryResponses; i++ {
+		select {
+		case <-time.After(time.Second * 2):
+			t.Fatalf("didn't get msg from syncer2")
+
+		case msgs := <-msgChan2:
+			for _, msg := range msgs {
+				// The message MUST be a ReplyChannelRange message.
+				_, ok := msg.(*lnwire.ReplyChannelRange)
+				if !ok {
+					t.Fatalf("wrong message: expected "+
+						"QueryChannelRange for %T", msg)
+				}
+
+				select {
+				case <-time.After(time.Second * 2):
+					t.Fatalf("node 2 didn't read msg")
+
+				case syncer1.gossipMsgs <- msg:
+				}
+			}
+		}
+	}
+
+	// We'll now have syncer1 process the received sids from syncer2.
+	select {
+	case <-time.After(time.Second * 2):
+		t.Fatalf("no query recvd")
+
+	case <-chanSeries1.filterReq:
+		chanSeries1.filterResp <- syncer2Chans
+	}
+
+	// At this point, syncer1 should start to send out initial requests to
+	// query the chan IDs of the remote party. We'll keep track of the
+	// number of queries made using the iterated value, which starts at one
+	// due the initial contribution of the QueryChannelRange msgs.
+	for i := 1; i < numTotalQueries; i++ {
+		expDelayResponse := i >= numUndelayedQueries
+		queryBatch(t,
+			msgChan1, msgChan2,
+			syncer1, syncer2,
+			chanSeries2,
+			expDelayResponse,
+			delayedQueryInterval,
+			delayTolerance,
+		)
+	}
+}
+
 // queryBatch is a helper method that will query for a single batch of channels
 // from a peer and assert the responses. The method can also be used to assert
 // the same transition happens, but is delayed by the remote peer's DOS
diff --git a/itest/list_on_test.go b/itest/list_on_test.go
index 795e0e3f9..8066fb359 100644
--- a/itest/list_on_test.go
+++ b/itest/list_on_test.go
@@ -378,10 +378,6 @@ var allTestCases = []*lntest.TestCase{
 		Name:     "sendtoroute multi path payment",
 		TestFunc: testSendToRouteMultiPath,
 	},
-	{
-		Name:     "send to route fail payment notification",
-		TestFunc: testSendToRouteFailPaymentNotification,
-	},
 	{
 		Name:     "send multi path payment",
 		TestFunc: testSendMultiPathPayment,
@@ -402,6 +398,10 @@ var allTestCases = []*lntest.TestCase{
 		Name:     "sendtoroute amp",
 		TestFunc: testSendToRouteAMP,
 	},
+	{
+		Name:     "send payment keysend mpp fail",
+		TestFunc: testSendPaymentKeysendMPPFail,
+	},
 	{
 		Name:     "forward interceptor dedup htlcs",
 		TestFunc: testForwardInterceptorDedupHtlc,
diff --git a/itest/lnd_payment_test.go b/itest/lnd_payment_test.go
index 80f7dcac8..f9617c68f 100644
--- a/itest/lnd_payment_test.go
+++ b/itest/lnd_payment_test.go
@@ -1,6 +1,7 @@
 package itest
 
 import (
+	"bytes"
 	"context"
 	"crypto/sha256"
 	"encoding/hex"
@@ -20,6 +21,7 @@ import (
 	"github.com/lightningnetwork/lnd/lntest/wait"
 	"github.com/lightningnetwork/lnd/lntypes"
 	"github.com/lightningnetwork/lnd/lnwire"
+	"github.com/lightningnetwork/lnd/record"
 	"github.com/stretchr/testify/require"
 )
 
@@ -1351,3 +1353,46 @@ func runSendToRouteFailHTLCTimeout(ht *lntest.HarnessTest, restartAlice bool) {
 	ht.AssertPaymentStatus(alice, preimage.Hash(), lnrpc.Payment_FAILED)
 	ht.AssertPaymentStatusFromStream(payStream, lnrpc.Payment_FAILED)
 }
+
+// testSendPaymentKeysendMPPFail tests sending a keysend payment and trying to
+// split it will fail because keysend payment in combination with MPP is not
+// supported.
+func testSendPaymentKeysendMPPFail(ht *lntest.HarnessTest) {
+	const chanAmt = btcutil.Amount(100_000)
+	p := lntest.OpenChannelParams{Amt: chanAmt}
+
+	// Initialize the test context with 2 connected nodes.
+	cfgs := [][]string{nil, nil}
+
+	// Open and wait for channels.
+	_, nodes := ht.CreateSimpleNetwork(cfgs, p)
+	alice, bob := nodes[0], nodes[1]
+
+	// Send a keysend payment which has a total amount which is smaller
+	// than the maxShardAmt. The payment will still be recorded in the db
+	// but it will fail immediately because keysend and MPP cannot be
+	// combined.
+	dest, err := hex.DecodeString(bob.PubKeyStr)
+	require.NoError(ht, err)
+
+	// Create a preimage and corresponding payment hash for the keysend
+	// payment.
+	preimage := bytes.Repeat([]byte{0x10}, 32)
+	hash := sha256.Sum256(preimage)
+
+	client := alice.RPC.SendPayment(&routerrpc.SendPaymentRequest{
+		DestCustomRecords: map[uint64][]byte{
+			record.KeySendType: preimage,
+		},
+		MaxShardSizeMsat: 10_000,
+		Amt:              100_000,
+		MaxParts:         10,
+		Dest:             dest,
+		PaymentHash:      hash[:],
+	})
+
+	// We expect the payment to fail immediately because keysend and MPP
+	// cannot be combined.
+	_, err = ht.ReceivePaymentUpdate(client)
+	require.Error(ht, err)
+}
diff --git a/itest/lnd_trackpayments_test.go b/itest/lnd_trackpayments_test.go
index dc8655a26..b3a2eedb1 100644
--- a/itest/lnd_trackpayments_test.go
+++ b/itest/lnd_trackpayments_test.go
@@ -2,10 +2,8 @@ package itest
 
 import (
 	"encoding/hex"
-	"time"
 
 	"github.com/btcsuite/btcd/btcutil"
-	"github.com/lightningnetwork/lnd/chainreg"
 	"github.com/lightningnetwork/lnd/lnrpc"
 	"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
 	"github.com/lightningnetwork/lnd/lntest"
@@ -150,96 +148,3 @@ func testTrackPaymentsCompatible(ht *lntest.HarnessTest) {
 	require.NoError(ht, err, "unable to get payment update")
 	require.Equal(ht, lnrpc.Payment_SUCCEEDED, payment3.Status)
 }
-
-// testSendToRouteFailPaymentNotification tests that when we are failing a
-// payment via SendToRouteV2 we only receive one failure notification, also
-// making sure the failure reason is not overwritten in our db.
-func testSendToRouteFailPaymentNotification(ht *lntest.HarnessTest) {
-	// Create a simple network with Alice->Bob.
-	_, nodes := ht.CreateSimpleNetwork(
-		[][]string{nil, nil},
-		lntest.OpenChannelParams{Amt: 100_000},
-	)
-	alice, bob := nodes[0], nodes[1]
-
-	// Make Bob create an invoice for Alice to pay.
-	_, rHashes, _ := ht.CreatePayReqs(bob, paymentAmt, 1)
-
-	rHash := rHashes[0]
-
-	// We create a dummy payment address so that the payment fails at
-	// the first hop.
-	payAddr := make([]byte, 32)
-
-	// Subscribe to all the payments because otherwise we would not be able
-	// to verify if two failure notifications are received. The single
-	// subscriber is deleted after receiving the first failure notification.
-	tracker := alice.RPC.TrackPayments(&routerrpc.TrackPaymentsRequest{
-		NoInflightUpdates: true,
-	})
-
-	bobPubKey, err := hex.DecodeString(bob.PubKeyStr)
-	require.NoError(ht, err, "unable to decode bob's pubkey")
-
-	// Build a route for the specified hops.
-	r := alice.RPC.BuildRoute(&routerrpc.BuildRouteRequest{
-		AmtMsat:        int64(paymentAmt * 1000),
-		FinalCltvDelta: chainreg.DefaultBitcoinTimeLockDelta,
-		HopPubkeys:     [][]byte{bobPubKey},
-	}).Route
-
-	// Set the MPP records to indicate this is a payment shard.
-	hop := r.Hops[len(r.Hops)-1]
-	hop.MppRecord = &lnrpc.MPPRecord{
-		PaymentAddr:  payAddr,
-		TotalAmtMsat: int64(paymentAmt * 1000),
-	}
-
-	// Send the only shard.
-	sendReq := &routerrpc.SendToRouteRequest{
-		PaymentHash: rHash,
-		Route:       r,
-	}
-
-	// We launch the payment and check the payment stream to verify that
-	// we are receiving exactly one failure notification.
-	respChan := make(chan *lnrpc.HTLCAttempt, 1)
-	go func() {
-		attempt := alice.RPC.SendToRouteV2(sendReq)
-		respChan <- attempt
-	}()
-
-	// We excpect the attempt and the payment to fail.
-	select {
-	case attempt := <-respChan:
-		require.Equal(ht, attempt.Status, lnrpc.HTLCAttempt_FAILED)
-
-	case <-time.After(defaultTimeout):
-		ht.Fatal("timeout waiting for SendToRouteV2 response")
-	}
-
-	// Assert the first track payment update should be a failed update.
-	update, err := tracker.Recv()
-	require.NoError(ht, err, "unable to receive payment update")
-	require.Equal(ht, lnrpc.Payment_FAILED, update.Status)
-
-	// Now check no other notifications come in.
-	notifChan := make(chan *lnrpc.Payment, 1)
-	go func() {
-		notif, err := tracker.Recv()
-		if err != nil {
-			return
-		}
-
-		notifChan <- notif
-	}()
-
-	// We do not expect any other notifications.
-	select {
-	case unexpectedNotif := <-notifChan:
-		ht.Fatalf("received unexpected notification: %v",
-			unexpectedNotif)
-
-	case <-time.After(100 * time.Millisecond):
-	}
-}
diff --git a/lntest/node/config.go b/lntest/node/config.go
index d71723aac..6cba60941 100644
--- a/lntest/node/config.go
+++ b/lntest/node/config.go
@@ -222,8 +222,6 @@ func (cfg *BaseNodeConfig) GenArgs() []string {
 	switch cfg.NetParams {
 	case &chaincfg.TestNet3Params:
 		args = append(args, "--bitcoin.testnet")
-	case &chaincfg.TestNet4Params:
-		args = append(args, "--bitcoin.testnet4")
 	case &chaincfg.SimNetParams:
 		args = append(args, "--bitcoin.simnet")
 	case &chaincfg.RegressionNetParams:
diff --git a/lnwire/lnwire_test.go b/lnwire/lnwire_test.go
index 7f068e526..123689902 100644
--- a/lnwire/lnwire_test.go
+++ b/lnwire/lnwire_test.go
@@ -3,20 +3,31 @@ package lnwire
 import (
 	"bytes"
 	crand "crypto/rand"
+	"encoding/binary"
 	"encoding/hex"
+	"fmt"
+	"image/color"
+	"io"
 	"math"
 	"math/rand"
 	"net"
+	"reflect"
 	"testing"
+	"testing/quick"
 	"time"
 
 	"github.com/btcsuite/btcd/btcec/v2"
 	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
+	"github.com/btcsuite/btcd/btcutil"
+	"github.com/btcsuite/btcd/chaincfg"
 	"github.com/btcsuite/btcd/chaincfg/chainhash"
 	"github.com/btcsuite/btcd/wire"
+	"github.com/lightningnetwork/lnd/fn/v2"
+	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
+	"github.com/lightningnetwork/lnd/tlv"
 	"github.com/lightningnetwork/lnd/tor"
+	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
-	"pgregory.net/rapid"
 )
 
 var (
@@ -43,6 +54,106 @@ var (
 
 const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 
+func randLocalNonce(r *rand.Rand) Musig2Nonce {
+	var nonce Musig2Nonce
+	_, _ = io.ReadFull(r, nonce[:])
+
+	return nonce
+}
+
+func someLocalNonce[T tlv.TlvType](
+	r *rand.Rand) tlv.OptionalRecordT[T, Musig2Nonce] {
+
+	return tlv.SomeRecordT(tlv.NewRecordT[T, Musig2Nonce](
+		randLocalNonce(r),
+	))
+}
+
+func randPartialSig(r *rand.Rand) (*PartialSig, error) {
+	var sigBytes [32]byte
+	if _, err := r.Read(sigBytes[:]); err != nil {
+		return nil, fmt.Errorf("unable to generate sig: %w", err)
+	}
+
+	var s btcec.ModNScalar
+	s.SetByteSlice(sigBytes[:])
+
+	return &PartialSig{
+		Sig: s,
+	}, nil
+}
+
+func somePartialSig(t *testing.T,
+	r *rand.Rand) tlv.OptionalRecordT[PartialSigType, PartialSig] {
+
+	sig, err := randPartialSig(r)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return tlv.SomeRecordT(tlv.NewRecordT[PartialSigType, PartialSig](
+		*sig,
+	))
+}
+
+func randPartialSigWithNonce(r *rand.Rand) (*PartialSigWithNonce, error) {
+	var sigBytes [32]byte
+	if _, err := r.Read(sigBytes[:]); err != nil {
+		return nil, fmt.Errorf("unable to generate sig: %w", err)
+	}
+
+	var s btcec.ModNScalar
+	s.SetByteSlice(sigBytes[:])
+
+	return &PartialSigWithNonce{
+		PartialSig: NewPartialSig(s),
+		Nonce:      randLocalNonce(r),
+	}, nil
+}
+
+func somePartialSigWithNonce(t *testing.T,
+	r *rand.Rand) OptPartialSigWithNonceTLV {
+
+	sig, err := randPartialSigWithNonce(r)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	return tlv.SomeRecordT(
+		tlv.NewRecordT[PartialSigWithNonceType, PartialSigWithNonce](
+			*sig,
+		),
+	)
+}
+
+func randAlias(r *rand.Rand) NodeAlias {
+	var a NodeAlias
+	for i := range a {
+		a[i] = letterBytes[r.Intn(len(letterBytes))]
+	}
+
+	return a
+}
+
+func randPubKey() (*btcec.PublicKey, error) {
+	priv, err := btcec.NewPrivateKey()
+	if err != nil {
+		return nil, err
+	}
+
+	return priv.PubKey(), nil
+}
+
+// pubkeyFromHex parses a Bitcoin public key from a hex encoded string.
+func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) {
+	pubKeyBytes, err := hex.DecodeString(keyHex)
+	if err != nil {
+		return nil, err
+	}
+
+	return btcec.ParsePubKey(pubKeyBytes)
+}
+
 // generateRandomBytes returns a slice of n random bytes.
 func generateRandomBytes(n int) ([]byte, error) {
 	b := make([]byte, n)
@@ -65,23 +176,140 @@ func randRawKey(t *testing.T) [33]byte {
 	return n
 }
 
-func randPubKey() (*btcec.PublicKey, error) {
-	priv, err := btcec.NewPrivateKey()
-	if err != nil {
+func randDeliveryAddress(r *rand.Rand) (DeliveryAddress, error) {
+	// Generate size minimum one. Empty scripts should be tested specifically.
+	size := r.Intn(deliveryAddressMaxSize) + 1
+	da := DeliveryAddress(make([]byte, size))
+
+	_, err := r.Read(da)
+	return da, err
+}
+
+func randRawFeatureVector(r *rand.Rand) *RawFeatureVector {
+	featureVec := NewRawFeatureVector()
+	for i := 0; i < 10000; i++ {
+		if r.Int31n(2) == 0 {
+			featureVec.Set(FeatureBit(i))
+		}
+	}
+	return featureVec
+}
+
+func randTCP4Addr(r *rand.Rand) (*net.TCPAddr, error) {
+	var ip [4]byte
+	if _, err := r.Read(ip[:]); err != nil {
 		return nil, err
 	}
 
-	return priv.PubKey(), nil
+	var port [2]byte
+	if _, err := r.Read(port[:]); err != nil {
+		return nil, err
+	}
+
+	addrIP := net.IP(ip[:])
+	addrPort := int(binary.BigEndian.Uint16(port[:]))
+
+	return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil
 }
 
-// pubkeyFromHex parses a Bitcoin public key from a hex encoded string.
-func pubkeyFromHex(keyHex string) (*btcec.PublicKey, error) {
-	pubKeyBytes, err := hex.DecodeString(keyHex)
+func randTCP6Addr(r *rand.Rand) (*net.TCPAddr, error) {
+	var ip [16]byte
+	if _, err := r.Read(ip[:]); err != nil {
+		return nil, err
+	}
+
+	var port [2]byte
+	if _, err := r.Read(port[:]); err != nil {
+		return nil, err
+	}
+
+	addrIP := net.IP(ip[:])
+	addrPort := int(binary.BigEndian.Uint16(port[:]))
+
+	return &net.TCPAddr{IP: addrIP, Port: addrPort}, nil
+}
+
+func randV2OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) {
+	var serviceID [tor.V2DecodedLen]byte
+	if _, err := r.Read(serviceID[:]); err != nil {
+		return nil, err
+	}
+
+	var port [2]byte
+	if _, err := r.Read(port[:]); err != nil {
+		return nil, err
+	}
+
+	onionService := tor.Base32Encoding.EncodeToString(serviceID[:])
+	onionService += tor.OnionSuffix
+	addrPort := int(binary.BigEndian.Uint16(port[:]))
+
+	return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil
+}
+
+func randV3OnionAddr(r *rand.Rand) (*tor.OnionAddr, error) {
+	var serviceID [tor.V3DecodedLen]byte
+	if _, err := r.Read(serviceID[:]); err != nil {
+		return nil, err
+	}
+
+	var port [2]byte
+	if _, err := r.Read(port[:]); err != nil {
+		return nil, err
+	}
+
+	onionService := tor.Base32Encoding.EncodeToString(serviceID[:])
+	onionService += tor.OnionSuffix
+	addrPort := int(binary.BigEndian.Uint16(port[:]))
+
+	return &tor.OnionAddr{OnionService: onionService, Port: addrPort}, nil
+}
+
+func randOpaqueAddr(r *rand.Rand) (*OpaqueAddrs, error) {
+	payloadLen := r.Int63n(64) + 1
+	payload := make([]byte, payloadLen)
+
+	// The first byte is the address type. So set it to one that we
+	// definitely don't know about.
+	payload[0] = math.MaxUint8
+
+	// Generate random bytes for the rest of the payload.
+	if _, err := r.Read(payload[1:]); err != nil {
+		return nil, err
+	}
+
+	return &OpaqueAddrs{Payload: payload}, nil
+}
+
+func randAddrs(r *rand.Rand) ([]net.Addr, error) {
+	tcp4Addr, err := randTCP4Addr(r)
 	if err != nil {
 		return nil, err
 	}
 
-	return btcec.ParsePubKey(pubKeyBytes)
+	tcp6Addr, err := randTCP6Addr(r)
+	if err != nil {
+		return nil, err
+	}
+
+	v2OnionAddr, err := randV2OnionAddr(r)
+	if err != nil {
+		return nil, err
+	}
+
+	v3OnionAddr, err := randV3OnionAddr(r)
+	if err != nil {
+		return nil, err
+	}
+
+	opaqueAddrs, err := randOpaqueAddr(r)
+	if err != nil {
+		return nil, err
+	}
+
+	return []net.Addr{
+		tcp4Addr, tcp6Addr, v2OnionAddr, v3OnionAddr, opaqueAddrs,
+	}, nil
 }
 
 // TestChanUpdateChanFlags ensures that converting the ChanUpdateChanFlags and
@@ -190,64 +418,1558 @@ func TestEmptyMessageUnknownType(t *testing.T) {
 	}
 }
 
-// TestLightningWireProtocol uses the rapid property-based testing framework to
-// verify that all message types can be serialized and deserialized correctly.
+// randCustomRecords generates a random set of custom records for testing.
+func randCustomRecords(t *testing.T, r *rand.Rand) CustomRecords {
+	var (
+		customRecords = CustomRecords{}
+
+		// We'll generate a random number of records, between 1 and 10.
+		numRecords = r.Intn(9) + 1
+	)
+
+	// For each record, we'll generate a random key and value.
+	for i := 0; i < numRecords; i++ {
+		// Keys must be equal to or greater than
+		// MinCustomRecordsTlvType.
+		keyOffset := uint64(r.Intn(100))
+		key := MinCustomRecordsTlvType + keyOffset
+
+		// Values are byte slices of any length.
+		value := make([]byte, r.Intn(10))
+		_, err := r.Read(value)
+		require.NoError(t, err)
+
+		customRecords[key] = value
+	}
+
+	// Validate the custom records as a sanity check.
+	err := customRecords.Validate()
+	require.NoError(t, err)
+
+	return customRecords
+}
+
+// TestLightningWireProtocol uses the testing/quick package to create a series
+// of fuzz tests to attempt to break a primary scenario which is implemented as
+// property based testing scenario.
 func TestLightningWireProtocol(t *testing.T) {
 	t.Parallel()
 
-	for msgType := MessageType(0); msgType < MsgEnd; msgType++ {
-		// If MakeEmptyMessage returns an error, then this isn't yet a
-		// used message type.
-		if _, err := MakeEmptyMessage(msgType); err != nil {
-			continue
+	// mainScenario is the primary test that will programmatically be
+	// executed for all registered wire messages. The quick-checker within
+	// testing/quick will attempt to find an input to this function, s.t
+	// the function returns false, if so then we've found an input that
+	// violates our model of the system.
+	mainScenario := func(msg Message) bool {
+		// Give a new message, we'll serialize the message into a new
+		// bytes buffer.
+		var b bytes.Buffer
+		if _, err := WriteMessage(&b, msg, 0); err != nil {
+			t.Fatalf("unable to write msg: %v", err)
+			return false
+		}
+
+		// Next, we'll ensure that the serialized payload (subtracting
+		// the 2 bytes for the message type) is _below_ the specified
+		// max payload size for this message.
+		payloadLen := uint32(b.Len()) - 2
+		if payloadLen > MaxMsgBody {
+			t.Fatalf("msg payload constraint violated: %v > %v",
+				payloadLen, MaxMsgBody)
+			return false
+		}
+
+		// Finally, we'll deserialize the message from the written
+		// buffer, and finally assert that the messages are equal.
+		newMsg, err := ReadMessage(&b, 0)
+		if err != nil {
+			t.Fatalf("unable to read msg: %v", err)
+			return false
 		}
+		if !assert.Equalf(t, msg, newMsg, "message mismatch") {
+			return false
+		}
+
+		return true
+	}
+
+	// customTypeGen is a map of functions that are able to randomly
+	// generate a given type. These functions are needed for types which
+	// are too complex for the testing/quick package to automatically
+	// generate.
+	customTypeGen := map[MessageType]func([]reflect.Value, *rand.Rand){
+		MsgStfu: func(v []reflect.Value, r *rand.Rand) {
+			req := Stfu{}
+			if _, err := r.Read(req.ChanID[:]); err != nil {
+				t.Fatalf("unable to generate ChanID: %v", err)
+			}
+
+			// 1/2 chance of being initiator
+			req.Initiator = r.Intn(2) == 1
+
+			// 1/2 chance additional TLV data.
+			if r.Intn(2) == 0 {
+				req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00}
+			}
 
-		t.Run(msgType.String(), rapid.MakeCheck(func(t *rapid.T) {
-			// Create an empty message of the given type.
-			m, err := MakeEmptyMessage(msgType)
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgInit: func(v []reflect.Value, r *rand.Rand) {
+			req := NewInitMessage(
+				randRawFeatureVector(r),
+				randRawFeatureVector(r),
+			)
+
+			v[0] = reflect.ValueOf(*req)
+		},
+		MsgOpenChannel: func(v []reflect.Value, r *rand.Rand) {
+			req := OpenChannel{
+				FundingAmount:    btcutil.Amount(r.Int63()),
+				PushAmount:       MilliSatoshi(r.Int63()),
+				DustLimit:        btcutil.Amount(r.Int63()),
+				MaxValueInFlight: MilliSatoshi(r.Int63()),
+				ChannelReserve:   btcutil.Amount(r.Int63()),
+				HtlcMinimum:      MilliSatoshi(r.Int31()),
+				FeePerKiloWeight: uint32(r.Int63()),
+				CsvDelay:         uint16(r.Int31()),
+				MaxAcceptedHTLCs: uint16(r.Int31()),
+				ChannelFlags:     FundingFlag(uint8(r.Int31())),
+			}
+
+			if _, err := r.Read(req.ChainHash[:]); err != nil {
+				t.Fatalf("unable to generate chain hash: %v", err)
+				return
+			}
 
-			// An error means this isn't a valid message type, so we
-			// skip it.
+			if _, err := r.Read(req.PendingChannelID[:]); err != nil {
+				t.Fatalf("unable to generate pending chan id: %v", err)
+				return
+			}
+
+			var err error
+			req.FundingKey, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.RevocationPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.PaymentPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.DelayedPaymentPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.HtlcPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.FirstCommitmentPoint, err = randPubKey()
 			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
 				return
 			}
 
-			// The message must support the message type interface.
-			testMsg, ok := m.(TestMessage)
-			require.True(
-				t, ok, "message %v doesn't support TestMessage",
-				msgType,
-			)
+			// 1/2 chance empty TLV records.
+			if r.Intn(2) == 0 {
+				req.UpfrontShutdownScript, err = randDeliveryAddress(r)
+				if err != nil {
+					t.Fatalf("unable to generate delivery address: %v", err)
+					return
+				}
 
-			// Use the RandTestMessage method to create a randomized
-			// message.
-			msg := testMsg.RandTestMessage(t)
-
-			// Serialize the message to a buffer.
-			var b bytes.Buffer
-			writtenBytes, err := WriteMessage(&b, msg, 0)
-			require.NoError(t, err, "unable to write msg")
-
-			// Check that the serialized payload is below the max
-			// payload size, accounting for the message type size.
-			payloadLen := uint32(writtenBytes) - 2
-			require.LessOrEqual(
-				t, payloadLen, uint32(MaxMsgBody),
-				"msg payload constraint violated: %v > %v",
-				payloadLen, MaxMsgBody,
-			)
+				req.ChannelType = new(ChannelType)
+				*req.ChannelType = ChannelType(*randRawFeatureVector(r))
 
-			// Deserialize the message from the buffer.
-			newMsg, err := ReadMessage(&b, 0)
-			require.NoError(t, err, "unable to read msg")
+				req.LeaseExpiry = new(LeaseExpiry)
+				*req.LeaseExpiry = LeaseExpiry(1337)
+
+				//nolint:ll
+				req.LocalNonce = someLocalNonce[NonceRecordTypeT](r)
+			} else {
+				req.UpfrontShutdownScript = []byte{}
+			}
+
+			// 1/2 chance additional TLV data.
+			if r.Intn(2) == 0 {
+				req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgAcceptChannel: func(v []reflect.Value, r *rand.Rand) {
+			req := AcceptChannel{
+				DustLimit:        btcutil.Amount(r.Int63()),
+				MaxValueInFlight: MilliSatoshi(r.Int63()),
+				ChannelReserve:   btcutil.Amount(r.Int63()),
+				MinAcceptDepth:   uint32(r.Int31()),
+				HtlcMinimum:      MilliSatoshi(r.Int31()),
+				CsvDelay:         uint16(r.Int31()),
+				MaxAcceptedHTLCs: uint16(r.Int31()),
+			}
+
+			if _, err := r.Read(req.PendingChannelID[:]); err != nil {
+				t.Fatalf("unable to generate pending chan id: %v", err)
+				return
+			}
+
+			var err error
+			req.FundingKey, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.RevocationPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.PaymentPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.DelayedPaymentPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.HtlcPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+			req.FirstCommitmentPoint, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+
+			// 1/2 chance empty TLV records.
+			if r.Intn(2) == 0 {
+				req.UpfrontShutdownScript, err = randDeliveryAddress(r)
+				if err != nil {
+					t.Fatalf("unable to generate delivery address: %v", err)
+					return
+				}
+
+				req.ChannelType = new(ChannelType)
+				*req.ChannelType = ChannelType(*randRawFeatureVector(r))
+
+				req.LeaseExpiry = new(LeaseExpiry)
+				*req.LeaseExpiry = LeaseExpiry(1337)
+
+				//nolint:ll
+				req.LocalNonce = someLocalNonce[NonceRecordTypeT](r)
+			} else {
+				req.UpfrontShutdownScript = []byte{}
+			}
+
+			// 1/2 chance additional TLV data.
+			if r.Intn(2) == 0 {
+				req.ExtraData = []byte{0xfd, 0x00, 0xff, 0x00}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgFundingCreated: func(v []reflect.Value, r *rand.Rand) {
+			req := FundingCreated{
+				ExtraData: make([]byte, 0),
+			}
+
+			if _, err := r.Read(req.PendingChannelID[:]); err != nil {
+				t.Fatalf("unable to generate pending chan id: %v", err)
+				return
+			}
+
+			if _, err := r.Read(req.FundingPoint.Hash[:]); err != nil {
+				t.Fatalf("unable to generate hash: %v", err)
+				return
+			}
+			req.FundingPoint.Index = uint32(r.Int31()) % math.MaxUint16
+
+			var err error
+			req.CommitSig, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			// 1/2 chance to attach a partial sig.
+			if r.Intn(2) == 0 {
+				req.PartialSig = somePartialSigWithNonce(t, r)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgFundingSigned: func(v []reflect.Value, r *rand.Rand) {
+			var c [32]byte
+			_, err := r.Read(c[:])
+			if err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+
+			req := FundingSigned{
+				ChanID:    ChannelID(c),
+				ExtraData: make([]byte, 0),
+			}
+			req.CommitSig, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			// 1/2 chance to attach a partial sig.
+			if r.Intn(2) == 0 {
+				req.PartialSig = somePartialSigWithNonce(t, r)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgChannelReady: func(v []reflect.Value, r *rand.Rand) {
+			var c [32]byte
+			_, err := r.Read(c[:])
+			require.NoError(t, err)
+
+			pubKey, err := randPubKey()
+			require.NoError(t, err)
+
+			req := NewChannelReady(c, pubKey)
+
+			if r.Int31()%2 == 0 {
+				scid := NewShortChanIDFromInt(uint64(r.Int63()))
+				req.AliasScid = &scid
+
+				//nolint:ll
+				req.NextLocalNonce = someLocalNonce[NonceRecordTypeT](r)
+			}
+
+			if r.Int31()%2 == 0 {
+				nodeNonce := tlv.ZeroRecordT[
+					tlv.TlvType0, Musig2Nonce,
+				]()
+				nodeNonce.Val = randLocalNonce(r)
+				req.AnnouncementNodeNonce = tlv.SomeRecordT(
+					nodeNonce,
+				)
+
+				btcNonce := tlv.ZeroRecordT[
+					tlv.TlvType2, Musig2Nonce,
+				]()
+				btcNonce.Val = randLocalNonce(r)
+				req.AnnouncementBitcoinNonce = tlv.SomeRecordT(
+					btcNonce,
+				)
+			}
+
+			v[0] = reflect.ValueOf(*req)
+		},
+		MsgShutdown: func(v []reflect.Value, r *rand.Rand) {
+			var c [32]byte
+			_, err := r.Read(c[:])
+			if err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+
+			shutdownAddr, err := randDeliveryAddress(r)
+			if err != nil {
+				t.Fatalf("unable to generate delivery "+
+					"address: %v", err)
+				return
+			}
+
+			req := Shutdown{
+				ChannelID: ChannelID(c),
+				Address:   shutdownAddr,
+			}
+
+			if r.Int31()%2 == 0 {
+				//nolint:ll
+				req.ShutdownNonce = someLocalNonce[ShutdownNonceType](r)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgClosingSigned: func(v []reflect.Value, r *rand.Rand) {
+			req := ClosingSigned{
+				FeeSatoshis: btcutil.Amount(r.Int63()),
+				ExtraData:   make([]byte, 0),
+			}
+			var err error
+			req.Signature, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			if _, err := r.Read(req.ChannelID[:]); err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+
+			if r.Int31()%2 == 0 {
+				req.PartialSig = somePartialSig(t, r)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgDynPropose: func(v []reflect.Value, r *rand.Rand) {
+			var dp DynPropose
+			rand.Read(dp.ChanID[:])
+
+			if rand.Uint32()%2 == 0 {
+				v := btcutil.Amount(rand.Uint32())
+				dp.DustLimit = fn.Some(v)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v := MilliSatoshi(rand.Uint32())
+				dp.MaxValueInFlight = fn.Some(v)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v := btcutil.Amount(rand.Uint32())
+				dp.ChannelReserve = fn.Some(v)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v := uint16(rand.Uint32())
+				dp.CsvDelay = fn.Some(v)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v := uint16(rand.Uint32())
+				dp.MaxAcceptedHTLCs = fn.Some(v)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v, _ := btcec.NewPrivateKey()
+				dp.FundingKey = fn.Some(*v.PubKey())
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v := ChannelType(*NewRawFeatureVector())
+				dp.ChannelType = fn.Some(v)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				v := chainfee.SatPerKWeight(rand.Uint32())
+				dp.KickoffFeerate = fn.Some(v)
+			}
+
+			v[0] = reflect.ValueOf(dp)
+		},
+		MsgDynReject: func(v []reflect.Value, r *rand.Rand) {
+			var dr DynReject
+			rand.Read(dr.ChanID[:])
+
+			features := NewRawFeatureVector()
+			if rand.Uint32()%2 == 0 {
+				features.Set(FeatureBit(DPDustLimitSatoshis))
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(
+					FeatureBit(DPMaxHtlcValueInFlightMsat),
+				)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(
+					FeatureBit(DPChannelReserveSatoshis),
+				)
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(FeatureBit(DPToSelfDelay))
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(FeatureBit(DPMaxAcceptedHtlcs))
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(FeatureBit(DPFundingPubkey))
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(FeatureBit(DPChannelType))
+			}
+
+			if rand.Uint32()%2 == 0 {
+				features.Set(FeatureBit(DPKickoffFeerate))
+			}
+			dr.UpdateRejections = *features
+
+			v[0] = reflect.ValueOf(dr)
+		},
+		MsgDynAck: func(v []reflect.Value, r *rand.Rand) {
+			var da DynAck
+
+			rand.Read(da.ChanID[:])
+			if rand.Uint32()%2 == 0 {
+				var nonce Musig2Nonce
+				rand.Read(nonce[:])
+				da.LocalNonce = fn.Some(nonce)
+			}
+
+			v[0] = reflect.ValueOf(da)
+		},
+		MsgKickoffSig: func(v []reflect.Value, r *rand.Rand) {
+			ks := KickoffSig{
+				ExtraData: make([]byte, 0),
+			}
+
+			rand.Read(ks.ChanID[:])
+			rand.Read(ks.Signature.bytes[:])
+
+			v[0] = reflect.ValueOf(ks)
+		},
+		MsgCommitSig: func(v []reflect.Value, r *rand.Rand) {
+			req := NewCommitSig()
+			if _, err := r.Read(req.ChanID[:]); err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+
+			var err error
+			req.CommitSig, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			// Only create the slice if there will be any signatures
+			// in it to prevent false positive test failures due to
+			// an empty slice versus a nil slice.
+			numSigs := uint16(r.Int31n(500))
+			if numSigs > 0 {
+				req.HtlcSigs = make([]Sig, numSigs)
+			}
+			for i := 0; i < int(numSigs); i++ {
+				req.HtlcSigs[i], err = NewSigFromSignature(
+					testSig,
+				)
+				if err != nil {
+					t.Fatalf("unable to parse sig: %v", err)
+					return
+				}
+			}
+
+			req.CustomRecords = randCustomRecords(t, r)
+
+			// 50/50 chance to attach a partial sig.
+			if r.Int31()%2 == 0 {
+				req.PartialSig = somePartialSigWithNonce(t, r)
+			}
+
+			v[0] = reflect.ValueOf(*req)
+		},
+		MsgRevokeAndAck: func(v []reflect.Value, r *rand.Rand) {
+			req := NewRevokeAndAck()
+			if _, err := r.Read(req.ChanID[:]); err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+			if _, err := r.Read(req.Revocation[:]); err != nil {
+				t.Fatalf("unable to generate bytes: %v", err)
+				return
+			}
+			var err error
+			req.NextRevocationKey, err = randPubKey()
+			if err != nil {
+				t.Fatalf("unable to generate key: %v", err)
+				return
+			}
+
+			// 50/50 chance to attach a local nonce.
+			if r.Int31()%2 == 0 {
+				//nolint:ll
+				req.LocalNonce = someLocalNonce[NonceRecordTypeT](r)
+			}
+
+			v[0] = reflect.ValueOf(*req)
+		},
+		MsgChannelAnnouncement: func(v []reflect.Value, r *rand.Rand) {
+			var err error
+			req := ChannelAnnouncement1{
+				ShortChannelID: NewShortChanIDFromInt(
+					uint64(r.Int63()),
+				),
+				NodeID1:         randRawKey(t),
+				NodeID2:         randRawKey(t),
+				BitcoinKey1:     randRawKey(t),
+				BitcoinKey2:     randRawKey(t),
+				Features:        randRawFeatureVector(r),
+				ExtraOpaqueData: make([]byte, 0),
+			}
+			req.NodeSig1, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+			req.NodeSig2, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+			req.BitcoinSig1, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+			req.BitcoinSig2, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			if _, err := r.Read(req.ChainHash[:]); err != nil {
+				t.Fatalf("unable to generate chain hash: %v", err)
+				return
+			}
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make([]byte, numExtraBytes)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				if err != nil {
+					t.Fatalf("unable to generate opaque "+
+						"bytes: %v", err)
+					return
+				}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgNodeAnnouncement: func(v []reflect.Value, r *rand.Rand) {
+			var err error
+			req := NodeAnnouncement{
+				NodeID:    randRawKey(t),
+				Features:  randRawFeatureVector(r),
+				Timestamp: uint32(r.Int31()),
+				Alias:     randAlias(r),
+				RGBColor: color.RGBA{
+					R: uint8(r.Int31()),
+					G: uint8(r.Int31()),
+					B: uint8(r.Int31()),
+				},
+				ExtraOpaqueData: make([]byte, 0),
+			}
+			req.Signature, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			req.Addresses, err = randAddrs(r)
+			if err != nil {
+				t.Fatalf("unable to generate addresses: %v", err)
+			}
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make([]byte, numExtraBytes)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				if err != nil {
+					t.Fatalf("unable to generate opaque "+
+						"bytes: %v", err)
+					return
+				}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgChannelUpdate: func(v []reflect.Value, r *rand.Rand) {
+			var err error
+
+			msgFlags := ChanUpdateMsgFlags(r.Int31())
+			maxHtlc := MilliSatoshi(r.Int63())
+
+			// We make the max_htlc field zero if it is not flagged
+			// as being part of the ChannelUpdate, to pass
+			// serialization tests, as it will be ignored if the bit
+			// is not set.
+			if msgFlags&ChanUpdateRequiredMaxHtlc == 0 {
+				maxHtlc = 0
+			}
+
+			req := ChannelUpdate1{
+				ShortChannelID: NewShortChanIDFromInt(
+					uint64(r.Int63()),
+				),
+				Timestamp:       uint32(r.Int31()),
+				MessageFlags:    msgFlags,
+				ChannelFlags:    ChanUpdateChanFlags(r.Int31()),
+				TimeLockDelta:   uint16(r.Int31()),
+				HtlcMinimumMsat: MilliSatoshi(r.Int63()),
+				HtlcMaximumMsat: maxHtlc,
+				BaseFee:         uint32(r.Int31()),
+				FeeRate:         uint32(r.Int31()),
+				ExtraOpaqueData: make([]byte, 0),
+			}
+			req.Signature, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			if _, err := r.Read(req.ChainHash[:]); err != nil {
+				t.Fatalf("unable to generate chain hash: %v", err)
+				return
+			}
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make([]byte, numExtraBytes)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				if err != nil {
+					t.Fatalf("unable to generate opaque "+
+						"bytes: %v", err)
+					return
+				}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgAnnounceSignatures: func(v []reflect.Value, r *rand.Rand) {
+			var err error
+			req := AnnounceSignatures1{
+				ShortChannelID:  NewShortChanIDFromInt(uint64(r.Int63())),
+				ExtraOpaqueData: make([]byte, 0),
+			}
+
+			req.NodeSignature, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			req.BitcoinSignature, err = NewSigFromSignature(testSig)
+			if err != nil {
+				t.Fatalf("unable to parse sig: %v", err)
+				return
+			}
+
+			if _, err := r.Read(req.ChannelID[:]); err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make([]byte, numExtraBytes)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				if err != nil {
+					t.Fatalf("unable to generate opaque "+
+						"bytes: %v", err)
+					return
+				}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgChannelReestablish: func(v []reflect.Value, r *rand.Rand) {
+			req := ChannelReestablish{
+				NextLocalCommitHeight:  uint64(r.Int63()),
+				RemoteCommitTailHeight: uint64(r.Int63()),
+				ExtraData:              make([]byte, 0),
+			}
+
+			// With a 50/50 probability, we'll include the
+			// additional fields so we can test our ability to
+			// properly parse, and write out the optional fields.
+			if r.Int()%2 == 0 {
+				_, err := r.Read(req.LastRemoteCommitSecret[:])
+				if err != nil {
+					t.Fatalf("unable to read commit secret: %v", err)
+					return
+				}
+
+				req.LocalUnrevokedCommitPoint, err = randPubKey()
+				if err != nil {
+					t.Fatalf("unable to generate key: %v", err)
+					return
+				}
+
+				//nolint:ll
+				req.LocalNonce = someLocalNonce[NonceRecordTypeT](r)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgGossipTimestampRange: func(v []reflect.Value, r *rand.Rand) {
+			req := GossipTimestampRange{
+				FirstTimestamp: rand.Uint32(),
+				TimestampRange: rand.Uint32(),
+				ExtraData:      make([]byte, 0),
+			}
+
+			_, err := rand.Read(req.ChainHash[:])
+			require.NoError(t, err)
+
+			// Sometimes add a block range.
+			if r.Int31()%2 == 0 {
+				firstBlock := tlv.ZeroRecordT[
+					tlv.TlvType2, uint32,
+				]()
+				firstBlock.Val = rand.Uint32()
+				req.FirstBlockHeight = tlv.SomeRecordT(
+					firstBlock,
+				)
+
+				blockRange := tlv.ZeroRecordT[
+					tlv.TlvType4, uint32,
+				]()
+				blockRange.Val = rand.Uint32()
+				req.BlockRange = tlv.SomeRecordT(blockRange)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgQueryShortChanIDs: func(v []reflect.Value, r *rand.Rand) {
+			req := QueryShortChanIDs{
+				ExtraData: make([]byte, 0),
+			}
+
+			// With a 50/50 change, we'll either use zlib encoding,
+			// or regular encoding.
+			if r.Int31()%2 == 0 {
+				req.EncodingType = EncodingSortedZlib
+			} else {
+				req.EncodingType = EncodingSortedPlain
+			}
+
+			if _, err := rand.Read(req.ChainHash[:]); err != nil {
+				t.Fatalf("unable to read chain hash: %v", err)
+				return
+			}
+
+			numChanIDs := rand.Int31n(5000)
+			for i := int32(0); i < numChanIDs; i++ {
+				req.ShortChanIDs = append(req.ShortChanIDs,
+					NewShortChanIDFromInt(uint64(r.Int63())))
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgReplyChannelRange: func(v []reflect.Value, r *rand.Rand) {
+			req := ReplyChannelRange{
+				FirstBlockHeight: uint32(r.Int31()),
+				NumBlocks:        uint32(r.Int31()),
+				ExtraData:        make([]byte, 0),
+			}
+
+			if _, err := rand.Read(req.ChainHash[:]); err != nil {
+				t.Fatalf("unable to read chain hash: %v", err)
+				return
+			}
+
+			req.Complete = uint8(r.Int31n(2))
+
+			// With a 50/50 change, we'll either use zlib encoding,
+			// or regular encoding.
+			if r.Int31()%2 == 0 {
+				req.EncodingType = EncodingSortedZlib
+			} else {
+				req.EncodingType = EncodingSortedPlain
+			}
+
+			numChanIDs := rand.Int31n(4000)
+			for i := int32(0); i < numChanIDs; i++ {
+				req.ShortChanIDs = append(req.ShortChanIDs,
+					NewShortChanIDFromInt(uint64(r.Int63())))
+			}
+
+			// With a 50/50 chance, add some timestamps.
+			if r.Int31()%2 == 0 {
+				for i := int32(0); i < numChanIDs; i++ {
+					timestamps := ChanUpdateTimestamps{
+						Timestamp1: rand.Uint32(),
+						Timestamp2: rand.Uint32(),
+					}
+					req.Timestamps = append(
+						req.Timestamps, timestamps,
+					)
+				}
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgQueryChannelRange: func(v []reflect.Value, r *rand.Rand) {
+			req := QueryChannelRange{
+				FirstBlockHeight: uint32(r.Int31()),
+				NumBlocks:        uint32(r.Int31()),
+				ExtraData:        make([]byte, 0),
+			}
+
+			_, err := rand.Read(req.ChainHash[:])
+			require.NoError(t, err)
+
+			// With a 50/50 change, we'll set a query option.
+			if r.Int31()%2 == 0 {
+				req.QueryOptions = NewTimestampQueryOption()
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgPing: func(v []reflect.Value, r *rand.Rand) {
+			// We use a special message generator here to ensure we
+			// don't generate ping messages that are too large,
+			// which'll cause the test to fail.
+			//
+			// We'll allow the test to generate padding bytes up to
+			// the max message limit, factoring in the 2 bytes for
+			// the num pong bytes and 2 bytes for encoding the
+			// length of the padding bytes.
+			paddingBytes := make([]byte, rand.Intn(MaxMsgBody-3))
+			req := Ping{
+				NumPongBytes: uint16(r.Intn(MaxPongBytes + 1)),
+				PaddingBytes: paddingBytes,
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgClosingComplete: func(v []reflect.Value, r *rand.Rand) {
+			var c [32]byte
+			_, err := r.Read(c[:])
+			if err != nil {
+				t.Fatalf("unable to generate chan id: %v",
+					err)
+				return
+			}
+
+			req := ClosingComplete{
+				ChannelID:   ChannelID(c),
+				FeeSatoshis: btcutil.Amount(r.Int63()),
+				LockTime:    uint32(r.Int63()),
+				ClosingSigs: ClosingSigs{},
+			}
+			req.CloserScript, err = randDeliveryAddress(r)
+			if err != nil {
+				t.Fatalf("unable to generate delivery "+
+					"address: %v", err)
+				return
+			}
+			req.CloseeScript, err = randDeliveryAddress(r)
+			if err != nil {
+				t.Fatalf("unable to generate delivery "+
+					"address: %v", err)
+				return
+			}
+
+			if r.Intn(2) == 0 {
+				sig := req.CloserNoClosee.Zero()
+				_, err := r.Read(sig.Val.bytes[:])
+				if err != nil {
+					t.Fatalf("unable to generate sig: %v",
+						err)
+					return
+				}
+
+				req.CloserNoClosee = tlv.SomeRecordT(sig)
+			}
+			if r.Intn(2) == 0 {
+				sig := req.NoCloserClosee.Zero()
+				_, err := r.Read(sig.Val.bytes[:])
+				if err != nil {
+					t.Fatalf("unable to generate sig: %v",
+						err)
+					return
+				}
+
+				req.NoCloserClosee = tlv.SomeRecordT(sig)
+			}
+			if r.Intn(2) == 0 {
+				sig := req.CloserAndClosee.Zero()
+				_, err := r.Read(sig.Val.bytes[:])
+				if err != nil {
+					t.Fatalf("unable to generate sig: %v",
+						err)
+					return
+				}
+
+				req.CloserAndClosee = tlv.SomeRecordT(sig)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgClosingSig: func(v []reflect.Value, r *rand.Rand) {
+			var c [32]byte
+			_, err := r.Read(c[:])
+			if err != nil {
+				t.Fatalf("unable to generate chan id: %v", err)
+				return
+			}
+
+			req := ClosingSig{
+				ChannelID:   ChannelID(c),
+				ClosingSigs: ClosingSigs{},
+				FeeSatoshis: btcutil.Amount(r.Int63()),
+				LockTime:    uint32(r.Int63()),
+			}
+			req.CloserScript, err = randDeliveryAddress(r)
+			if err != nil {
+				t.Fatalf("unable to generate delivery "+
+					"address: %v", err)
+				return
+			}
+			req.CloseeScript, err = randDeliveryAddress(r)
+			if err != nil {
+				t.Fatalf("unable to generate delivery "+
+					"address: %v", err)
+				return
+			}
+
+			if r.Intn(2) == 0 {
+				sig := req.CloserNoClosee.Zero()
+				_, err := r.Read(sig.Val.bytes[:])
+				if err != nil {
+					t.Fatalf("unable to generate sig: %v",
+						err)
+					return
+				}
+
+				req.CloserNoClosee = tlv.SomeRecordT(sig)
+			}
+			if r.Intn(2) == 0 {
+				sig := req.NoCloserClosee.Zero()
+				_, err := r.Read(sig.Val.bytes[:])
+				if err != nil {
+					t.Fatalf("unable to generate sig: %v",
+						err)
+					return
+				}
+
+				req.NoCloserClosee = tlv.SomeRecordT(sig)
+			}
+			if r.Intn(2) == 0 {
+				sig := req.CloserAndClosee.Zero()
+				_, err := r.Read(sig.Val.bytes[:])
+				if err != nil {
+					t.Fatalf("unable to generate sig: %v",
+						err)
+					return
+				}
+
+				req.CloserAndClosee = tlv.SomeRecordT(sig)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgUpdateAddHTLC: func(v []reflect.Value, r *rand.Rand) {
+			req := &UpdateAddHTLC{
+				ID:     r.Uint64(),
+				Amount: MilliSatoshi(r.Uint64()),
+				Expiry: r.Uint32(),
+			}
+
+			_, err := r.Read(req.ChanID[:])
+			require.NoError(t, err)
+
+			_, err = r.Read(req.PaymentHash[:])
+			require.NoError(t, err)
+
+			_, err = r.Read(req.OnionBlob[:])
+			require.NoError(t, err)
+
+			req.CustomRecords = randCustomRecords(t, r)
+
+			// Generate a blinding point 50% of the time, since not
+			// all update adds will use route blinding.
+			if r.Int31()%2 == 0 {
+				pubkey, err := randPubKey()
+				if err != nil {
+					t.Fatalf("unable to generate key: %v",
+						err)
+
+					return
+				}
+
+				req.BlindingPoint = tlv.SomeRecordT(
+					tlv.NewPrimitiveRecord[tlv.TlvType0](
+						pubkey,
+					),
+				)
+			}
+
+			v[0] = reflect.ValueOf(*req)
+		},
+		MsgUpdateFulfillHTLC: func(v []reflect.Value, r *rand.Rand) {
+			req := &UpdateFulfillHTLC{
+				ID: r.Uint64(),
+			}
+
+			_, err := r.Read(req.ChanID[:])
+			require.NoError(t, err)
+
+			_, err = r.Read(req.PaymentPreimage[:])
+			require.NoError(t, err)
+
+			req.CustomRecords = randCustomRecords(t, r)
+
+			// Generate some random TLV records 50% of the time.
+			if r.Int31()%2 == 0 {
+				req.ExtraData = []byte{
+					0x01, 0x03, 1, 2, 3,
+					0x02, 0x03, 4, 5, 6,
+				}
+			}
+
+			v[0] = reflect.ValueOf(*req)
+		},
+		MsgAnnounceSignatures2: func(v []reflect.Value,
+			r *rand.Rand) {
+
+			req := AnnounceSignatures2{
+				ShortChannelID: NewShortChanIDFromInt(
+					uint64(r.Int63()),
+				),
+				ExtraOpaqueData: make([]byte, 0),
+			}
+
+			_, err := r.Read(req.ChannelID[:])
+			require.NoError(t, err)
+
+			partialSig, err := randPartialSig(r)
+			require.NoError(t, err)
+
+			req.PartialSignature = *partialSig
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make(
+					[]byte, numExtraBytes,
+				)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				require.NoError(t, err)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgChannelAnnouncement2: func(v []reflect.Value, r *rand.Rand) {
+			req := ChannelAnnouncement2{
+				Signature:       testSchnorrSig,
+				ExtraOpaqueData: make([]byte, 0),
+			}
+
+			req.ShortChannelID.Val = NewShortChanIDFromInt(
+				uint64(r.Int63()),
+			)
+			req.Capacity.Val = rand.Uint64()
+
+			req.Features.Val = *randRawFeatureVector(r)
+
+			req.NodeID1.Val = randRawKey(t)
+			req.NodeID2.Val = randRawKey(t)
+
+			// Sometimes set chain hash to bitcoin mainnet genesis
+			// hash.
+			req.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
+			if r.Int31()%2 == 0 {
+				_, err := r.Read(req.ChainHash.Val[:])
+				require.NoError(t, err)
+			}
+
+			// Sometimes set the bitcoin keys.
+			if r.Int31()%2 == 0 {
+				btcKey1 := tlv.ZeroRecordT[
+					tlv.TlvType12, [33]byte,
+				]()
+				btcKey1.Val = randRawKey(t)
+				req.BitcoinKey1 = tlv.SomeRecordT(btcKey1)
+
+				btcKey2 := tlv.ZeroRecordT[
+					tlv.TlvType14, [33]byte,
+				]()
+				btcKey2.Val = randRawKey(t)
+				req.BitcoinKey2 = tlv.SomeRecordT(btcKey2)
+
+				// Occasionally also set the merkle root hash.
+				if r.Int31()%2 == 0 {
+					hash := tlv.ZeroRecordT[
+						tlv.TlvType16, [32]byte,
+					]()
+
+					_, err := r.Read(hash.Val[:])
+					require.NoError(t, err)
+
+					req.MerkleRootHash = tlv.SomeRecordT(
+						hash,
+					)
+				}
+			}
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make(
+					[]byte, numExtraBytes,
+				)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				require.NoError(t, err)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+		MsgChannelUpdate2: func(v []reflect.Value, r *rand.Rand) {
+			req := ChannelUpdate2{
+				Signature:       testSchnorrSig,
+				ExtraOpaqueData: make([]byte, 0),
+			}
+
+			req.ShortChannelID.Val = NewShortChanIDFromInt(
+				uint64(r.Int63()),
+			)
+			req.BlockHeight.Val = r.Uint32()
+			req.HTLCMaximumMsat.Val = MilliSatoshi(r.Uint64())
+
+			// Sometimes set chain hash to bitcoin mainnet genesis
+			// hash.
+			req.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
+			if r.Int31()%2 == 0 {
+				_, err := r.Read(req.ChainHash.Val[:])
+				require.NoError(t, err)
+			}
+
+			// Sometimes use default htlc min msat.
+			req.HTLCMinimumMsat.Val = defaultHtlcMinMsat
+			if r.Int31()%2 == 0 {
+				req.HTLCMinimumMsat.Val = MilliSatoshi(
+					r.Uint64(),
+				)
+			}
+
+			// Sometimes set the cltv expiry delta to the default.
+			req.CLTVExpiryDelta.Val = defaultCltvExpiryDelta
+			if r.Int31()%2 == 0 {
+				req.CLTVExpiryDelta.Val = uint16(r.Int31())
+			}
+
+			// Sometimes use default fee base.
+			req.FeeBaseMsat.Val = defaultFeeBaseMsat
+			if r.Int31()%2 == 0 {
+				req.FeeBaseMsat.Val = r.Uint32()
+			}
+
+			// Sometimes use default proportional fee.
+			req.FeeProportionalMillionths.Val =
+				defaultFeeProportionalMillionths
+			if r.Int31()%2 == 0 {
+				req.FeeProportionalMillionths.Val = r.Uint32()
+			}
+
+			// Alternate between the two direction possibilities.
+			if r.Int31()%2 == 0 {
+				req.SecondPeer = tlv.SomeRecordT(
+					tlv.ZeroRecordT[tlv.TlvType8, TrueBoolean](), //nolint:ll
+				)
+			}
+
+			// Sometimes set the incoming disabled flag.
+			if r.Int31()%2 == 0 {
+				req.DisabledFlags.Val |=
+					ChanUpdateDisableIncoming
+			}
+
+			// Sometimes set the outgoing disabled flag.
+			if r.Int31()%2 == 0 {
+				req.DisabledFlags.Val |=
+					ChanUpdateDisableOutgoing
+			}
+
+			numExtraBytes := r.Int31n(1000)
+			if numExtraBytes > 0 {
+				req.ExtraOpaqueData = make(
+					[]byte, numExtraBytes,
+				)
+				_, err := r.Read(req.ExtraOpaqueData[:])
+				require.NoError(t, err)
+			}
+
+			v[0] = reflect.ValueOf(req)
+		},
+	}
+
+	// With the above types defined, we'll now generate a slice of
+	// scenarios to feed into quick.Check. The function scans in input
+	// space of the target function under test, so we'll need to create a
+	// series of wrapper functions to force it to iterate over the target
+	// types, but re-use the mainScenario defined above.
+	tests := []struct {
+		msgType  MessageType
+		scenario interface{}
+	}{
+		{
+			msgType: MsgStfu,
+			scenario: func(m Stfu) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgInit,
+			scenario: func(m Init) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgWarning,
+			scenario: func(m Warning) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgError,
+			scenario: func(m Error) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgPing,
+			scenario: func(m Ping) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgPong,
+			scenario: func(m Pong) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgOpenChannel,
+			scenario: func(m OpenChannel) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgAcceptChannel,
+			scenario: func(m AcceptChannel) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgFundingCreated,
+			scenario: func(m FundingCreated) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgFundingSigned,
+			scenario: func(m FundingSigned) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgChannelReady,
+			scenario: func(m ChannelReady) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgShutdown,
+			scenario: func(m Shutdown) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgClosingSigned,
+			scenario: func(m ClosingSigned) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgDynPropose,
+			scenario: func(m DynPropose) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgDynReject,
+			scenario: func(m DynReject) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgDynAck,
+			scenario: func(m DynAck) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgKickoffSig,
+			scenario: func(m KickoffSig) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgUpdateAddHTLC,
+			scenario: func(m UpdateAddHTLC) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgUpdateFulfillHTLC,
+			scenario: func(m UpdateFulfillHTLC) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgUpdateFailHTLC,
+			scenario: func(m UpdateFailHTLC) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgCommitSig,
+			scenario: func(m CommitSig) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgRevokeAndAck,
+			scenario: func(m RevokeAndAck) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgUpdateFee,
+			scenario: func(m UpdateFee) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+
+			msgType: MsgUpdateFailMalformedHTLC,
+			scenario: func(m UpdateFailMalformedHTLC) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgChannelReestablish,
+			scenario: func(m ChannelReestablish) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgChannelAnnouncement,
+			scenario: func(m ChannelAnnouncement1) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgNodeAnnouncement,
+			scenario: func(m NodeAnnouncement) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgChannelUpdate,
+			scenario: func(m ChannelUpdate1) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgAnnounceSignatures,
+			scenario: func(m AnnounceSignatures1) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgGossipTimestampRange,
+			scenario: func(m GossipTimestampRange) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgQueryShortChanIDs,
+			scenario: func(m QueryShortChanIDs) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgReplyShortChanIDsEnd,
+			scenario: func(m ReplyShortChanIDsEnd) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgQueryChannelRange,
+			scenario: func(m QueryChannelRange) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgReplyChannelRange,
+			scenario: func(m ReplyChannelRange) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgClosingComplete,
+			scenario: func(m ClosingComplete) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgClosingSig,
+			scenario: func(m ClosingSig) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgAnnounceSignatures2,
+			scenario: func(m AnnounceSignatures2) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgChannelAnnouncement2,
+			scenario: func(m ChannelAnnouncement2) bool {
+				return mainScenario(&m)
+			},
+		},
+		{
+			msgType: MsgChannelUpdate2,
+			scenario: func(m ChannelUpdate2) bool {
+				return mainScenario(&m)
+			},
+		},
+	}
+	for _, test := range tests {
+		t.Run(test.msgType.String(), func(t *testing.T) {
+			var config *quick.Config
+
+			// If the type defined is within the custom type gen
+			// map above, then we'll modify the default config to
+			// use this Value function that knows how to generate
+			// the proper types.
+			if valueGen, ok := customTypeGen[test.msgType]; ok {
+				config = &quick.Config{
+					Values: valueGen,
+				}
+			}
+
+			t.Logf("Running fuzz tests for msgType=%v",
+				test.msgType)
+
+			err := quick.Check(test.scenario, config)
+			if err != nil {
+				t.Fatalf("fuzz checks for msg=%v failed: %v",
+					test.msgType, err)
+			}
+		})
+	}
 
-			// Verify the deserialized message matches the original.
-			require.Equal(
-				t, msg, newMsg,
-				"message mismatch for type %s", msgType,
-			)
-		}))
-	}
 }
 
 func init() {
diff --git a/lnwire/serialized_size_test.go b/lnwire/serialized_size_test.go
deleted file mode 100644
index cce315fa2..000000000
--- a/lnwire/serialized_size_test.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package lnwire
-
-import (
-	"bytes"
-	"math"
-	"testing"
-
-	"github.com/stretchr/testify/require"
-	"pgregory.net/rapid"
-)
-
-// TestSerializedSize uses property-based testing to verify that
-// SerializedSize returns the correct value for randomly generated messages.
-func TestSerializedSize(t *testing.T) {
-	t.Parallel()
-
-	rapid.Check(t, func(t *rapid.T) {
-		// Pick a random message type.
-		msgType := rapid.Custom(func(t *rapid.T) MessageType {
-			return MessageType(
-				rapid.IntRange(
-					0, int(math.MaxUint16),
-				).Draw(t, "msgType"),
-			)
-		}).Draw(t, "msgType")
-
-		// Create an empty message of the given type.
-		m, err := MakeEmptyMessage(msgType)
-
-		// An error means this isn't a valid message type, so we skip
-		// it.
-		if err != nil {
-			return
-		}
-
-		testMsg, ok := m.(TestMessage)
-		require.True(
-			t, ok, "message type %s does not "+
-				"implement TestMessage", msgType,
-		)
-
-		// Use the testMsg to make a new random message.
-		msg := testMsg.RandTestMessage(t)
-
-		// Type assertion to ensure the message implements
-		// SizeableMessage.
-		sizeMsg, ok := msg.(SizeableMessage)
-		require.True(
-			t, ok, "message type %s does not "+
-				"implement SizeableMessage", msgType,
-		)
-
-		// Get the size using SerializedSize.
-		size, err := sizeMsg.SerializedSize()
-		require.NoError(t, err, "SerializedSize error")
-
-		// Get the size by actually serializing the message.
-		var buf bytes.Buffer
-		writtenBytes, err := WriteMessage(&buf, msg, 0)
-		require.NoError(t, err, "WriteMessage error")
-
-		// The SerializedSize should match the number of bytes written.
-		require.Equal(t, uint32(writtenBytes), size,
-			"SerializedSize = %d, actual bytes "+
-				"written = %d for message type %s (populated)",
-			size, writtenBytes, msgType)
-	})
-}
diff --git a/lnwire/test_message.go b/lnwire/test_message.go
deleted file mode 100644
index 8b3d98400..000000000
--- a/lnwire/test_message.go
+++ /dev/null
@@ -1,1669 +0,0 @@
-package lnwire
-
-import (
-	"bytes"
-	"fmt"
-	"image/color"
-	"math"
-
-	"github.com/btcsuite/btcd/btcec/v2"
-	"github.com/btcsuite/btcd/btcutil"
-	"github.com/btcsuite/btcd/chaincfg/chainhash"
-	"github.com/lightningnetwork/lnd/fn/v2"
-	"github.com/lightningnetwork/lnd/lnwallet/chainfee"
-	"github.com/lightningnetwork/lnd/tlv"
-	"pgregory.net/rapid"
-)
-
-// TestMessage is an interface that extends the base Message interface with a
-// method to populate the message with random testing data.
-type TestMessage interface {
-	Message
-
-	// RandTestMessage populates the message with random data suitable for
-	// testing. It uses the rapid testing framework to generate random
-	// values.
-	RandTestMessage(t *rapid.T) Message
-}
-
-// A compile time check to ensure AcceptChannel implements the TestMessage
-// interface.
-var _ TestMessage = (*AcceptChannel)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *AcceptChannel) RandTestMessage(t *rapid.T) Message {
-	var pendingChanID [32]byte
-	pendingChanIDBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
-		t, "pendingChanID",
-	)
-	copy(pendingChanID[:], pendingChanIDBytes)
-
-	var channelType *ChannelType
-	includeChannelType := rapid.Bool().Draw(t, "includeChannelType")
-	includeLeaseExpiry := rapid.Bool().Draw(t, "includeLeaseExpiry")
-	includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
-
-	if includeChannelType {
-		channelType = RandChannelType(t)
-	}
-
-	var leaseExpiry *LeaseExpiry
-	if includeLeaseExpiry {
-		leaseExpiry = RandLeaseExpiry(t)
-	}
-
-	var localNonce OptMusig2NonceTLV
-	if includeLocalNonce {
-		nonce := RandMusig2Nonce(t)
-		localNonce = tlv.SomeRecordT(
-			tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
-		)
-	}
-
-	return &AcceptChannel{
-		PendingChannelID: pendingChanID,
-		DustLimit: btcutil.Amount(
-			rapid.IntRange(100, 1000).Draw(t, "dustLimit"),
-		),
-		MaxValueInFlight: MilliSatoshi(
-			rapid.IntRange(10000, 1000000).Draw(
-				t, "maxValueInFlight",
-			),
-		),
-		ChannelReserve: btcutil.Amount(
-			rapid.IntRange(1000, 10000).Draw(t, "channelReserve"),
-		),
-		HtlcMinimum: MilliSatoshi(
-			rapid.IntRange(1, 1000).Draw(t, "htlcMinimum"),
-		),
-		MinAcceptDepth: uint32(
-			rapid.IntRange(1, 10).Draw(t, "minAcceptDepth"),
-		),
-		CsvDelay: uint16(
-			rapid.IntRange(144, 1000).Draw(t, "csvDelay"),
-		),
-		MaxAcceptedHTLCs: uint16(
-			rapid.IntRange(10, 500).Draw(t, "maxAcceptedHTLCs"),
-		),
-		FundingKey:            RandPubKey(t),
-		RevocationPoint:       RandPubKey(t),
-		PaymentPoint:          RandPubKey(t),
-		DelayedPaymentPoint:   RandPubKey(t),
-		HtlcPoint:             RandPubKey(t),
-		FirstCommitmentPoint:  RandPubKey(t),
-		UpfrontShutdownScript: RandDeliveryAddress(t),
-		ChannelType:           channelType,
-		LeaseExpiry:           leaseExpiry,
-		LocalNonce:            localNonce,
-		ExtraData:             RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure AnnounceSignatures1 implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*AnnounceSignatures1)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *AnnounceSignatures1) RandTestMessage(t *rapid.T) Message {
-	return &AnnounceSignatures1{
-		ChannelID:        RandChannelID(t),
-		ShortChannelID:   RandShortChannelID(t),
-		NodeSignature:    RandSignature(t),
-		BitcoinSignature: RandSignature(t),
-		ExtraOpaqueData:  RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure AnnounceSignatures2 implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*AnnounceSignatures2)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *AnnounceSignatures2) RandTestMessage(t *rapid.T) Message {
-	return &AnnounceSignatures2{
-		ChannelID:        RandChannelID(t),
-		ShortChannelID:   RandShortChannelID(t),
-		PartialSignature: *RandPartialSig(t),
-		ExtraOpaqueData:  RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure ChannelAnnouncement1 implements the
-// TestMessage interface.
-var _ TestMessage = (*ChannelAnnouncement1)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *ChannelAnnouncement1) RandTestMessage(t *rapid.T) Message {
-	// Generate Node IDs and Bitcoin keys (compressed public keys)
-	node1PubKey := RandPubKey(t)
-	node2PubKey := RandPubKey(t)
-	bitcoin1PubKey := RandPubKey(t)
-	bitcoin2PubKey := RandPubKey(t)
-
-	// Convert to byte arrays
-	var nodeID1, nodeID2, bitcoinKey1, bitcoinKey2 [33]byte
-	copy(nodeID1[:], node1PubKey.SerializeCompressed())
-	copy(nodeID2[:], node2PubKey.SerializeCompressed())
-	copy(bitcoinKey1[:], bitcoin1PubKey.SerializeCompressed())
-	copy(bitcoinKey2[:], bitcoin2PubKey.SerializeCompressed())
-
-	// Ensure nodeID1 is numerically less than nodeID2
-	// This is a requirement stated in the field description
-	if bytes.Compare(nodeID1[:], nodeID2[:]) > 0 {
-		nodeID1, nodeID2 = nodeID2, nodeID1
-	}
-
-	// Generate chain hash
-	chainHash := RandChainHash(t)
-	var hash chainhash.Hash
-	copy(hash[:], chainHash[:])
-
-	return &ChannelAnnouncement1{
-		NodeSig1:        RandSignature(t),
-		NodeSig2:        RandSignature(t),
-		BitcoinSig1:     RandSignature(t),
-		BitcoinSig2:     RandSignature(t),
-		Features:        RandFeatureVector(t),
-		ChainHash:       hash,
-		ShortChannelID:  RandShortChannelID(t),
-		NodeID1:         nodeID1,
-		NodeID2:         nodeID2,
-		BitcoinKey1:     bitcoinKey1,
-		BitcoinKey2:     bitcoinKey2,
-		ExtraOpaqueData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure ChannelAnnouncement2 implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ChannelAnnouncement2)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ChannelAnnouncement2) RandTestMessage(t *rapid.T) Message {
-	features := RandFeatureVector(t)
-	shortChanID := RandShortChannelID(t)
-	capacity := uint64(rapid.IntRange(1, 16777215).Draw(t, "capacity"))
-
-	var nodeID1, nodeID2 [33]byte
-	copy(nodeID1[:], RandPubKey(t).SerializeCompressed())
-	copy(nodeID2[:], RandPubKey(t).SerializeCompressed())
-
-	// Make sure nodeID1 is numerically less than nodeID2 (as per spec).
-	if bytes.Compare(nodeID1[:], nodeID2[:]) > 0 {
-		nodeID1, nodeID2 = nodeID2, nodeID1
-	}
-
-	chainHash := RandChainHash(t)
-	var chainHashObj chainhash.Hash
-	copy(chainHashObj[:], chainHash[:])
-
-	msg := &ChannelAnnouncement2{
-		Signature: RandSignature(t),
-		ChainHash: tlv.NewPrimitiveRecord[tlv.TlvType0, chainhash.Hash](
-			chainHashObj,
-		),
-		Features: tlv.NewRecordT[tlv.TlvType2, RawFeatureVector](
-			*features,
-		),
-		ShortChannelID: tlv.NewRecordT[tlv.TlvType4, ShortChannelID](
-			shortChanID,
-		),
-		Capacity: tlv.NewPrimitiveRecord[tlv.TlvType6, uint64](
-			capacity,
-		),
-		NodeID1: tlv.NewPrimitiveRecord[tlv.TlvType8, [33]byte](
-			nodeID1,
-		),
-		NodeID2: tlv.NewPrimitiveRecord[tlv.TlvType10, [33]byte](
-			nodeID2,
-		),
-		ExtraOpaqueData: RandExtraOpaqueData(t, nil),
-	}
-
-	msg.Signature.ForceSchnorr()
-
-	// Randomly include optional fields
-	if rapid.Bool().Draw(t, "includeBitcoinKey1") {
-		var bitcoinKey1 [33]byte
-		copy(bitcoinKey1[:], RandPubKey(t).SerializeCompressed())
-		msg.BitcoinKey1 = tlv.SomeRecordT(
-			tlv.NewPrimitiveRecord[tlv.TlvType12, [33]byte](
-				bitcoinKey1,
-			),
-		)
-	}
-
-	if rapid.Bool().Draw(t, "includeBitcoinKey2") {
-		var bitcoinKey2 [33]byte
-		copy(bitcoinKey2[:], RandPubKey(t).SerializeCompressed())
-		msg.BitcoinKey2 = tlv.SomeRecordT(
-			tlv.NewPrimitiveRecord[tlv.TlvType14, [33]byte](
-				bitcoinKey2,
-			),
-		)
-	}
-
-	if rapid.Bool().Draw(t, "includeMerkleRootHash") {
-		hash := RandSHA256Hash(t)
-		var merkleRootHash [32]byte
-		copy(merkleRootHash[:], hash[:])
-		msg.MerkleRootHash = tlv.SomeRecordT(
-			tlv.NewPrimitiveRecord[tlv.TlvType16, [32]byte](
-				merkleRootHash,
-			),
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ChannelReady implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*ChannelReady)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ChannelReady) RandTestMessage(t *rapid.T) Message {
-	msg := &ChannelReady{
-		ChanID:                 RandChannelID(t),
-		NextPerCommitmentPoint: RandPubKey(t),
-		ExtraData:              RandExtraOpaqueData(t, nil),
-	}
-
-	includeAliasScid := rapid.Bool().Draw(t, "includeAliasScid")
-	includeNextLocalNonce := rapid.Bool().Draw(t, "includeNextLocalNonce")
-	includeAnnouncementNodeNonce := rapid.Bool().Draw(
-		t, "includeAnnouncementNodeNonce",
-	)
-	includeAnnouncementBitcoinNonce := rapid.Bool().Draw(
-		t, "includeAnnouncementBitcoinNonce",
-	)
-
-	if includeAliasScid {
-		scid := RandShortChannelID(t)
-		msg.AliasScid = &scid
-	}
-
-	if includeNextLocalNonce {
-		nonce := RandMusig2Nonce(t)
-		msg.NextLocalNonce = SomeMusig2Nonce(nonce)
-	}
-
-	if includeAnnouncementNodeNonce {
-		nonce := RandMusig2Nonce(t)
-		msg.AnnouncementNodeNonce = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType0, Musig2Nonce](nonce),
-		)
-	}
-
-	if includeAnnouncementBitcoinNonce {
-		nonce := RandMusig2Nonce(t)
-		msg.AnnouncementBitcoinNonce = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType2, Musig2Nonce](nonce),
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ChannelReestablish implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ChannelReestablish)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *ChannelReestablish) RandTestMessage(t *rapid.T) Message {
-	msg := &ChannelReestablish{
-		ChanID: RandChannelID(t),
-		NextLocalCommitHeight: rapid.Uint64().Draw(
-			t, "nextLocalCommitHeight",
-		),
-		RemoteCommitTailHeight: rapid.Uint64().Draw(
-			t, "remoteCommitTailHeight",
-		),
-		LastRemoteCommitSecret:    RandPaymentPreimage(t),
-		LocalUnrevokedCommitPoint: RandPubKey(t),
-		ExtraData:                 RandExtraOpaqueData(t, nil),
-	}
-
-	// Randomly decide whether to include optional fields
-	includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
-	includeDynHeight := rapid.Bool().Draw(t, "includeDynHeight")
-
-	if includeLocalNonce {
-		nonce := RandMusig2Nonce(t)
-		msg.LocalNonce = SomeMusig2Nonce(nonce)
-	}
-
-	if includeDynHeight {
-		height := DynHeight(rapid.Uint64().Draw(t, "dynHeight"))
-		msg.DynHeight = fn.Some(height)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ChannelUpdate1 implements the TestMessage
-// interface.
-var _ TestMessage = (*ChannelUpdate1)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *ChannelUpdate1) RandTestMessage(t *rapid.T) Message {
-	// Generate random message flags
-	// Randomly decide whether to include max HTLC field
-	includeMaxHtlc := rapid.Bool().Draw(t, "includeMaxHtlc")
-	var msgFlags ChanUpdateMsgFlags
-	if includeMaxHtlc {
-		msgFlags |= ChanUpdateRequiredMaxHtlc
-	}
-
-	// Generate random channel flags
-	// Randomly decide direction (node1 or node2)
-	isNode2 := rapid.Bool().Draw(t, "isNode2")
-	var chanFlags ChanUpdateChanFlags
-	if isNode2 {
-		chanFlags |= ChanUpdateDirection
-	}
-
-	// Randomly decide if channel is disabled
-	isDisabled := rapid.Bool().Draw(t, "isDisabled")
-	if isDisabled {
-		chanFlags |= ChanUpdateDisabled
-	}
-
-	// Generate chain hash
-	chainHash := RandChainHash(t)
-	var hash chainhash.Hash
-	copy(hash[:], chainHash[:])
-
-	// Generate other random fields
-	maxHtlc := MilliSatoshi(rapid.Uint64().Draw(t, "maxHtlc"))
-
-	// If max HTLC flag is not set, we need to zero the value
-	if !includeMaxHtlc {
-		maxHtlc = 0
-	}
-
-	return &ChannelUpdate1{
-		Signature:      RandSignature(t),
-		ChainHash:      hash,
-		ShortChannelID: RandShortChannelID(t),
-		Timestamp: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
-			t, "timestamp"),
-		),
-		MessageFlags: msgFlags,
-		ChannelFlags: chanFlags,
-		TimeLockDelta: uint16(rapid.IntRange(0, 65535).Draw(
-			t, "timelockDelta"),
-		),
-		HtlcMinimumMsat: MilliSatoshi(rapid.Uint64().Draw(
-			t, "htlcMinimum"),
-		),
-		BaseFee: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
-			t, "baseFee"),
-		),
-		FeeRate: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
-			t, "feeRate"),
-		),
-		HtlcMaximumMsat: maxHtlc,
-		ExtraOpaqueData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure ChannelUpdate2 implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ChannelUpdate2)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ChannelUpdate2) RandTestMessage(t *rapid.T) Message {
-	shortChanID := RandShortChannelID(t)
-	blockHeight := uint32(rapid.IntRange(0, 1000000).Draw(t, "blockHeight"))
-
-	var disabledFlags ChanUpdateDisableFlags
-	if rapid.Bool().Draw(t, "disableIncoming") {
-		disabledFlags |= ChanUpdateDisableIncoming
-	}
-	if rapid.Bool().Draw(t, "disableOutgoing") {
-		disabledFlags |= ChanUpdateDisableOutgoing
-	}
-
-	cltvExpiryDelta := uint16(rapid.IntRange(10, 200).Draw(
-		t, "cltvExpiryDelta"),
-	)
-
-	htlcMinMsat := MilliSatoshi(rapid.IntRange(1, 10000).Draw(
-		t, "htlcMinMsat"),
-	)
-	htlcMaxMsat := MilliSatoshi(rapid.IntRange(10000, 100000000).Draw(
-		t, "htlcMaxMsat"),
-	)
-	feeBaseMsat := uint32(rapid.IntRange(0, 10000).Draw(t, "feeBaseMsat"))
-	feeProportionalMillionths := uint32(rapid.IntRange(0, 10000).Draw(
-		t, "feeProportionalMillionths"),
-	)
-
-	chainHash := RandChainHash(t)
-	var chainHashObj chainhash.Hash
-	copy(chainHashObj[:], chainHash[:])
-
-	//nolint:ll
-	msg := &ChannelUpdate2{
-		Signature: RandSignature(t),
-		ChainHash: tlv.NewPrimitiveRecord[tlv.TlvType0, chainhash.Hash](
-			chainHashObj,
-		),
-		ShortChannelID: tlv.NewRecordT[tlv.TlvType2, ShortChannelID](
-			shortChanID,
-		),
-		BlockHeight: tlv.NewPrimitiveRecord[tlv.TlvType4, uint32](
-			blockHeight,
-		),
-		DisabledFlags: tlv.NewPrimitiveRecord[tlv.TlvType6, ChanUpdateDisableFlags]( //nolint:ll
-			disabledFlags,
-		),
-		CLTVExpiryDelta: tlv.NewPrimitiveRecord[tlv.TlvType10, uint16](
-			cltvExpiryDelta,
-		),
-		HTLCMinimumMsat: tlv.NewPrimitiveRecord[tlv.TlvType12, MilliSatoshi](
-			htlcMinMsat,
-		),
-		HTLCMaximumMsat: tlv.NewPrimitiveRecord[tlv.TlvType14, MilliSatoshi](
-			htlcMaxMsat,
-		),
-		FeeBaseMsat: tlv.NewPrimitiveRecord[tlv.TlvType16, uint32](
-			feeBaseMsat,
-		),
-		FeeProportionalMillionths: tlv.NewPrimitiveRecord[tlv.TlvType18, uint32](
-			feeProportionalMillionths,
-		),
-		ExtraOpaqueData: RandExtraOpaqueData(t, nil),
-	}
-
-	msg.Signature.ForceSchnorr()
-
-	if rapid.Bool().Draw(t, "isSecondPeer") {
-		msg.SecondPeer = tlv.SomeRecordT(
-			tlv.RecordT[tlv.TlvType8, TrueBoolean]{},
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ClosingComplete implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ClosingComplete)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ClosingComplete) RandTestMessage(t *rapid.T) Message {
-	msg := &ClosingComplete{
-		ChannelID: RandChannelID(t),
-		FeeSatoshis: btcutil.Amount(rapid.Int64Range(0, 1000000).Draw(
-			t, "feeSatoshis"),
-		),
-		LockTime: rapid.Uint32Range(0, 0xffffffff).Draw(
-			t, "lockTime",
-		),
-		CloseeScript: RandDeliveryAddress(t),
-		CloserScript: RandDeliveryAddress(t),
-		ExtraData:    RandExtraOpaqueData(t, nil),
-	}
-
-	includeCloserNoClosee := rapid.Bool().Draw(t, "includeCloserNoClosee")
-	includeNoCloserClosee := rapid.Bool().Draw(t, "includeNoCloserClosee")
-	includeCloserAndClosee := rapid.Bool().Draw(t, "includeCloserAndClosee")
-
-	// Ensure at least one signature is present.
-	if !includeCloserNoClosee && !includeNoCloserClosee &&
-		!includeCloserAndClosee {
-
-		// If all are false, enable at least one randomly.
-		choice := rapid.IntRange(0, 2).Draw(t, "sigChoice")
-		switch choice {
-		case 0:
-			includeCloserNoClosee = true
-		case 1:
-			includeNoCloserClosee = true
-		case 2:
-			includeCloserAndClosee = true
-		}
-	}
-
-	if includeCloserNoClosee {
-		sig := RandSignature(t)
-		msg.CloserNoClosee = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType1, Sig](sig),
-		)
-	}
-
-	if includeNoCloserClosee {
-		sig := RandSignature(t)
-		msg.NoCloserClosee = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType2, Sig](sig),
-		)
-	}
-
-	if includeCloserAndClosee {
-		sig := RandSignature(t)
-		msg.CloserAndClosee = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType3, Sig](sig),
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ClosingSig implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*ClosingSig)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ClosingSig) RandTestMessage(t *rapid.T) Message {
-	msg := &ClosingSig{
-		ChannelID:    RandChannelID(t),
-		CloseeScript: RandDeliveryAddress(t),
-		CloserScript: RandDeliveryAddress(t),
-		ExtraData:    RandExtraOpaqueData(t, nil),
-	}
-
-	includeCloserNoClosee := rapid.Bool().Draw(t, "includeCloserNoClosee")
-	includeNoCloserClosee := rapid.Bool().Draw(t, "includeNoCloserClosee")
-	includeCloserAndClosee := rapid.Bool().Draw(t, "includeCloserAndClosee")
-
-	// Ensure at least one signature is present.
-	if !includeCloserNoClosee && !includeNoCloserClosee &&
-		!includeCloserAndClosee {
-
-		// If all are false, enable at least one randomly.
-		choice := rapid.IntRange(0, 2).Draw(t, "sigChoice")
-		switch choice {
-		case 0:
-			includeCloserNoClosee = true
-		case 1:
-			includeNoCloserClosee = true
-		case 2:
-			includeCloserAndClosee = true
-		}
-	}
-
-	if includeCloserNoClosee {
-		sig := RandSignature(t)
-		msg.CloserNoClosee = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType1, Sig](sig),
-		)
-	}
-
-	if includeNoCloserClosee {
-		sig := RandSignature(t)
-		msg.NoCloserClosee = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType2, Sig](sig),
-		)
-	}
-
-	if includeCloserAndClosee {
-		sig := RandSignature(t)
-		msg.CloserAndClosee = tlv.SomeRecordT(
-			tlv.NewRecordT[tlv.TlvType3, Sig](sig),
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ClosingSigned implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ClosingSigned)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ClosingSigned) RandTestMessage(t *rapid.T) Message {
-	// Generate a random boolean to decide whether to include CommitSig or
-	// PartialSig Since they're mutually exclusive, when one is populated,
-	// the other must be blank.
-	usePartialSig := rapid.Bool().Draw(t, "usePartialSig")
-
-	msg := &ClosingSigned{
-		ChannelID: RandChannelID(t),
-		FeeSatoshis: btcutil.Amount(
-			rapid.Int64Range(0, 1000000).Draw(t, "feeSatoshis"),
-		),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-
-	if usePartialSig {
-		sigBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
-			t, "sigScalar",
-		)
-		var s btcec.ModNScalar
-		_ = s.SetByteSlice(sigBytes)
-
-		msg.PartialSig = SomePartialSig(NewPartialSig(s))
-		msg.Signature = Sig{}
-	} else {
-		msg.Signature = RandSignature(t)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure CommitSig implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*CommitSig)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *CommitSig) RandTestMessage(t *rapid.T) Message {
-	cr, _ := RandCustomRecords(t, nil, true)
-	sig := &CommitSig{
-		ChanID:        RandChannelID(t),
-		CommitSig:     RandSignature(t),
-		CustomRecords: cr,
-	}
-
-	numHtlcSigs := rapid.IntRange(0, 20).Draw(t, "numHtlcSigs")
-	htlcSigs := make([]Sig, numHtlcSigs)
-	for i := 0; i < numHtlcSigs; i++ {
-		htlcSigs[i] = RandSignature(t)
-	}
-
-	if len(htlcSigs) > 0 {
-		sig.HtlcSigs = htlcSigs
-	}
-
-	includePartialSig := rapid.Bool().Draw(t, "includePartialSig")
-	if includePartialSig {
-		sigWithNonce := RandPartialSigWithNonce(t)
-		sig.PartialSig = MaybePartialSigWithNonce(sigWithNonce)
-	}
-
-	return sig
-}
-
-// A compile time check to ensure Custom implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Custom)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *Custom) RandTestMessage(t *rapid.T) Message {
-	msgType := MessageType(
-		rapid.IntRange(int(CustomTypeStart), 65535).Draw(
-			t, "customMsgType",
-		),
-	)
-
-	dataLen := rapid.IntRange(0, 1000).Draw(t, "customDataLength")
-	data := rapid.SliceOfN(rapid.Byte(), dataLen, dataLen).Draw(
-		t, "customData",
-	)
-
-	msg, err := NewCustom(msgType, data)
-	if err != nil {
-		panic(fmt.Sprintf("Error creating custom message: %v", err))
-	}
-
-	return msg
-}
-
-// A compile time check to ensure DynAck implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*DynAck)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (da *DynAck) RandTestMessage(t *rapid.T) Message {
-	msg := &DynAck{
-		ChanID:    RandChannelID(t),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-
-	includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
-
-	if includeLocalNonce {
-		msg.LocalNonce = fn.Some(RandMusig2Nonce(t))
-	}
-
-	return msg
-}
-
-// A compile time check to ensure DynPropose implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*DynPropose)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (dp *DynPropose) RandTestMessage(t *rapid.T) Message {
-	msg := &DynPropose{
-		ChanID:    RandChannelID(t),
-		Initiator: rapid.Bool().Draw(t, "initiator"),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-
-	// Randomly decide which optional fields to include
-	includeDustLimit := rapid.Bool().Draw(t, "includeDustLimit")
-	includeMaxValueInFlight := rapid.Bool().Draw(
-		t, "includeMaxValueInFlight",
-	)
-	includeChannelReserve := rapid.Bool().Draw(t, "includeChannelReserve")
-	includeCsvDelay := rapid.Bool().Draw(t, "includeCsvDelay")
-	includeMaxAcceptedHTLCs := rapid.Bool().Draw(
-		t, "includeMaxAcceptedHTLCs",
-	)
-	includeFundingKey := rapid.Bool().Draw(t, "includeFundingKey")
-	includeChannelType := rapid.Bool().Draw(t, "includeChannelType")
-	includeKickoffFeerate := rapid.Bool().Draw(t, "includeKickoffFeerate")
-
-	// Generate random values for each included field
-	if includeDustLimit {
-		dl := btcutil.Amount(rapid.Uint32().Draw(t, "dustLimit"))
-		msg.DustLimit = fn.Some(dl)
-	}
-
-	if includeMaxValueInFlight {
-		mvif := MilliSatoshi(rapid.Uint64().Draw(t, "maxValueInFlight"))
-		msg.MaxValueInFlight = fn.Some(mvif)
-	}
-
-	if includeChannelReserve {
-		cr := btcutil.Amount(rapid.Uint32().Draw(t, "channelReserve"))
-		msg.ChannelReserve = fn.Some(cr)
-	}
-
-	if includeCsvDelay {
-		cd := rapid.Uint16().Draw(t, "csvDelay")
-		msg.CsvDelay = fn.Some(cd)
-	}
-
-	if includeMaxAcceptedHTLCs {
-		mah := rapid.Uint16().Draw(t, "maxAcceptedHTLCs")
-		msg.MaxAcceptedHTLCs = fn.Some(mah)
-	}
-
-	if includeFundingKey {
-		msg.FundingKey = fn.Some(*RandPubKey(t))
-	}
-
-	if includeChannelType {
-		msg.ChannelType = fn.Some(*RandChannelType(t))
-	}
-
-	if includeKickoffFeerate {
-		kf := chainfee.SatPerKWeight(rapid.Uint32().Draw(
-			t, "kickoffFeerate"),
-		)
-		msg.KickoffFeerate = fn.Some(kf)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure DynReject implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*DynReject)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (dr *DynReject) RandTestMessage(t *rapid.T) Message {
-	featureVec := NewRawFeatureVector()
-
-	numFeatures := rapid.IntRange(0, 8).Draw(t, "numRejections")
-	for i := 0; i < numFeatures; i++ {
-		bit := FeatureBit(
-			rapid.IntRange(0, 31).Draw(
-				t, fmt.Sprintf("rejectionBit-%d", i),
-			),
-		)
-		featureVec.Set(bit)
-	}
-
-	var extraData ExtraOpaqueData
-	randData := RandExtraOpaqueData(t, nil)
-	if len(randData) > 0 {
-		extraData = randData
-	}
-
-	return &DynReject{
-		ChanID:           RandChannelID(t),
-		UpdateRejections: *featureVec,
-		ExtraData:        extraData,
-	}
-}
-
-// A compile time check to ensure FundingCreated implements the TestMessage
-// interface.
-var _ TestMessage = (*FundingCreated)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (f *FundingCreated) RandTestMessage(t *rapid.T) Message {
-	var pendingChanID [32]byte
-	pendingChanIDBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
-		t, "pendingChanID",
-	)
-	copy(pendingChanID[:], pendingChanIDBytes)
-
-	includePartialSig := rapid.Bool().Draw(t, "includePartialSig")
-	var partialSig OptPartialSigWithNonceTLV
-	var commitSig Sig
-
-	if includePartialSig {
-		sigWithNonce := RandPartialSigWithNonce(t)
-		partialSig = MaybePartialSigWithNonce(sigWithNonce)
-
-		// When using partial sig, CommitSig should be empty/blank.
-		commitSig = Sig{}
-	} else {
-		commitSig = RandSignature(t)
-	}
-
-	return &FundingCreated{
-		PendingChannelID: pendingChanID,
-		FundingPoint:     RandOutPoint(t),
-		CommitSig:        commitSig,
-		PartialSig:       partialSig,
-		ExtraData:        RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure FundingSigned implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*FundingSigned)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (f *FundingSigned) RandTestMessage(t *rapid.T) Message {
-	usePartialSig := rapid.Bool().Draw(t, "usePartialSig")
-
-	msg := &FundingSigned{
-		ChanID:    RandChannelID(t),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-
-	if usePartialSig {
-		sigWithNonce := RandPartialSigWithNonce(t)
-		msg.PartialSig = MaybePartialSigWithNonce(sigWithNonce)
-
-		msg.CommitSig = Sig{}
-	} else {
-		msg.CommitSig = RandSignature(t)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure GossipTimestampRange implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*GossipTimestampRange)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (g *GossipTimestampRange) RandTestMessage(t *rapid.T) Message {
-	var chainHash chainhash.Hash
-	hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
-	copy(chainHash[:], hashBytes)
-
-	msg := &GossipTimestampRange{
-		ChainHash:      chainHash,
-		FirstTimestamp: rapid.Uint32().Draw(t, "firstTimestamp"),
-		TimestampRange: rapid.Uint32().Draw(t, "timestampRange"),
-		ExtraData:      RandExtraOpaqueData(t, nil),
-	}
-
-	includeFirstBlockHeight := rapid.Bool().Draw(
-		t, "includeFirstBlockHeight",
-	)
-	includeBlockRange := rapid.Bool().Draw(t, "includeBlockRange")
-
-	if includeFirstBlockHeight {
-		height := rapid.Uint32().Draw(t, "firstBlockHeight")
-		msg.FirstBlockHeight = tlv.SomeRecordT(
-			tlv.RecordT[tlv.TlvType2, uint32]{Val: height},
-		)
-	}
-
-	if includeBlockRange {
-		blockRange := rapid.Uint32().Draw(t, "blockRange")
-		msg.BlockRange = tlv.SomeRecordT(
-			tlv.RecordT[tlv.TlvType4, uint32]{Val: blockRange},
-		)
-	}
-
-	return msg
-}
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (msg *Init) RandTestMessage(t *rapid.T) Message {
-	global := NewRawFeatureVector()
-	local := NewRawFeatureVector()
-
-	numGlobalFeatures := rapid.IntRange(0, 20).Draw(t, "numGlobalFeatures")
-	for i := 0; i < numGlobalFeatures; i++ {
-		bit := FeatureBit(
-			rapid.IntRange(0, 100).Draw(
-				t, fmt.Sprintf("globalFeatureBit%d", i),
-			),
-		)
-		global.Set(bit)
-	}
-
-	numLocalFeatures := rapid.IntRange(0, 20).Draw(t, "numLocalFeatures")
-	for i := 0; i < numLocalFeatures; i++ {
-		bit := FeatureBit(
-			rapid.IntRange(0, 100).Draw(
-				t, fmt.Sprintf("localFeatureBit%d", i),
-			),
-		)
-		local.Set(bit)
-	}
-
-	return NewInitMessage(global, local)
-}
-
-// A compile time check to ensure KickoffSig implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*KickoffSig)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (ks *KickoffSig) RandTestMessage(t *rapid.T) Message {
-	return &KickoffSig{
-		ChanID:    RandChannelID(t),
-		Signature: RandSignature(t),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure NodeAnnouncement implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*NodeAnnouncement)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (a *NodeAnnouncement) RandTestMessage(t *rapid.T) Message {
-	// Generate random compressed public key for node ID
-	pubKey := RandPubKey(t)
-	var nodeID [33]byte
-	copy(nodeID[:], pubKey.SerializeCompressed())
-
-	// Generate random RGB color
-	rgbColor := color.RGBA{
-		R: uint8(rapid.IntRange(0, 255).Draw(t, "rgbR")),
-		G: uint8(rapid.IntRange(0, 255).Draw(t, "rgbG")),
-		B: uint8(rapid.IntRange(0, 255).Draw(t, "rgbB")),
-	}
-
-	return &NodeAnnouncement{
-		Signature: RandSignature(t),
-		Features:  RandFeatureVector(t),
-		Timestamp: uint32(rapid.IntRange(0, 0x7FFFFFFF).Draw(
-			t, "timestamp"),
-		),
-		NodeID:          nodeID,
-		RGBColor:        rgbColor,
-		Alias:           RandNodeAlias(t),
-		Addresses:       RandNetAddrs(t),
-		ExtraOpaqueData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure OpenChannel implements the TestMessage
-// interface.
-var _ TestMessage = (*OpenChannel)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (o *OpenChannel) RandTestMessage(t *rapid.T) Message {
-	chainHash := RandChainHash(t)
-	var hash chainhash.Hash
-	copy(hash[:], chainHash[:])
-
-	var pendingChanID [32]byte
-	pendingChanIDBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
-		t, "pendingChanID",
-	)
-	copy(pendingChanID[:], pendingChanIDBytes)
-
-	includeChannelType := rapid.Bool().Draw(t, "includeChannelType")
-	includeLeaseExpiry := rapid.Bool().Draw(t, "includeLeaseExpiry")
-	includeLocalNonce := rapid.Bool().Draw(t, "includeLocalNonce")
-
-	var channelFlags FundingFlag
-	if rapid.Bool().Draw(t, "announceChannel") {
-		channelFlags |= FFAnnounceChannel
-	}
-
-	var localNonce OptMusig2NonceTLV
-	if includeLocalNonce {
-		nonce := RandMusig2Nonce(t)
-		localNonce = tlv.SomeRecordT(
-			tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
-		)
-	}
-
-	var channelType *ChannelType
-	if includeChannelType {
-		channelType = RandChannelType(t)
-	}
-
-	var leaseExpiry *LeaseExpiry
-	if includeLeaseExpiry {
-		leaseExpiry = RandLeaseExpiry(t)
-	}
-
-	return &OpenChannel{
-		ChainHash:        hash,
-		PendingChannelID: pendingChanID,
-		FundingAmount: btcutil.Amount(
-			rapid.IntRange(5000, 10000000).Draw(t, "fundingAmount"),
-		),
-		PushAmount: MilliSatoshi(
-			rapid.IntRange(0, 1000000).Draw(t, "pushAmount"),
-		),
-		DustLimit: btcutil.Amount(
-			rapid.IntRange(100, 1000).Draw(t, "dustLimit"),
-		),
-		MaxValueInFlight: MilliSatoshi(
-			rapid.IntRange(10000, 1000000).Draw(
-				t, "maxValueInFlight",
-			),
-		),
-		ChannelReserve: btcutil.Amount(
-			rapid.IntRange(1000, 10000).Draw(t, "channelReserve"),
-		),
-		HtlcMinimum: MilliSatoshi(
-			rapid.IntRange(1, 1000).Draw(t, "htlcMinimum"),
-		),
-		FeePerKiloWeight: uint32(
-			rapid.IntRange(250, 10000).Draw(t, "feePerKw"),
-		),
-		CsvDelay: uint16(
-			rapid.IntRange(144, 1000).Draw(t, "csvDelay"),
-		),
-		MaxAcceptedHTLCs: uint16(
-			rapid.IntRange(10, 500).Draw(t, "maxAcceptedHTLCs"),
-		),
-		FundingKey:            RandPubKey(t),
-		RevocationPoint:       RandPubKey(t),
-		PaymentPoint:          RandPubKey(t),
-		DelayedPaymentPoint:   RandPubKey(t),
-		HtlcPoint:             RandPubKey(t),
-		FirstCommitmentPoint:  RandPubKey(t),
-		ChannelFlags:          channelFlags,
-		UpfrontShutdownScript: RandDeliveryAddress(t),
-		ChannelType:           channelType,
-		LeaseExpiry:           leaseExpiry,
-		LocalNonce:            localNonce,
-		ExtraData:             RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure Ping implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Ping)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (p *Ping) RandTestMessage(t *rapid.T) Message {
-	numPongBytes := uint16(rapid.IntRange(0, int(MaxPongBytes)).Draw(
-		t, "numPongBytes"),
-	)
-
-	// Generate padding bytes (but keeping within allowed message size)
-	// MaxMsgBody - 2 (for NumPongBytes) - 2 (for padding length)
-	maxPaddingLen := MaxMsgBody - 4
-	paddingLen := rapid.IntRange(0, maxPaddingLen).Draw(
-		t, "paddingLen",
-	)
-	padding := make(PingPayload, paddingLen)
-
-	// Fill padding with random bytes
-	for i := 0; i < paddingLen; i++ {
-		padding[i] = byte(rapid.IntRange(0, 255).Draw(
-			t, fmt.Sprintf("paddingByte%d", i)),
-		)
-	}
-
-	return &Ping{
-		NumPongBytes: numPongBytes,
-		PaddingBytes: padding,
-	}
-}
-
-// A compile time check to ensure Pong implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Pong)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (p *Pong) RandTestMessage(t *rapid.T) Message {
-	payloadLen := rapid.IntRange(0, 1000).Draw(t, "pongPayloadLength")
-	payload := rapid.SliceOfN(rapid.Byte(), payloadLen, payloadLen).Draw(
-		t, "pongPayload",
-	)
-
-	return &Pong{
-		PongBytes: payload,
-	}
-}
-
-// A compile time check to ensure QueryChannelRange implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*QueryChannelRange)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (q *QueryChannelRange) RandTestMessage(t *rapid.T) Message {
-	msg := &QueryChannelRange{
-		FirstBlockHeight: uint32(rapid.IntRange(0, 1000000).Draw(
-			t, "firstBlockHeight"),
-		),
-		NumBlocks: uint32(rapid.IntRange(1, 10000).Draw(
-			t, "numBlocks"),
-		),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-
-	// Generate chain hash
-	chainHash := RandChainHash(t)
-	var chainHashObj chainhash.Hash
-	copy(chainHashObj[:], chainHash[:])
-	msg.ChainHash = chainHashObj
-
-	// Randomly include QueryOptions
-	if rapid.Bool().Draw(t, "includeQueryOptions") {
-		queryOptions := &QueryOptions{}
-		*queryOptions = QueryOptions(*RandFeatureVector(t))
-		msg.QueryOptions = queryOptions
-	}
-
-	return msg
-}
-
-// A compile time check to ensure QueryShortChanIDs implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*QueryShortChanIDs)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (q *QueryShortChanIDs) RandTestMessage(t *rapid.T) Message {
-	var chainHash chainhash.Hash
-	hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
-	copy(chainHash[:], hashBytes)
-
-	encodingType := EncodingSortedPlain
-	if rapid.Bool().Draw(t, "useZlibEncoding") {
-		encodingType = EncodingSortedZlib
-	}
-
-	msg := &QueryShortChanIDs{
-		ChainHash:    chainHash,
-		EncodingType: encodingType,
-		ExtraData:    RandExtraOpaqueData(t, nil),
-		noSort:       false,
-	}
-
-	numIDs := rapid.IntRange(2, 20).Draw(t, "numShortChanIDs")
-
-	// Generate sorted short channel IDs.
-	shortChanIDs := make([]ShortChannelID, numIDs)
-	for i := 0; i < numIDs; i++ {
-		shortChanIDs[i] = RandShortChannelID(t)
-
-		// Ensure they're properly sorted.
-		if i > 0 && shortChanIDs[i].ToUint64() <=
-			shortChanIDs[i-1].ToUint64() {
-
-			// Ensure this ID is larger than the previous one.
-			shortChanIDs[i] = NewShortChanIDFromInt(
-				shortChanIDs[i-1].ToUint64() + 1,
-			)
-		}
-	}
-
-	msg.ShortChanIDs = shortChanIDs
-
-	return msg
-}
-
-// A compile time check to ensure ReplyChannelRange implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ReplyChannelRange)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ReplyChannelRange) RandTestMessage(t *rapid.T) Message {
-	msg := &ReplyChannelRange{
-		FirstBlockHeight: uint32(rapid.IntRange(0, 1000000).Draw(
-			t, "firstBlockHeight"),
-		),
-		NumBlocks: uint32(rapid.IntRange(1, 10000).Draw(
-			t, "numBlocks"),
-		),
-		Complete: uint8(rapid.IntRange(0, 1).Draw(t, "complete")),
-		EncodingType: QueryEncoding(
-			rapid.IntRange(0, 1).Draw(t, "encodingType"),
-		),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-
-	msg.ChainHash = RandChainHash(t)
-
-	numShortChanIDs := rapid.IntRange(0, 20).Draw(t, "numShortChanIDs")
-	if numShortChanIDs == 0 {
-		return msg
-	}
-
-	scidSet := fn.NewSet[ShortChannelID]()
-	scids := make([]ShortChannelID, numShortChanIDs)
-	for i := 0; i < numShortChanIDs; i++ {
-		scid := RandShortChannelID(t)
-		for scidSet.Contains(scid) {
-			scid = RandShortChannelID(t)
-		}
-
-		scids[i] = scid
-
-		scidSet.Add(scid)
-	}
-
-	// Make sure there're no duplicates.
-	msg.ShortChanIDs = scids
-
-	if rapid.Bool().Draw(t, "includeTimestamps") && numShortChanIDs > 0 {
-		msg.Timestamps = make(Timestamps, numShortChanIDs)
-		for i := 0; i < numShortChanIDs; i++ {
-			msg.Timestamps[i] = ChanUpdateTimestamps{
-				Timestamp1: uint32(rapid.IntRange(0, math.MaxInt32).Draw(t, fmt.Sprintf("timestamp-1-%d", i))), //nolint:ll
-				Timestamp2: uint32(rapid.IntRange(0, math.MaxInt32).Draw(t, fmt.Sprintf("timestamp-2-%d", i))), //nolint:ll
-			}
-		}
-	}
-
-	return msg
-}
-
-// A compile time check to ensure ReplyShortChanIDsEnd implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*ReplyShortChanIDsEnd)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *ReplyShortChanIDsEnd) RandTestMessage(t *rapid.T) Message {
-	var chainHash chainhash.Hash
-	hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
-	copy(chainHash[:], hashBytes)
-
-	complete := uint8(rapid.IntRange(0, 1).Draw(t, "complete"))
-
-	return &ReplyShortChanIDsEnd{
-		ChainHash: chainHash,
-		Complete:  complete,
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// RandTestMessage returns a RevokeAndAck message populated with random data.
-//
-// This is part of the TestMessage interface.
-func (c *RevokeAndAck) RandTestMessage(t *rapid.T) Message {
-	msg := NewRevokeAndAck()
-
-	var chanID ChannelID
-	bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "channelID")
-	copy(chanID[:], bytes)
-	msg.ChanID = chanID
-
-	revBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "revocation")
-	copy(msg.Revocation[:], revBytes)
-
-	msg.NextRevocationKey = RandPubKey(t)
-
-	if rapid.Bool().Draw(t, "includeLocalNonce") {
-		var nonce Musig2Nonce
-		nonceBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
-			t, "nonce",
-		)
-		copy(nonce[:], nonceBytes)
-
-		msg.LocalNonce = tlv.SomeRecordT(
-			tlv.NewRecordT[NonceRecordTypeT, Musig2Nonce](nonce),
-		)
-	}
-
-	return msg
-}
-
-// A compile-time check to ensure Shutdown implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Shutdown)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (s *Shutdown) RandTestMessage(t *rapid.T) Message {
-	// Generate random delivery address
-	// First decide the address type (P2PKH, P2SH, P2WPKH, P2WSH, P2TR)
-	addrType := rapid.IntRange(0, 4).Draw(t, "addrType")
-
-	// Generate random address length based on type
-	var addrLen int
-	switch addrType {
-	// P2PKH
-	case 0:
-		addrLen = 25
-	// P2SH
-	case 1:
-		addrLen = 23
-	// P2WPKH
-	case 2:
-		addrLen = 22
-	// P2WSH
-	case 3:
-		addrLen = 34
-	// P2TR
-	case 4:
-		addrLen = 34
-	}
-
-	addr := rapid.SliceOfN(rapid.Byte(), addrLen, addrLen).Draw(
-		t, "address",
-	)
-
-	// Randomly decide whether to include a shutdown nonce
-	includeNonce := rapid.Bool().Draw(t, "includeNonce")
-	var shutdownNonce ShutdownNonceTLV
-
-	if includeNonce {
-		shutdownNonce = SomeShutdownNonce(RandMusig2Nonce(t))
-	}
-
-	cr, _ := RandCustomRecords(t, nil, true)
-
-	return &Shutdown{
-		ChannelID:     RandChannelID(t),
-		Address:       addr,
-		ShutdownNonce: shutdownNonce,
-		CustomRecords: cr,
-	}
-}
-
-// A compile time check to ensure Stfu implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Stfu)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (s *Stfu) RandTestMessage(t *rapid.T) Message {
-	m := &Stfu{
-		ChanID:    RandChannelID(t),
-		Initiator: rapid.Bool().Draw(t, "initiator"),
-	}
-
-	extraData := RandExtraOpaqueData(t, nil)
-	if len(extraData) > 0 {
-		m.ExtraData = extraData
-	}
-
-	return m
-}
-
-// A compile time check to ensure UpdateAddHTLC implements the
-// lnwire.TestMessage interface.
-var _ TestMessage = (*UpdateAddHTLC)(nil)
-
-// RandTestMessage returns an UpdateAddHTLC message populated with random data.
-//
-// This is part of the TestMessage interface.
-func (c *UpdateAddHTLC) RandTestMessage(t *rapid.T) Message {
-	msg := &UpdateAddHTLC{
-		ChanID: RandChannelID(t),
-		ID:     rapid.Uint64().Draw(t, "id"),
-		Amount: MilliSatoshi(rapid.Uint64().Draw(t, "amount")),
-		Expiry: rapid.Uint32().Draw(t, "expiry"),
-	}
-
-	hashBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "paymentHash")
-	copy(msg.PaymentHash[:], hashBytes)
-
-	onionBytes := rapid.SliceOfN(
-		rapid.Byte(), OnionPacketSize, OnionPacketSize,
-	).Draw(t, "onionBlob")
-	copy(msg.OnionBlob[:], onionBytes)
-
-	numRecords := rapid.IntRange(0, 5).Draw(t, "numRecords")
-	if numRecords > 0 {
-		msg.CustomRecords, _ = RandCustomRecords(t, nil, true)
-	}
-
-	// 50/50 chance to add a blinding point
-	if rapid.Bool().Draw(t, "includeBlindingPoint") {
-		pubKey := RandPubKey(t)
-
-		msg.BlindingPoint = tlv.SomeRecordT(
-			tlv.NewPrimitiveRecord[BlindingPointTlvType](pubKey),
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure UpdateFailHTLC implements the TestMessage
-// interface.
-var _ TestMessage = (*UpdateFailHTLC)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *UpdateFailHTLC) RandTestMessage(t *rapid.T) Message {
-	return &UpdateFailHTLC{
-		ChanID:    RandChannelID(t),
-		ID:        rapid.Uint64().Draw(t, "id"),
-		Reason:    RandOpaqueReason(t),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure UpdateFailMalformedHTLC implements the
-// TestMessage interface.
-var _ TestMessage = (*UpdateFailMalformedHTLC)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *UpdateFailMalformedHTLC) RandTestMessage(t *rapid.T) Message {
-	return &UpdateFailMalformedHTLC{
-		ChanID:       RandChannelID(t),
-		ID:           rapid.Uint64().Draw(t, "id"),
-		ShaOnionBlob: RandSHA256Hash(t),
-		FailureCode:  RandFailCode(t),
-		ExtraData:    RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure UpdateFee implements the TestMessage
-// interface.
-var _ TestMessage = (*UpdateFee)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *UpdateFee) RandTestMessage(t *rapid.T) Message {
-	return &UpdateFee{
-		ChanID:    RandChannelID(t),
-		FeePerKw:  uint32(rapid.IntRange(1, 10000).Draw(t, "feePerKw")),
-		ExtraData: RandExtraOpaqueData(t, nil),
-	}
-}
-
-// A compile time check to ensure UpdateFulfillHTLC implements the TestMessage
-// interface.
-var _ TestMessage = (*UpdateFulfillHTLC)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *UpdateFulfillHTLC) RandTestMessage(t *rapid.T) Message {
-	msg := &UpdateFulfillHTLC{
-		ChanID:          RandChannelID(t),
-		ID:              rapid.Uint64().Draw(t, "id"),
-		PaymentPreimage: RandPaymentPreimage(t),
-	}
-
-	cr, ignoreRecords := RandCustomRecords(t, nil, true)
-	msg.CustomRecords = cr
-
-	randData := RandExtraOpaqueData(t, ignoreRecords)
-	if len(randData) > 0 {
-		msg.ExtraData = randData
-	}
-
-	return msg
-}
-
-// A compile time check to ensure Warning implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Warning)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *Warning) RandTestMessage(t *rapid.T) Message {
-	msg := &Warning{
-		ChanID: RandChannelID(t),
-	}
-
-	useASCII := rapid.Bool().Draw(t, "useASCII")
-	if useASCII {
-		length := rapid.IntRange(1, 100).Draw(t, "warningDataLength")
-		data := make([]byte, length)
-		for i := 0; i < length; i++ {
-			data[i] = byte(
-				rapid.IntRange(32, 126).Draw(
-					t, fmt.Sprintf("warningDataByte-%d", i),
-				),
-			)
-		}
-		msg.Data = data
-	} else {
-		length := rapid.IntRange(1, 100).Draw(t, "warningDataLength")
-		msg.Data = rapid.SliceOfN(rapid.Byte(), length, length).Draw(
-			t, "warningData",
-		)
-	}
-
-	return msg
-}
-
-// A compile time check to ensure Error implements the lnwire.TestMessage
-// interface.
-var _ TestMessage = (*Error)(nil)
-
-// RandTestMessage populates the message with random data suitable for testing.
-// It uses the rapid testing framework to generate random values.
-//
-// This is part of the TestMessage interface.
-func (c *Error) RandTestMessage(t *rapid.T) Message {
-	msg := &Error{
-		ChanID: RandChannelID(t),
-	}
-
-	useASCII := rapid.Bool().Draw(t, "useASCII")
-	if useASCII {
-		length := rapid.IntRange(1, 100).Draw(t, "errorDataLength")
-		data := make([]byte, length)
-		for i := 0; i < length; i++ {
-			data[i] = byte(
-				rapid.IntRange(32, 126).Draw(
-					t, fmt.Sprintf("errorDataByte-%d", i),
-				),
-			)
-		}
-		msg.Data = data
-	} else {
-		// Generate random binary data
-		length := rapid.IntRange(1, 100).Draw(t, "errorDataLength")
-		msg.Data = rapid.SliceOfN(
-			rapid.Byte(), length, length,
-		).Draw(t, "errorData")
-	}
-
-	return msg
-}
diff --git a/lnwire/test_utils.go b/lnwire/test_utils.go
deleted file mode 100644
index 1065cbacf..000000000
--- a/lnwire/test_utils.go
+++ /dev/null
@@ -1,360 +0,0 @@
-package lnwire
-
-import (
-	"crypto/sha256"
-	"fmt"
-	"net"
-
-	"github.com/btcsuite/btcd/btcec/v2"
-	"github.com/btcsuite/btcd/btcec/v2/ecdsa"
-	"github.com/btcsuite/btcd/chaincfg/chainhash"
-	"github.com/btcsuite/btcd/wire"
-	"github.com/lightningnetwork/lnd/fn/v2"
-	"github.com/stretchr/testify/require"
-	"pgregory.net/rapid"
-)
-
-// RandChannelUpdate generates a random ChannelUpdate message using rapid's
-// generators.
-func RandPartialSig(t *rapid.T) *PartialSig {
-	// Generate random private key bytes
-	sigBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "privKeyBytes")
-
-	var s btcec.ModNScalar
-	s.SetByteSlice(sigBytes)
-
-	return &PartialSig{
-		Sig: s,
-	}
-}
-
-// RandPartialSigWithNonce generates a random PartialSigWithNonce using rapid
-// generators.
-func RandPartialSigWithNonce(t *rapid.T) *PartialSigWithNonce {
-	sigLen := rapid.IntRange(1, 65).Draw(t, "partialSigLen")
-	sigBytes := rapid.SliceOfN(
-		rapid.Byte(), sigLen, sigLen,
-	).Draw(t, "partialSig")
-
-	sigScalar := new(btcec.ModNScalar)
-	sigScalar.SetByteSlice(sigBytes)
-
-	return NewPartialSigWithNonce(
-		RandMusig2Nonce(t), *sigScalar,
-	)
-}
-
-// RandPubKey generates a random public key using rapid's generators.
-func RandPubKey(t *rapid.T) *btcec.PublicKey {
-	privKeyBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(
-		t, "privKeyBytes",
-	)
-	_, pub := btcec.PrivKeyFromBytes(privKeyBytes)
-
-	return pub
-}
-
-// RandChannelID generates a random channel ID.
-func RandChannelID(t *rapid.T) ChannelID {
-	var c ChannelID
-	bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "channelID")
-	copy(c[:], bytes)
-
-	return c
-}
-
-// RandShortChannelID generates a random short channel ID.
-func RandShortChannelID(t *rapid.T) ShortChannelID {
-	return NewShortChanIDFromInt(
-		uint64(rapid.IntRange(1, 100000).Draw(t, "shortChanID")),
-	)
-}
-
-// RandFeatureVector generates a random feature vector.
-func RandFeatureVector(t *rapid.T) *RawFeatureVector {
-	featureVec := NewRawFeatureVector()
-
-	// Add a random number of random feature bits
-	numFeatures := rapid.IntRange(0, 20).Draw(t, "numFeatures")
-	for i := 0; i < numFeatures; i++ {
-		bit := FeatureBit(rapid.IntRange(0, 100).Draw(
-			t, fmt.Sprintf("featureBit-%d", i)),
-		)
-		featureVec.Set(bit)
-	}
-
-	return featureVec
-}
-
-// RandSignature generates a signature for testing.
-func RandSignature(t *rapid.T) Sig {
-	testRScalar := new(btcec.ModNScalar)
-	testSScalar := new(btcec.ModNScalar)
-
-	// Generate random bytes for R and S
-	rBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "rBytes")
-	sBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "sBytes")
-	_ = testRScalar.SetByteSlice(rBytes)
-	_ = testSScalar.SetByteSlice(sBytes)
-
-	testSig := ecdsa.NewSignature(testRScalar, testSScalar)
-
-	sig, err := NewSigFromSignature(testSig)
-	if err != nil {
-		panic(fmt.Sprintf("unable to create signature: %v", err))
-	}
-
-	return sig
-}
-
-// RandPaymentHash generates a random payment hash.
-func RandPaymentHash(t *rapid.T) [32]byte {
-	var hash [32]byte
-	bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "paymentHash")
-	copy(hash[:], bytes)
-
-	return hash
-}
-
-// RandPaymentPreimage generates a random payment preimage.
-func RandPaymentPreimage(t *rapid.T) [32]byte {
-	var preimage [32]byte
-	bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "preimage")
-	copy(preimage[:], bytes)
-
-	return preimage
-}
-
-// RandChainHash generates a random chain hash.
-func RandChainHash(t *rapid.T) chainhash.Hash {
-	var hash [32]byte
-	bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "chainHash")
-	copy(hash[:], bytes)
-
-	return hash
-}
-
-// RandNodeAlias generates a random node alias.
-func RandNodeAlias(t *rapid.T) NodeAlias {
-	var alias NodeAlias
-	aliasLength := rapid.IntRange(0, 32).Draw(t, "aliasLength")
-
-	aliasBytes := rapid.StringN(
-		0, aliasLength, aliasLength,
-	).Draw(t, "alias")
-
-	copy(alias[:], aliasBytes)
-
-	return alias
-}
-
-// RandNetAddrs generates random network addresses.
-func RandNetAddrs(t *rapid.T) []net.Addr {
-	numAddresses := rapid.IntRange(0, 5).Draw(t, "numAddresses")
-	if numAddresses == 0 {
-		return nil
-	}
-
-	addresses := make([]net.Addr, numAddresses)
-	for i := 0; i < numAddresses; i++ {
-		addressType := rapid.IntRange(0, 1).Draw(
-			t, fmt.Sprintf("addressType-%d", i),
-		)
-
-		switch addressType {
-		// IPv4.
-		case 0:
-			ipBytes := rapid.SliceOfN(rapid.Byte(), 4, 4).Draw(
-				t, fmt.Sprintf("ipv4-%d", i),
-			)
-			port := rapid.IntRange(1, 65535).Draw(
-				t, fmt.Sprintf("port-%d", i),
-			)
-			addresses[i] = &net.TCPAddr{
-				IP:   ipBytes,
-				Port: port,
-			}
-
-		// IPv6.
-		case 1:
-			ipBytes := rapid.SliceOfN(rapid.Byte(), 16, 16).Draw(
-				t, fmt.Sprintf("ipv6-%d", i),
-			)
-			port := rapid.IntRange(1, 65535).Draw(
-				t, fmt.Sprintf("port-%d", i),
-			)
-			addresses[i] = &net.TCPAddr{
-				IP:   ipBytes,
-				Port: port,
-			}
-		}
-	}
-
-	return addresses
-}
-
-// RandCustomRecords generates random custom TLV records.
-func RandCustomRecords(t *rapid.T,
-	ignoreRecords fn.Set[uint64],
-	custom bool) (CustomRecords, fn.Set[uint64]) {
-
-	numRecords := rapid.IntRange(0, 5).Draw(t, "numCustomRecords")
-	customRecords := make(CustomRecords)
-
-	if numRecords == 0 {
-		return nil, nil
-	}
-
-	rangeStart := 0
-	rangeStop := int(CustomTypeStart)
-	if custom {
-		rangeStart = 70_000
-		rangeStop = 100_000
-	}
-
-	ignoreSet := fn.NewSet[uint64]()
-	for i := 0; i < numRecords; i++ {
-		recordType := uint64(
-			rapid.IntRange(rangeStart, rangeStop).
-				Filter(func(i int) bool {
-					return !ignoreRecords.Contains(
-						uint64(i),
-					)
-				}).
-				Draw(
-					t, fmt.Sprintf("recordType-%d", i),
-				),
-		)
-		recordLen := rapid.IntRange(4, 64).Draw(
-			t, fmt.Sprintf("recordLen-%d", i),
-		)
-		record := rapid.SliceOfN(
-			rapid.Byte(), recordLen, recordLen,
-		).Draw(t, fmt.Sprintf("record-%d", i))
-
-		customRecords[recordType] = record
-
-		ignoreSet.Add(recordType)
-	}
-
-	return customRecords, ignoreSet
-}
-
-// RandMusig2Nonce generates a random musig2 nonce.
-func RandMusig2Nonce(t *rapid.T) Musig2Nonce {
-	var nonce Musig2Nonce
-	bytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "nonce")
-	copy(nonce[:], bytes)
-
-	return nonce
-}
-
-// RandExtraOpaqueData generates random extra opaque data.
-func RandExtraOpaqueData(t *rapid.T,
-	ignoreRecords fn.Set[uint64]) ExtraOpaqueData {
-
-	// Make some random records.
-	cRecords, _ := RandCustomRecords(t, ignoreRecords, false)
-	if cRecords == nil {
-		return ExtraOpaqueData{}
-	}
-
-	// Encode those records as opaque data.
-	recordBytes, err := cRecords.Serialize()
-	require.NoError(t, err)
-
-	return ExtraOpaqueData(recordBytes)
-}
-
-// RandOpaqueReason generates a random opaque reason for HTLC failures.
-func RandOpaqueReason(t *rapid.T) OpaqueReason {
-	reasonLen := rapid.IntRange(32, 300).Draw(t, "reasonLen")
-	return rapid.SliceOfN(rapid.Byte(), reasonLen, reasonLen).Draw(
-		t, "opaqueReason",
-	)
-}
-
-// RandFailCode generates a random HTLC failure code.
-func RandFailCode(t *rapid.T) FailCode {
-	// List of known failure codes to choose from Using only the documented
-	// codes.
-	validCodes := []FailCode{
-		CodeInvalidRealm,
-		CodeTemporaryNodeFailure,
-		CodePermanentNodeFailure,
-		CodeRequiredNodeFeatureMissing,
-		CodePermanentChannelFailure,
-		CodeRequiredChannelFeatureMissing,
-		CodeUnknownNextPeer,
-		CodeIncorrectOrUnknownPaymentDetails,
-		CodeIncorrectPaymentAmount,
-		CodeFinalExpiryTooSoon,
-		CodeInvalidOnionVersion,
-		CodeInvalidOnionHmac,
-		CodeInvalidOnionKey,
-		CodeTemporaryChannelFailure,
-		CodeChannelDisabled,
-		CodeExpiryTooSoon,
-		CodeMPPTimeout,
-		CodeInvalidOnionPayload,
-		CodeFeeInsufficient,
-	}
-
-	// Choose a random code from the list.
-	idx := rapid.IntRange(0, len(validCodes)-1).Draw(t, "failCodeIndex")
-
-	return validCodes[idx]
-}
-
-// RandSHA256Hash generates a random SHA256 hash.
-func RandSHA256Hash(t *rapid.T) [sha256.Size]byte {
-	var hash [sha256.Size]byte
-	bytes := rapid.SliceOfN(rapid.Byte(), sha256.Size, sha256.Size).Draw(
-		t, "sha256Hash",
-	)
-	copy(hash[:], bytes)
-
-	return hash
-}
-
-// RandDeliveryAddress generates a random delivery address (script).
-func RandDeliveryAddress(t *rapid.T) DeliveryAddress {
-	addrLen := rapid.IntRange(1, 34).Draw(t, "addrLen")
-
-	return rapid.SliceOfN(rapid.Byte(), addrLen, addrLen).Draw(
-		t, "deliveryAddress",
-	)
-}
-
-// RandChannelType generates a random channel type.
-func RandChannelType(t *rapid.T) *ChannelType {
-	vec := RandFeatureVector(t)
-	chanType := ChannelType(*vec)
-
-	return &chanType
-}
-
-// RandLeaseExpiry generates a random lease expiry.
-func RandLeaseExpiry(t *rapid.T) *LeaseExpiry {
-	exp := LeaseExpiry(
-		uint32(rapid.IntRange(1000, 1000000).Draw(t, "leaseExpiry")),
-	)
-
-	return &exp
-}
-
-// RandOutPoint generates a random transaction outpoint.
-func RandOutPoint(t *rapid.T) wire.OutPoint {
-	// Generate a random transaction ID
-	var txid chainhash.Hash
-	txidBytes := rapid.SliceOfN(rapid.Byte(), 32, 32).Draw(t, "txid")
-	copy(txid[:], txidBytes)
-
-	// Generate a random output index
-	vout := uint32(rapid.IntRange(0, 10).Draw(t, "vout"))
-
-	return wire.OutPoint{
-		Hash:  txid,
-		Index: vout,
-	}
-}
diff --git a/multimutex/multimutex_test.go b/multimutex/multimutex_test.go
deleted file mode 100644
index f02a10163..000000000
--- a/multimutex/multimutex_test.go
+++ /dev/null
@@ -1,159 +0,0 @@
-package multimutex_test
-
-import (
-	"sync"
-	"testing"
-	"time"
-
-	"github.com/lightningnetwork/lnd/multimutex"
-	"github.com/stretchr/testify/require"
-)
-
-// newTestMutex initializes a new mutex.
-func newTestMutex[T comparable]() *multimutex.Mutex[T] {
-	return multimutex.NewMutex[T]()
-}
-
-// TestMultiMutexLockUnlock verifies basic locking/unlocking functionality.
-func TestMultiMutexLockUnlock(t *testing.T) {
-	t.Parallel()
-	mtx := newTestMutex[string]()
-
-	// Lock and unlock the mutex with a single ID.
-	mtx.Lock("id1")
-	mtx.Unlock("id1")
-
-	// Lock and unlock with multiple IDs.
-	mtx.Lock("id1")
-	mtx.Lock("id2")
-	mtx.Unlock("id1")
-	mtx.Unlock("id2")
-}
-
-// TestMultiMutexConcurrency validates proper handling of concurrent access.
-func TestMultiMutexConcurrency(t *testing.T) {
-	t.Parallel()
-	mtx := newTestMutex[string]()
-
-	var counter int32
-	var wg sync.WaitGroup
-	const numOps = 100
-
-	for i := 0; i < numOps; i++ {
-		wg.Add(1)
-		go func() {
-			defer wg.Done()
-
-			mtx.Lock("shared")
-			current := counter
-
-			// Simulate some work to check atomicity.
-			time.Sleep(time.Millisecond)
-			counter = current + 1
-			mtx.Unlock("shared")
-		}()
-	}
-
-	wg.Wait()
-	require.Equal(t, int32(numOps), counter)
-}
-
-// TestMultiMutexMultipleIDs validates independent locking per ID.
-func TestMultiMutexMultipleIDs(t *testing.T) {
-	t.Parallel()
-	mtx := newTestMutex[string]()
-
-	counters := map[string]*int32{
-		"id1": new(int32),
-		"id2": new(int32),
-	}
-	var wg sync.WaitGroup
-	const numOps = 50
-
-	for i := 0; i < numOps; i++ {
-		wg.Add(2)
-
-		go func(id string) {
-			defer wg.Done()
-
-			mtx.Lock(id)
-			defer mtx.Unlock(id)
-
-			current := *counters[id]
-
-			// Simulate some work to check atomicity.
-			time.Sleep(time.Millisecond)
-			*counters[id] = current + 1
-		}("id1")
-
-		go func(id string) {
-			defer wg.Done()
-
-			mtx.Lock(id)
-			defer mtx.Unlock(id)
-
-			current := *counters[id]
-
-			// Simulate some work to check atomicity.
-			time.Sleep(time.Millisecond)
-			*counters[id] = current + 1
-		}("id2")
-	}
-
-	wg.Wait()
-	require.Equal(t, int32(numOps), *counters["id1"])
-	require.Equal(t, int32(numOps), *counters["id2"])
-}
-
-// TestMultiMutexReuse validates mutex reuse after unlocking.
-func TestMultiMutexReuse(t *testing.T) {
-	t.Parallel()
-	mtx := newTestMutex[int]()
-
-	for i := 0; i < 5; i++ {
-		mtx.Lock(1)
-		mtx.Unlock(1)
-	}
-
-	for i := 1; i <= 5; i++ {
-		mtx.Lock(i)
-	}
-	for i := 1; i <= 5; i++ {
-		mtx.Unlock(i)
-	}
-}
-
-// TestMultiMutexPanic validates proper panic behavior on invalid unlocks.
-func TestMultiMutexPanic(t *testing.T) {
-	t.Parallel()
-
-	t.Run("unlock never-locked ID", func(t *testing.T) {
-		mtx := newTestMutex[int]()
-		require.Panics(t, func() { mtx.Unlock(1) })
-	})
-
-	t.Run("double unlock", func(t *testing.T) {
-		mtx := newTestMutex[int]()
-		mtx.Lock(1)
-		mtx.Unlock(1)
-		require.Panics(t, func() { mtx.Unlock(1) })
-	})
-}
-
-// TestMultiMutexCustomType validates compatibility with custom types.
-func TestMultiMutexCustomType(t *testing.T) {
-	t.Parallel()
-
-	type CustomID struct {
-		ID   string
-		Type int
-	}
-
-	mtx := newTestMutex[CustomID]()
-	id1, id2 := CustomID{"test", 1}, CustomID{"test", 2}
-
-	mtx.Lock(id1)
-	mtx.Lock(id2)
-	mtx.Unlock(id1)
-	mtx.Unlock(id2)
-}
diff --git a/routing/router_test.go b/routing/router_test.go
index 62a68b213..6efc75df2 100644
--- a/routing/router_test.go
+++ b/routing/router_test.go
@@ -2215,6 +2215,13 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) {
 		mock.Anything, rt,
 	).Return(nil)
 
+	// Mock the control tower to return the mocked payment.
+	payment := &mockMPPayment{}
+	controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
+
+	// Mock the payment to return nil failure reason.
+	payment.On("TerminalInfo").Return(nil, nil)
+
 	// Expect a successful send to route.
 	attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
 	require.NoError(t, err)
@@ -2224,6 +2231,7 @@ func TestSendToRouteSkipTempErrSuccess(t *testing.T) {
 	controlTower.AssertExpectations(t)
 	payer.AssertExpectations(t)
 	missionControl.AssertExpectations(t)
+	payment.AssertExpectations(t)
 }
 
 // TestSendToRouteSkipTempErrNonMPP checks that an error is return when
@@ -2344,12 +2352,19 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) {
 		mock.Anything, mock.Anything, mock.Anything,
 	).Return(tempErr)
 
+	// Mock the control tower to return the mocked payment.
+	payment := &mockMPPayment{}
+	controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
+
 	// Mock the mission control to return a nil reason from reporting the
 	// attempt failure.
 	missionControl.On("ReportPaymentFail",
 		mock.Anything, rt, mock.Anything, mock.Anything,
 	).Return(nil, nil)
 
+	// Mock the payment to return nil failure reason.
+	payment.On("TerminalInfo").Return(nil, nil)
+
 	// Expect a failed send to route.
 	attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
 	require.Equal(t, tempErr, err)
@@ -2359,6 +2374,7 @@ func TestSendToRouteSkipTempErrTempFailure(t *testing.T) {
 	controlTower.AssertExpectations(t)
 	payer.AssertExpectations(t)
 	missionControl.AssertExpectations(t)
+	payment.AssertExpectations(t)
 }
 
 // TestSendToRouteSkipTempErrPermanentFailure validates a permanent failure
@@ -2420,9 +2436,7 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
 	).Return(testAttempt, nil)
 
 	// Expect the payment to be failed.
-	controlTower.On(
-		"FailPayment", payHash, mock.Anything,
-	).Return(nil).Once()
+	controlTower.On("FailPayment", payHash, mock.Anything).Return(nil)
 
 	// Mock an error to be returned from sending the htlc.
 	payer.On("SendHTLC",
@@ -2434,6 +2448,13 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
 		mock.Anything, rt, mock.Anything, mock.Anything,
 	).Return(&failureReason, nil)
 
+	// Mock the control tower to return the mocked payment.
+	payment := &mockMPPayment{}
+	controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
+
+	// Mock the payment to return a failure reason.
+	payment.On("TerminalInfo").Return(nil, &failureReason)
+
 	// Expect a failed send to route.
 	attempt, err := router.SendToRouteSkipTempErr(payHash, rt, nil)
 	require.Equal(t, permErr, err)
@@ -2443,6 +2464,7 @@ func TestSendToRouteSkipTempErrPermanentFailure(t *testing.T) {
 	controlTower.AssertExpectations(t)
 	payer.AssertExpectations(t)
 	missionControl.AssertExpectations(t)
+	payment.AssertExpectations(t)
 }
 
 // TestSendToRouteTempFailure validates a temporary failure will cause the
@@ -2503,9 +2525,7 @@ func TestSendToRouteTempFailure(t *testing.T) {
 	).Return(testAttempt, nil)
 
 	// Expect the payment to be failed.
-	controlTower.On(
-		"FailPayment", payHash, mock.Anything,
-	).Return(nil).Once()
+	controlTower.On("FailPayment", payHash, mock.Anything).Return(nil)
 
 	payer.On("SendHTLC",
 		mock.Anything, mock.Anything, mock.Anything,
@@ -2516,7 +2536,7 @@ func TestSendToRouteTempFailure(t *testing.T) {
 	controlTower.On("FetchPayment", payHash).Return(payment, nil).Once()
 
 	// Mock the payment to return nil failure reason.
-	payment.On("TerminalInfo").Return(nil, nil).Once()
+	payment.On("TerminalInfo").Return(nil, nil)
 
 	// Return a nil reason to mock a temporary failure.
 	missionControl.On("ReportPaymentFail",