Skip to content

Commit 5d53431

Browse files
authored
Merge pull request #8665 from GeorgeTsagk/custom-sender-bandwidth-hint
routing: add TlvTrafficShaper to bandwidth hints
2 parents 966f41f + 53e9f28 commit 5d53431

18 files changed

+316
-48
lines changed

config_builder.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ import (
4444
"github.com/lightningnetwork/lnd/lnwallet/btcwallet"
4545
"github.com/lightningnetwork/lnd/lnwallet/rpcwallet"
4646
"github.com/lightningnetwork/lnd/macaroons"
47+
"github.com/lightningnetwork/lnd/routing"
4748
"github.com/lightningnetwork/lnd/rpcperms"
4849
"github.com/lightningnetwork/lnd/signal"
4950
"github.com/lightningnetwork/lnd/sqldb"
@@ -157,6 +158,10 @@ type AuxComponents struct {
157158
// AuxLeafStore is an optional data source that can be used by custom
158159
// channels to fetch+store various data.
159160
AuxLeafStore fn.Option[lnwallet.AuxLeafStore]
161+
162+
// TrafficShaper is an optional traffic shaper that can be used to
163+
// control the outgoing channel of a payment.
164+
TrafficShaper fn.Option[routing.TlvTrafficShaper]
160165
}
161166

162167
// DefaultWalletImpl is the default implementation of our normal, btcwallet

htlcswitch/interfaces.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
1313
"github.com/lightningnetwork/lnd/lnwire"
1414
"github.com/lightningnetwork/lnd/record"
15+
"github.com/lightningnetwork/lnd/tlv"
1516
)
1617

1718
// InvoiceDatabase is an interface which represents the persistent subsystem
@@ -271,6 +272,16 @@ type ChannelLink interface {
271272
// have buffered messages.
272273
AttachMailBox(MailBox)
273274

275+
// FundingCustomBlob returns the custom funding blob of the channel that
276+
// this link is associated with. The funding blob represents static
277+
// information about the channel that was created at channel funding
278+
// time.
279+
FundingCustomBlob() fn.Option[tlv.Blob]
280+
281+
// CommitmentCustomBlob returns the custom blob of the current local
282+
// commitment of the channel that this link is associated with.
283+
CommitmentCustomBlob() fn.Option[tlv.Blob]
284+
274285
// Start/Stop are used to initiate the start/stop of the channel link
275286
// functioning.
276287
Start() error

htlcswitch/link.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3775,3 +3775,16 @@ func (l *channelLink) fail(linkErr LinkFailureError,
37753775
l.failed = true
37763776
l.cfg.OnChannelFailure(l.ChanID(), l.ShortChanID(), linkErr)
37773777
}
3778+
3779+
// FundingCustomBlob returns the custom funding blob of the channel that this
3780+
// link is associated with. The funding blob represents static information about
3781+
// the channel that was created at channel funding time.
3782+
func (l *channelLink) FundingCustomBlob() fn.Option[tlv.Blob] {
3783+
return l.channel.State().CustomBlob
3784+
}
3785+
3786+
// CommitmentCustomBlob returns the custom blob of the current local commitment
3787+
// of the channel that this link is associated with.
3788+
func (l *channelLink) CommitmentCustomBlob() fn.Option[tlv.Blob] {
3789+
return l.channel.LocalCommitmentBlob()
3790+
}

htlcswitch/mock.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/lightningnetwork/lnd/channeldb/models"
2828
"github.com/lightningnetwork/lnd/clock"
2929
"github.com/lightningnetwork/lnd/contractcourt"
30+
"github.com/lightningnetwork/lnd/fn"
3031
"github.com/lightningnetwork/lnd/htlcswitch/hop"
3132
"github.com/lightningnetwork/lnd/invoices"
3233
"github.com/lightningnetwork/lnd/lnpeer"
@@ -35,6 +36,7 @@ import (
3536
"github.com/lightningnetwork/lnd/lnwallet/chainfee"
3637
"github.com/lightningnetwork/lnd/lnwire"
3738
"github.com/lightningnetwork/lnd/ticker"
39+
"github.com/lightningnetwork/lnd/tlv"
3840
)
3941

4042
func isAlias(scid lnwire.ShortChannelID) bool {
@@ -912,6 +914,10 @@ func (f *mockChannelLink) ChannelPoint() wire.OutPoint {
912914
return wire.OutPoint{}
913915
}
914916

917+
func (f *mockChannelLink) ChannelCustomBlob() fn.Option[tlv.Blob] {
918+
return fn.Option[tlv.Blob]{}
919+
}
920+
915921
func (f *mockChannelLink) Stop() {}
916922
func (f *mockChannelLink) EligibleToForward() bool { return f.eligible }
917923
func (f *mockChannelLink) MayAddOutgoingHtlc(lnwire.MilliSatoshi) error { return nil }
@@ -942,6 +948,14 @@ func (f *mockChannelLink) OnCommitOnce(LinkDirection, func()) {
942948
// TODO(proofofkeags): Implement
943949
}
944950

951+
func (f *mockChannelLink) FundingCustomBlob() fn.Option[tlv.Blob] {
952+
return fn.None[tlv.Blob]()
953+
}
954+
955+
func (f *mockChannelLink) CommitmentCustomBlob() fn.Option[tlv.Blob] {
956+
return fn.None[tlv.Blob]()
957+
}
958+
945959
var _ ChannelLink = (*mockChannelLink)(nil)
946960

947961
func newDB() (*channeldb.DB, func(), error) {

lnwallet/channel.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9589,3 +9589,19 @@ func (lc *LightningChannel) MultiSigKeys() (keychain.KeyDescriptor,
95899589
return lc.channelState.LocalChanCfg.MultiSigKey,
95909590
lc.channelState.RemoteChanCfg.MultiSigKey
95919591
}
9592+
9593+
// LocalCommitmentBlob returns the custom blob of the local commitment.
9594+
func (lc *LightningChannel) LocalCommitmentBlob() fn.Option[tlv.Blob] {
9595+
lc.RLock()
9596+
defer lc.RUnlock()
9597+
9598+
chanState := lc.channelState
9599+
localBalance := chanState.LocalCommitment.CustomBlob
9600+
9601+
return fn.MapOption(func(b tlv.Blob) tlv.Blob {
9602+
newBlob := make([]byte, len(b))
9603+
copy(newBlob, b)
9604+
9605+
return newBlob
9606+
})(localBalance)
9607+
}

routing/bandwidth.go

Lines changed: 109 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
package routing
22

33
import (
4+
"fmt"
5+
6+
"github.com/btcsuite/btcd/btcutil"
47
"github.com/lightningnetwork/lnd/channeldb"
8+
"github.com/lightningnetwork/lnd/fn"
59
"github.com/lightningnetwork/lnd/htlcswitch"
610
"github.com/lightningnetwork/lnd/lnwire"
711
"github.com/lightningnetwork/lnd/routing/route"
12+
"github.com/lightningnetwork/lnd/tlv"
813
)
914

1015
// bandwidthHints provides hints about the currently available balance in our
@@ -18,7 +23,39 @@ type bandwidthHints interface {
1823
// will be used. If the channel is unavailable, a zero amount is
1924
// returned.
2025
availableChanBandwidth(channelID uint64,
21-
amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool)
26+
amount lnwire.MilliSatoshi,
27+
htlcBlob fn.Option[tlv.Blob]) (lnwire.MilliSatoshi, bool)
28+
}
29+
30+
// TlvTrafficShaper is an interface that allows the sender to determine if a
31+
// payment should be carried by a channel based on the TLV records that may be
32+
// present in the `update_add_htlc` message or the channel commitment itself.
33+
type TlvTrafficShaper interface {
34+
AuxHtlcModifier
35+
36+
// HandleTraffic is called in order to check if the channel identified
37+
// by the provided channel ID may have external mechanisms that would
38+
// allow it to carry out the payment.
39+
HandleTraffic(cid lnwire.ShortChannelID,
40+
fundingBlob fn.Option[tlv.Blob]) (bool, error)
41+
42+
// PaymentBandwidth returns the available bandwidth for a custom channel
43+
// decided by the given channel aux blob and HTLC blob. A return value
44+
// of 0 means there is no bandwidth available. To find out if a channel
45+
// is a custom channel that should be handled by the traffic shaper, the
46+
// HandleTraffic method should be called first.
47+
PaymentBandwidth(htlcBlob,
48+
commitmentBlob fn.Option[tlv.Blob]) (lnwire.MilliSatoshi, error)
49+
}
50+
51+
// AuxHtlcModifier is an interface that allows the sender to modify the outgoing
52+
// HTLC of a payment by changing the amount or the wire message tlv records.
53+
type AuxHtlcModifier interface {
54+
// ProduceHtlcExtraData is a function that, based on the previous extra
55+
// data blob of an HTLC, may produce a different blob or modify the
56+
// amount of bitcoin this htlc should carry.
57+
ProduceHtlcExtraData(totalAmount lnwire.MilliSatoshi,
58+
htlcBlob tlv.Blob) (btcutil.Amount, tlv.Blob, error)
2259
}
2360

2461
// getLinkQuery is the function signature used to lookup a link.
@@ -29,8 +66,9 @@ type getLinkQuery func(lnwire.ShortChannelID) (
2966
// uses the link lookup provided to query the link for our latest local channel
3067
// balances.
3168
type bandwidthManager struct {
32-
getLink getLinkQuery
33-
localChans map[lnwire.ShortChannelID]struct{}
69+
getLink getLinkQuery
70+
localChans map[lnwire.ShortChannelID]struct{}
71+
trafficShaper fn.Option[TlvTrafficShaper]
3472
}
3573

3674
// newBandwidthManager creates a bandwidth manager for the source node provided
@@ -40,11 +78,13 @@ type bandwidthManager struct {
4078
// allows us to reduce the number of extraneous attempts as we can skip channels
4179
// that are inactive, or just don't have enough bandwidth to carry the payment.
4280
func newBandwidthManager(graph routingGraph, sourceNode route.Vertex,
43-
linkQuery getLinkQuery) (*bandwidthManager, error) {
81+
linkQuery getLinkQuery,
82+
trafficShaper fn.Option[TlvTrafficShaper]) (*bandwidthManager, error) {
4483

4584
manager := &bandwidthManager{
46-
getLink: linkQuery,
47-
localChans: make(map[lnwire.ShortChannelID]struct{}),
85+
getLink: linkQuery,
86+
localChans: make(map[lnwire.ShortChannelID]struct{}),
87+
trafficShaper: trafficShaper,
4888
}
4989

5090
// First, we'll collect the set of outbound edges from the target
@@ -71,7 +111,8 @@ func newBandwidthManager(graph routingGraph, sourceNode route.Vertex,
71111
// queried is one of our local channels, so any failure to retrieve the link
72112
// is interpreted as the link being offline.
73113
func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID,
74-
amount lnwire.MilliSatoshi) lnwire.MilliSatoshi {
114+
amount lnwire.MilliSatoshi,
115+
htlcBlob fn.Option[tlv.Blob]) lnwire.MilliSatoshi {
75116

76117
link, err := b.getLink(cid)
77118
if err != nil {
@@ -89,30 +130,83 @@ func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID,
89130
return 0
90131
}
91132

92-
// If our link isn't currently in a state where it can add another
93-
// outgoing htlc, treat the link as unusable.
133+
var (
134+
auxBandwidth lnwire.MilliSatoshi
135+
auxBandwidthDetermined bool
136+
)
137+
err = fn.MapOptionZ(b.trafficShaper, func(ts TlvTrafficShaper) error {
138+
fundingBlob := link.FundingCustomBlob()
139+
shouldHandle, err := ts.HandleTraffic(cid, fundingBlob)
140+
if err != nil {
141+
return fmt.Errorf("traffic shaper failed to decide "+
142+
"whether to handle traffic: %w", err)
143+
}
144+
145+
log.Debugf("ShortChannelID=%v: external traffic shaper is "+
146+
"handling traffic: %v", cid, shouldHandle)
147+
148+
// If this channel isn't handled by the external traffic shaper,
149+
// we'll return early.
150+
if !shouldHandle {
151+
return nil
152+
}
153+
154+
// Ask for a specific bandwidth to be used for the channel.
155+
commitmentBlob := link.CommitmentCustomBlob()
156+
auxBandwidth, err = ts.PaymentBandwidth(
157+
htlcBlob, commitmentBlob,
158+
)
159+
if err != nil {
160+
return fmt.Errorf("failed to get bandwidth from "+
161+
"external traffic shaper: %w", err)
162+
}
163+
164+
log.Debugf("ShortChannelID=%v: external traffic shaper "+
165+
"reported available bandwidth: %v", cid, auxBandwidth)
166+
167+
auxBandwidthDetermined = true
168+
169+
return nil
170+
})
171+
if err != nil {
172+
log.Errorf("ShortChannelID=%v: failed to get bandwidth from "+
173+
"external traffic shaper: %v", cid, err)
174+
175+
return 0
176+
}
177+
178+
// If our link isn't currently in a state where it can add
179+
// another outgoing htlc, treat the link as unusable.
94180
if err := link.MayAddOutgoingHtlc(amount); err != nil {
95-
log.Warnf("ShortChannelID=%v: cannot add outgoing htlc: %v",
96-
cid, err)
181+
log.Warnf("ShortChannelID=%v: cannot add outgoing "+
182+
"htlc: %v", cid, err)
97183
return 0
98184
}
99185

100-
// Otherwise, we'll return the current best estimate for the available
101-
// bandwidth for the link.
186+
// If the external traffic shaper determined the bandwidth, we'll return
187+
// that value, even if it is zero (which would mean no bandwidth is
188+
// available on that channel).
189+
if auxBandwidthDetermined {
190+
return auxBandwidth
191+
}
192+
193+
// Otherwise, we'll return the current best estimate for the
194+
// available bandwidth for the link.
102195
return link.Bandwidth()
103196
}
104197

105198
// availableChanBandwidth returns the total available bandwidth for a channel
106199
// and a bool indicating whether the channel hint was found. If the channel is
107200
// unavailable, a zero amount is returned.
108201
func (b *bandwidthManager) availableChanBandwidth(channelID uint64,
109-
amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
202+
amount lnwire.MilliSatoshi,
203+
htlcBlob fn.Option[tlv.Blob]) (lnwire.MilliSatoshi, bool) {
110204

111205
shortID := lnwire.NewShortChanIDFromInt(channelID)
112206
_, ok := b.localChans[shortID]
113207
if !ok {
114208
return 0, false
115209
}
116210

117-
return b.getBandwidth(shortID, amount), true
211+
return b.getBandwidth(shortID, amount, htlcBlob), true
118212
}

routing/bandwidth_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55

66
"github.com/btcsuite/btcd/btcutil"
77
"github.com/go-errors/errors"
8+
"github.com/lightningnetwork/lnd/fn"
89
"github.com/lightningnetwork/lnd/htlcswitch"
910
"github.com/lightningnetwork/lnd/lnwire"
1011
"github.com/stretchr/testify/require"
@@ -115,11 +116,13 @@ func TestBandwidthManager(t *testing.T) {
115116

116117
m, err := newBandwidthManager(
117118
g, sourceNode.pubkey, testCase.linkQuery,
119+
fn.None[TlvTrafficShaper](),
118120
)
119121
require.NoError(t, err)
120122

121123
bandwidth, found := m.availableChanBandwidth(
122124
testCase.channelID, 10,
125+
fn.None[[]byte](),
123126
)
124127
require.Equal(t, testCase.expectedBandwidth, bandwidth)
125128
require.Equal(t, testCase.expectFound, found)

routing/integrated_routing_context_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88
"time"
99

10+
"github.com/lightningnetwork/lnd/fn"
1011
"github.com/lightningnetwork/lnd/kvdb"
1112
"github.com/lightningnetwork/lnd/lnwire"
1213
"github.com/lightningnetwork/lnd/routing/route"
@@ -24,7 +25,8 @@ type mockBandwidthHints struct {
2425
}
2526

2627
func (m *mockBandwidthHints) availableChanBandwidth(channelID uint64,
27-
_ lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
28+
_ lnwire.MilliSatoshi,
29+
htlcBlob fn.Option[[]byte]) (lnwire.MilliSatoshi, bool) {
2830

2931
if m.hints == nil {
3032
return 0, false
@@ -229,6 +231,7 @@ func (c *integratedRoutingContext) testPayment(maxParts uint32,
229231
// Find a route.
230232
route, err := session.RequestRoute(
231233
amtRemaining, lnwire.MaxMilliSatoshi, inFlightHtlcs, 0,
234+
nil,
232235
)
233236
if err != nil {
234237
return attempts, err

0 commit comments

Comments
 (0)