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

lightningnetwork / lnd / 15951470896

29 Jun 2025 04:23AM UTC coverage: 67.594% (-0.01%) from 67.606%
15951470896

Pull #9751

github

web-flow
Merge 599d9b051 into 6290edf14
Pull Request #9751: multi: update Go to 1.23.10 and update some packages

135088 of 199851 relevant lines covered (67.59%)

21909.44 hits per line

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

91.08
/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 {
264✔
56
        var v Vertex
264✔
57
        copy(v[:], pub.SerializeCompressed())
264✔
58
        return v
264✔
59
}
264✔
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,108✔
64
        vertexLen := len(b)
1,108✔
65
        if vertexLen != VertexSize {
1,110✔
66
                return Vertex{}, fmt.Errorf("invalid vertex length of %v, "+
2✔
67
                        "want %v", vertexLen, VertexSize)
2✔
68
        }
2✔
69

70
        var v Vertex
1,106✔
71
        copy(v[:], b)
1,106✔
72
        return v, nil
1,106✔
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,110✔
94
        return fmt.Sprintf("%x", v[:])
1,110✔
95
}
1,110✔
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 {
312✔
221

312✔
222
        // If this is a legacy payload, then we'll exit here as this method
312✔
223
        // shouldn't be called.
312✔
224
        if h.LegacyPayload {
312✔
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
312✔
232

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

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

247
        if err := optionalBlindedField(
308✔
248
                h.OutgoingTimeLock == 0, isBlinded, finalHop,
308✔
249
        ); err != nil {
311✔
250
                return fmt.Errorf("%w: outgoing timelock: %v", err,
3✔
251
                        h.OutgoingTimeLock)
3✔
252
        }
3✔
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)
305✔
257
        if amt != 0 {
603✔
258
                records = append(
298✔
259
                        records, record.NewAmtToFwdRecord(&amt),
298✔
260
                )
298✔
261
        }
298✔
262

263
        if h.OutgoingTimeLock != 0 {
603✔
264
                records = append(
298✔
265
                        records, record.NewLockTimeRecord(&h.OutgoingTimeLock),
298✔
266
                )
298✔
267
        }
298✔
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)
305✔
272
        if err != nil {
307✔
273
                return fmt.Errorf("%w: channel id: %v", err, nextChanID)
2✔
274
        }
2✔
275

276
        if nextChanID != 0 {
343✔
277
                records = append(records,
40✔
278
                        record.NewNextHopIDRecord(&nextChanID),
40✔
279
                )
40✔
280
        }
40✔
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 {
478✔
286
                if finalHop {
349✔
287
                        records = append(records, h.MPP.Record())
174✔
288
                } else {
175✔
289
                        return ErrIntermediateMPPHop
1✔
290
                }
1✔
291
        }
292

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

300
        if h.BlindingPoint != nil {
311✔
301
                records = append(records, record.NewBlindingPointRecord(
9✔
302
                        &h.BlindingPoint,
9✔
303
                ))
9✔
304
        }
9✔
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 {
311✔
311
                if h.MPP != nil {
15✔
312
                        records = append(records, h.AMP.Record())
6✔
313
                } else {
9✔
314
                        return ErrAMPMissingMPP
3✔
315
                }
3✔
316
        }
317

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

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

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

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

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

345
        return tlvStream.Encode(w)
299✔
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 {
617✔
355
        switch {
617✔
356
        // We are not in a blinded route and the TLV is not set when it should
357
        // be.
358
        case !blindedHop && isZero:
3✔
359
                return ErrMissingField
3✔
360

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

365
        // In a blinded route the final hop is expected to have TLV values set.
366
        case finalHop && isZero:
4✔
367
                return ErrMissingField
4✔
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
41✔
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 {
305✔
382
        switch {
305✔
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:
19✔
389
                return nil
19✔
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:
287✔
400
                return nil
287✔
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 {
476✔
621
        if len(r.Hops) == 0 {
477✔
622
                return 0
1✔
623
        }
1✔
624

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

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

634
        return r.Hops[len(r.Hops)-1].AmtToForward
1,229✔
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) {
134✔
651

134✔
652
        if len(hops) == 0 {
136✔
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{
132✔
662
                SourcePubKey:  sourceVertex,
132✔
663
                Hops:          hops,
132✔
664
                TotalTimeLock: timeLock,
132✔
665
                TotalAmount:   amtToSend,
132✔
666
        }
132✔
667

132✔
668
        return route, nil
132✔
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) {
227✔
676
        var path sphinx.PaymentPath
227✔
677

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

683
        // Check maximum route length.
684
        if len(r.Hops) > sphinx.NumMaxHops {
227✔
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 {
673✔
692
                pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
448✔
693
                if err != nil {
448✔
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)
448✔
700

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

708
                var payload sphinx.HopPayload
448✔
709

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

214✔
724
                        payload, err = sphinx.NewLegacyHopPayload(&hopData)
214✔
725
                        if err != nil {
214✔
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{
448✔
748
                        NodePub:    *pub,
448✔
749
                        HopPayload: payload,
448✔
750
                }
448✔
751
        }
752

753
        return &path, nil
225✔
754
}
755

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

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

772
        return fmt.Sprintf("%v, cltv %v",
36✔
773
                b.String(), r.TotalTimeLock,
36✔
774
        )
36✔
775
}
776

777
// ChanIDString returns the route's channel IDs as a formatted string.
778
func ChanIDString(r *Route) string {
2✔
779
        var b strings.Builder
2✔
780

2✔
781
        for i, hop := range r.Hops {
6✔
782
                b.WriteString(fmt.Sprintf("%v",
4✔
783
                        strconv.FormatUint(hop.ChannelID, 10),
4✔
784
                ))
4✔
785
                if i != len(r.Hops)-1 {
6✔
786
                        b.WriteString(" -> ")
2✔
787
                }
2✔
788
        }
789

790
        return b.String()
2✔
791
}
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