From 3b2a6042ff49e83dd550c7770ff86359e656f30e Mon Sep 17 00:00:00 2001 From: Elle Mouton Date: Sat, 4 May 2024 10:43:03 +0200 Subject: [PATCH] lnrpc/invoicesrpc: blinded path total path policy calc This commit adds a function that can be used to compute the accumulated path policy for a blinded path as defined in the spec: https://github.com/lightning/bolts/blob/db278ab9b2baa0b30cfe79fb3de39280595938d3/04-onion-routing.md?plain=1#L255 --- lnrpc/invoicesrpc/addinvoice.go | 57 ++++++++++++++++++++++++++++ lnrpc/invoicesrpc/addinvoice_test.go | 34 +++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/lnrpc/invoicesrpc/addinvoice.go b/lnrpc/invoicesrpc/addinvoice.go index 7896fcd34..6c278701c 100644 --- a/lnrpc/invoicesrpc/addinvoice.go +++ b/lnrpc/invoicesrpc/addinvoice.go @@ -46,6 +46,9 @@ const ( // maxHopHints is the maximum number of hint paths that will be included // in an invoice. maxHopHints = 20 + + // oneMillion is a constant used frequently in fee rate calculations. + oneMillion = uint32(1_000_000) ) // AddInvoiceConfig contains dependencies for invoice creation. @@ -841,6 +844,60 @@ func PopulateHopHints(cfg *SelectHopHintsCfg, amtMSat lnwire.MilliSatoshi, return hopHints, 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 +// already been adjusted with a buffer to account for easy probing attacks. +func calcBlindedPathPolicies(relayInfo []*record.PaymentRelayInfo, + ourMinFinalCLTVDelta uint16) (lnwire.MilliSatoshi, uint32, uint16) { + + var ( + totalFeeBase lnwire.MilliSatoshi + totalFeeProp uint32 + totalCLTV = ourMinFinalCLTVDelta + ) + // Use the algorithms defined in BOLT 4 to calculate the accumulated + // relay fees for the route: + //nolint:lll + // https://github.com/lightning/bolts/blob/db278ab9b2baa0b30cfe79fb3de39280595938d3/04-onion-routing.md?plain=1#L255 + for i := len(relayInfo) - 1; i >= 0; i-- { + info := relayInfo[i] + + totalFeeBase = calcNextTotalBaseFee( + totalFeeBase, info.BaseFee, info.FeeRate, + ) + + totalFeeProp = calcNextTotalFeeRate(totalFeeProp, info.FeeRate) + + totalCLTV += info.CltvExpiryDelta + } + + return totalFeeBase, totalFeeProp, totalCLTV +} + +// calcNextTotalBaseFee takes the current total accumulated base fee of a +// blinded path at hop `n` along with the fee rate and base fee of the hop at +// `n-1` and uses these to calculate the accumulated base fee at hop `n-1`. +func calcNextTotalBaseFee(currentTotal, hopBaseFee lnwire.MilliSatoshi, + hopFeeRate uint32) lnwire.MilliSatoshi { + + numerator := (uint32(hopBaseFee) * oneMillion) + + (uint32(currentTotal) * (oneMillion + hopFeeRate)) + + oneMillion - 1 + + return lnwire.MilliSatoshi(numerator / oneMillion) +} + +// calculateNextTotalFeeRate takes the current total accumulated fee rate of a +// blinded path at hop `n` along with the fee rate of the hop at `n-1` and uses +// these to calculate the accumulated fee rate at hop `n-1`. +func calcNextTotalFeeRate(currentTotal, hopFeeRate uint32) uint32 { + numerator := (currentTotal+hopFeeRate)*oneMillion + + currentTotal*hopFeeRate + oneMillion - 1 + + return numerator / oneMillion +} + // hopData packages the record.BlindedRouteData for a hop on a blinded path with // the real node ID of that hop. type hopData struct { diff --git a/lnrpc/invoicesrpc/addinvoice_test.go b/lnrpc/invoicesrpc/addinvoice_test.go index 76775508b..15c3ec677 100644 --- a/lnrpc/invoicesrpc/addinvoice_test.go +++ b/lnrpc/invoicesrpc/addinvoice_test.go @@ -902,6 +902,40 @@ func TestPopulateHopHints(t *testing.T) { } } +// TestBlindedPathAccumulatedPolicyCalc tests the logic for calculating the +// accumulated routing policies of a blinded route against an example mentioned +// in the spec document: +// https://github.com/lightning/bolts/blob/master/proposals/route-blinding.md +func TestBlindedPathAccumulatedPolicyCalc(t *testing.T) { + t.Parallel() + + // In the spec example, the blinded route is: + // Carol -> Bob -> Alice + // And Alice chooses the following buffered policy for both the C->B + // and B->A edges. + nodePolicy := &record.PaymentRelayInfo{ + FeeRate: 500, + BaseFee: 100, + CltvExpiryDelta: 144, + } + + hopPolicies := []*record.PaymentRelayInfo{ + nodePolicy, + nodePolicy, + } + + // Alice's minimum final expiry delta is chosen to be 12. + aliceMinFinalExpDelta := uint16(12) + + totalBase, totalRate, totalCLTVDelta := calcBlindedPathPolicies( + hopPolicies, aliceMinFinalExpDelta, + ) + + require.Equal(t, lnwire.MilliSatoshi(201), totalBase) + require.EqualValues(t, 1001, totalRate) + require.EqualValues(t, 300, totalCLTVDelta) +} + // TestPadBlindedHopInfo asserts that the padding of blinded hop data is done // correctly and that it takes the expected number of iterations. func TestPadBlindedHopInfo(t *testing.T) {