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

lightningnetwork / lnd / 11170835610

03 Oct 2024 10:41PM UTC coverage: 49.188% (-9.6%) from 58.738%
11170835610

push

github

web-flow
Merge pull request #9154 from ziggie1984/master

multi: bump btcd version.

3 of 6 new or added lines in 6 files covered. (50.0%)

26110 existing lines in 428 files now uncovered.

97359 of 197934 relevant lines covered (49.19%)

1.04 hits per line

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

69.69
/routing/route/route.go
1
package route
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "encoding/hex"
7
        "errors"
8
        "fmt"
9
        "io"
10
        "strconv"
11
        "strings"
12

13
        "github.com/btcsuite/btcd/btcec/v2"
14
        sphinx "github.com/lightningnetwork/lightning-onion"
15
        "github.com/lightningnetwork/lnd/lnwire"
16
        "github.com/lightningnetwork/lnd/record"
17
        "github.com/lightningnetwork/lnd/tlv"
18
)
19

20
// VertexSize is the size of the array to store a vertex.
21
const VertexSize = 33
22

23
var (
24
        // ErrNoRouteHopsProvided is returned when a caller attempts to
25
        // construct a new sphinx packet, but provides an empty set of hops for
26
        // each route.
27
        ErrNoRouteHopsProvided = fmt.Errorf("empty route hops provided")
28

29
        // ErrMaxRouteHopsExceeded is returned when a caller attempts to
30
        // construct a new sphinx packet, but provides too many hops.
31
        ErrMaxRouteHopsExceeded = fmt.Errorf("route has too many hops")
32

33
        // ErrIntermediateMPPHop is returned when a hop tries to deliver an MPP
34
        // record to an intermediate hop, only final hops can receive MPP
35
        // records.
36
        ErrIntermediateMPPHop = errors.New("cannot send MPP to intermediate")
37

38
        // ErrAMPMissingMPP is returned when the caller tries to attach an AMP
39
        // record but no MPP record is presented for the final hop.
40
        ErrAMPMissingMPP = errors.New("cannot send AMP without MPP record")
41

42
        // ErrMissingField is returned if a required TLV is missing.
43
        ErrMissingField = errors.New("required tlv missing")
44

45
        // ErrUnexpectedField is returned if a tlv field is included when it
46
        // should not be.
47
        ErrUnexpectedField = errors.New("unexpected tlv included")
48
)
49

50
// Vertex is a simple alias for the serialization of a compressed Bitcoin
51
// public key.
52
type Vertex [VertexSize]byte
53

54
// NewVertex returns a new Vertex given a public key.
55
func NewVertex(pub *btcec.PublicKey) Vertex {
2✔
56
        var v Vertex
2✔
57
        copy(v[:], pub.SerializeCompressed())
2✔
58
        return v
2✔
59
}
2✔
60

61
// NewVertexFromBytes returns a new Vertex based on a serialized pubkey in a
62
// byte slice.
63
func NewVertexFromBytes(b []byte) (Vertex, error) {
2✔
64
        vertexLen := len(b)
2✔
65
        if vertexLen != VertexSize {
2✔
66
                return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+
×
67
                        "want %v", vertexLen, VertexSize)
×
68
        }
×
69

70
        var v Vertex
2✔
71
        copy(v[:], b)
2✔
72
        return v, nil
2✔
73
}
74

75
// NewVertexFromStr returns a new Vertex given its hex-encoded string format.
76
func NewVertexFromStr(v string) (Vertex, error) {
2✔
77
        // Return error if hex string is of incorrect length.
2✔
78
        if len(v) != VertexSize*2 {
2✔
79
                return Vertex{}, fmt.Errorf("invalid vertex string length of "+
×
80
                        "%v, want %v", len(v), VertexSize*2)
×
81
        }
×
82

83
        vertex, err := hex.DecodeString(v)
2✔
84
        if err != nil {
2✔
85
                return Vertex{}, err
×
86
        }
×
87

88
        return NewVertexFromBytes(vertex)
2✔
89
}
90

91
// String returns a human readable version of the Vertex which is the
92
// hex-encoding of the serialized compressed public key.
93
func (v Vertex) String() string {
2✔
94
        return fmt.Sprintf("%x", v[:])
2✔
95
}
2✔
96

97
// Hop represents an intermediate or final node of the route. This naming
98
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
99
// The struct houses the channel along which this hop can be reached and
100
// the values necessary to create the HTLC that needs to be sent to the
101
// next hop. It is also used to encode the per-hop payload included within
102
// the Sphinx packet.
103
type Hop struct {
104
        // PubKeyBytes is the raw bytes of the public key of the target node.
105
        PubKeyBytes Vertex
106

107
        // ChannelID is the unique channel ID for the channel. The first 3
108
        // bytes are the block height, the next 3 the index within the block,
109
        // and the last 2 bytes are the output index for the channel.
110
        ChannelID uint64
111

112
        // OutgoingTimeLock is the timelock value that should be used when
113
        // crafting the _outgoing_ HTLC from this hop.
114
        OutgoingTimeLock uint32
115

116
        // AmtToForward is the amount that this hop will forward to the next
117
        // hop. This value is less than the value that the incoming HTLC
118
        // carries as a fee will be subtracted by the hop.
119
        AmtToForward lnwire.MilliSatoshi
120

121
        // MPP encapsulates the data required for option_mpp. This field should
122
        // only be set for the final hop.
123
        MPP *record.MPP
124

125
        // AMP encapsulates the data required for option_amp. This field should
126
        // only be set for the final hop.
127
        AMP *record.AMP
128

129
        // CustomRecords if non-nil are a set of additional TLV records that
130
        // should be included in the forwarding instructions for this node.
131
        CustomRecords record.CustomSet
132

133
        // LegacyPayload if true, then this signals that this node doesn't
134
        // understand the new TLV payload, so we must instead use the legacy
135
        // payload.
136
        //
137
        // NOTE: we should no longer ever create a Hop with Legacy set to true.
138
        // The only reason we are keeping this member is that it could be the
139
        // case that we have serialised hops persisted to disk where
140
        // LegacyPayload is true.
141
        LegacyPayload bool
142

143
        // Metadata is additional data that is sent along with the payment to
144
        // the payee.
145
        Metadata []byte
146

147
        // EncryptedData is an encrypted data blob includes for hops that are
148
        // part of a blinded route.
149
        EncryptedData []byte
150

151
        // BlindingPoint is an ephemeral public key used by introduction nodes
152
        // in blinded routes to unblind their portion of the route and pass on
153
        // the next ephemeral key to the next blinded node to do the same.
154
        BlindingPoint *btcec.PublicKey
155

156
        // TotalAmtMsat is the total amount for a blinded payment, potentially
157
        // spread over more than one HTLC. This field should only be set for
158
        // the final hop in a blinded path.
159
        TotalAmtMsat lnwire.MilliSatoshi
160
}
161

162
// Copy returns a deep copy of the Hop.
UNCOV
163
func (h *Hop) Copy() *Hop {
×
UNCOV
164
        c := *h
×
UNCOV
165

×
UNCOV
166
        if h.MPP != nil {
×
UNCOV
167
                m := *h.MPP
×
UNCOV
168
                c.MPP = &m
×
UNCOV
169
        }
×
170

UNCOV
171
        if h.AMP != nil {
×
UNCOV
172
                a := *h.AMP
×
UNCOV
173
                c.AMP = &a
×
UNCOV
174
        }
×
175

UNCOV
176
        if h.BlindingPoint != nil {
×
177
                b := *h.BlindingPoint
×
178
                c.BlindingPoint = &b
×
179
        }
×
180

UNCOV
181
        return &c
×
182
}
183

184
// PackHopPayload writes to the passed io.Writer, the series of byes that can
185
// be placed directly into the per-hop payload (EOB) for this hop. This will
186
// include the required routing fields, as well as serializing any of the
187
// passed optional TLVRecords. nextChanID is the unique channel ID that
188
// references the _outgoing_ channel ID that follows this hop. The lastHop bool
189
// is used to signal whether this hop is the final hop in a route. Previously,
190
// a zero nextChanID would be used for this purpose, but with the addition of
191
// blinded routes which allow zero nextChanID values for intermediate hops we
192
// add an explicit signal.
193
func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
194
        finalHop bool) error {
2✔
195

2✔
196
        // If this is a legacy payload, then we'll exit here as this method
2✔
197
        // shouldn't be called.
2✔
198
        if h.LegacyPayload {
2✔
199
                return fmt.Errorf("cannot pack hop payloads for legacy " +
×
200
                        "payloads")
×
201
        }
×
202

203
        // Otherwise, we'll need to make a new stream that includes our
204
        // required routing fields, as well as these optional values.
205
        var records []tlv.Record
2✔
206

2✔
207
        // Hops that are not part of a blinded path will have an amount and
2✔
208
        // a CLTV expiry field. In a blinded route (where encrypted data is
2✔
209
        // non-nil), these values may be omitted for intermediate nodes.
2✔
210
        // Validate these fields against the structure of the payload so that
2✔
211
        // we know they're included (or excluded) correctly.
2✔
212
        isBlinded := h.EncryptedData != nil
2✔
213

2✔
214
        if err := optionalBlindedField(
2✔
215
                h.AmtToForward == 0, isBlinded, finalHop,
2✔
216
        ); err != nil {
2✔
UNCOV
217
                return fmt.Errorf("%w: amount to forward: %v", err,
×
UNCOV
218
                        h.AmtToForward)
×
UNCOV
219
        }
×
220

221
        if err := optionalBlindedField(
2✔
222
                h.OutgoingTimeLock == 0, isBlinded, finalHop,
2✔
223
        ); err != nil {
2✔
UNCOV
224
                return fmt.Errorf("%w: outgoing timelock: %v", err,
×
UNCOV
225
                        h.OutgoingTimeLock)
×
UNCOV
226
        }
×
227

228
        // Once we've validated that these TLVs are set as we expect, we can
229
        // go ahead and include them if non-zero.
230
        amt := uint64(h.AmtToForward)
2✔
231
        if amt != 0 {
4✔
232
                records = append(
2✔
233
                        records, record.NewAmtToFwdRecord(&amt),
2✔
234
                )
2✔
235
        }
2✔
236

237
        if h.OutgoingTimeLock != 0 {
4✔
238
                records = append(
2✔
239
                        records, record.NewLockTimeRecord(&h.OutgoingTimeLock),
2✔
240
                )
2✔
241
        }
2✔
242

243
        // Validate channel TLV is present as expected based on location in
244
        // route and whether this hop is blinded.
245
        err := validateNextChanID(nextChanID != 0, isBlinded, finalHop)
2✔
246
        if err != nil {
2✔
UNCOV
247
                return fmt.Errorf("%w: channel id: %v", err, nextChanID)
×
UNCOV
248
        }
×
249

250
        if nextChanID != 0 {
4✔
251
                records = append(records,
2✔
252
                        record.NewNextHopIDRecord(&nextChanID),
2✔
253
                )
2✔
254
        }
2✔
255

256
        // If an MPP record is destined for this hop, ensure that we only ever
257
        // attach it to the final hop. Otherwise the route was constructed
258
        // incorrectly.
259
        if h.MPP != nil {
4✔
260
                if finalHop {
4✔
261
                        records = append(records, h.MPP.Record())
2✔
262
                } else {
2✔
UNCOV
263
                        return ErrIntermediateMPPHop
×
UNCOV
264
                }
×
265
        }
266

267
        // Add encrypted data and blinding point if present.
268
        if h.EncryptedData != nil {
4✔
269
                records = append(records, record.NewEncryptedDataRecord(
2✔
270
                        &h.EncryptedData,
2✔
271
                ))
2✔
272
        }
2✔
273

274
        if h.BlindingPoint != nil {
4✔
275
                records = append(records, record.NewBlindingPointRecord(
2✔
276
                        &h.BlindingPoint,
2✔
277
                ))
2✔
278
        }
2✔
279

280
        // If an AMP record is destined for this hop, ensure that we only ever
281
        // attach it if we also have an MPP record. We can infer that this is
282
        // already a final hop if MPP is non-nil otherwise we would have exited
283
        // above.
284
        if h.AMP != nil {
4✔
285
                if h.MPP != nil {
4✔
286
                        records = append(records, h.AMP.Record())
2✔
287
                } else {
2✔
UNCOV
288
                        return ErrAMPMissingMPP
×
UNCOV
289
                }
×
290
        }
291

292
        // If metadata is specified, generate a tlv record for it.
293
        if h.Metadata != nil {
2✔
UNCOV
294
                records = append(records,
×
UNCOV
295
                        record.NewMetadataRecord(&h.Metadata),
×
UNCOV
296
                )
×
UNCOV
297
        }
×
298

299
        if h.TotalAmtMsat != 0 {
4✔
300
                totalAmtInt := uint64(h.TotalAmtMsat)
2✔
301
                records = append(records,
2✔
302
                        record.NewTotalAmtMsatBlinded(&totalAmtInt),
2✔
303
                )
2✔
304
        }
2✔
305

306
        // Append any custom types destined for this hop.
307
        tlvRecords := tlv.MapToRecords(h.CustomRecords)
2✔
308
        records = append(records, tlvRecords...)
2✔
309

2✔
310
        // To ensure we produce a canonical stream, we'll sort the records
2✔
311
        // before encoding them as a stream in the hop payload.
2✔
312
        tlv.SortRecords(records)
2✔
313

2✔
314
        tlvStream, err := tlv.NewStream(records...)
2✔
315
        if err != nil {
2✔
316
                return err
×
317
        }
×
318

319
        return tlvStream.Encode(w)
2✔
320
}
321

322
// optionalBlindedField validates fields that we expect to be non-zero for all
323
// hops in a regular route, but may be zero for intermediate nodes in a blinded
324
// route. It will validate the following cases:
325
// - Not blinded: require non-zero values.
326
// - Intermediate blinded node: require zero values.
327
// - Final blinded node: require non-zero values.
328
func optionalBlindedField(isZero, blindedHop, finalHop bool) error {
2✔
329
        switch {
2✔
330
        // We are not in a blinded route and the TLV is not set when it should
331
        // be.
332
        case !blindedHop && isZero:
×
333
                return ErrMissingField
×
334

335
        // We are not in a blinded route and the TLV is set as expected.
336
        case !blindedHop:
2✔
337
                return nil
2✔
338

339
        // In a blinded route the final hop is expected to have TLV values set.
UNCOV
340
        case finalHop && isZero:
×
UNCOV
341
                return ErrMissingField
×
342

343
        // In an intermediate hop in a blinded route and the field is not zero.
344
        case !finalHop && !isZero:
×
345
                return ErrUnexpectedField
×
346
        }
347

348
        return nil
2✔
349
}
350

351
// validateNextChanID validates the presence of the nextChanID TLV field in
352
// a payload. For regular payments, it is expected to be present for all hops
353
// except the final hop. For blinded paths, it is not expected to be included
354
// at all (as this value is provided in encrypted data).
355
func validateNextChanID(nextChanIDIsSet, isBlinded, finalHop bool) error {
2✔
356
        switch {
2✔
357
        // Hops in a blinded route should not have a next channel ID set.
UNCOV
358
        case isBlinded && nextChanIDIsSet:
×
UNCOV
359
                return ErrUnexpectedField
×
360

361
        // Otherwise, blinded hops are allowed to have a zero value.
362
        case isBlinded:
2✔
363
                return nil
2✔
364

365
        // The final hop in a regular route is expected to have a zero value.
366
        case finalHop && nextChanIDIsSet:
×
367
                return ErrUnexpectedField
×
368

369
        // Intermediate hops in regular routes require non-zero value.
370
        case !finalHop && !nextChanIDIsSet:
×
371
                return ErrMissingField
×
372

373
        default:
2✔
374
                return nil
2✔
375
        }
376
}
377

378
// PayloadSize returns the total size this hop's payload would take up in the
379
// onion packet.
380
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
2✔
381
        if h.LegacyPayload {
2✔
UNCOV
382
                return sphinx.LegacyHopDataSize
×
UNCOV
383
        }
×
384

385
        var payloadSize uint64
2✔
386

2✔
387
        addRecord := func(tlvType tlv.Type, length uint64) {
4✔
388
                payloadSize += tlv.VarIntSize(uint64(tlvType)) +
2✔
389
                        tlv.VarIntSize(length) + length
2✔
390
        }
2✔
391

392
        // Add amount size.
393
        if h.AmtToForward != 0 {
4✔
394
                addRecord(record.AmtOnionType, tlv.SizeTUint64(
2✔
395
                        uint64(h.AmtToForward),
2✔
396
                ))
2✔
397
        }
2✔
398
        // Add lock time size.
399
        if h.OutgoingTimeLock != 0 {
4✔
400
                addRecord(
2✔
401
                        record.LockTimeOnionType,
2✔
402
                        tlv.SizeTUint64(uint64(h.OutgoingTimeLock)),
2✔
403
                )
2✔
404
        }
2✔
405

406
        // Add next hop if present.
407
        if nextChanID != 0 {
4✔
408
                addRecord(record.NextHopOnionType, 8)
2✔
409
        }
2✔
410

411
        // Add mpp if present.
412
        if h.MPP != nil {
4✔
413
                addRecord(record.MPPOnionType, h.MPP.PayloadSize())
2✔
414
        }
2✔
415

416
        // Add amp if present.
417
        if h.AMP != nil {
4✔
418
                addRecord(record.AMPOnionType, h.AMP.PayloadSize())
2✔
419
        }
2✔
420

421
        // Add encrypted data and blinding point if present.
422
        if h.EncryptedData != nil {
4✔
423
                addRecord(
2✔
424
                        record.EncryptedDataOnionType,
2✔
425
                        uint64(len(h.EncryptedData)),
2✔
426
                )
2✔
427
        }
2✔
428

429
        if h.BlindingPoint != nil {
4✔
430
                addRecord(
2✔
431
                        record.BlindingPointOnionType,
2✔
432
                        btcec.PubKeyBytesLenCompressed,
2✔
433
                )
2✔
434
        }
2✔
435

436
        // Add metadata if present.
437
        if h.Metadata != nil {
2✔
UNCOV
438
                addRecord(record.MetadataOnionType, uint64(len(h.Metadata)))
×
UNCOV
439
        }
×
440

441
        if h.TotalAmtMsat != 0 {
2✔
UNCOV
442
                addRecord(
×
UNCOV
443
                        record.TotalAmtMsatBlindedType,
×
UNCOV
444
                        tlv.SizeTUint64(uint64(h.AmtToForward)),
×
UNCOV
445
                )
×
UNCOV
446
        }
×
447

448
        // Add custom records.
449
        for k, v := range h.CustomRecords {
4✔
450
                addRecord(tlv.Type(k), uint64(len(v)))
2✔
451
        }
2✔
452

453
        // Add the size required to encode the payload length.
454
        payloadSize += tlv.VarIntSize(payloadSize)
2✔
455

2✔
456
        // Add HMAC.
2✔
457
        payloadSize += sphinx.HMACSize
2✔
458

2✔
459
        return payloadSize
2✔
460
}
461

462
// Route represents a path through the channel graph which runs over one or
463
// more channels in succession. This struct carries all the information
464
// required to craft the Sphinx onion packet, and send the payment along the
465
// first hop in the path. A route is only selected as valid if all the channels
466
// have sufficient capacity to carry the initial payment amount after fees are
467
// accounted for.
468
type Route struct {
469
        // TotalTimeLock is the cumulative (final) time lock across the entire
470
        // route. This is the CLTV value that should be extended to the first
471
        // hop in the route. All other hops will decrement the time-lock as
472
        // advertised, leaving enough time for all hops to wait for or present
473
        // the payment preimage to complete the payment.
474
        TotalTimeLock uint32
475

476
        // TotalAmount is the total amount of funds required to complete a
477
        // payment over this route. This value includes the cumulative fees at
478
        // each hop. As a result, the HTLC extended to the first-hop in the
479
        // route will need to have at least this many satoshis, otherwise the
480
        // route will fail at an intermediate node due to an insufficient
481
        // amount of fees.
482
        TotalAmount lnwire.MilliSatoshi
483

484
        // SourcePubKey is the pubkey of the node where this route originates
485
        // from.
486
        SourcePubKey Vertex
487

488
        // Hops contains details concerning the specific forwarding details at
489
        // each hop.
490
        Hops []*Hop
491

492
        // FirstHopAmount is the amount that should actually be sent to the
493
        // first hop in the route. This is only different from TotalAmount above
494
        // for custom channels where the on-chain amount doesn't necessarily
495
        // reflect all the value of an outgoing payment.
496
        FirstHopAmount tlv.RecordT[
497
                tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
498
        ]
499

500
        // FirstHopWireCustomRecords is a set of custom records that should be
501
        // included in the wire message sent to the first hop. This is only set
502
        // on custom channels and is used to include additional information
503
        // about the actual value of the payment.
504
        //
505
        // NOTE: Since these records already represent TLV records, and we
506
        // enforce them to be in the custom range (e.g. >= 65536), we don't use
507
        // another parent record type here. Instead, when serializing the Route
508
        // we merge the TLV records together with the custom records and encode
509
        // everything as a single TLV stream.
510
        FirstHopWireCustomRecords lnwire.CustomRecords
511
}
512

513
// Copy returns a deep copy of the Route.
UNCOV
514
func (r *Route) Copy() *Route {
×
UNCOV
515
        c := *r
×
UNCOV
516

×
UNCOV
517
        c.Hops = make([]*Hop, len(r.Hops))
×
UNCOV
518
        for i := range r.Hops {
×
UNCOV
519
                c.Hops[i] = r.Hops[i].Copy()
×
UNCOV
520
        }
×
521

UNCOV
522
        return &c
×
523
}
524

525
// HopFee returns the fee charged by the route hop indicated by hopIndex.
526
//
527
// This calculation takes into account the possibility that the route contains
528
// some blinded hops, that will not have the amount to forward set. We take
529
// note of various points in the blinded route.
530
//
531
// Given the following route where Carol is the introduction node and B2 is
532
// the recipient, Carol and B1's hops will not have an amount to forward set:
533
// Alice --- Bob ---- Carol (introduction) ----- B1 ----- B2
534
//
535
// We locate ourselves in the route as follows:
536
// * Regular Hop (eg Alice - Bob):
537
//
538
//        incomingAmt !=0
539
//        outgoingAmt !=0
540
//        ->  Fee = incomingAmt - outgoingAmt
541
//
542
// * Introduction Hop (eg Bob - Carol):
543
//
544
//        incomingAmt !=0
545
//        outgoingAmt = 0
546
//        -> Fee = incomingAmt - receiverAmt
547
//
548
// This has the impact of attributing the full fees for the blinded route to
549
// the introduction node.
550
//
551
// * Blinded Intermediate Hop (eg Carol - B1):
552
//
553
//        incomingAmt = 0
554
//        outgoingAmt = 0
555
//        -> Fee = 0
556
//
557
// * Final Blinded Hop (B1 - B2):
558
//
559
//        incomingAmt = 0
560
//        outgoingAmt !=0
561
//        -> Fee = 0
562
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
2✔
563
        var incomingAmt lnwire.MilliSatoshi
2✔
564
        if hopIndex == 0 {
4✔
565
                incomingAmt = r.TotalAmount
2✔
566
        } else {
4✔
567
                incomingAmt = r.Hops[hopIndex-1].AmtToForward
2✔
568
        }
2✔
569

570
        outgoingAmt := r.Hops[hopIndex].AmtToForward
2✔
571

2✔
572
        switch {
2✔
573
        // If both incoming and outgoing amounts are set, we're in a normal
574
        // hop
575
        case incomingAmt != 0 && outgoingAmt != 0:
2✔
576
                return incomingAmt - outgoingAmt
2✔
577

578
        // If the incoming amount is zero, we're at an intermediate hop in
579
        // a blinded route, so the fee is zero.
580
        case incomingAmt == 0:
2✔
581
                return 0
2✔
582

583
        // If we have a non-zero incoming amount and a zero outgoing amount,
584
        // we're at the introduction hop so we express the fees for the full
585
        // blinded route at this hop.
586
        default:
2✔
587
                return incomingAmt - r.ReceiverAmt()
2✔
588
        }
589
}
590

591
// TotalFees is the sum of the fees paid at each hop within the final route. In
592
// the case of a one-hop payment, this value will be zero as we don't need to
593
// pay a fee to ourself.
594
func (r *Route) TotalFees() lnwire.MilliSatoshi {
2✔
595
        if len(r.Hops) == 0 {
2✔
UNCOV
596
                return 0
×
UNCOV
597
        }
×
598

599
        return r.TotalAmount - r.ReceiverAmt()
2✔
600
}
601

602
// ReceiverAmt is the amount received by the final hop of this route.
603
func (r *Route) ReceiverAmt() lnwire.MilliSatoshi {
2✔
604
        if len(r.Hops) == 0 {
2✔
UNCOV
605
                return 0
×
UNCOV
606
        }
×
607

608
        return r.Hops[len(r.Hops)-1].AmtToForward
2✔
609
}
610

611
// FinalHop returns the last hop of the route, or nil if the route is empty.
612
func (r *Route) FinalHop() *Hop {
2✔
613
        if len(r.Hops) == 0 {
2✔
614
                return nil
×
615
        }
×
616

617
        return r.Hops[len(r.Hops)-1]
2✔
618
}
619

620
// NewRouteFromHops creates a new Route structure from the minimally required
621
// information to perform the payment. It infers fee amounts and populates the
622
// node, chan and prev/next hop maps.
623
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
624
        sourceVertex Vertex, hops []*Hop) (*Route, error) {
2✔
625

2✔
626
        if len(hops) == 0 {
2✔
UNCOV
627
                return nil, ErrNoRouteHopsProvided
×
UNCOV
628
        }
×
629

630
        // First, we'll create a route struct and populate it with the fields
631
        // for which the values are provided as arguments of this function.
632
        // TotalFees is determined based on the difference between the amount
633
        // that is send from the source and the final amount that is received
634
        // by the destination.
635
        route := &Route{
2✔
636
                SourcePubKey:  sourceVertex,
2✔
637
                Hops:          hops,
2✔
638
                TotalTimeLock: timeLock,
2✔
639
                TotalAmount:   amtToSend,
2✔
640
        }
2✔
641

2✔
642
        return route, nil
2✔
643
}
644

645
// ToSphinxPath converts a complete route into a sphinx PaymentPath that
646
// contains the per-hop payloads used to encoding the HTLC routing data for each
647
// hop in the route. This method also accepts an optional EOB payload for the
648
// final hop.
649
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
2✔
650
        var path sphinx.PaymentPath
2✔
651

2✔
652
        // We can only construct a route if there are hops provided.
2✔
653
        if len(r.Hops) == 0 {
2✔
UNCOV
654
                return nil, ErrNoRouteHopsProvided
×
UNCOV
655
        }
×
656

657
        // Check maximum route length.
658
        if len(r.Hops) > sphinx.NumMaxHops {
2✔
UNCOV
659
                return nil, ErrMaxRouteHopsExceeded
×
UNCOV
660
        }
×
661

662
        // For each hop encoded within the route, we'll convert the hop struct
663
        // to an OnionHop with matching per-hop payload within the path as used
664
        // by the sphinx package.
665
        for i, hop := range r.Hops {
4✔
666
                pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
2✔
667
                if err != nil {
2✔
668
                        return nil, err
×
669
                }
×
670

671
                // As a base case, the next hop is set to all zeroes in order
672
                // to indicate that the "last hop" as no further hops after it.
673
                nextHop := uint64(0)
2✔
674

2✔
675
                // If we aren't on the last hop, then we set the "next address"
2✔
676
                // field to be the channel that directly follows it.
2✔
677
                finalHop := i == len(r.Hops)-1
2✔
678
                if !finalHop {
4✔
679
                        nextHop = r.Hops[i+1].ChannelID
2✔
680
                }
2✔
681

682
                var payload sphinx.HopPayload
2✔
683

2✔
684
                // If this is the legacy payload, then we can just include the
2✔
685
                // hop data as normal.
2✔
686
                if hop.LegacyPayload {
2✔
UNCOV
687
                        // Before we encode this value, we'll pack the next hop
×
UNCOV
688
                        // into the NextAddress field of the hop info to ensure
×
UNCOV
689
                        // we point to the right now.
×
UNCOV
690
                        hopData := sphinx.HopData{
×
UNCOV
691
                                ForwardAmount: uint64(hop.AmtToForward),
×
UNCOV
692
                                OutgoingCltv:  hop.OutgoingTimeLock,
×
UNCOV
693
                        }
×
UNCOV
694
                        binary.BigEndian.PutUint64(
×
UNCOV
695
                                hopData.NextAddress[:], nextHop,
×
UNCOV
696
                        )
×
UNCOV
697

×
UNCOV
698
                        payload, err = sphinx.NewLegacyHopPayload(&hopData)
×
UNCOV
699
                        if err != nil {
×
700
                                return nil, err
×
701
                        }
×
702
                } else {
2✔
703
                        // For non-legacy payloads, we'll need to pack the
2✔
704
                        // routing information, along with any extra TLV
2✔
705
                        // information into the new per-hop payload format.
2✔
706
                        // We'll also pass in the chan ID of the hop this
2✔
707
                        // channel should be forwarded to so we can construct a
2✔
708
                        // valid payload.
2✔
709
                        var b bytes.Buffer
2✔
710
                        err := hop.PackHopPayload(&b, nextHop, finalHop)
2✔
711
                        if err != nil {
2✔
712
                                return nil, err
×
713
                        }
×
714

715
                        payload, err = sphinx.NewTLVHopPayload(b.Bytes())
2✔
716
                        if err != nil {
2✔
717
                                return nil, err
×
718
                        }
×
719
                }
720

721
                path[i] = sphinx.OnionHop{
2✔
722
                        NodePub:    *pub,
2✔
723
                        HopPayload: payload,
2✔
724
                }
2✔
725
        }
726

727
        return &path, nil
2✔
728
}
729

730
// String returns a human readable representation of the route.
731
func (r *Route) String() string {
2✔
732
        var b strings.Builder
2✔
733

2✔
734
        amt := r.TotalAmount
2✔
735
        for i, hop := range r.Hops {
4✔
736
                if i > 0 {
4✔
737
                        b.WriteString(" -> ")
2✔
738
                }
2✔
739
                b.WriteString(fmt.Sprintf("%v (%v)",
2✔
740
                        strconv.FormatUint(hop.ChannelID, 10),
2✔
741
                        amt,
2✔
742
                ))
2✔
743
                amt = hop.AmtToForward
2✔
744
        }
745

746
        return fmt.Sprintf("%v, cltv %v",
2✔
747
                b.String(), r.TotalTimeLock,
2✔
748
        )
2✔
749
}
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