From b96d57e88f9877ce239c49310fccf6b315731728 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 14 Apr 2025 15:30:00 +0200 Subject: [PATCH 1/4] lnwallet: add AuxHtlcView --- lnwallet/aux_signer.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lnwallet/aux_signer.go b/lnwallet/aux_signer.go index 510b64b5d..90a4325f6 100644 --- a/lnwallet/aux_signer.go +++ b/lnwallet/aux_signer.go @@ -5,6 +5,7 @@ import ( "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/input" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" ) @@ -13,6 +14,37 @@ import ( // signatures within the custom data for an existing HTLC. var htlcCustomSigType tlv.TlvType65543 +// AuxHtlcView is a struct that contains a safe copy of an HTLC view that can +// be used by aux components. +type AuxHtlcView struct { + // NextHeight is the height of the commitment transaction that will be + // created using this view. + NextHeight uint64 + + // Updates is a Dual of the Local and Remote HTLCs. + Updates lntypes.Dual[[]AuxHtlcDescriptor] + + // FeePerKw is the fee rate in sat/kw of the commitment transaction. + FeePerKw chainfee.SatPerKWeight +} + +// newAuxHtlcView creates a new safe copy of the HTLC view that can be used by +// aux components. +// +// NOTE: This function should only be called while holding the channel's read +// lock, since the underlying local/remote payment descriptors are accessed +// directly. +func newAuxHtlcView(v *HtlcView) AuxHtlcView { + return AuxHtlcView{ + NextHeight: v.NextHeight, + Updates: lntypes.Dual[[]AuxHtlcDescriptor]{ + Local: fn.Map(v.Updates.Local, newAuxHtlcDescriptor), + Remote: fn.Map(v.Updates.Remote, newAuxHtlcDescriptor), + }, + FeePerKw: v.FeePerKw, + } +} + // AuxHtlcDescriptor is a struct that contains the information needed to sign or // verify an HTLC for custom channels. type AuxHtlcDescriptor struct { @@ -99,6 +131,9 @@ func (a *AuxHtlcDescriptor) RemoveHeight( // newAuxHtlcDescriptor creates a new AuxHtlcDescriptor from a payment // descriptor. +// +// NOTE: This function should only be called while holding the channel's read +// lock, since the underlying payment descriptors are accessed directly. func newAuxHtlcDescriptor(p *paymentDescriptor) AuxHtlcDescriptor { return AuxHtlcDescriptor{ ChanID: p.ChanID, From a27905840866e179b18f49961af47f7a5ef770e0 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Mon, 14 Apr 2025 15:32:34 +0200 Subject: [PATCH 2/4] lnwallet: replace HtlcView with AuxHtlcView in existing code --- lnwallet/aux_leaf_store.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lnwallet/aux_leaf_store.go b/lnwallet/aux_leaf_store.go index 28a78e09d..aa2cc3b3e 100644 --- a/lnwallet/aux_leaf_store.go +++ b/lnwallet/aux_leaf_store.go @@ -133,7 +133,7 @@ type CommitDiffAuxInput struct { // UnfilteredView is the unfiltered, original HTLC view of the channel. // Unfiltered in this context means that the view contains all HTLCs, // including the canceled ones. - UnfilteredView *HtlcView + UnfilteredView AuxHtlcView // WhoseCommit denotes whose commitment transaction we are computing the // diff for. @@ -177,9 +177,8 @@ type AuxLeafStore interface { // correspond to the passed aux blob, and an existing channel // commitment. FetchLeavesFromCommit(chanState AuxChanState, - commit channeldb.ChannelCommitment, - keyRing CommitmentKeyRing, whoseCommit lntypes.ChannelParty, - ) fn.Result[CommitDiffAuxResult] + commit channeldb.ChannelCommitment, keyRing CommitmentKeyRing, + whoseCommit lntypes.ChannelParty) fn.Result[CommitDiffAuxResult] // FetchLeavesFromRevocation attempts to fetch the auxiliary leaves // from a channel revocation that stores balance + blob information. @@ -206,7 +205,7 @@ func auxLeavesFromView(leafStore AuxLeafStore, chanState *channeldb.OpenChannel, return leafStore.FetchLeavesFromView(CommitDiffAuxInput{ ChannelState: NewAuxChanState(chanState), PrevBlob: blob, - UnfilteredView: originalView, + UnfilteredView: newAuxHtlcView(originalView), WhoseCommit: whoseCommit, OurBalance: ourBalance, TheirBalance: theirBalance, @@ -227,13 +226,15 @@ func updateAuxBlob(leafStore AuxLeafStore, chanState *channeldb.OpenChannel, return fn.MapOptionZ( prevBlob, func(blob tlv.Blob) fn.Result[fn.Option[tlv.Blob]] { return leafStore.ApplyHtlcView(CommitDiffAuxInput{ - ChannelState: NewAuxChanState(chanState), - PrevBlob: blob, - UnfilteredView: nextViewUnfiltered, - WhoseCommit: whoseCommit, - OurBalance: ourBalance, - TheirBalance: theirBalance, - KeyRing: keyRing, + ChannelState: NewAuxChanState(chanState), + PrevBlob: blob, + UnfilteredView: newAuxHtlcView( + nextViewUnfiltered, + ), + WhoseCommit: whoseCommit, + OurBalance: ourBalance, + TheirBalance: theirBalance, + KeyRing: keyRing, }) }, ) From 53254c79b46a5059c6176facdc2fb1a8fe896a6d Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Tue, 8 Apr 2025 19:52:51 +0200 Subject: [PATCH 3/4] lnwallet: add FetchLatestHTLCView to LightningChannel We add a public method for the lightning channel to expose the latest HtlcView. This is used in a follow up commit by the channel link. --- lnwallet/channel.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lnwallet/channel.go b/lnwallet/channel.go index ab064fbd4..2c1e3a252 100644 --- a/lnwallet/channel.go +++ b/lnwallet/channel.go @@ -2704,6 +2704,20 @@ func (v *HtlcView) AuxTheirUpdates() []AuxHtlcDescriptor { return fn.Map(v.Updates.Remote, newAuxHtlcDescriptor) } +// FetchLatestAuxHTLCView returns the latest HTLC view of the lightning channel +// as a safe copy that can be used outside the wallet code in concurrent access. +func (lc *LightningChannel) FetchLatestAuxHTLCView() AuxHtlcView { + // This read lock is important, because we access both the local and + // remote log indexes as well as the underlying payment descriptors of + // the HTLCs when creating the view. + lc.RLock() + defer lc.RUnlock() + + return newAuxHtlcView(lc.fetchHTLCView( + lc.updateLogs.Remote.logIndex, lc.updateLogs.Local.logIndex, + )) +} + // fetchHTLCView returns all the candidate HTLC updates which should be // considered for inclusion within a commitment based on the passed HTLC log // indexes. From d0ef248ae2ee1249be5459fec9ee2cb0a66aa707 Mon Sep 17 00:00:00 2001 From: George Tsagkarelis Date: Tue, 8 Apr 2025 19:51:29 +0200 Subject: [PATCH 4/4] htlcswitch: add HtlcView as PaymentBandwidth argument In order to get more precise bandwidth reports, we also need to provide this method with the latest htlc view. Since aux data is committed to in the channel commitment, some uncommited HTLCs may not be accounted for, so we need to manually provide them via the HTLC view. --- htlcswitch/interfaces.go | 5 +++-- htlcswitch/link.go | 1 + routing/bandwidth_test.go | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/htlcswitch/interfaces.go b/htlcswitch/interfaces.go index 30a336085..786788e09 100644 --- a/htlcswitch/interfaces.go +++ b/htlcswitch/interfaces.go @@ -10,6 +10,7 @@ import ( "github.com/lightningnetwork/lnd/graph/db/models" "github.com/lightningnetwork/lnd/invoices" "github.com/lightningnetwork/lnd/lntypes" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwallet/chainfee" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/record" @@ -516,8 +517,8 @@ type AuxTrafficShaper interface { // is a custom channel that should be handled by the traffic shaper, the // ShouldHandleTraffic method should be called first. PaymentBandwidth(htlcBlob, commitmentBlob fn.Option[tlv.Blob], - linkBandwidth, - htlcAmt lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) + linkBandwidth, htlcAmt lnwire.MilliSatoshi, + htlcView lnwallet.AuxHtlcView) (lnwire.MilliSatoshi, error) // IsCustomHTLC returns true if the HTLC carries the set of relevant // custom records to put it under the purview of the traffic shaper, diff --git a/htlcswitch/link.go b/htlcswitch/link.go index ec26b0918..bddf8d95e 100644 --- a/htlcswitch/link.go +++ b/htlcswitch/link.go @@ -3505,6 +3505,7 @@ func (l *channelLink) AuxBandwidth(amount lnwire.MilliSatoshi, commitmentBlob := l.CommitmentCustomBlob() auxBandwidth, err := ts.PaymentBandwidth( htlcBlob, commitmentBlob, l.Bandwidth(), amount, + l.channel.FetchLatestAuxHTLCView(), ) if err != nil { return fn.Err[OptionalBandwidth](fmt.Errorf("failed to get "+ diff --git a/routing/bandwidth_test.go b/routing/bandwidth_test.go index 259f347ba..726275a6c 100644 --- a/routing/bandwidth_test.go +++ b/routing/bandwidth_test.go @@ -7,6 +7,7 @@ import ( "github.com/go-errors/errors" "github.com/lightningnetwork/lnd/fn/v2" "github.com/lightningnetwork/lnd/htlcswitch" + "github.com/lightningnetwork/lnd/lnwallet" "github.com/lightningnetwork/lnd/lnwire" "github.com/lightningnetwork/lnd/tlv" "github.com/stretchr/testify/require" @@ -150,7 +151,8 @@ func (*mockTrafficShaper) ShouldHandleTraffic(_ lnwire.ShortChannelID, // is a custom channel that should be handled by the traffic shaper, the // HandleTraffic method should be called first. func (*mockTrafficShaper) PaymentBandwidth(_, _ fn.Option[tlv.Blob], - linkBandwidth, _ lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) { + linkBandwidth, _ lnwire.MilliSatoshi, + _ lnwallet.AuxHtlcView) (lnwire.MilliSatoshi, error) { return linkBandwidth, nil }