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

lightningnetwork / lnd / 13211764208

08 Feb 2025 03:08AM UTC coverage: 49.288% (-9.5%) from 58.815%
13211764208

Pull #9489

github

calvinrzachman
itest: verify switchrpc server enforces send then track

We prevent the rpc server from allowing onion dispatches for
attempt IDs which have already been tracked by rpc clients.

This helps protect the client from leaking a duplicate onion
attempt. NOTE: This is not the only method for solving this
issue! The issue could be addressed via careful client side
programming which accounts for the uncertainty and async
nature of dispatching onions to a remote process via RPC.
This would require some lnd ChannelRouter changes for how
we intend to use these RPCs though.
Pull Request #9489: multi: add BuildOnion, SendOnion, and TrackOnion RPCs

474 of 990 new or added lines in 11 files covered. (47.88%)

27321 existing lines in 435 files now uncovered.

101192 of 205306 relevant lines covered (49.29%)

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.
UNCOV
124
func NewLegacyPayload(f *sphinx.HopData) *Payload {
×
UNCOV
125
        nextHop := binary.BigEndian.Uint64(f.NextAddress[:])
×
UNCOV
126

×
UNCOV
127
        return &Payload{
×
UNCOV
128
                FwdInfo: ForwardingInfo{
×
UNCOV
129
                        NextHop:         lnwire.NewShortChanIDFromInt(nextHop),
×
UNCOV
130
                        AmountToForward: lnwire.MilliSatoshi(f.ForwardAmount),
×
UNCOV
131
                        OutgoingCTLV:    f.OutgoingCltv,
×
UNCOV
132
                },
×
UNCOV
133
                customRecords: make(record.CustomSet),
×
UNCOV
134
        }
×
UNCOV
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✔
UNCOV
229
                return err
×
UNCOV
230
        }
×
231

232
        // Check for violation of the rules for mandatory fields.
233
        violatingType := getMinRequiredViolation(parsedTypes)
3✔
234
        if violatingType != nil {
3✔
UNCOV
235
                return ErrInvalidPayload{
×
UNCOV
236
                        Type:      *violatingType,
×
UNCOV
237
                        Violation: RequiredViolation,
×
UNCOV
238
                        FinalHop:  finalHop,
×
UNCOV
239
                }
×
UNCOV
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.
UNCOV
289
        case hasBlinding && updateAddBlinding:
×
UNCOV
290
                return ErrInvalidPayload{
×
UNCOV
291
                        Type:      record.BlindingPointOnionType,
×
UNCOV
292
                        Violation: IncludedViolation,
×
UNCOV
293
                        FinalHop:  isFinalHop,
×
UNCOV
294
                }
×
295

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

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

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

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

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

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

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

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

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

371
        // Intermediate nodes should never receive AMP fields.
UNCOV
372
        case !isFinalHop && hasAMP:
×
UNCOV
373
                return ErrInvalidPayload{
×
UNCOV
374
                        Type:      record.AMPOnionType,
×
UNCOV
375
                        Violation: IncludedViolation,
×
UNCOV
376
                        FinalHop:  isFinalHop,
×
UNCOV
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.
UNCOV
403
func (h *Payload) EncryptedData() []byte {
×
UNCOV
404
        return h.encryptedData
×
UNCOV
405
}
×
406

407
// BlindingPoint returns the route blinding point parsed from the onion payload.
UNCOV
408
func (h *Payload) BlindingPoint() *btcec.PublicKey {
×
UNCOV
409
        return h.blindingPoint
×
UNCOV
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

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

457
        if requiredViolation {
3✔
UNCOV
458
                return &minRequiredViolationType
×
UNCOV
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✔
UNCOV
481
                                err = ErrInvalidPayload{
×
UNCOV
482
                                        Type:      record.LockTimeOnionType,
×
UNCOV
483
                                        Violation: InsufficientViolation,
×
UNCOV
484
                                }
×
UNCOV
485
                        }
×
486

487
                        // MUST fail if the amount is below htlc_minimum_msat.
488
                        if incomingAmount < c.Val.HtlcMinimumMsat {
3✔
UNCOV
489
                                err = ErrInvalidPayload{
×
UNCOV
490
                                        Type:      record.AmtOnionType,
×
UNCOV
491
                                        Violation: InsufficientViolation,
×
UNCOV
492
                                }
×
UNCOV
493
                        }
×
494
                },
495
        )
496
        if err != nil {
3✔
UNCOV
497
                return err
×
UNCOV
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✔
UNCOV
512
                        if f.Val.UnknownFeatures() {
×
UNCOV
513
                                err = ErrInvalidPayload{
×
UNCOV
514
                                        Type:      14,
×
UNCOV
515
                                        Violation: IncludedViolation,
×
UNCOV
516
                                }
×
UNCOV
517
                        }
×
518
                },
519
        )
520
        if err != nil {
3✔
UNCOV
521
                return err
×
UNCOV
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

UNCOV
551
                return ErrInvalidPayload{
×
UNCOV
552
                        Type:      tlvType,
×
UNCOV
553
                        Violation: IncludedViolation,
×
UNCOV
554
                        FinalHop:  isFinalHop,
×
UNCOV
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