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

lightningnetwork / lnd / 13586005509

28 Feb 2025 10:14AM UTC coverage: 68.629% (+9.9%) from 58.77%
13586005509

Pull #9521

github

web-flow
Merge 37d3a70a5 into 8532955b3
Pull Request #9521: unit: remove GOACC, use Go 1.20 native coverage functionality

129950 of 189351 relevant lines covered (68.63%)

23726.46 hits per line

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

89.46
/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 {
256✔
56
        var v Vertex
256✔
57
        copy(v[:], pub.SerializeCompressed())
256✔
58
        return v
256✔
59
}
256✔
60

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

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

75
// NewVertexFromStr returns a new Vertex given its hex-encoded string format.
76
func NewVertexFromStr(v string) (Vertex, error) {
10✔
77
        // Return error if hex string is of incorrect length.
10✔
78
        if len(v) != VertexSize*2 {
10✔
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)
10✔
84
        if err != nil {
10✔
85
                return Vertex{}, err
×
86
        }
×
87

88
        return NewVertexFromBytes(vertex)
10✔
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 {
1,105✔
94
        return fmt.Sprintf("%x", v[:])
1,105✔
95
}
1,105✔
96

97
// Record returns a TLV record that can be used to encode/decode a Vertex
98
// to/from a TLV stream.
99
func (v *Vertex) Record() tlv.Record {
147✔
100
        return tlv.MakeStaticRecord(
147✔
101
                0, v, VertexSize, encodeVertex, decodeVertex,
147✔
102
        )
147✔
103
}
147✔
104

105
func encodeVertex(w io.Writer, val interface{}, _ *[8]byte) error {
97✔
106
        if b, ok := val.(*Vertex); ok {
194✔
107
                _, err := w.Write(b[:])
97✔
108
                return err
97✔
109
        }
97✔
110

111
        return tlv.NewTypeForEncodingErr(val, "Vertex")
×
112
}
113

114
func decodeVertex(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
53✔
115
        if b, ok := val.(*Vertex); ok {
106✔
116
                _, err := io.ReadFull(r, b[:])
53✔
117
                return err
53✔
118
        }
53✔
119

120
        return tlv.NewTypeForDecodingErr(val, "Vertex", l, VertexSize)
×
121
}
122

123
// Hop represents an intermediate or final node of the route. This naming
124
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
125
// The struct houses the channel along which this hop can be reached and
126
// the values necessary to create the HTLC that needs to be sent to the
127
// next hop. It is also used to encode the per-hop payload included within
128
// the Sphinx packet.
129
type Hop struct {
130
        // PubKeyBytes is the raw bytes of the public key of the target node.
131
        PubKeyBytes Vertex
132

133
        // ChannelID is the unique channel ID for the channel. The first 3
134
        // bytes are the block height, the next 3 the index within the block,
135
        // and the last 2 bytes are the output index for the channel.
136
        ChannelID uint64
137

138
        // OutgoingTimeLock is the timelock value that should be used when
139
        // crafting the _outgoing_ HTLC from this hop.
140
        OutgoingTimeLock uint32
141

142
        // AmtToForward is the amount that this hop will forward to the next
143
        // hop. This value is less than the value that the incoming HTLC
144
        // carries as a fee will be subtracted by the hop.
145
        AmtToForward lnwire.MilliSatoshi
146

147
        // MPP encapsulates the data required for option_mpp. This field should
148
        // only be set for the final hop.
149
        MPP *record.MPP
150

151
        // AMP encapsulates the data required for option_amp. This field should
152
        // only be set for the final hop.
153
        AMP *record.AMP
154

155
        // CustomRecords if non-nil are a set of additional TLV records that
156
        // should be included in the forwarding instructions for this node.
157
        CustomRecords record.CustomSet
158

159
        // LegacyPayload if true, then this signals that this node doesn't
160
        // understand the new TLV payload, so we must instead use the legacy
161
        // payload.
162
        //
163
        // NOTE: we should no longer ever create a Hop with Legacy set to true.
164
        // The only reason we are keeping this member is that it could be the
165
        // case that we have serialised hops persisted to disk where
166
        // LegacyPayload is true.
167
        LegacyPayload bool
168

169
        // Metadata is additional data that is sent along with the payment to
170
        // the payee.
171
        Metadata []byte
172

173
        // EncryptedData is an encrypted data blob includes for hops that are
174
        // part of a blinded route.
175
        EncryptedData []byte
176

177
        // BlindingPoint is an ephemeral public key used by introduction nodes
178
        // in blinded routes to unblind their portion of the route and pass on
179
        // the next ephemeral key to the next blinded node to do the same.
180
        BlindingPoint *btcec.PublicKey
181

182
        // TotalAmtMsat is the total amount for a blinded payment, potentially
183
        // spread over more than one HTLC. This field should only be set for
184
        // the final hop in a blinded path.
185
        TotalAmtMsat lnwire.MilliSatoshi
186
}
187

188
// Copy returns a deep copy of the Hop.
189
func (h *Hop) Copy() *Hop {
316✔
190
        c := *h
316✔
191

316✔
192
        if h.MPP != nil {
474✔
193
                m := *h.MPP
158✔
194
                c.MPP = &m
158✔
195
        }
158✔
196

197
        if h.AMP != nil {
316✔
198
                a := *h.AMP
×
199
                c.AMP = &a
×
200
        }
×
201

202
        if h.BlindingPoint != nil {
316✔
203
                b := *h.BlindingPoint
×
204
                c.BlindingPoint = &b
×
205
        }
×
206

207
        return &c
316✔
208
}
209

210
// PackHopPayload writes to the passed io.Writer, the series of byes that can
211
// be placed directly into the per-hop payload (EOB) for this hop. This will
212
// include the required routing fields, as well as serializing any of the
213
// passed optional TLVRecords. nextChanID is the unique channel ID that
214
// references the _outgoing_ channel ID that follows this hop. The lastHop bool
215
// is used to signal whether this hop is the final hop in a route. Previously,
216
// a zero nextChanID would be used for this purpose, but with the addition of
217
// blinded routes which allow zero nextChanID values for intermediate hops we
218
// add an explicit signal.
219
func (h *Hop) PackHopPayload(w io.Writer, nextChanID uint64,
220
        finalHop bool) error {
253✔
221

253✔
222
        // If this is a legacy payload, then we'll exit here as this method
253✔
223
        // shouldn't be called.
253✔
224
        if h.LegacyPayload {
253✔
225
                return fmt.Errorf("cannot pack hop payloads for legacy " +
×
226
                        "payloads")
×
227
        }
×
228

229
        // Otherwise, we'll need to make a new stream that includes our
230
        // required routing fields, as well as these optional values.
231
        var records []tlv.Record
253✔
232

253✔
233
        // Hops that are not part of a blinded path will have an amount and
253✔
234
        // a CLTV expiry field. In a blinded route (where encrypted data is
253✔
235
        // non-nil), these values may be omitted for intermediate nodes.
253✔
236
        // Validate these fields against the structure of the payload so that
253✔
237
        // we know they're included (or excluded) correctly.
253✔
238
        isBlinded := h.EncryptedData != nil
253✔
239

253✔
240
        if err := optionalBlindedField(
253✔
241
                h.AmtToForward == 0, isBlinded, finalHop,
253✔
242
        ); err != nil {
254✔
243
                return fmt.Errorf("%w: amount to forward: %v", err,
1✔
244
                        h.AmtToForward)
1✔
245
        }
1✔
246

247
        if err := optionalBlindedField(
252✔
248
                h.OutgoingTimeLock == 0, isBlinded, finalHop,
252✔
249
        ); err != nil {
253✔
250
                return fmt.Errorf("%w: outgoing timelock: %v", err,
1✔
251
                        h.OutgoingTimeLock)
1✔
252
        }
1✔
253

254
        // Once we've validated that these TLVs are set as we expect, we can
255
        // go ahead and include them if non-zero.
256
        amt := uint64(h.AmtToForward)
251✔
257
        if amt != 0 {
495✔
258
                records = append(
244✔
259
                        records, record.NewAmtToFwdRecord(&amt),
244✔
260
                )
244✔
261
        }
244✔
262

263
        if h.OutgoingTimeLock != 0 {
495✔
264
                records = append(
244✔
265
                        records, record.NewLockTimeRecord(&h.OutgoingTimeLock),
244✔
266
                )
244✔
267
        }
244✔
268

269
        // Validate channel TLV is present as expected based on location in
270
        // route and whether this hop is blinded.
271
        err := validateNextChanID(nextChanID != 0, isBlinded, finalHop)
251✔
272
        if err != nil {
253✔
273
                return fmt.Errorf("%w: channel id: %v", err, nextChanID)
2✔
274
        }
2✔
275

276
        if nextChanID != 0 {
288✔
277
                records = append(records,
39✔
278
                        record.NewNextHopIDRecord(&nextChanID),
39✔
279
                )
39✔
280
        }
39✔
281

282
        // If an MPP record is destined for this hop, ensure that we only ever
283
        // attach it to the final hop. Otherwise the route was constructed
284
        // incorrectly.
285
        if h.MPP != nil {
421✔
286
                if finalHop {
343✔
287
                        records = append(records, h.MPP.Record())
171✔
288
                } else {
172✔
289
                        return ErrIntermediateMPPHop
1✔
290
                }
1✔
291
        }
292

293
        // Add encrypted data and blinding point if present.
294
        if h.EncryptedData != nil {
262✔
295
                records = append(records, record.NewEncryptedDataRecord(
14✔
296
                        &h.EncryptedData,
14✔
297
                ))
14✔
298
        }
14✔
299

300
        if h.BlindingPoint != nil {
256✔
301
                records = append(records, record.NewBlindingPointRecord(
8✔
302
                        &h.BlindingPoint,
8✔
303
                ))
8✔
304
        }
8✔
305

306
        // If an AMP record is destined for this hop, ensure that we only ever
307
        // attach it if we also have an MPP record. We can infer that this is
308
        // already a final hop if MPP is non-nil otherwise we would have exited
309
        // above.
310
        if h.AMP != nil {
256✔
311
                if h.MPP != nil {
14✔
312
                        records = append(records, h.AMP.Record())
6✔
313
                } else {
8✔
314
                        return ErrAMPMissingMPP
2✔
315
                }
2✔
316
        }
317

318
        // If metadata is specified, generate a tlv record for it.
319
        if h.Metadata != nil {
409✔
320
                records = append(records,
163✔
321
                        record.NewMetadataRecord(&h.Metadata),
163✔
322
                )
163✔
323
        }
163✔
324

325
        if h.TotalAmtMsat != 0 {
250✔
326
                totalAmtInt := uint64(h.TotalAmtMsat)
4✔
327
                records = append(records,
4✔
328
                        record.NewTotalAmtMsatBlinded(&totalAmtInt),
4✔
329
                )
4✔
330
        }
4✔
331

332
        // Append any custom types destined for this hop.
333
        tlvRecords := tlv.MapToRecords(h.CustomRecords)
246✔
334
        records = append(records, tlvRecords...)
246✔
335

246✔
336
        // To ensure we produce a canonical stream, we'll sort the records
246✔
337
        // before encoding them as a stream in the hop payload.
246✔
338
        tlv.SortRecords(records)
246✔
339

246✔
340
        tlvStream, err := tlv.NewStream(records...)
246✔
341
        if err != nil {
246✔
342
                return err
×
343
        }
×
344

345
        return tlvStream.Encode(w)
246✔
346
}
347

348
// optionalBlindedField validates fields that we expect to be non-zero for all
349
// hops in a regular route, but may be zero for intermediate nodes in a blinded
350
// route. It will validate the following cases:
351
// - Not blinded: require non-zero values.
352
// - Intermediate blinded node: require zero values.
353
// - Final blinded node: require non-zero values.
354
func optionalBlindedField(isZero, blindedHop, finalHop bool) error {
502✔
355
        switch {
502✔
356
        // We are not in a blinded route and the TLV is not set when it should
357
        // be.
358
        case !blindedHop && isZero:
×
359
                return ErrMissingField
×
360

361
        // We are not in a blinded route and the TLV is set as expected.
362
        case !blindedHop:
473✔
363
                return nil
473✔
364

365
        // In a blinded route the final hop is expected to have TLV values set.
366
        case finalHop && isZero:
2✔
367
                return ErrMissingField
2✔
368

369
        // In an intermediate hop in a blinded route and the field is not zero.
370
        case !finalHop && !isZero:
×
371
                return ErrUnexpectedField
×
372
        }
373

374
        return nil
30✔
375
}
376

377
// validateNextChanID validates the presence of the nextChanID TLV field in
378
// a payload. For regular payments, it is expected to be present for all hops
379
// except the final hop. For blinded paths, it is not expected to be included
380
// at all (as this value is provided in encrypted data).
381
func validateNextChanID(nextChanIDIsSet, isBlinded, finalHop bool) error {
251✔
382
        switch {
251✔
383
        // Hops in a blinded route should not have a next channel ID set.
384
        case isBlinded && nextChanIDIsSet:
2✔
385
                return ErrUnexpectedField
2✔
386

387
        // Otherwise, blinded hops are allowed to have a zero value.
388
        case isBlinded:
14✔
389
                return nil
14✔
390

391
        // The final hop in a regular route is expected to have a zero value.
392
        case finalHop && nextChanIDIsSet:
×
393
                return ErrUnexpectedField
×
394

395
        // Intermediate hops in regular routes require non-zero value.
396
        case !finalHop && !nextChanIDIsSet:
×
397
                return ErrMissingField
×
398

399
        default:
238✔
400
                return nil
238✔
401
        }
402
}
403

404
// PayloadSize returns the total size this hop's payload would take up in the
405
// onion packet.
406
func (h *Hop) PayloadSize(nextChanID uint64) uint64 {
827✔
407
        if h.LegacyPayload {
829✔
408
                return sphinx.LegacyHopDataSize
2✔
409
        }
2✔
410

411
        var payloadSize uint64
825✔
412

825✔
413
        addRecord := func(tlvType tlv.Type, length uint64) {
2,962✔
414
                payloadSize += tlv.VarIntSize(uint64(tlvType)) +
2,137✔
415
                        tlv.VarIntSize(length) + length
2,137✔
416
        }
2,137✔
417

418
        // Add amount size.
419
        if h.AmtToForward != 0 {
1,636✔
420
                addRecord(record.AmtOnionType, tlv.SizeTUint64(
811✔
421
                        uint64(h.AmtToForward),
811✔
422
                ))
811✔
423
        }
811✔
424
        // Add lock time size.
425
        if h.OutgoingTimeLock != 0 {
1,424✔
426
                addRecord(
599✔
427
                        record.LockTimeOnionType,
599✔
428
                        tlv.SizeTUint64(uint64(h.OutgoingTimeLock)),
599✔
429
                )
599✔
430
        }
599✔
431

432
        // Add next hop if present.
433
        if nextChanID != 0 {
1,455✔
434
                addRecord(record.NextHopOnionType, 8)
630✔
435
        }
630✔
436

437
        // Add mpp if present.
438
        if h.MPP != nil {
880✔
439
                addRecord(record.MPPOnionType, h.MPP.PayloadSize())
55✔
440
        }
55✔
441

442
        // Add amp if present.
443
        if h.AMP != nil {
830✔
444
                addRecord(record.AMPOnionType, h.AMP.PayloadSize())
5✔
445
        }
5✔
446

447
        // Add encrypted data and blinding point if present.
448
        if h.EncryptedData != nil {
845✔
449
                addRecord(
20✔
450
                        record.EncryptedDataOnionType,
20✔
451
                        uint64(len(h.EncryptedData)),
20✔
452
                )
20✔
453
        }
20✔
454

455
        if h.BlindingPoint != nil {
841✔
456
                addRecord(
16✔
457
                        record.BlindingPointOnionType,
16✔
458
                        btcec.PubKeyBytesLenCompressed,
16✔
459
                )
16✔
460
        }
16✔
461

462
        // Add metadata if present.
463
        if h.Metadata != nil {
832✔
464
                addRecord(record.MetadataOnionType, uint64(len(h.Metadata)))
7✔
465
        }
7✔
466

467
        if h.TotalAmtMsat != 0 {
826✔
468
                addRecord(
1✔
469
                        record.TotalAmtMsatBlindedType,
1✔
470
                        tlv.SizeTUint64(uint64(h.AmtToForward)),
1✔
471
                )
1✔
472
        }
1✔
473

474
        // Add custom records.
475
        for k, v := range h.CustomRecords {
839✔
476
                addRecord(tlv.Type(k), uint64(len(v)))
14✔
477
        }
14✔
478

479
        // Add the size required to encode the payload length.
480
        payloadSize += tlv.VarIntSize(payloadSize)
825✔
481

825✔
482
        // Add HMAC.
825✔
483
        payloadSize += sphinx.HMACSize
825✔
484

825✔
485
        return payloadSize
825✔
486
}
487

488
// Route represents a path through the channel graph which runs over one or
489
// more channels in succession. This struct carries all the information
490
// required to craft the Sphinx onion packet, and send the payment along the
491
// first hop in the path. A route is only selected as valid if all the channels
492
// have sufficient capacity to carry the initial payment amount after fees are
493
// accounted for.
494
type Route struct {
495
        // TotalTimeLock is the cumulative (final) time lock across the entire
496
        // route. This is the CLTV value that should be extended to the first
497
        // hop in the route. All other hops will decrement the time-lock as
498
        // advertised, leaving enough time for all hops to wait for or present
499
        // the payment preimage to complete the payment.
500
        TotalTimeLock uint32
501

502
        // TotalAmount is the total amount of funds required to complete a
503
        // payment over this route. This value includes the cumulative fees at
504
        // each hop. As a result, the HTLC extended to the first-hop in the
505
        // route will need to have at least this many satoshis, otherwise the
506
        // route will fail at an intermediate node due to an insufficient
507
        // amount of fees.
508
        TotalAmount lnwire.MilliSatoshi
509

510
        // SourcePubKey is the pubkey of the node where this route originates
511
        // from.
512
        SourcePubKey Vertex
513

514
        // Hops contains details concerning the specific forwarding details at
515
        // each hop.
516
        Hops []*Hop
517

518
        // FirstHopAmount is the amount that should actually be sent to the
519
        // first hop in the route. This is only different from TotalAmount above
520
        // for custom channels where the on-chain amount doesn't necessarily
521
        // reflect all the value of an outgoing payment.
522
        FirstHopAmount tlv.RecordT[
523
                tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
524
        ]
525

526
        // FirstHopWireCustomRecords is a set of custom records that should be
527
        // included in the wire message sent to the first hop. This is only set
528
        // on custom channels and is used to include additional information
529
        // about the actual value of the payment.
530
        //
531
        // NOTE: Since these records already represent TLV records, and we
532
        // enforce them to be in the custom range (e.g. >= 65536), we don't use
533
        // another parent record type here. Instead, when serializing the Route
534
        // we merge the TLV records together with the custom records and encode
535
        // everything as a single TLV stream.
536
        FirstHopWireCustomRecords lnwire.CustomRecords
537
}
538

539
// Copy returns a deep copy of the Route.
540
func (r *Route) Copy() *Route {
158✔
541
        c := *r
158✔
542

158✔
543
        c.Hops = make([]*Hop, len(r.Hops))
158✔
544
        for i := range r.Hops {
474✔
545
                c.Hops[i] = r.Hops[i].Copy()
316✔
546
        }
316✔
547

548
        return &c
158✔
549
}
550

551
// HopFee returns the fee charged by the route hop indicated by hopIndex.
552
//
553
// This calculation takes into account the possibility that the route contains
554
// some blinded hops, that will not have the amount to forward set. We take
555
// note of various points in the blinded route.
556
//
557
// Given the following route where Carol is the introduction node and B2 is
558
// the recipient, Carol and B1's hops will not have an amount to forward set:
559
// Alice --- Bob ---- Carol (introduction) ----- B1 ----- B2
560
//
561
// We locate ourselves in the route as follows:
562
// * Regular Hop (eg Alice - Bob):
563
//
564
//        incomingAmt !=0
565
//        outgoingAmt !=0
566
//        ->  Fee = incomingAmt - outgoingAmt
567
//
568
// * Introduction Hop (eg Bob - Carol):
569
//
570
//        incomingAmt !=0
571
//        outgoingAmt = 0
572
//        -> Fee = incomingAmt - receiverAmt
573
//
574
// This has the impact of attributing the full fees for the blinded route to
575
// the introduction node.
576
//
577
// * Blinded Intermediate Hop (eg Carol - B1):
578
//
579
//        incomingAmt = 0
580
//        outgoingAmt = 0
581
//        -> Fee = 0
582
//
583
// * Final Blinded Hop (B1 - B2):
584
//
585
//        incomingAmt = 0
586
//        outgoingAmt !=0
587
//        -> Fee = 0
588
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
49✔
589
        var incomingAmt lnwire.MilliSatoshi
49✔
590
        if hopIndex == 0 {
74✔
591
                incomingAmt = r.TotalAmount
25✔
592
        } else {
52✔
593
                incomingAmt = r.Hops[hopIndex-1].AmtToForward
27✔
594
        }
27✔
595

596
        outgoingAmt := r.Hops[hopIndex].AmtToForward
49✔
597

49✔
598
        switch {
49✔
599
        // If both incoming and outgoing amounts are set, we're in a normal
600
        // hop
601
        case incomingAmt != 0 && outgoingAmt != 0:
43✔
602
                return incomingAmt - outgoingAmt
43✔
603

604
        // If the incoming amount is zero, we're at an intermediate hop in
605
        // a blinded route, so the fee is zero.
606
        case incomingAmt == 0:
5✔
607
                return 0
5✔
608

609
        // If we have a non-zero incoming amount and a zero outgoing amount,
610
        // we're at the introduction hop so we express the fees for the full
611
        // blinded route at this hop.
612
        default:
7✔
613
                return incomingAmt - r.ReceiverAmt()
7✔
614
        }
615
}
616

617
// TotalFees is the sum of the fees paid at each hop within the final route. In
618
// the case of a one-hop payment, this value will be zero as we don't need to
619
// pay a fee to ourself.
620
func (r *Route) TotalFees() lnwire.MilliSatoshi {
480✔
621
        if len(r.Hops) == 0 {
481✔
622
                return 0
1✔
623
        }
1✔
624

625
        return r.TotalAmount - r.ReceiverAmt()
479✔
626
}
627

628
// ReceiverAmt is the amount received by the final hop of this route.
629
func (r *Route) ReceiverAmt() lnwire.MilliSatoshi {
1,238✔
630
        if len(r.Hops) == 0 {
1,239✔
631
                return 0
1✔
632
        }
1✔
633

634
        return r.Hops[len(r.Hops)-1].AmtToForward
1,237✔
635
}
636

637
// FinalHop returns the last hop of the route, or nil if the route is empty.
638
func (r *Route) FinalHop() *Hop {
204✔
639
        if len(r.Hops) == 0 {
204✔
640
                return nil
×
641
        }
×
642

643
        return r.Hops[len(r.Hops)-1]
204✔
644
}
645

646
// NewRouteFromHops creates a new Route structure from the minimally required
647
// information to perform the payment. It infers fee amounts and populates the
648
// node, chan and prev/next hop maps.
649
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
650
        sourceVertex Vertex, hops []*Hop) (*Route, error) {
133✔
651

133✔
652
        if len(hops) == 0 {
135✔
653
                return nil, ErrNoRouteHopsProvided
2✔
654
        }
2✔
655

656
        // First, we'll create a route struct and populate it with the fields
657
        // for which the values are provided as arguments of this function.
658
        // TotalFees is determined based on the difference between the amount
659
        // that is send from the source and the final amount that is received
660
        // by the destination.
661
        route := &Route{
131✔
662
                SourcePubKey:  sourceVertex,
131✔
663
                Hops:          hops,
131✔
664
                TotalTimeLock: timeLock,
131✔
665
                TotalAmount:   amtToSend,
131✔
666
        }
131✔
667

131✔
668
        return route, nil
131✔
669
}
670

671
// ToSphinxPath converts a complete route into a sphinx PaymentPath that
672
// contains the per-hop payloads used to encoding the HTLC routing data for each
673
// hop in the route. This method also accepts an optional EOB payload for the
674
// final hop.
675
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
226✔
676
        var path sphinx.PaymentPath
226✔
677

226✔
678
        // We can only construct a route if there are hops provided.
226✔
679
        if len(r.Hops) == 0 {
227✔
680
                return nil, ErrNoRouteHopsProvided
1✔
681
        }
1✔
682

683
        // Check maximum route length.
684
        if len(r.Hops) > sphinx.NumMaxHops {
226✔
685
                return nil, ErrMaxRouteHopsExceeded
1✔
686
        }
1✔
687

688
        // For each hop encoded within the route, we'll convert the hop struct
689
        // to an OnionHop with matching per-hop payload within the path as used
690
        // by the sphinx package.
691
        for i, hop := range r.Hops {
670✔
692
                pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
446✔
693
                if err != nil {
446✔
694
                        return nil, err
×
695
                }
×
696

697
                // As a base case, the next hop is set to all zeroes in order
698
                // to indicate that the "last hop" as no further hops after it.
699
                nextHop := uint64(0)
446✔
700

446✔
701
                // If we aren't on the last hop, then we set the "next address"
446✔
702
                // field to be the channel that directly follows it.
446✔
703
                finalHop := i == len(r.Hops)-1
446✔
704
                if !finalHop {
671✔
705
                        nextHop = r.Hops[i+1].ChannelID
225✔
706
                }
225✔
707

708
                var payload sphinx.HopPayload
446✔
709

446✔
710
                // If this is the legacy payload, then we can just include the
446✔
711
                // hop data as normal.
446✔
712
                if hop.LegacyPayload {
658✔
713
                        // Before we encode this value, we'll pack the next hop
212✔
714
                        // into the NextAddress field of the hop info to ensure
212✔
715
                        // we point to the right now.
212✔
716
                        hopData := sphinx.HopData{
212✔
717
                                ForwardAmount: uint64(hop.AmtToForward),
212✔
718
                                OutgoingCltv:  hop.OutgoingTimeLock,
212✔
719
                        }
212✔
720
                        binary.BigEndian.PutUint64(
212✔
721
                                hopData.NextAddress[:], nextHop,
212✔
722
                        )
212✔
723

212✔
724
                        payload, err = sphinx.NewLegacyHopPayload(&hopData)
212✔
725
                        if err != nil {
212✔
726
                                return nil, err
×
727
                        }
×
728
                } else {
234✔
729
                        // For non-legacy payloads, we'll need to pack the
234✔
730
                        // routing information, along with any extra TLV
234✔
731
                        // information into the new per-hop payload format.
234✔
732
                        // We'll also pass in the chan ID of the hop this
234✔
733
                        // channel should be forwarded to so we can construct a
234✔
734
                        // valid payload.
234✔
735
                        var b bytes.Buffer
234✔
736
                        err := hop.PackHopPayload(&b, nextHop, finalHop)
234✔
737
                        if err != nil {
234✔
738
                                return nil, err
×
739
                        }
×
740

741
                        payload, err = sphinx.NewTLVHopPayload(b.Bytes())
234✔
742
                        if err != nil {
234✔
743
                                return nil, err
×
744
                        }
×
745
                }
746

747
                path[i] = sphinx.OnionHop{
446✔
748
                        NodePub:    *pub,
446✔
749
                        HopPayload: payload,
446✔
750
                }
446✔
751
        }
752

753
        return &path, nil
224✔
754
}
755

756
// String returns a human readable representation of the route.
757
func (r *Route) String() string {
70✔
758
        var b strings.Builder
70✔
759

70✔
760
        amt := r.TotalAmount
70✔
761
        for i, hop := range r.Hops {
190✔
762
                if i > 0 {
173✔
763
                        b.WriteString(" -> ")
53✔
764
                }
53✔
765
                b.WriteString(fmt.Sprintf("%v (%v)",
120✔
766
                        strconv.FormatUint(hop.ChannelID, 10),
120✔
767
                        amt,
120✔
768
                ))
120✔
769
                amt = hop.AmtToForward
120✔
770
        }
771

772
        return fmt.Sprintf("%v, cltv %v",
70✔
773
                b.String(), r.TotalTimeLock,
70✔
774
        )
70✔
775
}
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