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

lightningnetwork / lnd / 13302325565

13 Feb 2025 07:09AM UTC coverage: 58.859% (+1.2%) from 57.696%
13302325565

Pull #9448

github

yyforyongyu
sweep: fix error logging
Pull Request #9448: sweep: properly handle failed sweeping txns

274 of 305 new or added lines in 4 files covered. (89.84%)

20 existing lines in 7 files now uncovered.

136446 of 231819 relevant lines covered (58.86%)

19230.81 hits per line

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

93.82
/routing/unified_edges.go
1
package routing
2

3
import (
4
        "math"
5

6
        "github.com/btcsuite/btcd/btcutil"
7
        graphdb "github.com/lightningnetwork/lnd/graph/db"
8
        "github.com/lightningnetwork/lnd/graph/db/models"
9
        "github.com/lightningnetwork/lnd/lntypes"
10
        "github.com/lightningnetwork/lnd/lnwire"
11
        "github.com/lightningnetwork/lnd/routing/route"
12
)
13

14
// nodeEdgeUnifier holds all edge unifiers for connections towards a node.
15
type nodeEdgeUnifier struct {
16
        // edgeUnifiers contains an edge unifier for every from node.
17
        edgeUnifiers map[route.Vertex]*edgeUnifier
18

19
        // sourceNode is the sender of a payment. The rules to pick the final
20
        // policy are different for local channels.
21
        sourceNode route.Vertex
22

23
        // toNode is the node for which the edge unifiers are instantiated.
24
        toNode route.Vertex
25

26
        // useInboundFees indicates whether to take inbound fees into account.
27
        useInboundFees bool
28

29
        // outChanRestr is an optional outgoing channel restriction for the
30
        // local channel to use.
31
        outChanRestr map[uint64]struct{}
32
}
33

34
// newNodeEdgeUnifier instantiates a new nodeEdgeUnifier object. Channel
35
// policies can be added to this object.
36
func newNodeEdgeUnifier(sourceNode, toNode route.Vertex, useInboundFees bool,
37
        outChanRestr map[uint64]struct{}) *nodeEdgeUnifier {
717✔
38

717✔
39
        return &nodeEdgeUnifier{
717✔
40
                edgeUnifiers:   make(map[route.Vertex]*edgeUnifier),
717✔
41
                toNode:         toNode,
717✔
42
                useInboundFees: useInboundFees,
717✔
43
                sourceNode:     sourceNode,
717✔
44
                outChanRestr:   outChanRestr,
717✔
45
        }
717✔
46
}
717✔
47

48
// addPolicy adds a single channel policy. Capacity may be zero if unknown
49
// (light clients). We expect a non-nil payload size function and will request a
50
// graceful shutdown if it is not provided as this indicates that edges are
51
// incorrectly specified.
52
func (u *nodeEdgeUnifier) addPolicy(fromNode route.Vertex,
53
        edge *models.CachedEdgePolicy, inboundFee models.InboundFee,
54
        capacity btcutil.Amount, hopPayloadSizeFn PayloadSizeFunc,
55
        blindedPayment *BlindedPayment) {
1,677✔
56

1,677✔
57
        localChan := fromNode == u.sourceNode
1,677✔
58

1,677✔
59
        // Skip channels if there is an outgoing channel restriction.
1,677✔
60
        if localChan && u.outChanRestr != nil {
1,691✔
61
                if _, ok := u.outChanRestr[edge.ChannelID]; !ok {
22✔
62
                        log.Debugf("Skipped adding policy for restricted edge "+
8✔
63
                                "%v", edge.ChannelID)
8✔
64

8✔
65
                        return
8✔
66
                }
8✔
67
        }
68

69
        // Update the edgeUnifiers map.
70
        unifier, ok := u.edgeUnifiers[fromNode]
1,669✔
71
        if !ok {
3,321✔
72
                unifier = &edgeUnifier{
1,652✔
73
                        localChan: localChan,
1,652✔
74
                }
1,652✔
75
                u.edgeUnifiers[fromNode] = unifier
1,652✔
76
        }
1,652✔
77

78
        // In case no payload size function was provided a graceful shutdown
79
        // is requested, because this function is not used as intended.
80
        if hopPayloadSizeFn == nil {
1,669✔
81
                log.Criticalf("No payloadsize function was provided for the "+
×
82
                        "edge (chanid=%v) when adding it to the edge unifier "+
×
83
                        "of node: %v", edge.ChannelID, fromNode)
×
84

×
85
                return
×
86
        }
×
87

88
        // Zero inbound fee for exit hops.
89
        if !u.useInboundFees {
2,167✔
90
                inboundFee = models.InboundFee{}
498✔
91
        }
498✔
92

93
        unifier.edges = append(unifier.edges, newUnifiedEdge(
1,669✔
94
                edge, capacity, inboundFee, hopPayloadSizeFn, blindedPayment,
1,669✔
95
        ))
1,669✔
96
}
97

98
// addGraphPolicies adds all policies that are known for the toNode in the
99
// graph.
100
func (u *nodeEdgeUnifier) addGraphPolicies(g Graph) error {
711✔
101
        cb := func(channel *graphdb.DirectedChannel) error {
2,362✔
102
                // If there is no edge policy for this candidate node, skip.
1,651✔
103
                // Note that we are searching backwards so this node would have
1,651✔
104
                // come prior to the pivot node in the route.
1,651✔
105
                if channel.InPolicy == nil {
1,651✔
106
                        log.Debugf("Skipped adding edge %v due to nil policy",
×
107
                                channel.ChannelID)
×
108

×
109
                        return nil
×
110
                }
×
111

112
                // Add this policy to the corresponding edgeUnifier. We default
113
                // to the clear hop payload size function because
114
                // `addGraphPolicies` is only used for cleartext intermediate
115
                // hops in a route.
116
                inboundFee := models.NewInboundFeeFromWire(
1,651✔
117
                        channel.InboundFee,
1,651✔
118
                )
1,651✔
119

1,651✔
120
                u.addPolicy(
1,651✔
121
                        channel.OtherNode, channel.InPolicy, inboundFee,
1,651✔
122
                        channel.Capacity, defaultHopPayloadSize, nil,
1,651✔
123
                )
1,651✔
124

1,651✔
125
                return nil
1,651✔
126
        }
127

128
        // Iterate over all channels of the to node.
129
        return g.ForEachNodeChannel(u.toNode, cb)
711✔
130
}
131

132
// unifiedEdge is the individual channel data that is kept inside an edgeUnifier
133
// object.
134
type unifiedEdge struct {
135
        policy      *models.CachedEdgePolicy
136
        capacity    btcutil.Amount
137
        inboundFees models.InboundFee
138

139
        // hopPayloadSize supplies an edge with the ability to calculate the
140
        // exact payload size if this edge would be included in a route. This
141
        // is needed because hops of a blinded path differ in their payload
142
        // structure compared to cleartext hops.
143
        hopPayloadSizeFn PayloadSizeFunc
144

145
        // blindedPayment if set, is the BlindedPayment that this edge was
146
        // derived from originally.
147
        blindedPayment *BlindedPayment
148
}
149

150
// newUnifiedEdge constructs a new unifiedEdge.
151
func newUnifiedEdge(policy *models.CachedEdgePolicy, capacity btcutil.Amount,
152
        inboundFees models.InboundFee, hopPayloadSizeFn PayloadSizeFunc,
153
        blindedPayment *BlindedPayment) *unifiedEdge {
4,091✔
154

4,091✔
155
        return &unifiedEdge{
4,091✔
156
                policy:           policy,
4,091✔
157
                capacity:         capacity,
4,091✔
158
                inboundFees:      inboundFees,
4,091✔
159
                hopPayloadSizeFn: hopPayloadSizeFn,
4,091✔
160
                blindedPayment:   blindedPayment,
4,091✔
161
        }
4,091✔
162
}
4,091✔
163

164
// amtInRange checks whether an amount falls within the valid range for a
165
// channel.
166
func (u *unifiedEdge) amtInRange(amt lnwire.MilliSatoshi) bool {
1,354✔
167
        // If the capacity is available (non-light clients), skip channels that
1,354✔
168
        // are too small.
1,354✔
169
        if u.capacity > 0 &&
1,354✔
170
                amt > lnwire.NewMSatFromSatoshis(u.capacity) {
1,368✔
171

14✔
172
                log.Tracef("Not enough capacity: amt=%v, capacity=%v",
14✔
173
                        amt, u.capacity)
14✔
174
                return false
14✔
175
        }
14✔
176

177
        // Skip channels for which this htlc is too large.
178
        if u.policy.MessageFlags.HasMaxHtlc() &&
1,340✔
179
                amt > u.policy.MaxHTLC {
1,350✔
180

10✔
181
                log.Tracef("Exceeds policy's MaxHTLC: amt=%v, MaxHTLC=%v",
10✔
182
                        amt, u.policy.MaxHTLC)
10✔
183
                return false
10✔
184
        }
10✔
185

186
        // Skip channels for which this htlc is too small.
187
        if amt < u.policy.MinHTLC {
1,346✔
188
                log.Tracef("below policy's MinHTLC: amt=%v, MinHTLC=%v",
13✔
189
                        amt, u.policy.MinHTLC)
13✔
190
                return false
13✔
191
        }
13✔
192

193
        return true
1,323✔
194
}
195

196
// edgeUnifier is an object that covers all channels between a pair of nodes.
197
type edgeUnifier struct {
198
        edges     []*unifiedEdge
199
        localChan bool
200
}
201

202
// getEdge returns the optimal unified edge to use for this connection given a
203
// specific amount to send. It differentiates between local and network
204
// channels.
205
func (u *edgeUnifier) getEdge(netAmtReceived lnwire.MilliSatoshi,
206
        bandwidthHints bandwidthHints,
207
        nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
1,322✔
208

1,322✔
209
        if u.localChan {
1,497✔
210
                return u.getEdgeLocal(
175✔
211
                        netAmtReceived, bandwidthHints, nextOutFee,
175✔
212
                )
175✔
213
        }
175✔
214

215
        return u.getEdgeNetwork(netAmtReceived, nextOutFee)
1,150✔
216
}
217

218
// calcCappedInboundFee calculates the inbound fee for a channel, taking into
219
// account the total node fee for the "to" node.
220
func calcCappedInboundFee(edge *unifiedEdge, amt lnwire.MilliSatoshi,
221
        nextOutFee lnwire.MilliSatoshi) int64 {
1,347✔
222

1,347✔
223
        // Calculate the inbound fee charged for the amount that passes over the
1,347✔
224
        // channel.
1,347✔
225
        inboundFee := edge.inboundFees.CalcFee(amt)
1,347✔
226

1,347✔
227
        // Take into account that the total node fee cannot be negative.
1,347✔
228
        if inboundFee < -int64(nextOutFee) {
1,350✔
229
                inboundFee = -int64(nextOutFee)
3✔
230
        }
3✔
231

232
        return inboundFee
1,347✔
233
}
234

235
// getEdgeLocal returns the optimal unified edge to use for this local
236
// connection given a specific amount to send.
237
func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi,
238
        bandwidthHints bandwidthHints,
239
        nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
175✔
240

175✔
241
        var (
175✔
242
                bestEdge     *unifiedEdge
175✔
243
                maxBandwidth lnwire.MilliSatoshi
175✔
244
        )
175✔
245

175✔
246
        for _, edge := range u.edges {
353✔
247
                // Calculate the inbound fee charged at the receiving node.
178✔
248
                inboundFee := calcCappedInboundFee(
178✔
249
                        edge, netAmtReceived, nextOutFee,
178✔
250
                )
178✔
251

178✔
252
                // Add inbound fee to get to the amount that is sent over the
178✔
253
                // local channel.
178✔
254
                amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee)
178✔
255

178✔
256
                // Check valid amount range for the channel. We skip this test
178✔
257
                // for payments with custom HTLC data, as the amount sent on
178✔
258
                // the BTC layer may differ from the amount that is actually
178✔
259
                // forwarded in custom channels.
178✔
260
                if bandwidthHints.firstHopCustomBlob().IsNone() &&
178✔
261
                        !edge.amtInRange(amt) {
189✔
262

11✔
263
                        log.Debugf("Amount %v not in range for edge %v",
11✔
264
                                netAmtReceived, edge.policy.ChannelID)
11✔
265

11✔
266
                        continue
11✔
267
                }
268

269
                // For local channels, there is no fee to pay or an extra time
270
                // lock. We only consider the currently available bandwidth for
271
                // channel selection. The disabled flag is ignored for local
272
                // channels.
273

274
                // Retrieve bandwidth for this local channel. If not
275
                // available, assume this channel has enough bandwidth.
276
                //
277
                // TODO(joostjager): Possibly change to skipping this
278
                // channel. The bandwidth hint is expected to be
279
                // available.
280
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
167✔
281
                        edge.policy.ChannelID, amt,
167✔
282
                )
167✔
283
                if !ok {
246✔
284
                        log.Debugf("Cannot get bandwidth for edge %v, use max "+
79✔
285
                                "instead", edge.policy.ChannelID)
79✔
286
                        bandwidth = lnwire.MaxMilliSatoshi
79✔
287
                }
79✔
288

289
                // TODO(yy): if the above `!ok` is chosen, we'd have
290
                // `bandwidth` to be the max value, which will end up having
291
                // the `maxBandwidth` to be have the largest value and this
292
                // edge will be the chosen one. This is wrong in two ways,
293
                // 1. we need to understand why `availableChanBandwidth` cannot
294
                // find bandwidth for this edge as something is wrong with this
295
                // channel, and,
296
                // 2. this edge is likely NOT the local channel with the
297
                // highest available bandwidth.
298
                //
299
                // Skip channels that can't carry the payment.
300
                if amt > bandwidth {
177✔
301
                        log.Debugf("Skipped edge %v: not enough bandwidth, "+
10✔
302
                                "bandwidth=%v, amt=%v", edge.policy.ChannelID,
10✔
303
                                bandwidth, amt)
10✔
304

10✔
305
                        continue
10✔
306
                }
307

308
                // We pick the local channel with the highest available
309
                // bandwidth, to maximize the success probability. It can be
310
                // that the channel state changes between querying the bandwidth
311
                // hints and sending out the htlc.
312
                if bandwidth < maxBandwidth {
160✔
UNCOV
313
                        log.Debugf("Skipped edge %v: not max bandwidth, "+
×
UNCOV
314
                                "bandwidth=%v, maxBandwidth=%v",
×
UNCOV
315
                                edge.policy.ChannelID, bandwidth, maxBandwidth)
×
UNCOV
316

×
UNCOV
317
                        continue
×
318
                }
319
                maxBandwidth = bandwidth
160✔
320

160✔
321
                // Update best edge.
160✔
322
                bestEdge = newUnifiedEdge(
160✔
323
                        edge.policy, edge.capacity, edge.inboundFees,
160✔
324
                        edge.hopPayloadSizeFn, edge.blindedPayment,
160✔
325
                )
160✔
326
        }
327

328
        return bestEdge
175✔
329
}
330

331
// getEdgeNetwork returns the optimal unified edge to use for this connection
332
// given a specific amount to send. The goal is to return a unified edge with a
333
// policy that maximizes the probability of a successful forward in a non-strict
334
// forwarding context.
335
func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
336
        nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
1,150✔
337

1,150✔
338
        var (
1,150✔
339
                bestPolicy       *unifiedEdge
1,150✔
340
                maxFee           int64 = math.MinInt64
1,150✔
341
                maxTimelock      uint16
1,150✔
342
                maxCapMsat       lnwire.MilliSatoshi
1,150✔
343
                hopPayloadSizeFn PayloadSizeFunc
1,150✔
344
        )
1,150✔
345

1,150✔
346
        for _, edge := range u.edges {
2,311✔
347
                // Calculate the inbound fee charged at the receiving node.
1,161✔
348
                inboundFee := calcCappedInboundFee(
1,161✔
349
                        edge, netAmtReceived, nextOutFee,
1,161✔
350
                )
1,161✔
351

1,161✔
352
                // Add inbound fee to get to the amount that is sent over the
1,161✔
353
                // channel.
1,161✔
354
                amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee)
1,161✔
355

1,161✔
356
                // Check valid amount range for the channel.
1,161✔
357
                if !edge.amtInRange(amt) {
1,183✔
358
                        log.Debugf("Amount %v not in range for edge %v",
22✔
359
                                amt, edge.policy.ChannelID)
22✔
360
                        continue
22✔
361
                }
362

363
                // For network channels, skip the disabled ones.
364
                edgeFlags := edge.policy.ChannelFlags
1,142✔
365
                isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0
1,142✔
366
                if isDisabled {
1,144✔
367
                        log.Debugf("Skipped edge %v due to it being disabled",
2✔
368
                                edge.policy.ChannelID)
2✔
369
                        continue
2✔
370
                }
371

372
                // Track the maximal capacity for usable channels. If we don't
373
                // know the capacity, we fall back to MaxHTLC.
374
                capMsat := lnwire.NewMSatFromSatoshis(edge.capacity)
1,140✔
375
                if capMsat == 0 && edge.policy.MessageFlags.HasMaxHtlc() {
1,142✔
376
                        log.Tracef("No capacity available for channel %v, "+
2✔
377
                                "using MaxHtlcMsat (%v) as a fallback.",
2✔
378
                                edge.policy.ChannelID, edge.policy.MaxHTLC)
2✔
379

2✔
380
                        capMsat = edge.policy.MaxHTLC
2✔
381
                }
2✔
382
                maxCapMsat = lntypes.Max(capMsat, maxCapMsat)
1,140✔
383

1,140✔
384
                // Track the maximum time lock of all channels that are
1,140✔
385
                // candidate for non-strict forwarding at the routing node.
1,140✔
386
                maxTimelock = lntypes.Max(
1,140✔
387
                        maxTimelock, edge.policy.TimeLockDelta,
1,140✔
388
                )
1,140✔
389

1,140✔
390
                outboundFee := int64(edge.policy.ComputeFee(amt))
1,140✔
391
                fee := outboundFee + inboundFee
1,140✔
392

1,140✔
393
                // Use the policy that results in the highest fee for this
1,140✔
394
                // specific amount.
1,140✔
395
                if fee < maxFee {
1,141✔
396
                        log.Debugf("Skipped edge %v due to it produces less "+
1✔
397
                                "fee: fee=%v, maxFee=%v",
1✔
398
                                edge.policy.ChannelID, fee, maxFee)
1✔
399

1✔
400
                        continue
1✔
401
                }
402
                maxFee = fee
1,139✔
403

1,139✔
404
                bestPolicy = newUnifiedEdge(
1,139✔
405
                        edge.policy, 0, edge.inboundFees, nil,
1,139✔
406
                        edge.blindedPayment,
1,139✔
407
                )
1,139✔
408

1,139✔
409
                // The payload size function for edges to a connected peer is
1,139✔
410
                // always the same hence there is not need to find the maximum.
1,139✔
411
                // This also counts for blinded edges where we only have one
1,139✔
412
                // edge to a blinded peer.
1,139✔
413
                hopPayloadSizeFn = edge.hopPayloadSizeFn
1,139✔
414
        }
415

416
        // Return early if no channel matches.
417
        if bestPolicy == nil {
1,171✔
418
                return nil
21✔
419
        }
21✔
420

421
        // We have already picked the highest fee that could be required for
422
        // non-strict forwarding. To also cover the case where a lower fee
423
        // channel requires a longer time lock, we modify the policy by setting
424
        // the maximum encountered time lock. Note that this results in a
425
        // synthetic policy that is not actually present on the routing node.
426
        //
427
        // The reason we do this, is that we try to maximize the chance that we
428
        // get forwarded. Because we penalize pair-wise, there won't be a second
429
        // chance for this node pair. But this is all only needed for nodes that
430
        // have distinct policies for channels to the same peer.
431
        policyCopy := *bestPolicy.policy
1,132✔
432
        policyCopy.TimeLockDelta = maxTimelock
1,132✔
433
        modifiedEdge := newUnifiedEdge(
1,132✔
434
                &policyCopy, maxCapMsat.ToSatoshis(), bestPolicy.inboundFees,
1,132✔
435
                hopPayloadSizeFn, bestPolicy.blindedPayment,
1,132✔
436
        )
1,132✔
437

1,132✔
438
        return modifiedEdge
1,132✔
439
}
440

441
// minAmt returns the minimum amount that can be forwarded on this connection.
442
func (u *edgeUnifier) minAmt() lnwire.MilliSatoshi {
11✔
443
        min := lnwire.MaxMilliSatoshi
11✔
444
        for _, edge := range u.edges {
26✔
445
                min = lntypes.Min(min, edge.policy.MinHTLC)
15✔
446
        }
15✔
447

448
        return min
11✔
449
}
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