From daaa24b69ce601a235c5432ed0d5abed741aef30 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Wed, 15 May 2024 15:59:43 +0200 Subject: [PATCH] routing: let BlindedPaymentPathSet handle FinalCLTV logic Instead of needing to remember how to handle the FinalCLTV value of a blinded payment path at various points in the code base, we hide the logic behind a unified FinalCLTVDelta method on the blinded path. --- routing/blinding.go | 40 ++++++++++++++++++++++++++++++++++++---- routing/pathfind.go | 19 +++++-------------- routing/router.go | 12 +----------- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/routing/blinding.go b/routing/blinding.go index 32bcfa3ff..401d7f3ee 100644 --- a/routing/blinding.go +++ b/routing/blinding.go @@ -51,6 +51,22 @@ type BlindedPaymentPathSet struct { // moment we require that all paths for the same payment have the // same feature set. features *lnwire.FeatureVector + + // finalCLTV is the final hop's expiry delta of _any_ path in the set. + // For any multi-hop path, the final CLTV delta should be seen as zero + // since the final hop's final CLTV delta is accounted for in the + // accumulated path policy values. The only edge case is for when the + // final hop in the path is also the introduction node in which case + // that path's FinalCLTV must be the non-zero min CLTV of the final hop + // so that it is accounted for in path finding. For this reason, if + // we have any single path in the set with only one hop, then we throw + // away all the other paths. This should be fine to do since if there is + // a path where the intro node is also the destination node, then there + // isn't any need to try any other longer blinded path. In other words, + // if this value is non-zero, then there is only one path in this + // blinded path set and that path only has a single hop: the + // introduction node. + finalCLTV uint16 } // NewBlindedPaymentPathSet constructs a new BlindedPaymentPathSet from a set of @@ -95,19 +111,25 @@ func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet, } targetPub := targetPriv.PubKey() + var ( + pathSet = paths + finalCLTVDelta uint16 + ) // If any provided blinded path only has a single hop (ie, the // destination node is also the introduction node), then we discard all // other paths since we know the real pub key of the destination node. - // For a single hop path, there is also no need for the pseudo target - // pub key replacement, so our target pub key in this case just remains - // the real introduction node ID. - var pathSet = paths + // We also then set the final CLTV delta to the path's delta since + // there are no other edge hints that will account for it. For a single + // hop path, there is also no need for the pseudo target pub key + // replacement, so our target pub key in this case just remains the + // real introduction node ID. for _, path := range paths { if len(path.BlindedPath.BlindedHops) != 1 { continue } pathSet = []*BlindedPayment{path} + finalCLTVDelta = path.CltvExpiryDelta targetPub = path.BlindedPath.IntroductionPoint break @@ -117,6 +139,7 @@ func NewBlindedPaymentPathSet(paths []*BlindedPayment) (*BlindedPaymentPathSet, paths: pathSet, targetPubKey: targetPub, features: features, + finalCLTV: finalCLTVDelta, }, nil } @@ -137,6 +160,15 @@ func (s *BlindedPaymentPathSet) GetPath() *BlindedPayment { return s.paths[0] } +// FinalCLTVDelta is the minimum CLTV delta to use for the final hop on the +// route. In most cases this will return zero since the value is accounted for +// in the path's accumulated CLTVExpiryDelta. Only in the edge case of the path +// set only including a single path which only includes an introduction node +// will this return a non-zero value. +func (s *BlindedPaymentPathSet) FinalCLTVDelta() uint16 { + return s.finalCLTV +} + // LargestLastHopPayloadPath returns the BlindedPayment in the set that has the // largest last-hop payload. This is to be used for onion size estimation in // path finding. diff --git a/routing/pathfind.go b/routing/pathfind.go index bf1d3bf4f..5169bdab9 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -210,21 +210,12 @@ func newRoute(sourceVertex route.Vertex, // reporting through RPC. Set to zero for the final hop. fee = 0 - // Only include the final hop CLTV delta in the total - // time lock value if this is not a route to a blinded - // path. For blinded paths, the total time-lock from the - // whole path will be deduced from the introduction - // node's CLTV delta. The exception is for the case - // where the final hop is the blinded path introduction - // node. - if blindedPathSet == nil || - len(blindedPathSet.GetPath().BlindedPath. - BlindedHops) == 1 { - - // As this is the last hop, we'll use the - // specified final CLTV delta value instead of - // the value from the last link in the route. + if blindedPathSet == nil { totalTimeLock += uint32(finalHop.cltvDelta) + } else { + totalTimeLock += uint32( + blindedPathSet.FinalCLTVDelta(), + ) } outgoingTimeLock = totalTimeLock diff --git a/routing/router.go b/routing/router.go index 13e36f304..bca78b5ad 100644 --- a/routing/router.go +++ b/routing/router.go @@ -524,17 +524,7 @@ func NewRouteRequest(source route.Vertex, target *route.Vertex, return nil, ErrExpiryAndBlinded } - // If we have a blinded path with 1 hop, the cltv expiry - // will not be included in any hop hints (since we're just - // sending to the introduction node and need no blinded hints). - // In this case, we include it to make sure that the final - // cltv delta is accounted for (since it's part of the blinded - // delta). In the case of a multi-hop route, we set our final - // cltv to zero, since it's going to be accounted for in the - // delta for our hints. - if len(blindedPayment.BlindedPath.BlindedHops) == 1 { - requestExpiry = blindedPayment.CltvExpiryDelta - } + requestExpiry = blindedPathSet.FinalCLTVDelta() requestHints, err = blindedPathSet.ToRouteHints() if err != nil {