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

lightningnetwork / lnd / 13523316608

25 Feb 2025 02:12PM UTC coverage: 49.351% (-9.5%) from 58.835%
13523316608

Pull #9549

github

yyforyongyu
routing/chainview: refactor `TestFilteredChainView`

So each test has its own miner and chainView.
Pull Request #9549: Fix unit test flake `TestHistoricalConfDetailsTxIndex`

0 of 120 new or added lines in 1 file covered. (0.0%)

27196 existing lines in 434 files now uncovered.

100945 of 204543 relevant lines covered (49.35%)

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