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/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, }) }, ) 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, 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. 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 }