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

lightningnetwork / lnd / 13035292482

29 Jan 2025 03:59PM UTC coverage: 49.3% (-9.5%) from 58.777%
13035292482

Pull #9456

github

mohamedawnallah
docs: update release-notes-0.19.0.md

In this commit, we warn users about the removal
of RPCs `SendToRoute`, `SendToRouteSync`, `SendPayment`,
and `SendPaymentSync` in the next release 0.20.
Pull Request #9456: lnrpc+docs: deprecate warning `SendToRoute`, `SendToRouteSync`, `SendPayment`, and `SendPaymentSync` in Release 0.19

100634 of 204126 relevant lines covered (49.3%)

1.54 hits per line

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

53.35
/htlcswitch/hop/payload.go
1
package hop
2

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

8
        "github.com/btcsuite/btcd/btcec/v2"
9
        "github.com/btcsuite/btcd/chaincfg/chainhash"
10
        sphinx "github.com/lightningnetwork/lightning-onion"
11
        "github.com/lightningnetwork/lnd/lnwire"
12
        "github.com/lightningnetwork/lnd/record"
13
        "github.com/lightningnetwork/lnd/tlv"
14
)
15

16
// PayloadViolation is an enum encapsulating the possible invalid payload
17
// violations that can occur when processing or validating a payload.
18
type PayloadViolation byte
19

20
const (
21
        // OmittedViolation indicates that a type was expected to be found the
22
        // payload but was absent.
23
        OmittedViolation PayloadViolation = iota
24

25
        // IncludedViolation indicates that a type was expected to be omitted
26
        // from the payload but was present.
27
        IncludedViolation
28

29
        // RequiredViolation indicates that an unknown even type was found in
30
        // the payload that we could not process.
31
        RequiredViolation
32

33
        // InsufficientViolation indicates that the provided type does
34
        // not satisfy constraints.
35
        InsufficientViolation
36
)
37

38
// String returns a human-readable description of the violation as a verb.
39
func (v PayloadViolation) String() string {
×
40
        switch v {
×
41
        case OmittedViolation:
×
42
                return "omitted"
×
43

44
        case IncludedViolation:
×
45
                return "included"
×
46

47
        case RequiredViolation:
×
48
                return "required"
×
49

50
        case InsufficientViolation:
×
51
                return "insufficient"
×
52

53
        default:
×
54
                return "unknown violation"
×
55
        }
56
}
57

58
// ErrInvalidPayload is an error returned when a parsed onion payload either
59
// included or omitted incorrect records for a particular hop type.
60
type ErrInvalidPayload struct {
61
        // Type the record's type that cause the violation.
62
        Type tlv.Type
63

64
        // Violation is an enum indicating the type of violation detected in
65
        // processing Type.
66
        Violation PayloadViolation
67

68
        // FinalHop if true, indicates that the violation is for the final hop
69
        // in the route (identified by next hop id), otherwise the violation is
70
        // for an intermediate hop.
71
        FinalHop bool
72
}
73

74
// Error returns a human-readable description of the invalid payload error.
75
func (e ErrInvalidPayload) Error() string {
×
76
        hopType := "intermediate"
×
77
        if e.FinalHop {
×
78
                hopType = "final"
×
79
        }
×
80

81
        return fmt.Sprintf("onion payload for %s hop %v record with type %d",
×
82
                hopType, e.Violation, e.Type)
×
83
}
84

85
// Payload encapsulates all information delivered to a hop in an onion payload.
86
// A Hop can represent either a TLV or legacy payload. The primary forwarding
87
// instruction can be accessed via ForwardingInfo, and additional records can be
88
// accessed by other member functions.
89
type Payload struct {
90
        // FwdInfo holds the basic parameters required for HTLC forwarding, e.g.
91
        // amount, cltv, and next hop.
92
        FwdInfo ForwardingInfo
93

94
        // MPP holds the info provided in an option_mpp record when parsed from
95
        // a TLV onion payload.
96
        MPP *record.MPP
97

98
        // AMP holds the info provided in an option_amp record when parsed from
99
        // a TLV onion payload.
100
        AMP *record.AMP
101

102
        // customRecords are user-defined records in the custom type range that
103
        // were included in the payload.
104
        customRecords record.CustomSet
105

106
        // encryptedData is a blob of data encrypted by the receiver for use
107
        // in blinded routes.
108
        encryptedData []byte
109

110
        // blindingPoint is an ephemeral pubkey for use in blinded routes.
111
        blindingPoint *btcec.PublicKey
112

113
        // metadata is additional data that is sent along with the payment to
114
        // the payee.
115
        metadata []byte
116

117
        // totalAmtMsat holds the info provided in total_amount_msat when
118
        // parsed from a TLV onion payload.
119
        totalAmtMsat lnwire.MilliSatoshi
120
}
121

122
// NewLegacyPayload builds a Payload from the amount, cltv, and next hop
123
// parameters provided by leegacy onion payloads.
124
func NewLegacyPayload(f *sphinx.HopData) *Payload {
×
125
        nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
×
126

×
127
        return &Payload{
×
128
                FwdInfo: ForwardingInfo{
×
129
                        NextHop:         lnwire.NewShortChanIDFromInt(nextHop),
×
130
                        AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
×
131
                        OutgoingCTLV:    f.OutgoingCltv,
×
132
                },
×
133
                customRecords: make(record.CustomSet),
×
134
        }
×
135
}
×
136

137
// ParseTLVPayload builds a new Hop from the passed io.Reader and returns
138
// a map of all the types that were found in the payload. This function
139
// does not perform validation of TLV types included in the payload.
140
func ParseTLVPayload(r io.Reader) (*Payload, map[tlv.Type][]byte, error) {
3✔
141
        var (
3✔
142
                cid           uint64
3✔
143
                amt           uint64
3✔
144
                totalAmtMsat  uint64
3✔
145
                cltv          uint32
3✔
146
                mpp           = &record.MPP{}
3✔
147
                amp           = &record.AMP{}
3✔
148
                encryptedData []byte
3✔
149
                blindingPoint *btcec.PublicKey
3✔
150
                metadata      []byte
3✔
151
        )
3✔
152

3✔
153
        tlvStream, err := tlv.NewStream(
3✔
154
                record.NewAmtToFwdRecord(&amt),
3✔
155
                record.NewLockTimeRecord(&cltv),
3✔
156
                record.NewNextHopIDRecord(&cid),
3✔
157
                mpp.Record(),
3✔
158
                record.NewEncryptedDataRecord(&encryptedData),
3✔
159
                record.NewBlindingPointRecord(&blindingPoint),
3✔
160
                amp.Record(),
3✔
161
                record.NewMetadataRecord(&metadata),
3✔
162
                record.NewTotalAmtMsatBlinded(&totalAmtMsat),
3✔
163
        )
3✔
164
        if err != nil {
3✔
165
                return nil, nil, err
×
166
        }
×
167

168
        // Since this data is provided by a potentially malicious peer, pass it
169
        // into the P2P decoding variant.
170
        parsedTypes, err := tlvStream.DecodeWithParsedTypesP2P(r)
3✔
171
        if err != nil {
3✔
172
                return nil, nil, err
×
173
        }
×
174

175
        // If no MPP field was parsed, set the MPP field on the resulting
176
        // payload to nil.
177
        if _, ok := parsedTypes[record.MPPOnionType]; !ok {
6✔
178
                mpp = nil
3✔
179
        }
3✔
180

181
        // If no AMP field was parsed, set the MPP field on the resulting
182
        // payload to nil.
183
        if _, ok := parsedTypes[record.AMPOnionType]; !ok {
6✔
184
                amp = nil
3✔
185
        }
3✔
186

187
        // If no encrypted data was parsed, set the field on our resulting
188
        // payload to nil.
189
        if _, ok := parsedTypes[record.EncryptedDataOnionType]; !ok {
6✔
190
                encryptedData = nil
3✔
191
        }
3✔
192

193
        // If no metadata field was parsed, set the metadata field on the
194
        // resulting payload to nil.
195
        if _, ok := parsedTypes[record.MetadataOnionType]; !ok {
6✔
196
                metadata = nil
3✔
197
        }
3✔
198

199
        // Filter out the custom records.
200
        customRecords := NewCustomRecords(parsedTypes)
3✔
201

3✔
202
        return &Payload{
3✔
203
                FwdInfo: ForwardingInfo{
3✔
204
                        NextHop:         lnwire.NewShortChanIDFromInt(cid),
3✔
205
                        AmountToForward: lnwire.MilliSatoshi(amt),
3✔
206
                        OutgoingCTLV:    cltv,
3✔
207
                },
3✔
208
                MPP:           mpp,
3✔
209
                AMP:           amp,
3✔
210
                metadata:      metadata,
3✔
211
                encryptedData: encryptedData,
3✔
212
                blindingPoint: blindingPoint,
3✔
213
                customRecords: customRecords,
3✔
214
                totalAmtMsat:  lnwire.MilliSatoshi(totalAmtMsat),
3✔
215
        }, parsedTypes, nil
3✔
216
}
217

218
// ValidateTLVPayload validates the TLV fields that were included in a TLV
219
// payload.
220
func ValidateTLVPayload(parsedTypes map[tlv.Type][]byte,
221
        finalHop bool, updateAddBlinding bool) error {
3✔
222

3✔
223
        // Validate whether the sender properly included or omitted tlv records
3✔
224
        // in accordance with BOLT 04.
3✔
225
        err := ValidateParsedPayloadTypes(
3✔
226
                parsedTypes, finalHop, updateAddBlinding,
3✔
227
        )
3✔
228
        if err != nil {
3✔
229
                return err
×
230
        }
×
231

232
        // Check for violation of the rules for mandatory fields.
233
        violatingType := getMinRequiredViolation(parsedTypes)
3✔
234
        if violatingType != nil {
3✔
235
                return ErrInvalidPayload{
×
236
                        Type:      *violatingType,
×
237
                        Violation: RequiredViolation,
×
238
                        FinalHop:  finalHop,
×
239
                }
×
240
        }
×
241

242
        return nil
3✔
243
}
244

245
// ForwardingInfo returns the basic parameters required for HTLC forwarding,
246
// e.g. amount, cltv, and next hop.
247
func (h *Payload) ForwardingInfo() ForwardingInfo {
3✔
248
        return h.FwdInfo
3✔
249
}
3✔
250

251
// NewCustomRecords filters the types parsed from the tlv stream for custom
252
// records.
253
func NewCustomRecords(parsedTypes tlv.TypeMap) record.CustomSet {
3✔
254
        customRecords := make(record.CustomSet)
3✔
255
        for t, parseResult := range parsedTypes {
6✔
256
                if parseResult == nil || t < record.CustomTypeStart {
6✔
257
                        continue
3✔
258
                }
259
                customRecords[uint64(t)] = parseResult
3✔
260
        }
261
        return customRecords
3✔
262
}
263

264
// ValidateParsedPayloadTypes checks the types parsed from a hop payload to
265
// ensure that the proper fields are either included or omitted. The finalHop
266
// boolean should be true if the payload was parsed for an exit hop. The
267
// requirements for this method are described in BOLT 04.
268
func ValidateParsedPayloadTypes(parsedTypes tlv.TypeMap,
269
        isFinalHop, updateAddBlinding bool) error {
3✔
270

3✔
271
        _, hasAmt := parsedTypes[record.AmtOnionType]
3✔
272
        _, hasLockTime := parsedTypes[record.LockTimeOnionType]
3✔
273
        _, hasNextHop := parsedTypes[record.NextHopOnionType]
3✔
274
        _, hasMPP := parsedTypes[record.MPPOnionType]
3✔
275
        _, hasAMP := parsedTypes[record.AMPOnionType]
3✔
276
        _, hasEncryptedData := parsedTypes[record.EncryptedDataOnionType]
3✔
277
        _, hasBlinding := parsedTypes[record.BlindingPointOnionType]
3✔
278

3✔
279
        // All cleartext hops (including final hop) and the final hop in a
3✔
280
        // blinded path require the forwading amount and expiry TLVs to be set.
3✔
281
        needFwdInfo := isFinalHop || !hasEncryptedData
3✔
282

3✔
283
        // No blinded hops should have a next hop specified, and only the final
3✔
284
        // hop in a cleartext route should exclude it.
3✔
285
        needNextHop := !(hasEncryptedData || isFinalHop)
3✔
286

3✔
287
        switch {
3✔
288
        // Both blinding point being set is invalid.
289
        case hasBlinding && updateAddBlinding:
×
290
                return ErrInvalidPayload{
×
291
                        Type:      record.BlindingPointOnionType,
×
292
                        Violation: IncludedViolation,
×
293
                        FinalHop:  isFinalHop,
×
294
                }
×
295

296
        // If encrypted data is not provided, blinding points should not be
297
        // set.
298
        case !hasEncryptedData && (hasBlinding || updateAddBlinding):
×
299
                return ErrInvalidPayload{
×
300
                        Type:      record.EncryptedDataOnionType,
×
301
                        Violation: OmittedViolation,
×
302
                        FinalHop:  isFinalHop,
×
303
                }
×
304

305
        // If encrypted data is present, we require that one blinding point
306
        // is set.
307
        case hasEncryptedData && !(hasBlinding || updateAddBlinding):
×
308
                return ErrInvalidPayload{
×
309
                        Type:      record.EncryptedDataOnionType,
×
310
                        Violation: IncludedViolation,
×
311
                        FinalHop:  isFinalHop,
×
312
                }
×
313

314
        // Hops that need forwarding info must include an amount to forward.
315
        case needFwdInfo && !hasAmt:
×
316
                return ErrInvalidPayload{
×
317
                        Type:      record.AmtOnionType,
×
318
                        Violation: OmittedViolation,
×
319
                        FinalHop:  isFinalHop,
×
320
                }
×
321

322
        // Hops that need forwarding info must include a cltv expiry.
323
        case needFwdInfo && !hasLockTime:
×
324
                return ErrInvalidPayload{
×
325
                        Type:      record.LockTimeOnionType,
×
326
                        Violation: OmittedViolation,
×
327
                        FinalHop:  isFinalHop,
×
328
                }
×
329

330
        // Hops that don't need forwarding info shouldn't have an amount TLV.
331
        case !needFwdInfo && hasAmt:
×
332
                return ErrInvalidPayload{
×
333
                        Type:      record.AmtOnionType,
×
334
                        Violation: IncludedViolation,
×
335
                        FinalHop:  isFinalHop,
×
336
                }
×
337

338
        // Hops that don't need forwarding info shouldn't have a cltv TLV.
339
        case !needFwdInfo && hasLockTime:
×
340
                return ErrInvalidPayload{
×
341
                        Type:      record.LockTimeOnionType,
×
342
                        Violation: IncludedViolation,
×
343
                        FinalHop:  isFinalHop,
×
344
                }
×
345

346
        // The exit hop and all blinded hops should omit the next hop id.
347
        case !needNextHop && hasNextHop:
×
348
                return ErrInvalidPayload{
×
349
                        Type:      record.NextHopOnionType,
×
350
                        Violation: IncludedViolation,
×
351
                        FinalHop:  isFinalHop,
×
352
                }
×
353

354
        // Require that the next hop is set for intermediate hops in regular
355
        // routes.
356
        case needNextHop && !hasNextHop:
×
357
                return ErrInvalidPayload{
×
358
                        Type:      record.NextHopOnionType,
×
359
                        Violation: OmittedViolation,
×
360
                        FinalHop:  isFinalHop,
×
361
                }
×
362

363
        // Intermediate nodes should never receive MPP fields.
364
        case !isFinalHop && hasMPP:
×
365
                return ErrInvalidPayload{
×
366
                        Type:      record.MPPOnionType,
×
367
                        Violation: IncludedViolation,
×
368
                        FinalHop:  isFinalHop,
×
369
                }
×
370

371
        // Intermediate nodes should never receive AMP fields.
372
        case !isFinalHop && hasAMP:
×
373
                return ErrInvalidPayload{
×
374
                        Type:      record.AMPOnionType,
×
375
                        Violation: IncludedViolation,
×
376
                        FinalHop:  isFinalHop,
×
377
                }
×
378
        }
379

380
        return nil
3✔
381
}
382

383
// MultiPath returns the record corresponding the option_mpp parsed from the
384
// onion payload.
385
func (h *Payload) MultiPath() *record.MPP {
3✔
386
        return h.MPP
3✔
387
}
3✔
388

389
// AMPRecord returns the record corresponding with option_amp parsed from the
390
// onion payload.
391
func (h *Payload) AMPRecord() *record.AMP {
3✔
392
        return h.AMP
3✔
393
}
3✔
394

395
// CustomRecords returns the custom tlv type records that were parsed from the
396
// payload.
397
func (h *Payload) CustomRecords() record.CustomSet {
3✔
398
        return h.customRecords
3✔
399
}
3✔
400

401
// EncryptedData returns the route blinding encrypted data parsed from the
402
// onion payload.
403
func (h *Payload) EncryptedData() []byte {
×
404
        return h.encryptedData
×
405
}
×
406

407
// BlindingPoint returns the route blinding point parsed from the onion payload.
408
func (h *Payload) BlindingPoint() *btcec.PublicKey {
×
409
        return h.blindingPoint
×
410
}
×
411

412
// PathID returns the path ID that was encoded in the final hop payload of a
413
// blinded payment.
414
func (h *Payload) PathID() *chainhash.Hash {
3✔
415
        return h.FwdInfo.PathID
3✔
416
}
3✔
417

418
// Metadata returns the additional data that is sent along with the
419
// payment to the payee.
420
func (h *Payload) Metadata() []byte {
3✔
421
        return h.metadata
3✔
422
}
3✔
423

424
// TotalAmtMsat returns the total amount sent to the final hop, as set by the
425
// payee.
426
func (h *Payload) TotalAmtMsat() lnwire.MilliSatoshi {
3✔
427
        return h.totalAmtMsat
3✔
428
}
3✔
429

430
// getMinRequiredViolation checks for unrecognized required (even) fields in the
431
// standard range and returns the lowest required type. Always returning the
432
// lowest required type allows a failure message to be deterministic.
433
func getMinRequiredViolation(set tlv.TypeMap) *tlv.Type {
3✔
434
        var (
3✔
435
                requiredViolation        bool
3✔
436
                minRequiredViolationType tlv.Type
3✔
437
        )
3✔
438
        for t, parseResult := range set {
6✔
439
                // If a type is even but not known to us, we cannot process the
3✔
440
                // payload. We are required to understand a field that we don't
3✔
441
                // support.
3✔
442
                //
3✔
443
                // We always accept custom fields, because a higher level
3✔
444
                // application may understand them.
3✔
445
                if parseResult == nil || t%2 != 0 ||
3✔
446
                        t >= record.CustomTypeStart {
6✔
447

3✔
448
                        continue
3✔
449
                }
450

451
                if !requiredViolation || t < minRequiredViolationType {
×
452
                        minRequiredViolationType = t
×
453
                }
×
454
                requiredViolation = true
×
455
        }
456

457
        if requiredViolation {
3✔
458
                return &minRequiredViolationType
×
459
        }
×
460

461
        return nil
3✔
462
}
463

464
// ValidateBlindedRouteData performs the additional validation that is
465
// required for payments that rely on data provided in an encrypted blob to
466
// be forwarded. We enforce the blinded route's maximum expiry height so that
467
// the route "expires" and a malicious party does not have endless opportunity
468
// to probe the blinded route and compare it to updated channel policies in
469
// the network.
470
func ValidateBlindedRouteData(blindedData *record.BlindedRouteData,
471
        incomingAmount lnwire.MilliSatoshi, incomingTimelock uint32) error {
3✔
472

3✔
473
        // Bolt 04 notes that we should enforce payment constraints _if_ they
3✔
474
        // are present, so we do not fail if not provided.
3✔
475
        var err error
3✔
476
        blindedData.Constraints.WhenSome(
3✔
477
                func(c tlv.RecordT[tlv.TlvType12, record.PaymentConstraints]) {
6✔
478
                        // MUST fail if the expiry is greater than
3✔
479
                        // max_cltv_expiry.
3✔
480
                        if incomingTimelock > c.Val.MaxCltvExpiry {
3✔
481
                                err = ErrInvalidPayload{
×
482
                                        Type:      record.LockTimeOnionType,
×
483
                                        Violation: InsufficientViolation,
×
484
                                }
×
485
                        }
×
486

487
                        // MUST fail if the amount is below htlc_minimum_msat.
488
                        if incomingAmount < c.Val.HtlcMinimumMsat {
3✔
489
                                err = ErrInvalidPayload{
×
490
                                        Type:      record.AmtOnionType,
×
491
                                        Violation: InsufficientViolation,
×
492
                                }
×
493
                        }
×
494
                },
495
        )
496
        if err != nil {
3✔
497
                return err
×
498
        }
×
499

500
        // Fail if we don't understand any features (even or odd), because we
501
        // expect the features to have been set from our announcement. If the
502
        // feature vector TLV is not included, it's interpreted as an empty
503
        // vector (no validation required).
504
        // expect the features to have been set from our announcement.
505
        //
506
        // Note that we do not yet check the features that the blinded payment
507
        // is using against our own features, because there are currently no
508
        // payment-related features that they utilize other than tlv-onion,
509
        // which is implicitly supported.
510
        blindedData.Features.WhenSome(
3✔
511
                func(f tlv.RecordT[tlv.TlvType14, lnwire.FeatureVector]) {
3✔
512
                        if f.Val.UnknownFeatures() {
×
513
                                err = ErrInvalidPayload{
×
514
                                        Type:      14,
×
515
                                        Violation: IncludedViolation,
×
516
                                }
×
517
                        }
×
518
                },
519
        )
520
        if err != nil {
3✔
521
                return err
×
522
        }
×
523

524
        return nil
3✔
525
}
526

527
// ValidatePayloadWithBlinded validates a payload against the contents of
528
// its encrypted data blob.
529
func ValidatePayloadWithBlinded(isFinalHop bool,
530
        payloadParsed map[tlv.Type][]byte) error {
3✔
531

3✔
532
        // Blinded routes restrict the presence of TLVs more strictly than
3✔
533
        // regular routes, check that intermediate and final hops only have
3✔
534
        // the TLVs the spec allows them to have.
3✔
535
        allowedTLVs := map[tlv.Type]bool{
3✔
536
                record.EncryptedDataOnionType: true,
3✔
537
                record.BlindingPointOnionType: true,
3✔
538
        }
3✔
539

3✔
540
        if isFinalHop {
6✔
541
                allowedTLVs[record.AmtOnionType] = true
3✔
542
                allowedTLVs[record.LockTimeOnionType] = true
3✔
543
                allowedTLVs[record.TotalAmtMsatBlindedType] = true
3✔
544
        }
3✔
545

546
        for tlvType := range payloadParsed {
6✔
547
                if _, ok := allowedTLVs[tlvType]; ok {
6✔
548
                        continue
3✔
549
                }
550

551
                return ErrInvalidPayload{
×
552
                        Type:      tlvType,
×
553
                        Violation: IncludedViolation,
×
554
                        FinalHop:  isFinalHop,
×
555
                }
×
556
        }
557

558
        return nil
3✔
559
}
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