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

lightningnetwork / lnd / 13566028875

27 Feb 2025 12:09PM UTC coverage: 49.396% (-9.4%) from 58.748%
13566028875

Pull #9555

github

ellemouton
graph/db: populate the graph cache in Start instead of during construction

In this commit, we move the graph cache population logic out of the
ChannelGraph constructor and into its Start method instead.
Pull Request #9555: graph: extract cache from CRUD [6]

34 of 54 new or added lines in 4 files covered. (62.96%)

27464 existing lines in 436 files now uncovered.

101095 of 204664 relevant lines covered (49.4%)

1.54 hits per line

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

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

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

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

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

88
        return NewVertexFromBytes(vertex)
3✔
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 {
3✔
94
        return fmt.Sprintf("%x", v[:])
3✔
95
}
3✔
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 {
3✔
100
        return tlv.MakeStaticRecord(
3✔
101
                0, v, VertexSize, encodeVertex, decodeVertex,
3✔
102
        )
3✔
103
}
3✔
104

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

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

114
func decodeVertex(r io.Reader, val interface{}, _ *[8]byte, l uint64) error {
3✔
115
        if b, ok := val.(*Vertex); ok {
6✔
116
                _, err := io.ReadFull(r, b[:])
3✔
117
                return err
3✔
118
        }
3✔
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.
UNCOV
189
func (h *Hop) Copy() *Hop {
×
UNCOV
190
        c := *h
×
UNCOV
191

×
UNCOV
192
        if h.MPP != nil {
×
UNCOV
193
                m := *h.MPP
×
UNCOV
194
                c.MPP = &m
×
UNCOV
195
        }
×
196

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

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

UNCOV
207
        return &c
×
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 {
3✔
221

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

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

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

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

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

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

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

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

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

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

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

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

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

345
        return tlvStream.Encode(w)
3✔
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 {
3✔
355
        switch {
3✔
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:
3✔
363
                return nil
3✔
364

365
        // In a blinded route the final hop is expected to have TLV values set.
UNCOV
366
        case finalHop && isZero:
×
UNCOV
367
                return ErrMissingField
×
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
3✔
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 {
3✔
382
        switch {
3✔
383
        // Hops in a blinded route should not have a next channel ID set.
UNCOV
384
        case isBlinded && nextChanIDIsSet:
×
UNCOV
385
                return ErrUnexpectedField
×
386

387
        // Otherwise, blinded hops are allowed to have a zero value.
388
        case isBlinded:
3✔
389
                return nil
3✔
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:
3✔
400
                return nil
3✔
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 {
3✔
407
        if h.LegacyPayload {
3✔
UNCOV
408
                return sphinx.LegacyHopDataSize
×
UNCOV
409
        }
×
410

411
        var payloadSize uint64
3✔
412

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

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

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

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

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

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

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

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

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

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

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

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

3✔
485
        return payloadSize
3✔
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.
UNCOV
540
func (r *Route) Copy() *Route {
×
UNCOV
541
        c := *r
×
UNCOV
542

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

UNCOV
548
        return &c
×
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 {
3✔
589
        var incomingAmt lnwire.MilliSatoshi
3✔
590
        if hopIndex == 0 {
6✔
591
                incomingAmt = r.TotalAmount
3✔
592
        } else {
6✔
593
                incomingAmt = r.Hops[hopIndex-1].AmtToForward
3✔
594
        }
3✔
595

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

3✔
598
        switch {
3✔
599
        // If both incoming and outgoing amounts are set, we're in a normal
600
        // hop
601
        case incomingAmt != 0 && outgoingAmt != 0:
3✔
602
                return incomingAmt - outgoingAmt
3✔
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:
3✔
607
                return 0
3✔
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:
3✔
613
                return incomingAmt - r.ReceiverAmt()
3✔
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 {
3✔
621
        if len(r.Hops) == 0 {
3✔
UNCOV
622
                return 0
×
UNCOV
623
        }
×
624

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

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

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

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

643
        return r.Hops[len(r.Hops)-1]
3✔
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) {
3✔
651

3✔
652
        if len(hops) == 0 {
3✔
UNCOV
653
                return nil, ErrNoRouteHopsProvided
×
UNCOV
654
        }
×
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{
3✔
662
                SourcePubKey:  sourceVertex,
3✔
663
                Hops:          hops,
3✔
664
                TotalTimeLock: timeLock,
3✔
665
                TotalAmount:   amtToSend,
3✔
666
        }
3✔
667

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

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

683
        // Check maximum route length.
684
        if len(r.Hops) > sphinx.NumMaxHops {
3✔
UNCOV
685
                return nil, ErrMaxRouteHopsExceeded
×
UNCOV
686
        }
×
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 {
6✔
692
                pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
3✔
693
                if err != nil {
3✔
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)
3✔
700

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

708
                var payload sphinx.HopPayload
3✔
709

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

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

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

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

753
        return &path, nil
3✔
754
}
755

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

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

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