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

lightningnetwork / lnd / 13566028875

27 Feb 2025 12:09PM UTC coverage: 49.396% (-9.4%) from 58.748%
13566028875

Pull #9555

github

ellemouton
graph/db: populate the graph cache in Start instead of during construction

In this commit, we move the graph cache population logic out of the
ChannelGraph constructor and into its Start method instead.
Pull Request #9555: graph: extract cache from CRUD [6]

34 of 54 new or added lines in 4 files covered. (62.96%)

27464 existing lines in 436 files now uncovered.

101095 of 204664 relevant lines covered (49.4%)

1.54 hits per line

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

78.85
/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
        // firstHopCustomBlob returns the custom blob for the first hop of the
28
        // payment, if available.
29
        firstHopCustomBlob() fn.Option[tlv.Blob]
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) {
3✔
56

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

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

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

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

80
        return manager, nil
3✔
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 {
3✔
89

3✔
90
        link, err := b.getLink(cid)
3✔
91
        if err != nil {
6✔
92
                // If the link isn't online, then we'll report that it has
3✔
93
                // zero bandwidth.
3✔
94
                log.Warnf("ShortChannelID=%v: link not found: %v", cid, err)
3✔
95
                return 0
3✔
96
        }
3✔
97

98
        // If the link is found within the switch, but it isn't yet eligible
99
        // to forward any HTLCs, then we'll treat it as if it isn't online in
100
        // the first place.
101
        if !link.EligibleToForward() {
6✔
102
                log.Warnf("ShortChannelID=%v: not eligible to forward", cid)
3✔
103
                return 0
3✔
104
        }
3✔
105

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

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

3✔
124
        var (
3✔
125
                // We will pass the link bandwidth to the external traffic
3✔
126
                // shaper. This is the current best estimate for the available
3✔
127
                // bandwidth for the link.
3✔
128
                linkBandwidth = link.Bandwidth()
3✔
129

3✔
130
                bandwidthErr = func(err error) fn.Result[bandwidthResult] {
3✔
131
                        return fn.Err[bandwidthResult](err)
×
132
                }
×
133
        )
134

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

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

×
164
                return 0
×
165
        }
×
166

167
        htlcAmount := result.htlcAmount.UnwrapOr(amount)
3✔
168

3✔
169
        // If our link isn't currently in a state where it can add another
3✔
170
        // outgoing htlc, treat the link as unusable.
3✔
171
        if err := link.MayAddOutgoingHtlc(htlcAmount); err != nil {
6✔
172
                log.Warnf("ShortChannelID=%v: cannot add outgoing "+
3✔
173
                        "htlc with amount %v: %v", cid, htlcAmount, err)
3✔
174
                return 0
3✔
175
        }
3✔
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)
3✔
181

3✔
182
        return reportedBandwidth
3✔
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) {
3✔
190

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

197
        return b.getBandwidth(shortID, amount), true
3✔
198
}
199

200
// firstHopCustomBlob returns the custom blob for the first hop of the payment,
201
// if available.
202
func (b *bandwidthManager) firstHopCustomBlob() fn.Option[tlv.Blob] {
3✔
203
        return b.firstHopBlob
3✔
204
}
3✔
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