diff --git a/cmd/lncli/cmd_payments.go b/cmd/lncli/cmd_payments.go index ec6a04b37..228dd3415 100644 --- a/cmd/lncli/cmd_payments.go +++ b/cmd/lncli/cmd_payments.go @@ -1099,8 +1099,13 @@ var queryRoutesCommand = cli.Command{ }, cli.Int64Flag{ Name: "final_cltv_delta", - Usage: "(optional) number of blocks the last hop has to reveal " + - "the preimage", + Usage: "(optional) number of blocks the last hop has " + + "to reveal the preimage. Note that this " + + "should not be set in the case where the " + + "path includes a blinded path since in " + + "that case, the receiver will already have " + + "accounted for this value in the " + + "blinded_cltv value", }, cli.BoolFlag{ Name: "use_mc", @@ -1238,6 +1243,13 @@ func parseBlindedPaymentParameters(ctx *cli.Context) ( return nil, nil } + // If a blinded path has been provided, then the final_cltv_delta flag + // should not be provided since this value will be ignored. + if ctx.IsSet("final_cltv_delta") { + return nil, fmt.Errorf("`final_cltv_delta` should not be " + + "provided if a blinded path is provided") + } + // If any one of our blinding related flags is set, we expect the // full set to be set and we'll error out accordingly. introNode, err := route.NewVertexFromStr( diff --git a/routing/pathfind.go b/routing/pathfind.go index 801725d3e..208a55085 100644 --- a/routing/pathfind.go +++ b/routing/pathfind.go @@ -96,9 +96,17 @@ type edgePolicyWithSource struct { // such as amounts and cltvs, as well as more complex features like destination // custom records and payment address. type finalHopParams struct { - amt lnwire.MilliSatoshi - totalAmt lnwire.MilliSatoshi - cltvDelta uint16 + amt lnwire.MilliSatoshi + totalAmt lnwire.MilliSatoshi + + // cltvDelta is the final hop's minimum CLTV expiry delta. + // + // NOTE that in the case of paying to a blinded path, this value will + // be set to a duplicate of the blinded path's accumulated CLTV value. + // We would then only need to use this value in the case where the + // introduction node of the path is also the destination node. + cltvDelta uint16 + records record.CustomSet paymentAddr *[32]byte @@ -190,10 +198,21 @@ func newRoute(sourceVertex route.Vertex, // reporting through RPC. Set to zero for the final hop. fee = 0 - // 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. - totalTimeLock += uint32(finalHop.cltvDelta) + // 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 blindedPath == nil || + len(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. + totalTimeLock += uint32(finalHop.cltvDelta) + } outgoingTimeLock = totalTimeLock // Attach any custom records to the final hop. diff --git a/routing/pathfind_test.go b/routing/pathfind_test.go index fd9839dba..0f2a2659b 100644 --- a/routing/pathfind_test.go +++ b/routing/pathfind_test.go @@ -3322,16 +3322,19 @@ func TestBlindedRouteConstruction(t *testing.T) { ToNodeFeatures: tlvFeatures, } - // Create final hop parameters for payment amount = 110. Note - // that final cltv delta is not set because blinded paths - // include this final delta in their aggregate delta. A - // sender-set delta may be added to account for block arrival - // during payment, but we do not set it in this test. + // Create final hop parameters for payment amount = 110. totalAmt lnwire.MilliSatoshi = 110 finalHopParams = finalHopParams{ amt: totalAmt, totalAmt: totalAmt, metadata: metadata, + + // We set a CLTV delta here just to test that this will + // be ignored by newRoute since this is a blinded path + // where the accumulated CLTV delta for the route + // communicated in the blinded path should be assumed to + // include the CLTV delta of the final hop. + cltvDelta: MaxCLTVDelta, } ) diff --git a/zpay32/invoice.go b/zpay32/invoice.go index 2afc59d95..8b1457ab4 100644 --- a/zpay32/invoice.go +++ b/zpay32/invoice.go @@ -156,6 +156,10 @@ type Invoice struct { // This field is un-exported and can only be read by the // MinFinalCLTVExpiry() method. By forcing callers to read via this // method, we can easily enforce the default if not specified. + // + // NOTE: this field is ignored in the case that the invoice contains + // blinded paths since then the final minimum cltv expiry delta is + // expected to be included in the route's accumulated CLTV delta value. minFinalCLTVExpiry *uint64 // Description is a short description of the purpose of this invoice.