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

lightningnetwork / lnd / 15838907453

24 Jun 2025 01:26AM UTC coverage: 57.079% (-11.1%) from 68.172%
15838907453

Pull #9982

github

web-flow
Merge e42780be2 into 45c15646c
Pull Request #9982: lnwire+lnwallet: add LocalNonces field for splice nonce coordination w/ taproot channels

103 of 167 new or added lines in 5 files covered. (61.68%)

30191 existing lines in 463 files now uncovered.

96331 of 168768 relevant lines covered (57.08%)

0.6 hits per line

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

71.89
/channeldb/revocation_log.go
1
package channeldb
2

3
import (
4
        "bytes"
5
        "encoding/binary"
6
        "errors"
7
        "io"
8
        "math"
9

10
        "github.com/btcsuite/btcd/btcutil"
11
        "github.com/lightningnetwork/lnd/fn/v2"
12
        "github.com/lightningnetwork/lnd/kvdb"
13
        "github.com/lightningnetwork/lnd/lntypes"
14
        "github.com/lightningnetwork/lnd/lnwire"
15
        "github.com/lightningnetwork/lnd/tlv"
16
)
17

18
const (
19
        // OutputIndexEmpty is used when the output index doesn't exist.
20
        OutputIndexEmpty = math.MaxUint16
21
)
22

23
type (
24
        // BigSizeAmount is a type alias for a TLV record of a btcutil.Amount.
25
        BigSizeAmount = tlv.BigSizeT[btcutil.Amount]
26

27
        // BigSizeMilliSatoshi is a type alias for a TLV record of a
28
        // lnwire.MilliSatoshi.
29
        BigSizeMilliSatoshi = tlv.BigSizeT[lnwire.MilliSatoshi]
30
)
31

32
var (
33
        // revocationLogBucketDeprecated is dedicated for storing the necessary
34
        // delta state between channel updates required to re-construct a past
35
        // state in order to punish a counterparty attempting a non-cooperative
36
        // channel closure. This key should be accessed from within the
37
        // sub-bucket of a target channel, identified by its channel point.
38
        //
39
        // Deprecated: This bucket is kept for read-only in case the user
40
        // choose not to migrate the old data.
41
        revocationLogBucketDeprecated = []byte("revocation-log-key")
42

43
        // revocationLogBucket is a sub-bucket under openChannelBucket. This
44
        // sub-bucket is dedicated for storing the minimal info required to
45
        // re-construct a past state in order to punish a counterparty
46
        // attempting a non-cooperative channel closure.
47
        revocationLogBucket = []byte("revocation-log")
48

49
        // ErrLogEntryNotFound is returned when we cannot find a log entry at
50
        // the height requested in the revocation log.
51
        ErrLogEntryNotFound = errors.New("log entry not found")
52

53
        // ErrOutputIndexTooBig is returned when the output index is greater
54
        // than uint16.
55
        ErrOutputIndexTooBig = errors.New("output index is over uint16")
56
)
57

58
// SparsePayHash is a type alias for a 32 byte array, which when serialized is
59
// able to save some space by not including an empty payment hash on disk.
60
type SparsePayHash [32]byte
61

62
// NewSparsePayHash creates a new SparsePayHash from a 32 byte array.
63
func NewSparsePayHash(rHash [32]byte) SparsePayHash {
1✔
64
        return SparsePayHash(rHash)
1✔
65
}
1✔
66

67
// Record returns a tlv record for the SparsePayHash.
68
func (s *SparsePayHash) Record() tlv.Record {
1✔
69
        // We use a zero for the type here, as this'll be used along with the
1✔
70
        // RecordT type.
1✔
71
        return tlv.MakeDynamicRecord(
1✔
72
                0, s, s.hashLen,
1✔
73
                sparseHashEncoder, sparseHashDecoder,
1✔
74
        )
1✔
75
}
1✔
76

77
// hashLen is used by MakeDynamicRecord to return the size of the RHash.
78
//
79
// NOTE: for zero hash, we return a length 0.
80
func (s *SparsePayHash) hashLen() uint64 {
1✔
81
        if bytes.Equal(s[:], lntypes.ZeroHash[:]) {
1✔
UNCOV
82
                return 0
×
UNCOV
83
        }
×
84

85
        return 32
1✔
86
}
87

88
// sparseHashEncoder is the customized encoder which skips encoding the empty
89
// hash.
90
func sparseHashEncoder(w io.Writer, val interface{}, buf *[8]byte) error {
1✔
91
        v, ok := val.(*SparsePayHash)
1✔
92
        if !ok {
1✔
93
                return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
×
94
        }
×
95

96
        // If the value is an empty hash, we will skip encoding it.
97
        if bytes.Equal(v[:], lntypes.ZeroHash[:]) {
1✔
UNCOV
98
                return nil
×
UNCOV
99
        }
×
100

101
        vArray := (*[32]byte)(v)
1✔
102

1✔
103
        return tlv.EBytes32(w, vArray, buf)
1✔
104
}
105

106
// sparseHashDecoder is the customized decoder which skips decoding the empty
107
// hash.
108
func sparseHashDecoder(r io.Reader, val interface{}, buf *[8]byte,
109
        l uint64) error {
1✔
110

1✔
111
        v, ok := val.(*SparsePayHash)
1✔
112
        if !ok {
1✔
113
                return tlv.NewTypeForEncodingErr(val, "SparsePayHash")
×
114
        }
×
115

116
        // If the length is zero, we will skip encoding the empty hash.
117
        if l == 0 {
1✔
UNCOV
118
                return nil
×
UNCOV
119
        }
×
120

121
        vArray := (*[32]byte)(v)
1✔
122

1✔
123
        return tlv.DBytes32(r, vArray, buf, 32)
1✔
124
}
125

126
// HTLCEntry specifies the minimal info needed to be stored on disk for ALL the
127
// historical HTLCs, which is useful for constructing RevocationLog when a
128
// breach is detected.
129
// The actual size of each HTLCEntry varies based on its RHash and Amt(sat),
130
// summarized as follows,
131
//
132
//        | RHash empty | Amt<=252 | Amt<=65,535 | Amt<=4,294,967,295 | otherwise |
133
//        |:-----------:|:--------:|:-----------:|:------------------:|:---------:|
134
//        |     true    |    19    |      21     |         23         |     26    |
135
//        |     false   |    51    |      53     |         55         |     58    |
136
//
137
// So the size varies from 19 bytes to 58 bytes, where most likely to be 23 or
138
// 55 bytes.
139
//
140
// NOTE: all the fields saved to disk use the primitive go types so they can be
141
// made into tlv records without further conversion.
142
type HTLCEntry struct {
143
        // RHash is the payment hash of the HTLC.
144
        RHash tlv.RecordT[tlv.TlvType0, SparsePayHash]
145

146
        // RefundTimeout is the absolute timeout on the HTLC that the sender
147
        // must wait before reclaiming the funds in limbo.
148
        RefundTimeout tlv.RecordT[tlv.TlvType1, uint32]
149

150
        // OutputIndex is the output index for this particular HTLC output
151
        // within the commitment transaction.
152
        //
153
        // NOTE: we use uint16 instead of int32 here to save us 2 bytes, which
154
        // gives us a max number of HTLCs of 65K.
155
        OutputIndex tlv.RecordT[tlv.TlvType2, uint16]
156

157
        // Incoming denotes whether we're the receiver or the sender of this
158
        // HTLC.
159
        Incoming tlv.RecordT[tlv.TlvType3, bool]
160

161
        // Amt is the amount of satoshis this HTLC escrows.
162
        Amt tlv.RecordT[tlv.TlvType4, tlv.BigSizeT[btcutil.Amount]]
163

164
        // CustomBlob is an optional blob that can be used to store information
165
        // specific to revocation handling for a custom channel type.
166
        CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
167

168
        // HtlcIndex is the index of the HTLC in the channel.
169
        HtlcIndex tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]]
170
}
171

172
// toTlvStream converts an HTLCEntry record into a tlv representation.
173
func (h *HTLCEntry) toTlvStream() (*tlv.Stream, error) {
1✔
174
        records := []tlv.Record{
1✔
175
                h.RHash.Record(),
1✔
176
                h.RefundTimeout.Record(),
1✔
177
                h.OutputIndex.Record(),
1✔
178
                h.Incoming.Record(),
1✔
179
                h.Amt.Record(),
1✔
180
        }
1✔
181

1✔
182
        h.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
2✔
183
                records = append(records, r.Record())
1✔
184
        })
1✔
185

186
        h.HtlcIndex.WhenSome(func(r tlv.RecordT[tlv.TlvType6,
1✔
187
                tlv.BigSizeT[uint64]]) {
2✔
188

1✔
189
                records = append(records, r.Record())
1✔
190
        })
1✔
191

192
        tlv.SortRecords(records)
1✔
193

1✔
194
        return tlv.NewStream(records...)
1✔
195
}
196

197
// NewHTLCEntryFromHTLC creates a new HTLCEntry from an HTLC.
198
func NewHTLCEntryFromHTLC(htlc HTLC) (*HTLCEntry, error) {
1✔
199
        h := &HTLCEntry{
1✔
200
                RHash: tlv.NewRecordT[tlv.TlvType0](
1✔
201
                        NewSparsePayHash(htlc.RHash),
1✔
202
                ),
1✔
203
                RefundTimeout: tlv.NewPrimitiveRecord[tlv.TlvType1](
1✔
204
                        htlc.RefundTimeout,
1✔
205
                ),
1✔
206
                OutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType2](
1✔
207
                        uint16(htlc.OutputIndex),
1✔
208
                ),
1✔
209
                Incoming: tlv.NewPrimitiveRecord[tlv.TlvType3](htlc.Incoming),
1✔
210
                Amt: tlv.NewRecordT[tlv.TlvType4](
1✔
211
                        tlv.NewBigSizeT(htlc.Amt.ToSatoshis()),
1✔
212
                ),
1✔
213
                HtlcIndex: tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6](
1✔
214
                        tlv.NewBigSizeT(htlc.HtlcIndex),
1✔
215
                )),
1✔
216
        }
1✔
217

1✔
218
        if len(htlc.CustomRecords) != 0 {
2✔
219
                blob, err := htlc.CustomRecords.Serialize()
1✔
220
                if err != nil {
1✔
221
                        return nil, err
×
222
                }
×
223

224
                h.CustomBlob = tlv.SomeRecordT(
1✔
225
                        tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
1✔
226
                )
1✔
227
        }
228

229
        return h, nil
1✔
230
}
231

232
// RevocationLog stores the info needed to construct a breach retribution. Its
233
// fields can be viewed as a subset of a ChannelCommitment's. In the database,
234
// all historical versions of the RevocationLog are saved using the
235
// CommitHeight as the key.
236
type RevocationLog struct {
237
        // OurOutputIndex specifies our output index in this commitment. In a
238
        // remote commitment transaction, this is the to remote output index.
239
        OurOutputIndex tlv.RecordT[tlv.TlvType0, uint16]
240

241
        // TheirOutputIndex specifies their output index in this commitment. In
242
        // a remote commitment transaction, this is the to local output index.
243
        TheirOutputIndex tlv.RecordT[tlv.TlvType1, uint16]
244

245
        // CommitTxHash is the hash of the latest version of the commitment
246
        // state, broadcast able by us.
247
        CommitTxHash tlv.RecordT[tlv.TlvType2, [32]byte]
248

249
        // HTLCEntries is the set of HTLCEntry's that are pending at this
250
        // particular commitment height.
251
        HTLCEntries []*HTLCEntry
252

253
        // OurBalance is the current available balance within the channel
254
        // directly spendable by us. In other words, it is the value of the
255
        // to_remote output on the remote parties' commitment transaction.
256
        //
257
        // NOTE: this is an option so that it is clear if the value is zero or
258
        // nil. Since migration 30 of the channeldb initially did not include
259
        // this field, it could be the case that the field is not present for
260
        // all revocation logs.
261
        OurBalance tlv.OptionalRecordT[tlv.TlvType3, BigSizeMilliSatoshi]
262

263
        // TheirBalance is the current available balance within the channel
264
        // directly spendable by the remote node. In other words, it is the
265
        // value of the to_local output on the remote parties' commitment.
266
        //
267
        // NOTE: this is an option so that it is clear if the value is zero or
268
        // nil. Since migration 30 of the channeldb initially did not include
269
        // this field, it could be the case that the field is not present for
270
        // all revocation logs.
271
        TheirBalance tlv.OptionalRecordT[tlv.TlvType4, BigSizeMilliSatoshi]
272

273
        // CustomBlob is an optional blob that can be used to store information
274
        // specific to a custom channel type. This information is only created
275
        // at channel funding time, and after wards is to be considered
276
        // immutable.
277
        CustomBlob tlv.OptionalRecordT[tlv.TlvType5, tlv.Blob]
278
}
279

280
// NewRevocationLog creates a new RevocationLog from the given parameters.
281
func NewRevocationLog(ourOutputIndex uint16, theirOutputIndex uint16,
282
        commitHash [32]byte, ourBalance,
283
        theirBalance fn.Option[lnwire.MilliSatoshi], htlcs []*HTLCEntry,
UNCOV
284
        customBlob fn.Option[tlv.Blob]) RevocationLog {
×
UNCOV
285

×
UNCOV
286
        rl := RevocationLog{
×
UNCOV
287
                OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
×
UNCOV
288
                        ourOutputIndex,
×
UNCOV
289
                ),
×
UNCOV
290
                TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
×
UNCOV
291
                        theirOutputIndex,
×
UNCOV
292
                ),
×
UNCOV
293
                CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2](commitHash),
×
UNCOV
294
                HTLCEntries:  htlcs,
×
UNCOV
295
        }
×
UNCOV
296

×
UNCOV
297
        ourBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
×
UNCOV
298
                rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
×
UNCOV
299
                        tlv.NewBigSizeT(balance),
×
UNCOV
300
                ))
×
UNCOV
301
        })
×
302

UNCOV
303
        theirBalance.WhenSome(func(balance lnwire.MilliSatoshi) {
×
UNCOV
304
                rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
×
UNCOV
305
                        tlv.NewBigSizeT(balance),
×
UNCOV
306
                ))
×
UNCOV
307
        })
×
308

UNCOV
309
        customBlob.WhenSome(func(blob tlv.Blob) {
×
UNCOV
310
                rl.CustomBlob = tlv.SomeRecordT(
×
UNCOV
311
                        tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
×
UNCOV
312
                )
×
UNCOV
313
        })
×
314

UNCOV
315
        return rl
×
316
}
317

318
// putRevocationLog uses the fields `CommitTx` and `Htlcs` from a
319
// ChannelCommitment to construct a revocation log entry and saves them to
320
// disk. It also saves our output index and their output index, which are
321
// useful when creating breach retribution.
322
func putRevocationLog(bucket kvdb.RwBucket, commit *ChannelCommitment,
323
        ourOutputIndex, theirOutputIndex uint32, noAmtData bool) error {
1✔
324

1✔
325
        // Sanity check that the output indexes can be safely converted.
1✔
326
        if ourOutputIndex > math.MaxUint16 {
1✔
UNCOV
327
                return ErrOutputIndexTooBig
×
UNCOV
328
        }
×
329
        if theirOutputIndex > math.MaxUint16 {
1✔
UNCOV
330
                return ErrOutputIndexTooBig
×
UNCOV
331
        }
×
332

333
        rl := &RevocationLog{
1✔
334
                OurOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType0](
1✔
335
                        uint16(ourOutputIndex),
1✔
336
                ),
1✔
337
                TheirOutputIndex: tlv.NewPrimitiveRecord[tlv.TlvType1](
1✔
338
                        uint16(theirOutputIndex),
1✔
339
                ),
1✔
340
                CommitTxHash: tlv.NewPrimitiveRecord[tlv.TlvType2, [32]byte](
1✔
341
                        commit.CommitTx.TxHash(),
1✔
342
                ),
1✔
343
                HTLCEntries: make([]*HTLCEntry, 0, len(commit.Htlcs)),
1✔
344
        }
1✔
345

1✔
346
        commit.CustomBlob.WhenSome(func(blob tlv.Blob) {
1✔
UNCOV
347
                rl.CustomBlob = tlv.SomeRecordT(
×
UNCOV
348
                        tlv.NewPrimitiveRecord[tlv.TlvType5, tlv.Blob](blob),
×
UNCOV
349
                )
×
UNCOV
350
        })
×
351

352
        if !noAmtData {
2✔
353
                rl.OurBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType3](
1✔
354
                        tlv.NewBigSizeT(commit.LocalBalance),
1✔
355
                ))
1✔
356

1✔
357
                rl.TheirBalance = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType4](
1✔
358
                        tlv.NewBigSizeT(commit.RemoteBalance),
1✔
359
                ))
1✔
360
        }
1✔
361

362
        for _, htlc := range commit.Htlcs {
2✔
363
                // Skip dust HTLCs.
1✔
364
                if htlc.OutputIndex < 0 {
2✔
365
                        continue
1✔
366
                }
367

368
                // Sanity check that the output indexes can be safely
369
                // converted.
370
                if htlc.OutputIndex > math.MaxUint16 {
1✔
UNCOV
371
                        return ErrOutputIndexTooBig
×
UNCOV
372
                }
×
373

374
                entry, err := NewHTLCEntryFromHTLC(htlc)
1✔
375
                if err != nil {
1✔
376
                        return err
×
377
                }
×
378
                rl.HTLCEntries = append(rl.HTLCEntries, entry)
1✔
379
        }
380

381
        var b bytes.Buffer
1✔
382
        err := serializeRevocationLog(&b, rl)
1✔
383
        if err != nil {
1✔
384
                return err
×
385
        }
×
386

387
        logEntrykey := makeLogKey(commit.CommitHeight)
1✔
388
        return bucket.Put(logEntrykey[:], b.Bytes())
1✔
389
}
390

391
// fetchRevocationLog queries the revocation log bucket to find an log entry.
392
// Return an error if not found.
393
func fetchRevocationLog(log kvdb.RBucket,
394
        updateNum uint64) (RevocationLog, error) {
1✔
395

1✔
396
        logEntrykey := makeLogKey(updateNum)
1✔
397
        commitBytes := log.Get(logEntrykey[:])
1✔
398
        if commitBytes == nil {
2✔
399
                return RevocationLog{}, ErrLogEntryNotFound
1✔
400
        }
1✔
401

402
        commitReader := bytes.NewReader(commitBytes)
1✔
403

1✔
404
        return deserializeRevocationLog(commitReader)
1✔
405
}
406

407
// serializeRevocationLog serializes a RevocationLog record based on tlv
408
// format.
409
func serializeRevocationLog(w io.Writer, rl *RevocationLog) error {
1✔
410
        // Add the tlv records for all non-optional fields.
1✔
411
        records := []tlv.Record{
1✔
412
                rl.OurOutputIndex.Record(),
1✔
413
                rl.TheirOutputIndex.Record(),
1✔
414
                rl.CommitTxHash.Record(),
1✔
415
        }
1✔
416

1✔
417
        // Now we add any optional fields that are non-nil.
1✔
418
        rl.OurBalance.WhenSome(
1✔
419
                func(r tlv.RecordT[tlv.TlvType3, BigSizeMilliSatoshi]) {
2✔
420
                        records = append(records, r.Record())
1✔
421
                },
1✔
422
        )
423

424
        rl.TheirBalance.WhenSome(
1✔
425
                func(r tlv.RecordT[tlv.TlvType4, BigSizeMilliSatoshi]) {
2✔
426
                        records = append(records, r.Record())
1✔
427
                },
1✔
428
        )
429

430
        rl.CustomBlob.WhenSome(func(r tlv.RecordT[tlv.TlvType5, tlv.Blob]) {
1✔
UNCOV
431
                records = append(records, r.Record())
×
UNCOV
432
        })
×
433

434
        // Create the tlv stream.
435
        tlvStream, err := tlv.NewStream(records...)
1✔
436
        if err != nil {
1✔
437
                return err
×
438
        }
×
439

440
        // Write the tlv stream.
441
        if err := writeTlvStream(w, tlvStream); err != nil {
1✔
442
                return err
×
443
        }
×
444

445
        // Write the HTLCs.
446
        return serializeHTLCEntries(w, rl.HTLCEntries)
1✔
447
}
448

449
// serializeHTLCEntries serializes a list of HTLCEntry records based on tlv
450
// format.
451
func serializeHTLCEntries(w io.Writer, htlcs []*HTLCEntry) error {
1✔
452
        for _, htlc := range htlcs {
2✔
453
                // Create the tlv stream.
1✔
454
                tlvStream, err := htlc.toTlvStream()
1✔
455
                if err != nil {
1✔
456
                        return err
×
457
                }
×
458

459
                // Write the tlv stream.
460
                if err := writeTlvStream(w, tlvStream); err != nil {
1✔
461
                        return err
×
462
                }
×
463
        }
464

465
        return nil
1✔
466
}
467

468
// deserializeRevocationLog deserializes a RevocationLog based on tlv format.
469
func deserializeRevocationLog(r io.Reader) (RevocationLog, error) {
1✔
470
        var rl RevocationLog
1✔
471

1✔
472
        ourBalance := rl.OurBalance.Zero()
1✔
473
        theirBalance := rl.TheirBalance.Zero()
1✔
474
        customBlob := rl.CustomBlob.Zero()
1✔
475

1✔
476
        // Create the tlv stream.
1✔
477
        tlvStream, err := tlv.NewStream(
1✔
478
                rl.OurOutputIndex.Record(),
1✔
479
                rl.TheirOutputIndex.Record(),
1✔
480
                rl.CommitTxHash.Record(),
1✔
481
                ourBalance.Record(),
1✔
482
                theirBalance.Record(),
1✔
483
                customBlob.Record(),
1✔
484
        )
1✔
485
        if err != nil {
1✔
486
                return rl, err
×
487
        }
×
488

489
        // Read the tlv stream.
490
        parsedTypes, err := readTlvStream(r, tlvStream)
1✔
491
        if err != nil {
1✔
492
                return rl, err
×
493
        }
×
494

495
        if t, ok := parsedTypes[ourBalance.TlvType()]; ok && t == nil {
2✔
496
                rl.OurBalance = tlv.SomeRecordT(ourBalance)
1✔
497
        }
1✔
498

499
        if t, ok := parsedTypes[theirBalance.TlvType()]; ok && t == nil {
2✔
500
                rl.TheirBalance = tlv.SomeRecordT(theirBalance)
1✔
501
        }
1✔
502

503
        if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
1✔
UNCOV
504
                rl.CustomBlob = tlv.SomeRecordT(customBlob)
×
UNCOV
505
        }
×
506

507
        // Read the HTLC entries.
508
        rl.HTLCEntries, err = deserializeHTLCEntries(r)
1✔
509

1✔
510
        return rl, err
1✔
511
}
512

513
// deserializeHTLCEntries deserializes a list of HTLC entries based on tlv
514
// format.
515
func deserializeHTLCEntries(r io.Reader) ([]*HTLCEntry, error) {
1✔
516
        var (
1✔
517
                htlcs []*HTLCEntry
1✔
518

1✔
519
                // htlcIndexBlob defines the tlv record type to be used when
1✔
520
                // decoding from the disk. We use it instead of the one defined
1✔
521
                // in `HTLCEntry.HtlcIndex` as previously this field was encoded
1✔
522
                // using `uint16`, thus we will read it as raw bytes and
1✔
523
                // deserialize it further below.
1✔
524
                htlcIndexBlob tlv.OptionalRecordT[tlv.TlvType6, tlv.Blob]
1✔
525
        )
1✔
526

1✔
527
        for {
2✔
528
                var htlc HTLCEntry
1✔
529

1✔
530
                customBlob := htlc.CustomBlob.Zero()
1✔
531
                htlcIndex := htlcIndexBlob.Zero()
1✔
532

1✔
533
                // Create the tlv stream.
1✔
534
                records := []tlv.Record{
1✔
535
                        htlc.RHash.Record(),
1✔
536
                        htlc.RefundTimeout.Record(),
1✔
537
                        htlc.OutputIndex.Record(),
1✔
538
                        htlc.Incoming.Record(),
1✔
539
                        htlc.Amt.Record(),
1✔
540
                        customBlob.Record(),
1✔
541
                        htlcIndex.Record(),
1✔
542
                }
1✔
543

1✔
544
                tlvStream, err := tlv.NewStream(records...)
1✔
545
                if err != nil {
1✔
546
                        return nil, err
×
547
                }
×
548

549
                // Read the HTLC entry.
550
                parsedTypes, err := readTlvStream(r, tlvStream)
1✔
551
                if err != nil {
2✔
552
                        // We've reached the end when hitting an EOF.
1✔
553
                        if err == io.ErrUnexpectedEOF {
2✔
554
                                break
1✔
555
                        }
556
                        return nil, err
×
557
                }
558

559
                if t, ok := parsedTypes[customBlob.TlvType()]; ok && t == nil {
2✔
560
                        htlc.CustomBlob = tlv.SomeRecordT(customBlob)
1✔
561
                }
1✔
562

563
                if t, ok := parsedTypes[htlcIndex.TlvType()]; ok && t == nil {
2✔
564
                        record, err := deserializeHtlcIndexCompatible(
1✔
565
                                htlcIndex.Val,
1✔
566
                        )
1✔
567
                        if err != nil {
1✔
568
                                return nil, err
×
569
                        }
×
570

571
                        htlc.HtlcIndex = record
1✔
572
                }
573

574
                // Append the entry.
575
                htlcs = append(htlcs, &htlc)
1✔
576
        }
577

578
        return htlcs, nil
1✔
579
}
580

581
// deserializeHtlcIndexCompatible takes raw bytes and decodes it into an
582
// optional record that's assigned to the entry's HtlcIndex.
583
//
584
// NOTE: previously this `HtlcIndex` was a tlv record that used `uint16` to
585
// encode its value. Given now its value is encoded using BigSizeT, and for any
586
// BigSizeT, its possible length values are 1, 3, 5, and 8. This means if the
587
// tlv record has a length of 2, we know for sure it must be an old record
588
// whose value was encoded using uint16.
589
func deserializeHtlcIndexCompatible(rawBytes []byte) (
590
        tlv.OptionalRecordT[tlv.TlvType6, tlv.BigSizeT[uint64]], error) {
1✔
591

1✔
592
        var (
1✔
593
                // record defines the record that's used by the HtlcIndex in the
1✔
594
                // entry.
1✔
595
                record tlv.OptionalRecordT[
1✔
596
                        tlv.TlvType6, tlv.BigSizeT[uint64],
1✔
597
                ]
1✔
598

1✔
599
                // htlcIndexVal is the decoded uint64 value.
1✔
600
                htlcIndexVal uint64
1✔
601
        )
1✔
602

1✔
603
        // If the length of the tlv record is 2, it must be encoded using uint16
1✔
604
        // as the BigSizeT encoding cannot have this length.
1✔
605
        if len(rawBytes) == 2 {
1✔
UNCOV
606
                // Decode the raw bytes into uint16 and convert it into uint64.
×
UNCOV
607
                htlcIndexVal = uint64(binary.BigEndian.Uint16(rawBytes))
×
608
        } else {
1✔
609
                // This value is encoded using BigSizeT, we now use the decoder
1✔
610
                // to deserialize the raw bytes.
1✔
611
                r := bytes.NewBuffer(rawBytes)
1✔
612

1✔
613
                // Create a buffer to be used in the decoding process.
1✔
614
                buf := [8]byte{}
1✔
615

1✔
616
                // Use the BigSizeT's decoder.
1✔
617
                err := tlv.DBigSize(r, &htlcIndexVal, &buf, 8)
1✔
618
                if err != nil {
1✔
619
                        return record, err
×
620
                }
×
621
        }
622

623
        record = tlv.SomeRecordT(tlv.NewRecordT[tlv.TlvType6](
1✔
624
                tlv.NewBigSizeT(htlcIndexVal),
1✔
625
        ))
1✔
626

1✔
627
        return record, nil
1✔
628
}
629

630
// writeTlvStream is a helper function that encodes the tlv stream into the
631
// writer.
632
func writeTlvStream(w io.Writer, s *tlv.Stream) error {
1✔
633
        var b bytes.Buffer
1✔
634
        if err := s.Encode(&b); err != nil {
1✔
635
                return err
×
636
        }
×
637

638
        // Write the stream's length as a varint.
639
        err := tlv.WriteVarInt(w, uint64(b.Len()), &[8]byte{})
1✔
640
        if err != nil {
1✔
641
                return err
×
642
        }
×
643

644
        if _, err = w.Write(b.Bytes()); err != nil {
1✔
645
                return err
×
646
        }
×
647

648
        return nil
1✔
649
}
650

651
// readTlvStream is a helper function that decodes the tlv stream from the
652
// reader.
653
func readTlvStream(r io.Reader, s *tlv.Stream) (tlv.TypeMap, error) {
1✔
654
        var bodyLen uint64
1✔
655

1✔
656
        // Read the stream's length.
1✔
657
        bodyLen, err := tlv.ReadVarInt(r, &[8]byte{})
1✔
658
        switch {
1✔
659
        // We'll convert any EOFs to ErrUnexpectedEOF, since this results in an
660
        // invalid record.
661
        case err == io.EOF:
1✔
662
                return nil, io.ErrUnexpectedEOF
1✔
663

664
        // Other unexpected errors.
665
        case err != nil:
×
666
                return nil, err
×
667
        }
668

669
        // TODO(yy): add overflow check.
670
        lr := io.LimitReader(r, int64(bodyLen))
1✔
671

1✔
672
        return s.DecodeWithParsedTypes(lr)
1✔
673
}
674

675
// fetchOldRevocationLog finds the revocation log from the deprecated
676
// sub-bucket.
677
func fetchOldRevocationLog(log kvdb.RBucket,
UNCOV
678
        updateNum uint64) (ChannelCommitment, error) {
×
UNCOV
679

×
UNCOV
680
        logEntrykey := makeLogKey(updateNum)
×
UNCOV
681
        commitBytes := log.Get(logEntrykey[:])
×
UNCOV
682
        if commitBytes == nil {
×
UNCOV
683
                return ChannelCommitment{}, ErrLogEntryNotFound
×
UNCOV
684
        }
×
685

UNCOV
686
        commitReader := bytes.NewReader(commitBytes)
×
UNCOV
687
        return deserializeChanCommit(commitReader)
×
688
}
689

690
// fetchRevocationLogCompatible finds the revocation log from both the
691
// revocationLogBucket and revocationLogBucketDeprecated for compatibility
692
// concern. It returns three values,
693
//   - RevocationLog, if this is non-nil, it means we've found the log in the
694
//     new bucket.
695
//   - ChannelCommitment, if this is non-nil, it means we've found the log in the
696
//     old bucket.
697
//   - error, this can happen if the log cannot be found in neither buckets.
698
func fetchRevocationLogCompatible(chanBucket kvdb.RBucket,
699
        updateNum uint64) (*RevocationLog, *ChannelCommitment, error) {
1✔
700

1✔
701
        // Look into the new bucket first.
1✔
702
        logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
1✔
703
        if logBucket != nil {
2✔
704
                rl, err := fetchRevocationLog(logBucket, updateNum)
1✔
705
                // We've found the record, no need to visit the old bucket.
1✔
706
                if err == nil {
2✔
707
                        return &rl, nil, nil
1✔
708
                }
1✔
709

710
                // Return the error if it doesn't say the log cannot be found.
711
                if err != ErrLogEntryNotFound {
1✔
712
                        return nil, nil, err
×
713
                }
×
714
        }
715

716
        // Otherwise, look into the old bucket and try to find the log there.
717
        oldBucket := chanBucket.NestedReadBucket(revocationLogBucketDeprecated)
1✔
718
        if oldBucket != nil {
1✔
UNCOV
719
                c, err := fetchOldRevocationLog(oldBucket, updateNum)
×
UNCOV
720
                if err != nil {
×
UNCOV
721
                        return nil, nil, err
×
UNCOV
722
                }
×
723

724
                // Found an old record and return it.
UNCOV
725
                return nil, &c, nil
×
726
        }
727

728
        // If both the buckets are nil, then the sub-buckets haven't been
729
        // created yet.
730
        if logBucket == nil && oldBucket == nil {
2✔
731
                return nil, nil, ErrNoPastDeltas
1✔
732
        }
1✔
733

734
        // Otherwise, we've tried to query the new bucket but the log cannot be
735
        // found.
736
        return nil, nil, ErrLogEntryNotFound
1✔
737
}
738

739
// fetchLogBucket returns a read bucket by visiting both the new and the old
740
// bucket.
UNCOV
741
func fetchLogBucket(chanBucket kvdb.RBucket) (kvdb.RBucket, error) {
×
UNCOV
742
        logBucket := chanBucket.NestedReadBucket(revocationLogBucket)
×
UNCOV
743
        if logBucket == nil {
×
UNCOV
744
                logBucket = chanBucket.NestedReadBucket(
×
UNCOV
745
                        revocationLogBucketDeprecated,
×
UNCOV
746
                )
×
UNCOV
747
                if logBucket == nil {
×
UNCOV
748
                        return nil, ErrNoPastDeltas
×
UNCOV
749
                }
×
750
        }
751

UNCOV
752
        return logBucket, nil
×
753
}
754

755
// deleteLogBucket deletes the both the new and old revocation log buckets.
756
func deleteLogBucket(chanBucket kvdb.RwBucket) error {
1✔
757
        // Check if the bucket exists and delete it.
1✔
758
        logBucket := chanBucket.NestedReadWriteBucket(
1✔
759
                revocationLogBucket,
1✔
760
        )
1✔
761
        if logBucket != nil {
2✔
762
                err := chanBucket.DeleteNestedBucket(revocationLogBucket)
1✔
763
                if err != nil {
1✔
764
                        return err
×
765
                }
×
766
        }
767

768
        // We also check whether the old revocation log bucket exists
769
        // and delete it if so.
770
        oldLogBucket := chanBucket.NestedReadWriteBucket(
1✔
771
                revocationLogBucketDeprecated,
1✔
772
        )
1✔
773
        if oldLogBucket != nil {
1✔
UNCOV
774
                err := chanBucket.DeleteNestedBucket(
×
UNCOV
775
                        revocationLogBucketDeprecated,
×
UNCOV
776
                )
×
UNCOV
777
                if err != nil {
×
778
                        return err
×
779
                }
×
780
        }
781

782
        return nil
1✔
783
}
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