• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lightningnetwork / lnd / 16777559213

06 Aug 2025 12:56PM UTC coverage: 57.496%. First build
16777559213

Pull #10134

github

web-flow
Merge eccdd3c96 into a83945649
Pull Request #10134: release: create v0.19.3-rc1 branch

182 of 243 new or added lines in 12 files covered. (74.9%)

96033 of 167025 relevant lines covered (57.5%)

0.61 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.21
/routing/bandwidth.go
1
package routing
2

3
import (
4
        "fmt"
5

6
        "github.com/lightningnetwork/lnd/fn/v2"
7
        graphdb "github.com/lightningnetwork/lnd/graph/db"
8
        "github.com/lightningnetwork/lnd/htlcswitch"
9
        "github.com/lightningnetwork/lnd/lnwire"
10
        "github.com/lightningnetwork/lnd/routing/route"
11
        "github.com/lightningnetwork/lnd/tlv"
12
)
13

14
// bandwidthHints provides hints about the currently available balance in our
15
// channels.
16
type bandwidthHints interface {
17
        // availableChanBandwidth returns the total available bandwidth for a
18
        // channel and a bool indicating whether the channel hint was found.
19
        // The amount parameter is used to validate the outgoing htlc amount
20
        // that we wish to add to the channel against its flow restrictions. If
21
        // a zero amount is provided, the minimum htlc value for the channel
22
        // will be used. If the channel is unavailable, a zero amount is
23
        // returned.
24
        availableChanBandwidth(channelID uint64,
25
                amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool)
26

27
        // isCustomHTLCPayment returns true if this payment is a custom payment.
28
        // For custom payments policy checks might not be needed.
29
        isCustomHTLCPayment() bool
30
}
31

32
// getLinkQuery is the function signature used to lookup a link.
33
type getLinkQuery func(lnwire.ShortChannelID) (
34
        htlcswitch.ChannelLink, error)
35

36
// bandwidthManager is an implementation of the bandwidthHints interface which
37
// uses the link lookup provided to query the link for our latest local channel
38
// balances.
39
type bandwidthManager struct {
40
        getLink       getLinkQuery
41
        localChans    map[lnwire.ShortChannelID]struct{}
42
        firstHopBlob  fn.Option[tlv.Blob]
43
        trafficShaper fn.Option[htlcswitch.AuxTrafficShaper]
44
}
45

46
// newBandwidthManager creates a bandwidth manager for the source node provided
47
// which is used to obtain hints from the lower layer w.r.t the available
48
// bandwidth of edges on the network. Currently, we'll only obtain bandwidth
49
// hints for the edges we directly have open ourselves. Obtaining these hints
50
// allows us to reduce the number of extraneous attempts as we can skip channels
51
// that are inactive, or just don't have enough bandwidth to carry the payment.
52
func newBandwidthManager(graph Graph, sourceNode route.Vertex,
53
        linkQuery getLinkQuery, firstHopBlob fn.Option[tlv.Blob],
54
        ts fn.Option[htlcswitch.AuxTrafficShaper]) (*bandwidthManager,
55
        error) {
1✔
56

1✔
57
        manager := &bandwidthManager{
1✔
58
                getLink:       linkQuery,
1✔
59
                localChans:    make(map[lnwire.ShortChannelID]struct{}),
1✔
60
                firstHopBlob:  firstHopBlob,
1✔
61
                trafficShaper: ts,
1✔
62
        }
1✔
63

1✔
64
        // First, we'll collect the set of outbound edges from the target
1✔
65
        // source node and add them to our bandwidth manager's map of channels.
1✔
66
        err := graph.ForEachNodeDirectedChannel(sourceNode,
1✔
67
                func(channel *graphdb.DirectedChannel) error {
2✔
68
                        shortID := lnwire.NewShortChanIDFromInt(
1✔
69
                                channel.ChannelID,
1✔
70
                        )
1✔
71
                        manager.localChans[shortID] = struct{}{}
1✔
72

1✔
73
                        return nil
1✔
74
                })
1✔
75

76
        if err != nil {
1✔
77
                return nil, err
×
78
        }
×
79

80
        return manager, nil
1✔
81
}
82

83
// getBandwidth queries the current state of a link and gets its currently
84
// available bandwidth. Note that this function assumes that the channel being
85
// queried is one of our local channels, so any failure to retrieve the link
86
// is interpreted as the link being offline.
87
func (b *bandwidthManager) getBandwidth(cid lnwire.ShortChannelID,
88
        amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, error) {
1✔
89

1✔
90
        link, err := b.getLink(cid)
1✔
91
        if err != nil {
2✔
92
                return 0, fmt.Errorf("error querying switch for link: %w", err)
1✔
93
        }
1✔
94

95
        // If the link is found within the switch, but it isn't yet eligible
96
        // to forward any HTLCs, then we'll treat it as if it isn't online in
97
        // the first place.
98
        if !link.EligibleToForward() {
2✔
99
                return 0, fmt.Errorf("link not eligible to forward")
1✔
100
        }
1✔
101

102
        // bandwidthResult is an inline type that we'll use to pass the
103
        // bandwidth result from the external traffic shaper to the main logic
104
        // below.
105
        type bandwidthResult struct {
1✔
106
                // bandwidth is the available bandwidth for the channel as
1✔
107
                // reported by the external traffic shaper. If the external
1✔
108
                // traffic shaper is not handling the channel, this value will
1✔
109
                // be fn.None
1✔
110
                bandwidth fn.Option[lnwire.MilliSatoshi]
1✔
111

1✔
112
                // htlcAmount is the amount we're going to use to check if we
1✔
113
                // can add another HTLC to the channel. If the external traffic
1✔
114
                // shaper is handling the channel, we'll use 0 to just sanity
1✔
115
                // check the number of HTLCs on the channel, since we don't know
1✔
116
                // the actual HTLC amount that will be sent.
1✔
117
                htlcAmount fn.Option[lnwire.MilliSatoshi]
1✔
118
        }
1✔
119

1✔
120
        var (
1✔
121
                // We will pass the link bandwidth to the external traffic
1✔
122
                // shaper. This is the current best estimate for the available
1✔
123
                // bandwidth for the link.
1✔
124
                linkBandwidth = link.Bandwidth()
1✔
125

1✔
126
                bandwidthErr = func(err error) fn.Result[bandwidthResult] {
1✔
127
                        return fn.Err[bandwidthResult](err)
×
128
                }
×
129
        )
130

131
        result, err := fn.MapOptionZ(
1✔
132
                b.trafficShaper,
1✔
133
                func(s htlcswitch.AuxTrafficShaper) fn.Result[bandwidthResult] {
1✔
134
                        auxBandwidth, err := link.AuxBandwidth(
×
135
                                amount, cid, b.firstHopBlob, s,
×
136
                        ).Unpack()
×
137
                        if err != nil {
×
138
                                return bandwidthErr(fmt.Errorf("failed to get "+
×
139
                                        "auxiliary bandwidth: %w", err))
×
140
                        }
×
141

142
                        // If the external traffic shaper is not handling the
143
                        // channel, we'll just return the original bandwidth and
144
                        // no custom amount.
145
                        if !auxBandwidth.IsHandled {
×
146
                                return fn.Ok(bandwidthResult{})
×
147
                        }
×
148

149
                        // We don't know the actual HTLC amount that will be
150
                        // sent using the custom channel. But we'll still want
151
                        // to make sure we can add another HTLC, using the
152
                        // MayAddOutgoingHtlc method below. Passing 0 into that
153
                        // method will use the minimum HTLC value for the
154
                        // channel, which is okay to just check we don't exceed
155
                        // the max number of HTLCs on the channel. A proper
156
                        // balance check is done elsewhere.
157
                        return fn.Ok(bandwidthResult{
×
158
                                bandwidth:  auxBandwidth.Bandwidth,
×
159
                                htlcAmount: fn.Some[lnwire.MilliSatoshi](0),
×
160
                        })
×
161
                },
162
        ).Unpack()
163
        if err != nil {
1✔
164
                return 0, fmt.Errorf("failed to consult external traffic "+
×
165
                        "shaper: %w", err)
×
166
        }
×
167

168
        htlcAmount := result.htlcAmount.UnwrapOr(amount)
1✔
169

1✔
170
        // If our link isn't currently in a state where it can add another
1✔
171
        // outgoing htlc, treat the link as unusable.
1✔
172
        if err := link.MayAddOutgoingHtlc(htlcAmount); err != nil {
2✔
173
                return 0, fmt.Errorf("cannot add outgoing htlc to channel %v "+
1✔
174
                        "with amount %v: %w", cid, htlcAmount, err)
1✔
175
        }
1✔
176

177
        // If the external traffic shaper determined the bandwidth, we'll return
178
        // that value, even if it is zero (which would mean no bandwidth is
179
        // available on that channel).
180
        reportedBandwidth := result.bandwidth.UnwrapOr(linkBandwidth)
1✔
181

1✔
182
        return reportedBandwidth, nil
1✔
183
}
184

185
// availableChanBandwidth returns the total available bandwidth for a channel
186
// and a bool indicating whether the channel hint was found. If the channel is
187
// unavailable, a zero amount is returned.
188
func (b *bandwidthManager) availableChanBandwidth(channelID uint64,
189
        amount lnwire.MilliSatoshi) (lnwire.MilliSatoshi, bool) {
1✔
190

1✔
191
        shortID := lnwire.NewShortChanIDFromInt(channelID)
1✔
192
        _, ok := b.localChans[shortID]
1✔
193
        if !ok {
1✔
194
                return 0, false
×
195
        }
×
196

197
        bandwidth, err := b.getBandwidth(shortID, amount)
1✔
198
        if err != nil {
2✔
199
                log.Warnf("failed to get bandwidth for channel %v: %v",
1✔
200
                        shortID, err)
1✔
201

1✔
202
                return 0, true
1✔
203
        }
1✔
204

205
        return bandwidth, true
1✔
206
}
207

208
// isCustomHTLCPayment returns true if this payment is a custom payment.
209
// For custom payments policy checks might not be needed.
210
func (b *bandwidthManager) isCustomHTLCPayment() bool {
1✔
211
        var isCustomHTLCPayment bool
1✔
212

1✔
213
        b.firstHopBlob.WhenSome(func(blob tlv.Blob) {
2✔
214
                customRecords, err := lnwire.ParseCustomRecords(blob)
1✔
215
                if err != nil {
1✔
NEW
216
                        log.Warnf("failed to parse custom records when "+
×
NEW
217
                                "checking if payment is custom: %v", err)
×
NEW
218

×
NEW
219
                        return
×
NEW
220
                }
×
221

222
                isCustomHTLCPayment = fn.MapOptionZ(
1✔
223
                        b.trafficShaper,
1✔
224
                        func(s htlcswitch.AuxTrafficShaper) bool {
1✔
NEW
225
                                return s.IsCustomHTLC(customRecords)
×
NEW
226
                        },
×
227
                )
228
        })
229

230
        return isCustomHTLCPayment
1✔
231
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc