Skip to content

Commit

Permalink
Merge pull request #9333 from guggero/aux-traffic-shaper-refactor
Browse files Browse the repository at this point in the history
[custom channels]: refactor AuxTrafficManager to be used for forwarding as well
  • Loading branch information
Roasbeef authored Dec 5, 2024
2 parents 8d7f085 + 86b3be7 commit 366e48b
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 148 deletions.
4 changes: 2 additions & 2 deletions config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import (
"github.com/lightningnetwork/lnd/fn"
"github.com/lightningnetwork/lnd/funding"
graphdb "github.com/lightningnetwork/lnd/graph/db"
"github.com/lightningnetwork/lnd/htlcswitch"
"github.com/lightningnetwork/lnd/invoices"
"github.com/lightningnetwork/lnd/keychain"
"github.com/lightningnetwork/lnd/kvdb"
Expand All @@ -47,7 +48,6 @@ import (
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/lightningnetwork/lnd/msgmux"
"github.com/lightningnetwork/lnd/routing"
"github.com/lightningnetwork/lnd/rpcperms"
"github.com/lightningnetwork/lnd/signal"
"github.com/lightningnetwork/lnd/sqldb"
Expand Down Expand Up @@ -166,7 +166,7 @@ type AuxComponents struct {

// TrafficShaper is an optional traffic shaper that can be used to
// control the outgoing channel of a payment.
TrafficShaper fn.Option[routing.TlvTrafficShaper]
TrafficShaper fn.Option[htlcswitch.AuxTrafficShaper]

// MsgRouter is an optional message router that if set will be used in
// place of a new blank default message router.
Expand Down
68 changes: 58 additions & 10 deletions htlcswitch/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,11 @@ const (
Outgoing LinkDirection = true
)

// OptionalBandwidth is a type alias for the result of a bandwidth query that
// may return a bandwidth value or fn.None if the bandwidth is not available or
// not applicable.
type OptionalBandwidth = fn.Option[lnwire.MilliSatoshi]

// ChannelLink is an interface which represents the subsystem for managing the
// incoming htlc requests, applying the changes to the channel, and also
// propagating/forwarding it to htlc switch.
Expand Down Expand Up @@ -267,25 +272,26 @@ type ChannelLink interface {
// in order to signal to the source of the HTLC, the policy consistency
// issue.
CheckHtlcForward(payHash [32]byte, incomingAmt lnwire.MilliSatoshi,
amtToForward lnwire.MilliSatoshi,
incomingTimeout, outgoingTimeout uint32,
inboundFee models.InboundFee,
heightNow uint32, scid lnwire.ShortChannelID) *LinkError
amtToForward lnwire.MilliSatoshi, incomingTimeout,
outgoingTimeout uint32, inboundFee models.InboundFee,
heightNow uint32, scid lnwire.ShortChannelID,
customRecords lnwire.CustomRecords) *LinkError

// CheckHtlcTransit should return a nil error if the passed HTLC details
// satisfy the current channel policy. Otherwise, a LinkError with a
// valid protocol failure message should be returned in order to signal
// the violation. This call is intended to be used for locally initiated
// payments for which there is no corresponding incoming htlc.
CheckHtlcTransit(payHash [32]byte, amt lnwire.MilliSatoshi,
timeout uint32, heightNow uint32) *LinkError
timeout uint32, heightNow uint32,
customRecords lnwire.CustomRecords) *LinkError

// Stats return the statistics of channel link. Number of updates,
// total sent/received milli-satoshis.
Stats() (uint64, lnwire.MilliSatoshi, lnwire.MilliSatoshi)

// Peer returns the serialized public key of remote peer with which we
// have the channel link opened.
// PeerPubKey returns the serialized public key of remote peer with
// which we have the channel link opened.
PeerPubKey() [33]byte

// AttachMailBox delivers an active MailBox to the link. The MailBox may
Expand All @@ -302,9 +308,18 @@ type ChannelLink interface {
// commitment of the channel that this link is associated with.
CommitmentCustomBlob() fn.Option[tlv.Blob]

// Start/Stop are used to initiate the start/stop of the channel link
// functioning.
// AuxBandwidth returns the bandwidth that can be used for a channel,
// expressed in milli-satoshi. This might be different from the regular
// BTC bandwidth for custom channels. This will always return fn.None()
// for a regular (non-custom) channel.
AuxBandwidth(amount lnwire.MilliSatoshi, cid lnwire.ShortChannelID,
htlcBlob fn.Option[tlv.Blob],
ts AuxTrafficShaper) fn.Result[OptionalBandwidth]

// Start starts the channel link.
Start() error

// Stop requests the channel link to be shut down.
Stop()
}

Expand Down Expand Up @@ -440,7 +455,7 @@ type htlcNotifier interface {
NotifyForwardingEvent(key HtlcKey, info HtlcInfo,
eventType HtlcEventType)

// NotifyIncomingLinkFailEvent notifies that a htlc has failed on our
// NotifyLinkFailEvent notifies that a htlc has failed on our
// incoming link. It takes an isReceive bool to differentiate between
// our node's receives and forwards.
NotifyLinkFailEvent(key HtlcKey, info HtlcInfo,
Expand All @@ -461,3 +476,36 @@ type htlcNotifier interface {
NotifyFinalHtlcEvent(key models.CircuitKey,
info channeldb.FinalHtlcInfo)
}

// AuxHtlcModifier is an interface that allows the sender to modify the outgoing
// HTLC of a payment by changing the amount or the wire message tlv records.
type AuxHtlcModifier interface {
// ProduceHtlcExtraData is a function that, based on the previous extra
// data blob of an HTLC, may produce a different blob or modify the
// amount of bitcoin this htlc should carry.
ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi,
htlcCustomRecords lnwire.CustomRecords) (lnwire.MilliSatoshi,
lnwire.CustomRecords, error)
}

// AuxTrafficShaper is an interface that allows the sender to determine if a
// payment should be carried by a channel based on the TLV records that may be
// present in the `update_add_htlc` message or the channel commitment itself.
type AuxTrafficShaper interface {
AuxHtlcModifier

// ShouldHandleTraffic is called in order to check if the channel
// identified by the provided channel ID may have external mechanisms
// that would allow it to carry out the payment.
ShouldHandleTraffic(cid lnwire.ShortChannelID,
fundingBlob fn.Option[tlv.Blob]) (bool, error)

// PaymentBandwidth returns the available bandwidth for a custom channel
// decided by the given channel aux blob and HTLC blob. A return value
// of 0 means there is no bandwidth available. To find out if a channel
// 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)
}
98 changes: 88 additions & 10 deletions htlcswitch/link.go
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,10 @@ type ChannelLinkConfig struct {
// ShouldFwdExpEndorsement is a closure that indicates whether the link
// should forward experimental endorsement signals.
ShouldFwdExpEndorsement func() bool

// AuxTrafficShaper is an optional auxiliary traffic shaper that can be
// used to manage the bandwidth of the link.
AuxTrafficShaper fn.Option[AuxTrafficShaper]
}

// channelLink is the service which drives a channel's commitment update
Expand Down Expand Up @@ -3233,11 +3237,11 @@ func (l *channelLink) UpdateForwardingPolicy(
// issue.
//
// NOTE: Part of the ChannelLink interface.
func (l *channelLink) CheckHtlcForward(payHash [32]byte,
incomingHtlcAmt, amtToForward lnwire.MilliSatoshi,
incomingTimeout, outgoingTimeout uint32,
inboundFee models.InboundFee,
heightNow uint32, originalScid lnwire.ShortChannelID) *LinkError {
func (l *channelLink) CheckHtlcForward(payHash [32]byte, incomingHtlcAmt,
amtToForward lnwire.MilliSatoshi, incomingTimeout,
outgoingTimeout uint32, inboundFee models.InboundFee,
heightNow uint32, originalScid lnwire.ShortChannelID,
customRecords lnwire.CustomRecords) *LinkError {

l.RLock()
policy := l.cfg.FwrdingPolicy
Expand Down Expand Up @@ -3286,7 +3290,7 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
// Check whether the outgoing htlc satisfies the channel policy.
err := l.canSendHtlc(
policy, payHash, amtToForward, outgoingTimeout, heightNow,
originalScid,
originalScid, customRecords,
)
if err != nil {
return err
Expand Down Expand Up @@ -3322,8 +3326,8 @@ func (l *channelLink) CheckHtlcForward(payHash [32]byte,
// the violation. This call is intended to be used for locally initiated
// payments for which there is no corresponding incoming htlc.
func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32) *LinkError {
amt lnwire.MilliSatoshi, timeout uint32, heightNow uint32,
customRecords lnwire.CustomRecords) *LinkError {

l.RLock()
policy := l.cfg.FwrdingPolicy
Expand All @@ -3334,14 +3338,16 @@ func (l *channelLink) CheckHtlcTransit(payHash [32]byte,
// to occur.
return l.canSendHtlc(
policy, payHash, amt, timeout, heightNow, hop.Source,
customRecords,
)
}

// canSendHtlc checks whether the given htlc parameters satisfy
// the channel's amount and time lock constraints.
func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy,
payHash [32]byte, amt lnwire.MilliSatoshi, timeout uint32,
heightNow uint32, originalScid lnwire.ShortChannelID) *LinkError {
heightNow uint32, originalScid lnwire.ShortChannelID,
customRecords lnwire.CustomRecords) *LinkError {

// As our first sanity check, we'll ensure that the passed HTLC isn't
// too small for the next hop. If so, then we'll cancel the HTLC
Expand Down Expand Up @@ -3399,8 +3405,38 @@ func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy,
return NewLinkError(&lnwire.FailExpiryTooFar{})
}

// We now check the available bandwidth to see if this HTLC can be
// forwarded.
availableBandwidth := l.Bandwidth()
auxBandwidth, err := fn.MapOptionZ(
l.cfg.AuxTrafficShaper,
func(ts AuxTrafficShaper) fn.Result[OptionalBandwidth] {
var htlcBlob fn.Option[tlv.Blob]
blob, err := customRecords.Serialize()
if err != nil {
return fn.Err[OptionalBandwidth](
fmt.Errorf("unable to serialize "+
"custom records: %w", err))
}

if len(blob) > 0 {
htlcBlob = fn.Some(blob)
}

return l.AuxBandwidth(amt, originalScid, htlcBlob, ts)
},
).Unpack()
if err != nil {
l.log.Errorf("Unable to determine aux bandwidth: %v", err)
return NewLinkError(&lnwire.FailTemporaryNodeFailure{})
}

auxBandwidth.WhenSome(func(bandwidth lnwire.MilliSatoshi) {
availableBandwidth = bandwidth
})

// Check to see if there is enough balance in this channel.
if amt > l.Bandwidth() {
if amt > availableBandwidth {
l.log.Warnf("insufficient bandwidth to route htlc: %v is "+
"larger than %v", amt, l.Bandwidth())
cb := func(upd *lnwire.ChannelUpdate1) lnwire.FailureMessage {
Expand All @@ -3415,6 +3451,48 @@ func (l *channelLink) canSendHtlc(policy models.ForwardingPolicy,
return nil
}

// AuxBandwidth returns the bandwidth that can be used for a channel, expressed
// in milli-satoshi. This might be different from the regular BTC bandwidth for
// custom channels. This will always return fn.None() for a regular (non-custom)
// channel.
func (l *channelLink) AuxBandwidth(amount lnwire.MilliSatoshi,
cid lnwire.ShortChannelID, htlcBlob fn.Option[tlv.Blob],
ts AuxTrafficShaper) fn.Result[OptionalBandwidth] {

unknownBandwidth := fn.None[lnwire.MilliSatoshi]()

fundingBlob := l.FundingCustomBlob()
shouldHandle, err := ts.ShouldHandleTraffic(cid, fundingBlob)
if err != nil {
return fn.Err[OptionalBandwidth](fmt.Errorf("traffic shaper "+
"failed to decide whether to handle traffic: %w", err))
}

log.Debugf("ShortChannelID=%v: aux traffic shaper is handling "+
"traffic: %v", cid, shouldHandle)

// If this channel isn't handled by the aux traffic shaper, we'll return
// early.
if !shouldHandle {
return fn.Ok(unknownBandwidth)
}

// Ask for a specific bandwidth to be used for the channel.
commitmentBlob := l.CommitmentCustomBlob()
auxBandwidth, err := ts.PaymentBandwidth(
htlcBlob, commitmentBlob, l.Bandwidth(), amount,
)
if err != nil {
return fn.Err[OptionalBandwidth](fmt.Errorf("failed to get "+
"bandwidth from external traffic shaper: %w", err))
}

log.Debugf("ShortChannelID=%v: aux traffic shaper reported available "+
"bandwidth: %v", cid, auxBandwidth)

return fn.Ok(fn.Some(auxBandwidth))
}

// Stats returns the statistics of channel link.
//
// NOTE: Part of the ChannelLink interface.
Expand Down
Loading

0 comments on commit 366e48b

Please sign in to comment.