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

lightningnetwork / lnd / 9915780197

13 Jul 2024 12:30AM UTC coverage: 49.268% (-9.1%) from 58.413%
9915780197

push

github

web-flow
Merge pull request #8653 from ProofOfKeags/fn-prim

DynComms [0/n]: `fn` package additions

92837 of 188433 relevant lines covered (49.27%)

1.55 hits per line

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

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

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

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

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

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

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

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

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

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

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

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

54
// NewVertex returns a new Vertex given a public key.
55
func NewVertex(pub *btcec.PublicKey) Vertex {
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
// Hop represents an intermediate or final node of the route. This naming
98
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
99
// The struct houses the channel along which this hop can be reached and
100
// the values necessary to create the HTLC that needs to be sent to the
101
// next hop. It is also used to encode the per-hop payload included within
102
// the Sphinx packet.
103
type Hop struct {
104
        // PubKeyBytes is the raw bytes of the public key of the target node.
105
        PubKeyBytes Vertex
106

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

181
        return &c
×
182
}
183

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

348
        return nil
3✔
349
}
350

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

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

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

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

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

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

385
        var payloadSize uint64
3✔
386

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

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

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

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

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

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

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

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

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

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

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

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

3✔
459
        return payloadSize
3✔
460
}
461

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

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

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

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

493
// Copy returns a deep copy of the Route.
494
func (r *Route) Copy() *Route {
×
495
        c := *r
×
496

×
497
        c.Hops = make([]*Hop, len(r.Hops))
×
498
        for i := range r.Hops {
×
499
                c.Hops[i] = r.Hops[i].Copy()
×
500
        }
×
501

502
        return &c
×
503
}
504

505
// HopFee returns the fee charged by the route hop indicated by hopIndex.
506
//
507
// This calculation takes into account the possibility that the route contains
508
// some blinded hops, that will not have the amount to forward set. We take
509
// note of various points in the blinded route.
510
//
511
// Given the following route where Carol is the introduction node and B2 is
512
// the recipient, Carol and B1's hops will not have an amount to forward set:
513
// Alice --- Bob ---- Carol (introduction) ----- B1 ----- B2
514
//
515
// We locate ourselves in the route as follows:
516
// * Regular Hop (eg Alice - Bob):
517
//
518
//        incomingAmt !=0
519
//        outgoingAmt !=0
520
//        ->  Fee = incomingAmt - outgoingAmt
521
//
522
// * Introduction Hop (eg Bob - Carol):
523
//
524
//        incomingAmt !=0
525
//        outgoingAmt = 0
526
//        -> Fee = incomingAmt - receiverAmt
527
//
528
// This has the impact of attributing the full fees for the blinded route to
529
// the introduction node.
530
//
531
// * Blinded Intermediate Hop (eg Carol - B1):
532
//
533
//        incomingAmt = 0
534
//        outgoingAmt = 0
535
//        -> Fee = 0
536
//
537
// * Final Blinded Hop (B1 - B2):
538
//
539
//        incomingAmt = 0
540
//        outgoingAmt !=0
541
//        -> Fee = 0
542
func (r *Route) HopFee(hopIndex int) lnwire.MilliSatoshi {
3✔
543
        var incomingAmt lnwire.MilliSatoshi
3✔
544
        if hopIndex == 0 {
6✔
545
                incomingAmt = r.TotalAmount
3✔
546
        } else {
6✔
547
                incomingAmt = r.Hops[hopIndex-1].AmtToForward
3✔
548
        }
3✔
549

550
        outgoingAmt := r.Hops[hopIndex].AmtToForward
3✔
551

3✔
552
        switch {
3✔
553
        // If both incoming and outgoing amounts are set, we're in a normal
554
        // hop
555
        case incomingAmt != 0 && outgoingAmt != 0:
3✔
556
                return incomingAmt - outgoingAmt
3✔
557

558
        // If the incoming amount is zero, we're at an intermediate hop in
559
        // a blinded route, so the fee is zero.
560
        case incomingAmt == 0:
3✔
561
                return 0
3✔
562

563
        // If we have a non-zero incoming amount and a zero outgoing amount,
564
        // we're at the introduction hop so we express the fees for the full
565
        // blinded route at this hop.
566
        default:
3✔
567
                return incomingAmt - r.ReceiverAmt()
3✔
568
        }
569
}
570

571
// TotalFees is the sum of the fees paid at each hop within the final route. In
572
// the case of a one-hop payment, this value will be zero as we don't need to
573
// pay a fee to ourself.
574
func (r *Route) TotalFees() lnwire.MilliSatoshi {
3✔
575
        if len(r.Hops) == 0 {
3✔
576
                return 0
×
577
        }
×
578

579
        return r.TotalAmount - r.ReceiverAmt()
3✔
580
}
581

582
// ReceiverAmt is the amount received by the final hop of this route.
583
func (r *Route) ReceiverAmt() lnwire.MilliSatoshi {
3✔
584
        if len(r.Hops) == 0 {
3✔
585
                return 0
×
586
        }
×
587

588
        return r.Hops[len(r.Hops)-1].AmtToForward
3✔
589
}
590

591
// FinalHop returns the last hop of the route, or nil if the route is empty.
592
func (r *Route) FinalHop() *Hop {
3✔
593
        if len(r.Hops) == 0 {
3✔
594
                return nil
×
595
        }
×
596

597
        return r.Hops[len(r.Hops)-1]
3✔
598
}
599

600
// NewRouteFromHops creates a new Route structure from the minimally required
601
// information to perform the payment. It infers fee amounts and populates the
602
// node, chan and prev/next hop maps.
603
func NewRouteFromHops(amtToSend lnwire.MilliSatoshi, timeLock uint32,
604
        sourceVertex Vertex, hops []*Hop) (*Route, error) {
3✔
605

3✔
606
        if len(hops) == 0 {
3✔
607
                return nil, ErrNoRouteHopsProvided
×
608
        }
×
609

610
        // First, we'll create a route struct and populate it with the fields
611
        // for which the values are provided as arguments of this function.
612
        // TotalFees is determined based on the difference between the amount
613
        // that is send from the source and the final amount that is received
614
        // by the destination.
615
        route := &Route{
3✔
616
                SourcePubKey:  sourceVertex,
3✔
617
                Hops:          hops,
3✔
618
                TotalTimeLock: timeLock,
3✔
619
                TotalAmount:   amtToSend,
3✔
620
        }
3✔
621

3✔
622
        return route, nil
3✔
623
}
624

625
// ToSphinxPath converts a complete route into a sphinx PaymentPath that
626
// contains the per-hop payloads used to encoding the HTLC routing data for each
627
// hop in the route. This method also accepts an optional EOB payload for the
628
// final hop.
629
func (r *Route) ToSphinxPath() (*sphinx.PaymentPath, error) {
3✔
630
        var path sphinx.PaymentPath
3✔
631

3✔
632
        // We can only construct a route if there are hops provided.
3✔
633
        if len(r.Hops) == 0 {
3✔
634
                return nil, ErrNoRouteHopsProvided
×
635
        }
×
636

637
        // Check maximum route length.
638
        if len(r.Hops) > sphinx.NumMaxHops {
3✔
639
                return nil, ErrMaxRouteHopsExceeded
×
640
        }
×
641

642
        // For each hop encoded within the route, we'll convert the hop struct
643
        // to an OnionHop with matching per-hop payload within the path as used
644
        // by the sphinx package.
645
        for i, hop := range r.Hops {
6✔
646
                pub, err := btcec.ParsePubKey(hop.PubKeyBytes[:])
3✔
647
                if err != nil {
3✔
648
                        return nil, err
×
649
                }
×
650

651
                // As a base case, the next hop is set to all zeroes in order
652
                // to indicate that the "last hop" as no further hops after it.
653
                nextHop := uint64(0)
3✔
654

3✔
655
                // If we aren't on the last hop, then we set the "next address"
3✔
656
                // field to be the channel that directly follows it.
3✔
657
                finalHop := i == len(r.Hops)-1
3✔
658
                if !finalHop {
6✔
659
                        nextHop = r.Hops[i+1].ChannelID
3✔
660
                }
3✔
661

662
                var payload sphinx.HopPayload
3✔
663

3✔
664
                // If this is the legacy payload, then we can just include the
3✔
665
                // hop data as normal.
3✔
666
                if hop.LegacyPayload {
3✔
667
                        // Before we encode this value, we'll pack the next hop
×
668
                        // into the NextAddress field of the hop info to ensure
×
669
                        // we point to the right now.
×
670
                        hopData := sphinx.HopData{
×
671
                                ForwardAmount: uint64(hop.AmtToForward),
×
672
                                OutgoingCltv:  hop.OutgoingTimeLock,
×
673
                        }
×
674
                        binary.BigEndian.PutUint64(
×
675
                                hopData.NextAddress[:], nextHop,
×
676
                        )
×
677

×
678
                        payload, err = sphinx.NewLegacyHopPayload(&hopData)
×
679
                        if err != nil {
×
680
                                return nil, err
×
681
                        }
×
682
                } else {
3✔
683
                        // For non-legacy payloads, we'll need to pack the
3✔
684
                        // routing information, along with any extra TLV
3✔
685
                        // information into the new per-hop payload format.
3✔
686
                        // We'll also pass in the chan ID of the hop this
3✔
687
                        // channel should be forwarded to so we can construct a
3✔
688
                        // valid payload.
3✔
689
                        var b bytes.Buffer
3✔
690
                        err := hop.PackHopPayload(&b, nextHop, finalHop)
3✔
691
                        if err != nil {
3✔
692
                                return nil, err
×
693
                        }
×
694

695
                        payload, err = sphinx.NewTLVHopPayload(b.Bytes())
3✔
696
                        if err != nil {
3✔
697
                                return nil, err
×
698
                        }
×
699
                }
700

701
                path[i] = sphinx.OnionHop{
3✔
702
                        NodePub:    *pub,
3✔
703
                        HopPayload: payload,
3✔
704
                }
3✔
705
        }
706

707
        return &path, nil
3✔
708
}
709

710
// String returns a human readable representation of the route.
711
func (r *Route) String() string {
3✔
712
        var b strings.Builder
3✔
713

3✔
714
        amt := r.TotalAmount
3✔
715
        for i, hop := range r.Hops {
6✔
716
                if i > 0 {
6✔
717
                        b.WriteString(" -> ")
3✔
718
                }
3✔
719
                b.WriteString(fmt.Sprintf("%v (%v)",
3✔
720
                        strconv.FormatUint(hop.ChannelID, 10),
3✔
721
                        amt,
3✔
722
                ))
3✔
723
                amt = hop.AmtToForward
3✔
724
        }
725

726
        return fmt.Sprintf("%v, cltv %v",
3✔
727
                b.String(), r.TotalTimeLock,
3✔
728
        )
3✔
729
}
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