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

lightningnetwork / lnd / 16263831630

14 Jul 2025 10:00AM UTC coverage: 67.315% (-0.02%) from 67.339%
16263831630

Pull #10072

github

web-flow
Merge 480167d37 into 5bb227774
Pull Request #10072: [1/2] lnwire: fix encoding customized TLV records

265 of 275 new or added lines in 8 files covered. (96.36%)

101 existing lines in 19 files now uncovered.

135293 of 200984 relevant lines covered (67.32%)

21801.32 hits per line

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

64.29
/lnwire/channel_update_2.go
1
package lnwire
2

3
import (
4
        "bytes"
5
        "fmt"
6
        "io"
7

8
        "github.com/btcsuite/btcd/chaincfg"
9
        "github.com/btcsuite/btcd/chaincfg/chainhash"
10
        "github.com/lightningnetwork/lnd/tlv"
11
)
12

13
const (
14
        defaultCltvExpiryDelta           = uint16(80)
15
        defaultHtlcMinMsat               = MilliSatoshi(1)
16
        defaultFeeBaseMsat               = uint32(1000)
17
        defaultFeeProportionalMillionths = uint32(1)
18
)
19

20
// ChannelUpdate2 message is used after taproot channel has been initially
21
// announced. Each side independently announces its fees and minimum expiry for
22
// HTLCs and other parameters. This message is also used to redeclare initially
23
// set channel parameters.
24
type ChannelUpdate2 struct {
25
        // Signature is used to validate the announced data and prove the
26
        // ownership of node id.
27
        Signature Sig
28

29
        // ChainHash denotes the target chain that this channel was opened
30
        // within. This value should be the genesis hash of the target chain.
31
        // Along with the short channel ID, this uniquely identifies the
32
        // channel globally in a blockchain.
33
        ChainHash tlv.RecordT[tlv.TlvType0, chainhash.Hash]
34

35
        // ShortChannelID is the unique description of the funding transaction.
36
        ShortChannelID tlv.RecordT[tlv.TlvType2, ShortChannelID]
37

38
        // BlockHeight allows ordering in the case of multiple announcements. We
39
        // should ignore the message if block height is not greater than the
40
        // last-received. The block height must always be greater or equal to
41
        // the block height that the channel funding transaction was confirmed
42
        // in.
43
        BlockHeight tlv.RecordT[tlv.TlvType4, uint32]
44

45
        // DisabledFlags is an optional bitfield that describes various reasons
46
        // that the node is communicating that the channel should be considered
47
        // disabled.
48
        DisabledFlags tlv.RecordT[tlv.TlvType6, ChanUpdateDisableFlags]
49

50
        // SecondPeer is used to indicate which node the channel node has
51
        // created and signed this message. If this field is present, it was
52
        // node 2 otherwise it was node 1.
53
        SecondPeer tlv.OptionalRecordT[tlv.TlvType8, TrueBoolean]
54

55
        // CLTVExpiryDelta is the minimum number of blocks this node requires to
56
        // be added to the expiry of HTLCs. This is a security parameter
57
        // determined by the node operator. This value represents the required
58
        // gap between the time locks of the incoming and outgoing HTLC's set
59
        // to this node.
60
        CLTVExpiryDelta tlv.RecordT[tlv.TlvType10, uint16]
61

62
        // HTLCMinimumMsat is the minimum HTLC value which will be accepted.
63
        HTLCMinimumMsat tlv.RecordT[tlv.TlvType12, MilliSatoshi]
64

65
        // HtlcMaximumMsat is the maximum HTLC value which will be accepted.
66
        HTLCMaximumMsat tlv.RecordT[tlv.TlvType14, MilliSatoshi]
67

68
        // FeeBaseMsat is the base fee that must be used for incoming HTLC's to
69
        // this particular channel. This value will be tacked onto the required
70
        // for a payment independent of the size of the payment.
71
        FeeBaseMsat tlv.RecordT[tlv.TlvType16, uint32]
72

73
        // FeeProportionalMillionths is the fee rate that will be charged per
74
        // millionth of a satoshi.
75
        FeeProportionalMillionths tlv.RecordT[tlv.TlvType18, uint32]
76

77
        // ExtraOpaqueData is the set of data that was appended to this message
78
        // to fill out the full maximum transport message size. These fields can
79
        // be used to specify optional data such as custom TLV fields.
80
        ExtraOpaqueData ExtraOpaqueData
81
}
82

83
// Decode deserializes a serialized ChannelUpdate2 stored in the passed
84
// io.Reader observing the specified protocol version.
85
//
86
// This is part of the lnwire.Message interface.
87
func (c *ChannelUpdate2) Decode(r io.Reader, _ uint32) error {
224✔
88
        err := ReadElement(r, &c.Signature)
224✔
89
        if err != nil {
226✔
90
                return err
2✔
91
        }
2✔
92
        c.Signature.ForceSchnorr()
222✔
93

222✔
94
        return c.DecodeTLVRecords(r)
222✔
95
}
96

97
// DecodeTLVRecords decodes only the TLV section of the message.
98
func (c *ChannelUpdate2) DecodeTLVRecords(r io.Reader) error {
222✔
99
        // First extract into extra opaque data.
222✔
100
        var tlvRecords ExtraOpaqueData
222✔
101
        if err := ReadElement(r, &tlvRecords); err != nil {
222✔
102
                return err
×
103
        }
×
104

105
        var (
222✔
106
                chainHash  = tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
222✔
107
                secondPeer = tlv.ZeroRecordT[tlv.TlvType8, TrueBoolean]()
222✔
108
        )
222✔
109
        knownRecords, extraData, err := ParseAndExtractExtraData(
222✔
110
                tlvRecords, &chainHash, &c.ShortChannelID, &c.BlockHeight,
222✔
111
                &c.DisabledFlags, &secondPeer, &c.CLTVExpiryDelta,
222✔
112
                &c.HTLCMinimumMsat, &c.HTLCMaximumMsat, &c.FeeBaseMsat,
222✔
113
                &c.FeeProportionalMillionths,
222✔
114
        )
222✔
115
        if err != nil {
269✔
116
                return err
47✔
117
        }
47✔
118

119
        // By default, the chain-hash is the bitcoin mainnet genesis block hash.
120
        c.ChainHash.Val = *chaincfg.MainNetParams.GenesisHash
175✔
121
        if _, ok := knownRecords[c.ChainHash.TlvType()]; ok {
288✔
122
                c.ChainHash.Val = chainHash.Val
113✔
123
        }
113✔
124

125
        // The presence of the second_peer tlv type indicates "true".
126
        if _, ok := knownRecords[c.SecondPeer.TlvType()]; ok {
235✔
127
                c.SecondPeer = tlv.SomeRecordT(secondPeer)
60✔
128
        }
60✔
129

130
        // If the CLTV expiry delta was not encoded, then set it to the default
131
        // value.
132
        if _, ok := knownRecords[c.CLTVExpiryDelta.TlvType()]; !ok {
238✔
133
                c.CLTVExpiryDelta.Val = defaultCltvExpiryDelta
63✔
134
        }
63✔
135

136
        // If the HTLC Minimum msat was not encoded, then set it to the default
137
        // value.
138
        if _, ok := knownRecords[c.HTLCMinimumMsat.TlvType()]; !ok {
237✔
139
                c.HTLCMinimumMsat.Val = defaultHtlcMinMsat
62✔
140
        }
62✔
141

142
        // If the base fee was not encoded, then set it to the default value.
143
        if _, ok := knownRecords[c.FeeBaseMsat.TlvType()]; !ok {
242✔
144
                c.FeeBaseMsat.Val = defaultFeeBaseMsat
67✔
145
        }
67✔
146

147
        // If the proportional fee was not encoded, then set it to the default
148
        // value.
149
        if _, ok := knownRecords[c.FeeProportionalMillionths.TlvType()]; !ok {
253✔
150
                c.FeeProportionalMillionths.Val = defaultFeeProportionalMillionths //nolint:ll
78✔
151
        }
78✔
152

153
        c.ExtraOpaqueData = extraData
175✔
154

175✔
155
        return c.ExtraOpaqueData.ValidateTLV()
175✔
156
}
157

158
// Encode serializes the target ChannelUpdate2 into the passed io.Writer
159
// observing the protocol version specified.
160
//
161
// This is part of the lnwire.Message interface.
162
func (c *ChannelUpdate2) Encode(w *bytes.Buffer, _ uint32) error {
138✔
163
        _, err := w.Write(c.Signature.RawBytes())
138✔
164
        if err != nil {
138✔
165
                return err
×
166
        }
×
167

168
        tlvRecords, err := c.DataToSign()
138✔
169
        if err != nil {
138✔
170
                return err
×
171
        }
×
172

173
        return WriteBytes(w, tlvRecords)
138✔
174
}
175

176
// DataToSign is used to retrieve part of the announcement message which should
177
// be signed. For the ChannelUpdate2 message, this includes the serialised TLV
178
// records.
179
func (c *ChannelUpdate2) DataToSign() ([]byte, error) {
138✔
180
        producers, err := c.ExtraOpaqueData.RecordProducers()
138✔
181
        if err != nil {
138✔
NEW
182
                return nil, err
×
NEW
183
        }
×
184

185
        // The chain-hash record is only included if it is _not_ equal to the
186
        // bitcoin mainnet genisis block hash.
187
        if !c.ChainHash.Val.IsEqual(chaincfg.MainNetParams.GenesisHash) {
245✔
188
                hash := tlv.ZeroRecordT[tlv.TlvType0, [32]byte]()
107✔
189
                hash.Val = c.ChainHash.Val
107✔
190

107✔
191
                producers = append(producers, &hash)
107✔
192
        }
107✔
193

194
        producers = append(producers,
138✔
195
                &c.ShortChannelID, &c.BlockHeight,
138✔
196
        )
138✔
197

138✔
198
        // Only include the disable flags if any bit is set.
138✔
199
        if !c.DisabledFlags.Val.IsEnabled() {
209✔
200
                producers = append(producers, &c.DisabledFlags)
71✔
201
        }
71✔
202

203
        // We only need to encode the second peer boolean if it is true
204
        c.SecondPeer.WhenSome(func(r tlv.RecordT[tlv.TlvType8, TrueBoolean]) {
195✔
205
                producers = append(producers, &r)
57✔
206
        })
57✔
207

208
        // We only encode the cltv expiry delta if it is not equal to the
209
        // default.
210
        if c.CLTVExpiryDelta.Val != defaultCltvExpiryDelta {
244✔
211
                producers = append(producers, &c.CLTVExpiryDelta)
106✔
212
        }
106✔
213

214
        if c.HTLCMinimumMsat.Val != defaultHtlcMinMsat {
239✔
215
                producers = append(producers, &c.HTLCMinimumMsat)
101✔
216
        }
101✔
217

218
        producers = append(producers, &c.HTLCMaximumMsat)
138✔
219

138✔
220
        if c.FeeBaseMsat.Val != defaultFeeBaseMsat {
242✔
221
                producers = append(producers, &c.FeeBaseMsat)
104✔
222
        }
104✔
223

224
        if c.FeeProportionalMillionths.Val != defaultFeeProportionalMillionths {
233✔
225
                producers = append(
95✔
226
                        producers, &c.FeeProportionalMillionths,
95✔
227
                )
95✔
228
        }
95✔
229

230
        var tlvData ExtraOpaqueData
138✔
231
        err = tlvData.PackRecords(producers...)
138✔
232
        if err != nil {
138✔
233
                return nil, err
×
234
        }
×
235

236
        return tlvData, nil
138✔
237
}
238

239
// MsgType returns the integer uniquely identifying this message type on the
240
// wire.
241
//
242
// This is part of the lnwire.Message interface.
243
func (c *ChannelUpdate2) MsgType() MessageType {
137✔
244
        return MsgChannelUpdate2
137✔
245
}
137✔
246

247
// SerializedSize returns the serialized size of the message in bytes.
248
//
249
// This is part of the lnwire.SizeableMessage interface.
250
func (c *ChannelUpdate2) SerializedSize() (uint32, error) {
×
251
        return MessageSerializedSize(c)
×
252
}
×
253

254
func (c *ChannelUpdate2) ExtraData() ExtraOpaqueData {
×
255
        return c.ExtraOpaqueData
×
256
}
×
257

258
// A compile time check to ensure ChannelUpdate2 implements the
259
// lnwire.Message interface.
260
var _ Message = (*ChannelUpdate2)(nil)
261

262
// SCID returns the ShortChannelID of the channel that the update applies to.
263
//
264
// NOTE: this is part of the ChannelUpdate interface.
265
func (c *ChannelUpdate2) SCID() ShortChannelID {
×
266
        return c.ShortChannelID.Val
×
267
}
×
268

269
// IsNode1 is true if the update was produced by node 1 of the channel peers.
270
// Node 1 is the node with the lexicographically smaller public key.
271
//
272
// NOTE: this is part of the ChannelUpdate interface.
273
func (c *ChannelUpdate2) IsNode1() bool {
×
274
        return c.SecondPeer.IsNone()
×
275
}
×
276

277
// IsDisabled is true if the update is announcing that the channel should be
278
// considered disabled.
279
//
280
// NOTE: this is part of the ChannelUpdate interface.
281
func (c *ChannelUpdate2) IsDisabled() bool {
×
282
        return !c.DisabledFlags.Val.IsEnabled()
×
283
}
×
284

285
// GetChainHash returns the hash of the chain that the message is referring to.
286
//
287
// NOTE: this is part of the ChannelUpdate interface.
288
func (c *ChannelUpdate2) GetChainHash() chainhash.Hash {
×
289
        return c.ChainHash.Val
×
290
}
×
291

292
// ForwardingPolicy returns the set of forwarding constraints of the update.
293
//
294
// NOTE: this is part of the ChannelUpdate interface.
295
func (c *ChannelUpdate2) ForwardingPolicy() *ForwardingPolicy {
×
296
        return &ForwardingPolicy{
×
297
                TimeLockDelta: c.CLTVExpiryDelta.Val,
×
298
                BaseFee:       MilliSatoshi(c.FeeBaseMsat.Val),
×
299
                FeeRate:       MilliSatoshi(c.FeeProportionalMillionths.Val),
×
300
                MinHTLC:       c.HTLCMinimumMsat.Val,
×
301
                HasMaxHTLC:    true,
×
302
                MaxHTLC:       c.HTLCMaximumMsat.Val,
×
303
        }
×
304
}
×
305

306
// CmpAge can be used to determine if the update is older or newer than the
307
// passed update. It returns 1 if this update is newer, -1 if it is older, and
308
// 0 if they are the same age.
309
//
310
// NOTE: this is part of the ChannelUpdate interface.
311
func (c *ChannelUpdate2) CmpAge(update ChannelUpdate) (CompareResult, error) {
×
312
        other, ok := update.(*ChannelUpdate2)
×
313
        if !ok {
×
314
                return 0, fmt.Errorf("expected *ChannelUpdate2, got: %T",
×
315
                        update)
×
316
        }
×
317

318
        switch {
×
319
        case c.BlockHeight.Val > other.BlockHeight.Val:
×
320
                return GreaterThan, nil
×
321
        case c.BlockHeight.Val < other.BlockHeight.Val:
×
322
                return LessThan, nil
×
323
        default:
×
324
                return EqualTo, nil
×
325
        }
326
}
327

328
// SetDisabledFlag can be used to adjust the disabled flag of an update.
329
//
330
// NOTE: this is part of the ChannelUpdate interface.
331
func (c *ChannelUpdate2) SetDisabledFlag(disabled bool) {
×
332
        if disabled {
×
333
                c.DisabledFlags.Val |= ChanUpdateDisableIncoming
×
334
                c.DisabledFlags.Val |= ChanUpdateDisableOutgoing
×
335
        } else {
×
336
                c.DisabledFlags.Val &^= ChanUpdateDisableIncoming
×
337
                c.DisabledFlags.Val &^= ChanUpdateDisableOutgoing
×
338
        }
×
339
}
340

341
// SetSCID can be used to overwrite the SCID of the update.
342
//
343
// NOTE: this is part of the ChannelUpdate interface.
344
func (c *ChannelUpdate2) SetSCID(scid ShortChannelID) {
×
345
        c.ShortChannelID.Val = scid
×
346
}
×
347

348
// A compile time check to ensure ChannelUpdate2 implements the
349
// lnwire.ChannelUpdate interface.
350
var _ ChannelUpdate = (*ChannelUpdate2)(nil)
351

352
// ChanUpdateDisableFlags is a bit vector that can be used to indicate various
353
// reasons for the channel being marked as disabled.
354
type ChanUpdateDisableFlags uint8
355

356
const (
357
        // ChanUpdateDisableIncoming is a bit indicates that a channel is
358
        // disabled in the inbound direction meaning that the node broadcasting
359
        // the update is communicating that they cannot receive funds.
360
        ChanUpdateDisableIncoming ChanUpdateDisableFlags = 1 << iota
361

362
        // ChanUpdateDisableOutgoing is a bit indicates that a channel is
363
        // disabled in the outbound direction meaning that the node broadcasting
364
        // the update is communicating that they cannot send or route funds.
365
        ChanUpdateDisableOutgoing = 2
366
)
367

368
// IncomingDisabled returns true if the ChanUpdateDisableIncoming bit is set.
369
func (c ChanUpdateDisableFlags) IncomingDisabled() bool {
×
370
        return c&ChanUpdateDisableIncoming == ChanUpdateDisableIncoming
×
371
}
×
372

373
// OutgoingDisabled returns true if the ChanUpdateDisableOutgoing bit is set.
374
func (c ChanUpdateDisableFlags) OutgoingDisabled() bool {
×
375
        return c&ChanUpdateDisableOutgoing == ChanUpdateDisableOutgoing
×
376
}
×
377

378
// IsEnabled returns true if none of the disable bits are set.
379
func (c ChanUpdateDisableFlags) IsEnabled() bool {
138✔
380
        return c == 0
138✔
381
}
138✔
382

383
// String returns the bitfield flags as a string.
384
func (c ChanUpdateDisableFlags) String() string {
×
385
        return fmt.Sprintf("%08b", c)
×
386
}
×
387

388
// Record returns the tlv record for the disable flags.
389
func (c *ChanUpdateDisableFlags) Record() tlv.Record {
468✔
390
        return tlv.MakeStaticRecord(0, c, 1, encodeDisableFlags,
468✔
391
                decodeDisableFlags)
468✔
392
}
468✔
393

394
func encodeDisableFlags(w io.Writer, val interface{}, buf *[8]byte) error {
71✔
395
        if v, ok := val.(*ChanUpdateDisableFlags); ok {
142✔
396
                flagsInt := uint8(*v)
71✔
397

71✔
398
                return tlv.EUint8(w, &flagsInt, buf)
71✔
399
        }
71✔
400

401
        return tlv.NewTypeForEncodingErr(val, "lnwire.ChanUpdateDisableFlags")
×
402
}
403

404
func decodeDisableFlags(r io.Reader, val interface{}, buf *[8]byte,
405
        l uint64) error {
75✔
406

75✔
407
        if v, ok := val.(*ChanUpdateDisableFlags); ok {
150✔
408
                var flagsInt uint8
75✔
409
                err := tlv.DUint8(r, &flagsInt, buf, l)
75✔
410
                if err != nil {
77✔
411
                        return err
2✔
412
                }
2✔
413

414
                *v = ChanUpdateDisableFlags(flagsInt)
73✔
415

73✔
416
                return nil
73✔
417
        }
418

419
        return tlv.NewTypeForDecodingErr(val, "lnwire.ChanUpdateDisableFlags",
×
420
                l, l)
×
421
}
422

423
// TrueBoolean is a record that indicates true or false using the presence of
424
// the record. If the record is absent, it indicates false. If it is present,
425
// it indicates true.
426
type TrueBoolean struct{}
427

428
// Record returns the tlv record for the boolean entry.
429
func (b *TrueBoolean) Record() tlv.Record {
547✔
430
        return tlv.MakeStaticRecord(
547✔
431
                0, b, 0, booleanEncoder, booleanDecoder,
547✔
432
        )
547✔
433
}
547✔
434

435
func booleanEncoder(_ io.Writer, val interface{}, _ *[8]byte) error {
96✔
436
        if _, ok := val.(*TrueBoolean); ok {
192✔
437
                return nil
96✔
438
        }
96✔
439

440
        return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
×
441
}
442

443
func booleanDecoder(_ io.Reader, val interface{}, _ *[8]byte,
444
        l uint64) error {
84✔
445

84✔
446
        if _, ok := val.(*TrueBoolean); ok && (l == 0 || l == 1) {
167✔
447
                return nil
83✔
448
        }
83✔
449

450
        return tlv.NewTypeForEncodingErr(val, "TrueBoolean")
1✔
451
}
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