Repository: lightningnetwork/lnd
Pull request #8791 (2024-05-29 20:32:09)

routing: assume TLV onion feature bit
In this PR, we start assuming that hops on a path all support TLV onion encoding. 

This PR is the bare minimum. It does not completely remove the `Hop.LegacyPayload` member since we do still use this for deserialising persisted Hops for an HTLC attempt. So there is a chance that we have some on disk. And since we do have cases where we assume hops only support legacy encoding (either we did not receive their node announcement or we received chained hop hints in an invoice) the chances of this being the case are not that small. So some discussion/thought is perhaps needed around how we can handle this. 

The other thing we could potentially add to this PR is the removal of `--protocol.legacy.onion`

We already no longer read the `TlvPayload` boolean in `SendToRouteReq.Hop` so we could potentially remove this or warn the user if they are setting this to `false` that it is being ignored. 


Fixes: https://github.com/lightningnetwork/lnd/issues/8785

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 disabled on this repository.
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

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

---



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

<details>
<summary>Share</summary>

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

</details>

<details>
<summary>Tips</summary>

### Chat

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

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
	- `I pushed a fix in commit <commit_id>.`
	- `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 generate interesting stats about this repository and render them as a table.`
	- `@coderabbitai show all the console.log statements in this repository.`
	- `@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 as PR comments)

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


Additionally, you can add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.

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

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

### Documentation and Community

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

</details>

<!-- tips_end -->
lightninglabs-deploy: @yyforyongyu: review reminder

Patch:
diff --git a/docs/release-notes/release-notes-0.18.1.md b/docs/release-notes/release-notes-0.18.1.md
index 1d6608407..644ed5a99 100644
--- a/docs/release-notes/release-notes-0.18.1.md
+++ b/docs/release-notes/release-notes-0.18.1.md
@@ -79,6 +79,11 @@
 
 # Technical and Architectural Updates
 ## BOLT Spec Updates
+
+* Start assuming that all hops used during path-finding and route construction
+  [support the TLV onion 
+  format](https://github.com/lightningnetwork/lnd/pull/8791).
+
 ## Testing
 ## Database
 ## Code Health
@@ -88,5 +93,6 @@
 
 * Andras Banki-Horvath
 * Bufo
+* Elle Mouton
 * Matheus Degiovani
 * Slyghtning
diff --git a/routing/additional_edge.go b/routing/additional_edge.go
index 22cf3032b..eee17cce1 100644
--- a/routing/additional_edge.go
+++ b/routing/additional_edge.go
@@ -25,7 +25,7 @@ type AdditionalEdge interface {
 	// additional edge when being an intermediate hop in a route NOT the
 	// final hop.
 	IntermediatePayloadSize(amount lnwire.MilliSatoshi, expiry uint32,
-		legacy bool, channelID uint64) uint64
+		channelID uint64) uint64
 
 	// EdgePolicy returns the policy of the additional edge.
 	EdgePolicy() *models.CachedEdgePolicy
@@ -33,7 +33,7 @@ type AdditionalEdge interface {
 
 // PayloadSizeFunc defines the interface for the payload size function.
 type PayloadSizeFunc func(amount lnwire.MilliSatoshi, expiry uint32,
-	legacy bool, channelID uint64) uint64
+	channelID uint64) uint64
 
 // PrivateEdge implements the AdditionalEdge interface. As the name implies it
 // is used for private route hints that the receiver adds for example to an
@@ -50,12 +50,11 @@ func (p *PrivateEdge) EdgePolicy() *models.CachedEdgePolicy {
 // IntermediatePayloadSize returns the sphinx payload size defined in BOLT04 if
 // this edge were to be included in a route.
 func (p *PrivateEdge) IntermediatePayloadSize(amount lnwire.MilliSatoshi,
-	expiry uint32, legacy bool, channelID uint64) uint64 {
+	expiry uint32, channelID uint64) uint64 {
 
 	hop := route.Hop{
 		AmtToForward:     amount,
 		OutgoingTimeLock: expiry,
-		LegacyPayload:    legacy,
 	}
 
 	return hop.PayloadSize(channelID)
@@ -77,11 +76,10 @@ func (b *BlindedEdge) EdgePolicy() *models.CachedEdgePolicy {
 // IntermediatePayloadSize returns the sphinx payload size defined in BOLT04 if
 // this edge were to be included in a route.
 func (b *BlindedEdge) IntermediatePayloadSize(_ lnwire.MilliSatoshi, _ uint32,
-	_ bool, _ uint64) uint64 {
+	_ uint64) uint64 {
 
 	hop := route.Hop{
 		BlindingPoint: b.blindingPoint,
-		LegacyPayload: false,
 		EncryptedData: b.cipherText,
 	}
 
@@ -97,11 +95,11 @@ var _ AdditionalEdge = (*BlindedEdge)(nil)
 // defaultHopPayloadSize is the default payload size of a normal (not-blinded)
 // hop in the route.
 func defaultHopPayloadSize(amount lnwire.MilliSatoshi, expiry uint32,
-	legacy bool, channelID uint64) uint64 {
+	channelID uint64) uint64 {
 
 	// The payload size of a cleartext intermediate hop is equal to the
 	// payload size of a private edge therefore we reuse its size function.
 	edge := PrivateEdge{}
 
-	return edge.IntermediatePayloadSize(amount, expiry, legacy, channelID)
+	return edge.IntermediatePayloadSize(amount, expiry, channelID)
 }
diff --git a/routing/mocks.go b/routing/mocks.go
deleted file mode 100644
index 9c019abc9..000000000
--- a/routing/mocks.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package routing
-
-import (
-	"github.com/lightningnetwork/lnd/channeldb/models"
-	"github.com/lightningnetwork/lnd/lnwire"
-	"github.com/stretchr/testify/mock"
-)
-
-// mockAdditionalEdge is a mock of the AdditionalEdge interface.
-type mockAdditionalEdge struct{ mock.Mock }
-
-// IntermediatePayloadSize returns the sphinx payload size defined in BOLT04 if
-// this edge were to be included in a route.
-func (m *mockAdditionalEdge) IntermediatePayloadSize(amount lnwire.MilliSatoshi,
-	expiry uint32, legacy bool, channelID uint64) uint64 {
-
-	args := m.Called(amount, expiry, legacy, channelID)
-
-	return args.Get(0).(uint64)
-}
-
-// EdgePolicy return the policy of the mockAdditionalEdge.
-func (m *mockAdditionalEdge) EdgePolicy() *models.CachedEdgePolicy {
-	args := m.Called()
-
-	edgePolicy := args.Get(0)
-	if edgePolicy == nil {
-		return nil
-	}
-
-	return edgePolicy.(*models.CachedEdgePolicy)
-}
diff --git a/routing/pathfind.go b/routing/pathfind.go
index add833ecc..32302b941 100644
--- a/routing/pathfind.go
+++ b/routing/pathfind.go
@@ -161,7 +161,6 @@ func newRoute(sourceVertex route.Vertex,
 			fee                 int64
 			totalAmtMsatBlinded lnwire.MilliSatoshi
 			outgoingTimeLock    uint32
-			tlvPayload          bool
 			customRecords       record.CustomSet
 			mpp                 *record.MPP
 			metadata            []byte
@@ -180,13 +179,6 @@ func newRoute(sourceVertex route.Vertex,
 			return edge.ToNodeFeatures.HasFeature(feature)
 		}
 
-		// We start by assuming the node doesn't support TLV. We'll now
-		// inspect the node's feature vector to see if we can promote
-		// the hop. We assume already that the feature vector's
-		// transitive dependencies have already been validated by path
-		// finding or some other means.
-		tlvPayload = supports(lnwire.TLVOnionPayloadOptional)
-
 		if i == len(pathEdges)-1 {
 			// If this is the last hop, then the hop payload will
 			// contain the exact amount. In BOLT #4: Onion Routing
@@ -204,12 +196,7 @@ func newRoute(sourceVertex route.Vertex,
 			totalTimeLock += uint32(finalHop.cltvDelta)
 			outgoingTimeLock = totalTimeLock
 
-			// Attach any custom records to the final hop if the
-			// receiver supports TLV.
-			if !tlvPayload && finalHop.records != nil {
-				return nil, errors.New("cannot attach " +
-					"custom records")
-			}
+			// Attach any custom records to the final hop.
 			customRecords = finalHop.records
 
 			// If we're attaching a payment addr but the receiver
@@ -275,7 +262,6 @@ func newRoute(sourceVertex route.Vertex,
 			ChannelID:        edge.ChannelID,
 			AmtToForward:     amtToForward,
 			OutgoingTimeLock: outgoingTimeLock,
-			LegacyPayload:    !tlvPayload,
 			CustomRecords:    customRecords,
 			MPP:              mpp,
 			Metadata:         metadata,
@@ -659,8 +645,7 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
 
 	// The payload size of the final hop differ from intermediate hops
 	// and depends on whether the destination is blinded or not.
-	lastHopPayloadSize := lastHopPayloadSize(r, finalHtlcExpiry, amt,
-		!features.HasFeature(lnwire.TLVOnionPayloadOptional))
+	lastHopPayloadSize := lastHopPayloadSize(r, finalHtlcExpiry, amt)
 
 	// We can't always assume that the end destination is publicly
 	// advertised to the network so we'll manually include the target node.
@@ -882,14 +867,10 @@ func findPath(g *graphParams, r *RestrictParams, cfg *PathFindingConfig,
 				return
 			}
 
-			supportsTlv := fromFeatures.HasFeature(
-				lnwire.TLVOnionPayloadOptional,
-			)
-
 			payloadSize = edge.hopPayloadSizeFn(
 				amountToSend,
 				uint32(toNodeDist.incomingCltv),
-				!supportsTlv, edge.policy.ChannelID,
+				edge.policy.ChannelID,
 			)
 		}
 
@@ -1176,7 +1157,7 @@ func getProbabilityBasedDist(weight int64, probability float64,
 // It depends on the tlv types which are present and also whether the hop is
 // part of a blinded route or not.
 func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
-	amount lnwire.MilliSatoshi, legacy bool) uint64 {
+	amount lnwire.MilliSatoshi) uint64 {
 
 	if r.BlindedPayment != nil {
 		blindedPath := r.BlindedPayment.BlindedPath.BlindedHops
@@ -1186,7 +1167,6 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
 		finalHop := route.Hop{
 			AmtToForward:     amount,
 			OutgoingTimeLock: uint32(finalHtlcExpiry),
-			LegacyPayload:    false,
 			EncryptedData:    encryptedData,
 		}
 		if len(blindedPath) == 1 {
@@ -1214,7 +1194,6 @@ func lastHopPayloadSize(r *RestrictParams, finalHtlcExpiry int32,
 		AmtToForward:     amount,
 		OutgoingTimeLock: uint32(finalHtlcExpiry),
 		CustomRecords:    r.DestCustomRecords,
-		LegacyPayload:    legacy,
 		MPP:              mpp,
 		AMP:              amp,
 		Metadata:         r.Metadata,
diff --git a/routing/route/route.go b/routing/route/route.go
index 934a36ec1..0516cc7a2 100644
--- a/routing/route/route.go
+++ b/routing/route/route.go
@@ -133,6 +133,11 @@ type Hop struct {
 	// LegacyPayload if true, then this signals that this node doesn't
 	// understand the new TLV payload, so we must instead use the legacy
 	// payload.
+	//
+	// NOTE: we should no longer ever create a Hop with Legacy set to true.
+	// The only reason we are keeping this member is that it could be the
+	// case that we have serialised hops persisted to disk where
+	// LegacyPayload is true.
 	LegacyPayload bool
 
 	// Metadata is additional data that is sent along with the payment to
@@ -190,7 +195,7 @@ func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
 
 	// If this is a legacy payload, then we'll exit here as this method
 	// shouldn't be called.
-	if h.LegacyPayload == true {
+	if h.LegacyPayload {
 		return fmt.Errorf("cannot pack hop payloads for legacy " +
 			"payloads")
 	}
@@ -370,8 +375,8 @@ func validateNextChanID(nextChanIDIsSet, isBlinded, finalHop bool) error {
 	}
 }
 
-// Size returns the total size this hop's payload would take up in the onion
-// packet.
+// PayloadSize returns the total size this hop's payload would take up in the
+// onion packet.
 func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
 	if h.LegacyPayload {
 		return sphinx.LegacyHopDataSize


Test patch:
diff --git a/routing/additional_edge_test.go b/routing/additional_edge_test.go
index b3ea3b501..b5628c6b7 100644
--- a/routing/additional_edge_test.go
+++ b/routing/additional_edge_test.go
@@ -27,24 +27,12 @@ func TestIntermediatePayloadSize(t *testing.T) {
 		nextHop uint64
 		edge    AdditionalEdge
 	}{
-		{
-			name: "Legacy payload private edge",
-			hop: route.Hop{
-				AmtToForward:     1000,
-				OutgoingTimeLock: 600000,
-				ChannelID:        3432483437438,
-				LegacyPayload:    true,
-			},
-			nextHop: 1,
-			edge:    &PrivateEdge{},
-		},
 		{
 			name: "Tlv payload private edge",
 			hop: route.Hop{
 				AmtToForward:     1000,
 				OutgoingTimeLock: 600000,
 				ChannelID:        3432483437438,
-				LegacyPayload:    false,
 			},
 			nextHop: 1,
 			edge:    &PrivateEdge{},
@@ -86,7 +74,6 @@ func TestIntermediatePayloadSize(t *testing.T) {
 				IntermediatePayloadSize(
 					testCase.hop.AmtToForward,
 					testCase.hop.OutgoingTimeLock,
-					testCase.hop.LegacyPayload,
 					testCase.nextHop,
 				)
 
diff --git a/routing/blinding_test.go b/routing/blinding_test.go
index 69b25c696..f6327ecd5 100644
--- a/routing/blinding_test.go
+++ b/routing/blinding_test.go
@@ -202,10 +202,10 @@ func TestBlindedPaymentToHints(t *testing.T) {
 		// The arguments we use for the payload do not matter as long as
 		// both functions return the same payload.
 		expectedPayloadSize := expectedHint[0].IntermediatePayloadSize(
-			0, 0, false, 0,
+			0, 0, 0,
 		)
 		actualPayloadSize := actualHint[0].IntermediatePayloadSize(
-			0, 0, false, 0,
+			0, 0, 0,
 		)
 
 		require.Equal(t, expectedPayloadSize, actualPayloadSize)
diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go
index 57f723ce6..51859adf6 100644
--- a/routing/pathfind_test.go
+++ b/routing/pathfind_test.go
@@ -3,7 +3,6 @@ package routing
 import (
 	"bytes"
 	"crypto/sha256"
-	"encoding/binary"
 	"encoding/hex"
 	"encoding/json"
 	"errors"
@@ -25,6 +24,7 @@ import (
 	"github.com/lightningnetwork/lnd/channeldb"
 	"github.com/lightningnetwork/lnd/channeldb/models"
 	"github.com/lightningnetwork/lnd/htlcswitch"
+	switchhop "github.com/lightningnetwork/lnd/htlcswitch/hop"
 	"github.com/lightningnetwork/lnd/kvdb"
 	"github.com/lightningnetwork/lnd/lnwire"
 	"github.com/lightningnetwork/lnd/record"
@@ -763,9 +763,6 @@ func TestPathFinding(t *testing.T) {
 	}, {
 		name: "path finding with additional edges",
 		fn:   runPathFindingWithAdditionalEdges,
-	}, {
-		name: "path finding max payload restriction",
-		fn:   runPathFindingMaxPayloadRestriction,
 	}, {
 		name: "path finding with redundant additional edges",
 		fn:   runPathFindingWithRedundantAdditionalEdges,
@@ -1033,7 +1030,8 @@ var basicGraphPathFindingTests = []basicGraphPathFindingTestCase{
 	// Basic route with fee limit.
 	{target: "sophon", paymentAmt: 100, feeLimit: 50,
 		expectFailureNoPath: true,
-	}}
+	},
+}
 
 func runBasicGraphPathFinding(t *testing.T, useCache bool) {
 	testGraphInstance, err := parseTestGraph(t, useCache, basicGraphFilePath)
@@ -1126,32 +1124,27 @@ func testBasicGraphPathFindingCase(t *testing.T, graphInstance *testGraphInstanc
 
 	// Hops should point to the next hop
 	for i := 0; i < len(expectedHops)-1; i++ {
-		var expectedHop [8]byte
-		binary.BigEndian.PutUint64(expectedHop[:], route.Hops[i+1].ChannelID)
-
-		hopData, err := sphinxPath[i].HopPayload.HopData()
-		if err != nil {
-			t.Fatalf("unable to make hop data: %v", err)
-		}
+		payload, _, err := switchhop.ParseTLVPayload(
+			bytes.NewReader(sphinxPath[i].HopPayload.Payload),
+		)
+		require.NoError(t, err)
 
-		if !bytes.Equal(hopData.NextAddress[:], expectedHop[:]) {
-			t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
-				expectedHop[:], hopData.NextAddress[:])
-		}
+		require.Equal(
+			t, route.Hops[i+1].ChannelID,
+			payload.FwdInfo.NextHop.ToUint64(),
+		)
 	}
 
-	// The final hop should have a next hop value of all zeroes in order
-	// to indicate it's the exit hop.
-	var exitHop [8]byte
 	lastHopIndex := len(expectedHops) - 1
 
-	hopData, err := sphinxPath[lastHopIndex].HopPayload.HopData()
-	require.NoError(t, err, "unable to create hop data")
+	payload, _, err := switchhop.ParseTLVPayload(
+		bytes.NewReader(sphinxPath[lastHopIndex].HopPayload.Payload),
+	)
+	require.NoError(t, err)
 
-	if !bytes.Equal(hopData.NextAddress[:], exitHop[:]) {
-		t.Fatalf("first hop has incorrect next hop: expected %x, got %x",
-			exitHop[:], hopData.NextAddress)
-	}
+	// The final hop should have a next hop value of all zeroes in order
+	// to indicate it's the exit hop.
+	require.Zero(t, payload.FwdInfo.NextHop.ToUint64())
 
 	var expectedTotalFee lnwire.MilliSatoshi
 	for i := 0; i < expectedHopCount; i++ {
@@ -1291,122 +1284,6 @@ func runPathFindingWithAdditionalEdges(t *testing.T, useCache bool) {
 	assertExpectedPath(t, graph.aliasMap, path, "songoku", "doge")
 }
 
-// runPathFindingMaxPayloadRestriction tests the maximum size of a sphinx
-// package when creating a route. So we make sure the pathfinder does not return
-// a route which is greater than the maximum sphinx package size of 1300 bytes
-// defined in BOLT04.
-func runPathFindingMaxPayloadRestriction(t *testing.T, useCache bool) {
-	graph, err := parseTestGraph(t, useCache, basicGraphFilePath)
-	require.NoError(t, err, "unable to create graph")
-
-	sourceNode, err := graph.graph.SourceNode()
-	require.NoError(t, err, "unable to fetch source node")
-
-	paymentAmt := lnwire.NewMSatFromSatoshis(100)
-
-	// Create a node doge which is not visible in the graph.
-	dogePubKeyHex := "03dd46ff29a6941b4a2607525b043ec9b020b3f318a1bf281" +
-		"536fd7011ec59c882"
-	dogePubKeyBytes, err := hex.DecodeString(dogePubKeyHex)
-	require.NoError(t, err, "unable to decode public key")
-	dogePubKey, err := btcec.ParsePubKey(dogePubKeyBytes)
-	require.NoError(t, err, "unable to parse public key from bytes")
-
-	doge := &channeldb.LightningNode{}
-	doge.AddPubKey(dogePubKey)
-	doge.Alias = "doge"
-	copy(doge.PubKeyBytes[:], dogePubKeyBytes)
-	graph.aliasMap["doge"] = doge.PubKeyBytes
-
-	const (
-		chanID          uint64 = 1337
-		finalHtlcExpiry int32  = 0
-	)
-
-	// Create the channel edge going from songoku to doge and later add it
-	// with the mocked size function to the graph data.
-	songokuToDogePolicy := &models.CachedEdgePolicy{
-		ToNodePubKey: func() route.Vertex {
-			return doge.PubKeyBytes
-		},
-		ToNodeFeatures:            lnwire.EmptyFeatureVector(),
-		ChannelID:                 chanID,
-		FeeBaseMSat:               1,
-		FeeProportionalMillionths: 1000,
-		TimeLockDelta:             9,
-	}
-
-	// The route has 2 hops. The exit hop (doge) and the hop
-	// (songoku -> doge). The desired path looks like this:
-	// source -> songoku -> doge
-	tests := []struct {
-		name              string
-		mockedPayloadSize uint64
-		err               error
-	}{
-		{
-			// The final hop payload size needs to be considered
-			// as well and because it's treated differently than the
-			// intermediate hops the following tests choose to use
-			// the legacy payload format to have a constant final
-			// hop payload size.
-			name:              "route max payload size (1300)",
-			mockedPayloadSize: 1300 - sphinx.LegacyHopDataSize,
-		},
-		{
-			// We increase the enrypted data size by one byte.
-			name: "route 1 bytes bigger than max " +
-				"payload",
-			mockedPayloadSize: 1300 - sphinx.LegacyHopDataSize + 1,
-			err:               errNoPathFound,
-		},
-	}
-
-	for _, testCase := range tests {
-		testCase := testCase
-
-		t.Run(testCase.name, func(t *testing.T) {
-			t.Parallel()
-
-			restrictions := *noRestrictions
-			// No tlv payload, this makes sure the final hop uses
-			// the legacy payload.
-			restrictions.DestFeatures = lnwire.EmptyFeatureVector()
-
-			// Create the mocked AdditionalEdge and mock the
-			// corresponding calls.
-			mockedEdge := &mockAdditionalEdge{}
-
-			mockedEdge.On("EdgePolicy").Return(songokuToDogePolicy)
-
-			mockedEdge.On("IntermediatePayloadSize",
-				paymentAmt, uint32(finalHtlcExpiry), true,
-				chanID).Once().
-				Return(testCase.mockedPayloadSize)
-
-			additionalEdges := map[route.Vertex][]AdditionalEdge{
-				graph.aliasMap["songoku"]: {mockedEdge},
-			}
-
-			path, err := dbFindPath(
-				graph.graph, additionalEdges,
-				&mockBandwidthHints{}, &restrictions,
-				testPathFindingConfig, sourceNode.PubKeyBytes,
-				doge.PubKeyBytes, paymentAmt, 0,
-				finalHtlcExpiry,
-			)
-			require.ErrorIs(t, err, testCase.err)
-
-			if err == nil {
-				assertExpectedPath(t, graph.aliasMap, path,
-					"songoku", "doge")
-			}
-
-			mockedEdge.AssertExpectations(t)
-		})
-	}
-}
-
 // runPathFindingWithRedundantAdditionalEdges asserts that we are able to find
 // paths to nodes ignoring additional edges that are already known by self node.
 func runPathFindingWithRedundantAdditionalEdges(t *testing.T, useCache bool) {
@@ -1464,7 +1341,6 @@ func runPathFindingWithRedundantAdditionalEdges(t *testing.T, useCache bool) {
 // TestNewRoute tests whether the construction of hop payloads by newRoute
 // is executed correctly.
 func TestNewRoute(t *testing.T) {
-
 	var sourceKey [33]byte
 	sourceVertex := route.Vertex(sourceKey)
 
@@ -1541,8 +1417,6 @@ func TestNewRoute(t *testing.T) {
 		// expectError is true.
 		expectedErrorCode errorCode
 
-		expectedTLVPayload bool
-
 		expectedMPP *record.MPP
 	}{
 		{
@@ -1586,7 +1460,6 @@ func TestNewRoute(t *testing.T) {
 			expectedTimeLocks:     []uint32{1, 1},
 			expectedTotalAmount:   100130,
 			expectedTotalTimeLock: 6,
-			expectedTLVPayload:    true,
 		}, {
 			// For a two hop payment, only the fee for the first hop
 			// needs to be paid. The destination hop does not require
@@ -1603,7 +1476,6 @@ func TestNewRoute(t *testing.T) {
 			expectedTimeLocks:     []uint32{1, 1},
 			expectedTotalAmount:   100130,
 			expectedTotalTimeLock: 6,
-			expectedTLVPayload:    true,
 			expectedMPP: record.NewMPP(
 				100000, testPaymentAddr,
 			),
@@ -1717,14 +1589,6 @@ func TestNewRoute(t *testing.T) {
 			}
 
 			finalHop := route.Hops[len(route.Hops)-1]
-			if !finalHop.LegacyPayload !=
-				testCase.expectedTLVPayload {
-
-				t.Errorf("Expected final hop tlv payload: %t, "+
-					"but got: %t instead",
-					testCase.expectedTLVPayload,
-					!finalHop.LegacyPayload)
-			}
 
 			if !reflect.DeepEqual(
 				finalHop.MPP, testCase.expectedMPP,
@@ -1787,9 +1651,9 @@ func TestNewRoute(t *testing.T) {
 func runNewRoutePathTooLong(t *testing.T, useCache bool) {
 	var testChannels []*testChannel
 
-	// Setup a linear network of 21 hops.
+	// Setup a linear network of 26 hops.
 	fromNode := "start"
-	for i := 0; i < 21; i++ {
+	for i := 0; i < 26; i++ {
 		toNode := fmt.Sprintf("node-%v", i+1)
 		c := symmetricTestChannel(fromNode, toNode, 100000, &testChannelPolicy{
 			Expiry:  144,
@@ -1804,18 +1668,16 @@ func runNewRoutePathTooLong(t *testing.T, useCache bool) {
 
 	ctx := newPathFindingTestContext(t, useCache, testChannels, "start")
 
-	// Assert that we can find 20 hop routes.
-	node20 := ctx.keyFromAlias("node-20")
+	// Assert that we can find 25 hop routes.
+	node25 := ctx.keyFromAlias("node-25")
 	payAmt := lnwire.MilliSatoshi(100001)
-	_, err := ctx.findPath(node20, payAmt)
+	_, err := ctx.findPath(node25, payAmt)
 	require.NoError(t, err, "unexpected pathfinding failure")
 
-	// Assert that finding a 21 hop route fails.
-	node21 := ctx.keyFromAlias("node-21")
-	_, err = ctx.findPath(node21, payAmt)
-	if err != errNoPathFound {
-		t.Fatalf("not route error expected, but got %v", err)
-	}
+	// Assert that finding a 26 hop route fails.
+	node26 := ctx.keyFromAlias("node-26")
+	_, err = ctx.findPath(node26, payAmt)
+	require.ErrorIs(t, err, errNoPathFound)
 
 	// Assert that we can't find a 20 hop route if custom records make it
 	// exceed the maximum payload size.
@@ -1823,7 +1685,7 @@ func runNewRoutePathTooLong(t *testing.T, useCache bool) {
 	ctx.restrictParams.DestCustomRecords = map[uint64][]byte{
 		100000: bytes.Repeat([]byte{1}, 100),
 	}
-	_, err = ctx.findPath(node20, payAmt)
+	_, err = ctx.findPath(node25, payAmt)
 	if err != errNoPathFound {
 		t.Fatalf("not route error expected, but got %v", err)
 	}
@@ -3675,7 +3537,6 @@ func TestLastHopPayloadSize(t *testing.T) {
 		restrictions   *RestrictParams
 		finalHopExpiry int32
 		amount         lnwire.MilliSatoshi
-		legacy         bool
 	}{
 		{
 			name: "Non blinded final hop",
@@ -3687,22 +3548,6 @@ func TestLastHopPayloadSize(t *testing.T) {
 			},
 			amount:         amtToForward,
 			finalHopExpiry: finalHopExpiry,
-			legacy:         false,
-		},
-		{
-			name: "Non blinded final hop legacy",
-			restrictions: &RestrictParams{
-				// The legacy encoding has no ability to include
-				// those extra data we expect that this data is
-				// ignored.
-				PaymentAddr:       paymentAddr,
-				DestCustomRecords: customRecords,
-				Metadata:          metadata,
-				Amp:               ampOptions,
-			},
-			amount:         amtToForward,
-			finalHopExpiry: finalHopExpiry,
-			legacy:         true,
 		},
 		{
 			name: "Blinded final hop introduction point",
@@ -3754,7 +3599,6 @@ func TestLastHopPayloadSize(t *testing.T) {
 				finalHop = route.Hop{
 					AmtToForward:     tc.amount,
 					OutgoingTimeLock: uint32(tc.finalHopExpiry),
-					LegacyPayload:    false,
 					EncryptedData:    blindedPath[len(blindedPath)-1].CipherText,
 				}
 				if len(blindedPath) == 1 {
@@ -3763,7 +3607,6 @@ func TestLastHopPayloadSize(t *testing.T) {
 			} else {
 				//nolint:lll
 				finalHop = route.Hop{
-					LegacyPayload:    tc.legacy,
 					AmtToForward:     tc.amount,
 					OutgoingTimeLock: uint32(tc.finalHopExpiry),
 					Metadata:         tc.restrictions.Metadata,
@@ -3778,7 +3621,7 @@ func TestLastHopPayloadSize(t *testing.T) {
 
 			expectedPayloadSize := lastHopPayloadSize(
 				tc.restrictions, tc.finalHopExpiry,
-				tc.amount, tc.legacy,
+				tc.amount,
 			)
 
 			require.Equal(