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

lightningnetwork / lnd / 11124677510

01 Oct 2024 11:49AM UTC coverage: 58.814% (+0.08%) from 58.735%
11124677510

push

github

web-flow
Merge pull request #8911 from ellemouton/reduceMCRouteEncoding

routing+channeldb: use a more minimal encoding for MC routes

561 of 893 new or added lines in 12 files covered. (62.82%)

60 existing lines in 15 files now uncovered.

130302 of 221548 relevant lines covered (58.81%)

28707.83 hits per line

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

73.63
/channeldb/migration32/route.go
1
package migration32
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "fmt"
7
        "io"
8

9
        "github.com/btcsuite/btcd/btcec/v2"
10
        "github.com/btcsuite/btcd/wire"
11
        lnwire "github.com/lightningnetwork/lnd/channeldb/migration/lnwire21"
12
        "github.com/lightningnetwork/lnd/tlv"
13
)
14

15
const (
16
        // MPPOnionType is the type used in the onion to reference the MPP
17
        // fields: total_amt and payment_addr.
18
        MPPOnionType tlv.Type = 8
19

20
        // AMPOnionType is the type used in the onion to reference the AMP
21
        // fields: root_share, set_id, and child_index.
22
        AMPOnionType tlv.Type = 14
23
)
24

25
// VertexSize is the size of the array to store a vertex.
26
const VertexSize = 33
27

28
// Vertex is a simple alias for the serialization of a compressed Bitcoin
29
// public key.
30
type Vertex [VertexSize]byte
31

32
// Route represents a path through the channel graph which runs over one or
33
// more channels in succession. This struct carries all the information
34
// required to craft the Sphinx onion packet, and send the payment along the
35
// first hop in the path. A route is only selected as valid if all the channels
36
// have sufficient capacity to carry the initial payment amount after fees are
37
// accounted for.
38
type Route struct {
39
        // TotalTimeLock is the cumulative (final) time lock across the entire
40
        // route. This is the CLTV value that should be extended to the first
41
        // hop in the route. All other hops will decrement the time-lock as
42
        // advertised, leaving enough time for all hops to wait for or present
43
        // the payment preimage to complete the payment.
44
        TotalTimeLock uint32
45

46
        // TotalAmount is the total amount of funds required to complete a
47
        // payment over this route. This value includes the cumulative fees at
48
        // each hop. As a result, the HTLC extended to the first-hop in the
49
        // route will need to have at least this many satoshis, otherwise the
50
        // route will fail at an intermediate node due to an insufficient
51
        // amount of fees.
52
        TotalAmount lnwire.MilliSatoshi
53

54
        // SourcePubKey is the pubkey of the node where this route originates
55
        // from.
56
        SourcePubKey Vertex
57

58
        // Hops contains details concerning the specific forwarding details at
59
        // each hop.
60
        Hops []*Hop
61

62
        // FirstHopAmount is the amount that should actually be sent to the
63
        // first hop in the route. This is only different from TotalAmount above
64
        // for custom channels where the on-chain amount doesn't necessarily
65
        // reflect all the value of an outgoing payment.
66
        FirstHopAmount tlv.RecordT[
67
                tlv.TlvType0, tlv.BigSizeT[lnwire.MilliSatoshi],
68
        ]
69

70
        // FirstHopWireCustomRecords is a set of custom records that should be
71
        // included in the wire message sent to the first hop. This is only set
72
        // on custom channels and is used to include additional information
73
        // about the actual value of the payment.
74
        //
75
        // NOTE: Since these records already represent TLV records, and we
76
        // enforce them to be in the custom range (e.g. >= 65536), we don't use
77
        // another parent record type here. Instead, when serializing the Route
78
        // we merge the TLV records together with the custom records and encode
79
        // everything as a single TLV stream.
80
        FirstHopWireCustomRecords lnwire.CustomRecords
81
}
82

83
// Hop represents an intermediate or final node of the route. This naming
84
// is in line with the definition given in BOLT #4: Onion Routing Protocol.
85
// The struct houses the channel along which this hop can be reached and
86
// the values necessary to create the HTLC that needs to be sent to the
87
// next hop. It is also used to encode the per-hop payload included within
88
// the Sphinx packet.
89
type Hop struct {
90
        // PubKeyBytes is the raw bytes of the public key of the target node.
91
        PubKeyBytes Vertex
92

93
        // ChannelID is the unique channel ID for the channel. The first 3
94
        // bytes are the block height, the next 3 the index within the block,
95
        // and the last 2 bytes are the output index for the channel.
96
        ChannelID uint64
97

98
        // OutgoingTimeLock is the timelock value that should be used when
99
        // crafting the _outgoing_ HTLC from this hop.
100
        OutgoingTimeLock uint32
101

102
        // AmtToForward is the amount that this hop will forward to the next
103
        // hop. This value is less than the value that the incoming HTLC
104
        // carries as a fee will be subtracted by the hop.
105
        AmtToForward lnwire.MilliSatoshi
106

107
        // MPP encapsulates the data required for option_mpp. This field should
108
        // only be set for the final hop.
109
        MPP *MPP
110

111
        // AMP encapsulates the data required for option_amp. This field should
112
        // only be set for the final hop.
113
        AMP *AMP
114

115
        // CustomRecords if non-nil are a set of additional TLV records that
116
        // should be included in the forwarding instructions for this node.
117
        CustomRecords lnwire.CustomRecords
118

119
        // LegacyPayload if true, then this signals that this node doesn't
120
        // understand the new TLV payload, so we must instead use the legacy
121
        // payload.
122
        //
123
        // NOTE: we should no longer ever create a Hop with Legacy set to true.
124
        // The only reason we are keeping this member is that it could be the
125
        // case that we have serialised hops persisted to disk where
126
        // LegacyPayload is true.
127
        LegacyPayload bool
128

129
        // Metadata is additional data that is sent along with the payment to
130
        // the payee.
131
        Metadata []byte
132

133
        // EncryptedData is an encrypted data blob includes for hops that are
134
        // part of a blinded route.
135
        EncryptedData []byte
136

137
        // BlindingPoint is an ephemeral public key used by introduction nodes
138
        // in blinded routes to unblind their portion of the route and pass on
139
        // the next ephemeral key to the next blinded node to do the same.
140
        BlindingPoint *btcec.PublicKey
141

142
        // TotalAmtMsat is the total amount for a blinded payment, potentially
143
        // spread over more than one HTLC. This field should only be set for
144
        // the final hop in a blinded path.
145
        TotalAmtMsat lnwire.MilliSatoshi
146
}
147

148
// MPP is a record that encodes the fields necessary for multi-path payments.
149
type MPP struct {
150
        // paymentAddr is a random, receiver-generated value used to avoid
151
        // collisions with concurrent payers.
152
        paymentAddr [32]byte
153

154
        // totalMsat is the total value of the payment, potentially spread
155
        // across more than one HTLC.
156
        totalMsat lnwire.MilliSatoshi
157
}
158

159
// Record returns a tlv.Record that can be used to encode or decode this record.
160
func (r *MPP) Record() tlv.Record {
2✔
161
        // Fixed-size, 32 byte payment address followed by truncated 64-bit
2✔
162
        // total msat.
2✔
163
        size := func() uint64 {
2✔
NEW
164
                return 32 + tlv.SizeTUint64(uint64(r.totalMsat))
×
NEW
165
        }
×
166

167
        return tlv.MakeDynamicRecord(
2✔
168
                MPPOnionType, r, size, MPPEncoder, MPPDecoder,
2✔
169
        )
2✔
170
}
171

172
const (
173
        // minMPPLength is the minimum length of a serialized MPP TLV record,
174
        // which occurs when the truncated encoding of total_amt_msat takes 0
175
        // bytes, leaving only the payment_addr.
176
        minMPPLength = 32
177

178
        // maxMPPLength is the maximum length of a serialized MPP TLV record,
179
        // which occurs when the truncated encoding of total_amt_msat takes 8
180
        // bytes.
181
        maxMPPLength = 40
182
)
183

184
// MPPEncoder writes the MPP record to the provided io.Writer.
185
func MPPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
1✔
186
        if v, ok := val.(*MPP); ok {
2✔
187
                err := tlv.EBytes32(w, &v.paymentAddr, buf)
1✔
188
                if err != nil {
1✔
NEW
189
                        return err
×
NEW
190
                }
×
191

192
                return tlv.ETUint64T(w, uint64(v.totalMsat), buf)
1✔
193
        }
194

NEW
195
        return tlv.NewTypeForEncodingErr(val, "MPP")
×
196
}
197

198
// MPPDecoder reads the MPP record to the provided io.Reader.
199
func MPPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
1✔
200
        if v, ok := val.(*MPP); ok && minMPPLength <= l && l <= maxMPPLength {
2✔
201
                if err := tlv.DBytes32(r, &v.paymentAddr, buf, 32); err != nil {
1✔
NEW
202
                        return err
×
NEW
203
                }
×
204

205
                var total uint64
1✔
206
                if err := tlv.DTUint64(r, &total, buf, l-32); err != nil {
1✔
NEW
207
                        return err
×
NEW
208
                }
×
209
                v.totalMsat = lnwire.MilliSatoshi(total)
1✔
210

1✔
211
                return nil
1✔
212
        }
213

NEW
214
        return tlv.NewTypeForDecodingErr(val, "MPP", l, maxMPPLength)
×
215
}
216

217
// AMP is a record that encodes the fields necessary for atomic multi-path
218
// payments.
219
type AMP struct {
220
        rootShare  [32]byte
221
        setID      [32]byte
222
        childIndex uint32
223
}
224

225
// AMPEncoder writes the AMP record to the provided io.Writer.
226
func AMPEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
1✔
227
        if v, ok := val.(*AMP); ok {
2✔
228
                if err := tlv.EBytes32(w, &v.rootShare, buf); err != nil {
1✔
NEW
229
                        return err
×
NEW
230
                }
×
231

232
                if err := tlv.EBytes32(w, &v.setID, buf); err != nil {
1✔
NEW
233
                        return err
×
NEW
234
                }
×
235

236
                return tlv.ETUint32T(w, v.childIndex, buf)
1✔
237
        }
238

NEW
239
        return tlv.NewTypeForEncodingErr(val, "AMP")
×
240
}
241

242
const (
243
        // minAMPLength is the minimum length of a serialized AMP TLV record,
244
        // which occurs when the truncated encoding of child_index takes 0
245
        // bytes, leaving only the root_share and set_id.
246
        minAMPLength = 64
247

248
        // maxAMPLength is the maximum length of a serialized AMP TLV record,
249
        // which occurs when the truncated encoding of a child_index takes 2
250
        // bytes.
251
        maxAMPLength = 68
252
)
253

254
// AMPDecoder reads the AMP record from the provided io.Reader.
255
func AMPDecoder(r io.Reader, val interface{}, buf *[8]byte, l uint64) error {
1✔
256
        if v, ok := val.(*AMP); ok && minAMPLength <= l && l <= maxAMPLength {
2✔
257
                if err := tlv.DBytes32(r, &v.rootShare, buf, 32); err != nil {
1✔
NEW
258
                        return err
×
NEW
259
                }
×
260

261
                if err := tlv.DBytes32(r, &v.setID, buf, 32); err != nil {
1✔
NEW
262
                        return err
×
NEW
263
                }
×
264

265
                return tlv.DTUint32(r, &v.childIndex, buf, l-minAMPLength)
1✔
266
        }
267

NEW
268
        return tlv.NewTypeForDecodingErr(val, "AMP", l, maxAMPLength)
×
269
}
270

271
// Record returns a tlv.Record that can be used to encode or decode this record.
272
func (a *AMP) Record() tlv.Record {
2✔
273
        return tlv.MakeDynamicRecord(
2✔
274
                AMPOnionType, a, a.PayloadSize, AMPEncoder, AMPDecoder,
2✔
275
        )
2✔
276
}
2✔
277

278
// PayloadSize returns the size this record takes up in encoded form.
NEW
279
func (a *AMP) PayloadSize() uint64 {
×
NEW
280
        return 32 + 32 + tlv.SizeTUint32(a.childIndex)
×
NEW
281
}
×
282

283
// SerializeRoute serializes a route.
284
func SerializeRoute(w io.Writer, r Route) error {
2✔
285
        if err := WriteElements(w,
2✔
286
                r.TotalTimeLock, r.TotalAmount, r.SourcePubKey[:],
2✔
287
        ); err != nil {
2✔
NEW
288
                return err
×
NEW
289
        }
×
290

291
        if err := WriteElements(w, uint32(len(r.Hops))); err != nil {
2✔
NEW
292
                return err
×
NEW
293
        }
×
294

295
        for _, h := range r.Hops {
7✔
296
                if err := serializeHop(w, h); err != nil {
5✔
NEW
297
                        return err
×
NEW
298
                }
×
299
        }
300

301
        // Any new/extra TLV data is encoded in serializeHTLCAttemptInfo!
302

303
        return nil
2✔
304
}
305

306
func serializeHop(w io.Writer, h *Hop) error {
5✔
307
        if err := WriteElements(w,
5✔
308
                h.PubKeyBytes[:],
5✔
309
                h.ChannelID,
5✔
310
                h.OutgoingTimeLock,
5✔
311
                h.AmtToForward,
5✔
312
        ); err != nil {
5✔
NEW
313
                return err
×
NEW
314
        }
×
315

316
        if err := binary.Write(w, byteOrder, h.LegacyPayload); err != nil {
5✔
NEW
317
                return err
×
NEW
318
        }
×
319

320
        // For legacy payloads, we don't need to write any TLV records, so
321
        // we'll write a zero indicating the our serialized TLV map has no
322
        // records.
323
        if h.LegacyPayload {
6✔
324
                return WriteElements(w, uint32(0))
1✔
325
        }
1✔
326

327
        // Gather all non-primitive TLV records so that they can be serialized
328
        // as a single blob.
329
        //
330
        // TODO(conner): add migration to unify all fields in a single TLV
331
        // blobs. The split approach will cause headaches down the road as more
332
        // fields are added, which we can avoid by having a single TLV stream
333
        // for all payload fields.
334
        var records []tlv.Record
4✔
335
        if h.MPP != nil {
5✔
336
                records = append(records, h.MPP.Record())
1✔
337
        }
1✔
338

339
        // Add blinding point and encrypted data if present.
340
        if h.EncryptedData != nil {
7✔
341
                records = append(records, NewEncryptedDataRecord(
3✔
342
                        &h.EncryptedData,
3✔
343
                ))
3✔
344
        }
3✔
345

346
        if h.BlindingPoint != nil {
7✔
347
                records = append(records, NewBlindingPointRecord(
3✔
348
                        &h.BlindingPoint,
3✔
349
                ))
3✔
350
        }
3✔
351

352
        if h.AMP != nil {
5✔
353
                records = append(records, h.AMP.Record())
1✔
354
        }
1✔
355

356
        if h.Metadata != nil {
5✔
357
                records = append(records, NewMetadataRecord(&h.Metadata))
1✔
358
        }
1✔
359

360
        if h.TotalAmtMsat != 0 {
7✔
361
                totalMsatInt := uint64(h.TotalAmtMsat)
3✔
362
                records = append(
3✔
363
                        records, NewTotalAmtMsatBlinded(&totalMsatInt),
3✔
364
                )
3✔
365
        }
3✔
366

367
        // Final sanity check to absolutely rule out custom records that are not
368
        // custom and write into the standard range.
369
        if err := h.CustomRecords.Validate(); err != nil {
4✔
NEW
370
                return err
×
NEW
371
        }
×
372

373
        // Convert custom records to tlv and add to the record list.
374
        // MapToRecords sorts the list, so adding it here will keep the list
375
        // canonical.
376
        tlvRecords := tlv.MapToRecords(h.CustomRecords)
4✔
377
        records = append(records, tlvRecords...)
4✔
378

4✔
379
        // Otherwise, we'll transform our slice of records into a map of the
4✔
380
        // raw bytes, then serialize them in-line with a length (number of
4✔
381
        // elements) prefix.
4✔
382
        mapRecords, err := tlv.RecordsToMap(records)
4✔
383
        if err != nil {
4✔
NEW
384
                return err
×
NEW
385
        }
×
386

387
        numRecords := uint32(len(mapRecords))
4✔
388
        if err := WriteElements(w, numRecords); err != nil {
4✔
NEW
389
                return err
×
NEW
390
        }
×
391

392
        for recordType, rawBytes := range mapRecords {
18✔
393
                if err := WriteElements(w, recordType); err != nil {
14✔
NEW
394
                        return err
×
NEW
395
                }
×
396

397
                if err := wire.WriteVarBytes(w, 0, rawBytes); err != nil {
14✔
NEW
398
                        return err
×
NEW
399
                }
×
400
        }
401

402
        return nil
4✔
403
}
404

405
// DeserializeRoute deserializes a route.
406
func DeserializeRoute(r io.Reader) (Route, error) {
2✔
407
        rt := Route{}
2✔
408
        if err := ReadElements(r,
2✔
409
                &rt.TotalTimeLock, &rt.TotalAmount,
2✔
410
        ); err != nil {
2✔
NEW
411
                return rt, err
×
NEW
412
        }
×
413

414
        var pub []byte
2✔
415
        if err := ReadElements(r, &pub); err != nil {
2✔
NEW
416
                return rt, err
×
NEW
417
        }
×
418
        copy(rt.SourcePubKey[:], pub)
2✔
419

2✔
420
        var numHops uint32
2✔
421
        if err := ReadElements(r, &numHops); err != nil {
2✔
NEW
422
                return rt, err
×
NEW
423
        }
×
424

425
        var hops []*Hop
2✔
426
        for i := uint32(0); i < numHops; i++ {
7✔
427
                hop, err := deserializeHop(r)
5✔
428
                if err != nil {
5✔
NEW
429
                        return rt, err
×
NEW
430
                }
×
431
                hops = append(hops, hop)
5✔
432
        }
433
        rt.Hops = hops
2✔
434

2✔
435
        // Any new/extra TLV data is decoded in deserializeHTLCAttemptInfo!
2✔
436

2✔
437
        return rt, nil
2✔
438
}
439

440
// maxOnionPayloadSize is the largest Sphinx payload possible, so we don't need
441
// to read/write a TLV stream larger than this.
442
const maxOnionPayloadSize = 1300
443

444
func deserializeHop(r io.Reader) (*Hop, error) {
5✔
445
        h := &Hop{}
5✔
446

5✔
447
        var pub []byte
5✔
448
        if err := ReadElements(r, &pub); err != nil {
5✔
NEW
449
                return nil, err
×
NEW
450
        }
×
451
        copy(h.PubKeyBytes[:], pub)
5✔
452

5✔
453
        if err := ReadElements(r,
5✔
454
                &h.ChannelID, &h.OutgoingTimeLock, &h.AmtToForward,
5✔
455
        ); err != nil {
5✔
NEW
456
                return nil, err
×
NEW
457
        }
×
458

459
        // TODO(roasbeef): change field to allow LegacyPayload false to be the
460
        // legacy default?
461
        err := binary.Read(r, byteOrder, &h.LegacyPayload)
5✔
462
        if err != nil {
5✔
NEW
463
                return nil, err
×
NEW
464
        }
×
465

466
        var numElements uint32
5✔
467
        if err := ReadElements(r, &numElements); err != nil {
5✔
NEW
468
                return nil, err
×
NEW
469
        }
×
470

471
        // If there're no elements, then we can return early.
472
        if numElements == 0 {
6✔
473
                return h, nil
1✔
474
        }
1✔
475

476
        tlvMap := make(map[uint64][]byte)
4✔
477
        for i := uint32(0); i < numElements; i++ {
18✔
478
                var tlvType uint64
14✔
479
                if err := ReadElements(r, &tlvType); err != nil {
14✔
NEW
480
                        return nil, err
×
NEW
481
                }
×
482

483
                rawRecordBytes, err := wire.ReadVarBytes(
14✔
484
                        r, 0, maxOnionPayloadSize, "tlv",
14✔
485
                )
14✔
486
                if err != nil {
14✔
NEW
487
                        return nil, err
×
NEW
488
                }
×
489

490
                tlvMap[tlvType] = rawRecordBytes
14✔
491
        }
492

493
        // If the MPP type is present, remove it from the generic TLV map and
494
        // parse it back into a proper MPP struct.
495
        //
496
        // TODO(conner): add migration to unify all fields in a single TLV
497
        // blobs. The split approach will cause headaches down the road as more
498
        // fields are added, which we can avoid by having a single TLV stream
499
        // for all payload fields.
500
        mppType := uint64(MPPOnionType)
4✔
501
        if mppBytes, ok := tlvMap[mppType]; ok {
5✔
502
                delete(tlvMap, mppType)
1✔
503

1✔
504
                var (
1✔
505
                        mpp    = &MPP{}
1✔
506
                        mppRec = mpp.Record()
1✔
507
                        r      = bytes.NewReader(mppBytes)
1✔
508
                )
1✔
509
                err := mppRec.Decode(r, uint64(len(mppBytes)))
1✔
510
                if err != nil {
1✔
NEW
511
                        return nil, err
×
NEW
512
                }
×
513
                h.MPP = mpp
1✔
514
        }
515

516
        // If encrypted data or blinding key are present, remove them from
517
        // the TLV map and parse into proper types.
518
        encryptedDataType := uint64(EncryptedDataOnionType)
4✔
519
        if data, ok := tlvMap[encryptedDataType]; ok {
7✔
520
                delete(tlvMap, encryptedDataType)
3✔
521
                h.EncryptedData = data
3✔
522
        }
3✔
523

524
        blindingType := uint64(BlindingPointOnionType)
4✔
525
        if blindingPoint, ok := tlvMap[blindingType]; ok {
7✔
526
                delete(tlvMap, blindingType)
3✔
527

3✔
528
                h.BlindingPoint, err = btcec.ParsePubKey(blindingPoint)
3✔
529
                if err != nil {
3✔
NEW
530
                        return nil, fmt.Errorf("invalid blinding point: %w",
×
NEW
531
                                err)
×
NEW
532
                }
×
533
        }
534

535
        ampType := uint64(AMPOnionType)
4✔
536
        if ampBytes, ok := tlvMap[ampType]; ok {
5✔
537
                delete(tlvMap, ampType)
1✔
538

1✔
539
                var (
1✔
540
                        amp    = &AMP{}
1✔
541
                        ampRec = amp.Record()
1✔
542
                        r      = bytes.NewReader(ampBytes)
1✔
543
                )
1✔
544
                err := ampRec.Decode(r, uint64(len(ampBytes)))
1✔
545
                if err != nil {
1✔
NEW
546
                        return nil, err
×
NEW
547
                }
×
548
                h.AMP = amp
1✔
549
        }
550

551
        // If the metadata type is present, remove it from the tlv map and
552
        // populate directly on the hop.
553
        metadataType := uint64(MetadataOnionType)
4✔
554
        if metadata, ok := tlvMap[metadataType]; ok {
5✔
555
                delete(tlvMap, metadataType)
1✔
556

1✔
557
                h.Metadata = metadata
1✔
558
        }
1✔
559

560
        totalAmtMsatType := uint64(TotalAmtMsatBlindedType)
4✔
561
        if totalAmtMsat, ok := tlvMap[totalAmtMsatType]; ok {
7✔
562
                delete(tlvMap, totalAmtMsatType)
3✔
563

3✔
564
                var (
3✔
565
                        totalAmtMsatInt uint64
3✔
566
                        buf             [8]byte
3✔
567
                )
3✔
568
                if err := tlv.DTUint64(
3✔
569
                        bytes.NewReader(totalAmtMsat),
3✔
570
                        &totalAmtMsatInt,
3✔
571
                        &buf,
3✔
572
                        uint64(len(totalAmtMsat)),
3✔
573
                ); err != nil {
3✔
NEW
574
                        return nil, err
×
NEW
575
                }
×
576

577
                h.TotalAmtMsat = lnwire.MilliSatoshi(totalAmtMsatInt)
3✔
578
        }
579

580
        h.CustomRecords = tlvMap
4✔
581

4✔
582
        return h, nil
4✔
583
}
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