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

lightningnetwork / lnd / 11389453157

17 Oct 2024 04:50PM UTC coverage: 57.875% (-0.9%) from 58.81%
11389453157

Pull #9148

github

ProofOfKeags
lnwire: convert DynPropose and DynCommit to use typed tlv records
Pull Request #9148: DynComms [2/n]: lnwire: add authenticated wire messages for Dyn*

336 of 477 new or added lines in 12 files covered. (70.44%)

18956 existing lines in 244 files now uncovered.

99218 of 171435 relevant lines covered (57.87%)

36987.44 hits per line

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

94.86
/routing/unified_edges.go
1
package routing
2

3
import (
4
        "math"
5

6
        "github.com/btcsuite/btcd/btcutil"
7
        "github.com/lightningnetwork/lnd/channeldb"
8
        "github.com/lightningnetwork/lnd/channeldb/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 {
706✔
38

706✔
39
        return &nodeEdgeUnifier{
706✔
40
                edgeUnifiers:   make(map[route.Vertex]*edgeUnifier),
706✔
41
                toNode:         toNode,
706✔
42
                useInboundFees: useInboundFees,
706✔
43
                sourceNode:     sourceNode,
706✔
44
                outChanRestr:   outChanRestr,
706✔
45
        }
706✔
46
}
706✔
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,662✔
56

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

1,662✔
59
        // Skip channels if there is an outgoing channel restriction.
1,662✔
60
        if localChan && u.outChanRestr != nil {
1,676✔
61
                if _, ok := u.outChanRestr[edge.ChannelID]; !ok {
22✔
62
                        return
8✔
63
                }
8✔
64
        }
65

66
        // Update the edgeUnifiers map.
67
        unifier, ok := u.edgeUnifiers[fromNode]
1,654✔
68
        if !ok {
3,291✔
69
                unifier = &edgeUnifier{
1,637✔
70
                        localChan: localChan,
1,637✔
71
                }
1,637✔
72
                u.edgeUnifiers[fromNode] = unifier
1,637✔
73
        }
1,637✔
74

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

×
82
                return
×
83
        }
×
84

85
        // Zero inbound fee for exit hops.
86
        if !u.useInboundFees {
2,147✔
87
                inboundFee = models.InboundFee{}
493✔
88
        }
493✔
89

90
        unifier.edges = append(unifier.edges, newUnifiedEdge(
1,654✔
91
                edge, capacity, inboundFee, hopPayloadSizeFn, blindedPayment,
1,654✔
92
        ))
1,654✔
93
}
94

95
// addGraphPolicies adds all policies that are known for the toNode in the
96
// graph.
97
func (u *nodeEdgeUnifier) addGraphPolicies(g Graph) error {
700✔
98
        cb := func(channel *channeldb.DirectedChannel) error {
2,344✔
99
                // If there is no edge policy for this candidate node, skip.
1,644✔
100
                // Note that we are searching backwards so this node would have
1,644✔
101
                // come prior to the pivot node in the route.
1,644✔
102
                if channel.InPolicy == nil {
1,644✔
103
                        return nil
×
104
                }
×
105

106
                // Add this policy to the corresponding edgeUnifier. We default
107
                // to the clear hop payload size function because
108
                // `addGraphPolicies` is only used for cleartext intermediate
109
                // hops in a route.
110
                inboundFee := models.NewInboundFeeFromWire(
1,644✔
111
                        channel.InboundFee,
1,644✔
112
                )
1,644✔
113

1,644✔
114
                u.addPolicy(
1,644✔
115
                        channel.OtherNode, channel.InPolicy, inboundFee,
1,644✔
116
                        channel.Capacity, defaultHopPayloadSize, nil,
1,644✔
117
                )
1,644✔
118

1,644✔
119
                return nil
1,644✔
120
        }
121

122
        // Iterate over all channels of the to node.
123
        return g.ForEachNodeChannel(u.toNode, cb)
700✔
124
}
125

126
// unifiedEdge is the individual channel data that is kept inside an edgeUnifier
127
// object.
128
type unifiedEdge struct {
129
        policy      *models.CachedEdgePolicy
130
        capacity    btcutil.Amount
131
        inboundFees models.InboundFee
132

133
        // hopPayloadSize supplies an edge with the ability to calculate the
134
        // exact payload size if this edge would be included in a route. This
135
        // is needed because hops of a blinded path differ in their payload
136
        // structure compared to cleartext hops.
137
        hopPayloadSizeFn PayloadSizeFunc
138

139
        // blindedPayment if set, is the BlindedPayment that this edge was
140
        // derived from originally.
141
        blindedPayment *BlindedPayment
142
}
143

144
// newUnifiedEdge constructs a new unifiedEdge.
145
func newUnifiedEdge(policy *models.CachedEdgePolicy, capacity btcutil.Amount,
146
        inboundFees models.InboundFee, hopPayloadSizeFn PayloadSizeFunc,
147
        blindedPayment *BlindedPayment) *unifiedEdge {
4,057✔
148

4,057✔
149
        return &unifiedEdge{
4,057✔
150
                policy:           policy,
4,057✔
151
                capacity:         capacity,
4,057✔
152
                inboundFees:      inboundFees,
4,057✔
153
                hopPayloadSizeFn: hopPayloadSizeFn,
4,057✔
154
                blindedPayment:   blindedPayment,
4,057✔
155
        }
4,057✔
156
}
4,057✔
157

158
// amtInRange checks whether an amount falls within the valid range for a
159
// channel.
160
func (u *unifiedEdge) amtInRange(amt lnwire.MilliSatoshi) bool {
1,341✔
161
        // If the capacity is available (non-light clients), skip channels that
1,341✔
162
        // are too small.
1,341✔
163
        if u.capacity > 0 &&
1,341✔
164
                amt > lnwire.NewMSatFromSatoshis(u.capacity) {
1,355✔
165

14✔
166
                log.Tracef("Not enough capacity: amt=%v, capacity=%v",
14✔
167
                        amt, u.capacity)
14✔
168
                return false
14✔
169
        }
14✔
170

171
        // Skip channels for which this htlc is too large.
172
        if u.policy.MessageFlags.HasMaxHtlc() &&
1,327✔
173
                amt > u.policy.MaxHTLC {
1,334✔
174

7✔
175
                log.Tracef("Exceeds policy's MaxHTLC: amt=%v, MaxHTLC=%v",
7✔
176
                        amt, u.policy.MaxHTLC)
7✔
177
                return false
7✔
178
        }
7✔
179

180
        // Skip channels for which this htlc is too small.
181
        if amt < u.policy.MinHTLC {
1,330✔
182
                log.Tracef("below policy's MinHTLC: amt=%v, MinHTLC=%v",
10✔
183
                        amt, u.policy.MinHTLC)
10✔
184
                return false
10✔
185
        }
10✔
186

187
        return true
1,310✔
188
}
189

190
// edgeUnifier is an object that covers all channels between a pair of nodes.
191
type edgeUnifier struct {
192
        edges     []*unifiedEdge
193
        localChan bool
194
}
195

196
// getEdge returns the optimal unified edge to use for this connection given a
197
// specific amount to send. It differentiates between local and network
198
// channels.
199
func (u *edgeUnifier) getEdge(netAmtReceived lnwire.MilliSatoshi,
200
        bandwidthHints bandwidthHints,
201
        nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
1,309✔
202

1,309✔
203
        if u.localChan {
1,479✔
204
                return u.getEdgeLocal(
170✔
205
                        netAmtReceived, bandwidthHints, nextOutFee,
170✔
206
                )
170✔
207
        }
170✔
208

209
        return u.getEdgeNetwork(netAmtReceived, nextOutFee)
1,139✔
210
}
211

212
// calcCappedInboundFee calculates the inbound fee for a channel, taking into
213
// account the total node fee for the "to" node.
214
func calcCappedInboundFee(edge *unifiedEdge, amt lnwire.MilliSatoshi,
215
        nextOutFee lnwire.MilliSatoshi) int64 {
1,334✔
216

1,334✔
217
        // Calculate the inbound fee charged for the amount that passes over the
1,334✔
218
        // channel.
1,334✔
219
        inboundFee := edge.inboundFees.CalcFee(amt)
1,334✔
220

1,334✔
221
        // Take into account that the total node fee cannot be negative.
1,334✔
222
        if inboundFee < -int64(nextOutFee) {
1,337✔
223
                inboundFee = -int64(nextOutFee)
3✔
224
        }
3✔
225

226
        return inboundFee
1,334✔
227
}
228

229
// getEdgeLocal returns the optimal unified edge to use for this local
230
// connection given a specific amount to send.
231
func (u *edgeUnifier) getEdgeLocal(netAmtReceived lnwire.MilliSatoshi,
232
        bandwidthHints bandwidthHints,
233
        nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
170✔
234

170✔
235
        var (
170✔
236
                bestEdge     *unifiedEdge
170✔
237
                maxBandwidth lnwire.MilliSatoshi
170✔
238
        )
170✔
239

170✔
240
        for _, edge := range u.edges {
343✔
241
                // Calculate the inbound fee charged at the receiving node.
173✔
242
                inboundFee := calcCappedInboundFee(
173✔
243
                        edge, netAmtReceived, nextOutFee,
173✔
244
                )
173✔
245

173✔
246
                // Add inbound fee to get to the amount that is sent over the
173✔
247
                // local channel.
173✔
248
                amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee)
173✔
249

173✔
250
                // Check valid amount range for the channel. We skip this test
173✔
251
                // for payments with custom HTLC data, as the amount sent on
173✔
252
                // the BTC layer may differ from the amount that is actually
173✔
253
                // forwarded in custom channels.
173✔
254
                if bandwidthHints.firstHopCustomBlob().IsNone() &&
173✔
255
                        !edge.amtInRange(amt) {
184✔
256

11✔
257
                        log.Debugf("Amount %v not in range for edge %v",
11✔
258
                                netAmtReceived, edge.policy.ChannelID)
11✔
259

11✔
260
                        continue
11✔
261
                }
262

263
                // For local channels, there is no fee to pay or an extra time
264
                // lock. We only consider the currently available bandwidth for
265
                // channel selection. The disabled flag is ignored for local
266
                // channels.
267

268
                // Retrieve bandwidth for this local channel. If not
269
                // available, assume this channel has enough bandwidth.
270
                //
271
                // TODO(joostjager): Possibly change to skipping this
272
                // channel. The bandwidth hint is expected to be
273
                // available.
274
                bandwidth, ok := bandwidthHints.availableChanBandwidth(
162✔
275
                        edge.policy.ChannelID, amt,
162✔
276
                )
162✔
277
                if !ok {
239✔
278
                        log.Debugf("Cannot get bandwidth for edge %v, use max "+
77✔
279
                                "instead", edge.policy.ChannelID)
77✔
280
                        bandwidth = lnwire.MaxMilliSatoshi
77✔
281
                }
77✔
282

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

7✔
299
                        continue
7✔
300
                }
301

302
                // We pick the local channel with the highest available
303
                // bandwidth, to maximize the success probability. It can be
304
                // that the channel state changes between querying the bandwidth
305
                // hints and sending out the htlc.
306
                if bandwidth < maxBandwidth {
155✔
UNCOV
307
                        log.Debugf("Skipped edge %v: not max bandwidth, "+
×
UNCOV
308
                                "bandwidth=%v, maxBandwidth=%v",
×
UNCOV
309
                                edge.policy.ChannelID, bandwidth, maxBandwidth)
×
UNCOV
310

×
UNCOV
311
                        continue
×
312
                }
313
                maxBandwidth = bandwidth
155✔
314

155✔
315
                // Update best edge.
155✔
316
                bestEdge = newUnifiedEdge(
155✔
317
                        edge.policy, edge.capacity, edge.inboundFees,
155✔
318
                        edge.hopPayloadSizeFn, edge.blindedPayment,
155✔
319
                )
155✔
320
        }
321

322
        return bestEdge
170✔
323
}
324

325
// getEdgeNetwork returns the optimal unified edge to use for this connection
326
// given a specific amount to send. The goal is to return a unified edge with a
327
// policy that maximizes the probability of a successful forward in a non-strict
328
// forwarding context.
329
func (u *edgeUnifier) getEdgeNetwork(netAmtReceived lnwire.MilliSatoshi,
330
        nextOutFee lnwire.MilliSatoshi) *unifiedEdge {
1,139✔
331

1,139✔
332
        var (
1,139✔
333
                bestPolicy       *unifiedEdge
1,139✔
334
                maxFee           int64 = math.MinInt64
1,139✔
335
                maxTimelock      uint16
1,139✔
336
                maxCapMsat       lnwire.MilliSatoshi
1,139✔
337
                hopPayloadSizeFn PayloadSizeFunc
1,139✔
338
        )
1,139✔
339

1,139✔
340
        for _, edge := range u.edges {
2,289✔
341
                // Calculate the inbound fee charged at the receiving node.
1,150✔
342
                inboundFee := calcCappedInboundFee(
1,150✔
343
                        edge, netAmtReceived, nextOutFee,
1,150✔
344
                )
1,150✔
345

1,150✔
346
                // Add inbound fee to get to the amount that is sent over the
1,150✔
347
                // channel.
1,150✔
348
                amt := netAmtReceived + lnwire.MilliSatoshi(inboundFee)
1,150✔
349

1,150✔
350
                // Check valid amount range for the channel.
1,150✔
351
                if !edge.amtInRange(amt) {
1,169✔
352
                        log.Debugf("Amount %v not in range for edge %v",
19✔
353
                                amt, edge.policy.ChannelID)
19✔
354
                        continue
19✔
355
                }
356

357
                // For network channels, skip the disabled ones.
358
                edgeFlags := edge.policy.ChannelFlags
1,131✔
359
                isDisabled := edgeFlags&lnwire.ChanUpdateDisabled != 0
1,131✔
360
                if isDisabled {
1,133✔
361
                        log.Debugf("Skipped edge %v due to it being disabled",
2✔
362
                                edge.policy.ChannelID)
2✔
363
                        continue
2✔
364
                }
365

366
                // Track the maximal capacity for usable channels. If we don't
367
                // know the capacity, we fall back to MaxHTLC.
368
                capMsat := lnwire.NewMSatFromSatoshis(edge.capacity)
1,129✔
369
                if capMsat == 0 && edge.policy.MessageFlags.HasMaxHtlc() {
1,131✔
370
                        log.Tracef("No capacity available for channel %v, "+
2✔
371
                                "using MaxHtlcMsat (%v) as a fallback.",
2✔
372
                                edge.policy.ChannelID, edge.policy.MaxHTLC)
2✔
373

2✔
374
                        capMsat = edge.policy.MaxHTLC
2✔
375
                }
2✔
376
                maxCapMsat = lntypes.Max(capMsat, maxCapMsat)
1,129✔
377

1,129✔
378
                // Track the maximum time lock of all channels that are
1,129✔
379
                // candidate for non-strict forwarding at the routing node.
1,129✔
380
                maxTimelock = lntypes.Max(
1,129✔
381
                        maxTimelock, edge.policy.TimeLockDelta,
1,129✔
382
                )
1,129✔
383

1,129✔
384
                outboundFee := int64(edge.policy.ComputeFee(amt))
1,129✔
385
                fee := outboundFee + inboundFee
1,129✔
386

1,129✔
387
                // Use the policy that results in the highest fee for this
1,129✔
388
                // specific amount.
1,129✔
389
                if fee < maxFee {
1,131✔
390
                        log.Debugf("Skipped edge %v due to it produces less "+
2✔
391
                                "fee: fee=%v, maxFee=%v",
2✔
392
                                edge.policy.ChannelID, fee, maxFee)
2✔
393

2✔
394
                        continue
2✔
395
                }
396
                maxFee = fee
1,127✔
397

1,127✔
398
                bestPolicy = newUnifiedEdge(
1,127✔
399
                        edge.policy, 0, edge.inboundFees, nil,
1,127✔
400
                        edge.blindedPayment,
1,127✔
401
                )
1,127✔
402

1,127✔
403
                // The payload size function for edges to a connected peer is
1,127✔
404
                // always the same hence there is not need to find the maximum.
1,127✔
405
                // This also counts for blinded edges where we only have one
1,127✔
406
                // edge to a blinded peer.
1,127✔
407
                hopPayloadSizeFn = edge.hopPayloadSizeFn
1,127✔
408
        }
409

410
        // Return early if no channel matches.
411
        if bestPolicy == nil {
1,157✔
412
                return nil
18✔
413
        }
18✔
414

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

1,121✔
432
        return modifiedEdge
1,121✔
433
}
434

435
// minAmt returns the minimum amount that can be forwarded on this connection.
436
func (u *edgeUnifier) minAmt() lnwire.MilliSatoshi {
11✔
437
        min := lnwire.MaxMilliSatoshi
11✔
438
        for _, edge := range u.edges {
26✔
439
                min = lntypes.Min(min, edge.policy.MinHTLC)
15✔
440
        }
15✔
441

442
        return min
11✔
443
}
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