From 0855e3e71a94688e082a642a6ccbe6a3bb22dda0 Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sat, 4 May 2024 11:11:35 +0200 Subject: [PATCH] lnrpc/invoicesrpc: add blinded path policy buffer This commit adds a helper function that will be used to adjust a hops policy values by certain given increase and decrease multipliers. This will be used in blinded paths to give policy values some buffer to avoid easy probing of blinded paths. --- lnrpc/invoicesrpc/addinvoice.go | 71 +++++++++++++ lnrpc/invoicesrpc/addinvoice_test.go | 153 +++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index 6c278701c..4d3d451af 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -844,6 +844,77 @@ func PopulateHopHints(cfg *SelectHopHintsCfg, amtMSat lnwire.MilliSatoshi, return hopHints, nil } +// blindedHopPolicy holds the set of relay policy values to use for a channel +// in a blinded path. +type blindedHopPolicy struct { + cltvExpiryDelta uint16 + feeRate uint32 + baseFee lnwire.MilliSatoshi + minHTLCMsat lnwire.MilliSatoshi + maxHTLCMsat lnwire.MilliSatoshi +} + +// addPolicyBuffer constructs the bufferedChanPolicies for a path hop by taking +// its actual policy values and multiplying them by the given multipliers. +// The base fee, fee rate and minimum HTLC msat values are adjusted via the +// incMultiplier while the maximum HTLC msat value is adjusted via the +// decMultiplier. If adjustments of the HTLC values no longer make sense +// then the original HTLC value is used. +func addPolicyBuffer(policy *blindedHopPolicy, incMultiplier, + decMultiplier float64) (*blindedHopPolicy, error) { + + if incMultiplier < 1 { + return nil, fmt.Errorf("blinded path policy increase " + + "multiplier must be greater than or equal to 1") + } + + if decMultiplier < 0 || decMultiplier > 1 { + return nil, fmt.Errorf("blinded path policy decrease " + + "multiplier must be in the range [0;1]") + } + + var ( + minHTLCMsat = lnwire.MilliSatoshi( + float64(policy.minHTLCMsat) * incMultiplier, + ) + maxHTLCMsat = lnwire.MilliSatoshi( + float64(policy.maxHTLCMsat) * decMultiplier, + ) + ) + + // Make sure the new minimum is not more than the original maximum. + // If it is, then just stick to the original minimum. + if minHTLCMsat > policy.maxHTLCMsat { + minHTLCMsat = policy.minHTLCMsat + } + + // Make sure the new maximum is not less than the original minimum. + // If it is, then just stick to the original maximum. + if maxHTLCMsat < policy.minHTLCMsat { + maxHTLCMsat = policy.maxHTLCMsat + } + + // Also ensure that the new htlc bounds make sense. If the new minimum + // is greater than the new maximum, then just let both to their original + // values. + if minHTLCMsat > maxHTLCMsat { + minHTLCMsat = policy.minHTLCMsat + maxHTLCMsat = policy.maxHTLCMsat + } + + return &blindedHopPolicy{ + cltvExpiryDelta: uint16( + float64(policy.cltvExpiryDelta) * incMultiplier, + ), + feeRate: uint32(float64(policy.feeRate) * incMultiplier), + baseFee: lnwire.MilliSatoshi( + float64(policy.baseFee) * incMultiplier, + ), + minHTLCMsat: minHTLCMsat, + maxHTLCMsat: maxHTLCMsat, + }, nil +} + // calcBlindedPathPolicies computes the accumulated policy values for the path. // These values include the total base fee, the total proportional fee and the // total CLTV delta. This function assumes that all the passed relay infos have diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index 15c3ec677..bd83d53d2 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -902,6 +902,159 @@ func TestPopulateHopHints(t *testing.T) { } } +// TestApplyBlindedPathPolicyBuffer tests blinded policy adjustments. +func TestApplyBlindedPathPolicyBuffer(t *testing.T) { + tests := []struct { + name string + policyIn *blindedHopPolicy + expectedOut *blindedHopPolicy + incMultiplier float64 + decMultiplier float64 + expectedError string + }{ + { + name: "invalid increase multiplier", + incMultiplier: 0, + expectedError: "blinded path policy increase " + + "multiplier must be greater than or equal to 1", + }, + { + name: "decrease multiplier too small", + incMultiplier: 1, + decMultiplier: -1, + expectedError: "blinded path policy decrease " + + "multiplier must be in the range [0;1]", + }, + { + name: "decrease multiplier too big", + incMultiplier: 1, + decMultiplier: 2, + expectedError: "blinded path policy decrease " + + "multiplier must be in the range [0;1]", + }, + { + name: "no change", + incMultiplier: 1, + decMultiplier: 1, + policyIn: &blindedHopPolicy{ + cltvExpiryDelta: 1, + minHTLCMsat: 2, + maxHTLCMsat: 3, + baseFee: 4, + feeRate: 5, + }, + expectedOut: &blindedHopPolicy{ + cltvExpiryDelta: 1, + minHTLCMsat: 2, + maxHTLCMsat: 3, + baseFee: 4, + feeRate: 5, + }, + }, + { + name: "buffer up by 100% and down by and down " + + "by 50%", + incMultiplier: 2, + decMultiplier: 0.5, + policyIn: &blindedHopPolicy{ + cltvExpiryDelta: 10, + minHTLCMsat: 20, + maxHTLCMsat: 300, + baseFee: 40, + feeRate: 50, + }, + expectedOut: &blindedHopPolicy{ + cltvExpiryDelta: 20, + minHTLCMsat: 40, + maxHTLCMsat: 150, + baseFee: 80, + feeRate: 100, + }, + }, + { + name: "new HTLC minimum larger than OG " + + "maximum", + incMultiplier: 2, + decMultiplier: 1, + policyIn: &blindedHopPolicy{ + cltvExpiryDelta: 10, + minHTLCMsat: 20, + maxHTLCMsat: 30, + baseFee: 40, + feeRate: 50, + }, + expectedOut: &blindedHopPolicy{ + cltvExpiryDelta: 20, + minHTLCMsat: 20, + maxHTLCMsat: 30, + baseFee: 80, + feeRate: 100, + }, + }, + { + name: "new HTLC maximum smaller than OG " + + "minimum", + incMultiplier: 1, + decMultiplier: 0.5, + policyIn: &blindedHopPolicy{ + cltvExpiryDelta: 10, + minHTLCMsat: 20, + maxHTLCMsat: 30, + baseFee: 40, + feeRate: 50, + }, + expectedOut: &blindedHopPolicy{ + cltvExpiryDelta: 10, + minHTLCMsat: 20, + maxHTLCMsat: 30, + baseFee: 40, + feeRate: 50, + }, + }, + { + name: "new HTLC minimum and maximums are not " + + "compatible", + incMultiplier: 2, + decMultiplier: 0.5, + policyIn: &blindedHopPolicy{ + cltvExpiryDelta: 10, + minHTLCMsat: 30, + maxHTLCMsat: 100, + baseFee: 40, + feeRate: 50, + }, + expectedOut: &blindedHopPolicy{ + cltvExpiryDelta: 20, + minHTLCMsat: 30, + maxHTLCMsat: 100, + baseFee: 80, + feeRate: 100, + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + bufferedPolicy, err := addPolicyBuffer( + test.policyIn, test.incMultiplier, + test.decMultiplier, + ) + if test.expectedError != "" { + require.ErrorContains( + t, err, test.expectedError, + ) + + return + } + + require.Equal(t, test.expectedOut, bufferedPolicy) + }) + } +} + // TestBlindedPathAccumulatedPolicyCalc tests the logic for calculating the // accumulated routing policies of a blinded route against an example mentioned // in the spec document: